mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1299 lines
44 KiB
C++
1299 lines
44 KiB
C++
/**
|
|
* StudioIntellesenseFrame.cpp
|
|
* Copyright (c) 2013 ROBLOX Corp. All Rights Reserved.
|
|
* Created by Tyler Berg on 10/29/2013
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
// Qt Headers
|
|
#include <Qt>
|
|
#include <QWidget>
|
|
#include <QObject>
|
|
#include <QResource>
|
|
#include <QtCore>
|
|
#include <QDesktopWidget>
|
|
|
|
//Roblox Headers
|
|
#include "script/ScriptContext.h"
|
|
#include "util/UDim.h"
|
|
#include "Util/RobloxGoogleAnalytics.h"
|
|
#include "Util/PhysicalProperties.h"
|
|
|
|
// Roblox Studio Headers
|
|
#include "StudioIntellesense.h"
|
|
#include "QtUtilities.h"
|
|
#include "RobloxIDEDoc.h"
|
|
#include "RobloxDocManager.h"
|
|
#include "AuthoringSettings.h"
|
|
#include "UpdateUIManager.h"
|
|
#include "RobloxMainWindow.h"
|
|
#include "RobloxScriptDoc.h"
|
|
|
|
FASTFLAG(GoogleAnalyticsTrackingEnabled)
|
|
FASTFLAG(StudioVariableIntellesense)
|
|
|
|
FASTFLAGVARIABLE(StudioIntellisenseCodeExecutionFixEnabled, true)
|
|
FASTFLAGVARIABLE(StudioIntellesenseShowNonDeprecatedMembers, true)
|
|
FASTFLAGVARIABLE(StudioIntellisenseAllowUnderscore, false)
|
|
|
|
#define OB_IMAGE_SIZE 16
|
|
#define OBJECT_BROWSER_IMAGES ":/images/img_classtree.bmp"
|
|
|
|
#define MAX_CHILDREN 100
|
|
|
|
#define MAX_ITEMS_SHOWN 5
|
|
#define BASE_FRAME_WIDTH 250
|
|
|
|
#define AUTOCOMPLETE_FRAME_MARGIN 2
|
|
#define SCREEN_EDGE_MARGIN 6
|
|
|
|
namespace Studio {
|
|
|
|
Intellesense::Intellesense()
|
|
: m_focusProxy(NULL)
|
|
, m_menuBelow(true)
|
|
, m_cursorHeight(0)
|
|
{
|
|
m_AutoCompleteFrame = new QFrame();
|
|
m_AutoCompleteFrame->resize(QSize(1,1));
|
|
|
|
m_AutoCompleteFrame->setAttribute(Qt::WA_ShowWithoutActivating);
|
|
m_AutoCompleteFrame->setFocusPolicy(Qt::NoFocus);
|
|
m_AutoCompleteFrame->setWindowFlags(Qt::WindowStaysOnTopHint | Qt::SubWindow | Qt::FramelessWindowHint);
|
|
|
|
m_AutoCompleteFrame->show();
|
|
m_AutoCompleteFrame->setVisible(false);
|
|
|
|
m_AutoCompleteMenu = new QListWidget(m_AutoCompleteFrame);
|
|
m_AutoCompleteMenu->setAttribute(Qt::WA_ShowWithoutActivating);
|
|
m_AutoCompleteMenu->setFocusPolicy(Qt::NoFocus);
|
|
m_AutoCompleteMenu->viewport()->setFocusPolicy(Qt::NoFocus);
|
|
|
|
connect(m_AutoCompleteMenu, SIGNAL(itemClicked(QListWidgetItem*)),
|
|
this, SLOT(singleClick(QListWidgetItem*)));
|
|
|
|
connect(m_AutoCompleteMenu, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
|
|
this, SLOT(doubleClick(QListWidgetItem*)));
|
|
|
|
loadIntelleSenseMetadata();
|
|
|
|
qApp->installEventFilter(this);
|
|
}
|
|
|
|
Intellesense& Intellesense::singleton()
|
|
{
|
|
static Intellesense* studioIntellesenseFrame = new Intellesense();
|
|
return *studioIntellesenseFrame;
|
|
}
|
|
|
|
bool Intellesense::activate(QString &text, int row, int &cursorIndex, QKeyEvent* keyEvent,
|
|
QRect positionRange, bool searchDatamodel, QWidget *focusWidget,
|
|
const boost::optional<std::vector<RBX::ScriptAnalyzer::IntellesenseResult> >& result)
|
|
{
|
|
m_focusProxy = focusWidget;
|
|
|
|
m_cursorHeight = positionRange.height();
|
|
|
|
int key = keyEvent->key();
|
|
Qt::KeyboardModifiers modifiers = keyEvent->modifiers();
|
|
|
|
if (modifiers == Qt::ControlModifier)
|
|
return false;
|
|
|
|
if (key == Qt::Key_Period && modifiers != Qt::ShiftModifier)
|
|
{
|
|
text.insert(cursorIndex, '.');
|
|
cursorIndex++;
|
|
int itemFlags = populateItemLists(text, row, cursorIndex, searchDatamodel, result);
|
|
handleAutoCompleteMenu(positionRange, itemFlags, getCurrentWord(text, cursorIndex));
|
|
return false;
|
|
}
|
|
else if (key == Qt::Key_Colon || (key == Qt::Key_Semicolon && modifiers == Qt::ShiftModifier))
|
|
{
|
|
text.insert(cursorIndex, ':');
|
|
cursorIndex++;
|
|
populateItemLists(text, row, cursorIndex, searchDatamodel, result);
|
|
handleAutoCompleteMenu(positionRange, FUNCTION_ITEM, getCurrentWord(text, cursorIndex));
|
|
return false;
|
|
}
|
|
else if (((QChar)key).isLetter() || (FFlag::StudioVariableIntellesense && ((QChar)key).isNumber()) ||
|
|
(FFlag::StudioIntellisenseAllowUnderscore && key == Qt::Key_Minus && modifiers == Qt::ShiftModifier))
|
|
{
|
|
QChar keyEventCharacter = (QChar)key; //Remove this with removal of FFlag::StudioIntellisenseAllowUnderscore
|
|
if (modifiers != Qt::ShiftModifier)
|
|
keyEventCharacter = keyEventCharacter.toLower();
|
|
|
|
text.insert(cursorIndex, FFlag::StudioIntellisenseAllowUnderscore ? keyEvent->text() : keyEventCharacter);
|
|
cursorIndex++;
|
|
int itemFlags = populateItemLists(text, row, cursorIndex, searchDatamodel, result);
|
|
handleAutoCompleteMenu(positionRange, itemFlags, getCurrentWord(text, cursorIndex));
|
|
return false;
|
|
}
|
|
else if (key == Qt::Key_Backtab)
|
|
{
|
|
int itemFlags = populateItemLists(text, row, cursorIndex, searchDatamodel, result);
|
|
return handleAutoCompleteMenu(positionRange, itemFlags, getCurrentWord(text, cursorIndex));
|
|
}
|
|
|
|
if (!isActive())
|
|
return false;
|
|
|
|
if (key == Qt::Key_Up)
|
|
{
|
|
//Move selection up
|
|
if (m_AutoCompleteMenu->currentRow() > 0)
|
|
m_AutoCompleteMenu->setCurrentRow(m_AutoCompleteMenu->currentRow() - 1);
|
|
showToolTip();
|
|
return true;
|
|
}
|
|
else if (key == Qt::Key_Down)
|
|
{
|
|
//Move selection down
|
|
if (m_AutoCompleteMenu->currentRow() < m_AutoCompleteMenu->count() - 1)
|
|
m_AutoCompleteMenu->setCurrentRow(qMin(m_AutoCompleteMenu->count() - 1, m_AutoCompleteMenu->currentRow() + 1));
|
|
showToolTip();
|
|
return true;
|
|
}
|
|
else if (key == Qt::Key_Tab || key == Qt::Key_Return)
|
|
{
|
|
replaceTextWithCurrentItem(text, cursorIndex);
|
|
return true;
|
|
}
|
|
else if (key == Qt::Key_Backspace)
|
|
{
|
|
cursorIndex--;
|
|
text = text.remove(cursorIndex, 1);
|
|
positionRange.setX(positionRange.x() - 12);
|
|
int itemFlags = populateItemLists(text, row, cursorIndex, searchDatamodel, result);
|
|
handleAutoCompleteMenu(positionRange, itemFlags, getCurrentWord(text, cursorIndex));
|
|
return false;
|
|
}
|
|
else if (key == Qt::Key_Shift || key == Qt::Key_Alt)
|
|
{
|
|
return false;
|
|
}
|
|
deactivate();
|
|
return false;
|
|
}
|
|
|
|
void Intellesense::deactivate()
|
|
{
|
|
m_focusProxy = NULL;
|
|
m_AutoCompleteFrame->setVisible(false);
|
|
IntellesenseTooltip::singleton().hideText();
|
|
}
|
|
|
|
bool Intellesense::isActive()
|
|
{
|
|
return m_AutoCompleteFrame->isVisible();
|
|
}
|
|
|
|
void escapeSequence(QString& str)
|
|
{
|
|
int stringSize = str.size();
|
|
|
|
while (stringSize--)
|
|
{
|
|
switch (str.at(stringSize).toAscii())
|
|
{
|
|
case '"': case '\\': case '\n': case '\r':
|
|
str.insert(stringSize, "\\");
|
|
break;
|
|
case '\0':
|
|
str.remove(stringSize, 1);
|
|
str.insert(stringSize, "\\000");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Intellesense::replaceTextWithCurrentItem(QString& text, int& cursorIndex)
|
|
{
|
|
if (FFlag::GoogleAnalyticsTrackingEnabled)
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_ACTION, "IntellesenseTextReplace");
|
|
|
|
QListWidgetItem* currentItem = m_AutoCompleteMenu->currentItem();
|
|
|
|
if (!currentItem)
|
|
return false;
|
|
|
|
eItemType typeOfItem = (eItemType)(currentItem->data(TYPE_ROLE).toInt());
|
|
|
|
if (typeOfItem == NO_ITEM)
|
|
{
|
|
int itemFlags = populateItemLists(text, cursorIndex, true, true, boost::none);
|
|
handleAutoCompleteMenu(QRect(), itemFlags, "", m_AutoCompleteMenu->currentRow());
|
|
return false;
|
|
}
|
|
|
|
deactivate();
|
|
|
|
QString replacementText = currentItem->text();
|
|
|
|
bool invalidFormat = false;
|
|
|
|
if (replacementText.contains(QRegExp("[^a-zA-Z0-9_]")) || replacementText.contains(QRegExp("^[0-9]+$")))
|
|
invalidFormat = true;
|
|
|
|
if (typeOfItem == FUNCTION_ITEM)
|
|
replacementText.append("()");
|
|
|
|
if (invalidFormat)
|
|
{
|
|
escapeSequence(replacementText);
|
|
replacementText = QString("[\"%1\"]").arg(replacementText);
|
|
}
|
|
|
|
//Replace
|
|
int offset = cursorIndex - text.size() - 1;
|
|
QString previousText = text.left(text.lastIndexOf(QRegExp(FFlag::StudioVariableIntellesense ? "[^a-zA-Z0-9_]" : "[\\.\\:]"), offset) + 1);
|
|
QString postText = text.right(text.size() - cursorIndex);
|
|
|
|
if (invalidFormat)
|
|
previousText = previousText.left(previousText.size() - 1);
|
|
|
|
text = previousText + replacementText + postText;
|
|
|
|
cursorIndex = previousText.length() + replacementText.length();
|
|
|
|
if (typeOfItem == FUNCTION_ITEM)
|
|
cursorIndex--;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Intellesense::setStyleSheet(const QString& styleSheet)
|
|
{
|
|
m_AutoCompleteMenu->setStyleSheet(styleSheet);
|
|
m_AutoCompleteFrame->setStyleSheet(styleSheet);
|
|
IntellesenseTooltip::singleton().setStyleSheet(styleSheet);
|
|
QWidget::setStyleSheet(styleSheet);
|
|
}
|
|
|
|
QString Intellesense::getPreviousWord(const QString& text, int index)
|
|
{
|
|
int pos = index + 1;
|
|
QStringList stringList = text.split(QRegExp(FFlag::StudioIntellisenseAllowUnderscore ? "[^a-zA-Z\\d\\.\\:\\_]" : "[^a-zA-Z\\d\\.\\:]"));
|
|
|
|
for (QStringList::iterator i = stringList.begin(); i != stringList.end(); ++i)
|
|
{
|
|
pos -= i->size() + 1;
|
|
|
|
if (pos <= 0)
|
|
return i->left(i->lastIndexOf(QRegExp("[\\.\\:]")));
|
|
}
|
|
return QString("");
|
|
}
|
|
|
|
QString Intellesense::getCurrentWord(const QString& text, int index)
|
|
{
|
|
int pos = index + 1;
|
|
QStringList stringList = text.split(QRegExp(FFlag::StudioIntellisenseAllowUnderscore ? "[^a-zA-Z\\d\\_]" : "[^a-zA-Z\\d]"));
|
|
|
|
for (QStringList::iterator i = stringList.begin(); i != stringList.end(); ++i)
|
|
{
|
|
pos -= i->size() + 1;
|
|
|
|
if (pos <= 0)
|
|
return *i;
|
|
}
|
|
return QString("");
|
|
}
|
|
|
|
bool Intellesense::isAfterDeclarationKeyword(const QString& text, int index)
|
|
{
|
|
QStringList stringList = text.split(QRegExp("\\s"));
|
|
|
|
bool previousWordIsKeyword = false;
|
|
int pos = 0;
|
|
|
|
for (QStringList::iterator iter = stringList.begin(); iter != stringList.end(); ++iter)
|
|
{
|
|
QString token = *iter;
|
|
pos += token.length();
|
|
|
|
if (index <= pos)
|
|
return previousWordIsKeyword;
|
|
|
|
previousWordIsKeyword = (token == "local" || token == "function");
|
|
|
|
++pos;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Intellesense::loadIntelleSenseMetadata()
|
|
{
|
|
QResource metaDataResource(":/IntelleSenseMetadata.xml");
|
|
QByteArray metaDataByteArray((const char*)metaDataResource.data(),(int)metaDataResource.size());
|
|
|
|
metaDataByteArray = qUncompress(metaDataByteArray);
|
|
|
|
m_intelleSenseMetadata = QDomDocument("IntelleSenseMetadata");
|
|
m_intelleSenseMetadata.setContent(metaDataByteArray, false);
|
|
}
|
|
|
|
bool Intellesense::alphabeticalQListWidgetItemSort(QListWidgetItem i, QListWidgetItem j)
|
|
{
|
|
return i.text().compare(j.text()) < 0;
|
|
}
|
|
|
|
bool Intellesense::isEnumerator(QString previousWord)
|
|
{
|
|
if (previousWord == "Enum")
|
|
{
|
|
for (std::vector<const RBX::Reflection::EnumDescriptor*>::const_iterator i = RBX::Reflection::EnumDescriptor::enumsBegin();
|
|
i != RBX::Reflection::EnumDescriptor::enumsEnd();
|
|
++i)
|
|
{
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(**i);
|
|
if (RBX::Reflection::Metadata::Item::isDeprecated(metadata, **i))
|
|
{
|
|
continue;
|
|
}
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(ENUM_ITEM), (*i)->name.c_str());
|
|
listItem.setData(TYPE_ROLE, ENUM_ITEM);
|
|
m_enumeratorList.push_back(listItem);
|
|
}
|
|
|
|
std::sort(m_enumeratorList.begin(), m_enumeratorList.end(), &Intellesense::alphabeticalQListWidgetItemSort);
|
|
return true;
|
|
}
|
|
else if (previousWord.startsWith("Enum."))
|
|
{
|
|
for (std::vector<const RBX::Reflection::EnumDescriptor*>::const_iterator i = RBX::Reflection::EnumDescriptor::enumsBegin();
|
|
i != RBX::Reflection::EnumDescriptor::enumsEnd();
|
|
++i)
|
|
{
|
|
if (previousWord.right(previousWord.length() - 5) == (*i)->name.c_str())
|
|
{
|
|
std::for_each(
|
|
(*i)->begin(), (*i)->end(),
|
|
boost::bind(&Intellesense::populateEnumerator, this, _1));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Intellesense::clearAllItemLists()
|
|
{
|
|
m_staticFunctionList.clear();
|
|
m_enumeratorList.clear();
|
|
m_propertyList.clear();
|
|
m_functionList.clear();
|
|
m_eventList.clear();
|
|
m_callbackList.clear();
|
|
m_childList.clear();
|
|
}
|
|
|
|
int Intellesense::populateItemLists(QString text, int row, int index, bool searchDatamodel, const boost::optional<std::vector<RBX::ScriptAnalyzer::IntellesenseResult> >& result)
|
|
{
|
|
if (FFlag::StudioVariableIntellesense)
|
|
clearAllItemLists();
|
|
|
|
QString previousWord = getPreviousWord(text, index);
|
|
|
|
if (FFlag::StudioVariableIntellesense && isAfterDeclarationKeyword(text, index))
|
|
return 0;
|
|
|
|
bool gettingService = false;
|
|
QRegExp service(FFlag::StudioIntellisenseCodeExecutionFixEnabled ? "([gG]ame:(?:Find|Get)Service\\([^)]+\\)[.a-zA-Z\\d]*)([.:][a-zA-Z\\d]*)$" : "([gG]ame:(?:Find|Get)Service\\(.+\\))[.:][a-zA-Z\\d]*$");
|
|
gettingService = service.indexIn(text) > -1;
|
|
if (gettingService) {
|
|
previousWord = service.cap(1);
|
|
searchDatamodel = true;
|
|
}
|
|
|
|
if (previousWord.size() < 1)
|
|
return 0;
|
|
|
|
//getting last intellesense triggering character
|
|
QChar previousDivider = text.at(qMax(text.lastIndexOf(QRegExp("[^a-zA-Z\\d\\_]"), index - text.size() - 1), 0));
|
|
if (previousDivider != '.' && previousDivider != ':')
|
|
{
|
|
if (FFlag::StudioVariableIntellesense && result)
|
|
return repopulateUserVariables(result.get(), row, index);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!FFlag::StudioVariableIntellesense)
|
|
{
|
|
m_staticFunctionList.clear();
|
|
m_enumeratorList.clear();
|
|
}
|
|
|
|
|
|
if (previousDivider == '.')
|
|
{
|
|
if (repopulateItemMetadata(previousWord, true, "LuaLibrary") || repopulateItemMetadata(previousWord, true))
|
|
return FUNCTION_ITEM;
|
|
else if (isEnumerator(previousWord))
|
|
return ENUM_ITEM;
|
|
}
|
|
|
|
if (!FFlag::StudioVariableIntellesense)
|
|
{
|
|
m_propertyList.clear();
|
|
m_functionList.clear();
|
|
m_eventList.clear();
|
|
m_callbackList.clear();
|
|
m_childList.clear();
|
|
}
|
|
|
|
if (!gettingService) {
|
|
if (!previousWord.at(0).isLetterOrNumber() || !previousWord.at(previousWord.size() - 1).isLetterOrNumber())
|
|
return 0;
|
|
}
|
|
|
|
shared_ptr<RBX::LuaSourceContainer> script = shared_ptr<RBX::LuaSourceContainer>();
|
|
if (!searchDatamodel && previousWord.startsWith("script", Qt::CaseSensitive))
|
|
{
|
|
script = getCurrentScript();
|
|
searchDatamodel = true;
|
|
}
|
|
|
|
if (previousWord.startsWith("game", Qt::CaseInsensitive) || previousWord.startsWith("workspace", Qt::CaseInsensitive))
|
|
searchDatamodel = true;
|
|
|
|
if (!searchDatamodel)
|
|
return 0;
|
|
|
|
RobloxIDEDoc* playDoc = RobloxDocManager::Instance().getPlayDoc();
|
|
|
|
if (playDoc && populateItemListsFromVariant(playDoc->evaluateCommandBarItem(previousWord.toStdString().c_str(), script), false))
|
|
{
|
|
if (previousDivider == '.')
|
|
return PROPERTY_ITEM | EVENT_ITEM | CALLBACK_ITEM | CHILD_ITEM;
|
|
else if (previousDivider == ':')
|
|
return FUNCTION_ITEM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
QString Intellesense::getCurrentScriptPath()
|
|
{
|
|
if (RobloxScriptDoc* scriptDoc = dynamic_cast<RobloxScriptDoc *>(RobloxDocManager::Instance().getCurrentDoc()))
|
|
{
|
|
shared_ptr<RBX::Instance> currentInstance = scriptDoc->getCurrentScript().toInstance();
|
|
|
|
if (!currentInstance)
|
|
return "";
|
|
|
|
QString str = currentInstance->getName().c_str();
|
|
|
|
while (currentInstance->getParent() && currentInstance->getParent()->getParent())
|
|
{
|
|
currentInstance = shared_from(currentInstance->getParent());
|
|
|
|
if (!currentInstance)
|
|
return "";
|
|
|
|
str = str.prepend(QString(currentInstance->getName().c_str()).append("."));
|
|
}
|
|
str = str.prepend("game.");
|
|
return str;
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
shared_ptr<RBX::LuaSourceContainer> Intellesense::getCurrentScript()
|
|
{
|
|
if (RobloxScriptDoc* scriptDoc = dynamic_cast<RobloxScriptDoc *>(RobloxDocManager::Instance().getCurrentDoc()))
|
|
{
|
|
return scriptDoc->getCurrentScript().toInstance();
|
|
}
|
|
return shared_ptr<RBX::LuaSourceContainer>();
|
|
}
|
|
|
|
template<bool includeType>
|
|
static void catenateArg(int& count, QString& text, const RBX::Reflection::SignatureDescriptor::Item& item)
|
|
{
|
|
if (count>0)
|
|
text += ", ";
|
|
if (includeType)
|
|
{
|
|
text += item.type->name.c_str();
|
|
text += " ";
|
|
}
|
|
|
|
text += item.name->c_str();
|
|
|
|
if (includeType)
|
|
if (item.hasDefaultValue() && item.defaultValue.isString())
|
|
{
|
|
// A default value has been defined
|
|
text += " = ";
|
|
text += item.defaultValue.get<std::string>().c_str();
|
|
}
|
|
++count;
|
|
}
|
|
|
|
void Intellesense::addProperty(QString propertyName)
|
|
{
|
|
addProperty(propertyName, "");
|
|
}
|
|
|
|
void Intellesense::addProperty(QString propertyName, QString tooltip)
|
|
{
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(PROPERTY_ITEM), propertyName);
|
|
|
|
listItem.setData(TYPE_ROLE, PROPERTY_ITEM);
|
|
|
|
if (!tooltip.isNull())
|
|
listItem.setToolTip(tooltip);
|
|
|
|
m_propertyList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateProperty(const RBX::Reflection::PropertyDescriptor* d)
|
|
{
|
|
if (!d || !d->isPublic() || !d->isScriptable())
|
|
return;
|
|
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
if (AuthoringSettings::singleton().isHiddenInBrowser(metadata, *d))
|
|
return;
|
|
|
|
QString toolTip = "";
|
|
|
|
toolTip += d->type.name.toString().c_str();
|
|
toolTip += " ";
|
|
toolTip += d->name.toString().c_str();
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(PROPERTY_ITEM), QString(d->name.c_str()));
|
|
|
|
listItem.setData(TYPE_ROLE, PROPERTY_ITEM);
|
|
|
|
if (!toolTip.isNull())
|
|
listItem.setToolTip(toolTip);
|
|
|
|
m_propertyList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateFunction(const RBX::Reflection::FunctionDescriptor* d)
|
|
{
|
|
if (!d)
|
|
return;
|
|
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
|
|
// TODO - allow Get in browser, currently just adding a special case here.
|
|
if (AuthoringSettings::singleton().isHiddenInBrowser(metadata, *d) && d->name.toString().compare("Get") != 0)
|
|
return;
|
|
|
|
QString toolTip = "";
|
|
const RBX::Reflection::SignatureDescriptor& signature = d->getSignature();
|
|
toolTip = signature.resultType->name.toString().c_str();
|
|
toolTip += " ";
|
|
toolTip += d->name.toString().c_str();
|
|
toolTip += "(";
|
|
int count = 0;
|
|
std::for_each(
|
|
signature.arguments.begin(),
|
|
signature.arguments.end(),
|
|
boost::bind(&catenateArg<true>, boost::ref(count), boost::ref(toolTip), _1)
|
|
);
|
|
toolTip += ")";
|
|
|
|
if (metadata)
|
|
{
|
|
toolTip += "\n";
|
|
toolTip += metadata->description.c_str();
|
|
}
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(FUNCTION_ITEM), QString(d->name.c_str()));
|
|
|
|
listItem.setData(TYPE_ROLE, FUNCTION_ITEM);
|
|
|
|
if (!toolTip.isNull())
|
|
listItem.setToolTip(toolTip);
|
|
|
|
m_functionList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateYieldFunction(const RBX::Reflection::YieldFunctionDescriptor* d)
|
|
{
|
|
if (!d)
|
|
return;
|
|
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
|
|
if (AuthoringSettings::singleton().isHiddenInBrowser(metadata, *d))
|
|
return;
|
|
|
|
QString toolTip = "";
|
|
const RBX::Reflection::SignatureDescriptor& signature = d->getSignature();
|
|
toolTip = signature.resultType->name.toString().c_str();
|
|
toolTip += " ";
|
|
toolTip += d->name.toString().c_str();
|
|
toolTip += "(";
|
|
int count = 0;
|
|
std::for_each(
|
|
signature.arguments.begin(),
|
|
signature.arguments.end(),
|
|
boost::bind(&catenateArg<true>, boost::ref(count), boost::ref(toolTip), _1)
|
|
);
|
|
toolTip += ")";
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(FUNCTION_ITEM), QString(d->name.c_str()));
|
|
|
|
listItem.setData(TYPE_ROLE, FUNCTION_ITEM);
|
|
|
|
if (!toolTip.isNull())
|
|
listItem.setToolTip(toolTip);
|
|
|
|
m_functionList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateEvent(const RBX::Reflection::EventDescriptor* d)
|
|
{
|
|
if (!d || !d->isPublic() || !d->isScriptable())
|
|
return;
|
|
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
if (AuthoringSettings::singleton().isHiddenInBrowser(metadata, *d))
|
|
return;
|
|
|
|
const RBX::Reflection::SignatureDescriptor& signature = d->getSignature();
|
|
|
|
QString toolTip = "event ";
|
|
toolTip += d->name.toString().c_str();
|
|
toolTip += "(";
|
|
int count = 0;
|
|
std::for_each(
|
|
signature.arguments.begin(),
|
|
signature.arguments.end(),
|
|
boost::bind(&catenateArg<true>, boost::ref(count), boost::ref(toolTip), _1)
|
|
);
|
|
toolTip += ")";
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(EVENT_ITEM), QString(d->name.c_str()));
|
|
|
|
listItem.setData(TYPE_ROLE, EVENT_ITEM);
|
|
|
|
if (!toolTip.isNull())
|
|
listItem.setToolTip(toolTip);
|
|
|
|
m_eventList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateCallBack(const RBX::Reflection::CallbackDescriptor* d)
|
|
{
|
|
if (!d)
|
|
return;
|
|
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
if (AuthoringSettings::singleton().isHiddenInBrowser(metadata, *d))
|
|
return;
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(CALLBACK_ITEM), d->name.c_str());
|
|
|
|
listItem.setData(TYPE_ROLE, CALLBACK_ITEM);
|
|
|
|
listItem.setToolTip("callback");
|
|
|
|
m_callbackList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::populateEnumerator(const RBX::Reflection::EnumDescriptor::Item* d)
|
|
{
|
|
RBX::Reflection::Metadata::Item* metadata = RBX::Reflection::Metadata::Reflection::singleton()->get(*d);
|
|
if (RBX::Reflection::Metadata::Item::isDeprecated(metadata, *d))
|
|
{
|
|
return;
|
|
}
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(ENUM_ITEM), d->name.c_str());
|
|
listItem.setData(TYPE_ROLE, ENUM_ITEM);
|
|
listItem.setToolTip(QString("%1: %2").arg(d->value).arg(d->name.c_str()));
|
|
|
|
m_enumeratorList.push_back(listItem);
|
|
}
|
|
|
|
void Intellesense::showToolTip(QListWidgetItem* listItem)
|
|
{
|
|
if (!listItem)
|
|
listItem = m_AutoCompleteMenu->currentItem();
|
|
|
|
if (!listItem)
|
|
return;
|
|
|
|
QString tooltip = listItem->toolTip();
|
|
|
|
if (!tooltip.isEmpty() && isActive())
|
|
{
|
|
tooltip.replace("%1", "</b><br><br>");
|
|
tooltip.replace("\n", "</b><br><br>");
|
|
tooltip.prepend("<html><b>");
|
|
tooltip.append("</html>");
|
|
|
|
QPoint bottomLeftAlt;
|
|
QPoint topLeftAlt;
|
|
if (m_menuBelow)
|
|
{
|
|
bottomLeftAlt = m_AutoCompleteFrame->pos() - QPoint(0, m_cursorHeight);
|
|
topLeftAlt = m_AutoCompleteFrame->pos() + QPoint(0, m_AutoCompleteFrame->height() + AUTOCOMPLETE_FRAME_MARGIN);
|
|
}
|
|
else
|
|
{
|
|
bottomLeftAlt = m_AutoCompleteFrame->pos() - QPoint(0, AUTOCOMPLETE_FRAME_MARGIN);
|
|
topLeftAlt = QPoint();
|
|
}
|
|
|
|
IntellesenseTooltip::singleton().showText(
|
|
m_AutoCompleteFrame->pos() + QPoint(m_AutoCompleteFrame->width(), 0),
|
|
tooltip,
|
|
m_focusProxy,
|
|
bottomLeftAlt,
|
|
topLeftAlt
|
|
);
|
|
}
|
|
else
|
|
{
|
|
IntellesenseTooltip::singleton().hideText();
|
|
}
|
|
}
|
|
|
|
void Intellesense::singleClick(QListWidgetItem* listItem)
|
|
{
|
|
resetFocus();
|
|
showToolTip();
|
|
}
|
|
|
|
void Intellesense::doubleClick(QListWidgetItem* listItem)
|
|
{
|
|
resetFocus();
|
|
|
|
bool deactivateFrame = true;
|
|
QListWidgetItem* currentItem = m_AutoCompleteMenu->currentItem();
|
|
if (currentItem && (eItemType)(currentItem->data(TYPE_ROLE).toInt()) == NO_ITEM)
|
|
deactivateFrame = false;
|
|
|
|
Q_EMIT doubleClickSignal(listItem);
|
|
|
|
if (deactivateFrame)
|
|
deactivate();
|
|
}
|
|
|
|
void Intellesense::resetFocus()
|
|
{
|
|
if (isActive() && m_focusProxy)
|
|
{
|
|
m_focusProxy->activateWindow();
|
|
m_focusProxy->setFocus(Qt::MouseFocusReason);
|
|
}
|
|
}
|
|
|
|
int Intellesense::repopulateUserVariables(const std::vector<RBX::ScriptAnalyzer::IntellesenseResult>& result, unsigned int row, unsigned int column)
|
|
{
|
|
int itemTypes = NO_ITEM;
|
|
|
|
for (auto it : result)
|
|
{
|
|
if (!it.name.empty())
|
|
{
|
|
bool isAfter = it.location.end.line < row || (it.location.end.line == row && it.location.end.column <= column);
|
|
|
|
eItemType itemType = !it.isLocal || isAfter ? (it.isFunction ? FUNCTION_ITEM : PROPERTY_ITEM) : NO_ITEM;
|
|
|
|
if (itemType != NO_ITEM)
|
|
{
|
|
QList<QListWidgetItem>* itemList = it.isFunction ? &m_functionList : &m_propertyList;
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(itemType), it.name.c_str());
|
|
listItem.setData(TYPE_ROLE, QVariant(itemType));
|
|
itemList->push_front(listItem);
|
|
itemTypes |= itemType;
|
|
}
|
|
}
|
|
|
|
if (it.children.size() > 0)
|
|
{
|
|
if (((it.location.begin.line < row) ||
|
|
(it.location.begin.line == row && it.location.begin.column <= column)) &&
|
|
(it.location.end.line > row ||
|
|
(it.location.end.line == row && it.location.end.column >= column)))
|
|
{
|
|
itemTypes |= repopulateUserVariables(it.children, row, column);
|
|
}
|
|
}
|
|
}
|
|
return itemTypes;
|
|
}
|
|
|
|
bool Intellesense::repopulateItemMetadata(QString itemString, bool showStatic, QString parentTag)
|
|
{
|
|
QDomNodeList nodeList = m_intelleSenseMetadata.documentElement().elementsByTagName(parentTag.toStdString().c_str());
|
|
|
|
for (int i = 0; i < nodeList.count(); ++i)
|
|
{
|
|
QDomElement domElement = nodeList.at(i).toElement();
|
|
if (!domElement.attribute("name").compare(itemString))
|
|
{
|
|
for (QDomNode fNode = domElement.firstChild(); !fNode.isNull(); fNode = fNode.nextSibling())
|
|
{
|
|
QDomElement metadataElement = fNode.toElement();
|
|
|
|
if (showStatic != metadataElement.tagName().startsWith("Static"))
|
|
continue;
|
|
|
|
QString elementName = metadataElement.attribute("name");
|
|
if (elementName.isNull())
|
|
continue;
|
|
|
|
// Set role
|
|
eItemType itemType = PROPERTY_ITEM;
|
|
|
|
if (metadataElement.tagName().contains("Function"))
|
|
itemType = FUNCTION_ITEM;
|
|
|
|
QListWidgetItem listItem = QListWidgetItem(getItemIcon(itemType), elementName);
|
|
listItem.setData(TYPE_ROLE, QVariant(itemType));
|
|
|
|
// Set tooltip
|
|
QString toolTip = metadataElement.attribute("tooltip");
|
|
|
|
if (!toolTip.isNull())
|
|
listItem.setToolTip(toolTip);
|
|
|
|
if (showStatic)
|
|
m_staticFunctionList.push_back(listItem);
|
|
else if (itemType == PROPERTY_ITEM)
|
|
m_propertyList.push_back(listItem);
|
|
else
|
|
m_functionList.push_back(listItem);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Intellesense::populateItemListsFromVariant(const RBX::Reflection::Variant varValue, bool showAllChildren)
|
|
{
|
|
if(varValue.isType<boost::shared_ptr<RBX::Instance> >())
|
|
{
|
|
boost::shared_ptr<RBX::Instance> pInstance = varValue.get<boost::shared_ptr<RBX::Instance> >();
|
|
|
|
std::for_each(
|
|
pInstance->getDescriptor().begin<RBX::Reflection::PropertyDescriptor>(),
|
|
pInstance->getDescriptor().end<RBX::Reflection::PropertyDescriptor>(),
|
|
boost::bind(&Intellesense::populateProperty, this, _1)
|
|
);
|
|
|
|
std::for_each(
|
|
pInstance->getDescriptor().begin<RBX::Reflection::FunctionDescriptor>(),
|
|
pInstance->getDescriptor().end<RBX::Reflection::FunctionDescriptor>(),
|
|
boost::bind(&Intellesense::populateFunction, this, _1)
|
|
);
|
|
|
|
std::for_each(
|
|
pInstance->getDescriptor().begin<RBX::Reflection::YieldFunctionDescriptor>(),
|
|
pInstance->getDescriptor().end<RBX::Reflection::YieldFunctionDescriptor>(),
|
|
boost::bind(&Intellesense::populateYieldFunction, this, _1)
|
|
);
|
|
|
|
std::for_each(
|
|
pInstance->getDescriptor().begin<RBX::Reflection::EventDescriptor>(),
|
|
pInstance->getDescriptor().end<RBX::Reflection::EventDescriptor>(),
|
|
boost::bind(&Intellesense::populateEvent, this, _1)
|
|
);
|
|
|
|
std::for_each(
|
|
pInstance->getDescriptor().begin<RBX::Reflection::CallbackDescriptor>(),
|
|
pInstance->getDescriptor().end<RBX::Reflection::CallbackDescriptor>(),
|
|
boost::bind(&Intellesense::populateCallBack, this, _1)
|
|
);
|
|
|
|
if (pInstance->getChildren())
|
|
{
|
|
RBX::Instances::const_iterator iter = pInstance->getChildren()->begin();
|
|
for (int i = 0; iter != pInstance->getChildren()->end() && (i < MAX_CHILDREN || showAllChildren); ++iter, ++i)
|
|
{
|
|
QListWidgetItem childItem = QListWidgetItem(getItemIcon(CHILD_ITEM), iter->get()->getName().c_str());
|
|
childItem.setData(TYPE_ROLE, QVariant(CHILD_ITEM));
|
|
childItem.setToolTip(QString("%1 %2").arg(iter->get()->getClassNameStr().c_str()).arg(iter->get()->getName().c_str()));
|
|
|
|
m_childList.push_back(childItem);
|
|
}
|
|
if (iter != pInstance->getChildren()->end())
|
|
{
|
|
int remaining = pInstance->numChildren() - MAX_CHILDREN;
|
|
QListWidgetItem remainingChildren = QListWidgetItem(QString("%1 more children...").arg(remaining));
|
|
remainingChildren.setData(TYPE_ROLE, QVariant(NO_ITEM));
|
|
m_childList.push_back(remainingChildren);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
else if (varValue.isType<RBX::Vector2>())
|
|
return repopulateItemMetadata("Vector2");
|
|
else if (varValue.isType<RBX::Vector2int16>())
|
|
return repopulateItemMetadata("Vector2int16");
|
|
else if (varValue.isType<RBX::Vector3>())
|
|
return repopulateItemMetadata("Vector3");
|
|
else if (varValue.isType<RBX::Vector3int16>())
|
|
return repopulateItemMetadata("Vector3int16");
|
|
else if (varValue.isType<G3D::CoordinateFrame>())
|
|
return repopulateItemMetadata("CFrame");
|
|
else if (varValue.isType<RBX::Region3>())
|
|
return repopulateItemMetadata("Region3");
|
|
else if (varValue.isType<RBX::Region3int16>())
|
|
return repopulateItemMetadata("Region3int16");
|
|
else if (varValue.isType<RBX::BrickColor>())
|
|
return repopulateItemMetadata("BrickColor");
|
|
else if (varValue.isType<RBX::Color3>())
|
|
return repopulateItemMetadata("Color3");
|
|
else if (varValue.isType<RBX::RbxRay>())
|
|
return repopulateItemMetadata("Ray");
|
|
else if (varValue.isType<RBX::UDim>())
|
|
return repopulateItemMetadata("UDim");
|
|
else if (varValue.isType<RBX::UDim2>())
|
|
return repopulateItemMetadata("UDim2");
|
|
else if (varValue.isType<RBX::Rect2D>())
|
|
return repopulateItemMetadata("Rect");
|
|
else if (varValue.isType<RBX::PhysicalProperties>())
|
|
return repopulateItemMetadata("PhysicalProperties");
|
|
else if (varValue.isType<shared_ptr<const RBX::Reflection::ValueTable> >())
|
|
{
|
|
shared_ptr<const RBX::Reflection::ValueTable> valMap = varValue.get<shared_ptr<const RBX::Reflection::ValueTable> >();
|
|
|
|
for (RBX::Reflection::ValueTable::const_iterator iter = valMap->begin(); iter != valMap->end(); ++iter)
|
|
addProperty(iter->first.c_str());
|
|
|
|
return true;
|
|
}
|
|
else if (varValue.isType<shared_ptr<const RBX::Reflection::ValueArray> >())
|
|
{
|
|
//Can't access data through . operator in value array
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Intellesense::repopulateListWidgetHelper(QList<QListWidgetItem>& populateVector, QList<QListWidgetItem> *startsWithVector, int itemType, QString matchingWord)
|
|
{
|
|
int insertPos = 0;
|
|
|
|
matchingWord = matchingWord.toLower();
|
|
|
|
for (QList<QListWidgetItem>::iterator i = populateVector.end() - 1; i != populateVector.begin() - 1; --i)
|
|
{
|
|
QListWidgetItem* listItem = new QListWidgetItem(*i);
|
|
QString itemString = listItem->text().toLower();
|
|
|
|
if (FFlag::StudioIntellesenseShowNonDeprecatedMembers)
|
|
{
|
|
if (m_AutoCompleteMenu->findItems(listItem->text(), Qt::MatchExactly).size() > 0)
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
if (m_AutoCompleteMenu->findItems(itemString, Qt::MatchFixedString).size() > 0)
|
|
continue;
|
|
}
|
|
|
|
if (itemString.startsWith(matchingWord) && !matchingWord.isEmpty())
|
|
{
|
|
if (startsWithVector)
|
|
{
|
|
startsWithVector->append(*listItem);
|
|
}
|
|
else
|
|
{
|
|
m_AutoCompleteMenu->insertItem(0, listItem);
|
|
insertPos++;
|
|
}
|
|
}
|
|
else if (itemString.contains(matchingWord))
|
|
{
|
|
m_AutoCompleteMenu->insertItem(insertPos, listItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Intellesense::repopulateListWidget(int itemFlags, QString currentWord, int lineIndex)
|
|
{
|
|
m_AutoCompleteMenu->clear();
|
|
|
|
if (m_staticFunctionList.size() > 0)
|
|
{
|
|
itemFlags = 0;
|
|
int insertPos = 0;
|
|
|
|
currentWord = currentWord.toLower();
|
|
|
|
for (QList<QListWidgetItem>::iterator i = m_staticFunctionList.end() - 1; i != m_staticFunctionList.begin() - 1; --i)
|
|
{
|
|
QListWidgetItem* listItem = new QListWidgetItem(*i);
|
|
QString itemString = listItem->text().toLower();
|
|
|
|
if (itemString.startsWith(currentWord))
|
|
{
|
|
m_AutoCompleteMenu->insertItem(0, listItem);
|
|
insertPos++;
|
|
}
|
|
else if (itemString.contains(currentWord))
|
|
{
|
|
m_AutoCompleteMenu->insertItem(insertPos, listItem);
|
|
}
|
|
}
|
|
}
|
|
|
|
QList<QListWidgetItem> *topOfList = new QList<QListWidgetItem>();
|
|
|
|
if (m_enumeratorList.size() > 0)
|
|
{
|
|
itemFlags = 0;
|
|
repopulateListWidgetHelper(m_enumeratorList, topOfList, ENUM_ITEM, currentWord);
|
|
}
|
|
|
|
if (itemFlags & CHILD_ITEM)
|
|
repopulateListWidgetHelper(m_childList, topOfList, CHILD_ITEM, currentWord);
|
|
|
|
if (itemFlags & CALLBACK_ITEM)
|
|
repopulateListWidgetHelper(m_callbackList, topOfList, CALLBACK_ITEM, currentWord);
|
|
|
|
if (itemFlags & EVENT_ITEM)
|
|
repopulateListWidgetHelper(m_eventList, topOfList, EVENT_ITEM, currentWord);
|
|
|
|
if (itemFlags & FUNCTION_ITEM)
|
|
repopulateListWidgetHelper(m_functionList, topOfList, FUNCTION_ITEM, currentWord);
|
|
|
|
if (itemFlags & PROPERTY_ITEM)
|
|
repopulateListWidgetHelper(m_propertyList, topOfList, PROPERTY_ITEM, currentWord);
|
|
|
|
if (topOfList->count() > 0)
|
|
{
|
|
std::sort(topOfList->begin(), topOfList->end(), &Intellesense::alphabeticalQListWidgetItemSort);
|
|
repopulateListWidgetHelper(*topOfList, NULL, PROPERTY_ITEM, "");
|
|
}
|
|
|
|
if(m_AutoCompleteMenu->count() < 1 || (m_AutoCompleteMenu->count() == 1 && m_AutoCompleteMenu->item(0)->text() == currentWord))
|
|
{
|
|
deactivate();
|
|
return;
|
|
}
|
|
|
|
m_AutoCompleteMenu->setCurrentRow(lineIndex);
|
|
}
|
|
|
|
bool Intellesense::handleAutoCompleteMenu(const QRect& positionRange, int itemFlags, QString currentWord, int lineIndex)
|
|
{
|
|
if (!itemFlags)
|
|
{
|
|
deactivate();
|
|
return false;
|
|
}
|
|
|
|
repopulateListWidget(itemFlags, currentWord, lineIndex);
|
|
|
|
if(m_AutoCompleteMenu->count() < 1 || (m_AutoCompleteMenu->count() == 1 && m_AutoCompleteMenu->item(0)->text() == currentWord))
|
|
{
|
|
deactivate();
|
|
return false;
|
|
}
|
|
|
|
m_menuBelow = true;
|
|
|
|
int itemheight = m_AutoCompleteMenu->visualItemRect(m_AutoCompleteMenu->item(0)).height();
|
|
|
|
int menuHeight = (qMin(m_AutoCompleteMenu->count(), MAX_ITEMS_SHOWN) * itemheight) + AUTOCOMPLETE_FRAME_MARGIN;
|
|
|
|
#ifdef _WIN32
|
|
menuHeight += AUTOCOMPLETE_FRAME_MARGIN;
|
|
#endif
|
|
|
|
if(m_AutoCompleteMenu->count() < MAX_ITEMS_SHOWN)
|
|
m_AutoCompleteMenu->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
else
|
|
m_AutoCompleteMenu->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
|
|
|
|
|
m_AutoCompleteMenu->resize(BASE_FRAME_WIDTH, menuHeight);
|
|
|
|
int xPos = m_AutoCompleteFrame->pos().x();
|
|
int yPos = m_AutoCompleteFrame->pos().y();
|
|
|
|
if (!positionRange.isNull())
|
|
{
|
|
xPos = positionRange.x();
|
|
yPos = positionRange.y();
|
|
|
|
QRect screenBounds = QApplication::desktop()->screenGeometry(m_focusProxy);
|
|
|
|
yPos += positionRange.height();
|
|
|
|
if (yPos + menuHeight > screenBounds.y() + screenBounds.height())
|
|
{
|
|
yPos -= menuHeight + positionRange.height();
|
|
m_menuBelow = false;
|
|
}
|
|
|
|
if (xPos + BASE_FRAME_WIDTH > screenBounds.x() + screenBounds.width())
|
|
xPos = screenBounds.x() + screenBounds.width() - SCREEN_EDGE_MARGIN - BASE_FRAME_WIDTH;
|
|
|
|
|
|
}
|
|
m_AutoCompleteFrame->setGeometry(xPos, yPos, BASE_FRAME_WIDTH, menuHeight);
|
|
|
|
m_AutoCompleteFrame->setVisible(true);
|
|
|
|
showToolTip();
|
|
return true;
|
|
}
|
|
|
|
bool Intellesense::eventFilter(QObject *watched, QEvent *evt)
|
|
{
|
|
if (isActive())
|
|
{
|
|
if (evt->type() == QEvent::MouseButtonPress)
|
|
{
|
|
m_AutoCompleteMenu->viewport()->repaint();
|
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(evt);
|
|
if (mouseEvent && !m_AutoCompleteFrame->geometry().contains(mouseEvent->globalPos()))
|
|
deactivate();
|
|
}
|
|
else if (evt->type() == QEvent::KeyPress ||
|
|
evt->type() == QEvent::MouseMove ||
|
|
evt->type() == QEvent::Wheel)
|
|
m_AutoCompleteMenu->viewport()->repaint();//bug with qt does not repaint qlistwidget on mac correctly
|
|
else if (evt->type() == QEvent::ApplicationDeactivate)
|
|
deactivate();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QIcon Intellesense::getItemIcon(eItemType itemType)
|
|
{
|
|
int position = 0;
|
|
switch(itemType)
|
|
{
|
|
case CHILD_ITEM:
|
|
position = 3;
|
|
break;
|
|
case FUNCTION_ITEM:
|
|
position = 4;
|
|
break;
|
|
case PROPERTY_ITEM:
|
|
position = 6;
|
|
break;
|
|
case EVENT_ITEM:
|
|
position = 8;
|
|
break;
|
|
case ENUM_ITEM:
|
|
position = 9;
|
|
break;
|
|
case CALLBACK_ITEM:
|
|
position = 16;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return QIcon(QtUtilities::getPixmap(OBJECT_BROWSER_IMAGES, position, OB_IMAGE_SIZE, true));
|
|
}
|
|
|
|
IntellesenseTooltip::IntellesenseTooltip()
|
|
{
|
|
resize(QSize(1,1));
|
|
|
|
setAttribute(Qt::WA_ShowWithoutActivating);
|
|
setFocusPolicy(Qt::NoFocus);
|
|
setWindowFlags(Qt::WindowStaysOnTopHint | Qt::ToolTip | Qt::FramelessWindowHint);
|
|
|
|
show();
|
|
setVisible(false);
|
|
resize(200, 200);
|
|
|
|
toolTipLabel = new QLabel(this);
|
|
toolTipLabel->setGeometry(4, 4, width() - SCREEN_EDGE_MARGIN, 20);
|
|
toolTipLabel->setOpenExternalLinks(true);
|
|
toolTipLabel->setWordWrap(true);
|
|
toolTipLabel->setAlignment(Qt::AlignTop);
|
|
toolTipLabel->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
|
|
}
|
|
|
|
IntellesenseTooltip& IntellesenseTooltip::singleton()
|
|
{
|
|
static IntellesenseTooltip* studioIntellesenseTooltip = new IntellesenseTooltip();
|
|
return *studioIntellesenseTooltip;
|
|
}
|
|
|
|
void IntellesenseTooltip::showText(const QPoint& pos, const QString& text, QWidget* focusWidget, const QPoint& bottomLeftAlt, const QPoint& topLeftAlt)//, int altX, const QPoint& altPos)
|
|
{
|
|
int breakPos = text.indexOf("</b><br><br>");
|
|
|
|
QString header = text.left(breakPos);
|
|
QString body = text.mid(breakPos);
|
|
|
|
header = header.remove(QRegExp("<[^>]*>"));
|
|
body = body.remove(QRegExp("<[^>]*>"));
|
|
|
|
QFont boldFont = toolTipLabel->font();
|
|
boldFont.setBold(true);
|
|
QFontMetrics fontMetric = QFontMetrics(boldFont);
|
|
|
|
int toolTipWidth = fontMetric.width(header);
|
|
int toolTipHeight = fontMetric.height() * (qCeil((float)fontMetric.width(body) / (float)toolTipWidth) + AUTOCOMPLETE_FRAME_MARGIN);
|
|
|
|
if (breakPos == -1)
|
|
{
|
|
toolTipLabel->setText(header);
|
|
toolTipHeight = fontMetric.height();
|
|
}
|
|
else
|
|
toolTipLabel->setText(text);
|
|
|
|
toolTipLabel->resize(toolTipWidth, toolTipHeight);
|
|
toolTipLabel->adjustSize();
|
|
|
|
QPoint finalPosition = QPoint(pos.x() + AUTOCOMPLETE_FRAME_MARGIN, pos.y() + AUTOCOMPLETE_FRAME_MARGIN);
|
|
|
|
QRect screenBounds;
|
|
if (focusWidget)
|
|
screenBounds = QApplication::desktop()->screenGeometry(focusWidget);
|
|
else
|
|
screenBounds = QApplication::desktop()->screenGeometry();
|
|
|
|
// Snapping to screen bounds using alternative positions
|
|
if (finalPosition.x() + toolTipLabel->width() + SCREEN_EDGE_MARGIN > screenBounds.x() + screenBounds.width())
|
|
{
|
|
if (!topLeftAlt.isNull() && topLeftAlt.y() + toolTipLabel->height() + SCREEN_EDGE_MARGIN <= screenBounds.y() + screenBounds.height())
|
|
finalPosition = topLeftAlt;
|
|
else if (bottomLeftAlt.y() - toolTipLabel->height() - SCREEN_EDGE_MARGIN > AUTOCOMPLETE_FRAME_MARGIN)
|
|
finalPosition = QPoint(bottomLeftAlt.x(), bottomLeftAlt.y() - toolTipLabel->height() - SCREEN_EDGE_MARGIN);
|
|
|
|
if (finalPosition.x() < 0)
|
|
finalPosition.setX(AUTOCOMPLETE_FRAME_MARGIN);
|
|
|
|
if (finalPosition.x() + toolTipLabel->width() + SCREEN_EDGE_MARGIN > screenBounds.x() + screenBounds.width())
|
|
finalPosition.setX(screenBounds.x() + screenBounds.width() - toolTipLabel->width() - SCREEN_EDGE_MARGIN - AUTOCOMPLETE_FRAME_MARGIN);
|
|
}
|
|
else
|
|
{
|
|
if (finalPosition.y() < 0)
|
|
finalPosition.setY(AUTOCOMPLETE_FRAME_MARGIN);
|
|
|
|
if (finalPosition.y() + toolTipLabel->height() + SCREEN_EDGE_MARGIN > screenBounds.y() + screenBounds.height())
|
|
finalPosition.setY(screenBounds.y() + screenBounds.height() - toolTipLabel->height() - SCREEN_EDGE_MARGIN - AUTOCOMPLETE_FRAME_MARGIN);
|
|
}
|
|
|
|
setGeometry(finalPosition.x(), finalPosition.y(), toolTipLabel->width() + SCREEN_EDGE_MARGIN, toolTipLabel->height() + SCREEN_EDGE_MARGIN);
|
|
setVisible(true);
|
|
}
|
|
|
|
void IntellesenseTooltip::hideText()
|
|
{
|
|
setVisible(false);
|
|
}
|
|
|
|
void IntellesenseTooltip::setStyleSheet(const QString& styleSheet)
|
|
{
|
|
QFrame::setStyleSheet(styleSheet);
|
|
}
|
|
} //namespace
|