diff --git a/doc/images/qtcreator-refactoring-virtual-function-dialog.png b/doc/images/qtcreator-refactoring-virtual-function-dialog.png new file mode 100644 index 00000000000..b761f33db10 Binary files /dev/null and b/doc/images/qtcreator-refactoring-virtual-function-dialog.png differ diff --git a/doc/src/editors/creator-editors.qdoc b/doc/src/editors/creator-editors.qdoc index 9285f058fde..504559809d3 100644 --- a/doc/src/editors/creator-editors.qdoc +++ b/doc/src/editors/creator-editors.qdoc @@ -1957,6 +1957,19 @@ \endcode \li Function call or class name + \row + \li Insert (Pure) Virtual Functions + \li Select an insertion mode: + \list + \li Insert only declarations. + \li Insert declarations and the corresponding definitions inside the class. + \li Insert declarations and the corresponding definitions outside the class. + \li Insert declarations and the corresponding definitions in the implementation file + (only if an implementation file exists). + \endlist + \image qtcreator-refactoring-virtual-function-dialog.png + + \li Class or base class name \endtable diff --git a/src/plugins/cppeditor/cppeditorplugin.h b/src/plugins/cppeditor/cppeditorplugin.h index d5bcf52bead..d8714bb99c9 100644 --- a/src/plugins/cppeditor/cppeditorplugin.h +++ b/src/plugins/cppeditor/cppeditorplugin.h @@ -199,6 +199,18 @@ private slots: void test_quickfix_AssignToLocalVariable_noReturnClass(); void test_quickfix_AssignToLocalVariable_noReturnFunc(); + void test_quickfix_InsertVirtualMethods_onlyDecl(); + void test_quickfix_InsertVirtualMethods_onlyDeclWithoutVirtual(); + void test_quickfix_InsertVirtualMethods_Access(); + void test_quickfix_InsertVirtualMethods_Superclass(); + void test_quickfix_InsertVirtualMethods_SuperclassOverride(); + void test_quickfix_InsertVirtualMethods_PureVirtualOnlyDecl(); + void test_quickfix_InsertVirtualMethods_PureVirtualInside(); + void test_quickfix_InsertVirtualMethods_inside(); + void test_quickfix_InsertVirtualMethods_outside(); + void test_quickfix_InsertVirtualMethods_implementationFile(); + void test_quickfix_InsertVirtualMethods_notrigger_allImplemented(); + // The following tests depend on the projects that are loaded on startup // and will be skipped in case no projects are loaded. void test_openEachFile(); diff --git a/src/plugins/cppeditor/cppquickfix_test.cpp b/src/plugins/cppeditor/cppquickfix_test.cpp index 6b618f290b9..a1176692f84 100644 --- a/src/plugins/cppeditor/cppquickfix_test.cpp +++ b/src/plugins/cppeditor/cppquickfix_test.cpp @@ -2014,3 +2014,420 @@ void CppEditorPlugin::test_quickfix_AssignToLocalVariable_noReturnFunc() TestCase data(original, expected); data.run(&factory); } + +/// Test dialog for insert virtual functions +class InsertVirtualMethodsDialogTest : public InsertVirtualMethodsDialog +{ +public: + InsertVirtualMethodsDialogTest(ImplementationMode mode, bool virt, QWidget *parent = 0) + : InsertVirtualMethodsDialog(parent) + { + setImplementationsMode(mode); + setInsertKeywordVirtual(virt); + } + + bool gather() + { + return true; + } + + ImplementationMode implementationMode() const + { + return m_implementationMode; + } + + bool insertKeywordVirtual() const + { + return m_insertKeywordVirtual; + } +}; + +/// Check: Insert only declarations +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_onlyDecl() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert only declarations vithout virtual keyword +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_onlyDeclWithoutVirtual() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " int virtualFuncA();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, false)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Are access specifiers considered +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_Access() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "protected:\n" + " virtual int b();\n" + "private:\n" + " virtual int c();\n" + "public slots:\n" + " virtual int d();\n" + "protected slots:\n" + " virtual int e();\n" + "private slots:\n" + " virtual int f();\n" + "signals:\n" + " virtual int g();\n" + "};\n\n" + "class Der@ived : public BaseA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "protected:\n" + " virtual int b();\n" + "private:\n" + " virtual int c();\n" + "public slots:\n" + " virtual int d();\n" + "protected slots:\n" + " virtual int e();\n" + "private slots:\n" + " virtual int f();\n" + "signals:\n" + " virtual int g();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n\n" + "protected:\n" + " virtual int b();\n\n" + "private:\n" + " virtual int c();\n\n" + "public slots:\n" + " virtual int d();\n\n" + "protected slots:\n" + " virtual int e();\n\n" + "private slots:\n" + " virtual int f();\n\n" + "signals:\n" + " virtual int g();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Is a base class of a base class considered. +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_Superclass() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b();\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int b();\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseB interface\n" + "public:\n" + " virtual int b();\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Do not insert reimplemented functions twice. +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_SuperclassOverride() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class BaseB : public BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class Der@ived : public BaseB {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert only declarations for pure virtual function +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_PureVirtualOnlyDecl() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOnlyDeclarations, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert pure virtual functions inside class +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_PureVirtualInside() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA() = 0;\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA()\n" + " {\n" + " }\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeInsideClass, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert inside class +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_inside() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA()\n" + " {\n" + " }\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeInsideClass, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert outside class +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_outside() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "int Derived::virtualFuncA()\n" + "{\n" + "}\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOutsideClass, true)); + TestCase data(original, expected); + data.run(&factory); +} + +/// Check: Insert in implementation file +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_implementationFile() +{ + QList testFiles; + QByteArray original; + QByteArray expected; + + // Header File + original = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "public:\n" + " Derived();\n" + "};"; + expected = + "class BaseA {\n" + "public:\n" + " virtual int a();\n" + "};\n\n" + "class Derived : public BaseA {\n" + "public:\n" + " Derived();\n" + "\n" + " // BaseA interface\n" + "public:\n" + " virtual int a();\n" + "};\n"; + testFiles << TestDocument::create(original, expected, QLatin1String("file.h")); + + // Source File + original = "#include \"file.h\"\n"; + expected = + "#include \"file.h\"\n" + "\n\n" + "int Derived::a()\n" + "{\n}\n"; + testFiles << TestDocument::create(original, expected, QLatin1String("file.cpp")); + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeImplementationFile, true)); + TestCase data(testFiles); + data.run(&factory); +} + +/// Check: No trigger: all implemented +void CppEditorPlugin::test_quickfix_InsertVirtualMethods_notrigger_allImplemented() +{ + const QByteArray original = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};"; + const QByteArray expected = + "class BaseA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n\n" + "class Derived : public Bas@eA {\n" + "public:\n" + " virtual int virtualFuncA();\n" + "};\n"; + + InsertVirtualMethods factory(new InsertVirtualMethodsDialogTest( + InsertVirtualMethodsDialog::ModeOutsideClass, true)); + TestCase data(original, expected); + data.run(&factory); +} diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index d319932d4e7..71e03ade4a2 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -33,6 +33,8 @@ #include "cppfunctiondecldeflink.h" #include "cppquickfixassistant.h" +#include + #include #include #include @@ -49,16 +51,33 @@ #include +#include +#include + #include #include +#include +#include +#include #include #include +#include +#include #include +#include +#include #include +#include +#include +#include #include +#include +#include #include #include +#include +#include #include @@ -109,6 +128,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn) plugIn->addAutoReleasedObject(new MoveFuncDefToDecl); plugIn->addAutoReleasedObject(new AssignToLocalVariable); + + plugIn->addAutoReleasedObject(new InsertVirtualMethods); } // In the following anonymous namespace all functions are collected, which could be of interest for @@ -2356,7 +2377,7 @@ public: targetFile->apply(); } - static QString generateDeclaration(Function *function); + static QString generateDeclaration(const Function *function); private: QString m_targetFileName; @@ -2450,7 +2471,7 @@ void InsertDeclFromDef::match(const CppQuickFixInterface &interface, QuickFixOpe } } -QString InsertDeclOperation::generateDeclaration(Function *function) +QString InsertDeclOperation::generateDeclaration(const Function *function) { Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview(); oo.showFunctionSignatures = true; @@ -4284,3 +4305,735 @@ void AssignToLocalVariable::match(const CppQuickFixInterface &interface, QuickFi } } } + +namespace { + +class InsertVirtualMethodsOp : public CppQuickFixOperation +{ +public: + InsertVirtualMethodsOp(const QSharedPointer &interface, + InsertVirtualMethodsDialog *factory) + : CppQuickFixOperation(interface, 0) + , m_factory(factory) + , m_classAST(0) + , m_valid(false) + , m_cppFileName(QString::null) + , m_insertPosDecl(0) + , m_insertPosOutside(0) + , m_functionCount(0) + , m_implementedFunctionCount(0) + { + setDescription(QCoreApplication::translate( + "CppEditor::QuickFix", "Insert Virtual Functions of Base Classes")); + + const QList &path = interface->path(); + const int pathSize = path.size(); + if (pathSize < 2) + return; + + // Determine if cursor is on a class or a base class + if (SimpleNameAST *nameAST = path.at(pathSize - 1)->asSimpleName()) { + if (!interface->isCursorOn(nameAST)) + return; + + if (!(m_classAST = path.at(pathSize - 2)->asClassSpecifier())) { // normal class + int index = pathSize - 2; + const BaseSpecifierAST *baseAST = path.at(index)->asBaseSpecifier();// simple bclass + if (!baseAST) { + if (index > 0 && path.at(index)->asQualifiedName()) // namespaced base class + baseAST = path.at(--index)->asBaseSpecifier(); + } + --index; + if (baseAST && index >= 0) + m_classAST = path.at(index)->asClassSpecifier(); + } + } + if (!m_classAST || !m_classAST->base_clause_list) + return; + + // Determine insert positions + const int endOfClassAST = interface->currentFile()->endOf(m_classAST); + m_insertPosDecl = endOfClassAST - 1; // Skip last "}" + m_insertPosOutside = endOfClassAST + 1; // Step over ";" + + // Determine base classes + QList baseClasses; + QQueue baseClassQueue; + QSet visitedBaseClasses; + if (ClassOrNamespace *clazz = interface->context().lookupType(m_classAST->symbol)) + baseClassQueue.enqueue(clazz); + while (!baseClassQueue.isEmpty()) { + ClassOrNamespace *clazz = baseClassQueue.dequeue(); + visitedBaseClasses.insert(clazz); + const QList bases = clazz->usings(); + foreach (ClassOrNamespace *baseClass, bases) { + foreach (Symbol *symbol, baseClass->symbols()) { + Class *base = symbol->asClass(); + if (base + && (clazz = interface->context().lookupType(symbol)) + && !visitedBaseClasses.contains(clazz) + && !baseClasses.contains(base)) { + baseClasses << base; + baseClassQueue.enqueue(clazz); + } + } + } + } + + // Determine virtual functions + m_factory->classFunctionModel->clear(); + Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + printer.showFunctionSignatures = true; + const TextEditor::FontSettings &fs = + TextEditor::TextEditorSettings::instance()->fontSettings(); + const Format formatReimpFunc = fs.formatFor(C_DISABLED_CODE); + foreach (const Class *clazz, baseClasses) { + QStandardItem *itemBase = new QStandardItem(printer.prettyName(clazz->name())); + itemBase->setData(false, InsertVirtualMethodsDialog::Implemented); + itemBase->setData(qVariantFromValue((void *) clazz), + InsertVirtualMethodsDialog::ClassOrFunction); + const QString baseClassName = printer.prettyName(clazz->name()); + for (Scope::iterator it = clazz->firstMember(); it != clazz->lastMember(); ++it) { + if (const Function *func = (*it)->type()->asFunctionType()) { + if (!func->isVirtual()) + continue; + + // Filter virtual destructors + if (printer.prettyName(func->name()).startsWith(QLatin1Char('~'))) + continue; + + // Filter OQbject's + // - virtual const QMetaObject *metaObject() const; + // - virtual void *qt_metacast(const char *); + // - virtual int qt_metacall(QMetaObject::Call, int, void **); + if (baseClassName == QLatin1String("QObject")) { + if (printer.prettyName(func->name()) == QLatin1String("metaObject")) + continue; + if (printer.prettyName(func->name()) == QLatin1String("qt_metacast")) + continue; + if (printer.prettyName(func->name()) == QLatin1String("qt_metacall")) + continue; + } + + // Do not implement existing functions inside target class + bool funcExistsInClass = false; + const Name *funcName = func->name(); + for (Symbol *symbol = m_classAST->symbol->find(funcName->identifier()); + symbol; symbol = symbol->next()) { + if (!symbol->name() + || !funcName->identifier()->isEqualTo(symbol->identifier())) { + continue; + } + if (symbol->type().isEqualTo(func->type())) { + funcExistsInClass = true; + break; + } + } + + // Do not show when reimplemented from an other class + bool funcReimplemented = false; + for (int i = baseClasses.count() - 1; i >= 0; --i) { + const Class *prevClass = baseClasses.at(i); + if (clazz == prevClass) + break; // reached current class + + for (const Symbol *symbol = prevClass->find(funcName->identifier()); + symbol; symbol = symbol->next()) { + if (!symbol->name() + || !funcName->identifier()->isEqualTo(symbol->identifier())) { + continue; + } + if (symbol->type().isEqualTo(func->type())) { + funcReimplemented = true; + break; + } + } + } + if (funcReimplemented) + continue; + + // Construct function item + const bool isPureVirtual = func->isPureVirtual(); + QString itemName = printer.prettyType(func->type(), func->name()); + if (isPureVirtual) + itemName += QLatin1String(" = 0"); + const QString itemReturnTypeString = printer.prettyType(func->returnType()); + QStandardItem *funcItem = new QStandardItem( + itemName + QLatin1String(" : ") + itemReturnTypeString); + if (!funcExistsInClass) { + funcItem->setCheckable(true); + funcItem->setCheckState(Qt::Checked); + } else { + funcItem->setEnabled(false); + funcItem->setData(formatReimpFunc.foreground(), Qt::ForegroundRole); + if (formatReimpFunc.background().isValid()) + funcItem->setData(formatReimpFunc.background(), Qt::BackgroundRole); + } + + funcItem->setData(qVariantFromValue((void *) func), + InsertVirtualMethodsDialog::ClassOrFunction); + funcItem->setData(isPureVirtual, InsertVirtualMethodsDialog::PureVirtual); + funcItem->setData(acessSpec(*it), InsertVirtualMethodsDialog::AccessSpec); + funcItem->setData(funcExistsInClass, InsertVirtualMethodsDialog::Implemented); + + itemBase->appendRow(funcItem); + + // update internal counters + if (funcExistsInClass) + ++m_implementedFunctionCount; + else + ++m_functionCount; + } + } + if (itemBase->hasChildren()) { + for (int i = 0; i < itemBase->rowCount(); ++i) { + if (itemBase->child(i, 0)->isCheckable()) { + itemBase->setCheckable(true); + itemBase->setTristate(true); + itemBase->setCheckState(Qt::Checked); + itemBase->setData(false, InsertVirtualMethodsDialog::Implemented); + break; + } + } + m_factory->classFunctionModel->invisibleRootItem()->appendRow(itemBase); + } + } + if (!m_factory->classFunctionModel->invisibleRootItem()->hasChildren() + || m_functionCount == 0) { + return; + } + + bool isHeaderFile = false; + m_cppFileName = correspondingHeaderOrSource(interface->fileName(), &isHeaderFile); + m_factory->setHasImplementationFile(isHeaderFile && !m_cppFileName.isEmpty()); + m_factory->setHasReimplementedFunctions(m_implementedFunctionCount != 0); + + m_valid = true; + } + + bool isValid() const + { + return m_valid; + } + + InsertionPointLocator::AccessSpec acessSpec(const Symbol *symbol) + { + const Function *func = symbol->type()->asFunctionType(); + if (!func) + return InsertionPointLocator::Invalid; + if (func->isSignal()) + return InsertionPointLocator::Signals; + + InsertionPointLocator::AccessSpec spec = InsertionPointLocator::Invalid; + if (symbol->isPrivate()) + spec = InsertionPointLocator::Private; + else if (symbol->isProtected()) + spec = InsertionPointLocator::Protected; + else if (symbol->isPublic()) + spec = InsertionPointLocator::Public; + else + return InsertionPointLocator::Invalid; + + if (func->isSlot()) { + switch (spec) { + case InsertionPointLocator::Private: + return InsertionPointLocator::PrivateSlot; + break; + case InsertionPointLocator::Protected: + return InsertionPointLocator::ProtectedSlot; + break; + case InsertionPointLocator::Public: + return InsertionPointLocator::PublicSlot; + break; + default: + return spec; + break; + } + } + return spec; + } + + void perform() + { + if (!m_factory->gather()) + return; + + Core::ICore::settings()->setValue( + QLatin1String("QuickFix/InsertVirtualMethods/insertKeywordVirtual"), + m_factory->insertKeywordVirtual()); + Core::ICore::settings()->setValue( + QLatin1String("QuickFix/InsertVirtualMethods/implementationMode"), + m_factory->implementationMode()); + Core::ICore::settings()->setValue( + QLatin1String("QuickFix/InsertVirtualMethods/hideReimplementedFunctions"), + m_factory->hideReimplementedFunctions()); + + // Insert declarations (and definition if InsideClass) + Overview printer = CppCodeStyleSettings::currentProjectCodeStyleOverview(); + printer.showFunctionSignatures = true; + printer.showReturnTypes = true; + printer.showArgumentNames = true; + ChangeSet headerChangeSet; + for (int i = 0; i < m_factory->classFunctionModel->rowCount(); ++i) { + const QStandardItem *parent = + m_factory->classFunctionModel->invisibleRootItem()->child(i, 0); + if (!parent->isCheckable() || parent->checkState() == Qt::Unchecked) + continue; + const Class *clazz = (const Class *) + parent->data(InsertVirtualMethodsDialog::ClassOrFunction).value(); + + // Add comment + const QString comment = QLatin1String("\n// ") + printer.prettyName(clazz->name()) + + QLatin1String(" interface\n"); + headerChangeSet.insert(m_insertPosDecl, comment); + + // Insert Declarations (+ definitions) + QString lastAccessSpecString; + for (int j = 0; j < parent->rowCount(); ++j) { + const QStandardItem *item = parent->child(j, 0); + if (!item->isCheckable() || item->checkState() == Qt::Unchecked) + continue; + const Function *func = (const Function *) + item->data(InsertVirtualMethodsDialog::ClassOrFunction).value(); + + // Construct declaration + QString declaration = InsertDeclOperation::generateDeclaration(func); + if (m_factory->insertKeywordVirtual()) + declaration = QLatin1String("virtual ") + declaration; + if (m_factory->implementationMode() & InsertVirtualMethodsDialog::ModeInsideClass) + declaration.replace(declaration.size() - 2, 2, QLatin1String("\n{\n}\n")); + const InsertionPointLocator::AccessSpec spec = + static_cast( + item->data(InsertVirtualMethodsDialog::AccessSpec).toInt()); + const QString accessSpecString = InsertionPointLocator::accessSpecToString(spec); + if (accessSpecString != lastAccessSpecString) { + declaration = accessSpecString + declaration; + if (!lastAccessSpecString.isEmpty()) // separate if not direct after the comment + declaration = QLatin1String("\n") + declaration; + lastAccessSpecString = accessSpecString; + } + headerChangeSet.insert(m_insertPosDecl, declaration); + } + } + + // Insert outside class + const QString filename = assistInterface()->currentFile()->fileName(); + const CppRefactoringChanges refactoring(assistInterface()->snapshot()); + const CppRefactoringFilePtr headerFile = refactoring.file(filename); + const Document::Ptr headerDoc = headerFile->cppDocument(); + Class *targetClass = m_classAST->symbol; + if (m_factory->implementationMode() & InsertVirtualMethodsDialog::ModeOutsideClass) { + // make target lookup context + unsigned line, column; + headerDoc->translationUnit()->getPosition(m_insertPosOutside, &line, &column); + Scope *targetScope = headerDoc->scopeAt(line, column); + const LookupContext targetContext(headerDoc, assistInterface()->snapshot()); + ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + + // setup rewriting to get minimally qualified names + SubstitutionEnvironment env; + env.setContext(assistInterface()->context()); + env.switchScope(targetClass); + UseMinimalNames q(targetCoN); + env.enter(&q); + Control *control = assistInterface()->context().control().data(); + const QString fullClassName = printer.prettyName(LookupContext::minimalName( + targetClass, targetCoN, control)); + + for (int i = 0; i < m_factory->classFunctionModel->rowCount(); ++i) { + const QStandardItem *parent = + m_factory->classFunctionModel->invisibleRootItem()->child(i, 0); + if (!parent->isCheckable() || parent->checkState() == Qt::Unchecked) + continue; + + for (int j = 0; j < parent->rowCount(); ++j) { + const QStandardItem *item = parent->child(j, 0); + if (!item->isCheckable() || item->checkState() == Qt::Unchecked) + continue; + const Function *func = (const Function *) + item->data(InsertVirtualMethodsDialog::ClassOrFunction).value(); + + // rewrite the function type and name + const FullySpecifiedType tn = rewriteType(func->type(), &env, control); + const QString name = fullClassName + QLatin1String("::") + + printer.prettyName(func->name()); + const QString defText = printer.prettyType(tn, name) + QLatin1String("\n{\n}"); + + headerChangeSet.insert(m_insertPosOutside, QLatin1String("\n\n") + defText); + } + } + } + + // Write header file + headerFile->setChangeSet(headerChangeSet); + headerFile->appendIndentRange(ChangeSet::Range(m_insertPosDecl, m_insertPosDecl + 1)); + headerFile->setOpenEditor(true, m_insertPosDecl); + headerFile->apply(); + + // Insert in implementation file + if (m_factory->implementationMode() & InsertVirtualMethodsDialog::ModeImplementationFile) { + const Symbol *symbol = headerFile->cppDocument()->lastVisibleSymbolAt( + targetClass->line(), targetClass->column()); + if (!symbol) + return; + const Class *clazz = symbol->asClass(); + if (!clazz) + return; + + CppRefactoringFilePtr implementationFile = refactoring.file(m_cppFileName); + ChangeSet implementationChangeSet; + const int insertPos = qMax(0, implementationFile->document()->characterCount() - 1); + + // make target lookup context + Document::Ptr implementationDoc = implementationFile->cppDocument(); + unsigned line, column; + implementationDoc->translationUnit()->getPosition(insertPos, &line, &column); + Scope *targetScope = implementationDoc->scopeAt(line, column); + const LookupContext targetContext(implementationDoc, assistInterface()->snapshot()); + ClassOrNamespace *targetCoN = targetContext.lookupType(targetScope); + if (!targetCoN) + targetCoN = targetContext.globalNamespace(); + + // Loop through inserted declarations + for (unsigned i = targetClass->memberCount(); i < clazz->memberCount(); ++i) { + Declaration *decl = clazz->memberAt(i)->asDeclaration(); + if (!decl) + continue; + + // setup rewriting to get minimally qualified names + SubstitutionEnvironment env; + env.setContext(assistInterface()->context()); + env.switchScope(decl->enclosingScope()); + UseMinimalNames q(targetCoN); + env.enter(&q); + Control *control = assistInterface()->context().control().data(); + + // rewrite the function type and name + create definition + const FullySpecifiedType type = rewriteType(decl->type(), &env, control); + const QString name = printer.prettyName( + LookupContext::minimalName(decl, targetCoN, control)); + const QString defText = printer.prettyType(type, name) + QLatin1String("\n{\n}"); + + implementationChangeSet.insert(insertPos, QLatin1String("\n\n") + defText); + } + + implementationFile->setChangeSet(implementationChangeSet); + implementationFile->appendIndentRange(ChangeSet::Range(insertPos, insertPos + 1)); + implementationFile->apply(); + } + } + +private: + InsertVirtualMethodsDialog *m_factory; + const ClassSpecifierAST *m_classAST; + bool m_valid; + QString m_cppFileName; + int m_insertPosDecl; + int m_insertPosOutside; + unsigned m_functionCount; + unsigned m_implementedFunctionCount; +}; + +class InsertVirtualMethodsFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + InsertVirtualMethodsFilterModel(QObject *parent = 0) + : QSortFilterProxyModel(parent) + , m_hideReimplemented(false) + {} + + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const + { + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + // Handle base class + if (!sourceParent.isValid()) { + // check if any child is valid + if (!sourceModel()->hasChildren(index)) + return false; + if (!m_hideReimplemented) + return true; + + for (int i = 0; i < sourceModel()->rowCount(index); ++i) { + const QModelIndex child = sourceModel()->index(i, 0, index); + if (!child.data(InsertVirtualMethodsDialog::Implemented).toBool()) + return true; + } + return false; + } + + if (m_hideReimplemented) + return !index.data(InsertVirtualMethodsDialog::Implemented).toBool(); + return true; + } + + bool hideReimplemented() const + { + return m_hideReimplemented; + } + + void setHideReimplementedFunctions(bool show) + { + m_hideReimplemented = show; + invalidateFilter(); + } + +private: + bool m_hideReimplemented; +}; + +} // anonymous namespace + +InsertVirtualMethodsDialog::InsertVirtualMethodsDialog(QWidget *parent) + : QDialog(parent) + , m_view(0) + , m_hideReimplementedFunctions(0) + , m_insertMode(0) + , m_virtualKeyword(0) + , m_buttons(0) + , m_hasImplementationFile(false) + , m_hasReimplementedFunctions(false) + , m_implementationMode(ModeOnlyDeclarations) + , m_insertKeywordVirtual(false) + , classFunctionModel(new QStandardItemModel(this)) + , classFunctionFilterModel(new InsertVirtualMethodsFilterModel(this)) +{ + classFunctionFilterModel->setSourceModel(classFunctionModel); +} + +void InsertVirtualMethodsDialog::initGui() +{ + if (m_view) + return; + + setWindowTitle(tr("Insert Virtual Functions")); + QVBoxLayout *globalVerticalLayout = new QVBoxLayout; + + // View + QGroupBox *groupBoxView = new QGroupBox(tr("&Functions to insert:"), this); + QVBoxLayout *groupBoxViewLayout = new QVBoxLayout(groupBoxView); + m_view = new QTreeView(this); + m_view->setEditTriggers(QAbstractItemView::NoEditTriggers); + m_view->setHeaderHidden(true); + groupBoxViewLayout->addWidget(m_view); + m_hideReimplementedFunctions = + new QCheckBox(tr("&Hide already implemented functions of current class"), this); + groupBoxViewLayout->addWidget(m_hideReimplementedFunctions); + + // Insertion options + QGroupBox *groupBoxImplementation = new QGroupBox(tr("&Insertion options:"), this); + QVBoxLayout *groupBoxImplementationLayout = new QVBoxLayout(groupBoxImplementation); + m_insertMode = new QComboBox(this); + m_insertMode->addItem(tr("Insert only declarations"), ModeOnlyDeclarations); + m_insertMode->addItem(tr("Insert definitions inside class"), ModeInsideClass); + m_insertMode->addItem(tr("Insert definitions outside class"), ModeOutsideClass); + m_insertMode->addItem(tr("Insert definitions in implementation file"), ModeImplementationFile); + m_virtualKeyword = new QCheckBox(tr("&Add keyword 'virtual' to function declaration"), this); + groupBoxImplementationLayout->addWidget(m_insertMode); + groupBoxImplementationLayout->addWidget(m_virtualKeyword); + groupBoxImplementationLayout->addStretch(99); + + // Bottom button box + m_buttons = new QDialogButtonBox(this); + m_buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + connect(m_buttons, SIGNAL(accepted()), this, SLOT(accept())); + connect(m_buttons, SIGNAL(rejected()), this, SLOT(reject())); + + globalVerticalLayout->addWidget(groupBoxView, 9); + globalVerticalLayout->addWidget(groupBoxImplementation, 0); + globalVerticalLayout->addWidget(m_buttons, 0); + setLayout(globalVerticalLayout); + + connect(classFunctionModel, SIGNAL(itemChanged(QStandardItem *)), + this, SLOT(updateCheckBoxes(QStandardItem *))); + connect(m_hideReimplementedFunctions, SIGNAL(toggled(bool)), + this, SLOT(setHideReimplementedFunctions(bool))); +} + +void InsertVirtualMethodsDialog::initData() +{ + m_insertKeywordVirtual = Core::ICore::settings()->value( + QLatin1String("QuickFix/InsertVirtualMethods/insertKeywordVirtual"), + false).toBool(); + m_implementationMode = static_cast( + Core::ICore::settings()->value( + QLatin1String("QuickFix/InsertVirtualMethods/implementationMode"), 1).toInt()); + m_hideReimplementedFunctions->setChecked( + Core::ICore::settings()->value( + QLatin1String("QuickFix/InsertVirtualMethods/hideReimplementedFunctions"), + false).toBool()); + + m_view->setModel(classFunctionFilterModel); + m_expansionStateNormal.clear(); + m_expansionStateReimp.clear(); + m_hideReimplementedFunctions->setVisible(m_hasReimplementedFunctions); + m_virtualKeyword->setChecked(m_insertKeywordVirtual); + m_insertMode->setCurrentIndex(m_insertMode->findData(m_implementationMode)); + + setHideReimplementedFunctions(m_hideReimplementedFunctions->isChecked()); + + if (m_hasImplementationFile) { + if (m_insertMode->count() == 3) { + m_insertMode->addItem(tr("Insert definitions in implementation file"), + ModeImplementationFile); + } + } else { + if (m_insertMode->count() == 4) + m_insertMode->removeItem(3); + } +} + +bool InsertVirtualMethodsDialog::gather() +{ + initGui(); + initData(); + + // Expand the dialog a little bit + adjustSize(); + resize(size() * 1.5); + + QPointer that(this); + const int ret = exec(); + if (!that) + return false; + + m_implementationMode = implementationMode(); + m_insertKeywordVirtual = insertKeywordVirtual(); + return (ret == QDialog::Accepted); +} + +InsertVirtualMethodsDialog::ImplementationMode +InsertVirtualMethodsDialog::implementationMode() const +{ + return static_cast( + m_insertMode->itemData(m_insertMode->currentIndex()).toInt()); +} + +void InsertVirtualMethodsDialog::setImplementationsMode(InsertVirtualMethodsDialog::ImplementationMode mode) +{ + m_implementationMode = mode; +} + +bool InsertVirtualMethodsDialog::insertKeywordVirtual() const +{ + return m_virtualKeyword->isChecked(); +} + +void InsertVirtualMethodsDialog::setInsertKeywordVirtual(bool insert) +{ + m_insertKeywordVirtual = insert; +} + +void InsertVirtualMethodsDialog::setHasImplementationFile(bool file) +{ + m_hasImplementationFile = file; +} + +void InsertVirtualMethodsDialog::setHasReimplementedFunctions(bool functions) +{ + m_hasReimplementedFunctions = functions; +} + +bool InsertVirtualMethodsDialog::hideReimplementedFunctions() const +{ + // Safty check necessary because of testing class + return (m_hideReimplementedFunctions && m_hideReimplementedFunctions->isChecked()); +} + +void InsertVirtualMethodsDialog::updateCheckBoxes(QStandardItem *item) +{ + if (item->hasChildren()) { + const Qt::CheckState state = item->checkState(); + if (!item->isCheckable() || state == Qt::PartiallyChecked) + return; + for (int i = 0; i < item->rowCount(); ++i) { + if (item->child(i, 0)->isCheckable()) + item->child(i, 0)->setCheckState(state); + } + } else { + QStandardItem *parent = item->parent(); + if (!parent->isCheckable()) + return; + const Qt::CheckState state = item->checkState(); + for (int i = 0; i < parent->rowCount(); ++i) { + if (state != parent->child(i, 0)->checkState()) { + parent->setCheckState(Qt::PartiallyChecked); + return; + } + } + parent->setCheckState(state); + } +} + +void InsertVirtualMethodsDialog::setHideReimplementedFunctions(bool hide) +{ + InsertVirtualMethodsFilterModel *model = + qobject_cast(classFunctionFilterModel); + + if (m_expansionStateNormal.isEmpty() && m_expansionStateReimp.isEmpty()) { + model->setHideReimplementedFunctions(hide); + m_view->expandAll(); + saveExpansionState(); + return; + } + + if (model->hideReimplemented() == hide) + return; + + saveExpansionState(); + model->setHideReimplementedFunctions(hide); + restoreExpansionState(); +} + +void InsertVirtualMethodsDialog::saveExpansionState() +{ + InsertVirtualMethodsFilterModel *model = + qobject_cast(classFunctionFilterModel); + + QList &state = model->hideReimplemented() ? m_expansionStateReimp + : m_expansionStateNormal; + state.clear(); + for (int i = 0; i < model->rowCount(); ++i) + state << m_view->isExpanded(model->index(i, 0)); +} + +void InsertVirtualMethodsDialog::restoreExpansionState() +{ + InsertVirtualMethodsFilterModel *model = + qobject_cast(classFunctionFilterModel); + + const QList &state = model->hideReimplemented() ? m_expansionStateReimp + : m_expansionStateNormal; + const int stateCount = state.count(); + for (int i = 0; i < model->rowCount(); ++i) { + if (i < stateCount && !state.at(i)) { + m_view->collapse(model->index(i, 0)); + continue; + } + m_view->expand(model->index(i, 0)); + } +} + +InsertVirtualMethods::InsertVirtualMethods(InsertVirtualMethodsDialog *dialog) + : m_dialog(dialog) +{} + +InsertVirtualMethods::~InsertVirtualMethods() +{ + if (m_dialog) + m_dialog->deleteLater(); +} + +void InsertVirtualMethods::match(const CppQuickFixInterface &interface, QuickFixOperations &result) +{ + InsertVirtualMethodsOp *op = new InsertVirtualMethodsOp(interface, m_dialog); + if (op->isValid()) + result.append(QuickFixOperation::Ptr(op)); + else + delete op; +} + +#include "cppquickfixes.moc" diff --git a/src/plugins/cppeditor/cppquickfixes.h b/src/plugins/cppeditor/cppquickfixes.h index c77e4d211c0..63ba308ef3f 100644 --- a/src/plugins/cppeditor/cppquickfixes.h +++ b/src/plugins/cppeditor/cppquickfixes.h @@ -36,9 +36,18 @@ #include +#include + QT_BEGIN_NAMESPACE class QByteArray; +class QCheckBox; +class QComboBox; +class QDialogButtonBox; +class QStandardItem; +class QSortFilterProxyModel; +class QStandardItemModel; class QString; +class QTreeView; template class QList; QT_END_NAMESPACE @@ -482,6 +491,78 @@ public: void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result); }; +/*! + Insert (pure) virtual functions of a base class. + */ +class InsertVirtualMethodsDialog : public QDialog +{ + Q_OBJECT +public: + enum CustomItemRoles { + ClassOrFunction = Qt::UserRole + 1, + Implemented = Qt::UserRole + 2, + PureVirtual = Qt::UserRole + 3, + AccessSpec = Qt::UserRole + 4 + }; + + enum ImplementationMode { + ModeOnlyDeclarations = 0x00000001, + ModeInsideClass = 0x00000002, + ModeOutsideClass = 0x00000004, + ModeImplementationFile = 0x00000008 + }; + + InsertVirtualMethodsDialog(QWidget *parent = 0); + void initGui(); + void initData(); + virtual ImplementationMode implementationMode() const; + void setImplementationsMode(ImplementationMode mode); + virtual bool insertKeywordVirtual() const; + void setInsertKeywordVirtual(bool insert); + void setHasImplementationFile(bool file); + void setHasReimplementedFunctions(bool functions); + bool hideReimplementedFunctions() const; + virtual bool gather(); + +private slots: + void updateCheckBoxes(QStandardItem *item); + void setHideReimplementedFunctions(bool hide); + +private: + QTreeView *m_view; + QCheckBox *m_hideReimplementedFunctions; + QComboBox *m_insertMode; + QCheckBox *m_virtualKeyword; + QDialogButtonBox *m_buttons; + QList m_expansionStateNormal; + QList m_expansionStateReimp; + bool m_hasImplementationFile; + bool m_hasReimplementedFunctions; + + void saveExpansionState(); + void restoreExpansionState(); + +protected: + ImplementationMode m_implementationMode; + bool m_insertKeywordVirtual; + +public: + QStandardItemModel *classFunctionModel; + QSortFilterProxyModel *classFunctionFilterModel; +}; + +class InsertVirtualMethods: public CppQuickFixFactory +{ + Q_OBJECT +public: + InsertVirtualMethods(InsertVirtualMethodsDialog *dialog = new InsertVirtualMethodsDialog); + ~InsertVirtualMethods(); + void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result); + +private: + InsertVirtualMethodsDialog *m_dialog; +}; + } // namespace Internal } // namespace CppEditor diff --git a/src/plugins/cpptools/insertionpointlocator.cpp b/src/plugins/cpptools/insertionpointlocator.cpp index 2401117e783..7bbcd3da7a4 100644 --- a/src/plugins/cpptools/insertionpointlocator.cpp +++ b/src/plugins/cpptools/insertionpointlocator.cpp @@ -42,33 +42,6 @@ using namespace CppTools; namespace { -static QString generate(InsertionPointLocator::AccessSpec xsSpec) -{ - switch (xsSpec) { - default: - case InsertionPointLocator::Public: - return QLatin1String("public:\n"); - - case InsertionPointLocator::Protected: - return QLatin1String("protected:\n"); - - case InsertionPointLocator::Private: - return QLatin1String("private:\n"); - - case InsertionPointLocator::PublicSlot: - return QLatin1String("public slots:\n"); - - case InsertionPointLocator::ProtectedSlot: - return QLatin1String("protected slots:\n"); - - case InsertionPointLocator::PrivateSlot: - return QLatin1String("private slots:\n"); - - case InsertionPointLocator::Signals: - return QLatin1String("signals:\n"); - } -} - static int ordering(InsertionPointLocator::AccessSpec xsSpec) { static QList order = QList() @@ -161,7 +134,7 @@ protected: if (needsLeadingEmptyLine) prefix += QLatin1String("\n"); if (needsPrefix) - prefix += generate(_xsSpec); + prefix += InsertionPointLocator::accessSpecToString(_xsSpec); QString suffix; if (needsSuffix) @@ -299,6 +272,33 @@ InsertionLocation::InsertionLocation(const QString &fileName, , m_column(column) {} +QString InsertionPointLocator::accessSpecToString(InsertionPointLocator::AccessSpec xsSpec) +{ + switch (xsSpec) { + default: + case InsertionPointLocator::Public: + return QLatin1String("public:\n"); + + case InsertionPointLocator::Protected: + return QLatin1String("protected:\n"); + + case InsertionPointLocator::Private: + return QLatin1String("private:\n"); + + case InsertionPointLocator::PublicSlot: + return QLatin1String("public slots:\n"); + + case InsertionPointLocator::ProtectedSlot: + return QLatin1String("protected slots:\n"); + + case InsertionPointLocator::PrivateSlot: + return QLatin1String("private slots:\n"); + + case InsertionPointLocator::Signals: + return QLatin1String("signals:\n"); + } +} + InsertionPointLocator::InsertionPointLocator(const CppRefactoringChanges &refactoringChanges) : m_refactoringChanges(refactoringChanges) { diff --git a/src/plugins/cpptools/insertionpointlocator.h b/src/plugins/cpptools/insertionpointlocator.h index 7321c1ce517..da9799744b7 100644 --- a/src/plugins/cpptools/insertionpointlocator.h +++ b/src/plugins/cpptools/insertionpointlocator.h @@ -89,6 +89,7 @@ public: ProtectedSlot = Protected | SlotBit, PrivateSlot = Private | SlotBit }; + static QString accessSpecToString(InsertionPointLocator::AccessSpec xsSpec); public: InsertionPointLocator(const CppRefactoringChanges &refactoringChanges);