| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | #include <limits>
|
| |
|
| | #include <QApplication>
|
| | #include <QKeyEvent>
|
| | #include <QLabel>
|
| | #include <QPlainTextEdit>
|
| | #include <QRegularExpression>
|
| | #include <QRegularExpressionMatch>
|
| | #include <QTextCursor>
|
| | #include <QToolTip>
|
| |
|
| | #include <App/Property.h>
|
| | #include <App/PropertyContainer.h>
|
| | #include <App/PropertyContainerPy.h>
|
| | #include <App/Document.h>
|
| | #include <App/DocumentObject.h>
|
| | #include <App/DocumentPy.h>
|
| | #include <App/DocumentObjectPy.h>
|
| | #include <Base/Console.h>
|
| | #include <Base/Interpreter.h>
|
| | #include <Base/PyObjectBase.h>
|
| | #include <Gui/BitmapFactory.h>
|
| | #include <Gui/DocumentPy.h>
|
| |
|
| | #include "CallTips.h"
|
| |
|
| |
|
| | Q_DECLARE_METATYPE(Gui::CallTip)
|
| |
|
| | namespace Gui
|
| | {
|
| |
|
| | |
| | |
| | |
| |
|
| | template<typename TYPE>
|
| | class Temporary
|
| | {
|
| | public:
|
| | Temporary(TYPE& var, const TYPE tmpVal)
|
| | : _var(var)
|
| | , _saveVal(var)
|
| | {
|
| | var = tmpVal;
|
| | }
|
| |
|
| | ~Temporary()
|
| | {
|
| | _var = _saveVal;
|
| | }
|
| |
|
| | private:
|
| | TYPE& _var;
|
| | TYPE _saveVal;
|
| | };
|
| |
|
| | }
|
| |
|
| | using namespace Gui;
|
| |
|
| | CallTipsList::CallTipsList(QPlainTextEdit* parent)
|
| | : QListWidget(parent)
|
| | , textEdit(parent)
|
| | , cursorPos(0)
|
| | , validObject(true)
|
| | , doCallCompletion(false)
|
| | {
|
| |
|
| | QPalette pal = parent->palette();
|
| | pal.setColor(
|
| | QPalette::Inactive,
|
| | QPalette::Highlight,
|
| | pal.color(QPalette::Active, QPalette::Highlight)
|
| | );
|
| | pal.setColor(
|
| | QPalette::Inactive,
|
| | QPalette::HighlightedText,
|
| | pal.color(QPalette::Active, QPalette::HighlightedText)
|
| | );
|
| | parent->setPalette(pal);
|
| |
|
| | connect(this, &QListWidget::itemActivated, this, &CallTipsList::callTipItemActivated);
|
| |
|
| | hideKeys.append(Qt::Key_Space);
|
| | hideKeys.append(Qt::Key_Exclam);
|
| | hideKeys.append(Qt::Key_QuoteDbl);
|
| | hideKeys.append(Qt::Key_NumberSign);
|
| | hideKeys.append(Qt::Key_Dollar);
|
| | hideKeys.append(Qt::Key_Percent);
|
| | hideKeys.append(Qt::Key_Ampersand);
|
| | hideKeys.append(Qt::Key_Apostrophe);
|
| | hideKeys.append(Qt::Key_Asterisk);
|
| | hideKeys.append(Qt::Key_Plus);
|
| | hideKeys.append(Qt::Key_Comma);
|
| | hideKeys.append(Qt::Key_Minus);
|
| | hideKeys.append(Qt::Key_Period);
|
| | hideKeys.append(Qt::Key_Slash);
|
| | hideKeys.append(Qt::Key_Colon);
|
| | hideKeys.append(Qt::Key_Semicolon);
|
| | hideKeys.append(Qt::Key_Less);
|
| | hideKeys.append(Qt::Key_Equal);
|
| | hideKeys.append(Qt::Key_Greater);
|
| | hideKeys.append(Qt::Key_Question);
|
| | hideKeys.append(Qt::Key_At);
|
| | hideKeys.append(Qt::Key_Backslash);
|
| |
|
| | compKeys.append(Qt::Key_ParenLeft);
|
| | compKeys.append(Qt::Key_ParenRight);
|
| | compKeys.append(Qt::Key_BracketLeft);
|
| | compKeys.append(Qt::Key_BracketRight);
|
| | compKeys.append(Qt::Key_BraceLeft);
|
| | compKeys.append(Qt::Key_BraceRight);
|
| | }
|
| |
|
| | CallTipsList::~CallTipsList() = default;
|
| |
|
| | void CallTipsList::keyboardSearch(const QString& wordPrefix)
|
| | {
|
| |
|
| | for (int i = 0; i < count(); ++i) {
|
| | QString text = item(i)->text();
|
| | if (text.startsWith(wordPrefix)) {
|
| | setCurrentRow(i);
|
| | return;
|
| | }
|
| | }
|
| |
|
| |
|
| | for (int i = 0; i < count(); ++i) {
|
| | QString text = item(i)->text();
|
| | if (text.startsWith(wordPrefix, Qt::CaseInsensitive)) {
|
| | setCurrentRow(i);
|
| | return;
|
| | }
|
| | }
|
| |
|
| | if (currentItem()) {
|
| | currentItem()->setSelected(false);
|
| | }
|
| | }
|
| |
|
| | void CallTipsList::validateCursor()
|
| | {
|
| | QTextCursor cursor = textEdit->textCursor();
|
| | int currentPos = cursor.position();
|
| | if (currentPos < this->cursorPos) {
|
| | hide();
|
| | }
|
| | else {
|
| | cursor.setPosition(this->cursorPos);
|
| | cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
| | QString word = cursor.selectedText();
|
| | if (!word.isEmpty()) {
|
| |
|
| | const QChar underscore = QLatin1Char('_');
|
| | const QChar ch = word.at(0);
|
| | if (!ch.isLetterOrNumber() && ch != underscore) {
|
| | word.clear();
|
| | }
|
| | }
|
| | if (currentPos > this->cursorPos + word.length()) {
|
| | hide();
|
| | }
|
| | else if (!word.isEmpty()) {
|
| |
|
| |
|
| |
|
| | keyboardSearch(word);
|
| | }
|
| | }
|
| | }
|
| |
|
| | QString CallTipsList::extractContext(const QString& line) const
|
| | {
|
| | int len = line.size();
|
| | int index = len - 1;
|
| | for (int i = 0; i < len; i++) {
|
| | int pos = len - 1 - i;
|
| | const char ch = line.at(pos).toLatin1();
|
| | if ((ch >= 48 && ch <= 57) ||
|
| | (ch >= 65 && ch <= 90) ||
|
| | (ch >= 97 && ch <= 122) ||
|
| | (ch == '.') || (ch == '_') ||
|
| | (ch == ' ') || (ch == '\t')) {
|
| | index = pos;
|
| | }
|
| | else {
|
| | break;
|
| | }
|
| | }
|
| |
|
| | return line.mid(index);
|
| | }
|
| |
|
| | QMap<QString, CallTip> CallTipsList::extractTips(const QString& context) const
|
| | {
|
| | Base::PyGILStateLocker lock;
|
| | QMap<QString, CallTip> tips;
|
| | if (context.isEmpty()) {
|
| | return tips;
|
| | }
|
| |
|
| | try {
|
| | PyErr_Clear();
|
| | Py::Module module("__main__");
|
| | if (module.ptr() == nullptr) {
|
| | return tips;
|
| | }
|
| | Py::Dict dict = module.getDict();
|
| |
|
| |
|
| | QStringList items = context.split(QLatin1Char('.'));
|
| | QString modname = items.front();
|
| | items.pop_front();
|
| | if (!dict.hasKey(std::string(modname.toLatin1()))) {
|
| | return tips;
|
| | }
|
| |
|
| |
|
| | PyObject* code = Py_CompileString(
|
| | static_cast<const char*>(context.toLatin1()),
|
| | "<CallTipsList>",
|
| | Py_eval_input
|
| | );
|
| | if (!code) {
|
| | PyErr_Clear();
|
| | return tips;
|
| | }
|
| |
|
| | PyObject* eval = nullptr;
|
| | if (PyCode_Check(code)) {
|
| | eval = PyEval_EvalCode(code, dict.ptr(), dict.ptr());
|
| | }
|
| | Py_DECREF(code);
|
| | if (!eval) {
|
| | PyErr_Clear();
|
| | return tips;
|
| | }
|
| | Py::Object obj(eval, true);
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | Py::Object type(PyObject_Type(obj.ptr()), true);
|
| | Py::Object inst = obj;
|
| | PyObject* typeobj = Base::getTypeAsObject(&Base::PyObjectBase::Type);
|
| | PyObject* typedoc = Base::getTypeAsObject(&App::DocumentObjectPy::Type);
|
| | PyObject* basetype = Base::getTypeAsObject(&PyBaseObject_Type);
|
| |
|
| | if (PyObject_IsSubclass(type.ptr(), typedoc) == 1) {
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | if (!obj.hasAttr("__fc_template__")) {
|
| | obj = type;
|
| | }
|
| | }
|
| | else if (PyObject_IsSubclass(type.ptr(), typeobj) == 1) {
|
| | obj = type;
|
| | }
|
| | else if (PyObject_IsInstance(obj.ptr(), basetype) == 1) {
|
| |
|
| |
|
| | PyObject* typetype = Base::getTypeAsObject(&PyType_Type);
|
| | if (PyObject_IsInstance(obj.ptr(), typetype) != 1) {
|
| |
|
| |
|
| | QString typestr(QLatin1String(Py_TYPE(obj.ptr())->tp_name));
|
| |
|
| |
|
| |
|
| | if (!typestr.startsWith(QLatin1String("PySide"))
|
| | && Py_TYPE(obj.ptr())->tp_flags & Py_TPFLAGS_HEAPTYPE) {
|
| | obj = type;
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | if (PyObject_IsInstance(inst.ptr(), typeobj) == 1) {
|
| | auto baseobj = static_cast<Base::PyObjectBase*>(inst.ptr());
|
| | validObject = baseobj->isValid();
|
| | }
|
| | else {
|
| |
|
| | PyErr_Clear();
|
| | }
|
| |
|
| | Py::List list(obj.dir());
|
| |
|
| |
|
| |
|
| | PyObject* proptypeobj = Base::getTypeAsObject(&App::PropertyContainerPy::Type);
|
| | if (PyObject_IsSubclass(type.ptr(), proptypeobj) == 1) {
|
| |
|
| |
|
| | extractTipsFromProperties(inst, tips);
|
| | }
|
| |
|
| |
|
| |
|
| | PyObject* appdoctypeobj = Base::getTypeAsObject(&App::DocumentPy::Type);
|
| | if (PyObject_IsSubclass(type.ptr(), appdoctypeobj) == 1) {
|
| | auto docpy = static_cast<App::DocumentPy*>(inst.ptr());
|
| | auto document = docpy->getDocumentPtr();
|
| |
|
| | if (document) {
|
| | std::vector<App::DocumentObject*> objects = document->getObjects();
|
| | Py::List list;
|
| | for (const auto& object : objects) {
|
| | list.append(Py::String(object->getNameInDocument()));
|
| | }
|
| | extractTipsFromObject(inst, list, tips);
|
| | }
|
| | }
|
| |
|
| |
|
| |
|
| | PyObject* guidoctypeobj = Base::getTypeAsObject(&Gui::DocumentPy::Type);
|
| | if (PyObject_IsSubclass(type.ptr(), guidoctypeobj) == 1) {
|
| | auto docpy = static_cast<Gui::DocumentPy*>(inst.ptr());
|
| | if (docpy->getDocumentPtr()) {
|
| | App::Document* document = docpy->getDocumentPtr()->getDocument();
|
| |
|
| | if (document) {
|
| | std::vector<App::DocumentObject*> objects = document->getObjects();
|
| | Py::List list;
|
| | for (const auto& object : objects) {
|
| | list.append(Py::String(object->getNameInDocument()));
|
| | }
|
| | extractTipsFromObject(inst, list, tips);
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | extractTipsFromObject(obj, list, tips);
|
| | }
|
| | catch (Py::Exception& e) {
|
| |
|
| | e.clear();
|
| | }
|
| |
|
| | return tips;
|
| | }
|
| |
|
| | bool shibokenMayCrash(void)
|
| | {
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | Py::Module shiboken("shiboken6");
|
| | Py::Tuple version(shiboken.getAttr("__version_info__"));
|
| | int major = Py::Long(version.getItem(0));
|
| | int minor = Py::Long(version.getItem(1));
|
| | int patch = Py::Long(version.getItem(2));
|
| | bool brokenVersion = (major == 6 && minor >= 4 && minor < 8);
|
| | bool fixedVersion = (major == 6 && minor == 7 && patch > 2);
|
| | return brokenVersion && !fixedVersion;
|
| | }
|
| |
|
| | Py::Object CallTipsList::getAttrWorkaround(Py::Object& obj, Py::String& name) const
|
| | {
|
| | QString typestr(QLatin1String(Py_TYPE(obj.ptr())->tp_name));
|
| | bool hasWorkingGetAttr = !(typestr == QLatin1String("Shiboken.ObjectType") && shibokenMayCrash());
|
| |
|
| | if (hasWorkingGetAttr) {
|
| | return obj.getAttr(name.as_string());
|
| | }
|
| |
|
| | Py::Dict globals;
|
| | Py::Dict locals;
|
| | locals.setItem("obj", obj);
|
| | locals.setItem("attr", name);
|
| |
|
| | Py::Object bouncer(Py_CompileString("getattr(obj, attr)", "<CallTipsList>", Py_eval_input));
|
| | Py::Object attr(PyEval_EvalCode(bouncer.ptr(), globals.ptr(), locals.ptr()));
|
| |
|
| | return attr;
|
| | }
|
| |
|
| | void CallTipsList::extractTipsFromObject(Py::Object& obj, Py::List& list, QMap<QString, CallTip>& tips) const
|
| | {
|
| | for (Py::List::iterator it = list.begin(); it != list.end(); ++it) {
|
| | try {
|
| | Py::String attrname(*it);
|
| | std::string name(attrname.as_string());
|
| |
|
| | Py::Object attr = getAttrWorkaround(obj, attrname);
|
| | if (!attr.ptr()) {
|
| | Base::Console().log("Python attribute '%s' returns null!\n", name.c_str());
|
| | continue;
|
| | }
|
| |
|
| | CallTip tip;
|
| | QString str = QString::fromLatin1(name.c_str());
|
| | tip.name = str;
|
| |
|
| | if (attr.isCallable()) {
|
| | PyObject* basetype = Base::getTypeAsObject(&PyBaseObject_Type);
|
| | if (PyObject_IsSubclass(attr.ptr(), basetype) == 1) {
|
| | tip.type = CallTip::Class;
|
| | }
|
| | else {
|
| | PyErr_Clear();
|
| | tip.type = CallTip::Method;
|
| | }
|
| | }
|
| | else if (PyModule_Check(attr.ptr())) {
|
| | tip.type = CallTip::Module;
|
| | }
|
| | else {
|
| | tip.type = CallTip::Member;
|
| | }
|
| |
|
| | if (str == QLatin1String("__doc__") && attr.isString()) {
|
| | Py::Object help = attr;
|
| | if (help.isString()) {
|
| | Py::String doc(help);
|
| | QString longdoc = QString::fromUtf8(doc.as_string().c_str());
|
| | int pos = longdoc.indexOf(QLatin1Char('\n'));
|
| | pos = qMin(pos, 70);
|
| | if (pos < 0) {
|
| | pos = qMin(longdoc.length(), 70);
|
| | }
|
| | tip.description = stripWhiteSpace(longdoc);
|
| | tip.parameter = longdoc.left(pos);
|
| | }
|
| | }
|
| | else if (attr.hasAttr("__doc__")) {
|
| | Py::Object help = attr.getAttr("__doc__");
|
| | if (help.isString()) {
|
| | Py::String doc(help);
|
| | QString longdoc = QString::fromUtf8(doc.as_string().c_str());
|
| | int pos = longdoc.indexOf(QLatin1Char('\n'));
|
| | pos = qMin(pos, 70);
|
| | if (pos < 0) {
|
| | pos = qMin(longdoc.length(), 70);
|
| | }
|
| | tip.description = stripWhiteSpace(longdoc);
|
| | tip.parameter = longdoc.left(pos);
|
| | }
|
| | }
|
| |
|
| |
|
| | QMap<QString, CallTip>::iterator pos = tips.find(str);
|
| | if (pos == tips.end()) {
|
| | tips[str] = tip;
|
| | }
|
| | }
|
| | catch (Py::Exception& e) {
|
| |
|
| | e.clear();
|
| | }
|
| | }
|
| | }
|
| |
|
| | void CallTipsList::extractTipsFromProperties(Py::Object& obj, QMap<QString, CallTip>& tips) const
|
| | {
|
| | auto cont = static_cast<App::PropertyContainerPy*>(obj.ptr());
|
| | App::PropertyContainer* container = cont->getPropertyContainerPtr();
|
| |
|
| | if (!container) {
|
| | return;
|
| | }
|
| | std::map<std::string, App::Property*> Map;
|
| | container->getPropertyMap(Map);
|
| |
|
| | for (const auto& It : Map) {
|
| | CallTip tip;
|
| | QString str = QString::fromLatin1(It.first.c_str());
|
| | tip.name = str;
|
| | tip.type = CallTip::Property;
|
| | QString longdoc = QString::fromUtf8(container->getPropertyDocumentation(It.second));
|
| |
|
| | if (It.second->isDerivedFrom(Base::Type::fromName("App::PropertyComplexGeoData"))) {
|
| | Py::Object data(It.second->getPyObject(), true);
|
| | if (data.hasAttr("__doc__")) {
|
| | Py::Object help = data.getAttr("__doc__");
|
| | if (help.isString()) {
|
| | Py::String doc(help);
|
| | longdoc = QString::fromUtf8(doc.as_string().c_str());
|
| | }
|
| | }
|
| | }
|
| | if (!longdoc.isEmpty()) {
|
| | int pos = longdoc.indexOf(QLatin1Char('\n'));
|
| | pos = qMin(pos, 70);
|
| | if (pos < 0) {
|
| | pos = qMin(longdoc.length(), 70);
|
| | }
|
| | tip.description = stripWhiteSpace(longdoc);
|
| | tip.parameter = longdoc.left(pos);
|
| | }
|
| | tips[str] = tip;
|
| | }
|
| | }
|
| |
|
| | void CallTipsList::showTips(const QString& line)
|
| | {
|
| |
|
| | static QPixmap type_module_icon = BitmapFactory().pixmap("ClassBrowser/type_module.svg");
|
| | static QPixmap type_class_icon = BitmapFactory().pixmap("ClassBrowser/type_class.svg");
|
| | static QPixmap method_icon = BitmapFactory().pixmap("ClassBrowser/method.svg");
|
| | static QPixmap member_icon = BitmapFactory().pixmap("ClassBrowser/member.svg");
|
| | static QPixmap property_icon = BitmapFactory().pixmap("ClassBrowser/property.svg");
|
| |
|
| |
|
| | static QPixmap forbidden_icon(
|
| | Gui::BitmapFactory().pixmapFromSvg("forbidden", property_icon.size() / 4)
|
| | );
|
| | static QPixmap forbidden_type_module_icon
|
| | = BitmapFactory().merge(type_module_icon, forbidden_icon, BitmapFactoryInst::BottomLeft);
|
| | static QPixmap forbidden_type_class_icon
|
| | = BitmapFactory().merge(type_class_icon, forbidden_icon, BitmapFactoryInst::BottomLeft);
|
| | static QPixmap forbidden_method_icon
|
| | = BitmapFactory().merge(method_icon, forbidden_icon, BitmapFactoryInst::BottomLeft);
|
| | static QPixmap forbidden_member_icon
|
| | = BitmapFactory().merge(member_icon, forbidden_icon, BitmapFactoryInst::BottomLeft);
|
| | static QPixmap forbidden_property_icon
|
| | = BitmapFactory().merge(property_icon, forbidden_icon, BitmapFactoryInst::BottomLeft);
|
| |
|
| | this->validObject = true;
|
| | QString context = extractContext(line);
|
| | context = context.simplified();
|
| | QMap<QString, CallTip> tips = extractTips(context);
|
| | clear();
|
| | for (QMap<QString, CallTip>::Iterator it = tips.begin(); it != tips.end(); ++it) {
|
| | addItem(it.key());
|
| | QListWidgetItem* item = this->item(this->count() - 1);
|
| | item->setData(Qt::ToolTipRole, QVariant(it.value().description));
|
| | item->setData(Qt::UserRole, QVariant::fromValue(it.value()));
|
| | switch (it.value().type) {
|
| | case CallTip::Module: {
|
| | item->setIcon((this->validObject ? type_module_icon : forbidden_type_module_icon));
|
| | } break;
|
| | case CallTip::Class: {
|
| | item->setIcon((this->validObject ? type_class_icon : forbidden_type_class_icon));
|
| | } break;
|
| | case CallTip::Method: {
|
| | item->setIcon((this->validObject ? method_icon : forbidden_method_icon));
|
| | } break;
|
| | case CallTip::Member: {
|
| | item->setIcon((this->validObject ? member_icon : forbidden_member_icon));
|
| | } break;
|
| | case CallTip::Property: {
|
| | item->setIcon((this->validObject ? property_icon : forbidden_property_icon));
|
| | } break;
|
| | default:
|
| | break;
|
| | }
|
| | }
|
| |
|
| | if (count() == 0) {
|
| | return;
|
| | }
|
| |
|
| |
|
| | int h = 0;
|
| | int w = 0;
|
| | for (int i = 0; i < count(); ++i) {
|
| | QRect r = visualItemRect(item(i));
|
| | w = qMax(w, r.width());
|
| | h += r.height();
|
| | }
|
| |
|
| |
|
| | w += 2 * frameWidth();
|
| | h += 2 * frameWidth();
|
| |
|
| |
|
| | QTextCursor cursor = textEdit->textCursor();
|
| | this->cursorPos = cursor.position();
|
| | QRect rect = textEdit->cursorRect(cursor);
|
| | int posX = rect.x();
|
| | int posY = rect.y();
|
| | int boxH = h;
|
| |
|
| |
|
| | if (posY > textEdit->viewport()->height() / 2) {
|
| | h = qMin(qMin(h, posY), 250);
|
| | if (h < boxH) {
|
| | w += textEdit->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
|
| | }
|
| | setGeometry(posX, posY - h, w, h);
|
| | }
|
| | else {
|
| | h = qMin(qMin(h, textEdit->viewport()->height() - fontMetrics().height() - posY), 250);
|
| | if (h < boxH) {
|
| | w += textEdit->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
|
| | }
|
| | setGeometry(posX, posY + fontMetrics().height(), w, h);
|
| | }
|
| |
|
| | setCurrentRow(0);
|
| | show();
|
| | }
|
| |
|
| | void CallTipsList::showEvent(QShowEvent* e)
|
| | {
|
| | QListWidget::showEvent(e);
|
| |
|
| | qApp->installEventFilter(this);
|
| | }
|
| |
|
| | void CallTipsList::hideEvent(QHideEvent* e)
|
| | {
|
| | QListWidget::hideEvent(e);
|
| | qApp->removeEventFilter(this);
|
| | }
|
| |
|
| | |
| | |
| | |
| |
|
| | bool CallTipsList::eventFilter(QObject* watched, QEvent* event)
|
| | {
|
| |
|
| |
|
| | if (watched->inherits("QLabel")) {
|
| | auto label = qobject_cast<QLabel*>(watched);
|
| |
|
| | if (label->windowFlags() & Qt::ToolTip && event->type() == QEvent::Timer) {
|
| | return true;
|
| | }
|
| | }
|
| | if (isVisible() && watched == textEdit->viewport()) {
|
| | if (event->type() == QEvent::MouseButtonPress) {
|
| | hide();
|
| | }
|
| | }
|
| | else if (isVisible() && watched == textEdit) {
|
| | if (event->type() == QEvent::KeyPress) {
|
| | auto ke = static_cast<QKeyEvent*>(event);
|
| | if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
|
| | keyPressEvent(ke);
|
| | return true;
|
| | }
|
| | else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
|
| | keyPressEvent(ke);
|
| | return true;
|
| | }
|
| | else if (ke->key() == Qt::Key_Escape) {
|
| | hide();
|
| | return true;
|
| | }
|
| | else if ((ke->key() == Qt::Key_Minus) && (ke->modifiers() & Qt::ShiftModifier)) {
|
| |
|
| |
|
| | }
|
| | else if (this->hideKeys.indexOf(ke->key()) > -1) {
|
| | Q_EMIT itemActivated(currentItem());
|
| | return false;
|
| | }
|
| | else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter) {
|
| | Q_EMIT itemActivated(currentItem());
|
| | return true;
|
| | }
|
| | else if (ke->key() == Qt::Key_Tab) {
|
| |
|
| | Temporary<bool> tmp(this->doCallCompletion, true);
|
| |
|
| | Q_EMIT itemActivated(currentItem());
|
| | return true;
|
| | }
|
| | else if (this->compKeys.indexOf(ke->key()) > -1) {
|
| | Q_EMIT itemActivated(currentItem());
|
| | return false;
|
| | }
|
| | else if (ke->key() == Qt::Key_Shift || ke->key() == Qt::Key_Control
|
| | || ke->key() == Qt::Key_Meta || ke->key() == Qt::Key_Alt
|
| | || ke->key() == Qt::Key_AltGr) {
|
| |
|
| | return true;
|
| | }
|
| | }
|
| | else if (event->type() == QEvent::KeyRelease) {
|
| | auto ke = static_cast<QKeyEvent*>(event);
|
| | if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down || ke->key() == Qt::Key_PageUp
|
| | || ke->key() == Qt::Key_PageDown) {
|
| | QList<QListWidgetItem*> items = selectedItems();
|
| | if (!items.isEmpty()) {
|
| | QPoint p(width(), 0);
|
| | QString text = items.front()->toolTip();
|
| | if (!text.isEmpty()) {
|
| | QToolTip::showText(mapToGlobal(p), text);
|
| | }
|
| | else {
|
| | QToolTip::showText(p, QString());
|
| | }
|
| | }
|
| | return true;
|
| | }
|
| | }
|
| | else if (event->type() == QEvent::FocusOut) {
|
| | if (!hasFocus()) {
|
| | hide();
|
| | }
|
| | }
|
| | }
|
| |
|
| | return QListWidget::eventFilter(watched, event);
|
| | }
|
| |
|
| | void CallTipsList::callTipItemActivated(QListWidgetItem* item)
|
| | {
|
| | hide();
|
| | if (!item->isSelected()) {
|
| | return;
|
| | }
|
| |
|
| | QString text = item->text();
|
| | QTextCursor cursor = textEdit->textCursor();
|
| | cursor.setPosition(this->cursorPos);
|
| | cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
|
| | QString sel = cursor.selectedText();
|
| | if (!sel.isEmpty()) {
|
| |
|
| | const QChar underscore = QLatin1Char('_');
|
| | const QChar ch = sel.at(sel.size() - 1);
|
| | if (!ch.isLetterOrNumber() && ch != underscore) {
|
| | cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
|
| | }
|
| | }
|
| | cursor.insertText(text);
|
| |
|
| |
|
| | auto callTip = item->data(Qt::UserRole).value<CallTip>();
|
| |
|
| |
|
| | if (this->doCallCompletion
|
| | && (callTip.type == CallTip::Method || callTip.type == CallTip::Class)) {
|
| | cursor.insertText(QLatin1String("()"));
|
| |
|
| | |
| | |
| | |
| |
|
| | QRegularExpression argumentMatcher(
|
| | QRegularExpression::escape(callTip.name) + QLatin1String(R"(\s*\(\s*\w+.*\))")
|
| | );
|
| | argumentMatcher.setPatternOptions(
|
| | QRegularExpression::InvertedGreedinessOption
|
| | );
|
| | if (argumentMatcher.match(callTip.description).hasMatch()) {
|
| |
|
| | cursor.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, 1);
|
| | textEdit->setTextCursor(cursor);
|
| | }
|
| | }
|
| | textEdit->ensureCursorVisible();
|
| |
|
| | QRect rect = textEdit->cursorRect(cursor);
|
| | int posX = rect.x();
|
| | int posY = rect.y();
|
| |
|
| | QPoint p(posX, posY);
|
| | p = textEdit->mapToGlobal(p);
|
| | QToolTip::showText(p, callTip.parameter);
|
| | }
|
| |
|
| | QString CallTipsList::stripWhiteSpace(const QString& str) const
|
| | {
|
| | QString stripped = str;
|
| | QStringList lines = str.split(QLatin1String("\n"));
|
| | int minspace = std::numeric_limits<int>::max();
|
| | int line = 0;
|
| | for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
|
| | if (it->size() > 0 && line > 0) {
|
| | int space = 0;
|
| | for (int i = 0; i < it->size(); i++) {
|
| | if ((*it)[i] == QLatin1Char('\t')) {
|
| | space++;
|
| | }
|
| | else {
|
| | break;
|
| | }
|
| | }
|
| |
|
| | if (it->size() > space) {
|
| | minspace = std::min<int>(minspace, space);
|
| | }
|
| | }
|
| | }
|
| |
|
| |
|
| | if (minspace > 0 && minspace < std::numeric_limits<int>::max()) {
|
| | int line = 0;
|
| | QStringList strippedlines;
|
| | for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
|
| | if (line == 0 && !it->isEmpty()) {
|
| | strippedlines << *it;
|
| | }
|
| | else if (it->size() > 0 && line > 0) {
|
| | strippedlines << it->mid(minspace);
|
| | }
|
| | }
|
| |
|
| | stripped = strippedlines.join(QLatin1String("\n"));
|
| | }
|
| |
|
| | return stripped;
|
| | }
|
| |
|
| | #include "moc_CallTips.cpp"
|
| |
|