00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "PreCompiled.h"
00025
00026 #ifndef _PreComp_
00027 # include <QApplication>
00028 # include <QKeyEvent>
00029 # include <QLabel>
00030 # include <QTextCursor>
00031 # include <QPlainTextEdit>
00032 # include <QToolTip>
00033 #endif
00034
00035 #include <CXX/Objects.hxx>
00036 #include <Base/Interpreter.h>
00037 #include <Base/PyObjectBase.h>
00038 #include <App/Property.h>
00039 #include <App/PropertyContainer.h>
00040 #include <App/PropertyContainerPy.h>
00041 #include <App/Document.h>
00042 #include <App/DocumentObject.h>
00043 #include <App/DocumentPy.h>
00044 #include <Gui/BitmapFactory.h>
00045 #include <Gui/Document.h>
00046 #include <Gui/DocumentPy.h>
00047 #include "CallTips.h"
00048
00049 using namespace Gui;
00050
00051 CallTipsList::CallTipsList(QPlainTextEdit* parent)
00052 : QListWidget(parent), textEdit(parent), cursorPos(0), validObject(true)
00053 {
00054
00055 QPalette pal = parent->palette();
00056 pal.setColor(QPalette::Inactive, QPalette::Highlight, pal.color(QPalette::Active, QPalette::Highlight));
00057 pal.setColor(QPalette::Inactive, QPalette::HighlightedText, pal.color(QPalette::Active, QPalette::HighlightedText));
00058 parent->setPalette( pal );
00059
00060 connect(this, SIGNAL(itemActivated(QListWidgetItem *)),
00061 this, SLOT(callTipItemActivated(QListWidgetItem *)));
00062
00063 hideKeys.append(Qt::Key_Space);
00064 hideKeys.append(Qt::Key_Exclam);
00065 hideKeys.append(Qt::Key_QuoteDbl);
00066 hideKeys.append(Qt::Key_NumberSign);
00067 hideKeys.append(Qt::Key_Dollar);
00068 hideKeys.append(Qt::Key_Percent);
00069 hideKeys.append(Qt::Key_Ampersand);
00070 hideKeys.append(Qt::Key_Apostrophe);
00071 hideKeys.append(Qt::Key_Asterisk);
00072 hideKeys.append(Qt::Key_Plus);
00073 hideKeys.append(Qt::Key_Comma);
00074 hideKeys.append(Qt::Key_Minus);
00075 hideKeys.append(Qt::Key_Period);
00076 hideKeys.append(Qt::Key_Slash);
00077 hideKeys.append(Qt::Key_Colon);
00078 hideKeys.append(Qt::Key_Semicolon);
00079 hideKeys.append(Qt::Key_Less);
00080 hideKeys.append(Qt::Key_Equal);
00081 hideKeys.append(Qt::Key_Greater);
00082 hideKeys.append(Qt::Key_Question);
00083 hideKeys.append(Qt::Key_At);
00084 hideKeys.append(Qt::Key_Backslash);
00085
00086 compKeys.append(Qt::Key_ParenLeft);
00087 compKeys.append(Qt::Key_ParenRight);
00088 compKeys.append(Qt::Key_BracketLeft);
00089 compKeys.append(Qt::Key_BracketRight);
00090 compKeys.append(Qt::Key_BraceLeft);
00091 compKeys.append(Qt::Key_BraceRight);
00092 }
00093
00094 CallTipsList::~CallTipsList()
00095 {
00096 }
00097
00098 void CallTipsList::keyboardSearch(const QString& wordPrefix)
00099 {
00100
00101 for (int i=0; i<count(); ++i) {
00102 QString text = item(i)->text();
00103 if (text.startsWith(wordPrefix)) {
00104 setCurrentRow(i);
00105 return;
00106 }
00107 }
00108
00109
00110 for (int i=0; i<count(); ++i) {
00111 QString text = item(i)->text();
00112 if (text.startsWith(wordPrefix, Qt::CaseInsensitive)) {
00113 setCurrentRow(i);
00114 return;
00115 }
00116 }
00117
00118 setItemSelected(currentItem(), false);
00119 }
00120
00121 void CallTipsList::validateCursor()
00122 {
00123 QTextCursor cursor = textEdit->textCursor();
00124 int currentPos = cursor.position();
00125 if (currentPos < this->cursorPos) {
00126 hide();
00127 }
00128 else {
00129 cursor.setPosition(this->cursorPos);
00130 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
00131 QString word = cursor.selectedText();
00132 if (currentPos > this->cursorPos+word.length()) {
00133 hide();
00134 }
00135 else if (!word.isEmpty()){
00136
00137
00138
00139 keyboardSearch(word);
00140 }
00141 }
00142 }
00143
00144 QString CallTipsList::extractContext(const QString& line) const
00145 {
00146 int len = line.size();
00147 int index = len-1;
00148 for (int i=0; i<len; i++) {
00149 int pos = len-1-i;
00150 const char ch = line.at(pos).toAscii();
00151 if ((ch >= 48 && ch <= 57) ||
00152 (ch >= 65 && ch <= 90) ||
00153 (ch >= 97 && ch <= 122) ||
00154 (ch == '.') || (ch == '_'))
00155 index = pos;
00156 else
00157 break;
00158 }
00159
00160 return line.mid(index);
00161 }
00162
00163 QMap<QString, CallTip> CallTipsList::extractTips(const QString& context) const
00164 {
00165 Base::PyGILStateLocker lock;
00166 QMap<QString, CallTip> tips;
00167 if (context.isEmpty())
00168 return tips;
00169
00170 try {
00171 QStringList items = context.split(QLatin1Char('.'));
00172 Py::Module module("__main__");
00173 Py::Dict dict = module.getDict();
00174 QString modname = items.front();
00175 items.pop_front();
00176 if (!dict.hasKey(std::string(modname.toAscii())))
00177 return tips;
00178
00179
00180 Py::Object obj = dict.getItem(std::string(modname.toAscii()));
00181 while (!items.isEmpty()) {
00182 QByteArray name = items.front().toAscii();
00183 std::string attr = name.constData();
00184 items.pop_front();
00185 if (obj.hasAttr(attr))
00186 obj = obj.getAttr(attr);
00187 else
00188 return tips;
00189 }
00190
00191
00192
00193
00194
00195
00196
00197
00198 Py::Object type(PyObject_Type(obj.ptr()), true);
00199 Py::Object inst = obj;
00200 union PyType_Object typeobj = {&Base::PyObjectBase::Type};
00201 bool subclass = (PyObject_IsSubclass(type.ptr(), typeobj.o) == 1);
00202 if (subclass) obj = type;
00203
00204
00205 if (PyObject_IsInstance(inst.ptr(), typeobj.o) == 1) {
00206 Base::PyObjectBase* baseobj = static_cast<Base::PyObjectBase*>(inst.ptr());
00207 const_cast<CallTipsList*>(this)->validObject = baseobj->isValid();
00208 }
00209 else {
00210
00211 PyErr_Clear();
00212 }
00213
00214 Py::List list(PyObject_Dir(obj.ptr()), true);
00215
00216
00217
00218 union PyType_Object proptypeobj = {&App::PropertyContainerPy::Type};
00219 if (PyObject_IsSubclass(type.ptr(), proptypeobj.o) == 1) {
00220
00221
00222 extractTipsFromProperties(inst, tips);
00223 }
00224
00225
00226
00227 union PyType_Object appdoctypeobj = {&App::DocumentPy::Type};
00228 if (PyObject_IsSubclass(type.ptr(), appdoctypeobj.o) == 1) {
00229 App::DocumentPy* docpy = (App::DocumentPy*)(inst.ptr());
00230 App::Document* document = docpy->getDocumentPtr();
00231
00232 if (document) {
00233 std::vector<App::DocumentObject*> objects = document->getObjects();
00234 Py::List list;
00235 for (std::vector<App::DocumentObject*>::iterator it = objects.begin(); it != objects.end(); ++it)
00236 list.append(Py::String((*it)->getNameInDocument()));
00237 extractTipsFromObject(inst, list, tips);
00238 }
00239 }
00240
00241
00242
00243 union PyType_Object guidoctypeobj = {&Gui::DocumentPy::Type};
00244 if (PyObject_IsSubclass(type.ptr(), guidoctypeobj.o) == 1) {
00245 Gui::DocumentPy* docpy = (Gui::DocumentPy*)(inst.ptr());
00246 if (docpy->getDocumentPtr()) {
00247 App::Document* document = docpy->getDocumentPtr()->getDocument();
00248
00249 if (document) {
00250 std::vector<App::DocumentObject*> objects = document->getObjects();
00251 Py::List list;
00252 for (std::vector<App::DocumentObject*>::iterator it = objects.begin(); it != objects.end(); ++it)
00253 list.append(Py::String((*it)->getNameInDocument()));
00254 extractTipsFromObject(inst, list, tips);
00255 }
00256 }
00257 }
00258
00259
00260 extractTipsFromObject(obj, list, tips);
00261 }
00262 catch (Py::Exception& e) {
00263
00264 e.clear();
00265 }
00266
00267 return tips;
00268 }
00269
00270 void CallTipsList::extractTipsFromObject(Py::Object& obj, Py::List& list, QMap<QString, CallTip>& tips) const
00271 {
00272 try {
00273 for (Py::List::iterator it = list.begin(); it != list.end(); ++it) {
00274 Py::String attrname(*it);
00275 Py::Object attr = obj.getAttr(attrname.as_string());
00276
00277 CallTip tip;
00278 QString str = QString::fromAscii(attrname.as_string().c_str());
00279 tip.name = str;
00280
00281 if (attr.isCallable()) {
00282 union PyType_Object basetype = {&PyBaseObject_Type};
00283 if (PyObject_IsSubclass(attr.ptr(), basetype.o) == 1) {
00284 tip.type = CallTip::Class;
00285 }
00286 else {
00287 PyErr_Clear();
00288 tip.type = CallTip::Method;
00289 }
00290 }
00291 else if (PyModule_Check(attr.ptr())) {
00292 tip.type = CallTip::Module;
00293 }
00294 else {
00295 tip.type = CallTip::Member;
00296 }
00297
00298 if (str == QLatin1String("__doc__") && attr.isString()) {
00299 Py::Object help = attr;
00300 if (help.isString()) {
00301 Py::String doc(help);
00302 QString longdoc = QString::fromUtf8(doc.as_string().c_str());
00303 int pos = longdoc.indexOf(QLatin1Char('\n'));
00304 pos = qMin(pos, 70);
00305 if (pos < 0)
00306 pos = qMin(longdoc.length(), 70);
00307 tip.description = stripWhiteSpace(longdoc);
00308 tip.parameter = longdoc.left(pos);
00309 }
00310 }
00311 else if (attr.hasAttr("__doc__")) {
00312 Py::Object help = attr.getAttr("__doc__");
00313 if (help.isString()) {
00314 Py::String doc(help);
00315 QString longdoc = QString::fromUtf8(doc.as_string().c_str());
00316 int pos = longdoc.indexOf(QLatin1Char('\n'));
00317 pos = qMin(pos, 70);
00318 if (pos < 0)
00319 pos = qMin(longdoc.length(), 70);
00320 tip.description = stripWhiteSpace(longdoc);
00321 tip.parameter = longdoc.left(pos);
00322 }
00323 }
00324 tips[str] = tip;
00325 }
00326 }
00327 catch (Py::Exception& e) {
00328
00329 e.clear();
00330 }
00331 }
00332
00333 void CallTipsList::extractTipsFromProperties(Py::Object& obj, QMap<QString, CallTip>& tips) const
00334 {
00335 App::PropertyContainerPy* cont = (App::PropertyContainerPy*)(obj.ptr());
00336 App::PropertyContainer* container = cont->getPropertyContainerPtr();
00337
00338 if (!container) return;
00339 std::map<std::string,App::Property*> Map;
00340 container->getPropertyMap(Map);
00341
00342 for (std::map<std::string,App::Property*>::const_iterator It=Map.begin();It!=Map.end();++It) {
00343 CallTip tip;
00344 QString str = QString::fromAscii(It->first.c_str());
00345 tip.name = str;
00346 tip.type = CallTip::Property;
00347 QString longdoc = QString::fromUtf8(container->getPropertyDocumentation(It->second));
00348
00349 if (It->second->isDerivedFrom(Base::Type::fromName("App::PropertyComplexGeoData"))) {
00350 Py::Object data(It->second->getPyObject(), true);
00351 if (data.hasAttr("__doc__")) {
00352 Py::Object help = data.getAttr("__doc__");
00353 if (help.isString()) {
00354 Py::String doc(help);
00355 longdoc = QString::fromUtf8(doc.as_string().c_str());
00356 }
00357 }
00358 }
00359 if (!longdoc.isEmpty()) {
00360 int pos = longdoc.indexOf(QLatin1Char('\n'));
00361 pos = qMin(pos, 70);
00362 if (pos < 0)
00363 pos = qMin(longdoc.length(), 70);
00364 tip.description = stripWhiteSpace(longdoc);
00365 tip.parameter = longdoc.left(pos);
00366 }
00367 tips[str] = tip;
00368 }
00369 }
00370
00371 void CallTipsList::showTips(const QString& line)
00372 {
00373
00374 static QPixmap type_module_icon = BitmapFactory().pixmap("ClassBrowser/type_module");
00375 static QPixmap type_class_icon = BitmapFactory().pixmap("ClassBrowser/type_class");
00376 static QPixmap method_icon = BitmapFactory().pixmap("ClassBrowser/method");
00377 static QPixmap member_icon = BitmapFactory().pixmap("ClassBrowser/member");
00378 static QPixmap property_icon = BitmapFactory().pixmap("ClassBrowser/property");
00379
00380
00381 static const char * const forbidden_xpm[]={
00382 "8 8 3 1",
00383 ". c None",
00384 "# c #ff0000",
00385 "a c #ffffff",
00386 "..####..",
00387 ".######.",
00388 "########",
00389 "#aaaaaa#",
00390 "#aaaaaa#",
00391 "########",
00392 ".######.",
00393 "..####.."};
00394 static QPixmap forbidden_icon(forbidden_xpm);
00395 static QPixmap forbidden_type_module_icon = BitmapFactory().merge(type_module_icon,forbidden_icon,BitmapFactoryInst::BottomLeft);
00396 static QPixmap forbidden_type_class_icon = BitmapFactory().merge(type_class_icon,forbidden_icon,BitmapFactoryInst::BottomLeft);
00397 static QPixmap forbidden_method_icon = BitmapFactory().merge(method_icon,forbidden_icon,BitmapFactoryInst::BottomLeft);
00398 static QPixmap forbidden_member_icon = BitmapFactory().merge(member_icon,forbidden_icon,BitmapFactoryInst::BottomLeft);
00399 static QPixmap forbidden_property_icon = BitmapFactory().merge(property_icon,forbidden_icon,BitmapFactoryInst::BottomLeft);
00400
00401 this->validObject = true;
00402 QString context = extractContext(line);
00403 QMap<QString, CallTip> tips = extractTips(context);
00404 clear();
00405 for (QMap<QString, CallTip>::Iterator it = tips.begin(); it != tips.end(); ++it) {
00406 addItem(it.key());
00407 QListWidgetItem *item = this->item(this->count()-1);
00408 item->setData(Qt::ToolTipRole, QVariant(it.value().description));
00409 item->setData(Qt::UserRole, QVariant(it.value().parameter));
00410 switch (it.value().type)
00411 {
00412 case CallTip::Module:
00413 {
00414 item->setIcon((this->validObject ? type_module_icon : forbidden_type_module_icon));
00415 } break;
00416 case CallTip::Class:
00417 {
00418 item->setIcon((this->validObject ? type_class_icon : forbidden_type_class_icon));
00419 } break;
00420 case CallTip::Method:
00421 {
00422 item->setIcon((this->validObject ? method_icon : forbidden_method_icon));
00423 } break;
00424 case CallTip::Member:
00425 {
00426 item->setIcon((this->validObject ? member_icon : forbidden_member_icon));
00427 } break;
00428 case CallTip::Property:
00429 {
00430 item->setIcon((this->validObject ? property_icon : forbidden_property_icon));
00431 } break;
00432 default:
00433 break;
00434 }
00435 }
00436
00437 if (count()==0)
00438 return;
00439
00440
00441 int h = 0;
00442 int w = 0;
00443 for (int i = 0; i < count(); ++i) {
00444 QRect r = visualItemRect(item(i));
00445 w = qMax(w, r.width());
00446 h += r.height();
00447 }
00448
00449
00450 w += 2*frameWidth();
00451 h += 2*frameWidth();
00452
00453
00454 QTextCursor cursor = textEdit->textCursor();
00455 this->cursorPos = cursor.position();
00456 QRect rect = textEdit->cursorRect(cursor);
00457 int posX = rect.x();
00458 int posY = rect.y();
00459 int boxH = h;
00460
00461
00462 if (posY > textEdit->viewport()->height()/2) {
00463 h = qMin(qMin(h,posY), 250);
00464 if (h < boxH)
00465 w += textEdit->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
00466 setGeometry(posX,posY-h, w, h);
00467 }
00468 else {
00469 h = qMin(qMin(h,textEdit->viewport()->height()-fontMetrics().height()-posY), 250);
00470 if (h < boxH)
00471 w += textEdit->style()->pixelMetric(QStyle::PM_ScrollBarExtent);
00472 setGeometry(posX, posY+fontMetrics().height(), w, h);
00473 }
00474
00475 setCurrentRow(0);
00476 show();
00477 }
00478
00479 void CallTipsList::showEvent(QShowEvent* e)
00480 {
00481 QListWidget::showEvent(e);
00482
00483 qApp->installEventFilter(this);
00484 }
00485
00486 void CallTipsList::hideEvent(QHideEvent* e)
00487 {
00488 QListWidget::hideEvent(e);
00489 qApp->removeEventFilter(this);
00490 }
00491
00496 bool CallTipsList::eventFilter(QObject * watched, QEvent * event)
00497 {
00498
00499
00500 if (watched->inherits("QLabel")) {
00501 QLabel* label = qobject_cast<QLabel*>(watched);
00502
00503 if (label->windowFlags() & Qt::ToolTip && event->type() == QEvent::Timer)
00504 return true;
00505 }
00506 if (isVisible() && watched == textEdit->viewport()) {
00507 if (event->type() == QEvent::MouseButtonPress)
00508 hide();
00509 }
00510 else if (isVisible() && watched == textEdit) {
00511 if (event->type() == QEvent::KeyPress) {
00512 QKeyEvent* ke = (QKeyEvent*)event;
00513 if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down) {
00514 keyPressEvent(ke);
00515 return true;
00516 }
00517 else if (ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
00518 keyPressEvent(ke);
00519 return true;
00520 }
00521 else if (ke->key() == Qt::Key_Escape) {
00522 hide();
00523 return true;
00524 }
00525 else if (this->hideKeys.indexOf(ke->key()) > -1) {
00526 itemActivated(currentItem());
00527 return false;
00528 }
00529 else if (ke->key() == Qt::Key_Return || ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Tab) {
00530 itemActivated(currentItem());
00531 return true;
00532 }
00533 else if (this->compKeys.indexOf(ke->key()) > -1) {
00534 itemActivated(currentItem());
00535 return false;
00536 }
00537 else if (ke->key() == Qt::Key_Shift || ke->key() == Qt::Key_Control ||
00538 ke->key() == Qt::Key_Meta || ke->key() == Qt::Key_Alt ||
00539 ke->key() == Qt::Key_AltGr) {
00540
00541 return true;
00542 }
00543 }
00544 else if (event->type() == QEvent::KeyRelease) {
00545 QKeyEvent* ke = (QKeyEvent*)event;
00546 if (ke->key() == Qt::Key_Up || ke->key() == Qt::Key_Down ||
00547 ke->key() == Qt::Key_PageUp || ke->key() == Qt::Key_PageDown) {
00548 QList<QListWidgetItem *> items = selectedItems();
00549 if (!items.isEmpty()) {
00550 QPoint p(width(), 0);
00551 QString text = items.front()->toolTip();
00552 if (!text.isEmpty()){
00553 QToolTip::showText(mapToGlobal(p), text);
00554 } else {
00555 QToolTip::showText(p, QString());
00556 }
00557 }
00558 return true;
00559 }
00560 }
00561 else if (event->type() == QEvent::FocusOut) {
00562 if (!hasFocus())
00563 hide();
00564 }
00565 }
00566
00567 return QListWidget::eventFilter(watched, event);
00568 }
00569
00570 void CallTipsList::callTipItemActivated(QListWidgetItem *item)
00571 {
00572 hide();
00573 if (!isItemSelected(item)) return;
00574
00575 QString text = item->text();
00576 QTextCursor cursor = textEdit->textCursor();
00577 cursor.setPosition(this->cursorPos);
00578 cursor.movePosition(QTextCursor::EndOfWord, QTextCursor::KeepAnchor);
00579 QString sel = cursor.selectedText();
00580 if (!sel.isEmpty()) {
00581
00582 const QChar underscore = QLatin1Char('_');
00583 const QChar ch = sel.at(sel.count()-1);
00584 if (!ch.isLetterOrNumber() && ch != underscore)
00585 cursor.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor);
00586 }
00587 cursor.insertText( text );
00588 textEdit->ensureCursorVisible();
00589
00590 QRect rect = textEdit->cursorRect(cursor);
00591 int posX = rect.x();
00592 int posY = rect.y();
00593
00594 QPoint p(posX, posY);
00595 p = textEdit->mapToGlobal(p);
00596 QToolTip::showText(p, item->data(Qt::UserRole).toString());
00597 }
00598
00599 QString CallTipsList::stripWhiteSpace(const QString& str) const
00600 {
00601 QString stripped = str;
00602 QStringList lines = str.split(QLatin1String("\n"));
00603 int minspace=INT_MAX;
00604 int line=0;
00605 for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
00606 if (it->count() > 0 && line > 0) {
00607 int space = 0;
00608 for (int i=0; i<it->count(); i++) {
00609 if ((*it)[i] == QLatin1Char('\t'))
00610 space++;
00611 else
00612 break;
00613 }
00614
00615 if (it->count() > space)
00616 minspace = std::min<int>(minspace, space);
00617 }
00618 }
00619
00620
00621 if (minspace > 0 && minspace < INT_MAX) {
00622 int line=0;
00623 QStringList strippedlines;
00624 for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
00625 if (line == 0 && !it->isEmpty()) {
00626 strippedlines << *it;
00627 }
00628 else if (it->count() > 0 && line > 0) {
00629 strippedlines << it->mid(minspace);
00630 }
00631 }
00632
00633 stripped = strippedlines.join(QLatin1String("\n"));
00634 }
00635
00636 return stripped;
00637 }
00638
00639 #include "moc_CallTips.cpp"