diff --git a/src/plugins/designer/workbenchintegration.cpp b/src/plugins/designer/workbenchintegration.cpp index 4e49218a5c4..8cedef14b80 100644 --- a/src/plugins/designer/workbenchintegration.cpp +++ b/src/plugins/designer/workbenchintegration.cpp @@ -68,8 +68,8 @@ WorkbenchIntegration::WorkbenchIntegration(QDesignerFormEditorInterface *core, F setResourceFileWatcherBehaviour(QDesignerIntegration::ReloadSilently); setResourceEditingEnabled(false); setSlotNavigationEnabled(true); - connect(this, SIGNAL(navigateToSlot(QString, QString)), - this, SLOT(slotNavigateToSlot(QString, QString))); + connect(this, SIGNAL(navigateToSlot(QString, QString, QStringList)), + this, SLOT(slotNavigateToSlot(QString, QString, QStringList))); } void WorkbenchIntegration::updateSelection() @@ -87,7 +87,7 @@ QWidget *WorkbenchIntegration::containerWindow(QWidget * /*widget*/) const return fw->integrationContainer(); } -QList WorkbenchIntegration::findDocuments(const QString &uiFileName) const +static QList findDocumentsIncluding(const QString &fileName, bool checkFileNameOnly) { Core::ICore *core = ExtensionSystem::PluginManager::instance()->getObject(); CppTools::CppModelManagerInterface *cppModelManager = @@ -99,19 +99,34 @@ QList WorkbenchIntegration::findDocuments(const QString &uiFileNa foreach (Document::Ptr doc, docTable) { // we go through all documents QStringList includes = doc->includedFiles(); foreach (QString include, includes) { - const QFileInfo fi(include); // TODO: we should match an absolute path. Currently uiFileName is a file name only. - if (fi.fileName() == uiFileName) { // we are only interested in docs which includes our ui file - docList.append(doc); + if (checkFileNameOnly) { + const QFileInfo fi(include); + if (fi.fileName() == fileName) { // we are only interested in docs which includes fileName only + docList.append(doc); + } + } else { + if (include == fileName) + docList.append(doc); } } } return docList; } - - -Class *WorkbenchIntegration::findClass(Namespace *parentNameSpace, const QString &uiClassName) const +static Class *findClass(Namespace *parentNameSpace, const QString &uiClassName, QString *namespaceName) { + // construct proper ui class name, take into account namespaced ui class name + QString className1; + QString className2; + int indexOfScope = uiClassName.lastIndexOf(QLatin1String("::")); + if (indexOfScope < 0) { + className1 = QLatin1String("Ui::") + uiClassName; + className2 = QLatin1String("Ui_") + uiClassName; + } else { + className1 = uiClassName.left(indexOfScope + 2) + QLatin1String("Ui::") + uiClassName.mid(indexOfScope + 2); + className2 = uiClassName.left(indexOfScope + 2) + QLatin1String("Ui_") + uiClassName.mid(indexOfScope + 2); + } + for (unsigned i = 0; i < parentNameSpace->memberCount(); i++) { // we go through all namespace members if (Class *cl = parentNameSpace->memberAt(i)->asClass()) { // we have found a class - we are interested in classes only Overview o; @@ -119,9 +134,6 @@ Class *WorkbenchIntegration::findClass(Namespace *parentNameSpace, const QString for (unsigned j = 0; j < cl->memberCount(); j++) { // we go through class members const Declaration *decl = cl->memberAt(j)->asDeclaration(); if (decl) { // we want to know if the class contains a member (so we look into a declaration) of uiClassName type - const QString v1 = QLatin1String("Ui::") + uiClassName; // TODO: handle also the case of namespaced class name - const QString v2 = QLatin1String("Ui_") + uiClassName; - NamedType *nt = decl->type()->asNamedType(); // handle pointers to member variables @@ -131,35 +143,56 @@ Class *WorkbenchIntegration::findClass(Namespace *parentNameSpace, const QString if (nt) { Overview typeOverview; const QString memberClass = typeOverview.prettyName(nt->name()); - if (memberClass == v1 || memberClass == v2) // simple match here + if (memberClass == className1 || memberClass == className2) // names match return cl; + // memberClass can be shorter (can have some namespaces cut because of e.g. "using namespace" declaration) + if (memberClass == className1.right(memberClass.length())) { // memberClass lenght <= className length + const QString namespacePrefix = className1.left(className1.length() - memberClass.length()); + if (namespacePrefix.right(2) == QLatin1String("::")) + return cl; + } + // the same as above but for className2 + if (memberClass == className2.right(memberClass.length())) { // memberClass lenght <= className length + const QString namespacePrefix = className2.left(className1.length() - memberClass.length()); + if (namespacePrefix.right(2) == QLatin1String("::")) + return cl; + } } } } } else if (Namespace *ns = parentNameSpace->memberAt(i)->asNamespace()) { - return findClass(ns, uiClassName); + Overview o; + QString tempNS = *namespaceName + o.prettyName(ns->name()) + QLatin1String("::"); + Class *cl = findClass(ns, uiClassName, &tempNS); + if (cl) { + *namespaceName = tempNS; + return cl; + } } } return 0; } -Function *WorkbenchIntegration::findFunction(Class *cl, const QString &functionName) const +static Function *findDeclaration(Class *cl, const QString &functionName) { - // TODO: match properly function name and argument list, for now we match only function name - // (Roberto's suggestion: use QtMethodAST for that, enclosing function with SIGNAL() - // then it becames an expression). Robetro's also proposed he can add public methods to parse declarations - - // Quick implementation start - QString funName = functionName.left(functionName.indexOf(QLatin1Char('('))); - // Quick implementation end + const QString funName = QString::fromUtf8(QMetaObject::normalizedSignature(functionName.toUtf8())); for (unsigned j = 0; j < cl->memberCount(); j++) { // go through all members const Declaration *decl = cl->memberAt(j)->asDeclaration(); if (decl) { // we are interested only in declarations (can be decl of method or of a field) Function *fun = decl->type()->asFunction(); if (fun) { // we are only interested in declarations of methods - Overview typeOverview; - const QString memberFunction = typeOverview.prettyName(fun->name()); - if (memberFunction == funName) // simple match (we match only fun name, we should match also arguments) + Overview overview; + QString memberFunction = overview.prettyName(fun->name()) + QLatin1Char('('); + for (uint i = 0; i < fun->argumentCount(); i++) { // we build argument types string + Argument *arg = fun->argumentAt(i)->asArgument(); + if (i > 0) + memberFunction += QLatin1Char(','); + memberFunction += overview.prettyType(arg->type()); + } + memberFunction += QLatin1Char(')'); + // we compare normalized signatures + memberFunction = QString::fromUtf8(QMetaObject::normalizedSignature(memberFunction.toUtf8())); + if (memberFunction == funName) // we match function names and argument lists return fun; } } @@ -222,7 +255,7 @@ static bool isCompatible(Function *definition, Symbol *declaration, QualifiedNam } // TODO: remove me, this is taken from cppeditor.cpp. Find some common place for this method -Document::Ptr WorkbenchIntegration::findDefinition(Function *functionDeclaration, int *line) const +static Document::Ptr findDefinition(Function *functionDeclaration, int *line) { Core::ICore *core = ExtensionSystem::PluginManager::instance()->getObject(); CppTools::CppModelManagerInterface *cppModelManager = @@ -283,9 +316,47 @@ Document::Ptr WorkbenchIntegration::findDefinition(Function *functionDeclaration } -void WorkbenchIntegration::addDeclaration(const QString &docFileName, Class *cl, const QString &functionName) const +// TODO: Wait for robust Roberto's code using AST or whatever for that. Current implementation is hackish. +static int findClassEndPosition(const QString &headerContents, int classStartPosition) { - // TODO: add argument names (from designer we get only argument types) + const QString contents = headerContents.mid(classStartPosition); // we start serching from the beginning of class declaration + // We need to find the position of class closing "}" + int openedBlocksCount = 0; // counter of nested {} blocks + int idx = 0; // index of current position in the contents + while (true) { + if (idx < 0 || idx >= contents.length()) // indexOf returned -1, that means we don't have closing comment mark + break; + if (contents.mid(idx, 2) == QLatin1String("//")) { + idx = contents.indexOf(QLatin1Char('\n'), idx + 2) + 1; // drop everything up to the end of line + } else if (contents.mid(idx, 2) == QLatin1String("/*")) { + idx = contents.indexOf(QLatin1String("*/"), idx + 2) + 1; // drop everything up to the nearest */ + } else if (contents.mid(idx, 4) == QLatin1String("'\\\"'")) { + idx += 4; // drop it + } else if (contents.at(idx) == QLatin1Char('\"')) { + do { + idx = contents.indexOf(QLatin1Char('\"'), idx + 1); // drop everything up to the nearest " + } while (idx > 0 && contents.at(idx - 1) == QLatin1Char('\\')); // if the nearest " is preceeded by \ we find next one + if (idx < 0) + break; + idx++; + } else { + if (contents.at(idx) == QLatin1Char('{')) { + openedBlocksCount++; + } else if (contents.at(idx) == QLatin1Char('}')) { + openedBlocksCount--; + if (openedBlocksCount == 0) { + return classStartPosition + idx; + } + } + idx++; + } + } + return -1; +} + +static void addDeclaration(const QString &docFileName, Class *cl, const QString &functionName) +{ + // functionName comes already with argument names (if designer managed to do that) for (unsigned j = 0; j < cl->memberCount(); j++) { // go through all members const Declaration *decl = cl->memberAt(j)->asDeclaration(); if (decl) { // we want to find any method which is a private slot (then we don't need to add "private slots:" statement) @@ -293,7 +364,9 @@ void WorkbenchIntegration::addDeclaration(const QString &docFileName, Class *cl, if (fun) { // we are only interested in declarations of methods if (fun->isSlot() && fun->isPrivate()) { ITextEditable *editable = qobject_cast( - TextEditor::BaseTextEditor::openEditorAt(docFileName, fun->line()/*, fun->column()*/)); // TODO: fun->column() gives me weird number... + TextEditor::BaseTextEditor::openEditorAt(docFileName, fun->line(), fun->column())); + // fun->column() raturns always 0, what can cause trouble in case in one + // line there is: "private slots: void foo();" if (editable) { editable->insert(QLatin1String("void ") + functionName + QLatin1String(";\n ")); } @@ -303,24 +376,87 @@ void WorkbenchIntegration::addDeclaration(const QString &docFileName, Class *cl, } } - // TODO: we didn't find "private slots:", let's add it + // We didn't find any method under "private slots:", let's add "private slots:". Below code + // adds "private slots:" by the end of the class definition. - return; + ITextEditable *editable = qobject_cast( + TextEditor::BaseTextEditor::openEditorAt(docFileName, cl->line(), cl->column())); + if (editable) { + int classEndPosition = findClassEndPosition(editable->contents(), editable->position()); + if (classEndPosition >= 0) { + int line, column; + editable->convertPosition(classEndPosition, &line, &column); // converts back position into a line and column + editable->gotoLine(line, column); // go to position (we should be just before closing } of the class) + editable->insert(QLatin1String("\nprivate slots:\n ") + + QLatin1String("void ") + functionName + QLatin1String(";\n")); + } + } } -void WorkbenchIntegration::slotNavigateToSlot(const QString &objectName, const QString &signalSignature) +static Document::Ptr addDefinition(const QString &headerFileName, const QString &className, + const QString &functionName, int *line) +{ + // we find all documents which include headerFileName + QList docList = findDocumentsIncluding(headerFileName, false); + if (docList.isEmpty()) + return Document::Ptr(); + + QFileInfo headerFI(headerFileName); + const QString headerBaseName = headerFI.baseName(); + const QString headerAbsolutePath = headerFI.absolutePath(); + foreach (Document::Ptr doc, docList) { + QFileInfo sourceFI(doc->fileName()); + // we take only those documents which has the same filename and path (maybe we don't need to compare the path???) + if (headerBaseName == sourceFI.baseName() && headerAbsolutePath == sourceFI.absolutePath()) { + ITextEditable *editable = qobject_cast( // TODO: add the code into appropriate namespace + TextEditor::BaseTextEditor::openEditorAt(doc->fileName(), 0)); + if (editable) { + const QString contents = editable->contents(); + int column; + editable->convertPosition(contents.length(), line, &column); + editable->gotoLine(*line, column); + editable->insert(QLatin1String("\nvoid ") + className + QLatin1String("::") + + functionName + QLatin1String("\n {\n\n }\n")); + *line += 1; + + } + return doc; + } + } + return Document::Ptr(); +} + +static QString addParameterNames(const QString &functionSignature, const QStringList ¶meterNames) +{ + QString functionName = functionSignature.left(functionSignature.indexOf(QLatin1Char('(')) + 1); + QString argumentsString = functionSignature.mid(functionSignature.indexOf(QLatin1Char('(')) + 1); + argumentsString = argumentsString.left(argumentsString.indexOf(QLatin1Char(')'))); + const QStringList arguments = argumentsString.split(QLatin1Char(','), QString::SkipEmptyParts); + for (int i = 0; i < arguments.count(); ++i) { + if (i > 0) + functionName += QLatin1String(", "); + functionName += arguments.at(i); + if (i < parameterNames.count()) + functionName += QLatin1Char(' ') + parameterNames.at(i); + } + functionName += QLatin1Char(')'); + return functionName; +} + +void WorkbenchIntegration::slotNavigateToSlot(const QString &objectName, const QString &signalSignature, + const QStringList ¶meterNames) { const QString currentUiFile = m_few->activeFormWindow()->file()->fileName(); - // TODO: we should pass to findDocuments an absolute path to generated .h file from ui. - // Currently we are guessing the name of ui_<>.h file and pass the file name only to the findDocuments(). + // TODO: we should pass to findDocumentsIncluding an absolute path to generated .h file from ui. + // Currently we are guessing the name of ui_<>.h file and pass the file name only to the findDocumentsIncluding(). // The idea is that the .pro file knows if the .ui files is inside, and the .pro file knows it will // be generating the ui_<>.h file for it, and the .pro file knows what the generated file's name and its absolute path will be. // So we should somehow get that info from project manager (?) const QFileInfo fi(currentUiFile); const QString uicedName = QLatin1String("ui_") + fi.baseName() + QLatin1String(".h"); - QList docList = findDocuments(uicedName); + QList docList = findDocumentsIncluding(uicedName, true); // change to false when we know the absolute path to generated ui_<>.h file if (docList.isEmpty()) return; @@ -329,22 +465,33 @@ void WorkbenchIntegration::slotNavigateToSlot(const QString &objectName, const Q const QString uiClassName = fwi->mainContainer()->objectName(); foreach (Document::Ptr doc, docList) { - Class *cl = findClass(doc->globalNamespace(), uiClassName); + QString namespaceName; // namespace of the class found + Class *cl = findClass(doc->globalNamespace(), uiClassName, &namespaceName); if (cl) { + Overview o; + const QString className = namespaceName + o.prettyName(cl->name()); + QString functionName = QLatin1String("on_") + objectName + QLatin1Char('_') + signalSignature; - Function *fun = findFunction(cl, functionName); + QString functionNameWithParameterNames = addParameterNames(functionName, parameterNames); + Function *fun = findDeclaration(cl, functionName); int line = 0; + Document::Ptr sourceDoc; if (!fun) { // add function declaration to cl - addDeclaration(doc->fileName(), cl, functionName); - // TODO: add function definition to cpp file + addDeclaration(doc->fileName(), cl, functionNameWithParameterNames); + // add function definition to cpp file + sourceDoc = addDefinition(doc->fileName(), className, functionNameWithParameterNames, &line); } else { - doc = findDefinition(fun, &line); + sourceDoc = findDefinition(fun, &line); + if (!sourceDoc) { + // add function definition to cpp file + sourceDoc = addDefinition(doc->fileName(), className, functionNameWithParameterNames, &line); + } } - if (doc) { + if (sourceDoc) { // jump to function definition - TextEditor::BaseTextEditor::openEditorAt(doc->fileName(), line); + TextEditor::BaseTextEditor::openEditorAt(sourceDoc->fileName(), line); } return; } diff --git a/src/plugins/designer/workbenchintegration.h b/src/plugins/designer/workbenchintegration.h index c13e27620e3..ef8d946adbb 100644 --- a/src/plugins/designer/workbenchintegration.h +++ b/src/plugins/designer/workbenchintegration.h @@ -40,7 +40,7 @@ namespace Designer { namespace Internal { - + class FormEditorW; class WorkbenchIntegration : public qdesigner_internal::QDesignerIntegration { @@ -55,14 +55,8 @@ public: public slots: void updateSelection(); private slots: - void slotNavigateToSlot(const QString &objectName, const QString &signalSignature); + void slotNavigateToSlot(const QString &objectName, const QString &signalSignature, const QStringList ¶meterNames); private: - QList findDocuments(const QString &uiFileName) const; - CPlusPlus::Class *findClass(CPlusPlus::Namespace *parentNameSpace, const QString &uiClassName) const; - CPlusPlus::Function *findFunction(CPlusPlus::Class *cl, const QString &functionName) const; - CPlusPlus::Document::Ptr findDefinition(CPlusPlus::Function *functionDeclaration, int *line) const; - void addDeclaration(const QString &docFileName, CPlusPlus::Class *cl, const QString &functionName) const; - FormEditorW *m_few; };