diff --git a/debugger/variable/variablewidget.cpp b/debugger/variable/variablewidget.cpp index d48d2111ef..b8eab5952a 100644 --- a/debugger/variable/variablewidget.cpp +++ b/debugger/variable/variablewidget.cpp @@ -1,510 +1,500 @@ // ************************************************************************** // begin : Sun Aug 8 1999 // copyright : (C) 1999 by John Birch // email : jbb@kdevelop.org // ************************************************************************** // * Copyright 2006 Vladimir Prus // ************************************************************************** // * * // * This program is free software; you can redistribute it and/or modify * // * it under the terms of the GNU General Public License as published by * // * the Free Software Foundation; either version 2 of the License, or * // * (at your option) any later version. * // * * // ************************************************************************** #include "variablewidget.h" #include -#include -#include -#include -#include -#include #include -#include +#include #include -#include -#include #include -#include -#include +#include -#include -#include -#include +#include #include #include "../../interfaces/icore.h" #include #include "../interfaces/ivariablecontroller.h" #include "variablecollection.h" #include "util/debug.h" /** The variables widget is passive, and is invoked by the rest of the code via two main Q_SLOTS: - slotDbgStatus - slotCurrentFrame The first is received the program status changes and the second is received after current frame in the debugger can possibly changes. The widget has a list item for each frame/thread combination, with variables as children. However, at each moment only one item is shown. When handling the slotCurrentFrame, we check if variables for the current frame are available. If yes, we simply show the corresponding item. Otherwise, we fetch the new data from debugger. Fetching the data is done by emitting the produceVariablesInfo signal. In response, we get slotParametersReady and slotLocalsReady signal, in that order. The data is parsed and changed variables are highlighted. After that, we 'trim' variable items that were not reported by gdb -- that is, gone out of scope. */ // ************************************************************************** // ************************************************************************** // ************************************************************************** namespace KDevelop { VariableCollection *variableCollection() { return ICore::self()->debugController()->variableCollection(); } VariableWidget::VariableWidget(IDebugController* controller, QWidget *parent) : QWidget(parent), variablesRoot_(controller->variableCollection()->root()) { //setWindowIcon(QIcon::fromTheme("math_brace")); setWindowIcon(QIcon::fromTheme("debugger")); setWindowTitle(i18n("Debugger Variables")); varTree_ = new VariableTree(controller, this); setFocusProxy(varTree_); watchVarEditor_ = new KHistoryComboBox( this ); QVBoxLayout *topLayout = new QVBoxLayout(this); topLayout->addWidget(varTree_, 10); topLayout->addWidget(watchVarEditor_); topLayout->setMargin(0); connect(watchVarEditor_, static_cast(&KHistoryComboBox::returnPressed), this, &VariableWidget::slotAddWatch); //TODO //connect(plugin, SIGNAL(raiseVariableViews()), this, SIGNAL(requestRaise())); // Setup help items. setWhatsThis( i18n( "Variable tree" "The variable tree allows you to see the values of local " "variables and arbitrary expressions.
" "Local variables are displayed automatically and are updated " "as you step through your program. " "For each expression you enter, you can either evaluate it once, " "or \"watch\" it (make it auto-updated). Expressions that are not " "auto-updated can be updated manually from the context menu. " "Expressions can be renamed to more descriptive names by clicking " "on the name column.
" "To change the value of a variable or an expression, " "click on the value.
")); watchVarEditor_->setWhatsThis( i18n("Expression entry" "Type in expression to watch.")); } void VariableWidget::slotAddWatch(const QString &expression) { if (!expression.isEmpty()) { watchVarEditor_->addToHistory(expression); qCDebug(DEBUGGER) << "Trying to add watch\n"; Variable* v = variablesRoot_->watches()->add(expression); if (v) { QModelIndex index = variableCollection()->indexForItem(v, 0); /* For watches on structures, we really do want them to be shown expanded. Except maybe for structure with custom pretty printing, but will handle that later. FIXME: it does not actually works now. */ //varTree_->setExpanded(index, true); } watchVarEditor_->clearEditText(); } } void VariableWidget::hideEvent(QHideEvent* e) { QWidget::hideEvent(e); variableCollection()->variableWidgetHidden(); } void VariableWidget::showEvent(QShowEvent* e) { QWidget::showEvent(e); variableCollection()->variableWidgetShown(); } // ************************************************************************** // ************************************************************************** // ************************************************************************** VariableTree::VariableTree(IDebugController *controller, VariableWidget *parent) : AsyncTreeView(controller->variableCollection(), parent) #if 0 , activePopup_(0), toggleWatch_(0) #endif { setRootIsDecorated(true); setAllColumnsShowFocus(true); QModelIndex index = controller->variableCollection()->indexForItem( controller->variableCollection()->watches(), 0); setExpanded(index, true); m_signalMapper = new QSignalMapper(this); setupActions(); } VariableCollection* VariableTree::collection() const { Q_ASSERT(qobject_cast(model())); return static_cast(model()); } VariableTree::~VariableTree() { } void VariableTree::setupActions() { // TODO decorate this properly to make nice menu title m_contextMenuTitle = new QAction(this); m_contextMenuTitle->setEnabled(false); // make Format menu action group m_formatMenu = new QMenu(i18n("&Format"), this); QActionGroup *ag= new QActionGroup(m_formatMenu); QAction* act; act = new QAction(i18n("&Natural"), ag); act->setData(Variable::Natural); act->setShortcut(Qt::Key_N); m_formatMenu->addAction(act); act = new QAction(i18n("&Binary"), ag); act->setData(Variable::Binary); act->setShortcut(Qt::Key_B); m_formatMenu->addAction(act); act = new QAction(i18n("&Octal"), ag); act->setData(Variable::Octal); act->setShortcut(Qt::Key_O); m_formatMenu->addAction(act); act = new QAction(i18n("&Decimal"), ag); act->setData(Variable::Decimal); act->setShortcut(Qt::Key_D); m_formatMenu->addAction(act); act = new QAction(i18n("&Hexadecimal"), ag); act->setData(Variable::Hexadecimal); act->setShortcut(Qt::Key_H); m_formatMenu->addAction(act); foreach(QAction* act, m_formatMenu->actions()) { act->setCheckable(true); act->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_signalMapper->setMapping(act, act->data().toInt()); connect(act, &QAction::triggered, m_signalMapper, static_cast(&QSignalMapper::map)); addAction(act); } connect(m_signalMapper, static_cast(&QSignalMapper::mapped), this, &VariableTree::changeVariableFormat); m_watchDelete = new QAction( QIcon::fromTheme("edit-delete"), i18n( "Remove Watch Variable" ), this); m_watchDelete->setShortcut(Qt::Key_Delete); m_watchDelete->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(m_watchDelete); connect(m_watchDelete, &QAction::triggered, this, &VariableTree::watchDelete); m_copyVariableValue = new QAction(i18n("&Copy Value"), this); m_copyVariableValue->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_copyVariableValue->setShortcut(QKeySequence::Copy); connect(m_copyVariableValue, &QAction::triggered, this, &VariableTree::copyVariableValue); m_stopOnChange = new QAction(i18n("&Stop on Change"), this); connect(m_stopOnChange, &QAction::triggered, this, &VariableTree::stopOnChange); } Variable* VariableTree::selectedVariable() const { if (selectionModel()->selectedRows().isEmpty()) return 0; TreeItem* item = collection()->itemForIndex(selectionModel()->selectedRows().first()); if (!item) return 0; return dynamic_cast(item); } void VariableTree::contextMenuEvent(QContextMenuEvent* event) { if (!selectedVariable()) return; // set up menu QMenu contextMenu(this->parentWidget()); m_contextMenuTitle->setText(selectedVariable()->expression()); contextMenu.addAction(m_contextMenuTitle); if(selectedVariable()->canSetFormat()) contextMenu.addMenu(m_formatMenu); foreach(QAction* act, m_formatMenu->actions()) { if(act->data().toInt()==selectedVariable()->format()) act->setChecked(true); } if (dynamic_cast(selectedVariable()->parent())) { contextMenu.addAction(m_watchDelete); } contextMenu.addSeparator(); contextMenu.addAction(m_copyVariableValue); contextMenu.addAction(m_stopOnChange); contextMenu.exec(event->globalPos()); } void VariableTree::changeVariableFormat(int format) { if (!selectedVariable()) return; selectedVariable()->setFormat(static_cast(format)); } void VariableTree::watchDelete() { if (!selectedVariable()) return; if (!dynamic_cast(selectedVariable()->parent())) return; selectedVariable()->die(); } void VariableTree::copyVariableValue() { if (!selectedVariable()) return; QApplication::clipboard()->setText(selectedVariable()->value()); } void VariableTree::stopOnChange() { if (!selectedVariable()) return; IDebugSession *session = ICore::self()->debugController()->currentSession(); if (session && session->state() != IDebugSession::NotStartedState && session->state() != IDebugSession::EndedState) { session->variableController()->addWatchpoint(selectedVariable()); } } #if 0 void VariableTree::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if (!index.isValid()) return; AbstractVariableItem* item = collection()->itemForIndex(index); if (RecentItem* recent = qobject_cast(item)) { QMenu popup(this); popup.addTitle(i18n("Recent Expressions")); QAction* remove = popup.addAction(QIcon::fromTheme("editdelete"), i18n("Remove All")); QAction* reevaluate = popup.addAction(QIcon::fromTheme("reload"), i18n("Re-evaluate All")); if (controller()->stateIsOn(s_dbgNotStarted)) reevaluate->setEnabled(false); QAction* res = popup.exec(QCursor::pos()); if (res == remove) { collection()->deleteItem(recent); } else if (res == reevaluate) { foreach (AbstractVariableItem* item, recent->children()) { if (VariableItem* variable = qobject_cast(item)) variable->updateValue(); } } } else { activePopup_ = new QMenu(this); QMenu format(this); QAction* remember = 0; QAction* remove = 0; QAction* reevaluate = 0; QAction* watch = 0; QAction* natural = 0; QAction* hex = 0; QAction* decimal = 0; QAction* character = 0; QAction* binary = 0; #define MAYBE_DISABLE(action) if (!var->isAlive()) action->setEnabled(false) VariableItem* var = qobject_cast(item); AbstractVariableItem* root = item->abstractRoot(); RecentItem* recentRoot = qobject_cast(root); if (!recentRoot) { remember = activePopup_->addAction(QIcon::fromTheme("draw-freehand"), i18n("Remember Value")); MAYBE_DISABLE(remember); } if (!recentRoot) { watch = activePopup_->addAction(i18n("Watch Variable")); MAYBE_DISABLE(watch); } if (recentRoot) { reevaluate = activePopup_->addAction(QIcon::fromTheme("reload"), i18n("Reevaluate Expression")); MAYBE_DISABLE(reevaluate); remove = activePopup_->addAction(QIcon::fromTheme("editdelete"), i18n("Remove Expression")); remove->setShortcut(Qt::Key_Delete); } if (var) { toggleWatch_ = activePopup_->addAction( i18n("Data write breakpoint") ); toggleWatch_->setCheckable(true); toggleWatch_->setEnabled(false); } /* This code can be executed when debugger is stopped, and we invoke popup menu on a var under "recent expressions" just to delete it. */ if (var && var->isAlive() && !controller()->stateIsOn(s_dbgNotStarted)) { GDBCommand* cmd = new GDBCommand(DataEvaluateExpression, QStringLiteral("&%1") .arg(var->gdbExpression())); cmd->setHandler(this, &VariableTree::handleAddressComputed, true /*handles error*/); cmd->setThread(var->thread()); cmd->setFrame(var->frame()); controller_->addCommand(cmd); } QAction* res = activePopup_->exec(event->globalPos()); delete activePopup_; activePopup_ = 0; if (res == remember) { if (var) { ((VariableWidget*)parent())-> slotEvaluateExpression(var->gdbExpression()); } } else if (res == watch) { if (var) { ((VariableWidget*)parent())-> slotAddWatchVariable(var->gdbExpression()); } } else if (res == remove) { delete item; } else if (res == toggleWatch_) { if (var) emit toggleWatchpoint(var->gdbExpression()); } else if (res == reevaluate) { if (var) { var->updateValue(); } } event->accept(); } } void VariableTree::updateCurrentFrame() { } // ************************************************************************** void VariableTree::handleAddressComputed(const GDBMI::ResultRecord& r) { if (r.reason == "error") { // Not lvalue, leave item disabled. return; } if (activePopup_) { toggleWatch_->setEnabled(true); //quint64 address = r["value"].literal().toULongLong(0, 16); /*if (breakpointWidget_->hasWatchpointForAddress(address)) { toggleWatch_->setChecked(true); }*/ } } VariableCollection * VariableTree::collection() const { return controller_->variables(); } GDBController * VariableTree::controller() const { return controller_; } void VariableTree::showEvent(QShowEvent * event) { Q_UNUSED(event) for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } #endif // ************************************************************************** // ************************************************************************** // ************************************************************************** } diff --git a/debugger/variable/variablewidget.h b/debugger/variable/variablewidget.h index 235ba08f5d..6a47497324 100644 --- a/debugger/variable/variablewidget.h +++ b/debugger/variable/variablewidget.h @@ -1,118 +1,116 @@ /*************************************************************************** begin : Sun Aug 8 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_VARIABLEWIDGET_H #define KDEVPLATFORM_VARIABLEWIDGET_H -#include #include #include - -#include +#include #include #include "../util/treeview.h" #include "variablecollection.h" class KHistoryComboBox; namespace KDevelop { class IDebugController; class VariableTree; class AbstractVariableItem; class KDEVPLATFORMDEBUGGER_EXPORT VariableWidget : public QWidget { Q_OBJECT public: VariableWidget( IDebugController *controller, QWidget *parent=0 ); Q_SIGNALS: void requestRaise(); void addWatchVariable(const QString& indent); void evaluateExpression(const QString& indent); public Q_SLOTS: void slotAddWatch(const QString &ident); protected: virtual void showEvent(QShowEvent* e) override; virtual void hideEvent(QHideEvent* e) override; private: VariableTree *varTree_; KHistoryComboBox *watchVarEditor_; VariablesRoot *variablesRoot_; }; /***************************************************************************/ /***************************************************************************/ /***************************************************************************/ class VariableTree : public KDevelop::AsyncTreeView { Q_OBJECT public: VariableTree(IDebugController *controller, VariableWidget *parent); virtual ~VariableTree(); VariableCollection* collection() const; private: void setupActions(); virtual void contextMenuEvent(QContextMenuEvent* event) override; Variable *selectedVariable() const; private slots: void changeVariableFormat(int); void watchDelete(); void copyVariableValue(); void stopOnChange(); #if 0 Q_SIGNALS: void toggleWatchpoint(const QString &varName); protected: virtual void contextMenuEvent(QContextMenuEvent* event); virtual void keyPressEvent(QKeyEvent* e); virtual void showEvent(QShowEvent* event); private: // helper functions void handleAddressComputed(const GDBMI::ResultRecord& r); void updateCurrentFrame(); void copyToClipboard(AbstractVariableItem* item); QMenu* activePopup_; QAction* toggleWatch_; #endif private: QAction *m_contextMenuTitle; QMenu *m_formatMenu; QAction *m_watchDelete; QAction *m_copyVariableValue; QAction *m_stopOnChange; QSignalMapper *m_signalMapper; }; } #endif diff --git a/interfaces/ipartcontroller.cpp b/interfaces/ipartcontroller.cpp index bf2ccf590c..af49ab5f4f 100644 --- a/interfaces/ipartcontroller.cpp +++ b/interfaces/ipartcontroller.cpp @@ -1,96 +1,98 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "ipartcontroller.h" #include -#include -#include -#include -#include + +#include +#include +#include +#include + namespace KDevelop { - -IPartController::IPartController( QWidget* toplevel ) + +IPartController::IPartController( QWidget* toplevel ) : KParts::PartManager( toplevel, 0 ) { } KPluginFactory* IPartController::findPartFactory ( const QString& mimetype, const QString& parttype, const QString& preferredName ) { // parttype may be a interface type not derived from KParts/ReadOnlyPart const KService::List offers = KMimeTypeTrader::self()->query( mimetype, QString::fromLatin1( "KParts/ReadOnlyPart" ), QString::fromLatin1( "'%1' in ServiceTypes" ).arg( parttype ) ); if ( ! offers.isEmpty() ) { KService::Ptr ptr; // if there is a preferred plugin we'll take it if ( !preferredName.isEmpty() ) { KService::List::ConstIterator it; for ( it = offers.constBegin(); it != offers.constEnd(); ++it ) { if ( ( *it ) ->desktopEntryName() == preferredName ) { ptr = ( *it ); break; } } } // else we just take the first in the list if ( !ptr ) { ptr = offers.first(); } KPluginLoader loader( QFile::encodeName( ptr->library() ) ); return loader.factory(); } - + return 0; } KParts::Part* IPartController::createPart ( const QString& mimetype, const QString& prefName ) { const uint length = 1; static const char* const services[length] = { // Disable read/write parts until we can support them /*"KParts/ReadWritePart",*/ "KParts/ReadOnlyPart" }; KParts::Part* part = 0; for ( uint i = 0; i < length; ++i ) { KPluginFactory* editorFactory = findPartFactory( mimetype, QString::fromLatin1(services[ i ]), prefName ); if ( editorFactory ) { part = editorFactory->create( 0, this ); break; } } return part; } } diff --git a/interfaces/iplugin.cpp b/interfaces/iplugin.cpp index 270a72aedf..ce21b693c5 100644 --- a/interfaces/iplugin.cpp +++ b/interfaces/iplugin.cpp @@ -1,215 +1,216 @@ /* This file is part of the KDE project Copyright 2002 Simon Hausmann Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Harald Fernengel Copyright 2002 Falk Brettschneider Copyright 2003 Julian Rockey Copyright 2003 Roberto Raggi Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004,2007 Alexander Dymo Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "iplugin.h" -#include -#include -#include -#include +#include +#include +#include +#include + #include "icore.h" #include "iplugincontroller.h" #include "iprojectcontroller.h" #include "iproject.h" #include "contextmenuextension.h" namespace KDevelop { class IPluginPrivate { public: IPluginPrivate(IPlugin *q) : q(q) {} ~IPluginPrivate() { } void guiClientAdded(KXMLGUIClient *client) { if (client != q) return; q->initializeGuiState(); updateState(); } void updateState() { const int projectCount = ICore::self()->projectController()->projectCount(); IPlugin::ReverseStateChange reverse = IPlugin::StateNoReverse; if (! projectCount) reverse = IPlugin::StateReverse; q->stateChanged(QLatin1String("has_project"), reverse); } IPlugin *q; ICore *core; QStringList m_extensions; }; IPlugin::IPlugin( const QString &componentName, QObject *parent ) : KTextEditor::Plugin(parent) , KXMLGUIClient() , d(new IPluginPrivate(this)) { // The following cast is safe, there's no component in KDevPlatform that // creates plugins except the plugincontroller. The controller passes // Core::self() as parent to KServiceTypeTrader::createInstanceFromQuery // so we know the parent is always a Core* pointer. // This is the only way to pass the Core pointer to the plugin during its // creation so plugins have access to ICore during their creation. d->core = static_cast(parent); setComponentName(componentName, componentName); auto clientAdded = [=] (KXMLGUIClient* client) { d->guiClientAdded(client); }; foreach (KMainWindow* mw, KMainWindow::memberList()) { KXmlGuiWindow* guiWindow = qobject_cast(mw); if (! guiWindow) continue; connect(guiWindow->guiFactory(), &KXMLGUIFactory::clientAdded, this, clientAdded); } auto updateState = [=] { d->updateState(); }; connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, updateState); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, updateState); } IPlugin::~IPlugin() { delete d; } void IPlugin::unload() { } ICore *IPlugin::core() const { return d->core; } } QStringList KDevelop::IPlugin::extensions( ) const { return d->m_extensions; } void KDevelop::IPlugin::addExtension( const QString& ext ) { d->m_extensions << ext; } KDevelop::ContextMenuExtension KDevelop::IPlugin::contextMenuExtension( KDevelop::Context* ) { return KDevelop::ContextMenuExtension(); } void KDevelop::IPlugin::initializeGuiState() { } class CustomXmlGUIClient : public KXMLGUIClient { public: CustomXmlGUIClient(const QString& componentName) { // TODO KF5: Get rid off this setComponentName(componentName, componentName); } void setXmlFile(QString file) { KXMLGUIClient::setXMLFile(file); } }; KXMLGUIClient* KDevelop::IPlugin::createGUIForMainWindow(Sublime::MainWindow* window) { CustomXmlGUIClient* ret = new CustomXmlGUIClient(componentName()); QString file; createActionsForMainWindow(window, file, *ret->actionCollection()); if(!ret->actionCollection()->isEmpty()) { Q_ASSERT(!file.isEmpty()); //A file must have been set ret->setXmlFile(file); } else { delete ret; ret = 0; } return ret; } void KDevelop::IPlugin::createActionsForMainWindow( Sublime::MainWindow* /*window*/, QString& /*xmlFile*/, KActionCollection& /*actions*/ ) { } bool KDevelop::IPlugin::hasError() const { return false; } QString KDevelop::IPlugin::errorDescription() const { return QString(); } int KDevelop::IPlugin::perProjectConfigPages() const { return 0; } KDevelop::ConfigPage* KDevelop::IPlugin::perProjectConfigPage(int, const ProjectConfigOptions&, QWidget*) { return nullptr; } KDevelop::ConfigPage* KDevelop::IPlugin::configPage (int, QWidget*) { return nullptr; } QObject* KDevelop::IPlugin::createView(KTextEditor::MainWindow*) { Q_UNREACHABLE(); return nullptr; } #include "moc_iplugin.cpp" diff --git a/language/assistant/renameassistant.cpp b/language/assistant/renameassistant.cpp index fef0caa9f1..b9a5bcba9c 100644 --- a/language/assistant/renameassistant.cpp +++ b/language/assistant/renameassistant.cpp @@ -1,223 +1,223 @@ /* Copyright 2010 Olivier de Gaalon Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "renameassistant.h" #include "renameaction.h" #include "renamefileaction.h" #include "util/debug.h" #include "../codegen/basicrefactoring.h" #include "../codegen/documentchangeset.h" #include "../duchain/duchain.h" #include "../duchain/duchainlock.h" #include "../duchain/duchainutils.h" #include "../duchain/declaration.h" #include "../duchain/functiondefinition.h" #include "../duchain/classfunctiondeclaration.h" #include #include #include #include -#include +#include using namespace KDevelop; namespace { bool rangesConnect(const KTextEditor::Range& firstRange, const KTextEditor::Range& secondRange) { return !firstRange.intersect(secondRange + KTextEditor::Range(0, -1, 0, +1)).isEmpty(); } Declaration* getDeclarationForChangedRange(KTextEditor::View* view, const KTextEditor::Range& changed) { const KTextEditor::Cursor cursor(changed.start()); Declaration* declaration = DUChainUtils::itemUnderCursor(view->document()->url(), cursor); //If it's null we could be appending, but there's a case where appending gives a wrong decl //and not a null declaration ... "type var(init)", so check for that too if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { declaration = DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(cursor.line(), cursor.column()-1)); } //In this case, we may either not have a decl at the cursor, or we got a decl, but are editing its use. //In either of those cases, give up and return 0 if (!declaration || !rangesConnect(declaration->rangeInCurrentRevision(), changed)) { return 0; } return declaration; } } struct RenameAssistant::Private { Private(RenameAssistant* qq) : q(qq) , m_isUseful(false) , m_renameFile(false) { } void reset() { q->doHide(); q->clearActions(); m_oldDeclarationName = Identifier(); m_newDeclarationRange.reset(); m_oldDeclarationUses.clear(); m_isUseful = false; m_renameFile = false; } RenameAssistant* q; KDevelop::Identifier m_oldDeclarationName; QString m_newDeclarationName; KDevelop::PersistentMovingRange::Ptr m_newDeclarationRange; QVector m_oldDeclarationUses; bool m_isUseful; bool m_renameFile; }; RenameAssistant::RenameAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) , d(new Private(this)) { } RenameAssistant::~RenameAssistant() { } QString RenameAssistant::title() const { return tr("Rename"); } bool RenameAssistant::isUseful() const { return d->m_isUseful; } void RenameAssistant::textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText) { clearActions(); if (!supportedLanguage()->refactoring()) { qCWarning(LANGUAGE) << "Refactoring not supported. Aborting."; return; } if (!view) return; //If the inserted text isn't valid for a variable name, consider the editing ended QRegExp validDeclName("^[0-9a-zA-Z_]*$"); if (removedText.isEmpty() && !validDeclName.exactMatch(view->document()->text(invocationRange))) { d->reset(); return; } const QUrl url = view->document()->url(); const IndexedString indexedUrl(url); DUChainReadLocker lock; //If we've stopped editing m_newDeclarationRange or switched the view, // reset and see if there's another declaration being edited if (!d->m_newDeclarationRange.data() || !rangesConnect(d->m_newDeclarationRange->range(), invocationRange) || d->m_newDeclarationRange->document() != indexedUrl) { d->reset(); Declaration* declAtCursor = getDeclarationForChangedRange(view, invocationRange); if (!declAtCursor) { // not editing a declaration return; } if (supportedLanguage()->refactoring()->shouldRenameUses(declAtCursor)) { QMap< IndexedString, QList > declUses = declAtCursor->uses(); if (declUses.isEmpty()) { // new declaration is use-less return; } for(QMap< IndexedString, QList< RangeInRevision > >::const_iterator it = declUses.constBegin(); it != declUses.constEnd(); ++it) { foreach(const RangeInRevision& range, it.value()) { KTextEditor::Range currentRange = declAtCursor->transformFromLocalRevision(range); if(currentRange.isEmpty() || view->document()->text(currentRange) != declAtCursor->identifier().identifier().str()) return; // One of the uses is invalid. Maybe the replacement has already been performed. } } d->m_oldDeclarationUses = RevisionedFileRanges::convert(declUses); } else if (supportedLanguage()->refactoring()->shouldRenameFile(declAtCursor)) { d->m_renameFile = true; } else { // not a valid declaration return; } d->m_oldDeclarationName = declAtCursor->identifier(); KTextEditor::Range newRange = declAtCursor->rangeInCurrentRevision(); if (removedText.isEmpty() && newRange.intersect(invocationRange).isEmpty()) { newRange = newRange.encompass(invocationRange); //if text was added to the ends, encompass it } d->m_newDeclarationRange = new PersistentMovingRange(newRange, indexedUrl, true); } //Unfortunately this happens when you make a selection including one end of the decl's range and replace it if (removedText.isEmpty() && d->m_newDeclarationRange->range().intersect(invocationRange).isEmpty()) { d->m_newDeclarationRange = new PersistentMovingRange( d->m_newDeclarationRange->range().encompass(invocationRange), indexedUrl, true); } d->m_newDeclarationName = view->document()->text(d->m_newDeclarationRange->range()); if (d->m_newDeclarationName == d->m_oldDeclarationName.toString()) { d->reset(); return; } if (d->m_renameFile && supportedLanguage()->refactoring()->newFileName(url, d->m_newDeclarationName) == url.fileName()) { // no change, don't do anything return; } d->m_isUseful = true; IAssistantAction::Ptr action; if (d->m_renameFile) { action = new RenameFileAction(supportedLanguage()->refactoring(), url, d->m_newDeclarationName); } else { action =new RenameAction(d->m_oldDeclarationName, d->m_newDeclarationName, d->m_oldDeclarationUses); } connect(action.data(), &IAssistantAction::executed, this, [&] { d->reset(); }); addAction(action); emit actionsChanged(); } #include "moc_renameassistant.cpp" diff --git a/language/backgroundparser/backgroundparser.cpp b/language/backgroundparser/backgroundparser.cpp index ffe16e6102..b10917438e 100644 --- a/language/backgroundparser/backgroundparser.cpp +++ b/language/backgroundparser/backgroundparser.cpp @@ -1,834 +1,830 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2007 Kris Wong * Copyright 2007-2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "backgroundparser.h" #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 "util/debug.h" #include "parsejob.h" #include const bool separateThreadForHighPriority = true; /** * Elides string in @p path, e.g. "VEEERY/LONG/PATH" -> ".../LONG/PATH" * - probably much faster than QFontMetrics::elidedText() * - we dont need a widget context * - takes path separators into account * * @p width Maximum number of characters * * TODO: Move to kdevutil? */ static QString elidedPathLeft(const QString& path, int width) { static const QChar separator = QDir::separator(); static const QString placeholder = "..."; if (path.size() <= width) { return path; } int start = (path.size() - width) + placeholder.size(); int pos = path.indexOf(separator, start); if (pos == -1) { pos = start; // no separator => just cut off the path at the beginning } Q_ASSERT(path.size() - pos >= 0 && path.size() - pos <= width); QStringRef elidedText = path.rightRef(path.size() - pos); QString result = placeholder; result.append(elidedText); return result; } namespace { /** * @return true if @p url is non-empty, valid and has a clean path, false otherwise. */ inline bool isValidURL(const KDevelop::IndexedString& url) { if (url.isEmpty()) { return false; } QUrl original = url.toUrl(); if (!original.isValid() || original.isRelative() || original.fileName().isEmpty()) { qCWarning(LANGUAGE) << "INVALID URL ENCOUNTERED:" << url << original; return false; } QUrl cleaned = original.adjusted(QUrl::NormalizePathSegments); return original == cleaned; } } namespace KDevelop { class BackgroundParserPrivate { public: BackgroundParserPrivate(BackgroundParser *parser, ILanguageController *languageController) :m_parser(parser), m_languageController(languageController), m_shuttingDown(false), m_mutex(QMutex::Recursive) { parser->d = this; //Set this so we can safely call back BackgroundParser from within loadSettings() m_timer.setSingleShot(true); m_delay = 500; m_threads = 1; m_doneParseJobs = 0; m_maxParseJobs = 0; m_neededPriority = BackgroundParser::WorstPriority; ThreadWeaver::setDebugLevel(true, 1); QObject::connect(&m_timer, &QTimer::timeout, m_parser, &BackgroundParser::parseDocuments); } void startTimerThreadSafe() { QMetaObject::invokeMethod(m_parser, "startTimer", Qt::QueuedConnection); } ~BackgroundParserPrivate() { // Release dequeued jobs QHashIterator it = m_parseJobs; while (it.hasNext()) { it.next(); delete it.value(); } } // Non-mutex guarded functions, only call with m_mutex acquired. void parseDocumentsInternal() { if(m_shuttingDown) return; // Create delayed jobs, that is, jobs for documents which have been changed // by the user. QList jobs; // Before starting a new job, first wait for all higher-priority ones to finish. // That way, parse job priorities can be used for dependency handling. int bestRunningPriority = BackgroundParser::WorstPriority; foreach (const ThreadWeaver::QObjectDecorator* decorator, m_parseJobs) { const ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); if (parseJob->respectsSequentialProcessing() && parseJob->parsePriority() < bestRunningPriority) { bestRunningPriority = parseJob->parsePriority(); } } bool done = false; for (QMap >::Iterator it1 = m_documentsForPriority.begin(); it1 != m_documentsForPriority.end(); ++it1 ) { if(it1.key() > m_neededPriority) break; //The priority is not good enough to be processed right now for(QSet::Iterator it = it1.value().begin(); it != it1.value().end();) { //Only create parse-jobs for up to thread-count * 2 documents, so we don't fill the memory unnecessarily if(m_parseJobs.count() >= m_threads+1 || (m_parseJobs.count() >= m_threads && !separateThreadForHighPriority) ) break; if(m_parseJobs.count() >= m_threads && it1.key() > BackgroundParser::NormalPriority && !specialParseJob) break; //The additional parsing thread is reserved for higher priority parsing // When a document is scheduled for parsing while it is being parsed, it will be parsed // again once the job finished, but not now. if (m_parseJobs.contains(*it) ) { ++it; continue; } Q_ASSERT(m_documents.contains(*it)); const DocumentParsePlan& parsePlan = m_documents[*it]; // If the current job requires sequential processing, but not all jobs with a better priority have been // completed yet, it will not be created now. if ( parsePlan.sequentialProcessingFlags() & ParseJob::RequiresSequentialProcessing && parsePlan.priority() > bestRunningPriority ) { ++it; continue; } qCDebug(LANGUAGE) << "creating parse-job" << *it << "new count of active parse-jobs:" << m_parseJobs.count() + 1; const QString elidedPathString = elidedPathLeft(it->str(), 70); emit m_parser->showMessage(m_parser, i18n("Parsing: %1", elidedPathString)); ThreadWeaver::QObjectDecorator* decorator = createParseJob(*it, parsePlan.features(), parsePlan.notifyWhenReady(), parsePlan.priority()); if(m_parseJobs.count() == m_threads+1 && !specialParseJob) specialParseJob = decorator; //This parse-job is allocated into the reserved thread if (decorator) { ParseJob* parseJob = dynamic_cast(decorator->job()); parseJob->setSequentialProcessingFlags(parsePlan.sequentialProcessingFlags()); jobs.append(ThreadWeaver::JobPointer(decorator)); // update the currently best processed priority, if the created job respects sequential processing if ( parsePlan.sequentialProcessingFlags() & ParseJob::RespectsSequentialProcessing && parsePlan.priority() < bestRunningPriority) { bestRunningPriority = parsePlan.priority(); } } // Remove all mentions of this document. foreach(const DocumentParseTarget& target, parsePlan.targets) { if (target.priority != it1.key()) { m_documentsForPriority[target.priority].remove(*it); } } m_documents.remove(*it); it = it1.value().erase(it); --m_maxParseJobs; //We have added one when putting the document into m_documents if(!m_documents.isEmpty()) { // Only try creating one parse-job at a time, else we might iterate through thousands of files // without finding a language-support, and block the UI for a long time. // If there are more documents to parse, instantly re-try. QMetaObject::invokeMethod(m_parser, "parseDocuments", Qt::QueuedConnection); done = true; break; } } if ( done ) break; } // Ok, enqueueing is fine because m_parseJobs contains all of the jobs now foreach (ThreadWeaver::JobPointer job, jobs) m_weaver.enqueue(job); m_parser->updateProgressBar(); //We don't hide the progress-bar in updateProgressBar, so it doesn't permanently flash when a document is reparsed again and again. if(m_doneParseJobs == m_maxParseJobs || (m_neededPriority == BackgroundParser::BestPriority && m_weaver.queueLength() == 0)) { emit m_parser->hideProgress(m_parser); } } ThreadWeaver::QObjectDecorator* createParseJob(const IndexedString& url, TopDUContext::Features features, const QList >& notifyWhenReady, int priority = 0) { ///FIXME: use IndexedString in the other APIs as well! Esp. for createParseJob! QUrl qUrl = url.toUrl(); auto languages = m_languageController->languagesForUrl(qUrl); foreach (auto language, languages) { if(!language) { qCWarning(LANGUAGE) << "got zero language for" << qUrl; continue; } ParseJob* job = language->createParseJob(url); if (!job) { continue; // Language part did not produce a valid ParseJob. } job->setParsePriority(priority); job->setMinimumFeatures(features); job->setNotifyWhenReady(notifyWhenReady); ThreadWeaver::QObjectDecorator* decorator = new ThreadWeaver::QObjectDecorator(job); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::done, m_parser, &BackgroundParser::parseComplete); QObject::connect(decorator, &ThreadWeaver::QObjectDecorator::failed, m_parser, &BackgroundParser::parseComplete); QObject::connect(job, &ParseJob::progress, m_parser, &BackgroundParser::parseProgress, Qt::QueuedConnection); m_parseJobs.insert(url, decorator); ++m_maxParseJobs; // TODO more thinking required here to support multiple parse jobs per url (where multiple language plugins want to parse) return decorator; } if(languages.isEmpty()) qCDebug(LANGUAGE) << "found no languages for url" << qUrl; else qCDebug(LANGUAGE) << "could not create parse-job for url" << qUrl; //Notify that we failed typedef QPointer Notify; foreach(const Notify& n, notifyWhenReady) if(n) QMetaObject::invokeMethod(n.data(), "updateReady", Qt::QueuedConnection, Q_ARG(KDevelop::IndexedString, url), Q_ARG(KDevelop::ReferencedTopDUContext, ReferencedTopDUContext())); return nullptr; } void loadSettings() { ///@todo re-load settings when they have been changed! Q_ASSERT(ICore::self()->activeSession()); KConfigGroup config(ICore::self()->activeSession()->config(), "Background Parser"); // stay backwards compatible KConfigGroup oldConfig(KSharedConfig::openConfig(), "Background Parser"); #define BACKWARDS_COMPATIBLE_ENTRY(entry, default) \ config.readEntry(entry, oldConfig.readEntry(entry, default)) m_delay = BACKWARDS_COMPATIBLE_ENTRY("Delay", 500); m_timer.setInterval(m_delay); m_threads = 0; m_parser->setThreadCount(BACKWARDS_COMPATIBLE_ENTRY("Number of Threads", 1)); resume(); if (BACKWARDS_COMPATIBLE_ENTRY("Enabled", true)) { m_parser->enableProcessing(); } else { m_parser->disableProcessing(); } } void suspend() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (s) { // Already suspending return; } m_timer.stop(); m_weaver.suspend(); } void resume() { bool s = m_weaver.state()->stateId() == ThreadWeaver::Suspended || m_weaver.state()->stateId() == ThreadWeaver::Suspending; if (m_timer.isActive() && !s) { // Not suspending return; } m_timer.start(m_delay); m_weaver.resume(); } BackgroundParser *m_parser; ILanguageController* m_languageController; //Current parse-job that is executed in the additional thread QPointer specialParseJob; QTimer m_timer; int m_delay; int m_threads; bool m_shuttingDown; struct DocumentParseTarget { QPointer notifyWhenReady; int priority; TopDUContext::Features features; ParseJob::SequentialProcessingFlags sequentialProcessingFlags; bool operator==(const DocumentParseTarget& rhs) const { return notifyWhenReady == rhs.notifyWhenReady && priority == rhs.priority && features == rhs.features; } }; struct DocumentParsePlan { QSet targets; ParseJob::SequentialProcessingFlags sequentialProcessingFlags() const { //Pick the strictest possible flags ParseJob::SequentialProcessingFlags ret = ParseJob::IgnoresSequentialProcessing; foreach(const DocumentParseTarget &target, targets) { ret |= target.sequentialProcessingFlags; } return ret; } int priority() const { //Pick the best priority int ret = BackgroundParser::WorstPriority; foreach(const DocumentParseTarget &target, targets) { if(target.priority < ret) { ret = target.priority; } } return ret; } TopDUContext::Features features() const { //Pick the best features TopDUContext::Features ret = (TopDUContext::Features)0; foreach(const DocumentParseTarget &target, targets) { ret = (TopDUContext::Features) (ret | target.features); } return ret; } QList > notifyWhenReady() const { QList > ret; foreach(const DocumentParseTarget &target, targets) if(target.notifyWhenReady) ret << target.notifyWhenReady; return ret; } }; // A list of documents that are planned to be parsed, and their priority QHash m_documents; // The documents ordered by priority QMap > m_documentsForPriority; // Currently running parse jobs QHash m_parseJobs; // A change tracker for each managed document QHash m_managed; // The url for each managed document. Those may temporarily differ from the real url. QHash m_managedTextDocumentUrls; // Projects currently in progress of loading QSet m_loadingProjects; ThreadWeaver::Queue m_weaver; QMutex m_mutex; int m_maxParseJobs; int m_doneParseJobs; QHash m_jobProgress; int m_neededPriority; //The minimum priority needed for processed jobs }; inline uint qHash(const BackgroundParserPrivate::DocumentParseTarget& target) { return target.features * 7 + target.priority * 13 + target.sequentialProcessingFlags * 17 + reinterpret_cast(target.notifyWhenReady.data()); }; BackgroundParser::BackgroundParser(ILanguageController *languageController) : QObject(languageController), d(new BackgroundParserPrivate(this, languageController)) { Q_ASSERT(ICore::self()->documentController()); connect(ICore::self()->documentController(), &IDocumentController::documentLoaded, this, &BackgroundParser::documentLoaded); connect(ICore::self()->documentController(), &IDocumentController::documentUrlChanged, this, &BackgroundParser::documentUrlChanged); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, this, &BackgroundParser::documentClosed); connect(ICore::self(), &ICore::aboutToShutdown, this, &BackgroundParser::aboutToQuit); bool connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectAboutToBeOpened, this, &BackgroundParser::projectAboutToBeOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &BackgroundParser::projectOpened); Q_ASSERT(connected); connected = QObject::connect(ICore::self()->projectController(), &IProjectController::projectOpeningAborted, this, &BackgroundParser::projectOpeningAborted); Q_ASSERT(connected); Q_UNUSED(connected); } void BackgroundParser::aboutToQuit() { d->m_shuttingDown = true; } BackgroundParser::~BackgroundParser() { delete d; } QString BackgroundParser::statusName() const { return i18n("Background Parser"); } void BackgroundParser::loadSettings() { d->loadSettings(); } void BackgroundParser::parseProgress(KDevelop::ParseJob* job, float value, QString text) { Q_UNUSED(text) d->m_jobProgress[job] = value; updateProgressBar(); } void BackgroundParser::revertAllRequests(QObject* notifyWhenReady) { QMutexLocker lock(&d->m_mutex); for(QHash::iterator it = d->m_documents.begin(); it != d->m_documents.end(); ) { d->m_documentsForPriority[it.value().priority()].remove(it.key()); foreach ( const BackgroundParserPrivate::DocumentParseTarget& target, (*it).targets ) { if ( notifyWhenReady && target.notifyWhenReady.data() == notifyWhenReady ) { (*it).targets.remove(target); } } if((*it).targets.isEmpty()) { it = d->m_documents.erase(it); --d->m_maxParseJobs; continue; } d->m_documentsForPriority[it.value().priority()].insert(it.key()); ++it; } } void BackgroundParser::addDocument(const IndexedString& url, TopDUContext::Features features, int priority, QObject* notifyWhenReady, ParseJob::SequentialProcessingFlags flags) { // qCDebug(LANGUAGE) << "BackgroundParser::addDocument" << url.toUrl(); Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); { BackgroundParserPrivate::DocumentParseTarget target; target.priority = priority; target.features = features; target.sequentialProcessingFlags = flags; target.notifyWhenReady = QPointer(notifyWhenReady); QHash::iterator it = d->m_documents.find(url); if (it != d->m_documents.end()) { //Update the stored plan d->m_documentsForPriority[it.value().priority()].remove(url); it.value().targets << target; d->m_documentsForPriority[it.value().priority()].insert(url); }else{ // qCDebug(LANGUAGE) << "BackgroundParser::addDocument: queuing" << cleanedUrl; d->m_documents[url].targets << target; d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); ++d->m_maxParseJobs; //So the progress-bar waits for this document } d->startTimerThreadSafe(); } } void BackgroundParser::removeDocument(const IndexedString& url, QObject* notifyWhenReady) { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); if(d->m_documents.contains(url)) { d->m_documentsForPriority[d->m_documents[url].priority()].remove(url); foreach(const BackgroundParserPrivate::DocumentParseTarget& target, d->m_documents[url].targets) { if(target.notifyWhenReady.data() == notifyWhenReady) { d->m_documents[url].targets.remove(target); } } if(d->m_documents[url].targets.isEmpty()) { d->m_documents.remove(url); --d->m_maxParseJobs; }else{ //Insert with an eventually different priority d->m_documentsForPriority[d->m_documents[url].priority()].insert(url); } } } void BackgroundParser::parseDocuments() { if (!d->m_loadingProjects.empty()) { startTimer(); return; } QMutexLocker lock(&d->m_mutex); d->parseDocumentsInternal(); } void BackgroundParser::parseComplete(const ThreadWeaver::JobPointer& job) { auto decorator = dynamic_cast(job.data()); Q_ASSERT(decorator); ParseJob* parseJob = dynamic_cast(decorator->job()); Q_ASSERT(parseJob); emit parseJobFinished(parseJob); { QMutexLocker lock(&d->m_mutex); d->m_parseJobs.remove(parseJob->document()); d->m_jobProgress.remove(parseJob); ++d->m_doneParseJobs; updateProgressBar(); } //Continue creating more parse-jobs QMetaObject::invokeMethod(this, "parseDocuments", Qt::QueuedConnection); } void BackgroundParser::disableProcessing() { setNeededPriority(BestPriority); } void BackgroundParser::enableProcessing() { setNeededPriority(WorstPriority); } int BackgroundParser::priorityForDocument(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents[url].priority(); } bool BackgroundParser::isQueued(const IndexedString& url) const { Q_ASSERT(isValidURL(url)); QMutexLocker lock(&d->m_mutex); return d->m_documents.contains(url); } int BackgroundParser::queuedCount() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.count(); } bool BackgroundParser::isIdle() const { QMutexLocker lock(&d->m_mutex); return d->m_documents.isEmpty() && d->m_weaver.isIdle(); } void BackgroundParser::setNeededPriority(int priority) { QMutexLocker lock(&d->m_mutex); d->m_neededPriority = priority; d->startTimerThreadSafe(); } void BackgroundParser::suspend() { d->suspend(); emit hideProgress(this); } void BackgroundParser::resume() { d->resume(); updateProgressBar(); } void BackgroundParser::updateProgressBar() { if (d->m_doneParseJobs >= d->m_maxParseJobs) { if(d->m_doneParseJobs > d->m_maxParseJobs) { qCDebug(LANGUAGE) << "m_doneParseJobs larger than m_maxParseJobs:" << d->m_doneParseJobs << d->m_maxParseJobs; } d->m_doneParseJobs = 0; d->m_maxParseJobs = 0; } else { float additionalProgress = 0; for(QHash::const_iterator it = d->m_jobProgress.constBegin(); it != d->m_jobProgress.constEnd(); ++it) additionalProgress += *it; emit showProgress(this, 0, d->m_maxParseJobs*1000, (additionalProgress + d->m_doneParseJobs)*1000); } } ParseJob* BackgroundParser::parseJobForDocument(const IndexedString& document) const { Q_ASSERT(isValidURL(document)); QMutexLocker lock(&d->m_mutex); auto decorator = d->m_parseJobs.value(document); return decorator ? dynamic_cast(decorator->job()) : nullptr; } void BackgroundParser::setThreadCount(int threadCount) { if (d->m_threads != threadCount) { d->m_threads = threadCount; d->m_weaver.setMaximumNumberOfThreads(d->m_threads+1); //1 Additional thread for high-priority parsing } } int BackgroundParser::threadCount() const { return d->m_threads; } void BackgroundParser::setDelay(int miliseconds) { if (d->m_delay != miliseconds) { d->m_delay = miliseconds; d->m_timer.setInterval(d->m_delay); } } QList< IndexedString > BackgroundParser::managedDocuments() { QMutexLocker l(&d->m_mutex); return d->m_managed.keys(); } DocumentChangeTracker* BackgroundParser::trackerForUrl(const KDevelop::IndexedString& url) const { if (url.isEmpty()) { // this happens e.g. when setting the final location of a problem that is not // yet associated with a top ctx. return 0; } Q_ASSERT(isValidURL(url)); QMutexLocker l(&d->m_mutex); return d->m_managed.value(url, 0); } void BackgroundParser::documentClosed(IDocument* document) { QMutexLocker l(&d->m_mutex); if(document->textDocument()) { KTextEditor::Document* textDocument = document->textDocument(); if(!d->m_managedTextDocumentUrls.contains(textDocument)) return; // Probably the document had an invalid url, and thus it wasn't added to the background parser Q_ASSERT(d->m_managedTextDocumentUrls.contains(textDocument)); IndexedString url(d->m_managedTextDocumentUrls[textDocument]); Q_ASSERT(d->m_managed.contains(url)); qCDebug(LANGUAGE) << "removing" << url.str() << "from background parser"; delete d->m_managed[url]; d->m_managedTextDocumentUrls.remove(textDocument); d->m_managed.remove(url); } } void BackgroundParser::documentLoaded( IDocument* document ) { QMutexLocker l(&d->m_mutex); if(document->textDocument() && document->textDocument()->url().isValid()) { KTextEditor::Document* textDocument = document->textDocument(); IndexedString url(document->url()); // Some debugging because we had issues with this if(d->m_managed.contains(url) && d->m_managed[url]->document() == textDocument) { qCDebug(LANGUAGE) << "Got redundant documentLoaded from" << document->url() << textDocument; return; } qCDebug(LANGUAGE) << "Creating change tracker for " << document->url(); Q_ASSERT(!d->m_managed.contains(url)); Q_ASSERT(!d->m_managedTextDocumentUrls.contains(textDocument)); d->m_managedTextDocumentUrls[textDocument] = url; d->m_managed.insert(url, new DocumentChangeTracker(textDocument)); }else{ qCDebug(LANGUAGE) << "NOT creating change tracker for" << document->url(); } } void BackgroundParser::documentUrlChanged(IDocument* document) { documentClosed(document); // Only call documentLoaded if the file wasn't renamed to a filename that is already tracked. if(document->textDocument() && !d->m_managed.contains(IndexedString(document->textDocument()->url()))) documentLoaded(document); } void BackgroundParser::startTimer() { d->m_timer.start(d->m_delay); } void BackgroundParser::projectAboutToBeOpened(IProject* project) { d->m_loadingProjects.insert(project); } void BackgroundParser::projectOpened(IProject* project) { d->m_loadingProjects.remove(project); } void BackgroundParser::projectOpeningAborted(IProject* project) { d->m_loadingProjects.remove(project); } } Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParseTarget, Q_MOVABLE_TYPE); Q_DECLARE_TYPEINFO(KDevelop::BackgroundParserPrivate::DocumentParsePlan, Q_MOVABLE_TYPE); diff --git a/language/codegen/applychangeswidget.cpp b/language/codegen/applychangeswidget.cpp index d339dfd2f7..b18534495d 100644 --- a/language/codegen/applychangeswidget.cpp +++ b/language/codegen/applychangeswidget.cpp @@ -1,215 +1,211 @@ /* Copyright 2008 Aleix Pol * Copyright 2009 Ramón Zarazúa * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "applychangeswidget.h" #include #include -#include +#include +#include +#include #include -#include -#include +#include #include #include -#include -#include -#include -#include #include +#include +#include +#include +#include + #include "coderepresentation.h" #include #include -#include -#include -#include -#include -#include -#include namespace KDevelop { class ApplyChangesWidgetPrivate { public: ApplyChangesWidgetPrivate(ApplyChangesWidget * p) : parent(p), m_index(0) {} ~ApplyChangesWidgetPrivate() { qDeleteAll(m_temps); } void createEditPart(const KDevelop::IndexedString& url); ApplyChangesWidget * const parent; int m_index; QList m_editParts; QList m_temps; QList m_files; QTabWidget * m_documentTabs; QLabel* m_info; }; ApplyChangesWidget::ApplyChangesWidget(QWidget* parent) : QDialog(parent), d(new ApplyChangesWidgetPrivate(this)) { setSizeGripEnabled(true); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto mainLayout = new QVBoxLayout(this); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, this, &ApplyChangesWidget::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &ApplyChangesWidget::reject); QWidget* w=new QWidget(this); d->m_info=new QLabel(w); d->m_documentTabs = new QTabWidget(w); connect(d->m_documentTabs, &QTabWidget::currentChanged, this, &ApplyChangesWidget::indexChanged); QVBoxLayout* l = new QVBoxLayout(w); l->addWidget(d->m_info); l->addWidget(d->m_documentTabs); mainLayout->addWidget(w); mainLayout->addWidget(buttonBox); resize(QSize(800, 400)); } ApplyChangesWidget::~ApplyChangesWidget() { delete d; } bool ApplyChangesWidget::hasDocuments() const { return d->m_editParts.size() > 0; } KTextEditor::Document* ApplyChangesWidget::document() const { return qobject_cast(d->m_editParts[d->m_index]); } void ApplyChangesWidget::setInformation(const QString & info) { d->m_info->setText(info); } void ApplyChangesWidget::addDocuments(const IndexedString & original) { int idx=d->m_files.indexOf(original); if(idx<0) { QWidget * w = new QWidget; d->m_documentTabs->addTab(w, original.str()); d->m_documentTabs->setCurrentWidget(w); d->m_files.insert(d->m_index, original); d->createEditPart(original); } else { d->m_index=idx; } } bool ApplyChangesWidget::applyAllChanges() { /// @todo implement safeguard in case a file saving fails bool ret = true; for(int i = 0; i < d->m_files.size(); ++i ) if(d->m_editParts[i]->saveAs(d->m_files[i].toUrl())) { IDocument* doc = ICore::self()->documentController()->documentForUrl(d->m_files[i].toUrl()); if(doc && doc->state()==IDocument::Dirty) doc->reload(); } else ret = false; return ret; } } Q_DECLARE_METATYPE(KTextEditor::Range) namespace KDevelop { void ApplyChangesWidgetPrivate::createEditPart(const IndexedString & file) { QWidget * widget = m_documentTabs->currentWidget(); Q_ASSERT(widget); QVBoxLayout *m=new QVBoxLayout(widget); QSplitter *v=new QSplitter(widget); m->addWidget(v); QUrl url = file.toUrl(); QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(url); KParts::ReadWritePart* part=KMimeTypeTrader::self()->createPartInstanceFromQuery(mimetype.name(), widget, widget); KTextEditor::Document* document=qobject_cast(part); Q_ASSERT(document); Q_ASSERT(document->action("file_save")); document->action("file_save")->setEnabled(false); m_editParts.insert(m_index, part); //Open the best code representation, even if it is artificial CodeRepresentation::Ptr repr = createCodeRepresentation(file); if(!repr->fileExists()) { const auto templateName = QDir::tempPath() + QLatin1Char('/') + url.fileName().split('.').last(); QTemporaryFile * temp(new QTemporaryFile(templateName)); temp->open(); temp->write(repr->text().toUtf8()); temp->close(); url = QUrl::fromLocalFile(temp->fileName()); m_temps << temp; } m_editParts[m_index]->openUrl(url); v->addWidget(m_editParts[m_index]->widget()); v->setSizes(QList() << 400 << 100); } void ApplyChangesWidget::indexChanged(int newIndex) { Q_ASSERT(newIndex != -1); d->m_index = newIndex; } void ApplyChangesWidget::updateDiffView(int index) { int prevIndex = d->m_index; d->m_index = index == -1 ? d->m_index : index; d->m_index = prevIndex; } } diff --git a/language/codegen/basicrefactoring.cpp b/language/codegen/basicrefactoring.cpp index fc559a018a..57f1b96b0a 100644 --- a/language/codegen/basicrefactoring.cpp +++ b/language/codegen/basicrefactoring.cpp @@ -1,355 +1,356 @@ /* This file is part of KDevelop * * Copyright 2014 Miquel Sabaté * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // Qt #include // KDE / KDevelop +#include #include #include #include -#include + #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "progressdialogs/refactoringdialog.h" #include "util/debug.h" #include "ui_basicrefactoring.h" namespace { QPair splitFileAtExtension(const QString& fileName) { int idx = fileName.indexOf('.'); if (idx == -1) { return qMakePair(fileName, QString()); } return qMakePair(fileName.left(idx), fileName.mid(idx)); } } using namespace KDevelop; //BEGIN: BasicRefactoringCollector BasicRefactoringCollector::BasicRefactoringCollector(const IndexedDeclaration &decl) : UsesWidgetCollector(decl) { setCollectConstructors(true); setCollectDefinitions(true); setCollectOverloads(true); } QVector BasicRefactoringCollector::allUsingContexts() const { return m_allUsingContexts; } void BasicRefactoringCollector::processUses(KDevelop::ReferencedTopDUContext topContext) { m_allUsingContexts << IndexedTopDUContext(topContext.data()); UsesWidgetCollector::processUses(topContext); } //END: BasicRefactoringCollector //BEGIN: BasicRefactoring BasicRefactoring::BasicRefactoring(QObject *parent) : QObject(parent) { /* There's nothing to do here. */ } void BasicRefactoring::fillContextMenu(ContextMenuExtension &extension, Context *context) { DeclarationContext *declContext = dynamic_cast(context); if (!declContext) return; DUChainReadLocker lock; Declaration *declaration = declContext->declaration().data(); if (declaration && acceptForContextMenu(declaration)) { QFileInfo finfo(declaration->topContext()->url().str()); if (finfo.isWritable()) { QAction *action = new QAction(i18n("Rename \"%1\"...", declaration->qualifiedIdentifier().toString()), 0); action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); action->setIcon(QIcon::fromTheme("edit-rename")); connect(action, &QAction::triggered, this, &BasicRefactoring::executeRenameAction); extension.addAction(ContextMenuExtension::RefactorGroup, action); } } } bool BasicRefactoring::shouldRenameUses(KDevelop::Declaration* declaration) const { // Now we know we're editing a declaration, but some declarations we don't offer a rename for // basically that's any declaration that wouldn't be fully renamed just by renaming its uses(). if (declaration->internalContext() || declaration->isForwardDeclaration()) { //make an exception for non-class functions if (!declaration->isFunctionDeclaration() || dynamic_cast(declaration)) return false; } return true; } QString BasicRefactoring::newFileName(const QUrl& current, const QString& newName) { QPair nameExtensionPair = splitFileAtExtension(current.fileName()); // if current file is lowercased, keep that if (nameExtensionPair.first == nameExtensionPair.first.toLower()) { return newName.toLower() + nameExtensionPair.second; } else { return newName + nameExtensionPair.second; } } DocumentChangeSet::ChangeResult BasicRefactoring::addRenameFileChanges(const QUrl& current, const QString& newName, DocumentChangeSet* changes) { return changes->addDocumentRenameChange( IndexedString(current), IndexedString(newFileName(current, newName))); } bool BasicRefactoring::shouldRenameFile(Declaration* declaration) { // only try to rename files when we renamed a class/struct if (!dynamic_cast(declaration)) { return false; } const QUrl currUrl = declaration->topContext()->url().toUrl(); const QString fileName = currUrl.fileName(); const QPair nameExtensionPair = splitFileAtExtension(fileName); // check whether we renamed something that is called like the document it lives in return nameExtensionPair.first.compare(declaration->identifier().toString(), Qt::CaseInsensitive) == 0; } DocumentChangeSet::ChangeResult BasicRefactoring::applyChanges(const QString &oldName, const QString &newName, DocumentChangeSet &changes, DUContext *context, int usedDeclarationIndex) { if (usedDeclarationIndex == std::numeric_limits::max()) return DocumentChangeSet::ChangeResult(true); for (int a = 0; a < context->usesCount(); ++a) { const Use &use(context->uses()[a]); if (use.m_declarationIndex != usedDeclarationIndex) continue; if (use.m_range.isEmpty()) { qCDebug(LANGUAGE) << "found empty use"; continue; } DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(context->url(), context->transformFromLocalRevision(use.m_range), oldName, newName)); if (!result) return result; } foreach (DUContext *child, context->childContexts()) { DocumentChangeSet::ChangeResult result = applyChanges(oldName, newName, changes, child, usedDeclarationIndex); if (!result) return result; } return DocumentChangeSet::ChangeResult(true); } DocumentChangeSet::ChangeResult BasicRefactoring::applyChangesToDeclarations(const QString &oldName, const QString &newName, DocumentChangeSet &changes, const QList &declarations) { foreach (const IndexedDeclaration &decl, declarations) { Declaration *declaration = decl.data(); if (!declaration) continue; if (declaration->range().isEmpty()) qCDebug(LANGUAGE) << "found empty declaration"; TopDUContext *top = declaration->topContext(); DocumentChangeSet::ChangeResult result = changes.addChange(DocumentChange(top->url(), declaration->rangeInCurrentRevision(), oldName, newName)); if (!result) return result; } return DocumentChangeSet::ChangeResult(true); } KDevelop::IndexedDeclaration BasicRefactoring::declarationUnderCursor(bool allowUse) { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); Q_ASSERT(view); KTextEditor::Document* doc = view->document(); DUChainReadLocker lock; if (allowUse) return DUChainUtils::itemUnderCursor(doc->url(), KTextEditor::Cursor(view->cursorPosition())); else return DUChainUtils::declarationInLine(KTextEditor::Cursor(view->cursorPosition()), DUChainUtils::standardContextForUrl(doc->url())); } void BasicRefactoring::startInteractiveRename(const KDevelop::IndexedDeclaration &decl) { DUChainReadLocker lock(DUChain::lock()); Declaration *declaration = decl.data(); if (!declaration) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("No declaration under cursor")); return; } QFileInfo info(declaration->topContext()->url().str()); if (!info.isWritable()) { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), i18n("Declaration is located in non-writeable file %1.", declaration->topContext()->url().str())); return; } QString originalName = declaration->identifier().identifier().str(); lock.unlock(); NameAndCollector nc = newNameForDeclaration(DeclarationPointer(declaration)); if (nc.newName == originalName || nc.newName.isEmpty()) return; renameCollectedDeclarations(nc.collector.data(), nc.newName, originalName); } bool BasicRefactoring::acceptForContextMenu(const Declaration *decl) { // Default implementation. Some language plugins might override it to // handle some cases. Q_UNUSED(decl); return true; } void BasicRefactoring::executeRenameAction() { QAction *action = qobject_cast(sender()); if (action) { IndexedDeclaration decl = action->data().value(); if(!decl.isValid()) decl = declarationUnderCursor(); startInteractiveRename(decl); } } BasicRefactoring::NameAndCollector BasicRefactoring::newNameForDeclaration(const KDevelop::DeclarationPointer& declaration) { DUChainReadLocker lock; if (!declaration) { return {}; } QSharedPointer collector(new BasicRefactoringCollector(declaration.data())); Ui::RenameDialog renameDialog; QDialog dialog; renameDialog.setupUi(&dialog); UsesWidget uses(declaration.data(), collector); //So the context-links work QWidget *navigationWidget = declaration->context()->createNavigationWidget(declaration.data()); AbstractNavigationWidget* abstractNavigationWidget = dynamic_cast(navigationWidget); if (abstractNavigationWidget) connect(&uses, &UsesWidget::navigateDeclaration, abstractNavigationWidget, &AbstractNavigationWidget::navigateDeclaration); QString declarationName = declaration->toString(); dialog.setWindowTitle(i18nc("Renaming some declaration", "Rename \"%1\"", declarationName)); renameDialog.edit->setText(declaration->identifier().identifier().str()); renameDialog.edit->selectAll(); renameDialog.tabWidget->addTab(&uses, i18n("Uses")); if (navigationWidget) renameDialog.tabWidget->addTab(navigationWidget, i18n("Declaration Info")); lock.unlock(); if (dialog.exec() != QDialog::Accepted) return {}; RefactoringProgressDialog refactoringProgress(i18n("Renaming \"%1\" to \"%2\"", declarationName, renameDialog.edit->text()), collector.data()); if (!collector->isReady()) { refactoringProgress.exec(); if (refactoringProgress.result() != QDialog::Accepted) { return {}; } } //TODO: input validation return {renameDialog.edit->text(),collector}; } DocumentChangeSet BasicRefactoring::renameCollectedDeclarations(KDevelop::BasicRefactoringCollector* collector, const QString& replacementName, const QString& originalName, bool apply) { DocumentChangeSet changes; DUChainReadLocker lock; foreach (const KDevelop::IndexedTopDUContext& collected, collector->allUsingContexts()) { QSet hadIndices; foreach (const IndexedDeclaration& decl, collector->declarations()) { uint usedDeclarationIndex = collected.data()->indexForUsedDeclaration(decl.data(), false); if (hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); DocumentChangeSet::ChangeResult result = applyChanges(originalName, replacementName, changes, collected.data(), usedDeclarationIndex); if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } } } DocumentChangeSet::ChangeResult result = applyChangesToDeclarations(originalName, replacementName, changes, collector->declarations()); if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); return {}; } ///We have to ignore failed changes for now, since uses of a constructor or of operator() may be created on "(" parens changes.setReplacementPolicy(DocumentChangeSet::IgnoreFailedChange); if (!apply) { return changes; } result = changes.applyAllChanges(); if (!result) { KMessageBox::error(0, i18n("Applying changes failed: %1", result.m_failureReason)); } return {}; } //END: BasicRefactoring diff --git a/language/codegen/codegenerator.cpp b/language/codegen/codegenerator.cpp index 56ddfcf17a..e399965d70 100644 --- a/language/codegen/codegenerator.cpp +++ b/language/codegen/codegenerator.cpp @@ -1,242 +1,242 @@ /* Copyright 2008 Hamish Rodda Copyright 2009 Ramon Zarazua This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codegenerator.h" #include "documentchangeset.h" #include "duchainchangeset.h" +#include #include #include #include #include -#include #include "applychangeswidget.h" #include "util/debug.h" namespace KDevelop { class CodeGeneratorPrivate { public: CodeGeneratorPrivate() : autoGen(false), context(0) {} QMap duchainChanges; DocumentChangeSet documentChanges; bool autoGen; DUContext * context; DocumentRange range; QString error; }; CodeGeneratorBase::CodeGeneratorBase() : d(new CodeGeneratorPrivate) { } CodeGeneratorBase::~CodeGeneratorBase() { clearChangeSets(); delete d; } void CodeGeneratorBase::autoGenerate ( DUContext* context, const KDevelop::DocumentRange* range ) { d->autoGen = true; d->context = context; d->range = *range; } void CodeGeneratorBase::addChangeSet(DUChainChangeSet * duchainChange) { IndexedString file = duchainChange->topDuContext().data()->url() ; QMap::iterator it = d->duchainChanges.find(file); //if we already have an entry for this file, merge it if(it !=d->duchainChanges.end()) { **it << *duchainChange; delete duchainChange; } else d->duchainChanges.insert(file, duchainChange); } void CodeGeneratorBase::addChangeSet(DocumentChangeSet & docChangeSet) { d->documentChanges << docChangeSet; } DocumentChangeSet & CodeGeneratorBase::documentChangeSet() { return d->documentChanges; } const QString & CodeGeneratorBase::errorText() const { return d->error; } bool CodeGeneratorBase::autoGeneration() const { return d->autoGen; } void CodeGeneratorBase::setErrorText(const QString & errorText) { d->error = errorText; } void CodeGeneratorBase::clearChangeSets() { qCDebug(LANGUAGE) << "Cleaning up all the changesets registered by the generator"; foreach(DUChainChangeSet * changeSet, d->duchainChanges) delete changeSet; d->duchainChanges.clear(); d->documentChanges = DocumentChangeSet(); } bool CodeGeneratorBase::execute() { qCDebug(LANGUAGE) << "Checking Preconditions for the codegenerator"; //Shouldn't there be a method in iDocument to get a DocumentRange as well? QUrl document; if(!d->autoGen) { if( !ICore::self()->documentController()->activeDocument() ) { setErrorText( i18n("Could not find an open document" ) ); return false; } document = ICore::self()->documentController()->activeDocument()->url(); if(d->range.isEmpty()) { DUChainReadLocker lock(DUChain::lock()); d->range = DocumentRange(document.url(), ICore::self()->documentController()->activeDocument()->textSelection()); } } if(!d->context) { DUChainReadLocker lock(DUChain::lock()); TopDUContext * documentChain = DUChain::self()->chainForDocument(document); if(!documentChain) { setErrorText(i18n("Could not find the chain for the selected document: %1").arg(document.url())); return false; } d->context = documentChain->findContextIncluding(d->range); if(!d->context) { //Attempt to get the context again QList contexts = DUChain::self()->chainsForDocument(document); foreach(TopDUContext * top, contexts) { qCDebug(LANGUAGE) << "Checking top context with range: " << top->range() << " for a context"; if((d->context = top->findContextIncluding(d->range))) break; } } } if(!d->context) { setErrorText(i18n("Error finding context for selection range")); return false; } if(!checkPreconditions(d->context,d->range)) { setErrorText(i18n("Error checking conditions to generate code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Gathering user information for the codegenerator"; if(!gatherInformation()) { setErrorText(i18n("Error Gathering user information: %1",errorText())); return false; } } qCDebug(LANGUAGE) << "Generating code"; if(!process()) { setErrorText(i18n("Error generating code: %1",errorText())); return false; } if(!d->autoGen) { qCDebug(LANGUAGE) << "Submitting to the user for review"; return displayChanges(); } //If it is autogenerated, it shouldn't need to apply changes, instead return them to client that my be another generator DocumentChangeSet::ChangeResult result(true); if(!d->autoGen && !(result = d->documentChanges.applyAllChanges())) { setErrorText(result.m_failureReason); return false; } return true; } bool CodeGeneratorBase::displayChanges() { DocumentChangeSet::ChangeResult result = d->documentChanges.applyAllToTemp(); if(!result) { setErrorText(result.m_failureReason); return false; } ApplyChangesWidget widget; //TODO: Find some good information to put widget.setInformation("Info?"); QMap temps = d->documentChanges.tempNamesForAll(); for(QMap::iterator it = temps.begin(); it != temps.end(); ++it) widget.addDocuments(it.key() , it.value()); if(widget.exec()) return widget.applyAllChanges(); else return false; } } diff --git a/language/codegen/coderepresentation.cpp b/language/codegen/coderepresentation.cpp index c868af68b7..3e194a1e80 100644 --- a/language/codegen/coderepresentation.cpp +++ b/language/codegen/coderepresentation.cpp @@ -1,375 +1,377 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "coderepresentation.h" -#include + +#include #include + #include #include #include #include #include #include namespace KDevelop { - + static bool onDiskChangesForbidden = false; QString CodeRepresentation::rangeText(const KTextEditor::Range& range) const { Q_ASSERT(range.end().line() < lines()); - + //Easier for single line ranges which should happen most of the time if(range.onSingleLine()) return QString( line( range.start().line() ).mid( range.start().column(), range.columnWidth() ) ); - + //Add up al the requested lines QString rangedText = line(range.start().line()).mid(range.start().column()); - + for(int i = range.start().line() + 1; i <= range.end().line(); ++i) rangedText += '\n' + ((i == range.end().line()) ? line(i).left(range.end().column()) : line(i)); - + return rangedText; } static void grepLine(const QString& identifier, const QString& lineText, int lineNumber, QVector& ret, bool surroundedByBoundary) { if (identifier.isEmpty()) return; int pos = 0; while(true) { pos = lineText.indexOf(identifier, pos); if(pos == -1) break; int start = pos; pos += identifier.length(); int end = pos; - + if(!surroundedByBoundary || ( (end == lineText.length() || !lineText[end].isLetterOrNumber() || lineText[end] != '_') && (start-1 < 0 || !lineText[start-1].isLetterOrNumber() || lineText[start-1] != '_')) ) { ret << KTextEditor::Range(lineNumber, start, lineNumber, end); } } } class EditorCodeRepresentation : public DynamicCodeRepresentation { public: EditorCodeRepresentation(KTextEditor::Document* document) : m_document(document) { m_url = IndexedString(m_document->url()); } - + virtual QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < m_document->lines(); ++line) grepLine(identifier, m_document->line(line), line, ret, surroundedByBoundary); return ret; } virtual KDevEditingTransaction::Ptr makeEditTransaction() override { return KDevEditingTransaction::Ptr(new KDevEditingTransaction(m_document)); } - + QString line(int line) const override { if(line < 0 || line >= m_document->lines()) return QString(); return m_document->line(line); } - + virtual int lines() const override { return m_document->lines(); } - + QString text() const override { return m_document->text(); } - + bool setText(const QString& text) override { bool ret; { KDevEditingTransaction t(m_document); ret = m_document->setText(text); } ModificationRevision::clearModificationCache(m_url); return ret; } - + bool fileExists() override{ return QFile(m_document->url().path()).exists(); } - + bool replace(const KTextEditor::Range& range, const QString& oldText, const QString& newText, bool ignoreOldText) override { QString old = m_document->text(range); if(oldText != old && !ignoreOldText) { return false; } bool ret; { KDevEditingTransaction t(m_document); ret = m_document->replaceText(range, newText); } ModificationRevision::clearModificationCache(m_url); return ret; } - + virtual QString rangeText(const KTextEditor::Range& range) const override { return m_document->text(range); } - + private: KTextEditor::Document* m_document; IndexedString m_url; }; class FileCodeRepresentation : public CodeRepresentation { public: FileCodeRepresentation(const IndexedString& document) : m_document(document) { QString localFile(document.toUrl().toLocalFile()); - + QFile file( localFile ); if ( file.open(QIODevice::ReadOnly) ) { data = QString::fromLocal8Bit(file.readAll()); lineData = data.split('\n'); } m_exists = file.exists(); } - + QString line(int line) const override { if(line < 0 || line >= lineData.size()) return QString(); - + return lineData.at(line); } - + virtual QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < lineData.count(); ++line) grepLine(identifier, lineData.at(line), line, ret, surroundedByBoundary); return ret; } - + virtual int lines() const override { return lineData.count(); } - + QString text() const override { return data; } - + bool setText(const QString& text) override { Q_ASSERT(!onDiskChangesForbidden); QString localFile(m_document.toUrl().toLocalFile()); QFile file( localFile ); if ( file.open(QIODevice::WriteOnly) ) { QByteArray data = text.toLocal8Bit(); - + if(file.write(data) == data.size()) { ModificationRevision::clearModificationCache(m_document); return true; } } return false; } - + bool fileExists() override{ return m_exists; } - + private: //We use QByteArray, because the column-numbers are measured in utf-8 IndexedString m_document; bool m_exists; QStringList lineData; QString data; }; class ArtificialStringData : public QSharedData { public: ArtificialStringData(const QString& data) { setData(data); } void setData(const QString& data) { m_data = data; m_lineData = m_data.split('\n'); } QString data() const { return m_data; } const QStringList& lines() const { return m_lineData; } - + private: QString m_data; QStringList m_lineData; }; class StringCodeRepresentation : public CodeRepresentation { public: StringCodeRepresentation(QExplicitlySharedDataPointer _data) : data(_data) { Q_ASSERT(data); } - + QString line(int line) const override { if(line < 0 || line >= data->lines().size()) return QString(); - + return data->lines().at(line); } - + virtual int lines() const override { return data->lines().count(); } - + QString text() const override { return data->data(); } - + bool setText(const QString& text) override { data->setData(text); return true; } - + bool fileExists() override{ return false; } - + virtual QVector< KTextEditor::Range > grep ( const QString& identifier, bool surroundedByBoundary ) const override { QVector< KTextEditor::Range > ret; if (identifier.isEmpty()) return ret; for(int line = 0; line < data->lines().count(); ++line) grepLine(identifier, data->lines().at(line), line, ret, surroundedByBoundary); return ret; } - + private: QExplicitlySharedDataPointer data; }; static QHash > artificialStrings; //Return the representation for the given URL if it exists, or an empty pointer otherwise static QExplicitlySharedDataPointer representationForPath(const IndexedString& path) { if(artificialStrings.contains(path)) return artificialStrings[path]; else { IndexedString constructedPath(CodeRepresentation::artificialPath(path.str())); if(artificialStrings.contains(constructedPath)) return artificialStrings[constructedPath]; else return QExplicitlySharedDataPointer(); } } bool artificialCodeRepresentationExists(const IndexedString& path) { return representationForPath(path); } CodeRepresentation::Ptr createCodeRepresentation(const IndexedString& path) { if(artificialCodeRepresentationExists(path)) return CodeRepresentation::Ptr(new StringCodeRepresentation(representationForPath(path))); IDocument* document = ICore::self()->documentController()->documentForUrl(path.toUrl()); if(document && document->textDocument()) return CodeRepresentation::Ptr(new EditorCodeRepresentation(document->textDocument())); else return CodeRepresentation::Ptr(new FileCodeRepresentation(path)); } void CodeRepresentation::setDiskChangesForbidden(bool changesForbidden) { onDiskChangesForbidden = changesForbidden; } QString CodeRepresentation::artificialPath(const QString& name) { QUrl url = QUrl::fromLocalFile(name); return QString::fromLatin1("/kdev-artificial/") + url.adjusted(QUrl::NormalizePathSegments).path(); } InsertArtificialCodeRepresentation::InsertArtificialCodeRepresentation(const IndexedString& file, const QString& text) : m_file(file) { // make it simpler to use this by converting relative strings into artificial paths if(QUrl(m_file.str()).isRelative()) { m_file = IndexedString(CodeRepresentation::artificialPath(file.str())); - + int idx = 0; while(artificialStrings.contains(m_file)) { ++idx; m_file = IndexedString(CodeRepresentation::artificialPath(QStringLiteral("%1_%2").arg(idx).arg(file.str()))); } } - + Q_ASSERT(!artificialStrings.contains(m_file)); artificialStrings.insert(m_file, QExplicitlySharedDataPointer(new ArtificialStringData(text))); } IndexedString InsertArtificialCodeRepresentation::file() { return m_file; } InsertArtificialCodeRepresentation::~InsertArtificialCodeRepresentation() { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings.remove(m_file); } void InsertArtificialCodeRepresentation::setText(const QString& text) { Q_ASSERT(artificialStrings.contains(m_file)); artificialStrings[m_file]->setData(text); } QString InsertArtificialCodeRepresentation::text() { Q_ASSERT(artificialStrings.contains(m_file)); return artificialStrings[m_file]->data(); } } // kate: indent-width 4; diff --git a/language/codegen/templaterenderer.cpp b/language/codegen/templaterenderer.cpp index 5fcacb004e..3005924c7a 100644 --- a/language/codegen/templaterenderer.cpp +++ b/language/codegen/templaterenderer.cpp @@ -1,309 +1,310 @@ /* * This file is part of KDevelop * Copyright 2012 Miha Čančula * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "templaterenderer.h" #include "documentchangeset.h" #include "sourcefiletemplate.h" #include "templateengine.h" #include "templateengine_p.h" #include "archivetemplateloader.h" #include "util/debug.h" #include #include -#include -#include #include -#include #include +#include +#include + +#include using namespace Grantlee; class NoEscapeStream : public OutputStream { public: NoEscapeStream(); explicit NoEscapeStream (QTextStream* stream); virtual QString escape (const QString& input) const override; virtual QSharedPointer< OutputStream > clone (QTextStream* stream) const override; }; NoEscapeStream::NoEscapeStream() : OutputStream() { } NoEscapeStream::NoEscapeStream(QTextStream* stream) : OutputStream (stream) { } QString NoEscapeStream::escape(const QString& input) const { return input; } QSharedPointer NoEscapeStream::clone(QTextStream* stream) const { QSharedPointer clonedStream = QSharedPointer( new NoEscapeStream( stream ) ); return clonedStream; } using namespace KDevelop; namespace KDevelop { class TemplateRendererPrivate { public: Engine* engine; Grantlee::Context context; TemplateRenderer::EmptyLinesPolicy emptyLinesPolicy; QString errorString; }; } TemplateRenderer::TemplateRenderer() : d(new TemplateRendererPrivate) { d->engine = &TemplateEngine::self()->d->engine; d->emptyLinesPolicy = KeepEmptyLines; } TemplateRenderer::~TemplateRenderer() { delete d; } void TemplateRenderer::addVariables(const QVariantHash& variables) { QVariantHash::const_iterator it = variables.constBegin(); QVariantHash::const_iterator end = variables.constEnd(); for (; it != end; ++it) { d->context.insert(it.key(), it.value()); } } void TemplateRenderer::addVariable(const QString& name, const QVariant& value) { d->context.insert(name, value); } QVariantHash TemplateRenderer::variables() const { return d->context.stackHash(0); } QString TemplateRenderer::render(const QString& content, const QString& name) const { Template t = d->engine->newTemplate(content, name); QString output; QTextStream textStream(&output); NoEscapeStream stream(&textStream); t->render(&stream, &d->context); if (t->error() != Grantlee::NoError) { d->errorString = t->errorString(); } else { d->errorString.clear(); } if (d->emptyLinesPolicy == TrimEmptyLines && output.contains('\n')) { QStringList lines = output.split('\n', QString::KeepEmptyParts); QMutableStringListIterator it(lines); // Remove empty lines from the start of the document while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } else { break; } } // Remove single empty lines it.toFront(); bool prePreviousEmpty = false; bool previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.peekNext().trimmed().isEmpty(); if (!prePreviousEmpty && previousEmpty && !currentEmpty) { it.remove(); } prePreviousEmpty = previousEmpty; previousEmpty = currentEmpty; it.next(); } // Compress multiple empty lines it.toFront(); previousEmpty = false; while (it.hasNext()) { bool currentEmpty = it.next().trimmed().isEmpty(); if (currentEmpty && previousEmpty) { it.remove(); } previousEmpty = currentEmpty; } // Remove empty lines from the end it.toBack(); while (it.hasPrevious()) { if (it.previous().trimmed().isEmpty()) { it.remove(); } else { break; } } // Add a newline to the end of file it.toBack(); it.insert(QString()); output = lines.join("\n"); } else if (d->emptyLinesPolicy == RemoveEmptyLines) { QStringList lines = output.split('\n', QString::SkipEmptyParts); QMutableStringListIterator it(lines); while (it.hasNext()) { if (it.next().trimmed().isEmpty()) { it.remove(); } } it.toBack(); if (lines.size() > 1) { it.insert(QString()); } output = lines.join("\n"); } return output; } QString TemplateRenderer::renderFile(const QUrl& url, const QString& name) const { QFile file(url.toLocalFile()); file.open(QIODevice::ReadOnly); QString content(file.readAll()); qCDebug(LANGUAGE) << content; return render(content, name); } QStringList TemplateRenderer::render(const QStringList& contents) const { qCDebug(LANGUAGE) << d->context.stackHash(0); QStringList ret; foreach (const QString& content, contents) { ret << render(content); } return ret; } void TemplateRenderer::setEmptyLinesPolicy(TemplateRenderer::EmptyLinesPolicy policy) { d->emptyLinesPolicy = policy; } TemplateRenderer::EmptyLinesPolicy TemplateRenderer::emptyLinesPolicy() const { return d->emptyLinesPolicy; } DocumentChangeSet TemplateRenderer::renderFileTemplate(const SourceFileTemplate& fileTemplate, const QUrl& baseUrl, const QHash& fileUrls) { DocumentChangeSet changes; const QDir baseDir(baseUrl.path()); QRegExp nonAlphaNumeric("\\W"); for (QHash::const_iterator it = fileUrls.constBegin(); it != fileUrls.constEnd(); ++it) { QString cleanName = it.key().toLower(); cleanName.replace(nonAlphaNumeric, "_"); const QString path = it.value().toLocalFile(); addVariable("output_file_" + cleanName, baseDir.relativeFilePath(path)); addVariable("output_file_" + cleanName + "_absolute", path); } const KArchiveDirectory* directory = fileTemplate.directory(); ArchiveTemplateLocation location(directory); foreach (const SourceFileTemplate::OutputFile& outputFile, fileTemplate.outputFiles()) { const KArchiveEntry* entry = directory->entry(outputFile.fileName); if (!entry) { qCWarning(LANGUAGE) << "Entry" << outputFile.fileName << "is mentioned in group" << outputFile.identifier << "but is not present in the archive"; continue; } const KArchiveFile* file = dynamic_cast(entry); if (!file) { qCWarning(LANGUAGE) << "Entry" << entry->name() << "is not a file"; continue; } QUrl url = fileUrls[outputFile.identifier]; IndexedString document(url); KTextEditor::Range range(KTextEditor::Cursor(0, 0), 0); DocumentChange change(document, range, QString(), render(file->data(), outputFile.identifier)); changes.addChange(change); qCDebug(LANGUAGE) << "Added change for file" << document.str(); } return changes; } QString TemplateRenderer::errorString() const { return d->errorString; } diff --git a/language/duchain/navigation/abstractnavigationwidget.cpp b/language/duchain/navigation/abstractnavigationwidget.cpp index e3dcb2d6f3..b32ff5b1ea 100644 --- a/language/duchain/navigation/abstractnavigationwidget.cpp +++ b/language/duchain/navigation/abstractnavigationwidget.cpp @@ -1,293 +1,291 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "abstractnavigationwidget.h" -#include -#include +#include +#include +#include #include -#include #include #include #include "../declaration.h" #include "../ducontext.h" #include "../duchain.h" #include "../duchainlock.h" #include "util/debug.h" -#include -#include namespace KDevelop { AbstractNavigationWidget::AbstractNavigationWidget() : m_browser(0), m_currentWidget(0) { setPalette( QApplication::palette() ); setFocusPolicy(Qt::NoFocus); resize(100, 100); } const int maxNavigationWidgetWidth = 580; QSize AbstractNavigationWidget::sizeHint() const { if(m_browser) { updateIdealSize(); QSize ret = QSize(qMin(m_idealTextSize.width(), maxNavigationWidgetWidth), qMin(m_idealTextSize.height(), 300)); if(m_idealTextSize.height()>=300) { //make space for the scrollbar in case it's not fitting ret.rwidth() += 17; //m_browser->verticalScrollBar()->width() returns 100, for some reason } if(m_currentWidget) { ret.setHeight( ret.height() + m_currentWidget->sizeHint().height() ); if(m_currentWidget->sizeHint().width() > ret.width()) ret.setWidth(m_currentWidget->sizeHint().width()); if(ret.width() < 500) //When we embed a widget, give it some space, even if it doesn't have a large size-hint ret.setWidth(500); } return ret; } else return QWidget::sizeHint(); } void AbstractNavigationWidget::initBrowser(int height) { Q_UNUSED(height); m_browser = new QTextBrowser; // since we can embed arbitrary HTML we have to make sure it stays readable by forcing a black-white palette QPalette p; p.setColor(QPalette::AlternateBase, Qt::white); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); m_browser->setPalette( p ); m_browser->setOpenLinks(false); m_browser->setOpenExternalLinks(false); QVBoxLayout* layout = new QVBoxLayout; layout->addWidget(m_browser); layout->setMargin(0); setLayout(layout); connect( m_browser.data(), &QTextBrowser::anchorClicked, this, &AbstractNavigationWidget::anchorClicked ); foreach(QWidget* w, findChildren()) w->setContextMenuPolicy(Qt::NoContextMenu); } AbstractNavigationWidget::~AbstractNavigationWidget() { if(m_currentWidget) layout()->removeWidget(m_currentWidget); } void AbstractNavigationWidget::setContext(NavigationContextPointer context, int initBrows) { if(m_browser == 0) initBrowser(initBrows); if(!context) { qCDebug(LANGUAGE) << "no new context created"; return; } if(context == m_context && (!context || context->alreadyComputed())) return; if (!m_startContext) m_startContext = m_context; bool wasInitial = (m_context == m_startContext); m_context = context; update(); emit contextChanged(wasInitial, m_context == m_startContext); emit sizeHintChanged(); } void AbstractNavigationWidget::updateIdealSize() const { if(m_context && !m_idealTextSize.isValid()) { QTextDocument doc; doc.setHtml(m_currentText); if(doc.idealWidth() > maxNavigationWidgetWidth) { doc.setPageSize( QSize(maxNavigationWidgetWidth, 30) ); m_idealTextSize.setWidth(maxNavigationWidgetWidth); }else{ m_idealTextSize.setWidth(doc.idealWidth()); } m_idealTextSize.setHeight(doc.size().height()); } } void AbstractNavigationWidget::update() { setUpdatesEnabled(false); Q_ASSERT( m_context ); QString html = m_context->html(); if(!html.isEmpty()) { int scrollPos = m_browser->verticalScrollBar()->value(); m_browser->setHtml( html ); m_currentText = html; m_idealTextSize = QSize(); QSize hint = sizeHint(); if(hint.height() >= m_idealTextSize.height()) { m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); }else{ m_browser->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); } m_browser->verticalScrollBar()->setValue(scrollPos); m_browser->scrollToAnchor("currentPosition"); m_browser->show(); }else{ m_browser->hide(); } if(m_currentWidget) { layout()->removeWidget(m_currentWidget); m_currentWidget->setParent(0); } m_currentWidget = m_context->widget(); m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); m_browser->setMaximumHeight(10000); if(m_currentWidget) { if (m_currentWidget->metaObject() ->indexOfSignal(QMetaObject::normalizedSignature("navigateDeclaration(KDevelop::IndexedDeclaration)")) != -1) { connect(m_currentWidget, SIGNAL(navigateDeclaration(KDevelop::IndexedDeclaration)), this, SLOT(navigateDeclaration(KDevelop::IndexedDeclaration))); } layout()->addWidget(m_currentWidget); if(m_context->isWidgetMaximized()) { //Leave unused room to the widget m_browser->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); m_browser->setMaximumHeight(m_idealTextSize.height()); } } setUpdatesEnabled(true); } NavigationContextPointer AbstractNavigationWidget::context() { return m_context; } void AbstractNavigationWidget::navigateDeclaration(KDevelop::IndexedDeclaration decl) { DUChainReadLocker lock( DUChain::lock() ); setContext(m_context->accept(decl)); } void AbstractNavigationWidget::anchorClicked(const QUrl& url) { DUChainReadLocker lock( DUChain::lock() ); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->acceptLink(url.toString()); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::next() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->nextLink(); update(); } void AbstractNavigationWidget::previous() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); m_context->previousLink(); update(); } void AbstractNavigationWidget::accept() { DUChainReadLocker lock( DUChain::lock() ); Q_ASSERT( m_context ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->accept(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::back() { DUChainReadLocker lock( DUChain::lock() ); QPointer thisPtr(this); NavigationContextPointer oldContext = m_context; NavigationContextPointer nextContext = m_context->back(); if(thisPtr) setContext( nextContext ); } void AbstractNavigationWidget::up() { DUChainReadLocker lock( DUChain::lock() ); m_context->up(); update(); } void AbstractNavigationWidget::down() { DUChainReadLocker lock( DUChain::lock() ); m_context->down(); update(); } void AbstractNavigationWidget::embeddedWidgetAccept() { accept(); } void AbstractNavigationWidget::embeddedWidgetDown() { down(); } void AbstractNavigationWidget::embeddedWidgetRight() { next(); } void AbstractNavigationWidget::embeddedWidgetLeft() { previous(); } void AbstractNavigationWidget::embeddedWidgetUp() { up(); } void AbstractNavigationWidget::wheelEvent(QWheelEvent* event ) { QWidget::wheelEvent(event); event->accept(); return; } } diff --git a/language/duchain/navigation/abstractnavigationwidget.h b/language/duchain/navigation/abstractnavigationwidget.h index 78a86d4305..ec07546d14 100644 --- a/language/duchain/navigation/abstractnavigationwidget.h +++ b/language/duchain/navigation/abstractnavigationwidget.h @@ -1,106 +1,106 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ABSTRACTNAVIGATIONWIDGET_H #define KDEVPLATFORM_ABSTRACTNAVIGATIONWIDGET_H #include #include #include +#include #include "../../interfaces/quickopendataprovider.h" #include #include "abstractnavigationcontext.h" -class QWidget; class QTextBrowser; namespace KDevelop { /** * This class deleted itself when its part is deleted, so always use a QPointer when referencing it. * The duchain must be read-locked for most operations * */ class KDEVPLATFORMLANGUAGE_EXPORT AbstractNavigationWidget : public QWidget, public QuickOpenEmbeddedWidgetInterface { Q_OBJECT public: AbstractNavigationWidget(); virtual ~AbstractNavigationWidget(); void setContext(NavigationContextPointer context, int initBrowser = 400); - + QSize sizeHint() const override; - + public slots: ///Keyboard-action "next" virtual void next() override; ///Keyboard-action "previous" virtual void previous() override; ///Keyboard-action "accept" virtual void accept() override; virtual void up() override; virtual void down() override; virtual void back(); ///These are temporarily for gettings these events directly from kate ///@todo Do this through a public interface post 4.2 void embeddedWidgetRight(); ///Keyboard-action "previous" void embeddedWidgetLeft(); ///Keyboard-action "accept" void embeddedWidgetAccept(); void embeddedWidgetUp(); void embeddedWidgetDown(); - + NavigationContextPointer context(); Q_SIGNALS: void sizeHintChanged(); /// Emitted whenever the current navigation-context has changed /// @param wasInitial whether the previous context was the initial context /// @param isInitial whether the current context is the initial context void contextChanged(bool wasInitial, bool isInitial); public slots: void navigateDeclaration(KDevelop::IndexedDeclaration decl); private slots: void anchorClicked(const QUrl&); protected: virtual void wheelEvent(QWheelEvent* ) override; void updateIdealSize() const; void initBrowser(int height); void update(); NavigationContextPointer m_startContext; TopDUContextPointer m_topContext; QPointer m_browser; QWidget* m_currentWidget; QString m_currentText; mutable QSize m_idealTextSize; private: NavigationContextPointer m_context; }; } #endif diff --git a/language/duchain/navigation/problemnavigationcontext.cpp b/language/duchain/navigation/problemnavigationcontext.cpp index 8f0d7c8d68..99e2fd05bd 100644 --- a/language/duchain/navigation/problemnavigationcontext.cpp +++ b/language/duchain/navigation/problemnavigationcontext.cpp @@ -1,119 +1,119 @@ /* Copyright 2009 David Nolden - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemnavigationcontext.h" -#include +#include +#include + +#include + #include #include #include #include -#include -#include #include -#include -#include #include using namespace KDevelop; ProblemNavigationContext::ProblemNavigationContext(ProblemPointer problem): m_problem(problem) { m_widget = 0; QExplicitlySharedDataPointer< IAssistant > solution = problem->solutionAssistant(); if(solution && !solution->actions().isEmpty()) { m_widget = new QWidget; QHBoxLayout* layout = new QHBoxLayout(m_widget); RichTextPushButton* button = new RichTextPushButton; // button->setPopupMode(QToolButton::InstantPopup); if(!solution->title().isEmpty()) button->setHtml(i18n("Solve: %1", solution->title())); else button->setHtml(i18n("Solve")); QMenu* menu = new QMenu; menu->setFocusPolicy(Qt::NoFocus); foreach(IAssistantAction::Ptr action, solution->actions()) { menu->addAction(action->toKAction()); } button->setMenu(menu); layout->addWidget(button); layout->setAlignment(button, Qt::AlignLeft); m_widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); } } ProblemNavigationContext::~ProblemNavigationContext() { delete m_widget; } QWidget* ProblemNavigationContext::widget() const { return m_widget; } bool ProblemNavigationContext::isWidgetMaximized() const { return false; } QString ProblemNavigationContext::name() const { return i18n("Problem"); } QString ProblemNavigationContext::html(bool shorten) { clear(); m_shorten = shorten; modifyHtml() += "

"; modifyHtml() += i18n("Problem in %1:
", m_problem->sourceString()); modifyHtml() += m_problem->description().toHtmlEscaped(); modifyHtml() += "
"; modifyHtml() += "" + m_problem->explanation().toHtmlEscaped() + ""; const QList diagnostics = m_problem->diagnostics(); if (!diagnostics.isEmpty()) { modifyHtml() += "
"; DUChainReadLocker lock; for (auto diagnostic : diagnostics) { const DocumentRange range = diagnostic->finalLocation(); Declaration* declaration = DUChainUtils::itemUnderCursor(range.document.toUrl(), range.start()); modifyHtml() += labelHighlight(QStringLiteral("%1: ").arg(diagnostic->severityString())); modifyHtml() += diagnostic->description(); if (declaration) { modifyHtml() += "
"; makeLink(declaration->toString(), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); modifyHtml() += i18n(" in "); makeLink(QStringLiteral("%1 :%2").arg(declaration->url().toUrl().fileName()).arg(declaration->rangeInCurrentRevision().start().line()+1), KDevelop::DeclarationPointer(declaration), NavigationAction::NavigateDeclaration); } modifyHtml() += "
"; } } modifyHtml() += "

"; return currentHtml(); } diff --git a/language/duchain/navigation/useswidget.cpp b/language/duchain/navigation/useswidget.cpp index a13e3ef972..930a9137d4 100644 --- a/language/duchain/navigation/useswidget.cpp +++ b/language/duchain/navigation/useswidget.cpp @@ -1,652 +1,654 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "useswidget.h" #include "util/debug.h" -#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 using namespace KDevelop; const int tooltipContextSize = 2; //How many lines around the use are shown in the tooltip ///The returned text is fully escaped ///@param cutOff The total count of characters that should be cut of, all in all on both sides together. ///@param range The range that is highlighted, and that will be preserved during cutting, given that there is enough room beside it. QString highlightAndEscapeUseText(QString line, int cutOff, KTextEditor::Range range) { int leftCutRoom = range.start().column(); int rightCutRoom = line.length() - range.end().column(); if(range.start().column() < 0 || range.end().column() > line.length() || cutOff > leftCutRoom + rightCutRoom) return QString(); //Not enough room for cutting off on sides int leftCut = 0; int rightCut = 0; if(leftCutRoom < rightCutRoom) { if(leftCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. leftCut = cutOff / 2; rightCut = cutOff - leftCut; }else{ //Not enough room in left side, but enough room all together leftCut = leftCutRoom; rightCut = cutOff - leftCut; } }else{ if(rightCutRoom * 2 >= cutOff) { //Enough room on both sides. Just cut. rightCut = cutOff / 2; leftCut = cutOff - rightCut; }else{ //Not enough room in right side, but enough room all together rightCut = rightCutRoom; leftCut = cutOff - rightCut; } } Q_ASSERT(leftCut + rightCut <= cutOff); line = line.left(line.length() - rightCut); line = line.mid(leftCut); range += KTextEditor::Range(0, -leftCut, 0, -leftCut); Q_ASSERT(range.start().column() >= 0 && range.end().column() <= line.length()); //TODO: share code with context browser // mixing (255, 255, 0, 100) with white yields this: const QColor background(251, 250, 150); const QColor foreground(0, 0, 0); return "" + line.left(range.start().column()).toHtmlEscaped() + "" + line.mid(range.start().column(), range.end().column() - range.start().column()).toHtmlEscaped() + "" + line.mid(range.end().column(), line.length() - range.end().column()).toHtmlEscaped() + ""; } OneUseWidget::OneUseWidget(IndexedDeclaration declaration, IndexedString document, KTextEditor::Range range, const CodeRepresentation& code) : m_range(new PersistentMovingRange(range, document)), m_declaration(declaration), m_document(document) { //Make the sizing of this widget independent of the content, because we will adapt the content to the size setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); m_sourceLine = code.line(m_range->range().start().line()); m_layout = new QHBoxLayout(this); setLayout(m_layout); m_label = new QLabel(this); m_icon = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme("code-function").pixmap(16)); connect(m_label, &QLabel::linkActivated, this, &OneUseWidget::jumpTo); DUChainReadLocker lock(DUChain::lock()); QString text = "" + i18nc("refers to a line in source code", "Line %1:", range.start().line()) + QStringLiteral(""); if(!m_sourceLine.isEmpty() && m_sourceLine.length() > m_range->range().end().column()) { text += "  " + highlightAndEscapeUseText(m_sourceLine, 0, m_range->range()); //Useful tooltip: int start = m_range->range().start().line() - tooltipContextSize; int end = m_range->range().end().line() + tooltipContextSize + 1; QString toolTipText; for(int a = start; a < end; ++a) { QString lineText = code.line(a).toHtmlEscaped(); if (m_range->range().start().line() <= a && m_range->range().end().line() >= a) { lineText = QStringLiteral("") + lineText + QStringLiteral(""); } if(!lineText.trimmed().isEmpty()) { toolTipText += lineText + "
"; } } if ( toolTipText.endsWith("
") ) { toolTipText.remove(toolTipText.length() - 4, 4); } setToolTip(QStringLiteral("
") + toolTipText + QStringLiteral("
")); } m_label->setText(text); m_layout->addWidget(m_icon); m_layout->addWidget(m_label); m_layout->setAlignment(Qt::AlignLeft); } void OneUseWidget::jumpTo() { //This is used to execute the slot delayed in the event-loop, so crashes are avoided ICore::self()->documentController()->openDocument(m_document.toUrl(), m_range->range().start()); } OneUseWidget::~OneUseWidget() { } void OneUseWidget::resizeEvent ( QResizeEvent * event ) { ///Adapt the content QSize size = event->size(); KTextEditor::Range range = m_range->range(); int cutOff = 0; int maxCutOff = m_sourceLine.length() - (range.end().column() - range.start().column()); //Reset so we also get more context while up-sizing m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); while(sizeHint().width() > size.width() && cutOff < maxCutOff) { //We've got to save space m_label->setText(QStringLiteral("") + i18nc("Refers to a line in source code", "Line %1", range.start().line()+1) + QStringLiteral(" ") + highlightAndEscapeUseText(m_sourceLine, cutOff, range)); cutOff += 5; } event->accept(); QWidget::resizeEvent(event); } void NavigatableWidgetList::setShowHeader(bool show) { if(show && !m_headerLayout->parent()) m_layout->insertLayout(0, m_headerLayout); else m_headerLayout->setParent(0); } NavigatableWidgetList::~NavigatableWidgetList() { delete m_headerLayout; } NavigatableWidgetList::NavigatableWidgetList(bool allowScrolling, uint maxHeight, bool vertical) : m_allowScrolling(allowScrolling) { m_layout = new QVBoxLayout; m_layout->setMargin(0); m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); m_layout->setSpacing(0); setBackgroundRole(QPalette::Base); m_useArrows = false; if(vertical) m_itemLayout = new QVBoxLayout; else m_itemLayout = new QHBoxLayout; m_itemLayout->setMargin(0); m_itemLayout->setSpacing(0); // m_layout->setSizeConstraint(QLayout::SetMinAndMaxSize); // setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Maximum); setWidgetResizable(true); m_headerLayout = new QHBoxLayout; m_headerLayout->setMargin(0); m_headerLayout->setSpacing(0); if(m_useArrows) { m_previousButton = new QToolButton(); m_previousButton->setIcon(QIcon::fromTheme("go-previous")); m_nextButton = new QToolButton(); m_nextButton->setIcon(QIcon::fromTheme("go-next")); m_headerLayout->addWidget(m_previousButton); m_headerLayout->addWidget(m_nextButton); } //hide these buttons for now, they're senseless m_layout->addLayout(m_headerLayout); QHBoxLayout* spaceLayout = new QHBoxLayout; spaceLayout->addSpacing(10); spaceLayout->addLayout(m_itemLayout); m_layout->addLayout(spaceLayout); if(maxHeight) setMaximumHeight(maxHeight); if(m_allowScrolling) { QWidget* contentsWidget = new QWidget; contentsWidget->setLayout(m_layout); setWidget(contentsWidget); }else{ setLayout(m_layout); } } void NavigatableWidgetList::deleteItems() { foreach(QWidget* item, items()) delete item; } void NavigatableWidgetList::addItem(QWidget* widget, int pos) { if(pos == -1) m_itemLayout->addWidget(widget); else m_itemLayout->insertWidget(pos, widget); } QList NavigatableWidgetList::items() const { QList ret; for(int a = 0; a < m_itemLayout->count(); ++a) { QWidgetItem* widgetItem = dynamic_cast(m_itemLayout->itemAt(a)); if(widgetItem) { ret << widgetItem->widget(); } } return ret; } bool NavigatableWidgetList::hasItems() const { return (bool)m_itemLayout->count(); } void NavigatableWidgetList::addHeaderItem(QWidget* widget, Qt::Alignment alignment) { if(m_useArrows) { Q_ASSERT(m_headerLayout->count() >= 2); //At least the 2 back/next buttons m_headerLayout->insertWidget(m_headerLayout->count()-1, widget, alignment); }else{ //We need to do this so the header doesn't get stretched widget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); m_headerLayout->insertWidget(m_headerLayout->count(), widget, alignment); // widget->setMaximumHeight(20); } } ///Returns whether the uses in the child should be a new uses-group bool isNewGroup(DUContext* parent, DUContext* child) { if(parent->type() == DUContext::Other && child->type() == DUContext::Other) return false; else return true; } uint countUses(int usedDeclarationIndex, DUContext* context) { uint ret = 0; for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ++ret; foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += countUses(usedDeclarationIndex, child); return ret; } QList createUseWidgets(const CodeRepresentation& code, int usedDeclarationIndex, IndexedDeclaration decl, DUContext* context) { QList ret; VERIFY_FOREGROUND_LOCKED for(int useIndex = 0; useIndex < context->usesCount(); ++useIndex) if(context->uses()[useIndex].m_declarationIndex == usedDeclarationIndex) ret << new OneUseWidget(decl, context->url(), context->transformFromLocalRevision(context->uses()[useIndex].m_range), code); foreach(DUContext* child, context->childContexts()) if(!isNewGroup(context, child)) ret += createUseWidgets(code, usedDeclarationIndex, decl, child); return ret; } ContextUsesWidget::ContextUsesWidget(const CodeRepresentation& code, QList usedDeclarations, IndexedDUContext context) : m_context(context) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); QString headerText = i18n("Unknown context"); setUpdatesEnabled(false); if(context.data()) { DUContext* ctx = context.data(); if(ctx->scopeIdentifier(true).isEmpty()) headerText = i18n("Global"); else { headerText = ctx->scopeIdentifier(true).toString(); if(ctx->type() == DUContext::Function || (ctx->owner() && ctx->owner()->isFunctionDeclaration())) headerText += "(...)"; } QSet hadIndices; foreach(const IndexedDeclaration &usedDeclaration, usedDeclarations) { int usedDeclarationIndex = ctx->topContext()->indexForUsedDeclaration(usedDeclaration.data(), false); if(hadIndices.contains(usedDeclarationIndex)) continue; hadIndices.insert(usedDeclarationIndex); if(usedDeclarationIndex != std::numeric_limits::max()) { foreach(OneUseWidget* widget, createUseWidgets(code, usedDeclarationIndex, usedDeclaration, ctx)) addItem(widget); } } } QLabel* headerLabel = new QLabel(i18nc("%1: source file", "In %1", "" + headerText.toHtmlEscaped() + ": ")); addHeaderItem(headerLabel); setUpdatesEnabled(true); connect(headerLabel, &QLabel::linkActivated, this, &ContextUsesWidget::linkWasActivated); } void ContextUsesWidget::linkWasActivated(QString link) { if ( link == "navigateToFunction" ) { DUChainReadLocker lock(DUChain::lock()); DUContext* context = m_context.context(); if(context) { CursorInRevision contextStart = context->range().start; KTextEditor::Cursor cursor(contextStart.line, contextStart.column); QUrl url = context->url().toUrl(); lock.unlock(); ForegroundLock fgLock; ICore::self()->documentController()->openDocument(url, cursor); } } } DeclarationWidget::DeclarationWidget(const CodeRepresentation& code, const IndexedDeclaration& decl) { setFrameShape(NoFrame); DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); if (Declaration* dec = decl.data()) { QLabel* headerLabel = new QLabel(dec->isDefinition() ? i18n("Definition") : i18n("Declaration")); addHeaderItem(headerLabel); addItem(new OneUseWidget(decl, dec->url(), dec->rangeInCurrentRevision(), code)); } setUpdatesEnabled(true); } TopContextUsesWidget::TopContextUsesWidget(IndexedDeclaration declaration, QList allDeclarations, IndexedTopDUContext topContext) : m_topContext(topContext) , m_declaration(declaration) , m_allDeclarations(allDeclarations) , m_usesCount(0) { m_itemLayout->setContentsMargins(10, 0, 0, 5); setFrameShape(NoFrame); setUpdatesEnabled(false); DUChainReadLocker lock(DUChain::lock()); QHBoxLayout * labelLayout = new QHBoxLayout; QWidget* headerWidget = new QWidget; headerWidget->setLayout(labelLayout); headerWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); QLabel* label = new QLabel(this); m_icon = new QLabel(this); m_toggleButton = new QLabel(this); m_icon->setPixmap(QIcon::fromTheme("code-class").pixmap(16)); labelLayout->addWidget(m_icon); labelLayout->addWidget(label); labelLayout->addWidget(m_toggleButton); labelLayout->setAlignment(Qt::AlignLeft); if(topContext.isLoaded()) m_usesCount = DUChainUtils::contextCountUses(topContext.data(), declaration.data()); QString labelText = i18ncp("%1: number of uses, %2: filename with uses", "%2: 1 use", "%2: %1 uses", m_usesCount, ICore::self()->projectController()->prettyFileName(topContext.url().toUrl())); label->setText(labelText); m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); connect(m_toggleButton, &QLabel::linkActivated, this, &TopContextUsesWidget::labelClicked); addHeaderItem(headerWidget); setUpdatesEnabled(true); } int TopContextUsesWidget::usesCount() const { return m_usesCount; } QList buildContextUses(const CodeRepresentation& code, QList declarations, DUContext* context) { QList ret; if(!context->parentContext() || isNewGroup(context->parentContext(), context)) { ContextUsesWidget* created = new ContextUsesWidget(code, declarations, context); if(created->hasItems()) ret << created; else delete created; } foreach(DUContext* child, context->childContexts()) ret += buildContextUses(code, declarations, child); return ret; } void TopContextUsesWidget::setExpanded(bool expanded) { if(!expanded) { m_toggleButton->setText("   [" + i18nc("Refers to opening a UI element", "Expand") + "]"); deleteItems(); }else{ m_toggleButton->setText("   [" + i18nc("Refers to closing a UI element", "Collapse") + "]"); if(hasItems()) return; DUChainReadLocker lock(DUChain::lock()); TopDUContext* topContext = m_topContext.data(); if(topContext && m_declaration.data()) { CodeRepresentation::Ptr code = createCodeRepresentation(topContext->url()); setUpdatesEnabled(false); IndexedTopDUContext localTopContext(topContext); foreach(const IndexedDeclaration &decl, m_allDeclarations) { if(decl.indexedTopContext() == localTopContext) { addItem(new DeclarationWidget(*code, decl)); } } foreach(ContextUsesWidget* usesWidget, buildContextUses(*code, m_allDeclarations, topContext)) { addItem(usesWidget); } setUpdatesEnabled(true); } } } void TopContextUsesWidget::labelClicked() { if(hasItems()) { setExpanded(false); }else{ setExpanded(true); } } UsesWidget::~UsesWidget() { if (m_collector) { m_collector->setWidget(0); } } UsesWidget::UsesWidget(const IndexedDeclaration& declaration, QSharedPointer customCollector) : NavigatableWidgetList(true) { DUChainReadLocker lock(DUChain::lock()); setUpdatesEnabled(false); m_headerLine = new QLabel; redrawHeaderLine(); connect(m_headerLine, &QLabel::linkActivated, this, &UsesWidget::headerLinkActivated); m_layout->insertWidget(0, m_headerLine, 0, Qt::AlignTop); m_layout->setAlignment(Qt::AlignTop); m_itemLayout->setAlignment(Qt::AlignTop); m_progressBar = new QProgressBar; addHeaderItem(m_progressBar); if (!customCollector) { m_collector = QSharedPointer(new UsesWidgetCollector(declaration)); } else { m_collector = customCollector; } m_collector->setProcessDeclarations(true); m_collector->setWidget(this); m_collector->startCollecting(); setUpdatesEnabled(true); } void UsesWidget::redrawHeaderLine() { m_headerLine->setText(headerLineText()); } const QString UsesWidget::headerLineText() const { return i18np("1 use found", "%1 uses found", countAllUses()) + " • " "[" + i18n("Expand all") + "] • " "[" + i18n("Collapse all") + "]"; } unsigned int UsesWidget::countAllUses() const { unsigned int totalUses = 0; foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { totalUses += useWidget->usesCount(); } } return totalUses; } void UsesWidget::setAllExpanded(bool expanded) { foreach ( QWidget* w, items() ) { if ( TopContextUsesWidget* useWidget = dynamic_cast(w) ) { useWidget->setExpanded(expanded); } } } void UsesWidget::headerLinkActivated(QString linkName) { if(linkName == "expandAll") { setAllExpanded(true); } else if(linkName == "collapseAll") { setAllExpanded(false); } } UsesWidget::UsesWidgetCollector::UsesWidgetCollector(IndexedDeclaration decl) : UsesCollector(decl), m_widget(0) { } void UsesWidget::UsesWidgetCollector::setWidget(UsesWidget* widget ) { m_widget = widget; } void UsesWidget::UsesWidgetCollector::maximumProgress(uint max) { if (!m_widget) { return; } if(m_widget->m_progressBar) { m_widget->m_progressBar->setMaximum(max); m_widget->m_progressBar->setMinimum(0); m_widget->m_progressBar->setValue(0); }else{ qCWarning(LANGUAGE) << "maximumProgress called twice"; } } void UsesWidget::UsesWidgetCollector::progress(uint processed, uint total) { if (!m_widget) { return; } m_widget->redrawHeaderLine(); if(m_widget->m_progressBar) { m_widget->m_progressBar->setValue(processed); if(processed == total) { m_widget->setUpdatesEnabled(false); delete m_widget->m_progressBar; m_widget->m_progressBar = 0; m_widget->setShowHeader(false); m_widget->setUpdatesEnabled(true); } }else{ qCWarning(LANGUAGE) << "progress() called too often"; } } void UsesWidget::UsesWidgetCollector::processUses( KDevelop::ReferencedTopDUContext topContext ) { if (!m_widget) { return; } DUChainReadLocker lock; qCDebug(LANGUAGE) << "processing" << topContext->url().str(); TopContextUsesWidget* widget = new TopContextUsesWidget(declaration(), declarations(), topContext.data()); // move to back if it's just the declaration/definition bool toBack = widget->usesCount() == 0; // move to front the item belonging to the current open document IDocument* doc = ICore::self()->documentController()->activeDocument(); bool toFront = doc && (doc->url() == topContext->url().toUrl()); widget->setExpanded(true); m_widget->addItem(widget, toFront ? 0 : toBack ? widget->items().size() : -1); m_widget->redrawHeaderLine(); } QSize KDevelop::UsesWidget::sizeHint() const { QSize ret = QWidget::sizeHint(); if(ret.height() < 300) ret.setHeight(300); return ret; } diff --git a/language/duchain/persistentsetmap.h b/language/duchain/persistentsetmap.h index 6a19a50650..bade3f8f75 100644 --- a/language/duchain/persistentsetmap.h +++ b/language/duchain/persistentsetmap.h @@ -1,253 +1,253 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PERSISTENTSETMAP_H #define KDEVPLATFORM_PERSISTENTSETMAP_H #include "appendedlist.h" #include "../../util/embeddedfreetree.h" #include "serialization/itemrepository.h" namespace KDevelop { template struct IndexHasher { static uint hash(const T& t) { return t.index(); } }; template struct HashHasher { static uint hash(const T& t) { return t.hash(); } }; // #define DEFINE_PERSISTENT_SET_MAP(key, data, hasher, ) \ // typedef TemporaryDataManager > temporaryHash ## container ## member ## Type; \ -// K_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, temporaryHash ## container ## member ## Static, ( #container "::" #member )) \ +// Q_GLOBAL_STATIC_WITH_ARGS(temporaryHash ## container ## member ## Type, temporaryHash ## container ## member ## Static, ( #container "::" #member )) \ // temporaryHash ## container ## member ## Type& temporaryHash ## container ## member() { \ // return *temporaryHash ## container ## member ## Static; \ // } template class PersistentSetMapItem { public: static KDevelop::TemporaryDataManager >& temporaryHashPersistentSetMapItemdata() { static KDevelop::TemporaryDataManager > manager; return manager; } PersistentSetMapItem() : centralFreeItem(-1) { initializeAppendedLists(); } PersistentSetMapItem(const PersistentSetMapItem& rhs) : key(rhs.key), centralFreeItem(rhs.centralFreeItem) { initializeAppendedLists(); copyListsFrom(rhs); } ~PersistentSetMapItem() { freeAppendedLists(); } unsigned int hash() const { //We only compare the data. This allows us implementing a map, although the item-repository //originally represents a set. return Hasher::hash(key); } unsigned int itemSize() const { return dynamicSize(); } uint classSize() const { return sizeof(*this); } Key key; int centralFreeItem; START_APPENDED_LISTS(PersistentSetMapItem); APPENDED_LIST_FIRST(PersistentSetMapItem, Data, data); END_APPENDED_LISTS(PersistentSetMapItem, data); }; template class PersistentSetMapItemRequest { public: PersistentSetMapItemRequest(const PersistentSetMapItem& item) : m_item(item) { } enum { AverageSize = 30 //This should be the approximate average size of an Item }; unsigned int hash() const { return m_item.hash(); } uint itemSize() const { return m_item.itemSize(); } void createItem(PersistentSetMapItem* item) const { item->initializeAppendedLists(false); item->key = m_item.key; item->centralFreeItem = m_item.centralFreeItem; item->copyListsFrom(m_item); } bool equals(const PersistentSetMapItem* item) const { return m_item.key == item->key; } const PersistentSetMapItem& m_item; }; /** * This class allows easily implement a very efficient persistent map from a key, to a set of data items. * @param Key The key class, from which a set of Data items is mapped. It must be safely memory-copyable((no virtual functions etc), * @param Data The data class, of which a set will be stored. The data must be safely memory-copyable(no virtual functions etc), * and it must be compatbiel with EmbeddedFreeTree * @param Handler Must be a handler that allows storing additional information into invalid items, @see util/embeddedfreetree.h * @param Hasher A hasher that extracts a hash-value from the key. * */ template > class PersistentSetMap { public: PersistentSetMap(QString name) : m_repository(name) { } void addItem(const Key& key, const Data& data); void removeItem(const Key& key, const Data& data); ///The returned list may contain "invalid" items, those have to be filtered out by the user. void items(const Key& key, uint& countTarget, const Data*& datasTarget) const; private: ItemRepository, PersistentSetMapItemRequest > m_repository; }; template void PersistentSetMap::addItem(const Key& key, const Data& data) { PersistentSetMapItem item; item.key = key; PersistentSetMapItemRequest request(item); uint index = m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSetMapItem* oldItem = m_repository.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->data(), oldItem->dataSize(), oldItem->centralFreeItem); if(alg.indexOf(data) != -1) return; QMutexLocker lock(m_repository.mutex()); PersistentSetMapItem* editableItem = m_repository.dynamicItemFromIndex(index); EmbeddedTreeAddItem add(const_cast(editableItem->data()), editableItem->dataSize(), editableItem->centralFreeItem, data); uint newSize = add.newItemCount(); if(newSize != editableItem->dataSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.datasList().resize(newSize); add.transferData(item.datasList().data(), newSize, &item.centralFreeItem); m_repository.deleteItem(index); Q_ASSERT(!m_repository.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } }else{ item.datasList().append(data); } //This inserts the changed item m_repository.index(request); } template void PersistentSetMap::removeItem(const Key& key, const Data& data) { PersistentSetMapItem item; item.key = key; PersistentSetMapItemRequest request(item); uint index = m_repository.findIndex(item); if(index) { //Check whether the item is already in the mapped list, else copy the list into the new created item const PersistentSetMapItem* oldItem = m_repository.itemFromIndex(index); EmbeddedTreeAlgorithms alg(oldItem->data(), oldItem->dataSize(), oldItem->centralFreeItem); if(alg.indexOf(data) == -1) return; QMutexLocker lock(m_repository.mutex()); PersistentSetMapItem* editableItem = m_repository.dynamicItemFromIndex(index); EmbeddedTreeRemoveItem remove(const_cast(editableItem->data()), editableItem->dataSize(), editableItem->centralFreeItem, data); uint newSize = remove.newItemCount(); if(newSize != editableItem->dataSize()) { //We need to resize. Update and fill the new item, and delete the old item. item.datasList().resize(newSize); remove.transferData(item.datasList().data(), newSize, &item.centralFreeItem); m_repository.deleteItem(index); Q_ASSERT(!m_repository.findIndex(request)); }else{ //We're fine, the item could be added to the existing list return; } } //This inserts the changed item if(item.dataSize()) m_repository.index(request); } template void PersistentSetMap::items(const Key& key, uint& countTarget, const Data*& datasTarget) const { PersistentSetMapItem item; item.key = key; PersistentSetMapItemRequest request(item); uint index = m_repository.findIndex(item); if(index) { const PersistentSetMapItem* repositoryItem = m_repository.itemFromIndex(index); countTarget = repositoryItem->dataSize(); datasTarget = repositoryItem->data(); }else{ countTarget = 0; datasTarget = 0; } } } #endif diff --git a/plugins/classbrowser/classbrowserplugin.cpp b/plugins/classbrowser/classbrowserplugin.cpp index 67b0de6ed1..140ed7d9a2 100644 --- a/plugins/classbrowser/classbrowserplugin.cpp +++ b/plugins/classbrowser/classbrowserplugin.cpp @@ -1,189 +1,187 @@ /* * This file is part of KDevelop * * Copyright 2006 Adam Treat * Copyright 2006-2008 Hamish Rodda * Copyright 2009 Lior Mualem * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "classbrowserplugin.h" #include #include -#include -#include -#include +#include #include "interfaces/icore.h" #include "interfaces/iuicontroller.h" #include "interfaces/idocumentcontroller.h" #include "interfaces/contextmenuextension.h" #include "language/interfaces/codecontext.h" #include "language/duchain/duchainbase.h" #include "language/duchain/duchain.h" #include "language/duchain/duchainlock.h" #include "language/duchain/declaration.h" #include #include "debug.h" #include "classmodel.h" #include "classtree.h" #include "classwidget.h" #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CLASSBROWSER, "kdevplatform.plugins.classbrowser") K_PLUGIN_FACTORY_WITH_JSON(KDevClassBrowserFactory, "kdevclassbrowser.json", registerPlugin(); ) using namespace KDevelop; class ClassBrowserFactory: public KDevelop::IToolViewFactory { public: ClassBrowserFactory(ClassBrowserPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) override { return new ClassWidget(parent, m_plugin); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.ClassBrowserView"; } private: ClassBrowserPlugin *m_plugin; }; ClassBrowserPlugin::ClassBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin("kdevclassbrowser", parent) , m_factory(new ClassBrowserFactory(this)) , m_activeClassTree(0) { core()->uiController()->addToolView(i18n("Classes"), m_factory); setXMLFile( "kdevclassbrowser.rc" ); m_findInBrowser = new QAction(i18n("Find in &Class Browser"), this); connect(m_findInBrowser, &QAction::triggered, this, &ClassBrowserPlugin::findInClassBrowser); } ClassBrowserPlugin::~ClassBrowserPlugin() { } void ClassBrowserPlugin::unload() { core()->uiController()->removeToolView(m_factory); } KDevelop::ContextMenuExtension ClassBrowserPlugin::contextMenuExtension( KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); // No context menu if we don't have a class browser at hand. if ( m_activeClassTree == 0 ) return menuExt; KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker readLock(DUChain::lock()); Declaration* decl(codeContext->declaration().data()); if (decl) { if(decl->inSymbolTable()) { if(!ClassTree::populatingClassBrowserContextMenu() && ICore::self()->projectController()->findProjectForUrl(decl->url().toUrl()) && decl->kind() == Declaration::Type && decl->internalContext() && decl->internalContext()->type() == DUContext::Class) { //Currently "Find in Class Browser" seems to only work for classes, so only show it in that case m_findInBrowser->setData(QVariant::fromValue(DUChainBasePointer(decl))); menuExt.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, m_findInBrowser); } } } return menuExt; } void ClassBrowserPlugin::findInClassBrowser() { ICore::self()->uiController()->findToolView(i18n("Classes"), m_factory, KDevelop::IUiController::CreateAndRaise); Q_ASSERT(qobject_cast(sender())); if ( m_activeClassTree == 0 ) return; DUChainReadLocker readLock(DUChain::lock()); QAction* a = static_cast(sender()); Q_ASSERT(a->data().canConvert()); DeclarationPointer decl = qvariant_cast(a->data()).dynamicCast(); if (decl) m_activeClassTree->highlightIdentifier(decl->qualifiedIdentifier()); } void ClassBrowserPlugin::showDefinition(DeclarationPointer declaration) { DUChainReadLocker readLock(DUChain::lock()); if ( !declaration ) return; Declaration* decl = declaration.data(); // If it's a function, find the function definition to go to the actual declaration. if ( decl && decl->isFunctionDeclaration() ) { FunctionDefinition* funcDefinition = dynamic_cast(decl); if ( funcDefinition == 0 ) funcDefinition = FunctionDefinition::definition(decl); if ( funcDefinition ) decl = funcDefinition; } if (decl) { QUrl url = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); readLock.unlock(); ICore::self()->documentController()->openDocument(url, range.start()); } } #include "classbrowserplugin.moc" // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on diff --git a/plugins/contextbrowser/browsemanager.cpp b/plugins/contextbrowser/browsemanager.cpp index 94b3f1f57f..70a3125b37 100644 --- a/plugins/contextbrowser/browsemanager.cpp +++ b/plugins/contextbrowser/browsemanager.cpp @@ -1,344 +1,345 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "browsemanager.h" + +#include #include -#include -#include -#include +#include + +#include +#include #include #include #include "contextbrowserview.h" #include #include #include #include #include #include #include #include #include -#include -#include #include "contextbrowser.h" #include "debug.h" using namespace KDevelop; using namespace KTextEditor; EditorViewWatcher::EditorViewWatcher(QObject* parent) : QObject(parent) { connect(ICore::self()->documentController(), &IDocumentController::textDocumentCreated, this, &EditorViewWatcher::documentCreated); foreach(KDevelop::IDocument* document, ICore::self()->documentController()->openDocuments()) documentCreated(document); } void EditorViewWatcher::documentCreated( KDevelop::IDocument* document ) { KTextEditor::Document* textDocument = document->textDocument(); if(textDocument) { connect(textDocument, &Document::viewCreated, this, &EditorViewWatcher::viewCreated); foreach(KTextEditor::View* view, textDocument->views()) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } } } void EditorViewWatcher::addViewInternal(KTextEditor::View* view) { m_views << view; viewAdded(view); connect(view, &View::destroyed, this, &EditorViewWatcher::viewDestroyed); } void EditorViewWatcher::viewAdded(KTextEditor::View*) { } void EditorViewWatcher::viewDestroyed(QObject* view) { m_views.removeAll(static_cast(view)); } void EditorViewWatcher::viewCreated(KTextEditor::Document* /*doc*/, KTextEditor::View* view) { Q_ASSERT(view->parentWidget()); addViewInternal(view); } QList EditorViewWatcher::allViews() { return m_views; } void BrowseManager::eventuallyStartDelayedBrowsing() { if(m_browsingByKey && m_browingStartedInView) emit startDelayedBrowsing(m_browingStartedInView); } BrowseManager::BrowseManager(ContextBrowserPlugin* controller) : QObject(controller), m_plugin(controller), m_browsing(false), m_browsingByKey(0), m_watcher(this) { m_delayedBrowsingTimer = new QTimer(this); m_delayedBrowsingTimer->setSingleShot(true); connect(m_delayedBrowsingTimer, &QTimer::timeout, this, &BrowseManager::eventuallyStartDelayedBrowsing); foreach(KTextEditor::View* view, m_watcher.allViews()) viewAdded(view); } KTextEditor::View* viewFromWidget(QWidget* widget) { if(!widget) return 0; KTextEditor::View* view = qobject_cast(widget); if(view) return view; else return viewFromWidget(widget->parentWidget()); } bool BrowseManager::eventFilter(QObject * watched, QEvent * event) { QWidget* widget = qobject_cast(watched); Q_ASSERT(widget); KTextEditor::View* view = viewFromWidget(widget); if(!view) return false; QKeyEvent* keyEvent = dynamic_cast(event); const int browseKey = Qt::Key_Control; const int magicModifier = Qt::Key_Alt; //Eventually start key-browsing if(keyEvent && (keyEvent->key() == browseKey || keyEvent->key() == magicModifier) && !m_browsingByKey && keyEvent->type() == QEvent::KeyPress) { m_browsingByKey = keyEvent->key(); if(keyEvent->key() == magicModifier) { if(dynamic_cast(view) && dynamic_cast(view)->isCompletionActive()) { //Do nothing, completion is active. }else{ m_delayedBrowsingTimer->start(300); m_browingStartedInView = view; } if(magicModifier == Qt::Key_Alt) { //ugly hack: //If the magic modifier is ALT, we have to prevent it from being taken by the menu-bar to switch focus to it. //This behavior depends on the style, but if the menu-bar receives any key-press in between, it doesn't do it. //So we send a meaningless key-press here: The shift-key. QEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Shift, Qt::AltModifier); QEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, Qt::Key_Shift, Qt::AltModifier); QApplication::postEvent(masterWidget(widget), pressEvent); QApplication::postEvent(masterWidget(widget), releaseEvent); } } if(!m_browsing) m_plugin->setAllowBrowsing(true); } QFocusEvent* focusEvent = dynamic_cast(event); //Eventually stop key-browsing if((keyEvent && m_browsingByKey && keyEvent->key() == m_browsingByKey && keyEvent->type() == QEvent::KeyRelease) || (focusEvent && focusEvent->lostFocus())) { if(!m_browsing) m_plugin->setAllowBrowsing(false); m_browsingByKey = 0; emit stopDelayedBrowsing(); } QMouseEvent* mouseEvent = dynamic_cast(event); if(mouseEvent) { if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton1) { m_plugin->historyPrevious(); return true; } if (mouseEvent->type() == QEvent::MouseButtonPress && mouseEvent->button() == Qt::XButton2) { m_plugin->historyNext(); return true; } } if(!m_browsing && !m_browsingByKey) { resetChangedCursor(); return false; } if(mouseEvent) { KTextEditor::View* iface = dynamic_cast(view); if(!iface) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Update kdelibs for the browsing-mode to work"; return false; } QPoint coordinatesInView = widget->mapTo(view, mouseEvent->pos()); KTextEditor::Cursor textCursor = iface->coordinatesToCursor(coordinatesInView); if(textCursor.isValid()) { ///@todo find out why this is needed, fix the code in kate if(textCursor.column() > 0) textCursor.setColumn(textCursor.column()-1); QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QPair jumpTo; //Step 1: Look for a special language object(Macro, included header, etc.) foreach (auto language, languages) { jumpTo = language->specialLanguageObjectJumpCursor(viewUrl, KTextEditor::Cursor(textCursor)); if(jumpTo.first.isValid() && jumpTo.second.isValid()) break; //Found a special object to jump to } //Step 2: Look for a declaration/use if(!jumpTo.first.isValid() || !jumpTo.second.isValid()) { Declaration* foundDeclaration = 0; KDevelop::DUChainReadLocker lock( DUChain::lock() ); foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(textCursor)) ); if(foundDeclaration && (foundDeclaration->url().toUrl() == view->document()->url()) && foundDeclaration->range().contains( foundDeclaration->transformToLocalRevision(KTextEditor::Cursor(textCursor)))) { ///A declaration was clicked directly. Jumping to it is useless, so jump to the definition or something useful bool foundBetter = false; Declaration* definition = FunctionDefinition::definition(foundDeclaration); if(definition) { foundDeclaration = definition; foundBetter = true; } ForwardDeclaration* forward = dynamic_cast(foundDeclaration); if(forward) { TopDUContext* standardContext = DUChainUtils::standardContextForUrl(view->document()->url()); if(standardContext) { Declaration* resolved = forward->resolve(standardContext); if(resolved) { foundDeclaration = resolved; //This probably won't work foundBetter = true; } } } //This will do a search without visibility-restriction, and that search will prefer non forward declarations if(!foundBetter) { Declaration* betterDecl = foundDeclaration->id().getDeclaration(0); if(betterDecl) { foundDeclaration = betterDecl; foundBetter = true; } } } if( foundDeclaration ) { jumpTo.first = foundDeclaration->url().toUrl(); jumpTo.second = foundDeclaration->rangeInCurrentRevision().start(); } } if(jumpTo.first.isValid() && jumpTo.second.isValid()) { if(mouseEvent->button() == Qt::LeftButton) { if(mouseEvent->type() == QEvent::MouseButtonPress) { m_buttonPressPosition = textCursor; // view->setCursorPosition(textCursor); // return false; }else if(mouseEvent->type() == QEvent::MouseButtonRelease && textCursor == m_buttonPressPosition) { ICore::self()->documentController()->openDocument(jumpTo.first, jumpTo.second); // event->accept(); // return true; } }else if(mouseEvent->type() == QEvent::MouseMove) { //Make the cursor a "hand" setHandCursor(widget); return false; } } } resetChangedCursor(); } return false; } void BrowseManager::resetChangedCursor() { QMap, QCursor> cursors = m_oldCursors; m_oldCursors.clear(); for(QMap, QCursor>::iterator it = cursors.begin(); it != cursors.end(); ++it) if(it.key()) it.key()->setCursor(QCursor(Qt::IBeamCursor)); } void BrowseManager::setHandCursor(QWidget* widget) { if(m_oldCursors.contains(widget)) return; //Nothing to do m_oldCursors[widget] = widget->cursor(); widget->setCursor(QCursor(Qt::PointingHandCursor)); } void BrowseManager::applyEventFilter(QWidget* object, bool install) { if(install) object->installEventFilter(this); else object->removeEventFilter(this); foreach(QObject* child, object->children()) if(qobject_cast(child)) applyEventFilter(qobject_cast(child), install); } void BrowseManager::viewAdded(KTextEditor::View* view) { applyEventFilter(view, true); //We need to listen for cursorPositionChanged, to clear the shift-detector. The problem: Kate listens for the arrow-keys using shortcuts, //so those keys are not passed to the event-filter // can't use new signal/slot syntax here, these signals are only defined in KateView // TODO: should we really depend on kate internals here? connect(view, SIGNAL(navigateLeft()), m_plugin, SLOT(navigateLeft())); connect(view, SIGNAL(navigateRight()), m_plugin, SLOT(navigateRight())); connect(view, SIGNAL(navigateUp()), m_plugin, SLOT(navigateUp())); connect(view, SIGNAL(navigateDown()), m_plugin, SLOT(navigateDown())); connect(view, SIGNAL(navigateAccept()), m_plugin, SLOT(navigateAccept())); connect(view, SIGNAL(navigateBack()), m_plugin, SLOT(navigateBack())); } void BrowseManager::Watcher::viewAdded(KTextEditor::View* view) { m_manager->viewAdded(view); } void BrowseManager::setBrowsing(bool enabled) { if(m_browsingByKey) return; if(enabled == m_browsing) return; m_browsing = enabled; //This collects all the views if(enabled) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Enabled browsing-mode"; }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "Disabled browsing-mode"; resetChangedCursor(); } } BrowseManager::Watcher::Watcher(BrowseManager* manager) : EditorViewWatcher(manager), m_manager(manager) { foreach(KTextEditor::View* view, allViews()) m_manager->applyEventFilter(view, true); } diff --git a/plugins/contextbrowser/browsemanager.h b/plugins/contextbrowser/browsemanager.h index 3957080d98..51166cd03c 100644 --- a/plugins/contextbrowser/browsemanager.h +++ b/plugins/contextbrowser/browsemanager.h @@ -1,109 +1,109 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H #define KDEVPLATFORM_PLUGIN_BROWSEMANAGER_H -#include #include +#include #include +#include #include -#include -#include +#include class QWidget; namespace KTextEditor { class View; class Document; } namespace KDevelop { class IDocument; } class EditorViewWatcher : public QObject { Q_OBJECT public: ///@param sameWindow If this is true, only views that are child of the same window as the given widget are registered EditorViewWatcher(QObject* parent = 0); QList allViews(); private: ///Called for every added view. Reimplement this to catch them. virtual void viewAdded(KTextEditor::View*); - + private slots: void viewDestroyed(QObject* view); void viewCreated(KTextEditor::Document*, KTextEditor::View*); void documentCreated( KDevelop::IDocument* document ); private: void addViewInternal(KTextEditor::View* view); QList m_views; }; class ContextBrowserPlugin; /** * Integrates the context-browser with the editor views, by listening for navigation events, and implementing html-like source browsing */ class BrowseManager : public QObject { Q_OBJECT public: BrowseManager(ContextBrowserPlugin* controller); Q_SIGNALS: //Emitted when browsing was started using the magic-modifier void startDelayedBrowsing(KTextEditor::View* view); void stopDelayedBrowsing(); public slots: ///Enabled/disables the browsing mode void setBrowsing(bool); private slots: void eventuallyStartDelayedBrowsing(); private: void viewAdded(KTextEditor::View* view); class Watcher : public EditorViewWatcher { public: Watcher(BrowseManager* manager); virtual void viewAdded(KTextEditor::View*) override; private: BrowseManager* m_manager; }; - + void resetChangedCursor(); void setHandCursor(QWidget* widget); - + //Installs/uninstalls the event-filter void applyEventFilter(QWidget* object, bool install); virtual bool eventFilter(QObject * watched, QEvent * event) override ; ContextBrowserPlugin* m_plugin; bool m_browsing; int m_browsingByKey; //Whether the browsing was started because of a key Watcher m_watcher; //Maps widgets to their previously set cursors QMap, QCursor> m_oldCursors; QTimer* m_delayedBrowsingTimer; QPointer m_browingStartedInView; KTextEditor::Cursor m_buttonPressPosition; }; #endif diff --git a/plugins/contextbrowser/contextbrowser.cpp b/plugins/contextbrowser/contextbrowser.cpp index a223a966ff..8be67e9d0d 100644 --- a/plugins/contextbrowser/contextbrowser.cpp +++ b/plugins/contextbrowser/contextbrowser.cpp @@ -1,1381 +1,1379 @@ /* * This file is part of KDevelop * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowser.h" #include "contextbrowserview.h" #include "browsemanager.h" #include "debug.h" #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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_CONTEXTBROWSER, "kdevplatform.plugins.contextbrowser") using KTextEditor::Attribute; using KTextEditor::View; // Helper that follows the QObject::parent() chain, and returns the highest widget that has no parent. QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } namespace { const unsigned int highlightingTimeout = 150; const float highlightingZDepth = -5000; const int maxHistoryLength = 30; // Helper that determines the context to use for highlighting at a specific position DUContext* contextForHighlightingAt(const KTextEditor::Cursor& position, TopDUContext* topContext) { DUContext* ctx = topContext->findContextAt(topContext->transformToLocalRevision(position)); while(ctx && ctx->parentContext() && (ctx->type() == DUContext::Template || ctx->type() == DUContext::Helper || ctx->localScopeIdentifier().isEmpty())) { ctx = ctx->parentContext(); } return ctx; } ///Duchain must be locked DUContext* getContextAt(const QUrl& url, KTextEditor::Cursor cursor) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (!topContext) return 0; return contextForHighlightingAt(KTextEditor::Cursor(cursor), topContext); } DeclarationPointer cursorDeclaration() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if (!view) { return DeclarationPointer(); } DUChainReadLocker lock; Declaration *decl = DUChainUtils::declarationForDefinition(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition()))); return DeclarationPointer(decl); } } class ContextBrowserViewFactory: public KDevelop::IToolViewFactory { public: ContextBrowserViewFactory(ContextBrowserPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) override { ContextBrowserView* ret = new ContextBrowserView(m_plugin, parent); return ret; } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.ContextBrowser"; } private: ContextBrowserPlugin *m_plugin; }; KXMLGUIClient* ContextBrowserPlugin::createGUIForMainWindow( Sublime::MainWindow* window ) { KXMLGUIClient* ret = KDevelop::IPlugin::createGUIForMainWindow( window ); m_browseManager = new BrowseManager(this); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); m_previousButton = new QToolButton(); m_previousButton->setToolTip(i18n("Go back in context history")); m_previousButton->setPopupMode(QToolButton::MenuButtonPopup); m_previousButton->setIcon(QIcon::fromTheme("go-previous")); m_previousButton->setEnabled(false); m_previousButton->setFocusPolicy(Qt::NoFocus); m_previousMenu = new QMenu(); m_previousButton->setMenu(m_previousMenu); connect(m_previousButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyPrevious); connect(m_previousMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::previousMenuAboutToShow); m_nextButton = new QToolButton(); m_nextButton->setToolTip(i18n("Go forward in context history")); m_nextButton->setPopupMode(QToolButton::MenuButtonPopup); m_nextButton->setIcon(QIcon::fromTheme("go-next")); m_nextButton->setEnabled(false); m_nextButton->setFocusPolicy(Qt::NoFocus); m_nextMenu = new QMenu(); m_nextButton->setMenu(m_nextMenu); connect(m_nextButton.data(), &QToolButton::clicked, this, &ContextBrowserPlugin::historyNext); connect(m_nextMenu.data(), &QMenu::aboutToShow, this, &ContextBrowserPlugin::nextMenuAboutToShow); m_browseButton = new QToolButton(); m_browseButton->setIcon(QIcon::fromTheme("games-hint")); m_browseButton->setToolTip(i18n("Enable/disable source browse mode")); m_browseButton->setWhatsThis(i18n("When this is enabled, you can browse the source-code by clicking in the editor.")); m_browseButton->setCheckable(true); m_browseButton->setFocusPolicy(Qt::NoFocus); connect(m_browseButton.data(), &QToolButton::clicked, m_browseManager, &BrowseManager::setBrowsing); IQuickOpen* quickOpen = KDevelop::ICore::self()->pluginController()->extensionForPlugin("org.kdevelop.IQuickOpen"); if(quickOpen) { m_outlineLine = quickOpen->createQuickOpenLine(QStringList(), QStringList() << i18n("Outline"), IQuickOpen::Outline); m_outlineLine->setDefaultText(i18n("Outline...")); m_outlineLine->setToolTip(i18n("Navigate outline of active document, click to browse.")); } connect(m_browseManager, &BrowseManager::startDelayedBrowsing, this, &ContextBrowserPlugin::startDelayedBrowsing); connect(m_browseManager, &BrowseManager::stopDelayedBrowsing, this, &ContextBrowserPlugin::stopDelayedBrowsing); m_toolbarWidget = toolbarWidgetForMainWindow(window); m_toolbarWidgetLayout = new QHBoxLayout; m_toolbarWidgetLayout->setSizeConstraint(QLayout::SetMaximumSize); m_previousButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_nextButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_browseButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); m_toolbarWidgetLayout->setMargin(0); m_toolbarWidgetLayout->addWidget(m_previousButton); if (m_outlineLine) { m_toolbarWidgetLayout->addWidget(m_outlineLine); m_outlineLine->setMaximumWidth(600); connect(ICore::self()->documentController(), &IDocumentController::documentClosed, m_outlineLine.data(), &IQuickOpenLine::clear); } m_toolbarWidgetLayout->addWidget(m_nextButton); m_toolbarWidgetLayout->addWidget(m_browseButton); if(m_toolbarWidget->children().isEmpty()) m_toolbarWidget->setLayout(m_toolbarWidgetLayout); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ContextBrowserPlugin::documentActivated); return ret; } void ContextBrowserPlugin::createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) { xmlFile = "kdevcontextbrowser.rc" ; QAction* previousContext = actions.addAction("previous_context"); previousContext->setText( i18n("&Previous Visited Context") ); previousContext->setIcon( QIcon::fromTheme("go-previous-context" ) ); actions.setDefaultShortcut( previousContext, Qt::META | Qt::Key_Left ); QObject::connect(previousContext, &QAction::triggered, this, &ContextBrowserPlugin::previousContextShortcut); QAction* nextContext = actions.addAction("next_context"); nextContext->setText( i18n("&Next Visited Context") ); nextContext->setIcon( QIcon::fromTheme("go-next-context" ) ); actions.setDefaultShortcut( nextContext, Qt::META | Qt::Key_Right ); QObject::connect(nextContext, &QAction::triggered, this, &ContextBrowserPlugin::nextContextShortcut); QAction* previousUse = actions.addAction("previous_use"); previousUse->setText( i18n("&Previous Use") ); previousUse->setIcon( QIcon::fromTheme("go-previous-use") ); actions.setDefaultShortcut( previousUse, Qt::META | Qt::SHIFT | Qt::Key_Left ); QObject::connect(previousUse, &QAction::triggered, this, &ContextBrowserPlugin::previousUseShortcut); QAction* nextUse = actions.addAction("next_use"); nextUse->setText( i18n("&Next Use") ); nextUse->setIcon( QIcon::fromTheme("go-next-use") ); actions.setDefaultShortcut( nextUse, Qt::META | Qt::SHIFT | Qt::Key_Right ); QObject::connect(nextUse, &QAction::triggered, this, &ContextBrowserPlugin::nextUseShortcut); QWidgetAction* outline = new QWidgetAction(this); outline->setText(i18n("Context Browser")); QWidget* w = toolbarWidgetForMainWindow(window); w->setHidden(false); outline->setDefaultWidget(w); actions.addAction("outline_line", outline); // Add to the actioncollection so one can set global shortcuts for the action actions.addAction("find_uses", m_findUses); } void ContextBrowserPlugin::nextContextShortcut() { // TODO: cleanup historyNext(); } void ContextBrowserPlugin::previousContextShortcut() { // TODO: cleanup historyPrevious(); } K_PLUGIN_FACTORY_WITH_JSON(ContextBrowserFactory, "kdevcontextbrowser.json", registerPlugin();) ContextBrowserPlugin::ContextBrowserPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin("kdevcontextbrowser", parent) , m_viewFactory(new ContextBrowserViewFactory(this)) , m_nextHistoryIndex(0) { KDEV_USE_EXTENSION_INTERFACE( IContextBrowser ) core()->uiController()->addToolView(i18n("Code Browser"), m_viewFactory); connect( core()->documentController(), &IDocumentController::textDocumentCreated, this, &ContextBrowserPlugin::textDocumentCreated ); connect( core()->languageController()->backgroundParser(), &BackgroundParser::parseJobFinished, this, &ContextBrowserPlugin::parseJobFinished); connect( DUChain::self(), &DUChain::declarationSelected, this, &ContextBrowserPlugin::declarationSelectedInUI ); m_updateTimer = new QTimer(this); m_updateTimer->setSingleShot(true); connect( m_updateTimer, &QTimer::timeout, this, &ContextBrowserPlugin::updateViews ); //Needed global action for the context-menu extensions m_findUses = new QAction(i18n("Find Uses"), this); connect(m_findUses, &QAction::triggered, this, &ContextBrowserPlugin::findUses); } ContextBrowserPlugin::~ContextBrowserPlugin() { ///TODO: QObject inheritance should suffice? delete m_nextMenu; delete m_previousMenu; delete m_toolbarWidgetLayout; delete m_previousButton; delete m_outlineLine; delete m_nextButton; delete m_browseButton; } void ContextBrowserPlugin::unload() { core()->uiController()->removeToolView(m_viewFactory); } KDevelop::ContextMenuExtension ContextBrowserPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension menuExt = KDevelop::IPlugin::contextMenuExtension( context ); KDevelop::DeclarationContext *codeContext = dynamic_cast(context); if (!codeContext) return menuExt; DUChainReadLocker lock(DUChain::lock()); if(!codeContext->declaration().data()) return menuExt; qRegisterMetaType("KDevelop::IndexedDeclaration"); menuExt.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_findUses); return menuExt; } void ContextBrowserPlugin::showUses(const DeclarationPointer& declaration) { QMetaObject::invokeMethod(this, "showUsesDelayed", Qt::QueuedConnection, Q_ARG(KDevelop::DeclarationPointer, declaration)); } void ContextBrowserPlugin::showUsesDelayed(const DeclarationPointer& declaration) { DUChainReadLocker lock; Declaration* decl = declaration.data(); if(!decl) { return; } QWidget* toolView = ICore::self()->uiController()->findToolView(i18n("Code Browser"), m_viewFactory, KDevelop::IUiController::CreateAndRaise); if(!toolView) { return; } ContextBrowserView* view = dynamic_cast(toolView); Q_ASSERT(view); view->allowLockedUpdate(); view->setDeclaration(decl, decl->topContext(), true); //We may get deleted while the call to acceptLink, so make sure we don't crash in that case QPointer widget = dynamic_cast(view->navigationWidget()); if(widget && widget->context()) { NavigationContextPointer nextContext = widget->context()->execute( NavigationAction(declaration, KDevelop::NavigationAction::ShowUses)); if(widget) { widget->setContext( nextContext ); } } } void ContextBrowserPlugin::findUses() { showUses(cursorDeclaration()); } ContextBrowserHintProvider::ContextBrowserHintProvider(ContextBrowserPlugin* plugin) : m_plugin(plugin) { } QString ContextBrowserHintProvider::textHint(View* view, const KTextEditor::Cursor& cursor) { m_plugin->m_mouseHoverCursor = KTextEditor::Cursor(cursor); if(!view) { qWarning() << "could not cast to view"; }else{ m_plugin->m_mouseHoverDocument = view->document()->url(); m_plugin->m_updateViews << view; } m_plugin->m_updateTimer->start(1); // triggers updateViews() m_plugin->showToolTip(view, cursor); return QString(); } void ContextBrowserPlugin::stopDelayedBrowsing() { hideToolTip(); } void ContextBrowserPlugin::startDelayedBrowsing(KTextEditor::View* view) { if(!m_currentToolTip) { showToolTip(view, view->cursorPosition()); } } void ContextBrowserPlugin::hideToolTip() { if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } } // TODO: this is a hack, but Kate does not provide interface for this static int getLineHeight(KTextEditor::View* view, int curLine) { KTextEditor::Cursor c(curLine, 0); int currentHeight = view->cursorToCoordinate(c).y(); c.setLine(curLine + 1); if (view->cursorToCoordinate(c).y() < 0) { c.setLine(curLine - 1); } return std::abs(view->cursorToCoordinate(c).y() - currentHeight); } static QRect getItemBoundingRect(const QUrl& viewUrl, KTextEditor::View* view, KTextEditor::Cursor itemPosition) { DUChainReadLocker lock; KTextEditor::Range itemRange = DUChainUtils::itemRangeUnderCursor(viewUrl, KTextEditor::Cursor(itemPosition)); QPoint startPoint = view->mapToGlobal(view->cursorToCoordinate(itemRange.start())); QPoint endPoint = view->mapToGlobal(view->cursorToCoordinate(itemRange.end())); endPoint.ry() += getLineHeight(view, itemPosition.line()); return QRect(startPoint, endPoint); } void ContextBrowserPlugin::showToolTip(KTextEditor::View* view, KTextEditor::Cursor position) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView && contextView->isVisible() && !contextView->isLocked()) return; // If the context-browser view is visible, it will care about updating by itself QUrl viewUrl = view->document()->url(); auto languages = ICore::self()->languageController()->languagesForUrl(viewUrl); QWidget* navigationWidget = 0; { DUChainReadLocker lock(DUChain::lock()); foreach (auto language, languages) { auto widget = language->specialLanguageObjectNavigationWidget(viewUrl, KTextEditor::Cursor(position)); navigationWidget = qobject_cast(widget); if(navigationWidget) break; } if(!navigationWidget) { Declaration* decl = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(viewUrl, KTextEditor::Cursor(position)) ); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { if(m_currentToolTipDeclaration == IndexedDeclaration(decl) && m_currentToolTip) return; m_currentToolTipDeclaration = IndexedDeclaration(decl); navigationWidget = decl->context()->createNavigationWidget(decl, DUChainUtils::standardContextForUrl(viewUrl)); } } } if(navigationWidget) { // If we have an invisible context-view, assign the tooltip navigation-widget to it. // If the user makes the context-view visible, it will instantly contain the correct widget. if(contextView && !contextView->isLocked()) contextView->setNavigationWidget(navigationWidget); if(m_currentToolTip) { m_currentToolTip->deleteLater(); m_currentToolTip = 0; m_currentNavigationWidget = 0; } KDevelop::NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(view, view->mapToGlobal(view->cursorToCoordinate(position)) + QPoint(20, 40), navigationWidget); tooltip->addExtendRect(getItemBoundingRect(viewUrl, view, position)); tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); qCDebug(PLUGIN_CONTEXTBROWSER) << "tooltip size" << tooltip->size(); m_currentToolTip = tooltip; m_currentNavigationWidget = navigationWidget; ActiveToolTip::showToolTip(tooltip); if ( ! navigationWidget->property("DoNotCloseOnCursorMove").toBool() ) { connect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip, Qt::UniqueConnection); } else { disconnect(view, &View::cursorPositionChanged, this, &ContextBrowserPlugin::hideToolTip); } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not showing tooltip, no navigation-widget"; } } void ContextBrowserPlugin::clearMouseHover() { m_mouseHoverCursor = KTextEditor::Cursor::invalid(); m_mouseHoverDocument.clear(); } Attribute::Ptr highlightedUseAttribute() { static Attribute::Ptr standardAttribute = Attribute::Ptr(); if( !standardAttribute ) { standardAttribute= Attribute::Ptr( new Attribute() ); standardAttribute->setBackgroundFillWhitespace(true); // mixing (255, 255, 0, 100) with white yields this: standardAttribute->setBackground(QColor(251, 250, 150)); // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar // foreground color could change, hence apply it everytime standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color } return standardAttribute; } Attribute::Ptr highlightedSpecialObjectAttribute() { static Attribute::Ptr standardAttribute = Attribute::Ptr(); if( !standardAttribute ) { standardAttribute = Attribute::Ptr( new Attribute() ); standardAttribute->setBackgroundFillWhitespace(true); // mixing (90, 255, 0, 100) with white yields this: standardAttribute->setBackground(QColor(190, 255, 155)); // force a foreground color to overwrite default Kate highlighting, i.e. of Q_OBJECT or similar // foreground color could change, hence apply it everytime standardAttribute->setForeground(QColor(0, 0, 0, 255)); //Don't use alpha here, as kate uses the alpha only to blend with the document background color } return standardAttribute; } void ContextBrowserPlugin::addHighlight( View* view, KDevelop::Declaration* decl ) { if( !view || !decl ) { qCDebug(PLUGIN_CONTEXTBROWSER) << "invalid view/declaration"; return; } ViewHighlights& highlights(m_highlightedRanges[view]); KDevelop::DUChainReadLocker lock; // Highlight the declaration highlights.highlights << decl->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute()); highlights.highlights.back()->setZDepth(highlightingZDepth); // Highlight uses { QMap< IndexedString, QList< KTextEditor::Range > > currentRevisionUses = decl->usesCurrentRevision(); for(QMap< IndexedString, QList< KTextEditor::Range > >::iterator fileIt = currentRevisionUses.begin(); fileIt != currentRevisionUses.end(); ++fileIt) { for(QList< KTextEditor::Range >::const_iterator useIt = (*fileIt).constBegin(); useIt != (*fileIt).constEnd(); ++useIt) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(*useIt, fileIt.key())); highlights.highlights.back()->setAttribute(highlightedUseAttribute()); highlights.highlights.back()->setZDepth(highlightingZDepth); } } } if( FunctionDefinition* def = FunctionDefinition::definition(decl) ) { highlights.highlights << def->createRangeMoving(); highlights.highlights.back()->setAttribute(highlightedUseAttribute()); highlights.highlights.back()->setZDepth(highlightingZDepth); } } Declaration* ContextBrowserPlugin::findDeclaration(View* view, const KTextEditor::Cursor& position, bool mouseHighlight) { Q_UNUSED(mouseHighlight); Declaration* foundDeclaration = 0; if(m_useDeclaration.data()) { foundDeclaration = m_useDeclaration.data(); }else{ //If we haven't found a special language object, search for a use/declaration and eventually highlight it foundDeclaration = DUChainUtils::declarationForDefinition( DUChainUtils::itemUnderCursor(view->document()->url(), position) ); if (foundDeclaration && foundDeclaration->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(foundDeclaration); Q_ASSERT(alias); DUChainReadLocker lock; foundDeclaration = alias->aliasedDeclaration().declaration(); } } return foundDeclaration; } ContextBrowserView* ContextBrowserPlugin::browserViewForWidget(QWidget* widget) { foreach(ContextBrowserView* contextView, m_views) { if(masterWidget(contextView) == masterWidget(widget)) { return contextView; } } return 0; } void ContextBrowserPlugin::updateForView(View* view) { bool allowHighlight = true; if(view->selection()) { // If something is selected, we unhighlight everything, so that we don't conflict with the // kate plugin that highlights occurrences of the selected string, and also to reduce the // overall amount of concurrent highlighting. allowHighlight = false; } if(m_highlightedRanges[view].keep) { m_highlightedRanges[view].keep = false; return; } // Clear all highlighting m_highlightedRanges.clear(); // Re-highlight ViewHighlights& highlights = m_highlightedRanges[view]; QUrl url = view->document()->url(); IDocument* activeDoc = core()->documentController()->activeDocument(); bool mouseHighlight = (url == m_mouseHoverDocument) && (m_mouseHoverCursor.isValid()); bool shouldUpdateBrowser = (mouseHighlight || (view == ICore::self()->documentController()->activeTextDocumentView() && activeDoc && activeDoc->textDocument() == view->document())); KTextEditor::Cursor highlightPosition; if (mouseHighlight) highlightPosition = m_mouseHoverCursor; else highlightPosition = KTextEditor::Cursor(view->cursorPosition()); ///Pick a language ILanguageSupport* language = nullptr; if(ICore::self()->languageController()->languagesForUrl(url).isEmpty()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "found no language for document" << url; return; }else{ language = ICore::self()->languageController()->languagesForUrl(url).front(); } ///Check whether there is a special language object to highlight (for example a macro) KTextEditor::Range specialRange = language->specialLanguageObjectRange(url, highlightPosition); ContextBrowserView* updateBrowserView = shouldUpdateBrowser ? browserViewForWidget(view) : 0; if(specialRange.isValid()) { // Highlight a special language object if(allowHighlight) { highlights.highlights << PersistentMovingRange::Ptr(new PersistentMovingRange(specialRange, IndexedString(url))); highlights.highlights.back()->setAttribute(highlightedSpecialObjectAttribute()); highlights.highlights.back()->setZDepth(highlightingZDepth); } if(updateBrowserView) updateBrowserView->setSpecialNavigationWidget(language->specialLanguageObjectNavigationWidget(url, highlightPosition)); }else{ KDevelop::DUChainReadLocker lock( DUChain::lock(), 100 ); if(!lock.locked()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "Failed to lock du-chain in time"; return; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(view->document()->url()); if (!topContext) return; DUContext* ctx = contextForHighlightingAt(highlightPosition, topContext); if (!ctx) return; //Only update the history if this context is around the text cursor if(core()->documentController()->activeDocument() && highlightPosition == KTextEditor::Cursor(view->cursorPosition()) && view->document() == core()->documentController()->activeDocument()->textDocument()) { updateHistory(ctx, highlightPosition); } Declaration* foundDeclaration = findDeclaration(view, highlightPosition, mouseHighlight); if( foundDeclaration ) { m_lastHighlightedDeclaration = highlights.declaration = IndexedDeclaration(foundDeclaration); if(allowHighlight) addHighlight( view, foundDeclaration ); if(updateBrowserView) updateBrowserView->setDeclaration(foundDeclaration, topContext); }else{ if(updateBrowserView) updateBrowserView->setContext(ctx); } } } void ContextBrowserPlugin::updateViews() { foreach( View* view, m_updateViews ) { updateForView(view); } m_updateViews.clear(); m_useDeclaration = IndexedDeclaration(); } void ContextBrowserPlugin::declarationSelectedInUI(const DeclarationPointer& decl) { m_useDeclaration = IndexedDeclaration(decl.data()); KTextEditor::View* view = core()->documentController()->activeTextDocumentView(); if(view) m_updateViews << view; if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); // triggers updateViews() } void ContextBrowserPlugin::parseJobFinished(KDevelop::ParseJob* job) { for(QMap< View*, ViewHighlights >::iterator it = m_highlightedRanges.begin(); it != m_highlightedRanges.end(); ++it) { if(it.key()->document()->url() == job->document().toUrl()) { if(!m_updateViews.contains(it.key())) { qCDebug(PLUGIN_CONTEXTBROWSER) << "adding view for update"; m_updateViews << it.key(); // Don't change the highlighted declaration after finished parse-jobs (*it).keep = true; } } } if(!m_updateViews.isEmpty()) m_updateTimer->start(highlightingTimeout); } void ContextBrowserPlugin::textDocumentCreated( KDevelop::IDocument* document ) { Q_ASSERT(document->textDocument()); connect( document->textDocument(), &KTextEditor::Document::viewCreated, this, &ContextBrowserPlugin::viewCreated ); foreach( View* view, document->textDocument()->views() ) viewCreated( document->textDocument(), view ); } void ContextBrowserPlugin::documentActivated( IDocument* doc ) { if (m_outlineLine) m_outlineLine->clear(); if (View* view = doc->activeTextView()) { cursorPositionChanged(view, view->cursorPosition()); } } void ContextBrowserPlugin::viewDestroyed( QObject* obj ) { m_highlightedRanges.remove(static_cast(obj)); m_updateViews.remove(static_cast(obj)); } void ContextBrowserPlugin::selectionChanged( View* view ) { clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::cursorPositionChanged( View* view, const KTextEditor::Cursor& newPosition ) { if(view->document() == m_lastInsertionDocument && newPosition == m_lastInsertionPos) { //Do not update the highlighting while typing m_lastInsertionDocument = 0; m_lastInsertionPos = KTextEditor::Cursor(); if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = true; }else{ if(m_highlightedRanges.contains(view)) m_highlightedRanges[view].keep = false; } clearMouseHover(); m_updateViews.insert(view); m_updateTimer->start(highlightingTimeout/2); // triggers updateViews() } void ContextBrowserPlugin::textInserted(KTextEditor::Document* doc, const KTextEditor::Cursor& cursor, const QString& text) { m_lastInsertionDocument = doc; m_lastInsertionPos = cursor + KTextEditor::Cursor(0, text.size()); } void ContextBrowserPlugin::viewCreated( KTextEditor::Document* , View* v ) { disconnect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); ///Just to make sure that multiple connections don't happen connect( v, &View::cursorPositionChanged, this, &ContextBrowserPlugin::cursorPositionChanged ); connect( v, &View::destroyed, this, &ContextBrowserPlugin::viewDestroyed ); disconnect( v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); connect(v->document(), &KTextEditor::Document::textInserted, this, &ContextBrowserPlugin::textInserted); disconnect(v, &View::selectionChanged, this, &ContextBrowserPlugin::selectionChanged); KTextEditor::TextHintInterface *iface = dynamic_cast(v); if( !iface ) return; iface->setTextHintDelay(highlightingTimeout); iface->registerTextHintProvider(new ContextBrowserHintProvider(this)); } void ContextBrowserPlugin::registerToolView(ContextBrowserView* view) { m_views << view; } void ContextBrowserPlugin::previousUseShortcut() { switchUse(false); } void ContextBrowserPlugin::nextUseShortcut() { switchUse(true); } KTextEditor::Range cursorToRange(KTextEditor::Cursor cursor) { return KTextEditor::Range(cursor, cursor); } void ContextBrowserPlugin::switchUse(bool forward) { View* view = core()->documentController()->activeTextDocumentView(); if(view) { KTextEditor::Document* doc = view->document(); KDevelop::DUChainReadLocker lock( DUChain::lock() ); KDevelop::TopDUContext* chosen = DUChainUtils::standardContextForUrl(doc->url()); if( chosen ) { KTextEditor::Cursor cCurrent(view->cursorPosition()); KDevelop::CursorInRevision c = chosen->transformToLocalRevision(cCurrent); Declaration* decl = 0; //If we have a locked declaration, use that for jumping foreach(ContextBrowserView* view, m_views) { decl = view->lockedDeclaration().data(); ///@todo Somehow match the correct context-browser view if there is multiple if(decl) break; } if(!decl) //Try finding a declaration under the cursor decl = DUChainUtils::itemUnderCursor(doc->url(), cCurrent); if (decl && decl->kind() == Declaration::Alias) { AliasDeclaration* alias = dynamic_cast(decl); Q_ASSERT(alias); DUChainReadLocker lock; decl = alias->aliasedDeclaration().declaration(); } if(decl) { Declaration* target = 0; if(forward) //Try jumping from definition to declaration target = DUChainUtils::declarationForDefinition(decl, chosen); else if(decl->url().toUrl() == doc->url() && decl->range().contains(c)) //Try jumping from declaration to definition target = FunctionDefinition::definition(decl); if(target && target != decl) { KTextEditor::Cursor jumpTo = target->rangeInCurrentRevision().start(); QUrl document = target->url().toUrl(); lock.unlock(); core()->documentController()->openDocument( document, cursorToRange(jumpTo) ); return; }else{ //Always work with the declaration instead of the definition decl = DUChainUtils::declarationForDefinition(decl, chosen); } } if(!decl) { //Pick the last use we have highlighted decl = m_lastHighlightedDeclaration.data(); } if(decl) { KDevVarLengthArray usingFiles = DUChain::uses()->uses(decl->id()); if(DUChainUtils::contextHasUse(decl->topContext(), decl) && usingFiles.indexOf(decl->topContext()) == -1) usingFiles.insert(0, decl->topContext()); if(decl->range().contains(c) && decl->url() == chosen->url()) { //The cursor is directly on the declaration. Jump to the first or last use. if(!usingFiles.isEmpty()) { TopDUContext* top = (forward ? usingFiles[0] : usingFiles.back()).data(); if(top) { QList useRanges = allUses(top, decl, true); std::sort(useRanges.begin(), useRanges.end()); if(!useRanges.isEmpty()) { QUrl url = top->url().toUrl(); KTextEditor::Range selectUse = chosen->transformFromLocalRevision(forward ? useRanges.first() : useRanges.back()); lock.unlock(); core()->documentController()->openDocument(url, cursorToRange(selectUse.start())); } } } return; } //Check whether we are within a use QList localUses = allUses(chosen, decl, true); std::sort(localUses.begin(), localUses.end()); for(int a = 0; a < localUses.size(); ++a) { int nextUse = (forward ? a+1 : a-1); bool pick = localUses[a].contains(c); if(!pick && forward && a+1 < localUses.size() && localUses[a].end <= c && localUses[a+1].start > c) { //Special case: We aren't on a use, but we are jumping forward, and are behind this and the next use pick = true; } if(!pick && !forward && a-1 >= 0 && c < localUses[a].start && c >= localUses[a-1].end) { //Special case: We aren't on a use, but we are jumping backward, and are in front of this use, but behind the previous one pick = true; } if(!pick && a == 0 && c < localUses[a].start) { if(!forward) { //Will automatically jump to previous file }else{ nextUse = 0; //We are before the first use, so jump to it. } pick = true; } if(!pick && a == localUses.size()-1 && c >= localUses[a].end) { if(forward) { //Will automatically jump to next file }else{ //We are behind the last use, but moving backward. So pick the last use. nextUse = a; } pick = true; } if(pick) { //Make sure we end up behind the use if(nextUse != a) while(forward && nextUse < localUses.size() && (localUses[nextUse].start <= localUses[a].end || localUses[nextUse].isEmpty())) ++nextUse; //Make sure we end up before the use if(nextUse != a) while(!forward && nextUse >= 0 && (localUses[nextUse].start >= localUses[a].start || localUses[nextUse].isEmpty())) --nextUse; //Jump to the next use qCDebug(PLUGIN_CONTEXTBROWSER) << "count of uses:" << localUses.size() << "nextUse" << nextUse; if(nextUse < 0 || nextUse == localUses.size()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "jumping to next file"; //Jump to the first use in the next using top-context int indexInFiles = usingFiles.indexOf(chosen); if(indexInFiles != -1) { int nextFile = (forward ? indexInFiles+1 : indexInFiles-1); qCDebug(PLUGIN_CONTEXTBROWSER) << "current file" << indexInFiles << "nextFile" << nextFile; if(nextFile < 0 || nextFile >= usingFiles.size()) { //Open the declaration, or the definition if(nextFile >= usingFiles.size()) { Declaration* definition = FunctionDefinition::definition(decl); if(definition) decl = definition; } QUrl u = decl->url().toUrl(); KTextEditor::Range range = decl->rangeInCurrentRevision(); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); return; }else{ TopDUContext* nextTop = usingFiles[nextFile].data(); QUrl u = nextTop->url().toUrl(); QList nextTopUses = allUses(nextTop, decl, true); std::sort(nextTopUses.begin(), nextTopUses.end()); if(!nextTopUses.isEmpty()) { KTextEditor::Range range = chosen->transformFromLocalRevision(forward ? nextTopUses.front() : nextTopUses.back()); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(u, range); } return; } }else{ qCDebug(PLUGIN_CONTEXTBROWSER) << "not found own file in use list"; } }else{ QUrl url = chosen->url().toUrl(); KTextEditor::Range range = chosen->transformFromLocalRevision(localUses[nextUse]); range.setEnd(range.start()); lock.unlock(); core()->documentController()->openDocument(url, range); return; } } } } } } } void ContextBrowserPlugin::unRegisterToolView(ContextBrowserView* view) { m_views.removeAll(view); } // history browsing QWidget* ContextBrowserPlugin::toolbarWidgetForMainWindow( Sublime::MainWindow* window ) { //TODO: support multiple windows (if that ever gets revived) if (!m_toolbarWidget) { m_toolbarWidget = new QWidget(window); } return m_toolbarWidget; } void ContextBrowserPlugin::documentJumpPerformed( KDevelop::IDocument* newDocument, const KTextEditor::Cursor& newCursor, KDevelop::IDocument* previousDocument, const KTextEditor::Cursor& previousCursor) { DUChainReadLocker lock(DUChain::lock()); /*TODO: support multiple windows if that ever gets revived if(newDocument && newDocument->textDocument() && newDocument->textDocument()->activeView() && masterWidget(newDocument->textDocument()->activeView()) != masterWidget(this)) return; */ if(previousDocument && previousCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump source"; DUContext* context = getContextAt(previousDocument->url(), previousCursor); if(context) { updateHistory(context, KTextEditor::Cursor(previousCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(previousDocument->url()), KTextEditor::Cursor(previousCursor)))); ++m_nextHistoryIndex; } } qCDebug(PLUGIN_CONTEXTBROWSER) << "new doc: " << newDocument << " new cursor: " << newCursor; if(newDocument && newCursor.isValid()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating jump target"; DUContext* context = getContextAt(newDocument->url(), newCursor); if(context) { updateHistory(context, KTextEditor::Cursor(newCursor), true); }else{ //We just want this place in the history m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(DocumentCursor(IndexedString(newDocument->url()), KTextEditor::Cursor(newCursor)))); ++m_nextHistoryIndex; if (m_outlineLine) m_outlineLine->clear(); } } } void ContextBrowserPlugin::updateButtonState() { m_nextButton->setEnabled( m_nextHistoryIndex < m_history.size() ); m_previousButton->setEnabled( m_nextHistoryIndex >= 2 ); } void ContextBrowserPlugin::historyNext() { if(m_nextHistoryIndex >= m_history.size()) { return; } openDocument(m_nextHistoryIndex); // opening the document at given position // will update the widget for us ++m_nextHistoryIndex; updateButtonState(); } void ContextBrowserPlugin::openDocument(int historyIndex) { Q_ASSERT_X(historyIndex >= 0, "openDocument", "negative history index"); Q_ASSERT_X(historyIndex < m_history.size(), "openDocument", "history index out of range"); DocumentCursor c = m_history[historyIndex].computePosition(); if (c.isValid() && !c.document.str().isEmpty()) { disconnect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); ICore::self()->documentController()->openDocument(c.document.toUrl(), c); connect(ICore::self()->documentController(), &IDocumentController::documentJumpPerformed, this, &ContextBrowserPlugin::documentJumpPerformed); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); updateDeclarationListBox(m_history[historyIndex].context.data()); } } void ContextBrowserPlugin::historyPrevious() { if(m_nextHistoryIndex < 2) { return; } --m_nextHistoryIndex; openDocument(m_nextHistoryIndex-1); // opening the document at given position // will update the widget for us updateButtonState(); } QString ContextBrowserPlugin::actionTextFor(int historyIndex) const { const HistoryEntry& entry = m_history.at(historyIndex); QString actionText = entry.context.data() ? entry.context.data()->scopeIdentifier(true).toString() : QString(); if(actionText.isEmpty()) actionText = entry.alternativeString; if(actionText.isEmpty()) actionText = ""; actionText += " @ "; QString fileName = entry.absoluteCursorPosition.document.toUrl().fileName(); actionText += QStringLiteral("%1:%2").arg(fileName).arg(entry.absoluteCursorPosition.line()+1); return actionText; } /* inline QDebug operator<<(QDebug debug, const ContextBrowserPlugin::HistoryEntry &he) { DocumentCursor c = he.computePosition(); debug << "\n\tHistoryEntry " << c.line << " " << c.document.str(); return debug; } */ void ContextBrowserPlugin::nextMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex; a < m_history.size(); ++a) { indices << a; } fillHistoryPopup(m_nextMenu, indices); } void ContextBrowserPlugin::previousMenuAboutToShow() { QList indices; for(int a = m_nextHistoryIndex-2; a >= 0; --a) { indices << a; } fillHistoryPopup(m_previousMenu, indices); } void ContextBrowserPlugin::fillHistoryPopup(QMenu* menu, const QList& historyIndices) { menu->clear(); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); foreach(int index, historyIndices) { QAction* action = new QAction(actionTextFor(index), menu); action->setData(index); menu->addAction(action); connect(action, &QAction::triggered, this, &ContextBrowserPlugin::actionTriggered); } } bool ContextBrowserPlugin::isPreviousEntry(KDevelop::DUContext* context, const KTextEditor::Cursor& /*position*/) const { if (m_nextHistoryIndex == 0) return false; Q_ASSERT(m_nextHistoryIndex <= m_history.count()); const HistoryEntry& he = m_history.at(m_nextHistoryIndex-1); KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); // is this necessary?? Q_ASSERT(context); return IndexedDUContext(context) == he.context; } void ContextBrowserPlugin::updateHistory(KDevelop::DUContext* context, const KTextEditor::Cursor& position, bool force) { qCDebug(PLUGIN_CONTEXTBROWSER) << "updating history"; if(m_outlineLine && m_outlineLine->isVisible()) updateDeclarationListBox(context); if(!context || (!context->owner() && !force)) { return; //Only add history-entries for contexts that have owners, which in practice should be functions and classes //This keeps the history cleaner } if (isPreviousEntry(context, position)) { if(m_nextHistoryIndex) { HistoryEntry& he = m_history[m_nextHistoryIndex-1]; he.setCursorPosition(position); } return; } else { // Append new history entry m_history.resize(m_nextHistoryIndex); // discard forward history m_history.append(HistoryEntry(IndexedDUContext(context), position)); ++m_nextHistoryIndex; updateButtonState(); if(m_history.size() > (maxHistoryLength + 5)) { m_history = m_history.mid(m_history.size() - maxHistoryLength); m_nextHistoryIndex = m_history.size(); } } } void ContextBrowserPlugin::setAllowBrowsing(bool allow) { m_browseButton->setChecked(allow); } void ContextBrowserPlugin::updateDeclarationListBox(DUContext* context) { if(!context || !context->owner()) { qCDebug(PLUGIN_CONTEXTBROWSER) << "not updating box"; m_listUrl = IndexedString(); ///@todo Compute the context in the document here if (m_outlineLine) m_outlineLine->clear(); return; } Declaration* decl = context->owner(); m_listUrl = context->url(); Declaration* specialDecl = SpecializationStore::self().applySpecialization(decl, decl->topContext()); FunctionType::Ptr function = specialDecl->type(); QString text = specialDecl->qualifiedIdentifier().toString(); if(function) text += function->partToString(KDevelop::FunctionType::SignatureArguments); if(m_outlineLine && !m_outlineLine->hasFocus()) { m_outlineLine->setText(text); m_outlineLine->setCursorPosition(0); } qCDebug(PLUGIN_CONTEXTBROWSER) << "updated" << text; } void ContextBrowserPlugin::actionTriggered() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); Q_ASSERT(action->data().type() == QVariant::Int); int historyPosition = action->data().toInt(); // qCDebug(PLUGIN_CONTEXTBROWSER) << "history pos" << historyPosition << m_history.size() << m_history; if(historyPosition >= 0 && historyPosition < m_history.size()) { m_nextHistoryIndex = historyPosition + 1; openDocument(historyPosition); updateButtonState(); } } void ContextBrowserPlugin::doNavigate(NavigationActionType action) { KTextEditor::View* view = qobject_cast(sender()); if(!view) { qWarning() << "sender is not a view"; return; } KTextEditor::CodeCompletionInterface* iface = qobject_cast(view); if(!iface || iface->isCompletionActive()) return; // If code completion is active, the actions should be handled by the completion widget QWidget* widget = m_currentNavigationWidget.data(); if(!widget || !widget->isVisible()) { ContextBrowserView* contextView = browserViewForWidget(view); if(contextView) widget = contextView->navigationWidget(); } if(widget) { AbstractNavigationWidget* navWidget = qobject_cast(widget); if (navWidget) { switch(action) { case Accept: navWidget->accept(); break; case Back: navWidget->back(); break; case Left: navWidget->previous(); break; case Right: navWidget->next(); break; case Up: navWidget->up(); break; case Down: navWidget->down(); break; } } } } void ContextBrowserPlugin::navigateAccept() { doNavigate(Accept); } void ContextBrowserPlugin::navigateBack() { doNavigate(Back); } void ContextBrowserPlugin::navigateDown() { doNavigate(Down); } void ContextBrowserPlugin::navigateLeft() { doNavigate(Left); } void ContextBrowserPlugin::navigateRight() { doNavigate(Right); } void ContextBrowserPlugin::navigateUp() { doNavigate(Up); } //BEGIN HistoryEntry ContextBrowserPlugin::HistoryEntry::HistoryEntry(KDevelop::DocumentCursor pos) : absoluteCursorPosition(pos) { } ContextBrowserPlugin::HistoryEntry::HistoryEntry(IndexedDUContext ctx, const KTextEditor::Cursor& cursorPosition) : context(ctx) { //Use a position relative to the context setCursorPosition(cursorPosition); if(ctx.data()) alternativeString = ctx.data()->scopeIdentifier(true).toString(); if(!alternativeString.isEmpty()) alternativeString += i18n("(changed)"); //This is used when the context was deleted in between } DocumentCursor ContextBrowserPlugin::HistoryEntry::computePosition() const { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); DocumentCursor ret; if(context.data()) { ret = DocumentCursor(context.data()->url(), relativeCursorPosition); ret.setLine(ret.line() + context.data()->range().start.line); }else{ ret = absoluteCursorPosition; } return ret; } void ContextBrowserPlugin::HistoryEntry::setCursorPosition(const KTextEditor::Cursor& cursorPosition) { KDevelop::DUChainReadLocker lock( KDevelop::DUChain::lock() ); if(context.data()) { absoluteCursorPosition = DocumentCursor(context.data()->url(), cursorPosition); relativeCursorPosition = cursorPosition; relativeCursorPosition.setLine(relativeCursorPosition.line() - context.data()->range().start.line); } } // kate: space-indent on; indent-width 2; tab-width 4; replace-tabs on; auto-insert-doxygen on #include "contextbrowser.moc" diff --git a/plugins/contextbrowser/contextbrowserview.cpp b/plugins/contextbrowser/contextbrowserview.cpp index b60ea33cd4..b42586218d 100644 --- a/plugins/contextbrowser/contextbrowserview.cpp +++ b/plugins/contextbrowser/contextbrowserview.cpp @@ -1,327 +1,320 @@ /* * This file is part of KDevelop * * Copyright 2008 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "contextbrowserview.h" -///TODO: cleanup includes -#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 #include "contextbrowser.h" #include "debug.h" #include #include #include #include "browsemanager.h" #include -#include -#include #include #include #include #include #include using namespace KDevelop; QWidget* ContextBrowserView::createWidget(KDevelop::DUContext* context) { m_context = IndexedDUContext(context); if(m_context.data()) { return m_context.data()->createNavigationWidget(); } return 0; } KDevelop::IndexedDeclaration ContextBrowserView::declaration() const { return m_declaration; } QWidget* ContextBrowserView::createWidget(Declaration* decl, TopDUContext* topContext) { m_declaration = IndexedDeclaration(decl); return decl->context()->createNavigationWidget(decl, topContext); } void ContextBrowserView::resetWidget() { if (m_navigationWidget) { delete m_navigationWidget; m_navigationWidget = 0; } } void ContextBrowserView::declarationMenu() { DUChainReadLocker lock(DUChain::lock()); AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget) { AbstractDeclarationNavigationContext* navigationContext = dynamic_cast(navigationWidget->context().data()); if(navigationContext && navigationContext->declaration().data()) { KDevelop::DeclarationContext* c = new KDevelop::DeclarationContext(navigationContext->declaration().data()); lock.unlock(); QMenu menu; QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(&menu, extensions); menu.exec(QCursor::pos()); } } } void ContextBrowserView::updateLockIcon(bool checked) { m_lockButton->setIcon(QIcon::fromTheme(checked ? "document-encrypt" : "document-decrypt")); } ContextBrowserView::ContextBrowserView( ContextBrowserPlugin* plugin, QWidget* parent ) : QWidget(parent), m_plugin(plugin), m_navigationWidget(new QTextBrowser()), m_autoLocked(false) { setWindowIcon( QIcon::fromTheme("applications-development-web") ); m_allowLockedUpdate = false; m_buttons = new QHBoxLayout; m_buttons->addStretch(); m_declarationMenuButton = new QToolButton(); m_declarationMenuButton->setIcon(QIcon::fromTheme("code-class")); m_declarationMenuButton->setToolTip(i18n("Declaration menu")); connect(m_declarationMenuButton, &QToolButton::clicked, this, &ContextBrowserView::declarationMenu); m_buttons->addWidget(m_declarationMenuButton); m_lockButton = new QToolButton(); m_lockButton->setCheckable(true); m_lockButton->setChecked(false); m_lockButton->setToolTip(i18n("Lock current view")); updateLockIcon(m_lockButton->isChecked()); connect(m_lockButton, &QToolButton::toggled, this, &ContextBrowserView::updateLockIcon); m_buttons->addWidget(m_lockButton); m_layout = new QVBoxLayout; m_layout->setSpacing(0); m_layout->setMargin(0); m_layout->addLayout(m_buttons); m_layout->addWidget(m_navigationWidget); //m_layout->addStretch(); setLayout(m_layout); m_plugin->registerToolView(this); } ContextBrowserView::~ContextBrowserView() { m_plugin->unRegisterToolView(this); } void ContextBrowserView::focusInEvent(QFocusEvent* event) { //Indicate that we have focus qCDebug(PLUGIN_CONTEXTBROWSER) << "got focus"; // parentWidget()->setBackgroundRole(QPalette::ToolTipBase); /* m_layout->removeItem(m_buttons);*/ return QWidget::focusInEvent(event); } void ContextBrowserView::focusOutEvent(QFocusEvent* event) { qCDebug(PLUGIN_CONTEXTBROWSER) << "lost focus"; // parentWidget()->setBackgroundRole(QPalette::Background); /* m_layout->insertLayout(0, m_buttons); for(int a = 0; a < m_buttons->count(); ++a) { QWidgetItem* item = dynamic_cast(m_buttons->itemAt(a)); }*/ QWidget::focusOutEvent(event); } bool ContextBrowserView::event(QEvent* event) { QKeyEvent* keyEvent = dynamic_cast(event); if(hasFocus() && keyEvent) { AbstractNavigationWidget* navigationWidget = dynamic_cast(m_navigationWidget.data()); if(navigationWidget && event->type() == QEvent::KeyPress) { int key = keyEvent->key(); if(key == Qt::Key_Left) navigationWidget->previous(); if(key == Qt::Key_Right) navigationWidget->next(); if(key == Qt::Key_Up) navigationWidget->up(); if(key == Qt::Key_Down) navigationWidget->down(); if(key == Qt::Key_Return || key == Qt::Key_Enter) navigationWidget->accept(); if(key == Qt::Key_L) m_lockButton->toggle(); } } return QWidget::event(event); } void ContextBrowserView::showEvent(QShowEvent* event) { DUChainReadLocker lock(DUChain::lock()); TopDUContext* top = m_lastUsedTopContext.data(); if(top && m_navigationWidgetDeclaration.isValid() && m_navigationWidgetDeclaration.getDeclaration(top)) { if(top) { //Update the navigation-widget Declaration* decl = m_navigationWidgetDeclaration.getDeclaration(top); setDeclaration(decl, top, true); //Update the declaration combo-box /* TODO: bring this back if required DUContext* context = 0; KDevelop::IDocument* doc = ICore::self()->documentController()->activeDocument(); if(doc && doc->textDocument() && doc->textDocument()->activeView()) { KTextEditor::Cursor c = doc->textDocument()->activeView()->cursorPosition(); context = getContextAt(top->url().toUrl(), c); } m_plugin->updateDeclarationListBox(context); */ } } QWidget::showEvent(event); } bool ContextBrowserView::isLocked() const { bool isLocked; if (m_allowLockedUpdate) { isLocked = false; } else { isLocked = m_lockButton->isChecked(); } return isLocked; } void ContextBrowserView::updateMainWidget(QWidget* widget) { if (widget) { setUpdatesEnabled(false); qCDebug(PLUGIN_CONTEXTBROWSER) << ""; resetWidget(); m_navigationWidget = widget; m_layout->insertWidget(1, widget, 1); m_allowLockedUpdate = false; setUpdatesEnabled(true); if (widget->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("contextChanged(bool,bool)")) != -1) { connect(widget, SIGNAL(contextChanged(bool,bool)), this, SLOT(navigationContextChanged(bool,bool))); } } } void ContextBrowserView::navigationContextChanged(bool wasInitial, bool isInitial) { if(wasInitial && !isInitial && !m_lockButton->isChecked()) { m_autoLocked = true; m_lockButton->setChecked(true); }else if(!wasInitial && isInitial && m_autoLocked) { m_autoLocked = false; m_lockButton->setChecked(false); }else if(isInitial) { m_autoLocked = false; } } void ContextBrowserView::setDeclaration(KDevelop::Declaration* decl, KDevelop::TopDUContext* topContext, bool force) { m_lastUsedTopContext = IndexedTopDUContext(topContext); if(isLocked() && (!m_navigationWidget.data() || !isVisible())) { // Automatically remove the locked state if the view is not visible or the widget was deleted, // because the locked state has side-effects on other navigation functionality. m_autoLocked = false; m_lockButton->setChecked(false); } if(m_navigationWidgetDeclaration == decl->id() && !force) return; m_navigationWidgetDeclaration = decl->id(); if (!isLocked() && (isVisible() || force)) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(decl, topContext); updateMainWidget(w); } } KDevelop::IndexedDeclaration ContextBrowserView::lockedDeclaration() const { if(m_lockButton->isChecked()) return declaration(); else return KDevelop::IndexedDeclaration(); } void ContextBrowserView::allowLockedUpdate() { m_allowLockedUpdate = true; } void ContextBrowserView::setNavigationWidget(QWidget* widget) { updateMainWidget(widget); } void ContextBrowserView::setContext(KDevelop::DUContext* context) { if(!context) return; m_lastUsedTopContext = IndexedTopDUContext(context->topContext()); if(context->owner()) { if(context->owner()->id() == m_navigationWidgetDeclaration) return; m_navigationWidgetDeclaration = context->owner()->id(); }else{ m_navigationWidgetDeclaration = DeclarationId(); } if (!isLocked() && isVisible()) { // NO-OP if toolview is hidden, for performance reasons QWidget* w = createWidget(context); updateMainWidget(w); } } void ContextBrowserView::setSpecialNavigationWidget(QWidget* widget) { if (!isLocked() && isVisible()) { Q_ASSERT(widget); updateMainWidget(widget); } else if(widget) { widget->deleteLater(); } } diff --git a/plugins/documentswitcher/documentswitcherplugin.cpp b/plugins/documentswitcher/documentswitcherplugin.cpp index 8a20764b47..241a3fa6b8 100644 --- a/plugins/documentswitcher/documentswitcherplugin.cpp +++ b/plugins/documentswitcher/documentswitcherplugin.cpp @@ -1,391 +1,385 @@ /*************************************************************************** * Copyright 2009,2013 Andreas Pakulat * * Copyright 2013 Jarosław Sierant * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "documentswitcherplugin.h" +#include +#include +#include #include -#include +#include +#include +#include #include -#include -#include -#include -#include +#include #include #include #include #include #include #include #include #include "documentswitchertreeview.h" -#include #include #include -#include -#include -#include -#include -#include -#include - #include Q_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER, "kdevplatform.plugins.documentswitcher") K_PLUGIN_FACTORY_WITH_JSON(DocumentSwitcherFactory, "kdevdocumentswitcher.json", registerPlugin();) //TODO: Show frame around view's widget while walking through //TODO: Make the widget transparent DocumentSwitcherPlugin::DocumentSwitcherPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin("kdevdocumentswitcher", parent), view(0) { setXMLFile("kdevdocumentswitcher.rc"); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "Adding active mainwindow from constructor" << KDevelop::ICore::self()->uiController()->activeMainWindow(); addMainWindow( qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ) ); connect( KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::mainWindowAdded, this, &DocumentSwitcherPlugin::addMainWindow ); forwardAction = actionCollection()->addAction ( "last_used_views_forward" ); forwardAction->setText( i18n( "Last Used Views" ) ); forwardAction->setIcon( QIcon::fromTheme("go-next-view-page") ); actionCollection()->setDefaultShortcut( forwardAction, Qt::CTRL | Qt::Key_Tab ); forwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views." ) ); forwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( forwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkForward ); backwardAction = actionCollection()->addAction ( "last_used_views_backward" ); backwardAction->setText( i18n( "Last Used Views (Reverse)" ) ); backwardAction->setIcon( QIcon::fromTheme("go-previous-view-page") ); actionCollection()->setDefaultShortcut( backwardAction, Qt::CTRL | Qt::SHIFT | Qt::Key_Tab ); backwardAction->setWhatsThis( i18n( "Opens a list to walk through the list of last used views in reverse." ) ); backwardAction->setStatusTip( i18n( "Walk through the list of last used views" ) ); connect( backwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkBackward ); view = new DocumentSwitcherTreeView( this ); view->setSelectionBehavior( QAbstractItemView::SelectRows ); view->setSelectionMode( QAbstractItemView::SingleSelection ); view->setUniformItemSizes( true ); view->setTextElideMode( Qt::ElideMiddle ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); view->addAction( forwardAction ); view->addAction( backwardAction ); connect( view, &QListView::pressed, this, &DocumentSwitcherPlugin::switchToClicked ); connect( view, &QListView::activated, this, &DocumentSwitcherPlugin::itemActivated ); model = new QStandardItemModel( view ); view->setModel( model ); } void DocumentSwitcherPlugin::setViewGeometry(Sublime::MainWindow* window) { const QSize centralSize = window->centralWidget()->size(); // Maximum size of the view is 3/4th of the central widget (the editor area) so the view does not overlap the // mainwindow since that looks awkward. const QSize viewMaxSize( centralSize.width() * 3/4, centralSize.height() * 3/4 ); // The actual view size should be as big as the columns/rows need it, but smaller than the max-size. This means // the view will get quite high with many open files but I think thats ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const QSize viewSize( std::min( view->sizeHintForColumn(0) + view->verticalScrollBar()->width(), viewMaxSize.width() ), std::min( std::max( view->sizeHintForRow(0) * view->model()->rowCount(), view->sizeHintForRow(0) * 6 ), viewMaxSize.height() ) ); // Position should be central over the editor area, so map to global from parent of central widget since // the view is positioned in global coords QPoint centralWidgetPos = window->mapToGlobal( window->centralWidget()->pos() ); const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width() - viewSize.width() ) / 2); const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height() ) / 2); view->setFixedSize(viewSize); view->move(xPos, yPos); } void DocumentSwitcherPlugin::walk(const int from, const int to) { Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); if( !window || !documentLists.contains( window ) || !documentLists[window].contains( window->area() ) ) { qWarning() << "This should not happen, tried to walk through document list of an unknown mainwindow!"; return; } QModelIndex idx; const int step = from < to ? 1 : -1; if(!view->isVisible()) { fillModel(window); setViewGeometry(window); idx = model->index(from + step, 0); if(!idx.isValid()) { idx = model->index(0, 0); } view->show(); } else { int newRow = view->selectionModel()->currentIndex().row() + step; if(newRow == to + step) { newRow = from; } idx = model->index(newRow, 0); } view->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); } void DocumentSwitcherPlugin::walkForward() { walk(0, model->rowCount()-1); } void DocumentSwitcherPlugin::walkBackward() { walk(model->rowCount()-1, 0); } void DocumentSwitcherPlugin::fillModel( Sublime::MainWindow* window ) { model->clear(); foreach( Sublime::View* v, documentLists[window][window->area()] ) { using namespace KDevelop; Sublime::Document const* const slDoc = v->document(); if( !slDoc ) { continue; } QString itemText = slDoc->title();// file name IDocument const* const doc = dynamic_cast(v->document()); if( doc ) { QString path = ICore::self()->projectController()->prettyFilePath(doc->url(), IProjectController::FormatPlain); const bool isPartOfOpenProject = QDir::isRelativePath(path); if( path.endsWith('/') ) { path.remove(path.length() - 1, 1); } if( isPartOfOpenProject ) { const int projectNameSize = path.indexOf("/"); // first: project name, second: path to file in project (might be just '/' when the file is in the project root dir) const QPair fileInProjectInfo = (projectNameSize < 0) ? qMakePair(path, QStringLiteral("/")) : qMakePair(path.left(projectNameSize), path.mid(projectNameSize)); itemText = QStringLiteral("%1 (%2:%3)").arg(itemText).arg(fileInProjectInfo.first) .arg(fileInProjectInfo.second); } else { itemText = itemText + " (" + path + ')'; } } model->appendRow( new QStandardItem( slDoc->icon(), itemText ) ); } } DocumentSwitcherPlugin::~DocumentSwitcherPlugin() { } void DocumentSwitcherPlugin::switchToClicked( const QModelIndex& idx ) { view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect); itemActivated(idx); } void DocumentSwitcherPlugin::itemActivated( const QModelIndex& idx ) { Q_UNUSED( idx ); if( view->selectionModel()->selectedRows().isEmpty() ) { return; } int row = view->selectionModel()->selectedRows().first().row(); Sublime::MainWindow* window = qobject_cast( KDevelop::ICore::self()->uiController()->activeMainWindow() ); Sublime::View* activatedView = 0; if( window && documentLists.contains( window ) && documentLists[window].contains( window->area() ) ) { const QList l = documentLists[window][window->area()]; if( row >= 0 && row < l.size() ) { activatedView = l.at( row ); } } if( activatedView ) { if( QApplication::mouseButtons() & Qt::MiddleButton ) { window->area()->closeView( activatedView ); fillModel( window ); if ( model->rowCount() == 0 ) { view->hide(); } else { view->selectionModel()->select( view->model()->index(0, 0), QItemSelectionModel::ClearAndSelect ); } } else { window->activateView( activatedView ); view->hide(); } } } void DocumentSwitcherPlugin::unload() { foreach( QObject* mw, documentLists.keys() ) { removeMainWindow( mw ); } delete forwardAction; delete backwardAction; view->deleteLater(); } void DocumentSwitcherPlugin::storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ) { if( !documentLists.contains( mainwindow ) || !documentLists[mainwindow].contains(area) ) { QMap > areas; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding area views for area:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); foreach( Sublime::View* v, area->views() ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "view:" << v << v->document()->title(); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "done"; areas.insert( area, area->views() ); documentLists.insert( mainwindow, areas ); } } void DocumentSwitcherPlugin::addMainWindow( Sublime::MainWindow* mainwindow ) { if( !mainwindow ) { return; } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding mainwindow:" << mainwindow << mainwindow->windowTitle(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "storing all views from area:" << mainwindow->area()->title() << mainwindow->area(); storeAreaViewList( mainwindow, mainwindow->area() ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "connecting signals on mainwindow"; connect( mainwindow, &Sublime::MainWindow::areaChanged, this, &DocumentSwitcherPlugin::changeArea ); connect( mainwindow, &Sublime::MainWindow::activeViewChanged, this, &DocumentSwitcherPlugin::changeView ); connect( mainwindow, &Sublime::MainWindow::viewAdded, this, &DocumentSwitcherPlugin::addView ); connect( mainwindow, &Sublime::MainWindow::aboutToRemoveView, this, &DocumentSwitcherPlugin::removeView ); connect( mainwindow, &Sublime::MainWindow::destroyed, this, &DocumentSwitcherPlugin::removeMainWindow); mainwindow->installEventFilter( this ); } bool DocumentSwitcherPlugin::eventFilter( QObject* watched, QEvent* ev ) { Sublime::MainWindow* mw = dynamic_cast( watched ); if( mw && ev->type() == QEvent::WindowActivate ) { enableActions(); } return QObject::eventFilter( watched, ev ); } void DocumentSwitcherPlugin::addView( Sublime::View* view ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); if( !mainwindow ) return; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got signal from mainwindow:" << mainwindow << mainwindow->windowTitle(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "its area is:" << mainwindow->area() << mainwindow->area()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "adding view:" << view << view->document()->title(); enableActions(); documentLists[mainwindow][mainwindow->area()].append( view ); } void DocumentSwitcherPlugin::enableActions() { forwardAction->setEnabled(true); backwardAction->setEnabled(true); } void DocumentSwitcherPlugin::removeMainWindow( QObject* obj ) { if( !obj || !documentLists.contains(obj) ) { return; } obj->removeEventFilter( this ); disconnect( obj, 0, this, 0 ); documentLists.remove( obj ); } void DocumentSwitcherPlugin::changeArea( Sublime::Area* area ) { Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "area changed:" << area << area->title() << "mainwindow:" << mainwindow << mainwindow->windowTitle(); //Since the main-window only emits aboutToRemoveView for views within the current area, we must forget all areas except the active one documentLists.remove(mainwindow); if( !documentLists[mainwindow].contains( area ) ) { qCDebug(PLUGIN_DOCUMENTSWITCHER) << "got area change, storing its views"; storeAreaViewList( mainwindow, area ); } enableActions(); } void DocumentSwitcherPlugin::changeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "moving view to front, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); documentLists[mainwindow][area].prepend( view ); enableActions(); } void DocumentSwitcherPlugin::removeView( Sublime::View* view ) { if( !view ) return; Sublime::MainWindow* mainwindow = qobject_cast( sender() ); Q_ASSERT( mainwindow ); Sublime::Area* area = mainwindow->area(); int idx = documentLists[mainwindow][area].indexOf( view ); if( idx != -1 ) { documentLists[mainwindow][area].removeAt( idx ); } qCDebug(PLUGIN_DOCUMENTSWITCHER) << "removing view, list should now not contain this view anymore" << view << view->document()->title(); qCDebug(PLUGIN_DOCUMENTSWITCHER) << "current area is:" << area << area->title() << "mainwnidow:" << mainwindow << mainwindow->windowTitle();; qCDebug(PLUGIN_DOCUMENTSWITCHER) << "idx of this view in list:" << documentLists[mainwindow][area].indexOf( view ); enableActions(); } #include "documentswitcherplugin.moc" diff --git a/plugins/documentview/kdevdocumentview.cpp b/plugins/documentview/kdevdocumentview.cpp index 3b0a242f23..74d072ba93 100644 --- a/plugins/documentview/kdevdocumentview.cpp +++ b/plugins/documentview/kdevdocumentview.cpp @@ -1,348 +1,347 @@ /* This file is part of KDevelop Copyright 2005 Adam Treat Copyright 2013 Sebastian Kügler This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kdevdocumentview.h" #include "kdevdocumentviewplugin.h" #include "kdevdocumentmodel.h" -#include +#include +#include +#include #include #include -#include #include -#include #include -#include -#include +#include #include "kdevdocumentselection.h" #include "kdevdocumentviewdelegate.h" #include #include #include #include #include #include #include #include using namespace KDevelop; KDevDocumentView::KDevDocumentView( KDevDocumentViewPlugin *plugin, QWidget *parent ) : QTreeView( parent ), m_plugin( plugin ) { connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &KDevDocumentView::updateProjectPaths); connect(ICore::self()->projectController(), &IProjectController::projectClosed, this, &KDevDocumentView::updateProjectPaths); m_documentModel = new KDevDocumentModel(this); m_delegate = new KDevDocumentViewDelegate( this ); m_proxy = new QSortFilterProxyModel( this ); m_proxy->setSourceModel( m_documentModel ); m_proxy->setDynamicSortFilter( true ); m_proxy->setSortCaseSensitivity( Qt::CaseInsensitive ); m_proxy->sort(0); m_selectionModel = new KDevDocumentSelection( m_proxy ); setModel( m_proxy ); setSelectionModel( m_selectionModel ); setItemDelegate( m_delegate ); setObjectName( i18n( "Documents" ) ); setWindowIcon( QIcon::fromTheme( "document-multiple" ) ); setWindowTitle( i18n( "Documents" ) ); setFocusPolicy( Qt::NoFocus ); header()->hide(); setSelectionBehavior( QAbstractItemView::SelectRows ); setSelectionMode( QAbstractItemView::ExtendedSelection ); } KDevDocumentView::~KDevDocumentView() {} KDevDocumentViewPlugin *KDevDocumentView::plugin() const { return m_plugin; } void KDevDocumentView::mousePressEvent( QMouseEvent * event ) { QModelIndex proxyIndex = indexAt( event->pos() ); QModelIndex index = m_proxy->mapToSource( proxyIndex ); if (event->button() == Qt::LeftButton && event->modifiers() == Qt::NoModifier) { if (proxyIndex.parent().isValid()) { // this is a document item KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); QUrl documentUrl = static_cast(m_documentModel->itemFromIndex(index))->fileItem()->url(); if (dc->documentForUrl(documentUrl) != dc->activeDocument()) { dc->openDocument(documentUrl); return; } } else { // this is a folder item setExpanded(proxyIndex, !isExpanded(proxyIndex)); return; } } QTreeView::mousePressEvent( event ); } template void KDevDocumentView::visitItems(F f, bool selectedItems) { KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); QList docs = selectedItems ? m_selectedDocs : m_unselectedDocs; - + foreach(const QUrl& url, docs) { KDevelop::IDocument* doc = dc->documentForUrl(url); if (doc) f(doc); } } namespace { class DocSaver { public: void operator()(KDevelop::IDocument* doc) { doc->save(); } }; class DocCloser { public: void operator()(KDevelop::IDocument* doc) { doc->close(); } }; class DocReloader { public: void operator()(KDevelop::IDocument* doc) { doc->reload(); } }; } void KDevDocumentView::saveSelected() { visitItems(DocSaver(), true); } void KDevDocumentView::closeSelected() { visitItems(DocCloser(), true); } void KDevDocumentView::closeUnselected() { visitItems(DocCloser(), false); } void KDevDocumentView::reloadSelected() { visitItems(DocReloader(), true); } void KDevDocumentView::contextMenuEvent( QContextMenuEvent * event ) { QModelIndex proxyIndex = indexAt( event->pos() ); // for now, ignore clicks on empty space or folder items if (!proxyIndex.isValid() || !proxyIndex.parent().isValid()) { return; } updateSelectedDocs(); if (!m_selectedDocs.isEmpty()) { QMenu* ctxMenu = new QMenu(this); KDevelop::FileContext context(m_selectedDocs); QList extensions = m_plugin->core()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList vcsActions; QList fileActions; QList editActions; QList extensionActions; foreach( const KDevelop::ContextMenuExtension& ext, extensions ) { fileActions += ext.actions(KDevelop::ContextMenuExtension::FileGroup); vcsActions += ext.actions(KDevelop::ContextMenuExtension::VcsGroup); editActions += ext.actions(KDevelop::ContextMenuExtension::EditGroup); extensionActions += ext.actions(KDevelop::ContextMenuExtension::ExtensionGroup); } - + appendActions(ctxMenu, fileActions); - + QAction* save = KStandardAction::save(this, SLOT(saveSelected()), ctxMenu); save->setEnabled(selectedDocHasChanges()); ctxMenu->addAction(save); ctxMenu->addAction(QIcon::fromTheme("view-refresh"), i18n( "Reload" ), this, SLOT(reloadSelected())); - + appendActions(ctxMenu, editActions); - + ctxMenu->addAction(KStandardAction::close(this, SLOT(closeSelected()), ctxMenu)); QAction* closeUnselected = ctxMenu->addAction(QIcon::fromTheme("document-close"), i18n( "Close Other Files" ), this, SLOT(closeUnselected())); closeUnselected->setEnabled(!m_unselectedDocs.isEmpty()); appendActions(ctxMenu, vcsActions); - + appendActions(ctxMenu, extensionActions); - + connect(ctxMenu, &QMenu::aboutToHide, ctxMenu, &QMenu::deleteLater); ctxMenu->popup( event->globalPos() ); } } void KDevDocumentView::appendActions(QMenu* menu, const QList& actions) { foreach( QAction* act, actions ) { menu->addAction(act); } menu->addSeparator(); } bool KDevDocumentView::selectedDocHasChanges() { KDevelop::IDocumentController* dc = m_plugin->core()->documentController(); foreach(const QUrl& url, m_selectedDocs) { KDevelop::IDocument* doc = dc->documentForUrl(url); if (!doc) continue; if (doc->state() != KDevelop::IDocument::Clean) { return true; } } return false; } void KDevDocumentView::updateSelectedDocs() { m_selectedDocs.clear(); m_unselectedDocs.clear(); QList allItems = m_documentModel->findItems("*", Qt::MatchWildcard | Qt::MatchRecursive); foreach (QStandardItem* item, allItems) { if (KDevFileItem * fileItem = dynamic_cast(item)->fileItem()) { if (m_selectionModel->isSelected(m_proxy->mapFromSource(m_documentModel->indexFromItem(fileItem)))) m_selectedDocs << fileItem->url(); else m_unselectedDocs << fileItem->url(); } } } void KDevDocumentView::activated( KDevelop::IDocument* document ) { setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( m_doc2index[ document ] ) ) ); } void KDevDocumentView::saved( KDevelop::IDocument* ) { } void KDevDocumentView::opened( KDevelop::IDocument* document ) { const QString path = QFileInfo( document->url().path() ).path(); KDevCategoryItem *categoryItem = m_documentModel->category( path ); if ( !categoryItem ) { categoryItem = new KDevCategoryItem( path ); categoryItem->setUrl( document->url() ); m_documentModel->insertRow( m_documentModel->rowCount(), categoryItem ); setExpanded( m_proxy->mapFromSource( m_documentModel->indexFromItem( categoryItem ) ), false); updateCategoryItem( categoryItem ); } if ( !categoryItem->file( document->url() ) ) { KDevFileItem * fileItem = new KDevFileItem( document->url() ); categoryItem->setChild( categoryItem->rowCount(), fileItem ); setCurrentIndex( m_proxy->mapFromSource( m_documentModel->indexFromItem( fileItem ) ) ); m_doc2index[ document ] = fileItem; } } void KDevDocumentView::closed( KDevelop::IDocument* document ) { KDevFileItem* file = m_doc2index[ document ]; if ( !file ) return; QStandardItem* categoryItem = file->parent(); qDeleteAll(categoryItem->takeRow(m_documentModel->indexFromItem(file).row())); m_doc2index.remove(document); if ( categoryItem->hasChildren() ) return; qDeleteAll(m_documentModel->takeRow(m_documentModel->indexFromItem(categoryItem).row())); doItemsLayout(); } void KDevDocumentView::updateCategoryItem( KDevCategoryItem *item ) { QString text = KDevelop::ICore::self()->projectController()->prettyFilePath(item->url(), KDevelop::IProjectController::FormatPlain); // remove trailing slash if (text.length() > 1) { text.chop(1); } item->setText(text); } void KDevDocumentView::updateProjectPaths() { foreach ( KDevCategoryItem *it, m_documentModel->categoryList() ) updateCategoryItem( it ); } void KDevDocumentView::contentChanged( KDevelop::IDocument* ) { } void KDevDocumentView::stateChanged( KDevelop::IDocument* document ) { KDevDocumentItem * documentItem = m_doc2index[ document ]; if ( documentItem && documentItem->documentState() != document->state() ) documentItem->setDocumentState( document->state() ); doItemsLayout(); } void KDevDocumentView::documentUrlChanged( KDevelop::IDocument* document ) { closed(document); opened(document); } diff --git a/plugins/documentview/kdevdocumentviewplugin.h b/plugins/documentview/kdevdocumentviewplugin.h index 5875aeebf0..d8f51e5784 100644 --- a/plugins/documentview/kdevdocumentviewplugin.h +++ b/plugins/documentview/kdevdocumentviewplugin.h @@ -1,52 +1,49 @@ /* This file is part of KDevelop Copyright 2005 Adam Treat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_KDEVDOCUMENTVIEW_PART_H #define KDEVPLATFORM_PLUGIN_KDEVDOCUMENTVIEW_PART_H -#include -#include - #include #include class KDevDocumentViewPluginFactory; class KDevDocumentViewPlugin: public KDevelop::IPlugin { Q_OBJECT public: enum RefreshPolicy { Refresh, NoRefresh, ForceRefresh }; public: KDevDocumentViewPlugin( QObject *parent, const QVariantList& args ); virtual ~KDevDocumentViewPlugin(); virtual void unload() override; private: KDevDocumentViewPluginFactory* factory; }; #endif diff --git a/plugins/documentview/settings/preferences.cpp b/plugins/documentview/settings/preferences.cpp index d0444cf6dc..e47cedcc19 100644 --- a/plugins/documentview/settings/preferences.cpp +++ b/plugins/documentview/settings/preferences.cpp @@ -1,72 +1,70 @@ /* Document View Settings * * Copyright 2006 Matt Rogers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "preferences.h" #include -#include - #include "ui_settingswidget.h" #include typedef KGenericFactory PreferencesFactory; K_EXPORT_COMPONENT_FACTORY( kcm_documentview_settings, PreferencesFactory( "kcm_documentview_settings" ) ) Preferences::Preferences(QWidget *parent, const QStringList &args) : KDevelop::ConfigModule( DocumentViewSettings::self(), PreferencesFactory::componentData(), parent, args ) { QVBoxLayout* l = new QVBoxLayout( this ); QWidget* w = new QWidget; preferencesDialog = new Ui::SettingsWidget; preferencesDialog->setupUi( w ); l->addWidget( w ); addConfig( DocumentViewSettings::self(), w ); load(); } Preferences::~Preferences( ) { delete preferencesDialog; } void Preferences::save() { KDevelop::ConfigModule::save(); } void Preferences::load() { KDevelop::ConfigModule::load(); } void Preferences::slotSettingsChanged() { emit changed( true ); } void Preferences::defaults() { } diff --git a/plugins/execute/executeplugin.cpp b/plugins/execute/executeplugin.cpp index eea8c8ce0c..770e33eef0 100644 --- a/plugins/execute/executeplugin.cpp +++ b/plugins/execute/executeplugin.cpp @@ -1,255 +1,252 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executeplugin.h" -#include #include +#include +#include #include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include #include #include #include #include #include #include "nativeappconfig.h" #include "debug.h" #include #include -#include #include QString ExecutePlugin::_nativeAppConfigTypeId = "Native Application"; QString ExecutePlugin::workingDirEntry = "Working Directory"; QString ExecutePlugin::executableEntry = "Executable"; QString ExecutePlugin::argumentsEntry = "Arguments"; QString ExecutePlugin::isExecutableEntry = "isExecutable"; QString ExecutePlugin::dependencyEntry = "Dependencies"; QString ExecutePlugin::environmentGroupEntry = "EnvironmentGroup"; QString ExecutePlugin::useTerminalEntry = "Use External Terminal"; QString ExecutePlugin::terminalEntry = "External Terminal"; QString ExecutePlugin::userIdToRunEntry = "User Id to Run"; QString ExecutePlugin::dependencyActionEntry = "Dependency Action"; QString ExecutePlugin::projectTargetEntry = "Project Target"; using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTE, "kdevplatform.plugins.execute") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecute.json", registerPlugin();) ExecutePlugin::ExecutePlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin("kdevexecute", parent) { KDEV_USE_EXTENSION_INTERFACE( IExecutePlugin ) m_configType = new NativeAppConfigType(); m_configType->addLauncher( new NativeAppLauncher() ); qCDebug(PLUGIN_EXECUTE) << "adding native app launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecutePlugin::~ExecutePlugin() { } void ExecutePlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = 0; } QStringList ExecutePlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecutePlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } KJob* ExecutePlugin::dependencyJob( KDevelop::ILaunchConfiguration* cfg ) const { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( dependencyEntry, QString() ) ).toList(); QString depAction = cfg->config().readEntry( dependencyActionEntry, "Nothing" ); if( depAction != "Nothing" && !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(core()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob(); if( depAction == "Build" ) { job->addItems( KDevelop::BuilderJob::Build, items ); } else if( depAction == "Install" ) { job->addItems( KDevelop::BuilderJob::Install, items ); } job->updateJobName(); return job; } return 0; } QString ExecutePlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return ""; } return cfg->config().readEntry( ExecutePlugin::environmentGroupEntry, "" ); } QUrl ExecutePlugin::executable( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QUrl executable; if( !cfg ) { return executable; } KConfigGroup grp = cfg->config(); if( grp.readEntry(ExecutePlugin::isExecutableEntry, false ) ) { executable = grp.readEntry( ExecutePlugin::executableEntry, QUrl() ); } else { QStringList prjitem = grp.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex(prjitem) ); if( item && item->executable() ) { // TODO: Need an option in the gui to choose between installed and builddir url here, currently cmake only supports builddir url executable = item->executable()->builtUrl(); } } if( executable.isEmpty() ) { err = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid executable set"; } else { KShell::Errors err_; if( KShell::splitArgs( executable.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { executable = QUrl(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the executable " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "executable for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "executable has meta characters"; } } return executable; } bool ExecutePlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecutePlugin::useTerminalEntry, false ); } QString ExecutePlugin::terminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QString(); } return cfg->config().readEntry( ExecutePlugin::terminalEntry, QString() ); } QUrl ExecutePlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecutePlugin::workingDirEntry, QUrl() ); } QString ExecutePlugin::nativeAppConfigTypeId() const { return _nativeAppConfigTypeId; } #include "executeplugin.moc" diff --git a/plugins/execute/nativeappconfig.cpp b/plugins/execute/nativeappconfig.cpp index 9c83db9e02..0364dbd383 100644 --- a/plugins/execute/nativeappconfig.cpp +++ b/plugins/execute/nativeappconfig.cpp @@ -1,522 +1,521 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2010 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nativeappconfig.h" -#include -#include - #include #include #include #include #include "nativeappjob.h" #include #include #include #include #include -#include #include #include -#include + #include #include "executeplugin.h" #include "debug.h" #include #include #include #include "projecttargetscombobox.h" -#include + #include +#include +#include -#include #include +#include #include + using namespace KDevelop; QIcon NativeAppConfigPage::icon() const { return QIcon::fromTheme("system-run"); } static KDevelop::ProjectBaseItem* itemForPath(const QStringList& path, KDevelop::ProjectModel* model) { return model->itemFromIndex(model->pathToIndex(path)); } //TODO: Make sure to auto-add the executable target to the dependencies when its used. void NativeAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); projectTarget->setBaseItem( project ? project->projectItem() : 0, true); projectTarget->setCurrentItemPath( cfg.readEntry( ExecutePlugin::projectTargetEntry, QStringList() ) ); QUrl exe = cfg.readEntry( ExecutePlugin::executableEntry, QUrl()); if( !exe.isEmpty() || project ){ executablePath->setUrl( !exe.isEmpty() ? exe : project->folder() ); }else{ KDevelop::IProjectController* pc = KDevelop::ICore::self()->projectController(); if( pc ){ executablePath->setUrl( pc->projects().count() ? pc->projects().first()->folder() : QUrl() ); } } //executablePath->setFilter("application/x-executable"); executableRadio->setChecked( true ); if ( !cfg.readEntry( ExecutePlugin::isExecutableEntry, false ) && projectTarget->count() ){ projectTargetRadio->setChecked( true ); } arguments->setClearButtonEnabled( true ); arguments->setText( cfg.readEntry( ExecutePlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecutePlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecutePlugin::environmentGroupEntry, QString() ) ); runInTerminal->setChecked( cfg.readEntry( ExecutePlugin::useTerminalEntry, false ) ); terminal->setEditText( cfg.readEntry( ExecutePlugin::terminalEntry, terminal->itemText(0) ) ); QVariantList deps = KDevelop::stringToQVariant( cfg.readEntry( ExecutePlugin::dependencyEntry, QString() ) ).toList(); QStringList strDeps; foreach( const QVariant& dep, deps ) { QStringList deplist = dep.toStringList(); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectBaseItem* pitem=itemForPath(deplist, model); QIcon icon; if(pitem) icon=QIcon::fromTheme(pitem->iconName()); QListWidgetItem* item = new QListWidgetItem(icon, KDevelop::joinWithEscaping( deplist, '/', '\\' ), dependencies ); item->setData( Qt::UserRole, dep ); } dependencyAction->setCurrentIndex( dependencyAction->findData( cfg.readEntry( ExecutePlugin::dependencyActionEntry, "Nothing" ) ) ); blockSignals( b ); } NativeAppConfigPage::NativeAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); //Setup data info for combobox dependencyAction->setItemData(0, "Nothing" ); dependencyAction->setItemData(1, "Build" ); dependencyAction->setItemData(2, "Install" ); dependencyAction->setItemData(3, "SudoInstall" ); addDependency->setIcon( QIcon::fromTheme("list-add") ); removeDependency->setIcon( QIcon::fromTheme("list-remove") ); moveDepUp->setIcon( QIcon::fromTheme("go-up") ); moveDepDown->setIcon( QIcon::fromTheme("go-down") ); browseProject->setIcon(QIcon::fromTheme("folder-document")); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); configureEnvironment->setSelectionWidget(environment); //connect signals to changed signal connect( projectTarget, static_cast(&ProjectTargetsComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( projectTargetRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executableRadio, &QRadioButton::toggled, this, &NativeAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &NativeAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &NativeAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &NativeAppConfigPage::changed ); connect( addDependency, &QPushButton::clicked, this, &NativeAppConfigPage::addDep ); connect( addDependency, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( removeDependency, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( removeDependency, &QPushButton::clicked, this, &NativeAppConfigPage::removeDep ); connect( moveDepDown, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( moveDepUp, &QPushButton::clicked, this, &NativeAppConfigPage::changed ); connect( moveDepDown, &QPushButton::clicked, this, &NativeAppConfigPage::moveDependencyDown ); connect( moveDepUp, &QPushButton::clicked, this, &NativeAppConfigPage::moveDependencyUp ); connect( dependencies->selectionModel(), &QItemSelectionModel::selectionChanged, this, &NativeAppConfigPage::checkActions ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( runInTerminal, &QCheckBox::toggled, this, &NativeAppConfigPage::changed ); connect( terminal, &KComboBox::editTextChanged, this, &NativeAppConfigPage::changed ); connect( terminal, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::changed ); connect( dependencyAction, static_cast(&KComboBox::currentIndexChanged), this, &NativeAppConfigPage::activateDeps ); connect( targetDependency, &ProjectItemLineEdit::textChanged, this, &NativeAppConfigPage::depEdited); connect( browseProject, &QPushButton::clicked, this, &NativeAppConfigPage::selectItemDialog); } void NativeAppConfigPage::depEdited( const QString& str ) { int pos; QString tmp = str; addDependency->setEnabled( !str.isEmpty() && ( !targetDependency->validator() || targetDependency->validator()->validate( tmp, pos ) == QValidator::Acceptable ) ); } void NativeAppConfigPage::activateDeps( int idx ) { browseProject->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); dependencies->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); targetDependency->setEnabled( dependencyAction->itemData( idx ).toString() != "Nothing" ); } void NativeAppConfigPage::checkActions( const QItemSelection& selected, const QItemSelection& unselected ) { Q_UNUSED( unselected ); qCDebug(PLUGIN_EXECUTE) << "checkActions"; if( !selected.indexes().isEmpty() ) { qCDebug(PLUGIN_EXECUTE) << "have selection"; Q_ASSERT( selected.indexes().count() == 1 ); QModelIndex idx = selected.indexes().at( 0 ); qCDebug(PLUGIN_EXECUTE) << "index" << idx; moveDepUp->setEnabled( idx.row() > 0 ); moveDepDown->setEnabled( idx.row() < dependencies->count() - 1 ); removeDependency->setEnabled( true ); } else { removeDependency->setEnabled( false ); moveDepUp->setEnabled( false ); moveDepDown->setEnabled( false ); } } void NativeAppConfigPage::moveDependencyDown() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); QListWidgetItem* item = list.at( 0 ); int row = dependencies->row( item ); dependencies->takeItem( row ); dependencies->insertItem( row+1, item ); dependencies->selectionModel()->select( dependencies->model()->index( row+1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::moveDependencyUp() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); QListWidgetItem* item = list.at( 0 ); int row = dependencies->row( item ); dependencies->takeItem( row ); dependencies->insertItem( row-1, item ); dependencies->selectionModel()->select( dependencies->model()->index( row-1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::addDep() { QIcon icon; KDevelop::ProjectBaseItem* pitem = targetDependency->currentItem(); if(pitem) icon = QIcon::fromTheme(pitem->iconName()); QListWidgetItem* item = new QListWidgetItem(icon, targetDependency->text(), dependencies); item->setData( Qt::UserRole, targetDependency->itemPath() ); targetDependency->setText(""); addDependency->setEnabled( false ); dependencies->selectionModel()->clearSelection(); item->setSelected(true); // dependencies->selectionModel()->select( dependencies->model()->index( dependencies->model()->rowCount() - 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } void NativeAppConfigPage::selectItemDialog() { if(targetDependency->selectItemDialog()) { addDep(); } } void NativeAppConfigPage::removeDep() { QList list = dependencies->selectedItems(); if( !list.isEmpty() ) { Q_ASSERT( list.count() == 1 ); int row = dependencies->row( list.at(0) ); delete dependencies->takeItem( row ); dependencies->selectionModel()->select( dependencies->model()->index( row - 1, 0, QModelIndex() ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::SelectCurrent ); } } void NativeAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecutePlugin::isExecutableEntry, executableRadio->isChecked() ); cfg.writeEntry( ExecutePlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecutePlugin::projectTargetEntry, projectTarget->currentItemPath() ); cfg.writeEntry( ExecutePlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecutePlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecutePlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecutePlugin::useTerminalEntry, runInTerminal->isChecked() ); cfg.writeEntry( ExecutePlugin::terminalEntry, terminal->currentText() ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, dependencyAction->itemData( dependencyAction->currentIndex() ).toString() ); QVariantList deps; for( int i = 0; i < dependencies->count(); i++ ) { deps << dependencies->item( i )->data( Qt::UserRole ); } cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariant( deps ) ) ); } QString NativeAppConfigPage::title() const { return i18n("Configure Native Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > NativeAppLauncher::configPages() const { return QList(); } QString NativeAppLauncher::description() const { return "Executes Native Applications"; } QString NativeAppLauncher::id() { return "nativeAppLauncher"; } QString NativeAppLauncher::name() const { return i18n("Native Application"); } NativeAppLauncher::NativeAppLauncher() { } KJob* NativeAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } if( launchMode == "execute" ) { IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(iface); KJob* depjob = iface->dependencyJob( cfg ); QList l; if( depjob ) { l << depjob; } l << new NativeAppJob( KDevelop::ICore::self()->runController(), cfg ); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), l ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } QStringList NativeAppLauncher::supportedModes() const { return QStringList() << "execute"; } KDevelop::LaunchConfigurationPage* NativeAppPageFactory::createWidget(QWidget* parent) { return new NativeAppConfigPage( parent ); } NativeAppPageFactory::NativeAppPageFactory() { } NativeAppConfigType::NativeAppConfigType() { factoryList.append( new NativeAppPageFactory() ); } NativeAppConfigType::~NativeAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString NativeAppConfigType::name() const { return i18n("Compiled Binary"); } QList NativeAppConfigType::configPages() const { return factoryList; } QString NativeAppConfigType::id() const { return ExecutePlugin::_nativeAppConfigTypeId; } QIcon NativeAppConfigType::icon() const { return QIcon::fromTheme("application-x-executable"); } bool NativeAppConfigType::canLaunch ( KDevelop::ProjectBaseItem* item ) const { if( item->target() && item->target()->executable() ) { return canLaunch( item->target()->executable()->builtUrl() ); } return false; } bool NativeAppConfigType::canLaunch ( const QUrl& file ) const { return ( file.isLocalFile() && QFileInfo( file.toLocalFile() ).isExecutable() ); } void NativeAppConfigType::configureLaunchFromItem ( KConfigGroup cfg, KDevelop::ProjectBaseItem* item ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, false ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); cfg.writeEntry( ExecutePlugin::projectTargetEntry, model->pathFromIndex( model->indexFromItem( item ) ) ); cfg.writeEntry( ExecutePlugin::workingDirEntry, item->executable()->builtUrl().adjusted(QUrl::RemoveFilename) ); cfg.sync(); } void NativeAppConfigType::configureLaunchFromCmdLineArguments ( KConfigGroup cfg, const QStringList& args ) const { cfg.writeEntry( ExecutePlugin::isExecutableEntry, true ); Q_ASSERT(QFile::exists(args.first())); // TODO: we probably want to flexibilize, but at least we won't be accepting wrong values anymore cfg.writeEntry( ExecutePlugin::executableEntry, QUrl::fromLocalFile(args.first()) ); QStringList a(args); a.removeFirst(); cfg.writeEntry( ExecutePlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.sync(); } QList targetsInFolder(KDevelop::ProjectFolderItem* folder) { QList ret; foreach(KDevelop::ProjectFolderItem* f, folder->folderList()) ret += targetsInFolder(f); ret += folder->targetList(); return ret; } bool actionLess(QAction* a, QAction* b) { return a->text() < b->text(); } bool menuLess(QMenu* a, QMenu* b) { return a->title() < b->title(); } QMenu* NativeAppConfigType::launcherSuggestions() { QMenu* ret = new QMenu(i18n("Project Executables")); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* project, projects) { if(project->projectFileManager()->features() & KDevelop::IProjectFileManager::Targets) { QList targets=targetsInFolder(project->projectItem()); QHash > targetsContainer; QMenu* projectMenu = ret->addMenu(QIcon::fromTheme("project-development"), project->name()); foreach(KDevelop::ProjectTargetItem* target, targets) { if(target->executable()) { QStringList path = model->pathFromIndex(target->index()); if(!path.isEmpty()){ QAction* act = new QAction(projectMenu); act->setData(KDevelop::joinWithEscaping(path, '/','\\')); act->setProperty("name", target->text()); path.removeFirst(); act->setText(path.join("/")); act->setIcon(QIcon::fromTheme("system-run")); connect(act, &QAction::triggered, this, &NativeAppConfigType::suggestionTriggered); targetsContainer[target->parent()].append(act); } } } QList separateActions; QList submenus; foreach(KDevelop::ProjectBaseItem* folder, targetsContainer.keys()) { QList actions = targetsContainer.value(folder); if(actions.size()==1 || !folder->parent()) { separateActions += actions.first(); } else { foreach(QAction* a, actions) { a->setText(a->property("name").toString()); } QStringList path = model->pathFromIndex(folder->index()); path.removeFirst(); QMenu* submenu = new QMenu(path.join("/")); submenu->addActions(actions); submenus += submenu; } } std::sort(separateActions.begin(), separateActions.end(), actionLess); std::sort(submenus.begin(), submenus.end(), menuLess); foreach(QMenu* m, submenus) projectMenu->addMenu(m); projectMenu->addActions(separateActions); projectMenu->setEnabled(!projectMenu->isEmpty()); } } return ret; } void NativeAppConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); KDevelop::ProjectTargetItem* pitem = dynamic_cast(itemForPath(KDevelop::splitWithEscaping(action->data().toString(),'/', '\\'), model)); if(pitem) { QPair launcher = qMakePair( launchers().at( 0 )->supportedModes().at(0), launchers().at( 0 )->id() ); KDevelop::IProject* p = pitem->project(); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, pitem->text()); KConfigGroup cfg = config->config(); QStringList splitPath = model->pathFromIndex(pitem->index()); // QString path = KDevelop::joinWithEscaping(splitPath,'/','\\'); cfg.writeEntry( ExecutePlugin::projectTargetEntry, splitPath ); cfg.writeEntry( ExecutePlugin::dependencyEntry, KDevelop::qvariantToString( QVariantList() << splitPath ) ); cfg.writeEntry( ExecutePlugin::dependencyActionEntry, "Build" ); cfg.sync(); emit signalAddLaunchConfiguration(config); } } diff --git a/plugins/execute/nativeappjob.cpp b/plugins/execute/nativeappjob.cpp index 9dc27b2124..f8e2ba0207 100644 --- a/plugins/execute/nativeappjob.cpp +++ b/plugins/execute/nativeappjob.cpp @@ -1,222 +1,219 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nativeappjob.h" -#include #include +#include -#include #include -#include -#include -#include +#include +#include +#include #include #include #include #include #include -#include #include #include #include #include #include "iexecuteplugin.h" #include "debug.h" -#include using namespace KDevelop; NativeAppJob::NativeAppJob(QObject* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(0) { setCapabilities(Killable); IExecutePlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecutePlugin", "kdevexecute")->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; QUrl executable = iface->executable( cfg, err ); if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } proc = new KProcess( this ); lineMaker = new KDevelop::ProcessLineMaker( proc, this ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); OutputModel* m = new OutputModel; m->setFilteringStrategy(OutputModel::NativeAppErrorFilter); setModel(m); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &NativeAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &NativeAppJob::processFinished ); // Now setup the process parameters proc->setEnvironment( l.createEnvironment( envgrp, proc->systemEnvironment()) ); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( executable.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", executable ); qCDebug(PLUGIN_EXECUTE) << "setting app:" << executable << arguments; proc->setOutputChannelMode(KProcess::MergedChannels); if (iface->useTerminal(cfg)) { QStringList args = KShell::splitArgs(iface->terminal(cfg)); for (QStringList::iterator it = args.begin(); it != args.end(); ++it) { if (*it == "%exe") { *it = KShell::quoteArg(executable.toLocalFile()); } else if (*it == "%workdir") { *it = KShell::quoteArg(wc.toLocalFile()); } } args.append( arguments ); proc->setProgram( args ); } else { proc->setProgram( executable.toLocalFile(), arguments ); } setObjectName(cfg->name()); } void NativeAppJob::start() { qCDebug(PLUGIN_EXECUTE) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(" ") ) ); proc->start(); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool NativeAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void NativeAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { if (!model()) { outputDone(); return; } connect(model(), &OutputModel::allDone, this, &NativeAppJob::outputDone); lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } model()->ensureAllDone(); } void NativeAppJob::outputDone() { emitResult(); } void NativeAppJob::processError( QProcess::ProcessError error ) { if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(" ") ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTE) << "Process error"; } void NativeAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* NativeAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/executescript/executescriptplugin.cpp b/plugins/executescript/executescriptplugin.cpp index 66d83ba41a..342c00e030 100644 --- a/plugins/executescript/executescriptplugin.cpp +++ b/plugins/executescript/executescriptplugin.cpp @@ -1,271 +1,263 @@ /* * This file is part of KDevelop * * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "executescriptplugin.h" -#include - -#include +#include #include -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include #include #include #include - #include "scriptappconfig.h" #include "debug.h" #include #include -#include #include QString ExecuteScriptPlugin::_scriptAppConfigTypeId = "Script Application"; QString ExecuteScriptPlugin::interpreterEntry = "Interpreter"; QString ExecuteScriptPlugin::workingDirEntry = "Working Directory"; QString ExecuteScriptPlugin::executableEntry = "Executable"; QString ExecuteScriptPlugin::executeOnRemoteHostEntry = "Execute on Remote Host"; QString ExecuteScriptPlugin::runCurrentFileEntry = "Run current file"; QString ExecuteScriptPlugin::remoteHostEntry = "Remote Host"; QString ExecuteScriptPlugin::argumentsEntry = "Arguments"; QString ExecuteScriptPlugin::isExecutableEntry = "isExecutable"; QString ExecuteScriptPlugin::environmentGroupEntry = "EnvironmentGroup"; //QString ExecuteScriptPlugin::useTerminalEntry = "Use External Terminal"; QString ExecuteScriptPlugin::userIdToRunEntry = "User Id to Run"; QString ExecuteScriptPlugin::projectTargetEntry = "Project Target"; QString ExecuteScriptPlugin::outputFilteringEntry = "Output Filtering Mode"; using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_EXECUTESCRIPT, "kdevplatform.plugins.executescript") K_PLUGIN_FACTORY_WITH_JSON(KDevExecuteFactory, "kdevexecutescript.json", registerPlugin();) ExecuteScriptPlugin::ExecuteScriptPlugin(QObject *parent, const QVariantList&) : KDevelop::IPlugin("kdevexecutescript", parent) { KDEV_USE_EXTENSION_INTERFACE( IExecuteScriptPlugin ) m_configType = new ScriptAppConfigType(); m_configType->addLauncher( new ScriptAppLauncher( this ) ); qCDebug(PLUGIN_EXECUTESCRIPT) << "adding script launch config"; core()->runController()->addConfigurationType( m_configType ); } ExecuteScriptPlugin::~ExecuteScriptPlugin() { } void ExecuteScriptPlugin::unload() { core()->runController()->removeConfigurationType( m_configType ); delete m_configType; m_configType = 0; } QUrl ExecuteScriptPlugin::script( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { QUrl script; if( !cfg ) { return script; } KConfigGroup grp = cfg->config(); script = grp.readEntry( ExecuteScriptPlugin::executableEntry, QUrl() ); if( !script.isLocalFile() || script.isEmpty() ) { err_ = i18n("No valid executable specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid script set"; } else { KShell::Errors err; if( KShell::splitArgs( script.toLocalFile(), KShell::TildeExpand | KShell::AbortOnMeta, &err ).isEmpty() || err != KShell::NoError ) { script = QUrl(); if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the script " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "script for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "script has meta characters"; } } return script; } QString ExecuteScriptPlugin::remoteHost(ILaunchConfiguration* cfg, QString& err) const { if (!cfg) return QString(); KConfigGroup grp = cfg->config(); if(grp.readEntry(ExecuteScriptPlugin::executeOnRemoteHostEntry, false)) { QString host = grp.readEntry(ExecuteScriptPlugin::remoteHostEntry, ""); if (host.isEmpty()) { err = i18n("No remote host set for launch configuration '%1'. " "Aborting start.", cfg->name() ); qWarning() << "Launch Configuration:" << cfg->name() << "no remote host set"; } return host; } return QString(); } QStringList ExecuteScriptPlugin::arguments( KDevelop::ILaunchConfiguration* cfg, QString& err_ ) const { if( !cfg ) { return QStringList(); } KShell::Errors err; QStringList args = KShell::splitArgs( cfg->config().readEntry( ExecuteScriptPlugin::argumentsEntry, "" ), KShell::TildeExpand | KShell::AbortOnMeta, &err ); if( err != KShell::NoError ) { if( err == KShell::BadQuoting ) { err_ = i18n("There is a quoting error in the arguments for " "the launch configuration '%1'. Aborting start.", cfg->name() ); } else { err_ = i18n("A shell meta character was included in the " "arguments for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } args = QStringList(); qWarning() << "Launch Configuration:" << cfg->name() << "arguments have meta characters"; } return args; } QString ExecuteScriptPlugin::environmentGroup( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return ""; } return cfg->config().readEntry( ExecuteScriptPlugin::environmentGroupEntry, "" ); } int ExecuteScriptPlugin::outputFilterModeId( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return 0; } return cfg->config().readEntry( ExecuteScriptPlugin::outputFilteringEntry, 0 ); } bool ExecuteScriptPlugin::runCurrentFile(ILaunchConfiguration* cfg) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); } QString ExecuteScriptPlugin::interpreter( KDevelop::ILaunchConfiguration* cfg, QString& err ) const { QString interpreter; if( !cfg ) { return interpreter; } KConfigGroup grp = cfg->config(); interpreter = grp.readEntry( ExecuteScriptPlugin::interpreterEntry, QString() ); if( interpreter.isEmpty() ) { err = i18n("No valid interpreter specified"); qWarning() << "Launch Configuration:" << cfg->name() << "no valid interpreter set"; } else { KShell::Errors err_; if( KShell::splitArgs( interpreter, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ).isEmpty() || err_ != KShell::NoError ) { interpreter.clear(); if( err_ == KShell::BadQuoting ) { err = i18n("There is a quoting error in the interpreter " "for the launch configuration '%1'. " "Aborting start.", cfg->name() ); } else { err = i18n("A shell meta character was included in the " "interpreter for the launch configuration '%1', " "this is not supported currently. Aborting start.", cfg->name() ); } qWarning() << "Launch Configuration:" << cfg->name() << "interpreter has meta characters"; } } return interpreter; } /* bool ExecuteScriptPlugin::useTerminal( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return false; } return cfg->config().readEntry( ExecuteScriptPlugin::useTerminalEntry, false ); } */ QUrl ExecuteScriptPlugin::workingDirectory( KDevelop::ILaunchConfiguration* cfg ) const { if( !cfg ) { return QUrl(); } return cfg->config().readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ); } QString ExecuteScriptPlugin::scriptAppConfigTypeId() const { return _scriptAppConfigTypeId; } #include "executescriptplugin.moc" diff --git a/plugins/executescript/scriptappconfig.cpp b/plugins/executescript/scriptappconfig.cpp index 947cc464a4..6a4147fbdf 100644 --- a/plugins/executescript/scriptappconfig.cpp +++ b/plugins/executescript/scriptappconfig.cpp @@ -1,257 +1,253 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappconfig.h" +#include #include -#include +#include #include #include -#include #include #include #include #include #include "scriptappjob.h" #include #include #include #include -#include #include #include -#include -#include -#include #include #include #include "executescriptplugin.h" #include #include #include using namespace KDevelop; static const QString interpreterForUrl(const QUrl& url) { auto mimetype = QMimeDatabase().mimeTypeForUrl(url); static QHash knownMimetypes; if ( knownMimetypes.isEmpty() ) { knownMimetypes["text/x-python"] = "python"; knownMimetypes["application/x-php"] = "php"; knownMimetypes["application/x-ruby"] = "ruby"; knownMimetypes["application/x-shellscript"] = "bash"; knownMimetypes["application/x-perl"] = "perl -e"; } const QString& interp = knownMimetypes.value(mimetype.name()); return interp; } QIcon ScriptAppConfigPage::icon() const { return QIcon::fromTheme("system-run"); } void ScriptAppConfigPage::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* project ) { bool b = blockSignals( true ); if( project ) { executablePath->setStartDir( project->folder() ); } auto doc = KDevelop::ICore::self()->documentController()->activeDocument(); interpreter->lineEdit()->setText( cfg.readEntry( ExecuteScriptPlugin::interpreterEntry, doc ? interpreterForUrl(doc->url()) : "" ) ); executablePath->setUrl( QUrl::fromLocalFile(cfg.readEntry( ExecuteScriptPlugin::executableEntry, QString() )) ); remoteHostCheckbox->setChecked( cfg.readEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, false ) ); remoteHost->setText( cfg.readEntry( ExecuteScriptPlugin::remoteHostEntry, "" ) ); bool runCurrent = cfg.readEntry( ExecuteScriptPlugin::runCurrentFileEntry, true ); if ( runCurrent ) { runCurrentFile->setChecked( true ); } else { runFixedFile->setChecked( true ); } arguments->setText( cfg.readEntry( ExecuteScriptPlugin::argumentsEntry, "" ) ); workingDirectory->setUrl( cfg.readEntry( ExecuteScriptPlugin::workingDirEntry, QUrl() ) ); environment->setCurrentProfile( cfg.readEntry( ExecuteScriptPlugin::environmentGroupEntry, QString() ) ); outputFilteringMode->setCurrentIndex( cfg.readEntry( ExecuteScriptPlugin::outputFilteringEntry, 2u )); //runInTerminal->setChecked( cfg.readEntry( ExecuteScriptPlugin::useTerminalEntry, false ) ); blockSignals( b ); } ScriptAppConfigPage::ScriptAppConfigPage( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); interpreter->lineEdit()->setPlaceholderText(i18n("Type or select an interpreter")); //Set workingdirectory widget to ask for directories rather than files workingDirectory->setMode(KFile::Directory | KFile::ExistingOnly | KFile::LocalOnly); //connect signals to changed signal connect( interpreter->lineEdit(), &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( executablePath, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( arguments, &QLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( workingDirectory, &KUrlRequester::urlSelected, this, &ScriptAppConfigPage::changed ); connect( workingDirectory->lineEdit(), &KLineEdit::textEdited, this, &ScriptAppConfigPage::changed ); connect( environment, &EnvironmentSelectionWidget::currentProfileChanged, this, &ScriptAppConfigPage::changed ); //connect( runInTerminal, SIGNAL(toggled(bool)), SIGNAL(changed()) ); } void ScriptAppConfigPage::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, interpreter->lineEdit()->text() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, executablePath->url() ); cfg.writeEntry( ExecuteScriptPlugin::executeOnRemoteHostEntry, remoteHostCheckbox->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::remoteHostEntry, remoteHost->text() ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, runCurrentFile->isChecked() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, arguments->text() ); cfg.writeEntry( ExecuteScriptPlugin::workingDirEntry, workingDirectory->url() ); cfg.writeEntry( ExecuteScriptPlugin::environmentGroupEntry, environment->currentProfile() ); cfg.writeEntry( ExecuteScriptPlugin::outputFilteringEntry, outputFilteringMode->currentIndex() ); //cfg.writeEntry( ExecuteScriptPlugin::useTerminalEntry, runInTerminal->isChecked() ); } QString ScriptAppConfigPage::title() const { return i18n("Configure Script Application"); } QList< KDevelop::LaunchConfigurationPageFactory* > ScriptAppLauncher::configPages() const { return QList(); } QString ScriptAppLauncher::description() const { return i18n("Executes Script Applications"); } QString ScriptAppLauncher::id() { return "scriptAppLauncher"; } QString ScriptAppLauncher::name() const { return i18n("Script Application"); } ScriptAppLauncher::ScriptAppLauncher(ExecuteScriptPlugin* plugin) : m_plugin( plugin ) { } KJob* ScriptAppLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } if( launchMode == "execute" ) { return new ScriptAppJob( m_plugin, cfg); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } QStringList ScriptAppLauncher::supportedModes() const { return QStringList() << "execute"; } KDevelop::LaunchConfigurationPage* ScriptAppPageFactory::createWidget(QWidget* parent) { return new ScriptAppConfigPage( parent ); } ScriptAppPageFactory::ScriptAppPageFactory() { } ScriptAppConfigType::ScriptAppConfigType() { factoryList.append( new ScriptAppPageFactory() ); } ScriptAppConfigType::~ScriptAppConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString ScriptAppConfigType::name() const { return i18n("Script Application"); } QList ScriptAppConfigType::configPages() const { return factoryList; } QString ScriptAppConfigType::id() const { return ExecuteScriptPlugin::_scriptAppConfigTypeId; } QIcon ScriptAppConfigType::icon() const { return QIcon::fromTheme("preferences-plugin-script"); } bool ScriptAppConfigType::canLaunch(const QUrl& file) const { return ! interpreterForUrl(file).isEmpty(); } bool ScriptAppConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { return ! interpreterForUrl(item->path().toUrl()).isEmpty(); } void ScriptAppConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry(ExecuteScriptPlugin::executableEntry, item->path().toUrl()); config.writeEntry(ExecuteScriptPlugin::interpreterEntry, interpreterForUrl(item->path().toUrl())); config.writeEntry(ExecuteScriptPlugin::outputFilteringEntry, 2u); config.writeEntry(ExecuteScriptPlugin::runCurrentFileEntry, false); config.sync(); } void ScriptAppConfigType::configureLaunchFromCmdLineArguments(KConfigGroup cfg, const QStringList &args) const { QStringList a(args); cfg.writeEntry( ExecuteScriptPlugin::interpreterEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::executableEntry, a.takeFirst() ); cfg.writeEntry( ExecuteScriptPlugin::argumentsEntry, KShell::joinArgs(a) ); cfg.writeEntry( ExecuteScriptPlugin::runCurrentFileEntry, false ); cfg.sync(); } diff --git a/plugins/executescript/scriptappjob.cpp b/plugins/executescript/scriptappjob.cpp index cbf9d75482..c78621cb5a 100644 --- a/plugins/executescript/scriptappjob.cpp +++ b/plugins/executescript/scriptappjob.cpp @@ -1,259 +1,256 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "scriptappjob.h" #include "executescriptplugin.h" -#include #include +#include -#include #include -#include -#include -#include -#include +#include +#include +#include #include #include #include #include #include -#include #include #include #include #include #include #include "iexecutescriptplugin.h" #include "debug.h" using namespace KDevelop; ScriptAppJob::ScriptAppJob(ExecuteScriptPlugin* parent, KDevelop::ILaunchConfiguration* cfg) : KDevelop::OutputJob( parent ), proc(0) { qCDebug(PLUGIN_EXECUTESCRIPT) << "creating script app job"; setCapabilities(Killable); IExecuteScriptPlugin* iface = KDevelop::ICore::self()->pluginController()->pluginForExtension("org.kdevelop.IExecuteScriptPlugin")->extension(); Q_ASSERT(iface); KDevelop::EnvironmentGroupList l(KSharedConfig::openConfig()); QString envgrp = iface->environmentGroup(cfg); QString err; QString interpreterString = iface->interpreter( cfg, err ); // check for errors happens in the executescript plugin already KShell::Errors err_; QStringList interpreter = KShell::splitArgs( interpreterString, KShell::TildeExpand | KShell::AbortOnMeta, &err_ ); if ( interpreter.isEmpty() ) { // This should not happen, because of the checks done in the executescript plugin qWarning() << "no interpreter specified"; return; } if( !err.isEmpty() ) { setError( -1 ); setErrorText( err ); return; } QUrl script; if( !iface->runCurrentFile( cfg ) ) { script = iface->script( cfg, err ); } else { KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument(); if( !document ) { setError( -1 ); setErrorText( i18n( "There is no active document to launch." ) ); return; } script = document->url(); } if( !err.isEmpty() ) { setError( -3 ); setErrorText( err ); return; } QString remoteHost = iface->remoteHost( cfg, err ); if( !err.isEmpty() ) { setError( -4 ); setErrorText( err ); return; } if( envgrp.isEmpty() ) { qWarning() << "Launch Configuration:" << cfg->name() << i18n("No environment group specified, looks like a broken " "configuration, please check run configuration '%1'. " "Using default environment group.", cfg->name() ); envgrp = l.defaultGroup(); } QStringList arguments = iface->arguments( cfg, err ); if( !err.isEmpty() ) { setError( -2 ); setErrorText( err ); } if( error() != 0 ) { qWarning() << "Launch Configuration:" << cfg->name() << "oops, problem" << errorText(); return; } KDevelop::OutputModel::OutputFilterStrategy currentFilterMode = static_cast( iface->outputFilterModeId( cfg ) ); proc = new KProcess( this ); lineMaker = new KDevelop::ProcessLineMaker( proc, this ); setStandardToolView(KDevelop::IOutputView::RunView); setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); KDevelop::OutputModel* m = new KDevelop::OutputModel; m->setFilteringStrategy(currentFilterMode); setModel( m ); setDelegate( new KDevelop::OutputDelegate ); connect( lineMaker, &ProcessLineMaker::receivedStdoutLines, model(), &OutputModel::appendLines ); connect( proc, static_cast(&KProcess::error), this, &ScriptAppJob::processError ); connect( proc, static_cast(&KProcess::finished), this, &ScriptAppJob::processFinished ); // Now setup the process parameters proc->setEnvironment( l.createEnvironment( envgrp, proc->systemEnvironment()) ); QUrl wc = iface->workingDirectory( cfg ); if( !wc.isValid() || wc.isEmpty() ) { wc = QUrl::fromLocalFile( QFileInfo( script.toLocalFile() ).absolutePath() ); } proc->setWorkingDirectory( wc.toLocalFile() ); proc->setProperty( "executable", interpreter.first() ); QStringList program; if (!remoteHost.isEmpty()) { program << "ssh"; QStringList parts = remoteHost.split(":"); program << parts.first(); if (parts.length() > 1) { program << "-p "+parts.at(1); } } program << interpreter; program << script.toLocalFile(); program << arguments; qCDebug(PLUGIN_EXECUTESCRIPT) << "setting app:" << program; proc->setOutputChannelMode(KProcess::MergedChannels); proc->setProgram( program ); setTitle(cfg->name()); } void ScriptAppJob::start() { qCDebug(PLUGIN_EXECUTESCRIPT) << "launching?" << proc; if( proc ) { startOutput(); appendLine( i18n("Starting: %1", proc->program().join(" ") ) ); proc->start(); } else { qWarning() << "No process, something went wrong when creating the job"; // No process means we've returned early on from the constructor, some bad error happened emitResult(); } } bool ScriptAppJob::doKill() { if( proc ) { proc->kill(); appendLine( i18n( "*** Killed Application ***" ) ); } return true; } void ScriptAppJob::processFinished( int exitCode , QProcess::ExitStatus status ) { lineMaker->flushBuffers(); if (exitCode == 0 && status == QProcess::NormalExit) { appendLine( i18n("*** Exited normally ***") ); } else if (status == QProcess::NormalExit) { appendLine( i18n("*** Exited with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } else if (error() == KJob::KilledJobError) { appendLine( i18n("*** Process aborted ***") ); setError(KJob::KilledJobError); } else { appendLine( i18n("*** Crashed with return code: %1 ***", QString::number(exitCode)) ); setError(OutputJob::FailedShownError); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process done"; emitResult(); } void ScriptAppJob::processError( QProcess::ProcessError error ) { qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardError(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->readAllStandardOutput(); qCDebug(PLUGIN_EXECUTESCRIPT) << proc->errorString(); if( error == QProcess::FailedToStart ) { setError( FailedShownError ); QString errmsg = i18n("*** Could not start program '%1'. Make sure that the " "path is specified correctly ***", proc->program().join(" ") ); appendLine( errmsg ); setErrorText( errmsg ); emitResult(); } qCDebug(PLUGIN_EXECUTESCRIPT) << "Process error"; } void ScriptAppJob::appendLine(const QString& l) { if (KDevelop::OutputModel* m = model()) { m->appendLine(l); } } KDevelop::OutputModel* ScriptAppJob::model() { return dynamic_cast( OutputJob::model() ); } diff --git a/plugins/externalscript/editexternalscript.cpp b/plugins/externalscript/editexternalscript.cpp index 6c52b06636..b8c0b34338 100644 --- a/plugins/externalscript/editexternalscript.cpp +++ b/plugins/externalscript/editexternalscript.cpp @@ -1,198 +1,195 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "editexternalscript.h" #include "externalscriptitem.h" -#include +#include #include -#include -#include -#include -#include -#include +#include +#include EditExternalScript::EditExternalScript( ExternalScriptItem* item, QWidget* parent, Qt::WindowFlags flags ) : QDialog( parent, flags ), m_item( item ) { setupUi(this); connect(buttonBox, &QDialogButtonBox::accepted, this, &EditExternalScript::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &EditExternalScript::reject); shortcutWidget->layout()->setMargin(0); //BEGIN setup tooltips QString tooltip = i18n( "

Defines the command that should be executed when this script is run. Basic shell features of your platform should be available.

\n" "

There are a few placeholders you can use in the command:

\n" "
\n" "
%u
\n" "
Gets replaced by the URL of the active document.
\n" "
%f
\n" "
Gets replaced by the local filepath to the active document.
\n" "
%n
\n" "
Gets replaced by the name of the active document, including its extension.
\n" "
%b
\n" "
Gets replaced by the name of the active document without its extension.
\n" "
%d
\n" "
Gets replaced by the path to the directory of the active document.
\n" "
%p
\n" "
Gets replaced by the URL to the project of the active document.
\n" "
%s
\n" "
Gets replaced with the shell escaped contents of the selection in the active document.
\n" "
%i
\n" "
Gets replaced with the PID of the currently running KDevelop process.
\n" "
\n" "

NOTE: It is your responsibility to prevent running hazardous commands that could lead to data loss.

\n" ); commandEdit->setToolTip( tooltip ); commandLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what the external script should get as input (via STDIN).

" ); stdinCombo->setToolTip( tooltip ); stdinLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what should be done with the output (i.e. STDOUT) of the script.

" ); stdoutCombo->setToolTip( tooltip ); stdoutLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines what should be done with the errors (i.e. STDERR) of the script.

" "

Note: if the action is the same as that chosen for the output, the channels will be merged " "and handled together.

" ); stderrCombo->setToolTip( tooltip ); stderrLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines the name of the script. Just for displaying purposes.

" ); nameEdit->setToolTip( tooltip ); nameLabel->setToolTip( tooltip ); tooltip = i18n( "

Defines the shortcut(s) you can use to execute this external script.

" ); shortcutLabel->setToolTip( tooltip ); shortcutWidget->setToolTip( tooltip ); tooltip = i18n( "

Defines whether documents should be saved before the script gets executed.

" ); saveLabel->setToolTip( tooltip ); saveCombo->setToolTip( tooltip ); tooltip = i18n( "

Defines whether the output of the script should be shown in a toolview.

" ); showOutputBox->setToolTip( tooltip ); tooltip = i18n( "

Defines what type of filtering should be applied to the output. E.g. to indicate errors by red text.

" ); outputFilterLabel->setToolTip( tooltip ); outputFilterCombo->setToolTip( tooltip ); //END setup tooltips //BEGIN item to UI copying if ( item->text().isEmpty() ) { setWindowTitle( i18n("Create new external script") ); } else { setWindowTitle( i18n("Edit external script '%1'", item->text()) ); } nameEdit->setText( item->text() ); commandEdit->setText( item->command() ); stdinCombo->setCurrentIndex( item->inputMode() ); stdoutCombo->setCurrentIndex( item->outputMode() ); stderrCombo->setCurrentIndex( item->errorMode() ); saveCombo->setCurrentIndex( item->saveMode() ); shortcutWidget->setShortcut( item->action()->shortcuts() ); showOutputBox->setChecked( item->showOutput() ); outputFilterCombo->setCurrentIndex( item->filterMode() ); //END item to UI copying validate(); nameEdit->setFocus(); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, &EditExternalScript::save); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, &EditExternalScript::save); connect(nameEdit, &QLineEdit::textEdited, this, &EditExternalScript::validate); connect(commandEdit, &QLineEdit::textEdited, this, &EditExternalScript::validate); } EditExternalScript::~EditExternalScript() { } void EditExternalScript::save() { m_item->setText( nameEdit->text() ); m_item->setCommand( commandEdit->text() ); ExternalScriptItem::InputMode inputMode = static_cast( stdinCombo->currentIndex() ); m_item->setInputMode( inputMode ); ExternalScriptItem::OutputMode outputMode = static_cast( stdoutCombo->currentIndex() ); m_item->setOutputMode( outputMode ); ExternalScriptItem::ErrorMode errorMode = static_cast( stderrCombo->currentIndex() ); m_item->setErrorMode( errorMode ); ExternalScriptItem::SaveMode saveMode = static_cast( saveCombo->currentIndex() ); m_item->setSaveMode( saveMode ); m_item->setShowOutput( showOutputBox->isChecked() ); m_item->setFilterMode( outputFilterCombo->currentIndex() ); m_item->action()->setShortcuts( shortcutWidget->shortcut() ); } void EditExternalScript::validate() { bool valid = !nameEdit->text().isEmpty() && !commandEdit->text().isEmpty(); if ( valid ) { KShell::Errors errors = KShell::NoError; KShell::splitArgs( commandEdit->text(), KShell::TildeExpand, &errors ); valid = errors == KShell::NoError; } buttonBox->button(QDialogButtonBox::Ok)->setEnabled(valid); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(valid); } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/externalscript/externalscriptview.cpp b/plugins/externalscript/externalscriptview.cpp index 5fe989cc84..917d56e05d 100644 --- a/plugins/externalscript/externalscriptview.cpp +++ b/plugins/externalscript/externalscriptview.cpp @@ -1,178 +1,177 @@ /* This plugin is part of KDevelop. Copyright (C) 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "externalscriptview.h" #include "externalscriptplugin.h" #include "externalscriptitem.h" #include "editexternalscript.h" -#include - #include -#include -#include +#include #include +#include + +#include #include -#include -#include + ExternalScriptView::ExternalScriptView( ExternalScriptPlugin* plugin, QWidget* parent ) : QWidget( parent ), m_plugin( plugin ) { Ui::ExternalScriptViewBase::setupUi( this ); setWindowTitle( i18n( "External Scripts" ) ); m_model = new QSortFilterProxyModel( this ); m_model->setSourceModel( m_plugin->model() ); m_model->setDynamicSortFilter( true ); m_model->sort( 0 ); connect( filterText, &QLineEdit::textEdited, m_model, &QSortFilterProxyModel::setFilterWildcard ); scriptTree->setModel( m_model ); scriptTree->setContextMenuPolicy( Qt::CustomContextMenu ); scriptTree->viewport()->installEventFilter( this ); scriptTree->header()->hide(); connect(scriptTree, &QTreeView::customContextMenuRequested, this, &ExternalScriptView::contextMenu); m_addScriptAction = new QAction(QIcon::fromTheme("document-new"), i18n("Add External Script"), this); connect(m_addScriptAction, &QAction::triggered, this, &ExternalScriptView::addScript); addAction(m_addScriptAction); m_editScriptAction = new QAction(QIcon::fromTheme("document-edit"), i18n("Edit External Script"), this); connect(m_editScriptAction, &QAction::triggered, this, &ExternalScriptView::editScript); addAction(m_editScriptAction); m_removeScriptAction = new QAction(QIcon::fromTheme("document-close"), i18n("Remove External Script"), this); connect(m_removeScriptAction, &QAction::triggered, this, &ExternalScriptView::removeScript); addAction(m_removeScriptAction); connect(scriptTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ExternalScriptView::validateActions); validateActions(); } ExternalScriptView::~ExternalScriptView() { } ExternalScriptItem* ExternalScriptView::currentItem() const { return itemForIndex( scriptTree->currentIndex() ); } ExternalScriptItem* ExternalScriptView::itemForIndex( const QModelIndex& index ) const { if ( !index.isValid() ) { return 0; } const QModelIndex mappedIndex = m_model->mapToSource( index ); return static_cast( m_plugin->model()->itemFromIndex( mappedIndex ) ); } void ExternalScriptView::validateActions() { bool itemSelected = currentItem(); m_removeScriptAction->setEnabled( itemSelected ); m_editScriptAction->setEnabled( itemSelected ); } void ExternalScriptView::contextMenu( const QPoint& pos ) { QMenu menu; menu.addActions( actions() ); menu.exec( scriptTree->mapToGlobal( pos ) ); } bool ExternalScriptView::eventFilter( QObject* obj, QEvent* e ) { // no, listening to activated() is not enough since that would also trigger the edit mode which we _dont_ want here // users may still rename stuff via select + F2 though if ( obj == scriptTree->viewport() ) { // const bool singleClick = KGlobalSettings::singleClick(); const bool singleClick = true; //FIXME: enable singleClick for the sake of porting, should find a proper way if ( ( !singleClick && e->type() == QEvent::MouseButtonDblClick ) || ( singleClick && e->type() == QEvent::MouseButtonRelease ) ) { QMouseEvent* mouseEvent = dynamic_cast(e); Q_ASSERT( mouseEvent ); ExternalScriptItem* item = itemForIndex( scriptTree->indexAt( mouseEvent->pos() ) ); if ( item ) { m_plugin->execute( item ); e->accept(); return true; } } } return QObject::eventFilter( obj, e ); } void ExternalScriptView::addScript() { ExternalScriptItem* item = new ExternalScriptItem; EditExternalScript dlg( item, this ); int ret = dlg.exec(); if ( ret == QDialog::Accepted) { m_plugin->model()->appendRow( item ); } else { delete item; } } void ExternalScriptView::removeScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } - int ret = KMessageBox::questionYesNo( this, + int ret = KMessageBox::questionYesNo( this, i18n("

Do you really want to remove the external script configuration for %1?

" "

Note: The script itself will not be removed.

", item->text()), i18n("Confirm External Script Removal") ); if ( ret == KMessageBox::Yes ) { m_plugin->model()->removeRow( m_plugin->model()->indexFromItem( item ).row() ); } } void ExternalScriptView::editScript() { ExternalScriptItem* item = currentItem(); if ( !item ) { return; } EditExternalScript dlg( item, this ); int ret = dlg.exec(); if (ret == QDialog::Accepted) { item->save(); } } // kate: indent-mode cstyle; space-indent on; indent-width 2; replace-tabs on; diff --git a/plugins/filemanager/bookmarkhandler.cpp b/plugins/filemanager/bookmarkhandler.cpp index ad0cf316df..de749c6872 100644 --- a/plugins/filemanager/bookmarkhandler.cpp +++ b/plugins/filemanager/bookmarkhandler.cpp @@ -1,75 +1,74 @@ /* This file is part of the KDE project Copyright (C) xxxx KFile Authors Copyright (C) 2002 Anders Lund Copyright (C) 2009 Dominik Haumann Copyright (C) 2007 Mirko Stocker Copyright (C) 2012 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "bookmarkhandler.h" #include "filemanager.h" #include "kdevfilemanagerplugin.h" #include "debug.h" #include #include -#include #include BookmarkHandler::BookmarkHandler( FileManager *parent, QMenu* kpopupmenu ) : QObject( parent ), KBookmarkOwner(), m_parent( parent ), m_menu( kpopupmenu ) { setObjectName( "BookmarkHandler" ); QUrl bookmarksPath = KDevelop::ICore::self()->activeSession()->pluginDataArea(parent->plugin()); bookmarksPath.setPath(bookmarksPath.path() + "fsbookmarks.xml"); qCDebug(PLUGIN_FILEMANAGER) << bookmarksPath; KBookmarkManager *manager = KBookmarkManager::managerForFile( bookmarksPath.toLocalFile(), "kdevplatform" ); manager->setUpdate( true ); m_bookmarkMenu = new KBookmarkMenu( manager, this, m_menu, parent->actionCollection() ); //remove shortcuts as they might conflict with others (eg. Ctrl+B) foreach (QAction *action, parent->actionCollection()->actions()) { action->setShortcut(QKeySequence()); } } BookmarkHandler::~BookmarkHandler() { delete m_bookmarkMenu; } QUrl BookmarkHandler::currentUrl() const { return m_parent->dirOperator()->url(); } QString BookmarkHandler::currentTitle() const { return currentUrl().toDisplayString(); } void BookmarkHandler::openBookmark( const KBookmark & bm, Qt::MouseButtons, Qt::KeyboardModifiers ) { emit openUrl(bm.url()); } diff --git a/plugins/filemanager/bookmarkhandler.h b/plugins/filemanager/bookmarkhandler.h index b05e5e6c91..d31e2d7abb 100644 --- a/plugins/filemanager/bookmarkhandler.h +++ b/plugins/filemanager/bookmarkhandler.h @@ -1,61 +1,61 @@ /* This file is part of the KDE project Copyright (C) xxxx KFile Authors Copyright (C) 2002 Anders Lund Copyright (C) 2007 Mirko Stocker Copyright (C) 2009 Dominik Haumann Copyright (C) 2012 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_BOOKMARKHANDLER_H #define KDEVPLATFORM_PLUGIN_BOOKMARKHANDLER_H -#include -#include +#include +#include #include class FileManager; class BookmarkHandler : public QObject, public KBookmarkOwner { Q_OBJECT public: explicit BookmarkHandler( FileManager *parent, QMenu *kpopupmenu = 0 ); ~BookmarkHandler(); // KBookmarkOwner interface: virtual QUrl currentUrl() const override; virtual QString currentTitle() const override; QMenu *menu() const { return m_menu; } virtual void openBookmark( const KBookmark &, Qt::MouseButtons, Qt::KeyboardModifiers ) override; Q_SIGNALS: void openUrl( const QUrl& url ); private: FileManager *m_parent; QMenu *m_menu; KBookmarkMenu *m_bookmarkMenu; }; #endif // KDEVPLATFORM_PLUGIN_BOOKMARKHANDLER_H diff --git a/plugins/filemanager/kdevfilemanagerplugin.cpp b/plugins/filemanager/kdevfilemanagerplugin.cpp index ba847cc75b..207e342ffa 100644 --- a/plugins/filemanager/kdevfilemanagerplugin.cpp +++ b/plugins/filemanager/kdevfilemanagerplugin.cpp @@ -1,97 +1,94 @@ /*************************************************************************** * Copyright 2006 Alexander Dymo * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "kdevfilemanagerplugin.h" #include #include -#include -#include -#include -#include +#include #include #include #include "filemanager.h" K_PLUGIN_FACTORY_WITH_JSON(KDevFileManagerFactory, "kdevfilemanager.json", registerPlugin();) class KDevFileManagerViewFactory: public KDevelop::IToolViewFactory{ public: KDevFileManagerViewFactory(KDevFileManagerPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) override { Q_UNUSED(parent) return new FileManager(m_plugin,parent); } QList toolBarActions( QWidget* w ) const override { FileManager* m = qobject_cast(w); if( m ) return m->toolBarActions(); return KDevelop::IToolViewFactory::toolBarActions( w ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.FileManagerView"; } virtual bool allowMultiple() const override { return true; } private: KDevFileManagerPlugin *m_plugin; }; KDevFileManagerPlugin::KDevFileManagerPlugin(QObject *parent, const QVariantList &/*args*/) :KDevelop::IPlugin("kdevfilemanager", parent) { setXMLFile("kdevfilemanager.rc"); QMetaObject::invokeMethod(this, "init", Qt::QueuedConnection); } void KDevFileManagerPlugin::init() { m_factory = new KDevFileManagerViewFactory(this); core()->uiController()->addToolView(i18n("Filesystem"), m_factory); } KDevFileManagerPlugin::~KDevFileManagerPlugin() { } void KDevFileManagerPlugin::unload() { core()->uiController()->removeToolView(m_factory); } #include "kdevfilemanagerplugin.moc" diff --git a/plugins/git/gitplugin.cpp b/plugins/git/gitplugin.cpp index a72fbfc411..9c7fd116be 100644 --- a/plugins/git/gitplugin.cpp +++ b/plugins/git/gitplugin.cpp @@ -1,1458 +1,1455 @@ /*************************************************************************** * Copyright 2008 Evgeniy Ivanov * * Copyright 2009 Hugo Parente Lima * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "gitplugin.h" -#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 "gitclonejob.h" #include -#include #include #include "stashmanagerdialog.h" + +#include +#include +#include #include #include -#include #include -#include + #include "gitjob.h" #include "gitmessagehighlighter.h" #include "gitplugincheckinrepositoryjob.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_GIT, "kdevplatform.plugins.git") using namespace KDevelop; QVariant runSynchronously(KDevelop::VcsJob* job) { QVariant ret; if(job->exec() && job->status()==KDevelop::VcsJob::JobSucceeded) { ret = job->fetchResults(); } delete job; return ret; } namespace { QDir dotGitDirectory(const QUrl& dirPath) { const QFileInfo finfo(dirPath.toLocalFile()); QDir dir = finfo.isDir() ? QDir(finfo.filePath()): finfo.absoluteDir(); static const QString gitDir = QStringLiteral(".git"); while (!dir.exists(gitDir) && dir.cdUp()) {} // cdUp, until there is a sub-directory called .git if (dir.isRoot()) { qWarning() << "couldn't find the git root for" << dirPath; } return dir; } /** * Whenever a directory is provided, change it for all the files in it but not inner directories, * that way we make sure we won't get into recursion, */ static QList preventRecursion(const QList& urls) { QList ret; foreach(const QUrl& url, urls) { QDir d(url.toLocalFile()); if(d.exists()) { QStringList entries = d.entryList(QDir::Files | QDir::NoDotAndDotDot); foreach(const QString& entry, entries) { QUrl entryUrl = QUrl::fromLocalFile(d.absoluteFilePath(entry)); ret += entryUrl; } } else ret += url; } return ret; } QString toRevisionName(const KDevelop::VcsRevision& rev, QString currentRevision=QString()) { switch(rev.revisionType()) { case VcsRevision::Special: switch(rev.revisionValue().value()) { case VcsRevision::Head: return "^HEAD"; case VcsRevision::Base: return ""; case VcsRevision::Working: return ""; case VcsRevision::Previous: Q_ASSERT(!currentRevision.isEmpty()); return currentRevision + "^1"; case VcsRevision::Start: return ""; case VcsRevision::UserSpecialType: //Not used Q_ASSERT(false && "i don't know how to do that"); } break; case VcsRevision::GlobalNumber: return rev.revisionValue().toString(); case VcsRevision::Date: case VcsRevision::FileNumber: case VcsRevision::Invalid: case VcsRevision::UserSpecialType: Q_ASSERT(false); } return QString(); } QString revisionInterval(const KDevelop::VcsRevision& rev, const KDevelop::VcsRevision& limit) { QString ret; if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Start) //if we want it to the beginning just put the revisionInterval ret = toRevisionName(limit, QString()); else { QString dst = toRevisionName(limit); if(dst.isEmpty()) ret = dst; else { QString src = toRevisionName(rev, dst); if(src.isEmpty()) ret = src; else ret = src+".."+dst; } } return ret; } QDir urlDir(const QUrl& url) { QFileInfo f(url.toLocalFile()); if(f.isDir()) return QDir(url.toLocalFile()); else return f.absoluteDir(); } QDir urlDir(const QList& urls) { return urlDir(urls.first()); } //TODO: could be improved } GitPlugin::GitPlugin( QObject *parent, const QVariantList & ) : DistributedVersionControlPlugin(parent, "kdevgit"), m_oldVersion(false), m_usePrefix(true) { if (QStandardPaths::findExecutable("git").isEmpty()) { m_hasError = true; m_errorDescription = i18n("git is not installed"); return; } KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBasicVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IDistributedVersionControl ) KDEV_USE_EXTENSION_INTERFACE( KDevelop::IBranchingVersionControl ) m_hasError = false; setObjectName("Git"); DVcsJob* versionJob = new DVcsJob(QDir::tempPath(), this, KDevelop::OutputJob::Silent); *versionJob << "git" << "--version"; connect(versionJob, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitVersionOutput); ICore::self()->runController()->registerJob(versionJob); m_watcher = new KDirWatch(this); connect(m_watcher, &KDirWatch::dirty, this, &GitPlugin::fileChanged); connect(m_watcher, &KDirWatch::created, this, &GitPlugin::fileChanged); } GitPlugin::~GitPlugin() {} bool emptyOutput(DVcsJob* job) { QScopedPointer _job(job); if(job->exec() && job->status()==VcsJob::JobSucceeded) return job->rawOutput().trimmed().isEmpty(); return false; } bool GitPlugin::hasStashes(const QDir& repository) { return !emptyOutput(gitStash(repository, QStringList("list"), KDevelop::OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& d) { return !emptyOutput(lsFiles(d, QStringList("-m"), OutputJob::Silent)); } bool GitPlugin::hasModifications(const QDir& repo, const QUrl& file) { return !emptyOutput(lsFiles(repo, QStringList() << "-m" << file.path(), OutputJob::Silent)); } void GitPlugin::additionalMenuEntries(QMenu* menu, const QList& urls) { m_urls = urls; QDir dir=urlDir(urls); bool hasSt = hasStashes(dir); menu->addSeparator()->setText(i18n("Git Stashes")); menu->addAction(i18n("Stash Manager"), this, SLOT(ctxStashManager()))->setEnabled(hasSt); menu->addAction(i18n("Push Stash"), this, SLOT(ctxPushStash())); menu->addAction(i18n("Pop Stash"), this, SLOT(ctxPopStash()))->setEnabled(hasSt); } void GitPlugin::ctxPushStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList(), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxPopStash() { VcsJob* job = gitStash(urlDir(m_urls), QStringList("pop"), KDevelop::OutputJob::Verbose); ICore::self()->runController()->registerJob(job); } void GitPlugin::ctxStashManager() { QPointer d = new StashManagerDialog(urlDir(m_urls), this, 0); d->exec(); delete d; } DVcsJob* GitPlugin::errorsFound(const QString& error, KDevelop::OutputJob::OutputJobVerbosity verbosity=OutputJob::Verbose) { DVcsJob* j = new DVcsJob(QDir::temp(), this, verbosity); *j << "echo" << i18n("error: %1", error) << "-n"; return j; } QString GitPlugin::name() const { return QLatin1String("Git"); } QUrl GitPlugin::repositoryRoot(const QUrl& path) { return QUrl::fromLocalFile(dotGitDirectory(path).absolutePath()); } bool GitPlugin::isValidDirectory(const QUrl & dirPath) { QDir dir=dotGitDirectory(dirPath); return dir.cd(".git") && dir.exists("HEAD"); } bool GitPlugin::isVersionControlled(const QUrl &path) { QFileInfo fsObject(path.toLocalFile()); if (!fsObject.exists()) { return false; } if (fsObject.isDir()) { return isValidDirectory(path); } QString filename = fsObject.fileName(); QStringList otherFiles = getLsFiles(fsObject.dir(), QStringList("--") << filename, KDevelop::OutputJob::Silent); return !otherFiles.empty(); } VcsJob* GitPlugin::init(const QUrl &directory) { DVcsJob* job = new DVcsJob(urlDir(directory), this); job->setType(VcsJob::Import); *job << "git" << "init"; return job; } VcsJob* GitPlugin::createWorkingCopy(const KDevelop::VcsLocation & source, const QUrl& dest, KDevelop::IBasicVersionControl::RecursionMode) { DVcsJob* job = new GitCloneJob(urlDir(dest), this); job->setType(VcsJob::Import); *job << "git" << "clone" << "--progress" << "--" << source.localUrl().url() << dest; return job; } VcsJob* GitPlugin::add(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Add); *job << "git" << "add" << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } KDevelop::VcsJob* GitPlugin::status(const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty()) return errorsFound(i18n("Did not specify the list of files"), OutputJob::Verbose); DVcsJob* job = new GitJob(urlDir(localLocations), this, OutputJob::Silent); job->setType(VcsJob::Status); if(m_oldVersion) { *job << "git" << "ls-files" << "-t" << "-m" << "-c" << "-o" << "-d" << "-k" << "--directory"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput_old); } else { *job << "git" << "status" << "--porcelain"; job->setIgnoreError(true); connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitStatusOutput); } *job << "--" << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } VcsJob* GitPlugin::diff(const QUrl& fileOrDirectory, const KDevelop::VcsRevision& srcRevision, const KDevelop::VcsRevision& dstRevision, VcsDiff::Type /*type*/, IBasicVersionControl::RecursionMode recursion) { //TODO: control different types DVcsJob* job = new GitJob(dotGitDirectory(fileOrDirectory), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Diff); *job << "git" << "diff" << "--no-color" << "--no-ext-diff"; if (!usePrefix()) { // KDE's ReviewBoard now requires p1 patchfiles, so `git diff --no-prefix` to generate p0 patches // has become optional. *job << "--no-prefix"; } if(srcRevision.revisionType()==VcsRevision::Special && dstRevision.revisionType()==VcsRevision::Special && srcRevision.specialType()==VcsRevision::Base && dstRevision.specialType()==VcsRevision::Working) *job << "HEAD"; else { QString revstr = revisionInterval(srcRevision, dstRevision); if(!revstr.isEmpty()) *job << revstr; } *job << "--"; if (recursion == IBasicVersionControl::Recursive) { *job << fileOrDirectory; } else { *job << preventRecursion(QList() << fileOrDirectory); } connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitDiffOutput); return job; } VcsJob* GitPlugin::revert(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { if(localLocations.isEmpty() ) return errorsFound(i18n("Could not revert changes"), OutputJob::Verbose); QDir repo = urlDir(repositoryRoot(localLocations.first())); QString modified; for (const auto& file: localLocations) { if (hasModifications(repo, file)) { modified.append(file.toDisplayString(QUrl::PreferLocalFile) + "
"); } } if (!modified.isEmpty()) { auto res = KMessageBox::questionYesNo(nullptr, i18n("The following files have uncommited changes, " "which will be lost. Continue?") + "

" + modified); if (res != KMessageBox::Yes) { return errorsFound(QString(), OutputJob::Silent); } } DVcsJob* job = new GitJob(dotGitDirectory(localLocations.front()), this); job->setType(VcsJob::Revert); *job << "git" << "checkout" << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } //TODO: git doesn't like empty messages, but "KDevelop didn't provide any message, it may be a bug" looks ugly... //If no files specified then commit already added files VcsJob* GitPlugin::commit(const QString& message, const QList& localLocations, KDevelop::IBasicVersionControl::RecursionMode recursion) { if (localLocations.empty() || message.isEmpty()) return errorsFound(i18n("No files or message specified")); QDir dir = dotGitDirectory(localLocations.front()); DVcsJob* job = new DVcsJob(dir, this); job->setType(VcsJob::Commit); QList files = (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); addNotVersionedFiles(dir, files); *job << "git" << "commit" << "-m" << message; *job << "--" << files; return job; } void GitPlugin::addNotVersionedFiles(const QDir& dir, const QList& files) { QStringList otherStr = getLsFiles(dir, QStringList() << "--others", KDevelop::OutputJob::Silent); QList toadd, otherFiles; foreach(const QString& file, otherStr) { QUrl v = QUrl::fromLocalFile(dir.absoluteFilePath(file)); otherFiles += v; } //We add the files that are not versioned foreach(const QUrl& file, files) { if(otherFiles.contains(file) && QFileInfo(file.toLocalFile()).isFile()) toadd += file; } if(!toadd.isEmpty()) { VcsJob* job = add(toadd); job->exec(); } } bool isEmptyDirStructure(const QDir &dir) { foreach (const QFileInfo &i, dir.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot)) { if (i.isDir()) { if (!isEmptyDirStructure(QDir(i.filePath()))) return false; } else if (i.isFile()) { return false; } } return true; } VcsJob* GitPlugin::remove(const QList& files) { if (files.isEmpty()) return errorsFound(i18n("No files to remove")); QDir dotGitDir = dotGitDirectory(files.front()); QList files_(files); QMutableListIterator i(files_); while (i.hasNext()) { QUrl file = i.next(); QFileInfo fileInfo(file.toLocalFile()); QStringList otherStr = getLsFiles(dotGitDir, QStringList() << "--others" << "--" << file.toLocalFile(), KDevelop::OutputJob::Silent); if(!otherStr.isEmpty()) { //remove files not under version control QList otherFiles; foreach(const QString &f, otherStr) { otherFiles << QUrl::fromLocalFile(dotGitDir.path()+'/'+f); } if (fileInfo.isFile()) { //if it's an unversioned file we are done, don't use git rm on it i.remove(); } auto trashJob = KIO::trash(otherFiles); trashJob->exec(); qCDebug(PLUGIN_GIT) << "other files" << otherFiles; } if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(file.toLocalFile()))) { //remove empty folders, git doesn't do that auto trashJob = KIO::trash(file); trashJob->exec(); qCDebug(PLUGIN_GIT) << "empty folder, removing" << file; //we already deleted it, don't use git rm on it i.remove(); } } } if (files_.isEmpty()) return 0; DVcsJob* job = new GitJob(dotGitDir, this); job->setType(VcsJob::Remove); // git refuses to delete files with local modifications // use --force to overcome this *job << "git" << "rm" << "-r" << "--force"; *job << "--" << files_; return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& src, const KDevelop::VcsRevision& dst) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString rev = revisionInterval(dst, src); if(!rev.isEmpty()) *job << rev; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } VcsJob* GitPlugin::log(const QUrl& localLocation, const KDevelop::VcsRevision& rev, unsigned long int limit) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Log); *job << "git" << "log" << "--date=raw" << "--name-status" << "-M80%" << "--follow"; QString revStr = toRevisionName(rev, QString()); if(!revStr.isEmpty()) *job << revStr; if(limit>0) *job << QStringLiteral("-%1").arg(limit); *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitLogOutput); return job; } KDevelop::VcsJob* GitPlugin::annotate(const QUrl &localLocation, const KDevelop::VcsRevision&) { DVcsJob* job = new GitJob(dotGitDirectory(localLocation), this, KDevelop::OutputJob::Silent); job->setType(VcsJob::Annotate); *job << "git" << "blame" << "--porcelain" << "-w"; *job << "--" << localLocation; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBlameOutput); return job; } void GitPlugin::parseGitBlameOutput(DVcsJob *job) { QVariantList results; VcsAnnotationLine* annotation = 0; QStringList lines = job->output().split('\n'); bool skipNext=false; QMap definedRevisions; for(QStringList::const_iterator it=lines.constBegin(), itEnd=lines.constEnd(); it!=itEnd; ++it) { if(skipNext) { skipNext=false; results += qVariantFromValue(*annotation); continue; } if(it->isEmpty()) continue; QString name = it->left(it->indexOf(' ')); QString value = it->right(it->size()-name.size()-1); if(name=="author") annotation->setAuthor(value); else if(name=="author-mail") {} //TODO: do smth with the e-mail? else if(name=="author-tz") {} //TODO: does it really matter? else if(name=="author-time") annotation->setDate(QDateTime::fromTime_t(value.toUInt())); else if(name=="summary") annotation->setCommitMessage(value); else if(name.startsWith("committer")) {} //We will just store the authors else if(name=="previous") {} //We don't need that either else if(name=="filename") { skipNext=true; } else if(name=="boundary") { definedRevisions.insert("boundary", VcsAnnotationLine()); } else { QStringList values = value.split(' '); VcsRevision rev; rev.setRevisionValue(name.left(8), KDevelop::VcsRevision::GlobalNumber); skipNext = definedRevisions.contains(name); if(!skipNext) definedRevisions.insert(name, VcsAnnotationLine()); annotation = &definedRevisions[name]; annotation->setLineNumber(values[1].toInt() - 1); annotation->setRevision(rev); } } job->setResults(results); } DVcsJob* GitPlugin::lsFiles(const QDir &repository, const QStringList &args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "ls-files" << args; return job; } DVcsJob* GitPlugin::gitStash(const QDir& repository, const QStringList& args, OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(repository, this, verbosity); *job << "git" << "stash" << args; return job; } VcsJob* GitPlugin::tag(const QUrl& repository, const QString& commitMessage, const VcsRevision& rev, const QString& tagName) { DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "tag" << "-m" << commitMessage << tagName; if(rev.revisionValue().isValid()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::switchBranch(const QUrl &repository, const QString &branch) { QDir d=urlDir(repository); if(hasModifications(d) && KMessageBox::questionYesNo(0, i18n("There are pending changes, do you want to stash them first?"))==KMessageBox::Yes) { QScopedPointer stash(gitStash(d, QStringList(), KDevelop::OutputJob::Verbose)); stash->exec(); } DVcsJob* job = new DVcsJob(d, this); *job << "git" << "checkout" << branch; return job; } VcsJob* GitPlugin::branch(const QUrl& repository, const KDevelop::VcsRevision& rev, const QString& branchName) { Q_ASSERT(!branchName.isEmpty()); DVcsJob* job = new DVcsJob(urlDir(repository), this); *job << "git" << "branch" << "--" << branchName; if(!rev.prettyValue().isEmpty()) *job << rev.revisionValue().toString(); return job; } VcsJob* GitPlugin::deleteBranch(const QUrl& repository, const QString& branchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-D" << branchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::renameBranch(const QUrl& repository, const QString& oldBranchName, const QString& newBranchName) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); *job << "git" << "branch" << "-m" << newBranchName << oldBranchName; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } VcsJob* GitPlugin::currentBranch(const QUrl& repository) { DVcsJob* job = new DVcsJob(urlDir(repository), this, OutputJob::Silent); job->setIgnoreError(true); *job << "git" << "symbolic-ref" << "-q" << "--short" << "HEAD"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitCurrentBranch); return job; } void GitPlugin::parseGitCurrentBranch(DVcsJob* job) { QString out = job->output().trimmed(); job->setResults(out); } VcsJob* GitPlugin::branches(const QUrl &repository) { DVcsJob* job=new DVcsJob(urlDir(repository)); *job << "git" << "branch" << "-a"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitBranchOutput); return job; } void GitPlugin::parseGitBranchOutput(DVcsJob* job) { QStringList branchListDirty = job->output().split('\n', QString::SkipEmptyParts); QStringList branchList; foreach(QString branch, branchListDirty) { // Skip pointers to another branches (one example of this is "origin/HEAD -> origin/master"); // "git rev-list" chokes on these entries and we do not need duplicate branches altogether. if (branch.contains("->")) continue; // Skip entries such as '(no branch)' if (branch.contains("(no branch)")) continue; if (branch.startsWith('*')) branch = branch.right(branch.size()-2); branchList<setResults(branchList); } /* Few words about how this hardcore works: 1. get all commits (with --paretns) 2. select master (root) branch and get all unicial commits for branches (git-rev-list br2 ^master ^br3) 3. parse allCommits. While parsing set mask (columns state for every row) for BRANCH, INITIAL, CROSS, MERGE and INITIAL are also set in DVCScommit::setParents (depending on parents count) another setType(INITIAL) is used for "bottom/root/first" commits of branches 4. find and set merges, HEADS. It's an ittaration through all commits. - first we check if parent is from the same branch, if no then we go through all commits searching parent's index and set CROSS/HCROSS for rows (in 3 rows are set EMPTY after commit with parent from another tree met) - then we check branchesShas[i][0] to mark heads 4 can be a seporate function. TODO: All this porn require refactoring (rewriting is better)! It's a very dirty implementation. FIXME: 1. HEAD which is head has extra line to connect it with further commit 2. If you menrge branch2 to master, only new commits of branch2 will be visible (it's fine, but there will be extra merge rectangle in master. If there are no extra commits in branch2, but there are another branches, then the place for branch2 will be empty (instead of be used for branch3). 3. Commits that have additional commit-data (not only history merging, but changes to fix conflicts) are shown incorrectly */ QList GitPlugin::getAllCommits(const QString &repo) { initBranchHash(repo); QStringList args; args << "--all" << "--pretty" << "--parents"; QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); static QRegExp rx_com("commit \\w{40,40}"); QListcommitList; DVcsEvent item; //used to keep where we have empty/cross/branch entry //true if it's an active branch (then cross or branch) and false if not QVector additionalFlags(branchesShas.count()); additionalFlags.fill(false); //parse output for(int i = 0; i < commits.count(); ++i) { if (commits[i].contains(rx_com)) { qCDebug(PLUGIN_GIT) << "commit found in " << commits[i]; item.setCommit(commits[i].section(' ', 1, 1).trimmed()); // qCDebug(PLUGIN_GIT) << "commit is: " << commits[i].section(' ', 1); QStringList parents; QString parent = commits[i].section(' ', 2); int section = 2; while (!parent.isEmpty()) { /* qCDebug(PLUGIN_GIT) << "Parent is: " << parent;*/ parents.append(parent.trimmed()); section++; parent = commits[i].section(' ', section); } item.setParents(parents); //Avoid Merge string while (!commits[i].contains("Author: ")) ++i; item.setAuthor(commits[i].section("Author: ", 1).trimmed()); // qCDebug(PLUGIN_GIT) << "author is: " << commits[i].section("Author: ", 1); item.setDate(commits[++i].section("Date: ", 1).trimmed()); // qCDebug(PLUGIN_GIT) << "date is: " << commits[i].section("Date: ", 1); QString log; i++; //next line! while (i < commits.count() && !commits[i].contains(rx_com)) log += commits[i++]; --i; //while took commit line item.setLog(log.trimmed()); // qCDebug(PLUGIN_GIT) << "log is: " << log; //mask is used in CommitViewDelegate to understand what we should draw for each branch QList mask; //set mask (properties for each graph column in row) for(int i = 0; i < branchesShas.count(); ++i) { qCDebug(PLUGIN_GIT)<<"commit: " << item.getCommit(); if (branchesShas[i].contains(item.getCommit())) { mask.append(item.getType()); //we set type in setParents //check if parent from the same branch, if not then we have found a root of the branch //and will use empty column for all futher (from top to bottom) revisions //FIXME: we should set CROSS between parent and child (and do it when find merge point) additionalFlags[i] = false; foreach(const QString &sha, item.getParents()) { if (branchesShas[i].contains(sha)) additionalFlags[i] = true; } if (additionalFlags[i] == false) item.setType(DVcsEvent::INITIAL); //hasn't parents from the same branch, used in drawing } else { if (additionalFlags[i] == false) mask.append(DVcsEvent::EMPTY); else mask.append(DVcsEvent::CROSS); } qCDebug(PLUGIN_GIT) << "mask " << i << "is " << mask[i]; } item.setProperties(mask); commitList.append(item); } } //find and set merges, HEADS, require refactoring! for(QList::iterator iter = commitList.begin(); iter != commitList.end(); ++iter) { QStringList parents = iter->getParents(); //we need only only child branches if (parents.count() != 1) break; QString parent = parents[0]; QString commit = iter->getCommit(); bool parent_checked = false; int heads_checked = 0; for(int i = 0; i < branchesShas.count(); ++i) { //check parent if (branchesShas[i].contains(commit)) { if (!branchesShas[i].contains(parent)) { //parent and child are not in same branch //since it is list, than parent has i+1 index //set CROSS and HCROSS for(QList::iterator f_iter = iter; f_iter != commitList.end(); ++f_iter) { if (parent == f_iter->getCommit()) { for(int j = 0; j < i; ++j) { if(branchesShas[j].contains(parent)) f_iter->setPropetry(j, DVcsEvent::MERGE); else f_iter->setPropetry(j, DVcsEvent::HCROSS); } f_iter->setType(DVcsEvent::MERGE); f_iter->setPropetry(i, DVcsEvent::MERGE_RIGHT); qCDebug(PLUGIN_GIT) << parent << " is parent of " << commit; qCDebug(PLUGIN_GIT) << f_iter->getCommit() << " is merge"; parent_checked = true; break; } else f_iter->setPropetry(i, DVcsEvent::CROSS); } } } //mark HEADs if (!branchesShas[i].empty() && commit == branchesShas[i][0]) { iter->setType(DVcsEvent::HEAD); iter->setPropetry(i, DVcsEvent::HEAD); heads_checked++; qCDebug(PLUGIN_GIT) << "HEAD found"; } //some optimization if (heads_checked == branchesShas.count() && parent_checked) break; } } return commitList; } void GitPlugin::initBranchHash(const QString &repo) { const QUrl repoUrl = QUrl::fromLocalFile(repo); QStringList gitBranches = runSynchronously(branches(repoUrl)).toStringList(); qCDebug(PLUGIN_GIT) << "BRANCHES: " << gitBranches; //Now root branch is the current branch. In future it should be the longest branch //other commitLists are got with git-rev-lits branch ^br1 ^ br2 QString root = runSynchronously(currentBranch(repoUrl)).toString(); QScopedPointer job(gitRevList(repo, QStringList(root))); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); foreach(const QString &branch, gitBranches) { if (branch == root) continue; QStringList args(branch); foreach(const QString &branch_arg, gitBranches) { if (branch_arg != branch) //man gitRevList for '^' args<<'^' + branch_arg; } QScopedPointer job(gitRevList(repo, args)); bool ret = job->exec(); Q_ASSERT(ret && job->status()==VcsJob::JobSucceeded && "TODO: provide a fall back in case of failing"); Q_UNUSED(ret); QStringList commits = job->output().split('\n', QString::SkipEmptyParts); // qCDebug(PLUGIN_GIT) << "\n\n\n commits" << commits << "\n\n\n"; branchesShas.append(commits); } } //Actually we can just copy the output without parsing. So it's a kind of draft for future void GitPlugin::parseLogOutput(const DVcsJob * job, QList& commits) const { // static QRegExp rx_sep( "[-=]+" ); // static QRegExp rx_date( "date:\\s+([^;]*);\\s+author:\\s+([^;]*).*" ); static QRegExp rx_com( "commit \\w{1,40}" ); QStringList lines = job->output().split('\n', QString::SkipEmptyParts); DVcsEvent item; QString commitLog; for (int i=0; i commits; QString contents = job->output(); // check if git-log returned anything if (contents.isEmpty()) { job->setResults(commits); // empty list return; } // start parsing the output QTextStream s(&contents); VcsEvent item; QString message; bool pushCommit = false; while (!s.atEnd()) { QString line = s.readLine(); if (commitRegex.exactMatch(line)) { if (pushCommit) { item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); item.setItems(QList()); } else { pushCommit = true; } VcsRevision rev; rev.setRevisionValue(commitRegex.cap(1), KDevelop::VcsRevision::GlobalNumber); item.setRevision(rev); message.clear(); } else if (infoRegex.exactMatch(line)) { QString cap1 = infoRegex.cap(1); if (cap1 == "Author") { item.setAuthor(infoRegex.cap(2).trimmed()); } else if (cap1 == "Date") { item.setDate(QDateTime::fromTime_t(infoRegex.cap(2).trimmed().split(' ')[0].toUInt())); } } else if (modificationsRegex.exactMatch(line)) { VcsItemEvent::Actions a = actionsFromString(modificationsRegex.cap(1)[0].toLatin1()); QString filenameA = modificationsRegex.cap(2); VcsItemEvent itemEvent; itemEvent.setActions(a); itemEvent.setRepositoryLocation(filenameA); if(a==VcsItemEvent::Replaced) { QString filenameB = modificationsRegex.cap(3); itemEvent.setRepositoryCopySourceLocation(filenameB); } item.addItem(itemEvent); } else if (line.startsWith(" ")) { message += line.remove(0, 4); message += '\n'; } } item.setMessage(message.trimmed()); commits.append(QVariant::fromValue(item)); job->setResults(commits); } void GitPlugin::parseGitDiffOutput(DVcsJob* job) { VcsDiff diff; diff.setDiff(job->output()); diff.setBaseDiff(repositoryRoot(QUrl::fromLocalFile(job->directory().absolutePath()))); diff.setDepth(usePrefix()? 1 : 0); job->setResults(qVariantFromValue(diff)); } static VcsStatusInfo::State lsfilesToState(char id) { switch(id) { case 'H': return VcsStatusInfo::ItemUpToDate; //Cached case 'S': return VcsStatusInfo::ItemUpToDate; //Skip work tree case 'M': return VcsStatusInfo::ItemHasConflicts; //unmerged case 'R': return VcsStatusInfo::ItemDeleted; //removed/deleted case 'C': return VcsStatusInfo::ItemModified; //modified/changed case 'K': return VcsStatusInfo::ItemDeleted; //to be killed case '?': return VcsStatusInfo::ItemUnknown; //other } Q_ASSERT(false); return VcsStatusInfo::ItemUnknown; } void GitPlugin::parseGitStatusOutput_old(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir dir = job->directory(); QMap allStatus; foreach(const QString& line, outputLines) { VcsStatusInfo::State status = lsfilesToState(line[0].toLatin1()); QUrl url = QUrl::fromLocalFile(dir.absoluteFilePath(line.right(line.size()-2))); allStatus[url] = status; } QVariantList statuses; QMap< QUrl, VcsStatusInfo::State >::const_iterator it = allStatus.constBegin(), itEnd=allStatus.constEnd(); for(; it!=itEnd; ++it) { VcsStatusInfo status; status.setUrl(it.key()); status.setState(it.value()); statuses.append(qVariantFromValue(status)); } job->setResults(statuses); } void GitPlugin::parseGitStatusOutput(DVcsJob* job) { QStringList outputLines = job->output().split('\n', QString::SkipEmptyParts); QDir workingDir = job->directory(); QDir dotGit = dotGitDirectory(QUrl::fromLocalFile(workingDir.absolutePath())); QVariantList statuses; QList processedFiles; foreach(const QString& line, outputLines) { //every line is 2 chars for the status, 1 space then the file desc QString curr=line.right(line.size()-3); QString state = line.left(2); int arrow = curr.indexOf(" -> "); if(arrow>=0) { VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr.left(arrow)))); status.setState(VcsStatusInfo::ItemDeleted); statuses.append(qVariantFromValue(status)); processedFiles += status.url(); curr = curr.mid(arrow+4); } if(curr.startsWith('\"') && curr.endsWith('\"')) { //if the path is quoted, unquote curr = curr.mid(1, curr.size()-2); } VcsStatusInfo status; status.setUrl(QUrl::fromLocalFile(dotGit.absoluteFilePath(curr))); status.setState(messageToState(state)); processedFiles.append(status.url()); qCDebug(PLUGIN_GIT) << "Checking git status for " << line << curr << messageToState(state); statuses.append(qVariantFromValue(status)); } QStringList paths; QStringList oldcmd=job->dvcsCommand(); QStringList::const_iterator it=oldcmd.constBegin()+oldcmd.indexOf("--")+1, itEnd=oldcmd.constEnd(); for(; it!=itEnd; ++it) paths += *it; //here we add the already up to date files QStringList files = getLsFiles(job->directory(), QStringList() << "-c" << "--" << paths, OutputJob::Silent); foreach(const QString& file, files) { QUrl fileUrl = QUrl::fromLocalFile(workingDir.absoluteFilePath(file)); if(!processedFiles.contains(fileUrl)) { VcsStatusInfo status; status.setUrl(fileUrl); status.setState(VcsStatusInfo::ItemUpToDate); statuses.append(qVariantFromValue(status)); } } job->setResults(statuses); } void GitPlugin::parseGitVersionOutput(DVcsJob* job) { QStringList versionString = job->output().trimmed().split(' ').last().split('.'); static const QList minimumVersion = QList() << 1 << 7; qCDebug(PLUGIN_GIT) << "checking git version" << versionString << "against" << minimumVersion; m_oldVersion = false; if (versionString.size() < minimumVersion.size()) { m_oldVersion = true; qWarning() << "invalid git version string:" << job->output().trimmed(); return; } foreach(int num, minimumVersion) { QString curr = versionString.takeFirst(); int valcurr = curr.toInt(); if (valcurr < num) { m_oldVersion = true; break; } if (valcurr > num) { m_oldVersion = false; break; } } qCDebug(PLUGIN_GIT) << "the current git version is old: " << m_oldVersion; } QStringList GitPlugin::getLsFiles(const QDir &directory, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { QScopedPointer job(lsFiles(directory, args, verbosity)); if (job->exec() && job->status() == KDevelop::VcsJob::JobSucceeded) return job->output().split('\n', QString::SkipEmptyParts); return QStringList(); } DVcsJob* GitPlugin::gitRevParse(const QString &repository, const QStringList &args, KDevelop::OutputJob::OutputJobVerbosity verbosity) { DVcsJob* job = new DVcsJob(QDir(repository), this, verbosity); *job << "git" << "rev-parse" << args; return job; } DVcsJob* GitPlugin::gitRevList(const QString& directory, const QStringList& args) { DVcsJob* job = new DVcsJob(urlDir(QUrl::fromLocalFile(directory)), this, KDevelop::OutputJob::Silent); { *job << "git" << "rev-list" << args; return job; } } VcsStatusInfo::State GitPlugin::messageToState(const QString& msg) { Q_ASSERT(msg.size()==1 || msg.size()==2); VcsStatusInfo::State ret = VcsStatusInfo::ItemUnknown; if(msg.contains('U') || msg == "AA" || msg == "DD") ret = VcsStatusInfo::ItemHasConflicts; else switch(msg[0].toLatin1()) { case 'M': ret = VcsStatusInfo::ItemModified; break; case 'A': ret = VcsStatusInfo::ItemAdded; break; case 'R': ret = VcsStatusInfo::ItemModified; break; case 'C': ret = VcsStatusInfo::ItemHasConflicts; break; case ' ': ret = msg[1] == 'M' ? VcsStatusInfo::ItemModified : VcsStatusInfo::ItemDeleted; break; case 'D': ret = VcsStatusInfo::ItemDeleted; break; case '?': ret = VcsStatusInfo::ItemUnknown; break; default: qCDebug(PLUGIN_GIT) << "Git status not identified:" << msg; break; } return ret; } StandardJob::StandardJob(IPlugin* parent, KJob* job, OutputJob::OutputJobVerbosity verbosity) : VcsJob(parent, verbosity) , m_job(job) , m_plugin(parent) , m_status(JobNotStarted) {} void StandardJob::start() { connect(m_job, &KJob::result, this, &StandardJob::result); m_job->start(); m_status=JobRunning; } void StandardJob::result(KJob* job) { m_status=job->error() == 0? JobSucceeded : JobFailed; emitResult(); } VcsJob* GitPlugin::copy(const QUrl& localLocationSrc, const QUrl& localLocationDstn) { //TODO: Probably we should "git add" after return new StandardJob(this, KIO::copy(localLocationSrc, localLocationDstn), KDevelop::OutputJob::Silent); } VcsJob* GitPlugin::move(const QUrl& source, const QUrl& destination) { QDir dir = urlDir(source); QFileInfo fileInfo(source.toLocalFile()); if (fileInfo.isDir()) { if (isEmptyDirStructure(QDir(source.toLocalFile()))) { //move empty folder, git doesn't do that qCDebug(PLUGIN_GIT) << "empty folder" << source; return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } QStringList otherStr = getLsFiles(dir, QStringList() << "--others" << "--" << source.toLocalFile(), KDevelop::OutputJob::Silent); if(otherStr.isEmpty()) { DVcsJob* job = new DVcsJob(dir, this, KDevelop::OutputJob::Verbose); *job << "git" << "mv" << source.toLocalFile() << destination.toLocalFile(); return job; } else { return new StandardJob(this, KIO::move(source, destination), KDevelop::OutputJob::Silent); } } void GitPlugin::parseGitRepoLocationOutput(DVcsJob* job) { job->setResults(QVariant::fromValue(QUrl::fromLocalFile(job->output()))); } VcsJob* GitPlugin::repositoryLocation(const QUrl& localLocation) { DVcsJob* job = new DVcsJob(urlDir(localLocation), this); //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "config" << "remote.origin.url"; connect(job, &DVcsJob::readyForParsing, this, &GitPlugin::parseGitRepoLocationOutput); return job; } VcsJob* GitPlugin::pull(const KDevelop::VcsLocation& localOrRepoLocationSrc, const QUrl& localRepositoryLocation) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "-c" << "color.diff=false" << "pull"; if(!localOrRepoLocationSrc.localUrl().isEmpty()) *job << localOrRepoLocationSrc.localUrl().url(); return job; } VcsJob* GitPlugin::push(const QUrl& localRepositoryLocation, const KDevelop::VcsLocation& localOrRepoLocationDst) { DVcsJob* job = new DVcsJob(urlDir(localRepositoryLocation), this); job->setCommunicationMode(KProcess::MergedChannels); *job << "git" << "push"; if(!localOrRepoLocationDst.localUrl().isEmpty()) *job << localOrRepoLocationDst.localUrl().url(); return job; } VcsJob* GitPlugin::resolve(const QList& localLocations, IBasicVersionControl::RecursionMode recursion) { return add(localLocations, recursion); } VcsJob* GitPlugin::update(const QList& localLocations, const KDevelop::VcsRevision& rev, IBasicVersionControl::RecursionMode recursion) { if(rev.revisionType()==VcsRevision::Special && rev.revisionValue().value()==VcsRevision::Head) { return pull(VcsLocation(), localLocations.first()); } else { DVcsJob* job = new DVcsJob(urlDir(localLocations.first()), this); { //Probably we should check first if origin is the proper remote we have to use but as a first attempt it works *job << "git" << "checkout" << rev.revisionValue().toString() << "--"; *job << (recursion == IBasicVersionControl::Recursive ? localLocations : preventRecursion(localLocations)); return job; } } } void GitPlugin::setupCommitMessageEditor(const QUrl& localLocation, KTextEdit* editor) const { new GitMessageHighlighter(editor); QFile mergeMsgFile(dotGitDirectory(localLocation).filePath(".git/MERGE_MSG")); // Some limit on the file size should be set since whole content is going to be read into // the memory. 1Mb seems to be good value since it's rather strange to have so huge commit // message. static const qint64 maxMergeMsgFileSize = 1024*1024; if (mergeMsgFile.size() > maxMergeMsgFileSize || !mergeMsgFile.open(QIODevice::ReadOnly)) return; QString mergeMsg = QString::fromLocal8Bit(mergeMsgFile.read(maxMergeMsgFileSize)); editor->setPlainText(mergeMsg); } class GitVcsLocationWidget : public KDevelop::StandardVcsLocationWidget { public: GitVcsLocationWidget(QWidget* parent = 0, Qt::WindowFlags f = 0) : StandardVcsLocationWidget(parent, f) {} virtual bool isCorrect() const override { return !url().isEmpty(); } }; KDevelop::VcsLocationWidget* GitPlugin::vcsLocation(QWidget* parent) const { return new GitVcsLocationWidget(parent); } bool GitPlugin::hasError() const { return m_hasError; } QString GitPlugin::errorDescription() const { return m_errorDescription; } void GitPlugin::registerRepositoryForCurrentBranchChanges(const QUrl& repository) { QDir dir = dotGitDirectory(repository); QString headFile = dir.absoluteFilePath(".git/HEAD"); m_watcher->addFile(headFile); } void GitPlugin::fileChanged(const QString& file) { Q_ASSERT(file.endsWith("HEAD")); //SMTH/.git/HEAD -> SMTH/ const QUrl fileUrl = Path(file).parent().parent().toUrl(); //We need to delay the emitted signal, otherwise the branch hasn't change yet //and the repository is not functional m_branchesChange.append(fileUrl); QTimer::singleShot(1000, this, SLOT(delayedBranchChanged())); } void GitPlugin::delayedBranchChanged() { emit repositoryBranchChanged(m_branchesChange.takeFirst()); } CheckInRepositoryJob* GitPlugin::isInRepository(KTextEditor::Document* document) { CheckInRepositoryJob* job = new GitPluginCheckInRepositoryJob(document, repositoryRoot(document->url()).path()); job->start(); return job; } diff --git a/plugins/git/stashmanagerdialog.cpp b/plugins/git/stashmanagerdialog.cpp index 1fb939409e..d0d436f304 100644 --- a/plugins/git/stashmanagerdialog.cpp +++ b/plugins/git/stashmanagerdialog.cpp @@ -1,153 +1,153 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "stashmanagerdialog.h" #include "ui_stashmanagerdialog.h" #include "gitplugin.h" #include "stashpatchsource.h" #include #include #include -#include -#include #include -#include -#include + #include +#include #include -#include + +#include +#include using namespace KDevelop; StashManagerDialog::StashManagerDialog(const QDir& stashed, GitPlugin* plugin, QWidget* parent) : QDialog(parent), m_plugin(plugin), m_dir(stashed) { setWindowTitle(i18n("Stash Manager")); m_mainWidget = new QWidget(this); m_ui = new Ui::StashManager; m_ui->setupUi(m_mainWidget); - + StashModel* m = new StashModel(stashed, plugin, this); m_ui->stashView->setModel(m); - + connect(m_ui->show, &QPushButton::clicked, this, &StashManagerDialog::showStash); connect(m_ui->apply, &QPushButton::clicked, this, &StashManagerDialog::applyClicked); connect(m_ui->branch, &QPushButton::clicked, this, &StashManagerDialog::branchClicked); connect(m_ui->pop, &QPushButton::clicked, this, &StashManagerDialog::popClicked); connect(m_ui->drop, &QPushButton::clicked, this, &StashManagerDialog::dropClicked); connect(m, &StashModel::rowsInserted, this, &StashManagerDialog::stashesFound); - + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &StashManagerDialog::reject); m_mainWidget->setEnabled(false); //we won't enable it until we have the model with data and selection } StashManagerDialog::~StashManagerDialog() { delete m_ui; } void StashManagerDialog::stashesFound() { QModelIndex firstIdx=m_ui->stashView->model()->index(0, 0); m_ui->stashView->setCurrentIndex(firstIdx); m_mainWidget->setEnabled(true); } QString StashManagerDialog::selection() const { QModelIndex idx = m_ui->stashView->currentIndex(); Q_ASSERT(idx.isValid()); return idx.data().toString(); } void StashManagerDialog::runStash(const QStringList& arguments) { VcsJob* job = m_plugin->gitStash(m_dir, arguments, OutputJob::Verbose); connect(job, &VcsJob::result, this, &StashManagerDialog::accept); - + m_mainWidget->setEnabled(false); - + ICore::self()->runController()->registerJob(job); } void StashManagerDialog::showStash() { IPatchReview * review = ICore::self()->pluginController()->extensionForPlugin(); IPatchSource::Ptr stashPatch(new StashPatchSource(selection(), m_plugin, m_dir)); review->startReview(stashPatch); accept(); } void StashManagerDialog::applyClicked() { runStash(QStringList("apply") << selection()); } void StashManagerDialog::popClicked() { runStash(QStringList("pop") << selection()); } void StashManagerDialog::dropClicked() { QString sel = selection(); int ret = KMessageBox::questionYesNo(this, i18n("Are you sure you want to drop the stash '%1'?", sel)); - + if(ret == KMessageBox::Yes) runStash(QStringList("drop") << sel); } void StashManagerDialog::branchClicked() { QString branchName = QInputDialog::getText(this, i18n("KDevelop - Git Stash"), i18n("Select a name for the new branch:")); - + if(!branchName.isEmpty()) runStash(QStringList("branch") << branchName << selection()); } //////////////////StashModel StashModel::StashModel(const QDir& dir, GitPlugin* git, QObject* parent) : QStandardItemModel(parent) { VcsJob* job=git->gitStash(dir, QStringList("list"), OutputJob::Silent); connect(job, &VcsJob::finished, this, &StashModel::stashListReady); - + ICore::self()->runController()->registerJob(job); } void StashModel::stashListReady(KJob* _job) { DVcsJob* job = qobject_cast(_job); QList< QByteArray > output = job->rawOutput().split('\n'); - + foreach(const QByteArray& line, output) { QList< QByteArray > fields = line.split(':'); - + QList elements; foreach(const QByteArray& field, fields) elements += new QStandardItem(QString(field.trimmed())); - + appendRow(elements); } } diff --git a/plugins/grepview/grepdialog.cpp b/plugins/grepview/grepdialog.cpp index 4671c30bcd..62d2c2d89e 100644 --- a/plugins/grepview/grepdialog.cpp +++ b/plugins/grepview/grepdialog.cpp @@ -1,487 +1,481 @@ /*************************************************************************** * Copyright 1999-2001 Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepdialog.h" #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 #include #include #include #include #include #include #include "grepviewplugin.h" #include "grepjob.h" #include "grepoutputview.h" #include "grepfindthread.h" #include "greputil.h" using namespace KDevelop; namespace { QString allOpenFilesString = i18n("All Open Files"); QString allOpenProjectsString = i18n("All Open Projects"); const QStringList template_desc = QStringList() << "verbatim" << "word" << "assignment" << "->MEMBER(" << "class::MEMBER(" << "OBJECT->member("; const QStringList template_str = QStringList() << "%s" << "\\b%s\\b" << "\\b%s\\b\\s*=[^=]" << "\\->\\s*\\b%s\\b\\s*\\(" << "([a-z0-9_$]+)\\s*::\\s*\\b%s\\b\\s*\\(" << "\\b%s\\b\\s*\\->\\s*([a-z0-9_$]+)\\s*\\("; const QStringList repl_template = QStringList() << "%s" << "%s" << "%s = " << "->%s(" << "\\1::%s(" << "%s->\\1("; const QStringList filepatterns = QStringList() << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.idl,*.c,*.m,*.mm,*.M,*.y,*.ypp,*.yxx,*.y++,*.l" << "*.cpp,*.cc,*.C,*.c++,*.cxx,*.ocl,*.inl,*.c,*.m,*.mm,*.M" << "*.h,*.hxx,*.hpp,*.hh,*.h++,*.H,*.tlh,*.idl" << "*.adb" << "*.cs" << "*.f" << "*.html,*.htm" << "*.hs" << "*.java" << "*.js" << "*.php,*.php3,*.php4" << "*.pl" << "*.pp,*.pas" << "*.py" << "*.js,*.css,*.yml,*.rb,*.rhtml,*.html.erb,*.rjs,*.js.rjs,*.rxml,*.xml.builder" << "CMakeLists.txt,*.cmake" << "*"; const QStringList excludepatterns = QStringList() << "/CVS/,/SCCS/,/.svn/,/_darcs/,/build/,/.git/" << ""; ///Separator used to separate search paths. const QString pathsSeparator(";"); ///Max number of items in paths combo box. const int pathsMaxCount = 25; } GrepDialog::GrepDialog( GrepViewPlugin * plugin, QWidget *parent ) : QDialog(parent), Ui::GrepWidget(), m_plugin( plugin ) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle( i18n("Find/Replace in Files") ); setupUi(this); auto searchButton = buttonBox->button(QDialogButtonBox::Ok); Q_ASSERT(searchButton); searchButton->setText(tr("Search...")); searchButton->setIcon(QIcon::fromTheme("edit-find")); connect(searchButton, &QPushButton::clicked, this, &GrepDialog::startSearch); connect(buttonBox, &QDialogButtonBox::rejected, this, &GrepDialog::reject); KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); patternCombo->addItems( cg.readEntry("LastSearchItems", QStringList()) ); patternCombo->setInsertPolicy(QComboBox::InsertAtTop); templateTypeCombo->addItems(template_desc); templateTypeCombo->setCurrentIndex( cg.readEntry("LastUsedTemplateIndex", 0) ); templateEdit->addItems( cg.readEntry("LastUsedTemplateString", template_str) ); templateEdit->setEditable(true); templateEdit->setCompletionMode(KCompletion::CompletionPopup); KCompletion* comp = templateEdit->completionObject(); connect(templateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(templateEdit->itemText(i)); replacementTemplateEdit->addItems( cg.readEntry("LastUsedReplacementTemplateString", repl_template) ); replacementTemplateEdit->setEditable(true); replacementTemplateEdit->setCompletionMode(KCompletion::CompletionPopup); comp = replacementTemplateEdit->completionObject(); connect(replacementTemplateEdit, static_cast(&KComboBox::returnPressed), comp, static_cast(&KCompletion::addItem)); for(int i=0; icount(); i++) comp->addItem(replacementTemplateEdit->itemText(i)); regexCheck->setChecked(cg.readEntry("regexp", false )); caseSensitiveCheck->setChecked(cg.readEntry("case_sens", true)); searchPaths->setCompletionObject(new KUrlCompletion()); searchPaths->setAutoDeleteCompletionObject(true); QList projects = m_plugin->core()->projectController()->projects(); searchPaths->addItems(cg.readEntry("SearchPaths", QStringList(!projects.isEmpty() ? allOpenProjectsString : QDir::homePath() ) )); searchPaths->setInsertPolicy(QComboBox::InsertAtTop); syncButton->setIcon(QIcon::fromTheme("dirsync")); syncButton->setMenu(createSyncButtonMenu()); depthSpin->setValue(cg.readEntry("depth", -1)); limitToProjectCheck->setChecked(cg.readEntry("search_project_files", true)); filesCombo->addItems(cg.readEntry("file_patterns", filepatterns)); excludeCombo->addItems(cg.readEntry("exclude_patterns", excludepatterns) ); connect(templateTypeCombo, static_cast(&KComboBox::activated), this, &GrepDialog::templateTypeComboActivated); connect(patternCombo, &QComboBox::editTextChanged, this, &GrepDialog::patternComboEditTextChanged); patternComboEditTextChanged( patternCombo->currentText() ); patternCombo->setFocus(); connect(searchPaths, static_cast(&KComboBox::activated), this, &GrepDialog::setSearchLocations); directorySelector->setIcon(QIcon::fromTheme("document-open")); connect(directorySelector, &QPushButton::clicked, this, &GrepDialog::selectDirectoryDialog ); directoryChanged(directorySelector->text()); } void GrepDialog::selectDirectoryDialog() { const QString dirName = QFileDialog::getExistingDirectory(this, tr("Select directory to search in"), searchPaths->lineEdit()->text()); if (!dirName.isEmpty()) { setSearchLocations(dirName); } } void GrepDialog::addUrlToMenu(QMenu* menu, const QUrl& url) { QAction* action = menu->addAction(m_plugin->core()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain)); action->setData(QVariant(url.toString())); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::addStringToMenu(QMenu* menu, QString string) { QAction* action = menu->addAction(string); action->setData(QVariant(string)); connect(action, &QAction::triggered, this, &GrepDialog::synchronizeDirActionTriggered); } void GrepDialog::synchronizeDirActionTriggered(bool) { QAction* action = qobject_cast(sender()); Q_ASSERT(action); setSearchLocations(action->data().value()); } QMenu* GrepDialog::createSyncButtonMenu() { QMenu* ret = new QMenu; QSet hadUrls; IDocument *doc = m_plugin->core()->documentController()->activeDocument(); if ( doc ) { Path url = Path(doc->url()).parent(); // always add the current file's parent directory hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); while(m_plugin->core()->projectController()->findProjectForUrl(url.toUrl())) { if(hadUrls.contains(url)) break; hadUrls.insert(url); addUrlToMenu(ret, url.toUrl()); url = url.parent(); } } foreach(IProject* project, m_plugin->core()->projectController()->projects()) { if (!hadUrls.contains(project->path())) { addUrlToMenu(ret, project->path().toUrl()); } } addStringToMenu(ret, allOpenFilesString); addStringToMenu(ret, allOpenProjectsString); return ret; } void GrepDialog::directoryChanged(const QString& dir) { QUrl currentUrl = QUrl::fromLocalFile(dir); if( !currentUrl.isValid() ) { setEnableProjectBox(false); return; } bool projectAvailable = true; foreach(QUrl url, getDirectoryChoice()) { IProject *proj = ICore::self()->projectController()->findProjectForUrl( currentUrl ); if( !proj || !proj->folder().isLocalFile() ) projectAvailable = false; } setEnableProjectBox(projectAvailable); } GrepDialog::~GrepDialog() { KConfigGroup cg = ICore::self()->activeSession()->config()->group( "GrepDialog" ); // memorize the last patterns and paths cg.writeEntry("LastSearchItems", qCombo2StringList(patternCombo)); cg.writeEntry("regexp", regexCheck->isChecked()); cg.writeEntry("depth", depthSpin->value()); cg.writeEntry("search_project_files", limitToProjectCheck->isChecked()); cg.writeEntry("case_sens", caseSensitiveCheck->isChecked()); cg.writeEntry("exclude_patterns", qCombo2StringList(excludeCombo)); cg.writeEntry("file_patterns", qCombo2StringList(filesCombo)); cg.writeEntry("LastUsedTemplateIndex", templateTypeCombo->currentIndex()); cg.writeEntry("LastUsedTemplateString", qCombo2StringList(templateEdit)); cg.writeEntry("LastUsedReplacementTemplateString", qCombo2StringList(replacementTemplateEdit)); cg.writeEntry("SearchPaths", qCombo2StringList(searchPaths)); cg.sync(); } void GrepDialog::templateTypeComboActivated(int index) { templateEdit->setCurrentItem( template_str[index], true ); replacementTemplateEdit->setCurrentItem( repl_template[index], true ); } void GrepDialog::setEnableProjectBox(bool enable) { limitToProjectCheck->setEnabled(enable); limitToProjectLabel->setEnabled(enable); } void GrepDialog::setPattern(const QString &pattern) { patternCombo->setEditText(pattern); patternComboEditTextChanged(pattern); } void GrepDialog::setSearchLocations(const QString &dir) { if(!dir.isEmpty()) { if(QDir::isAbsolutePath(dir)) { static_cast(searchPaths->completionObject())->setDir( QUrl::fromLocalFile(dir) ); } if (searchPaths->contains(dir)) { searchPaths->removeItem(searchPaths->findText(dir)); } searchPaths->insertItem(0, dir); searchPaths->setCurrentItem(dir); if (searchPaths->count() > pathsMaxCount) { searchPaths->removeItem(searchPaths->count() - 1); } } directoryChanged(dir); } QString GrepDialog::patternString() const { return patternCombo->currentText(); } QString GrepDialog::templateString() const { return templateEdit->currentText().isEmpty() ? "%s" : templateEdit->currentText(); } QString GrepDialog::replacementTemplateString() const { return replacementTemplateEdit->currentText(); } QString GrepDialog::filesString() const { return filesCombo->currentText(); } QString GrepDialog::excludeString() const { return excludeCombo->currentText(); } bool GrepDialog::useProjectFilesFlag() const { if (!limitToProjectCheck->isEnabled()) return false; return limitToProjectCheck->isChecked(); } bool GrepDialog::regexpFlag() const { return regexCheck->isChecked(); } int GrepDialog::depthValue() const { return depthSpin->value(); } bool GrepDialog::caseSensitiveFlag() const { return caseSensitiveCheck->isChecked(); } void GrepDialog::patternComboEditTextChanged( const QString& text) { buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); } QList< QUrl > GrepDialog::getDirectoryChoice() const { QList< QUrl > ret; QString text = searchPaths->currentText(); if(text == allOpenFilesString) { foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) ret << doc->url(); }else if(text == allOpenProjectsString) { foreach(IProject* project, ICore::self()->projectController()->projects()) ret << project->folder(); }else{ QStringList semicolonSeparatedFileList = text.split(pathsSeparator); if(!semicolonSeparatedFileList.isEmpty() && QFileInfo(semicolonSeparatedFileList[0]).exists()) { // We use QFileInfo to make sure this is really a semicolon-separated file list, not a file containing // a semicolon in the name. foreach(QString file, semicolonSeparatedFileList) ret << QUrl::fromLocalFile(file); }else{ ret << QUrl::fromUserInput(searchPaths->currentText()); } } return ret; } bool GrepDialog::isPartOfChoice(QUrl url) const { foreach(QUrl choice, getDirectoryChoice()) if(choice.isParentOf(url) || choice == url) return true; return false; } void GrepDialog::startSearch() { // search for unsaved documents QList unsavedFiles; QStringList include = GrepFindFilesThread::parseInclude(filesString()); QStringList exclude = GrepFindFilesThread::parseExclude(excludeString()); foreach(IDocument* doc, ICore::self()->documentController()->openDocuments()) { QUrl docUrl = doc->url(); if(doc->state() != IDocument::Clean && isPartOfChoice(docUrl) && QDir::match(include, docUrl.fileName()) && !QDir::match(exclude, docUrl.toLocalFile())) { unsavedFiles << doc; } } if(!ICore::self()->documentController()->saveSomeDocuments(unsavedFiles)) { close(); return; } QList choice = getDirectoryChoice(); GrepJob* job = m_plugin->newGrepJob(); QString descriptionOrUrl(searchPaths->currentText()); QString description = descriptionOrUrl; // Shorten the description if(descriptionOrUrl != allOpenFilesString && descriptionOrUrl != allOpenProjectsString && choice.size() > 1) description = i18np("%2, and %1 more item", "%2, and %1 more items", choice.size() - 1, choice[0].toDisplayString(QUrl::PreferLocalFile)); GrepOutputView *toolView = (GrepOutputView*)ICore::self()->uiController()-> findToolView(i18n("Find/Replace in Files"), m_plugin->toolViewFactory(), IUiController::CreateAndRaise); GrepOutputModel* outputModel = toolView->renewModel(patternString(), description); connect(job, &GrepJob::showErrorMessage, toolView, &GrepOutputView::showErrorMessage); //the GrepOutputModel gets the 'showMessage' signal to store it and forward //it to toolView connect(job, &GrepJob::showMessage, outputModel, &GrepOutputModel::showMessageSlot); connect(outputModel, &GrepOutputModel::showMessage, toolView, &GrepOutputView::showMessage); connect(toolView, &GrepOutputView::outputViewIsClosed, job, [=]() {job->kill();}); job->setOutputModel(outputModel); job->setPatternString(patternString()); job->setReplacementTemplateString(replacementTemplateString()); job->setTemplateString(templateString()); job->setFilesString(filesString()); job->setExcludeString(excludeString()); job->setDirectoryChoice(choice); job->setProjectFilesFlag( useProjectFilesFlag() ); job->setRegexpFlag( regexpFlag() ); job->setDepth( depthValue() ); job->setCaseSensitive( caseSensitiveFlag() ); ICore::self()->runController()->registerJob(job); m_plugin->rememberSearchDirectory(descriptionOrUrl); close(); } diff --git a/plugins/grepview/grepdialog.h b/plugins/grepview/grepdialog.h index d34f851154..9a8a89419d 100644 --- a/plugins/grepview/grepdialog.h +++ b/plugins/grepview/grepdialog.h @@ -1,81 +1,80 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann and the KDevelop Team * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPDIALOG_H #define KDEVPLATFORM_PLUGIN_GREPDIALOG_H #include #include #include "ui_grepwidget.h" class KConfig; class KUrlRequester; -class QPushButton; class GrepViewPlugin; class QLineEdit; class GrepDialog : public QDialog, private Ui::GrepWidget { Q_OBJECT public: explicit GrepDialog( GrepViewPlugin * plugin, QWidget *parent=0 ); ~GrepDialog(); void setPattern(const QString &pattern); void setEnableProjectBox(bool enable); QString patternString() const; QString templateString() const; QString replacementTemplateString() const; QString filesString() const; QString excludeString() const; bool useProjectFilesFlag() const; bool regexpFlag() const; bool caseSensitiveFlag() const; int depthValue() const; public Q_SLOTS: void startSearch(); ///Sets directory(ies)/files to search in. Also it can be semicolon separated list of directories/files or one of special strings: allOpenFilesString, allOpenProjectsString void setSearchLocations(const QString &dir); private Q_SLOTS: void templateTypeComboActivated(int); void patternComboEditTextChanged( const QString& ); void directoryChanged(const QString &dir); QMenu* createSyncButtonMenu(); void addUrlToMenu(QMenu* ret, const QUrl& url); void addStringToMenu(QMenu* ret, QString string); void synchronizeDirActionTriggered(bool); ///Opens the dialog to select a directory to search in, and inserts it into Location(s) field. void selectDirectoryDialog(); private: // Returns the chosen directories or files (only the top directories, not subfiles) QList< QUrl > getDirectoryChoice() const; // Returns whether the given url is a subfile/subdirectory of one of the chosen directories/files // This is slow, so don't call it too often bool isPartOfChoice(QUrl url) const; GrepViewPlugin * m_plugin; }; #endif diff --git a/plugins/grepview/grepjob.cpp b/plugins/grepview/grepjob.cpp index 719f5b76ba..57b0628ee2 100644 --- a/plugins/grepview/grepjob.cpp +++ b/plugins/grepview/grepjob.cpp @@ -1,333 +1,333 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2008 by Hamish Rodda * * rodda@kde.org * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepjob.h" #include "grepoutputmodel.h" #include "greputil.h" +#include #include #include -#include #include +#include #include -#include #include #include #include #include using namespace KDevelop; GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re) { GrepOutputItem::List res; QFile file(filename); - + if(!file.open(QIODevice::ReadOnly)) return res; int lineno = 0; - - + + // detect encoding (unicode files can be feed forever, stops when confidence reachs 99% KEncodingProber prober; while(!file.atEnd() && prober.state() == KEncodingProber::Probing && prober.confidence() < 0.99) { prober.feed(file.read(0xFF)); } - + // reads file with detected encoding file.seek(0); QTextStream stream(&file); if(prober.confidence()>0.7) stream.setCodec(prober.encoding()); while( !stream.atEnd() ) { QString data = stream.readLine(); - + // remove line terminators (in order to not match them) for(int pos = data.length()-1; pos >= 0 && (data[pos] == '\r' || data[pos] == '\n'); pos--) { data.chop(1); } - + int offset = 0; // allow empty string matching result in an infinite loop ! while( re.indexIn(data, offset)!=-1 && re.cap(0).length() > 0 ) { int start = re.pos(0); int end = start + re.cap(0).length(); - + DocumentChangePointer change = DocumentChangePointer(new DocumentChange( - IndexedString(filename), + IndexedString(filename), KTextEditor::Range(lineno, start, lineno, end), re.cap(0), QString())); - + res << GrepOutputItem(change, data, false); offset = end; } lineno++; } file.close(); return res; } GrepJob::GrepJob( QObject* parent ) : KJob( parent ) , m_workState(WorkIdle) , m_fileIndex(0) , m_useProjectFilesFlag(false) , m_regexpFlag(true) , m_caseSensitiveFlag(true) , m_depthValue(-1) , m_findSomething(false) { setCapabilities(Killable); KDevelop::ICore::self()->uiController()->registerStatus(this); - + connect(this, &GrepJob::result, this, &GrepJob::testFinishState); } QString GrepJob::statusName() const { return i18n("Find in Files"); } void GrepJob::slotFindFinished() { if(m_findThread && !m_findThread->triesToAbort()) { m_fileList = m_findThread->files(); delete m_findThread; } else { m_fileList.clear(); emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18n("Search aborted"); emitResult(); return; } if(m_fileList.isEmpty()) { m_workState = WorkIdle; emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18n("No files found matching the wildcard patterns"); //model()->slotFailed(); emitResult(); return; } - - if(!m_regexpFlag) + + if(!m_regexpFlag) { m_patternString = QRegExp::escape(m_patternString); } if(m_regexpFlag && QRegExp(m_patternString).captureCount() > 0) { m_workState = WorkIdle; emit hideProgress(this); emit clearMessage(this); m_errorMessage = i18nc("Capture is the text which is \"captured\" with () in regular expressions " "see http://doc.trolltech.com/qregexp.html#capturedTexts", "Captures are not allowed in pattern string"); emitResult(); return; } - + QString pattern = substitudePattern(m_templateString, m_patternString); m_regExp.setPattern(pattern); m_regExp.setPatternSyntax(QRegExp::RegExp2); m_regExp.setCaseSensitivity( m_caseSensitiveFlag ? Qt::CaseSensitive : Qt::CaseInsensitive ); if(pattern == QRegExp::escape(pattern)) { // enable wildcard mode when possible // if pattern has already been escaped (raw text serch) a second escape will result in a different string anyway m_regExp.setPatternSyntax(QRegExp::Wildcard); } - + m_outputModel->setRegExp(m_regExp); m_outputModel->setReplacementTemplate(m_replacementTemplateString); emit showMessage(this, i18np("Searching for %2 in one file", "Searching for %2 in %1 files", m_fileList.length(), m_regExp.pattern().toHtmlEscaped())); m_workState = WorkGrep; QMetaObject::invokeMethod( this, "slotWork", Qt::QueuedConnection); } void GrepJob::slotWork() { switch(m_workState) { case WorkIdle: m_workState = WorkCollectFiles; m_fileIndex = 0; emit showProgress(this, 0,0,0); QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); break; case WorkCollectFiles: m_findThread = new GrepFindFilesThread(this, m_directoryChoice, m_depthValue, m_filesString, m_excludeString, m_useProjectFilesFlag); emit showMessage(this, i18n("Collecting files...")); connect(m_findThread.data(), &GrepFindFilesThread::finished, this, &GrepJob::slotFindFinished); m_findThread->start(); break; case WorkGrep: if(m_fileIndex < m_fileList.length()) { emit showProgress(this, 0, m_fileList.length(), m_fileIndex); if(m_fileIndex < m_fileList.length()) { QString file = m_fileList[m_fileIndex].toLocalFile(); GrepOutputItem::List items = grepFile(file, m_regExp); if(!items.isEmpty()) { m_findSomething = true; emit foundMatches(file, items); } m_fileIndex++; } QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); } else { emit hideProgress(this); emit clearMessage(this); m_workState = WorkIdle; //model()->slotCompleted(); emitResult(); } break; - case WorkCancelled: + case WorkCancelled: emit hideProgress(this); emit clearMessage(this); emit showErrorMessage(i18n("Search aborted"), 5000); emitResult(); break; } } void GrepJob::start() { if(m_workState!=WorkIdle) return; - + m_fileList.clear(); m_workState = WorkIdle; m_fileIndex = 0; m_findSomething = false; m_outputModel->clear(); qRegisterMetaType(); connect(this, &GrepJob::foundMatches, m_outputModel, &GrepOutputModel::appendOutputs, Qt::QueuedConnection); QMetaObject::invokeMethod(this, "slotWork", Qt::QueuedConnection); } bool GrepJob::doKill() { if(m_workState!=WorkIdle && !m_findThread.isNull()) { m_workState = WorkIdle; m_findThread->tryAbort(); return false; } else { m_workState = WorkCancelled; } return true; } void GrepJob::testFinishState(KJob *job) { if(!job->error()) { if (!m_errorMessage.isEmpty()) { emit showErrorMessage(i18n("Failed: %1", m_errorMessage)); } else if (!m_findSomething) { emit showMessage(this, i18n("No results found")); } } } void GrepJob::setOutputModel(GrepOutputModel* model) { m_outputModel = model; } void GrepJob::setTemplateString(const QString& templateString) { m_templateString = templateString; } void GrepJob::setReplacementTemplateString(const QString &replTmplString) { m_replacementTemplateString = replTmplString; } void GrepJob::setFilesString(const QString& filesString) { m_filesString = filesString; } void GrepJob::setExcludeString(const QString& excludeString) { m_excludeString = excludeString; } void GrepJob::setDirectoryChoice(const QList& choice) { m_directoryChoice = choice; } void GrepJob::setCaseSensitive(bool caseSensitive) { m_caseSensitiveFlag = caseSensitive; } void GrepJob::setDepth(int depth) { m_depthValue = depth; } void GrepJob::setRegexpFlag(bool regexpFlag) { m_regexpFlag = regexpFlag; } void GrepJob::setProjectFilesFlag(bool projectFilesFlag) { m_useProjectFilesFlag = projectFilesFlag; } void GrepJob::setPatternString(const QString& patternString) { m_patternString = patternString; setObjectName(i18n("Grep: %1", m_patternString)); } diff --git a/plugins/grepview/grepjob.h b/plugins/grepview/grepjob.h index 369db264d2..cb4e81fbd4 100644 --- a/plugins/grepview/grepjob.h +++ b/plugins/grepview/grepjob.h @@ -1,124 +1,123 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2008 by Hamish Rodda * * rodda@kde.org * * Copyright 2010 Silvère Lestang * * Copyright 2010 Julien Desgats * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_GREPJOB_H #define KDEVPLATFORM_PLUGIN_GREPJOB_H -#include #include #include -#include +#include #include #include "grepfindthread.h" #include "grepoutputmodel.h" namespace KDevelop { class IProject; class ProcessLineMaker; } class QRegExp; class GrepViewPlugin; class FindReplaceTest; //FIXME: this is useful only for tests class GrepJob : public KJob, public KDevelop::IStatus { Q_OBJECT Q_INTERFACES( KDevelop::IStatus ) - + friend class GrepViewPlugin; friend class FindReplaceTest; private: ///Job can only be instanciated by plugin GrepJob( QObject *parent = 0 ); - + public: void setOutputModel(GrepOutputModel * model); void setPatternString(const QString& patternString); void setTemplateString(const QString &templateString); void setReplacementTemplateString(const QString &replTmplString); void setFilesString(const QString &filesString); void setExcludeString(const QString &excludeString); void setDirectoryChoice(const QList &choice); void setDepth(int depth); void setRegexpFlag(bool regexpFlag); void setCaseSensitive(bool caseSensitive); void setProjectFilesFlag(bool projectFilesFlag); virtual void start() override; virtual QString statusName() const override; protected: virtual bool doKill() override; // GrepOutputModel* model() const; private Q_SLOTS: void slotFindFinished(); void testFinishState(KJob *job); Q_SIGNALS: void clearMessage( KDevelop::IStatus* ) override; void showMessage( KDevelop::IStatus*, const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress( KDevelop::IStatus* ) override; void showProgress( KDevelop::IStatus*, int minimum, int maximum, int value) override; void foundMatches( const QString& filename, const GrepOutputItem::List& matches); private: Q_INVOKABLE void slotWork(); QString m_patternString; QRegExp m_regExp; QString m_regExpSimple; GrepOutputModel *m_outputModel; enum { WorkCollectFiles, WorkGrep, WorkIdle, WorkCancelled } m_workState; QList m_fileList; int m_fileIndex; QPointer m_findThread; QString m_errorMessage; QString m_templateString; QString m_replacementTemplateString; QString m_filesString; QString m_excludeString; QList m_directoryChoice; bool m_useProjectFilesFlag; bool m_regexpFlag; bool m_caseSensitiveFlag; int m_depthValue; bool m_findSomething; }; //FIXME: this function is used externally only for tests, find a way to keep it // static for a regular compilation GrepOutputItem::List grepFile(const QString &filename, const QRegExp &re); #endif diff --git a/plugins/grepview/grepviewplugin.cpp b/plugins/grepview/grepviewplugin.cpp index d20836846f..4226302cd5 100644 --- a/plugins/grepview/grepviewplugin.cpp +++ b/plugins/grepview/grepviewplugin.cpp @@ -1,236 +1,231 @@ /*************************************************************************** * Copyright 1999-2001 by Bernd Gehrmann * * bernd@kdevelop.org * * Copyright 2007 Dukju Ahn * * Copyright 2010 Benjamin Port * * Copyright 2010 Julien Desgats * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "grepviewplugin.h" #include "grepdialog.h" #include "grepoutputmodel.h" #include "grepoutputdelegate.h" #include "grepjob.h" #include "grepoutputview.h" #include "debug.h" -#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 #include #include #include #include #include Q_LOGGING_CATEGORY(PLUGIN_GREPVIEW, "kdevplatform.plugins.grepview") static QString patternFromSelection(const KDevelop::IDocument* doc) { if (!doc) return QString(); QString pattern; KTextEditor::Range range = doc->textSelection(); if( range.isValid() ) { pattern = doc->textDocument()->text( range ); } if( pattern.isEmpty() ) { pattern = doc->textWord(); } // Before anything, this removes line feeds from the // beginning and the end. int len = pattern.length(); if (len > 0 && pattern[0] == '\n') { pattern.remove(0, 1); len--; } if (len > 0 && pattern[len-1] == '\n') pattern.truncate(len-1); return pattern; } GrepViewPlugin::GrepViewPlugin( QObject *parent, const QVariantList & ) : KDevelop::IPlugin( "kdevgrepview", parent ), m_currentJob(0) { setXMLFile("kdevgrepview.rc"); QDBusConnection::sessionBus().registerObject( "/org/kdevelop/GrepViewPlugin", this, QDBusConnection::ExportScriptableSlots ); QAction*action = actionCollection()->addAction("edit_grep"); action->setText(i18n("Find/Replace in Fi&les...")); actionCollection()->setDefaultShortcut( action, QKeySequence("Ctrl+Alt+F") ); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); action->setToolTip( i18n("Search for expressions over several files") ); action->setWhatsThis( i18n("Opens the 'Find/Replace in files' dialog. There you " "can enter a regular expression which is then " "searched for within all files in the directories " "you specify. Matches will be displayed, you " "can switch to a match directly. You can also do replacement.") ); action->setIcon(QIcon::fromTheme("edit-find")); // instantiate delegate, it's supposed to be deleted via QObject inheritance new GrepOutputDelegate(this); m_factory = new GrepOutputViewFactory(this); core()->uiController()->addToolView(i18n("Find/Replace in Files"), m_factory); } GrepOutputViewFactory* GrepViewPlugin::toolViewFactory() const { return m_factory; } GrepViewPlugin::~GrepViewPlugin() { } void GrepViewPlugin::unload() { core()->uiController()->removeToolView(m_factory); } void GrepViewPlugin::startSearch(QString pattern, QString directory, bool showOptions) { m_directory = directory; showDialog(false, pattern, showOptions); } KDevelop::ContextMenuExtension GrepViewPlugin::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension extension = KDevelop::IPlugin::contextMenuExtension(context); if( context->type() == KDevelop::Context::ProjectItemContext ) { KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); // verify if there is only one folder selected if ((items.count() == 1) && (items.first()->folder())) { QAction* action = new QAction( i18n( "Find/Replace in This Folder" ), this ); action->setIcon(QIcon::fromTheme("edit-find")); m_contextMenuDirectory = items.at(0)->folder()->path().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } if ( context->type() == KDevelop::Context::EditorContext ) { KDevelop::EditorContext *econtext = dynamic_cast(context); if ( econtext->view()->selection() ) { QAction* action = new QAction(QIcon::fromTheme("edit-find"), i18n("&Find/Replace in Files"), this); connect(action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromMenu); extension.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, action); } } if(context->type() == KDevelop::Context::FileContext) { KDevelop::FileContext *fcontext = dynamic_cast(context); // TODO: just stat() or QFileInfo().isDir() for local files? should be faster than mime type checking QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(fcontext->urls().first()); static const QMimeType directoryMime = QMimeDatabase().mimeTypeForName("inode/directory"); if (mimetype == directoryMime) { QAction* action = new QAction( i18n( "Find/Replace in This Folder" ), this ); action->setIcon(QIcon::fromTheme("edit-find")); m_contextMenuDirectory = fcontext->urls().first().toLocalFile(); connect( action, &QAction::triggered, this, &GrepViewPlugin::showDialogFromProject); extension.addAction( KDevelop::ContextMenuExtension::ExtensionGroup, action ); } } return extension; } void GrepViewPlugin::showDialog(bool setLastUsed, QString pattern, bool showOptions) { GrepDialog* dlg = new GrepDialog( this, core()->uiController()->activeMainWindow() ); KDevelop::IDocument* doc = core()->documentController()->activeDocument(); if(!pattern.isEmpty()) { dlg->setPattern(pattern); } else if(!setLastUsed) { QString pattern = patternFromSelection(doc); if (!pattern.isEmpty()) { dlg->setPattern( pattern ); } } //if directory is empty then use a default value from the config file. if (!m_directory.isEmpty()) { dlg->setSearchLocations(m_directory); } if(showOptions) dlg->show(); else{ dlg->startSearch(); dlg->deleteLater(); } } void GrepViewPlugin::showDialogFromMenu() { showDialog(); } void GrepViewPlugin::showDialogFromProject() { rememberSearchDirectory(m_contextMenuDirectory); showDialog(); } void GrepViewPlugin::rememberSearchDirectory(QString const & directory) { m_directory = directory; } GrepJob* GrepViewPlugin::newGrepJob() { if(m_currentJob != 0) { m_currentJob->kill(); } m_currentJob = new GrepJob(); connect(m_currentJob, &GrepJob::finished, this, &GrepViewPlugin::jobFinished); return m_currentJob; } GrepJob* GrepViewPlugin::grepJob() { return m_currentJob; } void GrepViewPlugin::jobFinished(KJob* job) { if(job == m_currentJob) { m_currentJob = 0; } } diff --git a/plugins/konsole/kdevkonsoleview.cpp b/plugins/konsole/kdevkonsoleview.cpp index b2eb647318..ad5533b2be 100644 --- a/plugins/konsole/kdevkonsoleview.cpp +++ b/plugins/konsole/kdevkonsoleview.cpp @@ -1,159 +1,154 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevkonsoleview.h" #include "debug.h" -#include -#include #include -#include -#include +#include #include -#include #include +#include -#include -#include #include -#include -#include -#include +#include +#include +#include #include #include #include #include "kdevkonsoleviewplugin.h" class KDevKonsoleViewPrivate { public: KDevKonsoleViewPlugin* mplugin; KDevKonsoleView* m_view; KParts::ReadOnlyPart *konsolepart; QVBoxLayout *m_vbox; // TODO: remove this once we can depend on a Qt version that includes https://codereview.qt-project.org/#/c/83800/ QMetaObject::Connection m_partDestroyedConnection; void _k_slotTerminalClosed(); void init( KPluginFactory* factory ) { Q_ASSERT( konsolepart == 0 ); Q_ASSERT( factory != 0 ); if ( ( konsolepart = factory->create( m_view ) ) ) { QObject::disconnect(m_partDestroyedConnection); m_partDestroyedConnection = QObject::connect(konsolepart, &KParts::ReadOnlyPart::destroyed, m_view, [&] { _k_slotTerminalClosed(); }); konsolepart->widget() ->setFocusPolicy( Qt::WheelFocus ); konsolepart->widget() ->setFocus(); konsolepart->widget() ->installEventFilter( m_view ); if ( QFrame * frame = qobject_cast( konsolepart->widget() ) ) frame->setFrameStyle( QFrame::Panel | QFrame::Sunken ); m_vbox->addWidget( konsolepart->widget() ); m_view->setFocusProxy( konsolepart->widget() ); konsolepart->widget() ->show(); TerminalInterface* interface = qobject_cast(konsolepart); Q_ASSERT(interface); interface->showShellInDir( QString() ); interface->sendInput( "kdevelop! -s \"" + KDevelop::ICore::self()->activeSession()->id().toString() + "\"\n" ); }else { qCDebug(PLUGIN_KONSOLE) << "Couldn't create KParts::ReadOnlyPart from konsole factory!"; } } ~KDevKonsoleViewPrivate() { QObject::disconnect(m_partDestroyedConnection); } }; void KDevKonsoleViewPrivate::_k_slotTerminalClosed() { konsolepart = 0; init( mplugin->konsoleFactory() ); } KDevKonsoleView::KDevKonsoleView( KDevKonsoleViewPlugin *plugin, QWidget* parent ) : QWidget( parent ), d(new KDevKonsoleViewPrivate) { d->mplugin = plugin; d->m_view = this; d->konsolepart = 0; setObjectName( i18n( "Konsole" ) ); setWindowIcon( QIcon::fromTheme( "utilities-terminal" ) ); setWindowTitle( i18n( "Konsole" ) ); d->m_vbox = new QVBoxLayout( this ); d->m_vbox->setMargin( 0 ); d->m_vbox->setSpacing( 0 ); d->init( d->mplugin->konsoleFactory() ); //TODO Make this configurable in the future, // but by default the konsole shouldn't // automatically switch directories on you. // connect( KDevelop::Core::documentController(), SIGNAL(documentActivated(KDevDocument*)), // this, SLOT(documentActivated(KDevDocument*)) ); } KDevKonsoleView::~KDevKonsoleView() { delete d; } void KDevKonsoleView::setDirectory( const QUrl &url ) { if ( !url.isValid() || !url.isLocalFile() ) return ; if ( d->konsolepart && url != d->konsolepart->url() ) d->konsolepart->openUrl( url ); } bool KDevKonsoleView::eventFilter( QObject* obj, QEvent *e ) { switch( e->type() ) { case QEvent::ShortcutOverride: { QKeyEvent *k = static_cast(e); // Don't propagate Esc to the top level, it should be used by konsole if (k->key() == Qt::Key_Escape) { if (d->konsolepart && d->konsolepart->widget()) { e->accept(); return true; } } break; } default: break; } return QWidget::eventFilter( obj, e ); } #include "moc_kdevkonsoleview.cpp" diff --git a/plugins/konsole/kdevkonsoleviewplugin.cpp b/plugins/konsole/kdevkonsoleviewplugin.cpp index 19b83dc0b5..97592c9aac 100644 --- a/plugins/konsole/kdevkonsoleviewplugin.cpp +++ b/plugins/konsole/kdevkonsoleviewplugin.cpp @@ -1,97 +1,97 @@ /*************************************************************************** * Copyright 2003, 2006 Adam Treat * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "kdevkonsoleviewplugin.h" -#include -#include +#include +#include #include #include -#include + #include "kdevkonsoleview.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_KONSOLE, "kdevplatform.plugins.konsole") QObject* createKonsoleView( QWidget*, QObject* op, const QVariantList& args) { KService::Ptr service = KService::serviceByDesktopName("konsolepart"); KPluginFactory* factory = nullptr; if (service) { factory = KPluginLoader(*service.data()).factory(); } if (!factory) { qWarning() << "Failed to load 'konsolepart' plugin"; } return new KDevKonsoleViewPlugin(factory, op, args); } K_PLUGIN_FACTORY_WITH_JSON(KonsoleViewFactory, "kdevkonsoleview.json", registerPlugin( QString(), &createKonsoleView );) class KDevKonsoleViewFactory: public KDevelop::IToolViewFactory{ public: KDevKonsoleViewFactory(KDevKonsoleViewPlugin *plugin): mplugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) override { return new KDevKonsoleView(mplugin, parent); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.KonsoleView"; } private: KDevKonsoleViewPlugin *mplugin; }; KDevKonsoleViewPlugin::KDevKonsoleViewPlugin( KPluginFactory* konsoleFactory, QObject *parent, const QVariantList & ) : KDevelop::IPlugin( QStringLiteral("kdevkonsoleview"), parent ) , m_konsoleFactory(konsoleFactory) , m_viewFactory(konsoleFactory ? new KDevKonsoleViewFactory(this) : nullptr) { if (m_viewFactory) { core()->uiController()->addToolView("Konsole", m_viewFactory); } } void KDevKonsoleViewPlugin::unload() { if (m_viewFactory) { core()->uiController()->removeToolView(m_viewFactory); } } bool KDevKonsoleViewPlugin::hasError() const { return !m_viewFactory; } QString KDevKonsoleViewPlugin::errorDescription() const { return !m_viewFactory ? i18n("Failed to load 'konsolepart' plugin") : QString(); } KPluginFactory* KDevKonsoleViewPlugin::konsoleFactory() const { return m_konsoleFactory; } KDevKonsoleViewPlugin::~KDevKonsoleViewPlugin() { } #include "kdevkonsoleviewplugin.moc" diff --git a/plugins/openwith/openwithplugin.cpp b/plugins/openwith/openwithplugin.cpp index a6338779b1..7fd2f48a35 100644 --- a/plugins/openwith/openwithplugin.cpp +++ b/plugins/openwith/openwithplugin.cpp @@ -1,263 +1,262 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "openwithplugin.h" -#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 -#include -#include -#include -#include + #include #include #include #include -#include -#include -#include using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(KDevOpenWithFactory, "kdevopenwith.json", registerPlugin();) OpenWithPlugin::OpenWithPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevopenwith", parent ), m_actionMap( 0 ) { KDEV_USE_EXTENSION_INTERFACE( IOpenWith ) } OpenWithPlugin::~OpenWithPlugin() { } KDevelop::ContextMenuExtension OpenWithPlugin::contextMenuExtension( KDevelop::Context* context ) { // do not recurse if (context->hasType(KDevelop::Context::OpenWithContext)) { return ContextMenuExtension(); } m_urls.clear(); m_actionMap.reset(); m_services.clear(); FileContext* filectx = dynamic_cast( context ); ProjectItemContext* projctx = dynamic_cast( context ); if ( filectx && filectx->urls().count() > 0 ) { m_urls = filectx->urls(); } else if ( projctx && projctx->items().count() > 0 ) { // For now, let's handle *either* files only *or* directories only const int wantedType = projctx->items().first()->type(); foreach( ProjectBaseItem* item, projctx->items() ) { if (wantedType == ProjectBaseItem::File && item->file()) { m_urls << item->file()->path().toUrl(); } else if ((wantedType == ProjectBaseItem::Folder || wantedType == ProjectBaseItem::BuildFolder) && item->folder()) { m_urls << item->folder()->path().toUrl(); } } } if (m_urls.isEmpty()) { return KDevelop::ContextMenuExtension(); } m_actionMap.reset(new QSignalMapper( this )); connect( m_actionMap.data(), static_cast(&QSignalMapper::mapped), this, &OpenWithPlugin::open ); // Ok, lets fetch the mimetype for the !!first!! url and the relevant services // TODO: Think about possible alternatives to using the mimetype of the first url. QMimeType mimetype = QMimeDatabase().mimeTypeForUrl(m_urls.first()); m_mimeType = mimetype.name(); QList partActions = actionsForServiceType("KParts/ReadOnlyPart"); QList appActions = actionsForServiceType("Application"); OpenWithContext subContext(m_urls, mimetype); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &subContext ); foreach( const ContextMenuExtension& ext, extensions ) { appActions += ext.actions(ContextMenuExtension::OpenExternalGroup); partActions += ext.actions(ContextMenuExtension::OpenEmbeddedGroup); } // Now setup a menu with actions for each part and app QMenu* menu = new QMenu( i18n("Open With" ) ); menu->setIcon( QIcon::fromTheme( "document-open" ) ); if (!partActions.isEmpty()) { menu->addSection(i18n("Embedded Editors")); menu->addActions( partActions ); } if (!appActions.isEmpty()) { menu->addSection(i18n("External Applications")); menu->addActions( appActions ); } QAction* openAction = new QAction( i18n( "Open" ), this ); openAction->setIcon( QIcon::fromTheme( "document-open" ) ); connect( openAction, &QAction::triggered, this, &OpenWithPlugin::openDefault ); KDevelop::ContextMenuExtension ext; ext.addAction( KDevelop::ContextMenuExtension::FileGroup, openAction ); if (!menu->isEmpty()) { ext.addAction(KDevelop::ContextMenuExtension::FileGroup, menu->menuAction()); } else { delete menu; } return ext; } bool sortActions(QAction* left, QAction* right) { return left->text() < right->text(); } bool isTextEditor(const KService::Ptr& service) { return service->serviceTypes().contains( "KTextEditor/Document" ); } QString defaultForMimeType(const QString& mimeType) { KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (config.hasKey(mimeType)) { QString storageId = config.readEntry(mimeType, QString()); if (!storageId.isEmpty() && KService::serviceByStorageId(storageId)) { return storageId; } } return QString(); } QList OpenWithPlugin::actionsForServiceType( const QString& serviceType ) { KService::List list = KMimeTypeTrader::self()->query( m_mimeType, serviceType ); KService::Ptr pref = KMimeTypeTrader::self()->preferredService( m_mimeType, serviceType ); m_services += list; QList actions; QAction* standardAction = 0; const QString defaultId = defaultForMimeType(m_mimeType); foreach( KService::Ptr svc, list ) { QAction* act = new QAction( isTextEditor(svc) ? i18n("Default Editor") : svc->name(), this ); act->setIcon( QIcon::fromTheme( svc->icon() ) ); if (svc->storageId() == defaultId || (defaultId.isEmpty() && isTextEditor(svc))) { QFont font = act->font(); font.setBold(true); act->setFont(font); } connect(act, &QAction::triggered, m_actionMap.data(), static_cast(&QSignalMapper::map)); m_actionMap->setMapping( act, svc->storageId() ); actions << act; if ( isTextEditor(svc) ) { standardAction = act; } else if ( svc->storageId() == pref->storageId() ) { standardAction = act; } } std::sort(actions.begin(), actions.end(), sortActions); if (standardAction) { actions.removeOne(standardAction); actions.prepend(standardAction); } return actions; } void OpenWithPlugin::openDefault() { // check preferred handler const QString defaultId = defaultForMimeType(m_mimeType); if (!defaultId.isEmpty()) { open(defaultId); return; } // default handlers if (m_mimeType == "inode/directory") { KService::Ptr service = KMimeTypeTrader::self()->preferredService(m_mimeType); KRun::run(*service, m_urls, ICore::self()->uiController()->activeMainWindow()); } else { foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u ); } } } void OpenWithPlugin::open( const QString& storageid ) { KService::Ptr svc = KService::serviceByStorageId( storageid ); if( svc->isApplication() ) { KRun::run( *svc, m_urls, ICore::self()->uiController()->activeMainWindow() ); } else { QString prefName = svc->desktopEntryName(); if ( isTextEditor(svc) ) { // If the user chose a KTE part, lets make sure we're creating a TextDocument instead of // a PartDocument by passing no preferredpart to the documentcontroller // TODO: Solve this rather inside DocumentController prefName = ""; } foreach( const QUrl& u, m_urls ) { ICore::self()->documentController()->openDocument( u, prefName ); } } KConfigGroup config = KSharedConfig::openConfig()->group("Open With Defaults"); if (storageid != config.readEntry(m_mimeType, QString())) { int setDefault = KMessageBox::questionYesNo( qApp->activeWindow(), i18nc("%1: mime type name, %2: app/part name", "Do you want to open all '%1' files by default with %2?", m_mimeType, svc->name() ), i18n("Set as default?"), KStandardGuiItem::yes(), KStandardGuiItem::no(), QStringLiteral("OpenWith-%1").arg(m_mimeType) ); if (setDefault == KMessageBox::Yes) { config.writeEntry(m_mimeType, storageid); } } } void OpenWithPlugin::openFilesInternal( const QList& files ) { if (files.isEmpty()) { return; } m_urls = files; m_mimeType = QMimeDatabase().mimeTypeForUrl(m_urls.first()).name(); openDefault(); } #include "openwithplugin.moc" diff --git a/plugins/openwith/openwithplugin.h b/plugins/openwith/openwithplugin.h index 1ed2490978..0dbfc3ce94 100644 --- a/plugins/openwith/openwithplugin.h +++ b/plugins/openwith/openwithplugin.h @@ -1,62 +1,63 @@ /* * This file is part of KDevelop * Copyright 2009 Andreas Pakulat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H #define KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H #include #include -#include + +#include #include "iopenwith.h" class QSignalMapper; namespace KDevelop { class ContextMenuExtension; class Context; } class OpenWithPlugin : public KDevelop::IPlugin, public KDevelop::IOpenWith { Q_OBJECT Q_INTERFACES( KDevelop::IOpenWith ) public: OpenWithPlugin( QObject* parent, const QVariantList& args ); virtual ~OpenWithPlugin(); virtual KDevelop::ContextMenuExtension contextMenuExtension ( KDevelop::Context* context ) override; protected: virtual void openFilesInternal( const QList& files ) override; private slots: void open( const QString& ); void openDefault(); private: QList actionsForServiceType( const QString& serviceType ); QScopedPointer m_actionMap; QList m_urls; QString m_mimeType; KService::List m_services; }; #endif // KDEVPLATFORM_PLUGIN_OPENWITHPLUGIN_H diff --git a/plugins/pastebin/pastebinplugin.cpp b/plugins/pastebin/pastebinplugin.cpp index 739874515f..84ce63da6d 100644 --- a/plugins/pastebin/pastebinplugin.cpp +++ b/plugins/pastebin/pastebinplugin.cpp @@ -1,105 +1,104 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pastebinplugin.h" + +#include #include #include -#include -#include -#include +#include +#include #include -#include -#include #include -#include +#include using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_PASTEBIN, "kdevplatform.plugins.pastebin") K_PLUGIN_FACTORY_WITH_JSON(KDevPastebinFactory, "kdevpastebin.json", registerPlugin();) PastebinPlugin::PastebinPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevpastebin", parent ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchExporter ) } PastebinPlugin::~PastebinPlugin() {} namespace { QByteArray urlToData(const QUrl& url) { QByteArray ret; if(url.isLocalFile()) { QFile f(url.toLocalFile()); Q_ASSERT(f.exists()); bool corr=f.open(QFile::ReadOnly | QFile::Text); Q_ASSERT(corr); Q_UNUSED(corr); ret = f.readAll(); } else { //TODO: add downloading the data } return ret; } } void PastebinPlugin::exportPatch(IPatchSource::Ptr source) { qCDebug(PLUGIN_PASTEBIN) << "exporting patch to pastebin" << source->file(); QByteArray bytearray = "api_option=paste&api_paste_private=1&api_paste_name=kdevelop-pastebin-plugin&api_paste_expire_date=1D&api_paste_format=diff&api_dev_key=0c8b6add8e0f6d53f61fe5ce870a1afa&api_paste_code="+QUrl::toPercentEncoding(urlToData(source->file()), "/"); QUrl url("http://pastebin.com/api/api_post.php"); KIO::TransferJob *tf = KIO::http_post(url, bytearray); tf->addMetaData("content-type","Content-Type: application/x-www-form-urlencoded"); connect(tf, &KIO::TransferJob::data, this, &PastebinPlugin::data); m_result.insert(tf, QByteArray()); KIO::getJobTracker()->registerJob(tf); } void PastebinPlugin::data(KIO::Job* job, const QByteArray &data) { QMap< KIO::Job*, QString >::iterator it = m_result.find(job); Q_ASSERT(it!=m_result.end()); if (data.isEmpty()) { if (job->error()) { KMessageBox::error(0, job->errorString()); } else if (it->isEmpty() || it->startsWith("ERROR")) { KMessageBox::error(0, *it); } else { QString htmlLink=QStringLiteral("%1").arg(*it); KMessageBox::information(0, i18nc("The parameter is the link where the patch is stored", "You can find your patch at:
%1
", htmlLink), QString(), QString(), KMessageBox::AllowLink | KMessageBox::Notify); } m_result.erase(it); } else { *it += data; } } #include "pastebinplugin.moc" diff --git a/plugins/problemreporter/problemtreeview.cpp b/plugins/problemreporter/problemtreeview.cpp index 2e890b5af9..1c42034e60 100644 --- a/plugins/problemreporter/problemtreeview.cpp +++ b/plugins/problemreporter/problemtreeview.cpp @@ -1,268 +1,266 @@ /* * KDevelop Problem Reporter * * Copyright (c) 2006-2007 Hamish Rodda * Copyright 2006 Adam Treat * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "problemtreeview.h" -#include -#include +#include #include -#include #include - -#include -#include -#include #include #include +#include + +#include +#include #include #include #include #include #include #include #include "problemreporterplugin.h" #include "problemmodel.h" //#include "modeltest.h" using namespace KDevelop; ProblemTreeView::ProblemTreeView(QWidget* parent, ProblemReporterPlugin* plugin) : QTreeView(parent) , m_plugin(plugin) { setObjectName("Problem Reporter Tree"); setWindowTitle(i18n("Problems")); setWindowIcon( QIcon::fromTheme("dialog-information") ); ///@todo Use a proper icon setRootIsDecorated(false); setWhatsThis( i18n( "Problems" ) ); setModel(m_plugin->getModel()); header()->setStretchLastSection(false); QAction* fullUpdateAction = new QAction(this); fullUpdateAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); fullUpdateAction->setText(i18n("Force Full Update")); fullUpdateAction->setToolTip(i18nc("@info:tooltip", "Re-parse all watched documents")); fullUpdateAction->setIcon(QIcon::fromTheme("view-refresh")); connect(fullUpdateAction, &QAction::triggered, model(), &ProblemModel::forceFullUpdate); addAction(fullUpdateAction); QAction* showImportsAction = new QAction(this); addAction(showImportsAction); showImportsAction->setCheckable(true); showImportsAction->setChecked(false); showImportsAction->setText(i18n("Show Imports")); showImportsAction->setToolTip(i18nc("@info:tooltip", "Display problems in imported files")); this->model()->setShowImports(false); connect(showImportsAction, &QAction::triggered, this->model(), &ProblemModel::setShowImports); KActionMenu* scopeMenu = new KActionMenu(this); scopeMenu->setDelayed(false); scopeMenu->setText(i18n("Scope")); scopeMenu->setToolTip(i18nc("@info:tooltip", "Which files to display the problems for")); QActionGroup* scopeActions = new QActionGroup(this); QAction* currentDocumentAction = new QAction(this); currentDocumentAction->setText(i18n("Current Document")); currentDocumentAction->setToolTip(i18nc("@info:tooltip", "Display problems in current document")); QAction* openDocumentsAction = new QAction(this); openDocumentsAction->setText(i18n("Open Documents")); openDocumentsAction->setToolTip(i18nc("@info:tooltip", "Display problems in all open documents")); QAction* currentProjectAction = new QAction(this); currentProjectAction->setText(i18n("Current Project")); currentProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in current project")); QAction* allProjectAction = new QAction(this); allProjectAction->setText(i18n("All Projects")); allProjectAction->setToolTip(i18nc("@info:tooltip", "Display problems in all projects")); QAction* scopeActionArray[] = {currentDocumentAction, openDocumentsAction, currentProjectAction, allProjectAction}; for (int i = 0; i < 4; ++i) { scopeActionArray[i]->setCheckable(true); scopeActions->addAction(scopeActionArray[i]); scopeMenu->addAction(scopeActionArray[i]); } addAction(scopeMenu); currentDocumentAction->setChecked(true); model()->setScope(ProblemModel::CurrentDocument); QSignalMapper * scopeMapper = new QSignalMapper(this); scopeMapper->setMapping(currentDocumentAction, ProblemModel::CurrentDocument); scopeMapper->setMapping(openDocumentsAction, ProblemModel::OpenDocuments); scopeMapper->setMapping(currentProjectAction, ProblemModel::CurrentProject); scopeMapper->setMapping(allProjectAction, ProblemModel::AllProjects); connect(currentDocumentAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(openDocumentsAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(currentProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(allProjectAction, &QAction::triggered, scopeMapper, static_cast(&QSignalMapper::map)); connect(scopeMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setScope); KActionMenu* severityMenu = new KActionMenu(i18n("Severity"), this); severityMenu->setDelayed(false); severityMenu->setToolTip(i18nc("@info:tooltip", "Select the lowest level of problem severity to be displayed")); QActionGroup* severityActions = new QActionGroup(this); QAction* errorSeverityAction = new QAction(i18n("Error"), this); errorSeverityAction->setToolTip(i18nc("@info:tooltip", "Display only errors")); QAction* warningSeverityAction = new QAction(i18n("Warning"), this); warningSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors and warnings")); QAction* hintSeverityAction = new QAction(i18n("Hint"), this); hintSeverityAction->setToolTip(i18nc("@info:tooltip", "Display errors, warnings and hints")); QAction* severityActionArray[] = {errorSeverityAction, warningSeverityAction, hintSeverityAction}; for (int i = 0; i < 3; ++i) { severityActionArray[i]->setCheckable(true); severityActions->addAction(severityActionArray[i]); severityMenu->addAction(severityActionArray[i]); } addAction(severityMenu); hintSeverityAction->setChecked(true); model()->setSeverity(ProblemData::Hint); QSignalMapper * severityMapper = new QSignalMapper(this); severityMapper->setMapping(errorSeverityAction, ProblemData::Error); severityMapper->setMapping(warningSeverityAction, ProblemData::Warning); severityMapper->setMapping(hintSeverityAction, ProblemData::Hint); connect(errorSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(warningSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(hintSeverityAction, &QAction::triggered, severityMapper, static_cast(&QSignalMapper::map)); connect(severityMapper, static_cast(&QSignalMapper::mapped), model(), &ProblemModel::setSeverity); connect(this, &ProblemTreeView::activated, this, &ProblemTreeView::itemActivated); } ProblemTreeView::~ProblemTreeView() { } void ProblemTreeView::itemActivated(const QModelIndex& index) { if (!index.isValid()) return; KTextEditor::Cursor start; QUrl url; { // TODO: is this really necessary? DUChainReadLocker lock(DUChain::lock()); ProblemPointer problem = model()->problemForIndex(index); url = problem->finalLocation().document.toUrl(); start = problem->finalLocation().start(); } m_plugin->core()->documentController()->openDocument(url, start); } void ProblemTreeView::resizeColumns() { // Do actual resizing only if the widget is visible and there are not too many items const int ResizeRowLimit = 15; if (isVisible() && model()->rowCount() > 0 && model()->rowCount() < ResizeRowLimit) { const int columnCount = model()->columnCount(); QVector widthArray(columnCount); int totalWidth = 0; for (int i = 0; i < columnCount; ++i) { widthArray[i] = columnWidth(i); totalWidth += widthArray[i]; } for (int i = 0; i < columnCount; ++i) { int columnWidthHint = qMax(sizeHintForColumn(i), header()->sectionSizeHint(i)); if (columnWidthHint - widthArray[i] > 0) { if (columnWidthHint - widthArray[i] < width() - totalWidth) { // enough space to resize setColumnWidth(i, columnWidthHint); totalWidth += (columnWidthHint - widthArray[i]); } else { setColumnWidth(i, widthArray[i] + width() - totalWidth); break; } } } } } void ProblemTreeView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) { QTreeView::dataChanged(topLeft, bottomRight, roles); resizeColumns(); } void ProblemTreeView::reset() { QTreeView::reset(); resizeColumns(); } ProblemModel * ProblemTreeView::model() const { return static_cast(QTreeView::model()); } void ProblemTreeView::setModel(QAbstractItemModel* model) { Q_ASSERT(qobject_cast(model)); QTreeView::setModel(model); } void ProblemTreeView::contextMenuEvent(QContextMenuEvent* event) { QModelIndex index = indexAt(event->pos()); if(index.isValid()) { KDevelop::ProblemPointer problem = model()->problemForIndex(index); if(problem) { QExplicitlySharedDataPointer solution = problem->solutionAssistant(); if(solution) { QList actions; foreach(KDevelop::IAssistantAction::Ptr action, solution->actions()) { actions << action->toKAction(); } if(!actions.isEmpty()) { QString title = solution->title(); title = KDevelop::htmlToPlainText(title); title.replace("'", "\'"); QPointer m = new QMenu(this); m->addSection(title); m->addActions(actions); m->exec(event->globalPos()); delete m; } } } } } void ProblemTreeView::showEvent(QShowEvent * event) { Q_UNUSED(event) for (int i = 0; i < model()->columnCount(); ++i) resizeColumnToContents(i); } diff --git a/plugins/projectmanagerview/projectmanagerview.cpp b/plugins/projectmanagerview/projectmanagerview.cpp index 95ae9a9027..5e0d72af87 100644 --- a/plugins/projectmanagerview/projectmanagerview.cpp +++ b/plugins/projectmanagerview/projectmanagerview.cpp @@ -1,267 +1,266 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmanagerview.h" #include #include #include +#include #include -#include -#include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" #include #include #include "projectmanagerviewplugin.h" #include "vcsoverlayproxymodel.h" #include "ui_projectmanagerview.h" #include "debug.h" using namespace KDevelop; ProjectManagerViewItemContext::ProjectManagerViewItemContext(const QList< ProjectBaseItem* >& items, ProjectManagerView* view) : ProjectItemContextImpl(items), m_view(view) { } ProjectManagerView *ProjectManagerViewItemContext::view() const { return m_view; } static const char sessionConfigGroup[] = "ProjectManagerView"; static const char splitterStateConfigKey[] = "splitterState"; static const int projectTreeViewStrechFactor = 75; // % static const int projectBuildSetStrechFactor = 25; // % ProjectManagerView::ProjectManagerView( ProjectManagerViewPlugin* plugin, QWidget *parent ) : QWidget( parent ), m_ui(new Ui::ProjectManagerView), m_plugin(plugin) { m_ui->setupUi( this ); m_ui->projectTreeView->installEventFilter(this); setWindowIcon( QIcon::fromTheme( "project-development" ) ); KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); if (pmviewConfig.hasKey(splitterStateConfigKey)) { QByteArray geometry = pmviewConfig.readEntry(splitterStateConfigKey, QByteArray()); m_ui->splitter->restoreState(geometry); } else { m_ui->splitter->setStretchFactor(0, projectTreeViewStrechFactor); m_ui->splitter->setStretchFactor(1, projectBuildSetStrechFactor); } m_syncAction = plugin->actionCollection()->action("locate_document"); Q_ASSERT(m_syncAction); m_syncAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); m_syncAction->setText(i18n("Locate Current Document")); m_syncAction->setToolTip(i18n("Locates the current document in the project tree and selects it.")); m_syncAction->setIcon(QIcon::fromTheme("dirsync")); m_syncAction->setShortcut(Qt::ControlModifier + Qt::Key_Less); connect(m_syncAction, &QAction::triggered, this, &ProjectManagerView::locateCurrentDocument); addAction(m_syncAction); updateSyncAction(); addAction(plugin->actionCollection()->action("project_build")); addAction(plugin->actionCollection()->action("project_install")); addAction(plugin->actionCollection()->action("project_clean")); connect(m_ui->projectTreeView, &ProjectTreeView::activate, this, &ProjectManagerView::open); m_ui->buildSetView->setProjectView( this ); m_modelFilter = new ProjectProxyModel( this ); m_modelFilter->setSourceModel(ICore::self()->projectController()->projectModel()); m_overlayProxy = new VcsOverlayProxyModel( this ); m_overlayProxy->setSourceModel(m_modelFilter); m_ui->projectTreeView->setModel( m_overlayProxy ); connect( m_ui->projectTreeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ProjectManagerView::selectionChanged ); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentClosed, this, &ProjectManagerView::updateSyncAction); connect( KDevelop::ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ProjectManagerView::updateSyncAction); connect( qobject_cast(KDevelop::ICore::self()->uiController()->activeMainWindow()), &Sublime::MainWindow::areaChanged, this, &ProjectManagerView::updateSyncAction); selectionChanged(); //Update the "sync" button after the initialization has completed, to see whether there already is some open documents QMetaObject::invokeMethod(this, "updateSyncAction", Qt::QueuedConnection); // Need to set this to get horizontal scrollbar. Also needs to be done after // the setModel call m_ui->projectTreeView->header()->setSectionResizeMode( QHeaderView::ResizeToContents ); } bool ProjectManagerView::eventFilter(QObject* obj, QEvent* event) { if (obj == m_ui->projectTreeView) { if (event->type() == QEvent::KeyRelease) { QKeyEvent* keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Delete && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->removeItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_F2 && keyEvent->modifiers() == Qt::NoModifier) { m_plugin->renameItems(selectedItems()); return true; } else if (keyEvent->key() == Qt::Key_C && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->copyFromContextMenu(); return true; } else if (keyEvent->key() == Qt::Key_V && keyEvent->modifiers() == Qt::ControlModifier) { m_plugin->pasteFromContextMenu(); return true; } } } return QObject::eventFilter(obj, event); } void ProjectManagerView::selectionChanged() { m_ui->buildSetView->selectionChanged(); QList selected; foreach( const QModelIndex& idx, m_ui->projectTreeView->selectionModel()->selectedRows() ) { selected << ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView( idx )); } selected.removeAll(0); KDevelop::ICore::self()->selectionController()->updateSelection( new ProjectManagerViewItemContext( selected, this ) ); } void ProjectManagerView::updateSyncAction() { m_syncAction->setEnabled( KDevelop::ICore::self()->documentController()->activeDocument() ); } ProjectManagerView::~ProjectManagerView() { KConfigGroup pmviewConfig(ICore::self()->activeSession()->config(), sessionConfigGroup); pmviewConfig.writeEntry(splitterStateConfigKey, m_ui->splitter->saveState()); pmviewConfig.sync(); delete m_ui; } QList ProjectManagerView::selectedItems() const { QList items; foreach( const QModelIndex &idx, m_ui->projectTreeView->selectionModel()->selectedIndexes() ) { KDevelop::ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemFromIndex(indexFromView(idx)); if( item ) items << item; else qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "adding an unknown item"; } return items; } void ProjectManagerView::selectItems(const QList< ProjectBaseItem* >& items) { QItemSelection selection; foreach (ProjectBaseItem *item, items) { QModelIndex indx = indexToView(item->index()); selection.append(QItemSelectionRange(indx, indx)); m_ui->projectTreeView->setCurrentIndex(indx); } m_ui->projectTreeView->selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } void ProjectManagerView::expandItem(ProjectBaseItem* item) { m_ui->projectTreeView->expand( indexToView(item->index())); } void ProjectManagerView::locateCurrentDocument() { ICore::self()->uiController()->raiseToolView(this); KDevelop::IDocument *doc = ICore::self()->documentController()->activeDocument(); if (!doc) { // in theory we should never get a null pointer as the action is only enabled // when there is an active document. // but: in practice it can happen that you close the last document and press // the shortcut to locate a doc or vice versa... so just do the failsafe thing here... return; } QModelIndex bestMatch; foreach (IProject* proj, ICore::self()->projectController()->projects()) { foreach (KDevelop::ProjectFileItem* item, proj->filesForUrl(doc->url())) { QModelIndex index = indexToView(item->index()); if (index.isValid()) { if (!bestMatch.isValid()) { bestMatch = index; } else if (KDevelop::ProjectBaseItem* parent = item->parent()) { // prefer files in their real folders over the 'copies' in the target folders if (!parent->target()) { bestMatch = index; break; } } } } } if (bestMatch.isValid()) { m_ui->projectTreeView->clearSelection(); m_ui->projectTreeView->setCurrentIndex(bestMatch); m_ui->projectTreeView->expand(bestMatch); m_ui->projectTreeView->scrollTo(bestMatch); } } void ProjectManagerView::open( const Path& path ) { IOpenWith::openFiles(QList() << path.toUrl()); } QModelIndex ProjectManagerView::indexFromView(const QModelIndex& index) const { return m_modelFilter->mapToSource( m_overlayProxy->mapToSource(index) ); } QModelIndex ProjectManagerView::indexToView(const QModelIndex& index) const { return m_overlayProxy->mapFromSource( m_modelFilter->mapFromSource(index) ); } diff --git a/plugins/projectmanagerview/projectmanagerview.h b/plugins/projectmanagerview/projectmanagerview.h index 2dab478f2e..57774ffac0 100644 --- a/plugins/projectmanagerview/projectmanagerview.h +++ b/plugins/projectmanagerview/projectmanagerview.h @@ -1,95 +1,93 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H #define KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H -#include - #include +#include #include #include -class QAction; class QModelIndex; namespace Ui { class ProjectManagerView; } class ProjectProxyModel; class VcsOverlayProxyModel; namespace KDevelop { class ProjectBaseItem; class Path; } class ProjectManagerView; class ProjectManagerViewPlugin; //own subclass to the current view can be passed from ProjectManagetView to ProjectManagerViewPlugin class ProjectManagerViewItemContext : public KDevelop::ProjectItemContextImpl { public: ProjectManagerViewItemContext(const QList< KDevelop::ProjectBaseItem* >& items, ProjectManagerView *view); ProjectManagerView *view() const; private: ProjectManagerView *m_view; }; class ProjectManagerView: public QWidget { Q_OBJECT public: ProjectManagerView( ProjectManagerViewPlugin*, QWidget *parent ); virtual ~ProjectManagerView(); ProjectManagerViewPlugin* plugin() const { return m_plugin; } QList selectedItems() const; void selectItems(const QList &items); void expandItem(KDevelop::ProjectBaseItem *item); protected: virtual bool eventFilter(QObject* obj, QEvent* event) override; private slots: void selectionChanged(); void locateCurrentDocument(); void updateSyncAction(); void open( const KDevelop::Path& ); private: QModelIndex indexFromView(const QModelIndex& index) const; QModelIndex indexToView(const QModelIndex& index) const; QAction* m_syncAction; Ui::ProjectManagerView* m_ui; QStringList m_cachedFileList; ProjectProxyModel* m_modelFilter; VcsOverlayProxyModel* m_overlayProxy; ProjectManagerViewPlugin* m_plugin; }; #endif // KDEVPLATFORM_PLUGIN_PROJECTMANAGERVIEW_H diff --git a/plugins/projectmanagerview/projectmanagerviewplugin.cpp b/plugins/projectmanagerview/projectmanagerviewplugin.cpp index 595b99c461..7ac9ad7ed1 100644 --- a/plugins/projectmanagerview/projectmanagerviewplugin.cpp +++ b/plugins/projectmanagerview/projectmanagerviewplugin.cpp @@ -1,713 +1,712 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmanagerviewplugin.h" -#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 #include #include #include #include #include #include #include #include #include #include "projectmanagerview.h" #include "debug.h" using namespace KDevelop; Q_LOGGING_CATEGORY(PLUGIN_PROJECTMANAGERVIEW, "kdevplatform.plugins.projectmanagerview") K_PLUGIN_FACTORY_WITH_JSON(ProjectManagerFactory, "kdevprojectmanagerview.json", registerPlugin();) class KDevProjectManagerViewFactory: public KDevelop::IToolViewFactory { public: KDevProjectManagerViewFactory( ProjectManagerViewPlugin *plugin ): mplugin( plugin ) {} virtual QWidget* create( QWidget *parent = 0 ) override { return new ProjectManagerView( mplugin, parent ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::LeftDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.ProjectsView"; } private: ProjectManagerViewPlugin *mplugin; }; class ProjectManagerViewPluginPrivate { public: ProjectManagerViewPluginPrivate() {} KDevProjectManagerViewFactory *factory; QList ctxProjectItemList; QAction* m_buildAll; QAction* m_build; QAction* m_install; QAction* m_clean; QAction* m_configure; QAction* m_prune; }; static QList itemsFromIndexes(const QList& indexes) { QList items; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach(const QModelIndex& index, indexes) { items += model->itemFromIndex(index); } return items; } ProjectManagerViewPlugin::ProjectManagerViewPlugin( QObject *parent, const QVariantList& ) : IPlugin( "kdevprojectmanagerview", parent ), d(new ProjectManagerViewPluginPrivate) { d->m_buildAll = new QAction( i18n("Build all Projects"), this ); d->m_buildAll->setIcon(QIcon::fromTheme("run-build")); connect( d->m_buildAll, &QAction::triggered, this, &ProjectManagerViewPlugin::buildAllProjects ); actionCollection()->addAction( "project_buildall", d->m_buildAll ); d->m_build = new QAction( i18n("Build Selection"), this ); d->m_build->setIconText( i18n("Build") ); actionCollection()->setDefaultShortcut( d->m_build, Qt::Key_F8 ); d->m_build->setIcon(QIcon::fromTheme("run-build")); d->m_build->setEnabled( false ); connect( d->m_build, &QAction::triggered, this, &ProjectManagerViewPlugin::buildProjectItems ); actionCollection()->addAction( "project_build", d->m_build ); d->m_install = new QAction( i18n("Install Selection"), this ); d->m_install->setIconText( i18n("Install") ); d->m_install->setIcon(QIcon::fromTheme("run-build-install")); actionCollection()->setDefaultShortcut( d->m_install, Qt::SHIFT + Qt::Key_F8 ); d->m_install->setEnabled( false ); connect( d->m_install, &QAction::triggered, this, &ProjectManagerViewPlugin::installProjectItems ); actionCollection()->addAction( "project_install", d->m_install ); d->m_clean = new QAction( i18n("Clean Selection"), this ); d->m_clean->setIconText( i18n("Clean") ); d->m_clean->setIcon(QIcon::fromTheme("run-build-clean")); d->m_clean->setEnabled( false ); connect( d->m_clean, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanProjectItems ); actionCollection()->addAction( "project_clean", d->m_clean ); d->m_configure = new QAction( i18n("Configure Selection"), this ); d->m_configure->setIconText( i18n("Configure") ); d->m_configure->setIcon(QIcon::fromTheme("run-build-configure")); d->m_configure->setEnabled( false ); connect( d->m_configure, &QAction::triggered, this, &ProjectManagerViewPlugin::configureProjectItems ); actionCollection()->addAction( "project_configure", d->m_configure ); d->m_prune = new QAction( i18n("Prune Selection"), this ); d->m_prune->setIconText( i18n("Prune") ); d->m_prune->setIcon(QIcon::fromTheme("run-build-prune")); d->m_prune->setEnabled( false ); connect( d->m_prune, &QAction::triggered, this, &ProjectManagerViewPlugin::pruneProjectItems ); actionCollection()->addAction( "project_prune", d->m_prune ); // only add the action so that its known in the actionCollection // and so that it's shortcut etc. pp. is restored // apparently that is not possible to be done in the view itself *sigh* actionCollection()->addAction( "locate_document" ); setXMLFile( "kdevprojectmanagerview.rc" ); d->factory = new KDevProjectManagerViewFactory( this ); core()->uiController()->addToolView( i18n("Projects"), d->factory ); connect(core()->selectionController(), &ISelectionController::selectionChanged, this, &ProjectManagerViewPlugin::updateActionState); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsInserted, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::rowsRemoved, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); connect(ICore::self()->projectController()->buildSetModel(), &KDevelop::ProjectBuildSetModel::modelReset, this, &ProjectManagerViewPlugin::updateFromBuildSetChange); } void ProjectManagerViewPlugin::updateFromBuildSetChange() { updateActionState( core()->selectionController()->currentSelection() ); } void ProjectManagerViewPlugin::updateActionState( KDevelop::Context* ctx ) { bool isEmpty = ICore::self()->projectController()->buildSetModel()->items().isEmpty(); if( isEmpty ) { isEmpty = !ctx || ctx->type() != Context::ProjectItemContext || dynamic_cast( ctx )->items().isEmpty(); } d->m_build->setEnabled( !isEmpty ); d->m_install->setEnabled( !isEmpty ); d->m_clean->setEnabled( !isEmpty ); d->m_configure->setEnabled( !isEmpty ); d->m_prune->setEnabled( !isEmpty ); } ProjectManagerViewPlugin::~ProjectManagerViewPlugin() { delete d; } void ProjectManagerViewPlugin::unload() { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "unloading manager view"; core()->uiController()->removeToolView(d->factory); } ContextMenuExtension ProjectManagerViewPlugin::contextMenuExtension( KDevelop::Context* context ) { if( context->type() != KDevelop::Context::ProjectItemContext ) return IPlugin::contextMenuExtension( context ); KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); QList items = ctx->items(); d->ctxProjectItemList.clear(); if( items.isEmpty() ) return IPlugin::contextMenuExtension( context ); //TODO: also needs: removeTarget, removeFileFromTarget, runTargetsFromContextMenu ContextMenuExtension menuExt; bool needsCreateFile = true; bool needsCreateFolder = true; bool needsCloseProjects = true; bool needsBuildItems = true; bool needsFolderItems = true; bool needsRemoveAndRename = true; bool needsRemoveTargetFiles = true; bool needsPaste = true; //needsCreateFile if there is one item and it's a folder or target needsCreateFile &= (items.count() == 1) && (items.first()->folder() || items.first()->target()); //needsCreateFolder if there is one item and it's a folder needsCreateFolder &= (items.count() == 1) && (items.first()->folder()); needsPaste = needsCreateFolder; foreach( ProjectBaseItem* item, items ) { d->ctxProjectItemList << item->index(); //needsBuildItems if items are limited to targets and buildfolders needsBuildItems &= item->target() || item->type() == ProjectBaseItem::BuildFolder; //needsCloseProjects if items are limited to top level folders (Project Folders) needsCloseProjects &= item->folder() && !item->folder()->parent(); //needsFolderItems if items are limited to folders needsFolderItems &= (bool)item->folder(); //needsRemove if items are limited to non-top-level folders or files that don't belong to targets needsRemoveAndRename &= (item->folder() && item->parent()) || (item->file() && !item->parent()->target()); //needsRemoveTargets if items are limited to file items with target parents needsRemoveTargetFiles &= (item->file() && item->parent()->target()); } if ( needsCreateFile ) { QAction* action = new QAction( i18n( "Create File" ), this ); action->setIcon(QIcon::fromTheme("document-new")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFileFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsCreateFolder ) { QAction* action = new QAction( i18n( "Create Folder" ), this ); action->setIcon(QIcon::fromTheme("folder-new")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::createFolderFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsBuildItems ) { QAction* action = new QAction( i18nc( "@action", "Build" ), this ); action->setIcon(QIcon::fromTheme("run-build")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::buildItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Install" ), this ); action->setIcon(QIcon::fromTheme("run-install")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::installItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18nc( "@action", "Clean" ), this ); action->setIcon(QIcon::fromTheme("run-clean")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::cleanItemsFromContextMenu ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); action = new QAction( i18n( "Add to Build Set" ), this ); action->setIcon(QIcon::fromTheme("list-add")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset ); menuExt.addAction( ContextMenuExtension::BuildGroup, action ); } if ( needsCloseProjects ) { QAction* close = new QAction( i18np( "Close Project", "Close Projects", items.count() ), this ); close->setIcon(QIcon::fromTheme("project-development-close")); connect( close, &QAction::triggered, this, &ProjectManagerViewPlugin::closeProjects ); menuExt.addAction( ContextMenuExtension::ProjectGroup, close ); } if ( needsFolderItems ) { QAction* action = new QAction( i18n( "Reload" ), this ); action->setIcon(QIcon::fromTheme("view-refresh")); connect( action, &QAction::triggered, this, &ProjectManagerViewPlugin::reloadFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, action ); } if ( needsRemoveAndRename ) { QAction* remove = new QAction( i18n( "Remove" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); QAction* rename = new QAction( i18n( "Rename" ), this ); rename->setIcon(QIcon::fromTheme("edit-rename")); connect( rename, &QAction::triggered, this, &ProjectManagerViewPlugin::renameItemFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, rename ); } if ( needsRemoveTargetFiles ) { QAction* remove = new QAction( i18n( "Remove From Target" ), this ); remove->setIcon(QIcon::fromTheme("user-trash")); connect( remove, &QAction::triggered, this, &ProjectManagerViewPlugin::removeTargetFilesFromContextMenu ); menuExt.addAction( ContextMenuExtension::FileGroup, remove ); } { QAction* copy = KStandardAction::copy(this, SLOT(copyFromContextMenu()), this); copy->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, copy ); } if (needsPaste) { QAction* paste = KStandardAction::paste(this, SLOT(pasteFromContextMenu()), this); paste->setShortcutContext(Qt::WidgetShortcut); menuExt.addAction( ContextMenuExtension::FileGroup, paste ); } return menuExt; } void ProjectManagerViewPlugin::closeProjects() { QList projectsToClose; ProjectModel* model = ICore::self()->projectController()->projectModel(); foreach( const QModelIndex& index, d->ctxProjectItemList ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex(index); if( !projectsToClose.contains( item->project() ) ) { projectsToClose << item->project(); } } d->ctxProjectItemList.clear(); foreach( KDevelop::IProject* proj, projectsToClose ) { core()->projectController()->closeProject( proj ); } } void ProjectManagerViewPlugin::installItemsFromContextMenu() { runBuilderJob( BuilderJob::Install, itemsFromIndexes(d->ctxProjectItemList) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::cleanItemsFromContextMenu() { runBuilderJob( BuilderJob::Clean, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } void ProjectManagerViewPlugin::buildItemsFromContextMenu() { runBuilderJob( BuilderJob::Build, itemsFromIndexes( d->ctxProjectItemList ) ); d->ctxProjectItemList.clear(); } QList ProjectManagerViewPlugin::collectAllProjects() { QList items; foreach( KDevelop::IProject* project, core()->projectController()->projects() ) { items << project->projectItem(); } return items; } void ProjectManagerViewPlugin::buildAllProjects() { runBuilderJob( BuilderJob::Build, collectAllProjects() ); } QList ProjectManagerViewPlugin::collectItems() { QList items; QList buildItems = ICore::self()->projectController()->buildSetModel()->items(); if( !buildItems.isEmpty() ) { foreach( const BuildItem& buildItem, buildItems ) { if( ProjectBaseItem* item = buildItem.findItem() ) { items << item; } } } else { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); items = ctx->items(); } return items; } void ProjectManagerViewPlugin::runBuilderJob( BuilderJob::BuildType type, QList items ) { BuilderJob* builder = new BuilderJob; builder->addItems( type, items ); builder->updateJobName(); ICore::self()->runController()->registerJob( builder ); } void ProjectManagerViewPlugin::installProjectItems() { runBuilderJob( KDevelop::BuilderJob::Install, collectItems() ); } void ProjectManagerViewPlugin::pruneProjectItems() { runBuilderJob( KDevelop::BuilderJob::Prune, collectItems() ); } void ProjectManagerViewPlugin::configureProjectItems() { runBuilderJob( KDevelop::BuilderJob::Configure, collectItems() ); } void ProjectManagerViewPlugin::cleanProjectItems() { runBuilderJob( KDevelop::BuilderJob::Clean, collectItems() ); } void ProjectManagerViewPlugin::buildProjectItems() { runBuilderJob( KDevelop::BuilderJob::Build, collectItems() ); } void ProjectManagerViewPlugin::addItemsFromContextMenuToBuildset( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { ICore::self()->projectController()->buildSetModel()->addProjectItem( item ); } } void ProjectManagerViewPlugin::runTargetsFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { KDevelop::ProjectExecutableTargetItem* t=item->executable(); if(t) { qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "Running target: " << t->text() << t->builtUrl(); } } } void ProjectManagerViewPlugin::projectConfiguration( ) { if( !d->ctxProjectItemList.isEmpty() ) { ProjectModel* model = ICore::self()->projectController()->projectModel(); core()->projectController()->configureProject( model->itemFromIndex(d->ctxProjectItemList.at( 0 ))->project() ); } } void ProjectManagerViewPlugin::reloadFromContextMenu( ) { QList< KDevelop::ProjectFolderItem* > folders; foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { // since reloading should be recursive, only pass the upper-most items bool found = false; foreach ( KDevelop::ProjectFolderItem* existing, folders ) { if ( existing->path().isParentOf(item->folder()->path()) ) { // simply skip this child found = true; break; } else if ( item->folder()->path().isParentOf(existing->path()) ) { // remove the child in the list and add the current item instead folders.removeOne(existing); // continue since there could be more than one existing child } } if ( !found ) { folders << item->folder(); } } } foreach( KDevelop::ProjectFolderItem* folder, folders ) { folder->project()->projectFileManager()->reload(folder); } } void ProjectManagerViewPlugin::createFolderFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList )) { if ( item->folder() ) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); QString name = QInputDialog::getText ( window, i18n ( "Create Folder in %1", item->folder()->path().pathOrUrl() ), i18n ( "Folder Name" ) ); if (!name.isEmpty()) { item->project()->projectFileManager()->addFolder( Path(item->path(), name), item->folder() ); } } } } void ProjectManagerViewPlugin::removeFromContextMenu() { removeItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::removeItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } //copy the list of selected items and sort it to guarantee parents will come before children QList sortedItems = items; std::sort(sortedItems.begin(), sortedItems.end(), ProjectBaseItem::pathLessThan); Path lastFolder; QMap< IProjectFileManager*, QList > filteredItems; QStringList itemPaths; foreach( KDevelop::ProjectBaseItem* item, sortedItems ) { if (item->isProjectRoot()) { continue; } else if (item->folder() || item->file()) { //make sure no children of folders that will be deleted are listed if (lastFolder.isParentOf(item->path())) { continue; } else if (item->folder()) { lastFolder = item->path(); } IProjectFileManager* manager = item->project()->projectFileManager(); if (manager) { filteredItems[manager] << item; itemPaths << item->path().pathOrUrl(); } } } if (filteredItems.isEmpty()) { return; } if (KMessageBox::warningYesNoList( QApplication::activeWindow(), i18np("Do you really want to delete this item?", "Do you really want to delete these %1 items?", itemPaths.size()), itemPaths, i18n("Delete Files"), KStandardGuiItem::del(), KStandardGuiItem::cancel() ) == KMessageBox::No) { return; } //Go though projectmanagers, have them remove the files and folders that they own QMap< IProjectFileManager*, QList >::iterator it; for (it = filteredItems.begin(); it != filteredItems.end(); ++it) { Q_ASSERT(it.key()); it.key()->removeFilesAndFolders(it.value()); } } void ProjectManagerViewPlugin::removeTargetFilesFromContextMenu() { QList items = itemsFromIndexes( d->ctxProjectItemList ); QMap< IBuildSystemManager*, QList > itemsByBuildSystem; foreach(ProjectBaseItem *item, items) itemsByBuildSystem[item->project()->buildSystemManager()].append(item->file()); QMap< IBuildSystemManager*, QList >::iterator it; for (it = itemsByBuildSystem.begin(); it != itemsByBuildSystem.end(); ++it) it.key()->removeFilesFromTargets(it.value()); } void ProjectManagerViewPlugin::renameItemFromContextMenu() { renameItems(itemsFromIndexes( d->ctxProjectItemList )); } void ProjectManagerViewPlugin::renameItems(const QList< ProjectBaseItem* >& items) { if (items.isEmpty()) { return; } QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); foreach( KDevelop::ProjectBaseItem* item, items ) { if ((item->type()!=ProjectBaseItem::BuildFolder && item->type()!=ProjectBaseItem::Folder && item->type()!=ProjectBaseItem::File) || !item->parent()) { continue; } const QString src = item->text(); //Change QInputDialog->KFileSaveDialog? QString name = QInputDialog::getText( window, i18n("Rename..."), i18n("New name for '%1':", item->text()), QLineEdit::Normal, item->text() ); if (!name.isEmpty() && name != src) { ProjectBaseItem::RenameStatus status = item->rename( name ); switch(status) { case ProjectBaseItem::RenameOk: break; case ProjectBaseItem::ExistingItemSameName: KMessageBox::error(window, i18n("There is already a file named '%1'", name)); break; case ProjectBaseItem::ProjectManagerRenameFailed: KMessageBox::error(window, i18n("Could not rename '%1'", name)); break; case ProjectBaseItem::InvalidNewName: KMessageBox::error(window, i18n("'%1' is not a valid file name", name)); break; } } } } ProjectFileItem* createFile(const ProjectFolderItem* item) { QWidget* window = ICore::self()->uiController()->activeMainWindow()->window(); QString name = QInputDialog::getText(window, i18n("Create File in %1", item->path().pathOrUrl()), i18n("File name:")); if(name.isEmpty()) return 0; ProjectFileItem* ret = item->project()->projectFileManager()->addFile( Path(item->path(), name), item->folder() ); if (ret) { ICore::self()->documentController()->openDocument( ret->path().toUrl() ); } return ret; } void ProjectManagerViewPlugin::createFileFromContextMenu( ) { foreach( KDevelop::ProjectBaseItem* item, itemsFromIndexes( d->ctxProjectItemList ) ) { if ( item->folder() ) { createFile(item->folder()); } else if ( item->target() ) { ProjectFolderItem* folder=dynamic_cast(item->parent()); if(folder) { ProjectFileItem* f=createFile(folder); if(f) item->project()->buildSystemManager()->addFilesToTarget(QList() << f, item->target()); } } } } void ProjectManagerViewPlugin::copyFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); QList urls; foreach (ProjectBaseItem* item, ctx->items()) { if (item->folder() || item->file()) { urls << item->path().toUrl(); } } qCDebug(PLUGIN_PROJECTMANAGERVIEW) << urls; if (!urls.isEmpty()) { QMimeData* data = new QMimeData; data->setUrls(urls); qApp->clipboard()->setMimeData(data); } } void ProjectManagerViewPlugin::pasteFromContextMenu() { KDevelop::ProjectItemContext* ctx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (ctx->items().count() != 1) return; //do nothing if multiple or none items are selected ProjectBaseItem* destItem = ctx->items().first(); if (!destItem->folder()) return; //do nothing if the target is not a directory const QMimeData* data = qApp->clipboard()->mimeData(); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << data->urls(); const Path::List paths = toPathList(data->urls()); bool success = destItem->project()->projectFileManager()->copyFilesAndFolders(paths, destItem->folder()); if (success) { ProjectManagerViewItemContext* viewCtx = dynamic_cast(ICore::self()->selectionController()->currentSelection()); if (viewCtx) { //expand target folder viewCtx->view()->expandItem(destItem); //and select new items QList newItems; foreach (const Path &path, paths) { const Path targetPath(destItem->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, destItem->children()) { if (item->path() == targetPath) { newItems << item; } } } viewCtx->view()->selectItems(newItems); } } } #include "projectmanagerviewplugin.moc" diff --git a/plugins/projectmanagerview/projectmodelsaver.h b/plugins/projectmanagerview/projectmodelsaver.h index 51994d4a09..a991144b7f 100644 --- a/plugins/projectmanagerview/projectmodelsaver.h +++ b/plugins/projectmanagerview/projectmodelsaver.h @@ -1,54 +1,54 @@ /* This file is part of KDevelop Copyright 2012 Andrew Fuller This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_PROJECTMODELSAVER_H #define KDEVPLATFORM_PLUGIN_PROJECTMODELSAVER_H -#include +#include namespace KDevelop { class IProject; -class ProjectModelSaver: public KViewStateSaver +class ProjectModelSaver: public KConfigViewStateSaver { Q_OBJECT public: ProjectModelSaver(); /** * If @p project is non-null, only files from that project will * be handled by @code index{From,To}ConfigString @endcode. * * For other files, an invalid index or empty string is be returned. */ void setProject(IProject* project); virtual QModelIndex indexFromConfigString(const QAbstractItemModel *model, const QString &key) const override; virtual QString indexToConfigString(const QModelIndex &index) const override; private: IProject* m_project; }; } #endif // KDEVPLATFORM_PLUGIN_PROJECTMODELSAVER_H diff --git a/plugins/projectmanagerview/projecttreeview.cpp b/plugins/projectmanagerview/projecttreeview.cpp index dbd25ffd28..9bd6b730b1 100644 --- a/plugins/projectmanagerview/projecttreeview.cpp +++ b/plugins/projectmanagerview/projecttreeview.cpp @@ -1,450 +1,446 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2009 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projecttreeview.h" -#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 #include "projectmanagerviewplugin.h" #include "projectmodelsaver.h" #include "projectmodelitemdelegate.h" #include "debug.h" #include #include #include #include using namespace KDevelop; namespace { const char settingsConfigGroup[] = "ProjectTreeView"; QList fileItemsWithin(const QList& items) { QList fileItems; fileItems.reserve(items.size()); foreach(ProjectBaseItem* item, items) { if (ProjectFileItem *file = item->file()) fileItems.append(file); else if (item->folder()) fileItems.append(fileItemsWithin(item->children())); } return fileItems; } QList topLevelItemsWithin(QList items) { std::sort(items.begin(), items.end(), ProjectBaseItem::pathLessThan); Path lastFolder; for (int i = items.size() - 1; i >= 0; --i) { if (lastFolder.isParentOf(items[i]->path())) items.removeAt(i); else if (items[i]->folder()) lastFolder = items[i]->path(); } return items; } template void filterDroppedItems(QList &items, ProjectBaseItem* dest) { for (int i = items.size() - 1; i >= 0; --i) { //No drag and drop from and to same location if (items[i]->parent() == dest) items.removeAt(i); //No moving between projects (technically feasible if the projectmanager is the same though...) else if (items[i]->project() != dest->project()) items.removeAt(i); } } //TODO test whether this could be replaced by projectbuildsetwidget.cpp::showContextMenu_appendActions void popupContextMenu_appendActions(QMenu& menu, const QList& actions) { menu.addActions(actions); menu.addSeparator(); } } ProjectTreeView::ProjectTreeView( QWidget *parent ) : QTreeView( parent ), m_ctxProject( 0 ) { header()->hide(); setEditTriggers( QAbstractItemView::EditKeyPressed ); setContextMenuPolicy( Qt::CustomContextMenu ); setSelectionMode( QAbstractItemView::ExtendedSelection ); setIndentation(10); setDragEnabled(true); setDragDropMode(QAbstractItemView::InternalMove); setAutoScroll(true); setAutoExpandDelay(300); setItemDelegate(new ProjectModelItemDelegate(this)); connect( this, &ProjectTreeView::customContextMenuRequested, this, &ProjectTreeView::popupContextMenu ); connect( this, &ProjectTreeView::activated, this, &ProjectTreeView::slotActivated ); connect( ICore::self(), &ICore::aboutToShutdown, this, &ProjectTreeView::aboutToShutdown); connect( ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectTreeView::restoreState ); connect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); restoreState(); } ProjectTreeView::~ProjectTreeView() { } ProjectBaseItem* ProjectTreeView::itemAtPos(QPoint pos) { return indexAt(pos).data(ProjectModel::ProjectItemRole).value(); } void ProjectTreeView::dropEvent(QDropEvent* event) { ProjectItemContext* selectionCtxt = static_cast(KDevelop::ICore::self()->selectionController()->currentSelection()); ProjectBaseItem* destItem = itemAtPos(event->pos()); if (destItem && (dropIndicatorPosition() == AboveItem || dropIndicatorPosition() == BelowItem)) destItem = destItem->parent(); if (selectionCtxt && destItem) { if (ProjectFolderItem *folder = destItem->folder()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ShiftModifier ).toString(); seq.chop(1); // chop superfluous '+' QAction* move = new QAction(i18n( "&Move Here" ) + '\t' + seq, &dropMenu); move->setIcon(QIcon::fromTheme("go-jump")); dropMenu.addAction(move); seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* copy = new QAction(i18n( "&Copy Here" ) + '\t' + seq, &dropMenu); copy->setIcon(QIcon::fromTheme("edit-copy")); dropMenu.addAction(copy); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme("process-stop")); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = copy; } else if (modifiers == Qt::ShiftModifier) { executedAction = move; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } QList usefulItems = topLevelItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); Path::List paths; foreach (ProjectBaseItem* i, usefulItems) { paths << i->path(); } bool success = false; if (executedAction == copy) { success =~ destItem->project()->projectFileManager()->copyFilesAndFolders(paths, folder); } else if (executedAction == move) { success =~ destItem->project()->projectFileManager()->moveFilesAndFolders(usefulItems, folder); } if (success) { //expand target folder expand( mapFromItem(folder)); //and select new items QItemSelection selection; foreach (const Path &path, paths) { const Path targetPath(folder->path(), path.lastPathSegment()); foreach (ProjectBaseItem *item, folder->children()) { if (item->path() == targetPath) { QModelIndex indx = mapFromItem( item ); selection.append(QItemSelectionRange(indx, indx)); setCurrentIndex(indx); } } } selectionModel()->select(selection, QItemSelectionModel::ClearAndSelect); } } else if (destItem->target() && destItem->project()->buildSystemManager()) { QMenu dropMenu(this); QString seq = QKeySequence( Qt::ControlModifier ).toString(); seq.chop(1); QAction* addToTarget = new QAction(i18n( "&Add to Target" ) + '\t' + seq, &dropMenu); addToTarget->setIcon(QIcon::fromTheme("edit-link")); dropMenu.addAction(addToTarget); dropMenu.addSeparator(); QAction* cancel = new QAction(i18n( "C&ancel" ) + '\t' + QKeySequence( Qt::Key_Escape ).toString(), &dropMenu); cancel->setIcon(QIcon::fromTheme("process-stop")); dropMenu.addAction(cancel); QAction *executedAction = 0; Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers(); if (modifiers == Qt::ControlModifier) { executedAction = addToTarget; } else { executedAction = dropMenu.exec(this->mapToGlobal(event->pos())); } if (executedAction == addToTarget) { QList usefulItems = fileItemsWithin(selectionCtxt->items()); filterDroppedItems(usefulItems, destItem); destItem->project()->buildSystemManager()->addFilesToTarget(usefulItems, destItem->target()); } } } event->accept(); } QModelIndex ProjectTreeView::mapFromSource(const QAbstractProxyModel* proxy, const QModelIndex& sourceIdx) { const QAbstractItemModel* next = proxy->sourceModel(); Q_ASSERT(next == sourceIdx.model() || qobject_cast(next)); if(next == sourceIdx.model()) return proxy->mapFromSource(sourceIdx); else { const QAbstractProxyModel* nextProxy = qobject_cast(next); QModelIndex idx = mapFromSource(nextProxy, sourceIdx); Q_ASSERT(idx.model() == nextProxy); return proxy->mapFromSource(idx); } } QModelIndex ProjectTreeView::mapFromItem(const ProjectBaseItem* item) { QModelIndex ret = mapFromSource(qobject_cast(model()), item->index()); Q_ASSERT(ret.model() == model()); return ret; } void ProjectTreeView::slotActivated( const QModelIndex &index ) { if ( QApplication::keyboardModifiers() & Qt::CTRL || QApplication::keyboardModifiers() & Qt::SHIFT ) { // Do not open file when Ctrl or Shift is pressed; that's for selection return; } KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value(); if ( item && item->file() ) { emit activate( item->file()->path() ); } } void ProjectTreeView::popupContextMenu( const QPoint &pos ) { QList itemlist; if ( indexAt(pos).isValid() ) { QModelIndexList indexes = selectionModel()->selectedRows(); foreach( const QModelIndex& index, indexes ) { if ( KDevelop::ProjectBaseItem *item = index.data(ProjectModel::ProjectItemRole).value() ) itemlist << item; } } if( !itemlist.isEmpty() ) { m_ctxProject = itemlist.at(0)->project(); } else { m_ctxProject = 0; } QMenu menu( this ); KDevelop::ProjectItemContextImpl context(itemlist); QList extensions = ICore::self()->pluginController()->queryPluginsForContextMenuExtensions( &context ); QList buildActions; QList vcsActions; QList extActions; QList projectActions; QList fileActions; QList runActions; foreach( const ContextMenuExtension& ext, extensions ) { buildActions += ext.actions(ContextMenuExtension::BuildGroup); fileActions += ext.actions(ContextMenuExtension::FileGroup); projectActions += ext.actions(ContextMenuExtension::ProjectGroup); vcsActions += ext.actions(ContextMenuExtension::VcsGroup); extActions += ext.actions(ContextMenuExtension::ExtensionGroup); runActions += ext.actions(ContextMenuExtension::RunGroup); } popupContextMenu_appendActions(menu, buildActions); popupContextMenu_appendActions(menu, runActions ); popupContextMenu_appendActions(menu, fileActions); popupContextMenu_appendActions(menu, vcsActions); popupContextMenu_appendActions(menu, extActions); if ( !itemlist.isEmpty() && itemlist.size() == 1 && itemlist[0]->folder() && !itemlist[0]->folder()->parent() ) { QAction* projectConfig = new QAction(i18n("Open Configuration..."), this); projectConfig->setIcon(QIcon::fromTheme("configure")); connect( projectConfig, &QAction::triggered, this, &ProjectTreeView::openProjectConfig ); projectActions << projectConfig; } popupContextMenu_appendActions(menu, projectActions); if(!itemlist.isEmpty()) KDevelop::populateParentItemsMenu(itemlist.front(), &menu); if ( !menu.isEmpty() ) { menu.exec( mapToGlobal( pos ) ); } } void ProjectTreeView::openProjectConfig() { if( m_ctxProject ) { IProjectController* ip = ICore::self()->projectController(); ip->configureProject( m_ctxProject ); } } void ProjectTreeView::saveState() { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); ProjectModelSaver saver; saver.setView( this ); saver.saveState( configGroup ); } void ProjectTreeView::restoreState(IProject* project) { KConfigGroup configGroup( ICore::self()->activeSession()->config(), settingsConfigGroup ); // Saver will delete itself when it is complete. ProjectModelSaver *saver = new ProjectModelSaver; saver->setProject( project ); saver->setView( this ); saver->restoreState( configGroup ); } void ProjectTreeView::aboutToShutdown() { // save all projects, not just the last one that is closed disconnect( ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectTreeView::saveState ); saveState(); } bool ProjectTreeView::event(QEvent* event) { if(event->type()==QEvent::ToolTip) { QPoint p = mapFromGlobal(QCursor::pos()); QModelIndex idxView = indexAt(p); ProjectBaseItem* it = idxView.data(ProjectModel::ProjectItemRole).value(); QModelIndex idx; if(it) idx = it->index(); if((m_idx!=idx || !m_tooltip) && it && it->file()) { m_idx=idx; ProjectFileItem* file=it->file(); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); TopDUContext* top= DUChainUtils::standardContextForUrl(file->path().toUrl()); if(m_tooltip) m_tooltip->close(); if(top) { QWidget* navigationWidget = top->createNavigationWidget(); if( navigationWidget ) { m_tooltip = new KDevelop::NavigationToolTip(this, mapToGlobal(p) + QPoint(40, 0), navigationWidget); m_tooltip->resize( navigationWidget->sizeHint() + QSize(10, 10) ); qCDebug(PLUGIN_PROJECTMANAGERVIEW) << "tooltip size" << m_tooltip->size(); ActiveToolTip::showToolTip(m_tooltip); return true; } } } } return QAbstractItemView::event(event); } void ProjectTreeView::keyPressEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Return && currentIndex().isValid() && state()!=QAbstractItemView::EditingState) { event->accept(); slotActivated(currentIndex()); } else QTreeView::keyPressEvent(event); } diff --git a/plugins/quickopen/projectfilequickopen.cpp b/plugins/quickopen/projectfilequickopen.cpp index acf3500ce9..014579e33e 100644 --- a/plugins/quickopen/projectfilequickopen.cpp +++ b/plugins/quickopen/projectfilequickopen.cpp @@ -1,358 +1,358 @@ /* This file is part of the KDE libraries Copyright (C) 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectfilequickopen.h" +#include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include "../openwith/iopenwith.h" -#include using namespace KDevelop; namespace { QSet openFiles() { QSet openFiles; const QList& docs = ICore::self()->documentController()->openDocuments(); openFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { openFiles << IndexedString(doc->url()); } return openFiles; } QString iconNameForUrl(const IndexedString& url) { if (url.isEmpty()) { return QStringLiteral("tab-duplicate"); } ProjectBaseItem* item = ICore::self()->projectController()->projectModel()->itemForPath(url); if (item) { return item->iconName(); } return QStringLiteral("unknown"); } } ProjectFileData::ProjectFileData( const ProjectFile& file ) : m_file(file) { } QString ProjectFileData::text() const { return m_file.projectPath.relativePath(m_file.path); } QString ProjectFileData::htmlDescription() const { return "" + i18nc("%1: project name", "Project %1", project()) + ""; } bool ProjectFileData::execute( QString& filterText ) { const QUrl url = m_file.path.toUrl(); IOpenWith::openFiles(QList() << url); QString path; uint lineNumber; if (extractLineNumber(filterText, path, lineNumber)) { IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { doc->setCursorPosition(KTextEditor::Cursor(lineNumber - 1, 0)); } } return true; } bool ProjectFileData::isExpandable() const { return true; } QList ProjectFileData::highlighting() const { QTextCharFormat boldFormat; boldFormat.setFontWeight(QFont::Bold); QTextCharFormat normalFormat; QString txt = text(); QList ret; int fileNameLength = m_file.path.lastPathSegment().length(); ret << 0; ret << txt.length() - fileNameLength; ret << QVariant(normalFormat); ret << txt.length() - fileNameLength; ret << fileNameLength; ret << QVariant(boldFormat); return ret; } QWidget* ProjectFileData::expandingWidget() const { const QUrl url = m_file.path.toUrl(); DUChainReadLocker lock; ///Find a du-chain for the document QList contexts = DUChain::self()->chainsForDocument(url); ///Pick a non-proxy context TopDUContext* chosen = 0; foreach( TopDUContext* ctx, contexts ) { if( !(ctx->parsingEnvironmentFile() && ctx->parsingEnvironmentFile()->isProxyContext()) ) { chosen = ctx; } } if( chosen ) { return chosen->createNavigationWidget(0, 0, "" + i18nc("%1: project name", "Project %1", project()) + ""); } else { QTextBrowser* ret = new QTextBrowser(); ret->resize(400, 100); ret->setText( "" + i18nc("%1: project name", "Project %1", project()) + "
" + i18n("Not parsed yet") + "
"); return ret; } return 0; } QIcon ProjectFileData::icon() const { const QString& iconName = iconNameForUrl(m_file.indexedPath); /** * FIXME: Move this cache into a more central place and reuse it elsewhere. * The project model e.g. could reuse this as well. * * Note: We cache here since otherwise displaying and esp. scrolling * in a large list of quickopen items becomes very slow. */ static QHash iconCache; QHash< QString, QPixmap >::const_iterator it = iconCache.constFind(iconName); if (it != iconCache.constEnd()) { return it.value(); } const QPixmap& pixmap = KIconLoader::global()->loadIcon(iconName, KIconLoader::Small); iconCache.insert(iconName, pixmap); return pixmap; } QString ProjectFileData::project() const { const IProject* project = ICore::self()->projectController()->findProjectForUrl(m_file.path.toUrl()); if (project) { return project->name(); } else { return i18n("none"); } } BaseFileDataProvider::BaseFileDataProvider() { } void BaseFileDataProvider::setFilterText( const QString& text ) { QString path(text); uint lineNumber; extractLineNumber(text, path, lineNumber); if ( path.startsWith(QLatin1String("./")) || path.startsWith(QLatin1String("../")) ) { // assume we want to filter relative to active document's url IDocument* doc = ICore::self()->documentController()->activeDocument(); if (doc) { path = Path(Path(doc->url()).parent(), path).pathOrUrl(); } } setFilter( path.split('/', QString::SkipEmptyParts) ); } uint BaseFileDataProvider::itemCount() const { return filteredItems().count(); } uint BaseFileDataProvider::unfilteredItemCount() const { return items().count(); } QuickOpenDataPointer BaseFileDataProvider::data(uint row) const { return QuickOpenDataPointer(new ProjectFileData( filteredItems().at(row) )); } ProjectFileDataProvider::ProjectFileDataProvider() { connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &ProjectFileDataProvider::projectClosing); connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &ProjectFileDataProvider::projectOpened); } void ProjectFileDataProvider::projectClosing( IProject* project ) { foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileRemovedFromSet(file); } } void ProjectFileDataProvider::projectOpened( IProject* project ) { const int processAfter = 1000; int processed = 0; foreach(ProjectFileItem* file, KDevelop::allFiles(project->projectItem())) { fileAddedToSet(file); if (++processed == processAfter) { // prevent UI-lockup when a huge project was imported QApplication::processEvents(); processed = 0; } } connect(project, &IProject::fileAddedToSet, this, &ProjectFileDataProvider::fileAddedToSet); connect(project, &IProject::fileRemovedFromSet, this, &ProjectFileDataProvider::fileRemovedFromSet); } void ProjectFileDataProvider::fileAddedToSet( ProjectFileItem* file ) { ProjectFile f; f.projectPath = file->project()->path(); f.path = file->path(); f.indexedPath = file->indexedPath(); f.outsideOfProject = !f.projectPath.isParentOf(f.path); auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), f); if (it == m_projectFiles.end() || it->path != f.path) { m_projectFiles.insert(it, f); } } void ProjectFileDataProvider::fileRemovedFromSet( ProjectFileItem* file ) { ProjectFile item; item.path = file->path(); // fast-path for non-generated files // NOTE: figuring out whether something is generated is expensive... and since // generated files are rare we apply this two-step algorithm here auto it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } // last try: maybe it was generated item.outsideOfProject = true; it = std::lower_bound(m_projectFiles.begin(), m_projectFiles.end(), item); if (it != m_projectFiles.end() && !(item < *it)) { m_projectFiles.erase(it); return; } } void ProjectFileDataProvider::reset() { clearFilter(); QList projectFiles = m_projectFiles; const auto& open = openFiles(); for(QList::iterator it = projectFiles.begin(); it != projectFiles.end();) { if (open.contains(it->indexedPath)) { it = projectFiles.erase(it); } else { ++it; } } setItems(projectFiles); } QSet ProjectFileDataProvider::files() const { QSet ret; foreach( IProject* project, ICore::self()->projectController()->projects() ) ret += project->fileSet(); return ret - openFiles(); } void OpenFilesDataProvider::reset() { clearFilter(); IProjectController* projCtrl = ICore::self()->projectController(); IDocumentController* docCtrl = ICore::self()->documentController(); const QList& docs = docCtrl->openDocuments(); QList currentFiles; currentFiles.reserve(docs.size()); foreach( IDocument* doc, docs ) { ProjectFile f; f.path = Path(doc->url()); IProject* project = projCtrl->findProjectForUrl(doc->url()); if (project) { f.projectPath = project->path(); } currentFiles << f; } std::sort(currentFiles.begin(), currentFiles.end()); setItems(currentFiles); } QSet OpenFilesDataProvider::files() const { return openFiles(); } diff --git a/plugins/reviewboard/reviewboardjobs.cpp b/plugins/reviewboard/reviewboardjobs.cpp index c6ba158e87..eb757aab67 100644 --- a/plugins/reviewboard/reviewboardjobs.cpp +++ b/plugins/reviewboard/reviewboardjobs.cpp @@ -1,375 +1,373 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "reviewboardjobs.h" - #include "debug.h" -#include -#include #include #include -#include -#include -#include -#include -#include + #include -#include -#include +#include +#include +#include #include +#include #include +#include +#include + using namespace ReviewBoard; QByteArray ReviewBoard::urlToData(const QUrl& url) { QByteArray ret; if (url.isLocalFile()) { QFile f(url.toLocalFile()); Q_ASSERT(f.exists()); bool corr=f.open(QFile::ReadOnly | QFile::Text); Q_ASSERT(corr); Q_UNUSED(corr); ret = f.readAll(); } else { //TODO: add downloading the data } return ret; } namespace { static const QByteArray m_boundary = "----------" + KRandom::randomString( 42 + 13 ).toLatin1(); QByteArray multipartFormData(const QList >& values) { QByteArray form_data; foreach(const auto& val, values) { QByteArray hstr("--"); hstr += m_boundary; hstr += "\r\n"; hstr += "Content-Disposition: form-data; name=\""; hstr += val.first.toLatin1(); hstr += "\""; //File if (val.second.type()==QVariant::Url) { QUrl path=val.second.toUrl(); hstr += "; filename=\"" + path.fileName().toLatin1() + "\""; const QMimeType mime = QMimeDatabase().mimeTypeForUrl(path); if (!mime.name().isEmpty()) { hstr += "\r\nContent-Type: "; hstr += mime.name().toLatin1().constData(); } } // hstr += "\r\n\r\n"; // append body form_data.append(hstr); if (val.second.type()==QVariant::Url) form_data += urlToData(val.second.toUrl()); else form_data += val.second.toByteArray(); form_data.append("\r\n"); //EOFILE } form_data += QByteArray("--" + m_boundary + "--\r\n"); return form_data; } QByteArray multipartFormData(const QVariantMap& values) { QList > vals; for(QVariantMap::const_iterator it = values.constBegin(), itEnd = values.constEnd(); it!=itEnd; ++it) { vals += qMakePair(it.key(), it.value()); } return multipartFormData(vals); } } HttpCall::HttpCall(const QUrl& s, const QString& apiPath, const QList >& queryParameters, Method method, const QByteArray& post, bool multipart, QObject* parent) : KJob(parent) , m_reply(nullptr) , m_post(post) , m_multipart(multipart) , m_method(method) { m_requrl=s; m_requrl.setPath(m_requrl.path() + '/' + apiPath); QUrlQuery query; for(QList >::const_iterator i = queryParameters.begin(); i < queryParameters.end(); i++) { query.addQueryItem(i->first, i->second); } m_requrl.setQuery(query); } void HttpCall::start() { QNetworkRequest r(m_requrl); if(!m_requrl.userName().isEmpty()) { QByteArray head = "Basic " + m_requrl.userInfo().toLatin1().toBase64(); r.setRawHeader("Authorization", head); } if(m_multipart) { r.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data"); r.setHeader(QNetworkRequest::ContentLengthHeader, QString::number(m_post.size())); r.setRawHeader( "Content-Type", "multipart/form-data; boundary=" + m_boundary ); } switch(m_method) { case Get: m_reply=m_manager.get(r); break; case Post: m_reply=m_manager.post(r, m_post); break; case Put: m_reply=m_manager.put(r, m_post); break; } connect(m_reply, &QNetworkReply::finished, this, &HttpCall::onFinished); // qCDebug(PLUGIN_REVIEWBOARD) << "starting... requrl=" << m_requrl << "post=" << m_post; } QVariant HttpCall::result() const { Q_ASSERT(m_reply->isFinished()); return m_result; } void HttpCall::onFinished() { if (m_reply->error()) { setError(55); setErrorText(i18n("Error %1 while accessing %2", m_reply->error(), m_reply->request().url().toDisplayString())); emitResult(); return; } QByteArray receivedData = m_reply->readAll(); QJsonParseError error; QJsonDocument parser = QJsonDocument::fromJson(receivedData, &error); const QVariant output = parser.toVariant(); if (error.error == 0) { m_result = output; } else { setError(1); setErrorText(i18n("JSON error: %1", error.errorString())); } if (output.toMap().value("stat").toString()!="ok") { setError(2); setErrorText(i18n("Request Error: %1", output.toMap().value("err").toMap().value("msg").toString())); } qCDebug(PLUGIN_REVIEWBOARD) << "parsing..." << receivedData; emitResult(); } NewRequest::NewRequest(const QUrl& server, const QString& projectPath, QObject* parent) : ReviewRequest(server, 0, parent), m_project(projectPath) { m_newreq = new HttpCall(this->server(), "/api/review-requests/", {}, HttpCall::Post, "repository="+projectPath.toLatin1(), false, this); connect(m_newreq, &HttpCall::finished, this, &NewRequest::done); } void NewRequest::start() { m_newreq->start(); } void NewRequest::done() { if (m_newreq->error()) { qCDebug(PLUGIN_REVIEWBOARD) << "Could not create the new request" << m_newreq->errorString(); setError(2); setErrorText(i18n("Could not create the new request:\n%1", m_newreq->errorString())); } else { QVariant res = m_newreq->result(); setRequestId(res.toMap()["review_request"].toMap()["id"].toString()); Q_ASSERT(!requestId().isEmpty()); } emitResult(); } SubmitPatchRequest::SubmitPatchRequest(const QUrl& server, const QUrl& patch, const QString& basedir, const QString& id, QObject* parent) : ReviewRequest(server, id, parent), m_patch(patch), m_basedir(basedir) { QList > vals; vals += QPair("basedir", m_basedir); vals += QPair("path", qVariantFromValue(m_patch)); m_uploadpatch = new HttpCall(this->server(), "/api/review-requests/"+requestId()+"/diffs/", {}, HttpCall::Post, multipartFormData(vals), true, this); connect(m_uploadpatch, &HttpCall::finished, this, &SubmitPatchRequest::done); } void SubmitPatchRequest::start() { m_uploadpatch->start(); } void SubmitPatchRequest::done() { if (m_uploadpatch->error()) { qCDebug(PLUGIN_REVIEWBOARD) << "Could not upload the patch" << m_uploadpatch->errorString(); setError(3); setErrorText(i18n("Could not upload the patch")); } emitResult(); } ProjectsListRequest::ProjectsListRequest(const QUrl& server, QObject* parent) : KJob(parent), m_server(server) { } void ProjectsListRequest::start() { requestRepositoryList(0); } QVariantList ProjectsListRequest::repositories() const { return m_repositories; } void ProjectsListRequest::requestRepositoryList(int startIndex) { QList > repositoriesParameters; // In practice, the web API will return at most 200 repos per call, so just hardcode that value here repositoriesParameters << qMakePair(QStringLiteral("max-results"), QStringLiteral("200")); repositoriesParameters << qMakePair(QStringLiteral("start"), QString::number(startIndex)); HttpCall* repositoriesCall = new HttpCall(m_server, "/api/repositories/", repositoriesParameters, HttpCall::Get, "", false, this); connect(repositoriesCall, &HttpCall::finished, this, &ProjectsListRequest::done); repositoriesCall->start(); } void ProjectsListRequest::done(KJob* job) { // TODO error // TODO max iterations HttpCall* repositoriesCall = qobject_cast(job); QMap resultMap = repositoriesCall->result().toMap(); const int totalResults = repositoriesCall->result().toMap()["total_results"].toInt(); m_repositories << repositoriesCall->result().toMap()["repositories"].toList(); if (m_repositories.count() < totalResults) { requestRepositoryList(m_repositories.count()); } else { emitResult(); } } ReviewListRequest::ReviewListRequest(const QUrl& server, const QString& user, const QString& reviewStatus, QObject* parent) : KJob(parent), m_server(server), m_user(user), m_reviewStatus(reviewStatus) { } void ReviewListRequest::start() { requestReviewList(0); } QVariantList ReviewListRequest::reviews() const { return m_reviews; } void ReviewListRequest::requestReviewList(int startIndex) { QList > reviewParameters; // In practice, the web API will return at most 200 repos per call, so just hardcode that value here reviewParameters << qMakePair(QStringLiteral("max-results"), QStringLiteral("200")); reviewParameters << qMakePair(QStringLiteral("start"), QString::number(startIndex)); reviewParameters << qMakePair(QStringLiteral("from-user"), m_user); reviewParameters << qMakePair(QStringLiteral("status"), m_reviewStatus); HttpCall* reviewsCall = new HttpCall(m_server, "/api/review-requests/", reviewParameters, HttpCall::Get, "", false, this); connect(reviewsCall, &HttpCall::finished, this, &ReviewListRequest::done); reviewsCall->start(); } void ReviewListRequest::done(KJob* job) { // TODO error // TODO max iterations if (job->error()) { qCDebug(PLUGIN_REVIEWBOARD) << "Could not get reviews list" << job->errorString(); setError(3); setErrorText(i18n("Could not get reviews list")); emitResult(); } HttpCall* reviewsCall = qobject_cast(job); QMap resultMap = reviewsCall->result().toMap(); const int totalResults = resultMap["total_results"].toInt(); m_reviews << resultMap["review_requests"].toList(); if (m_reviews.count() < totalResults) { requestReviewList(m_reviews.count()); } else { emitResult(); } } UpdateRequest::UpdateRequest(const QUrl& server, const QString& id, const QVariantMap& newValues, QObject* parent) : ReviewRequest(server, id, parent) { m_req = new HttpCall(this->server(), "/api/review-requests/"+id+"/draft/", {}, HttpCall::Put, multipartFormData(newValues), true, this); connect(m_req, &HttpCall::finished, this, &UpdateRequest::done); } void UpdateRequest::start() { m_req->start(); } void UpdateRequest::done() { if (m_req->error()) { qCWarning(PLUGIN_REVIEWBOARD) << "Could not set all metadata to the review" << m_req->errorString() << m_req->property("result"); setError(3); setErrorText(i18n("Could not set metadata")); } emitResult(); } #include "reviewboardjobs.moc" diff --git a/plugins/reviewboard/reviewboardjobs.h b/plugins/reviewboard/reviewboardjobs.h index 667d83ffaa..1c18915ac8 100644 --- a/plugins/reviewboard/reviewboardjobs.h +++ b/plugins/reviewboard/reviewboardjobs.h @@ -1,167 +1,168 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_REVIEWBOARDJOBS_H #define KDEVPLATFORM_PLUGIN_REVIEWBOARDJOBS_H -#include -#include #include -#include #include +#include +#include + +#include class QNetworkReply; namespace ReviewBoard { /** * Http call to the specified service. * Converts returned json data to a QVariant to be used from actual API calls * * @note It is reviewboard-agnostic. */ class HttpCall : public KJob { Q_OBJECT Q_PROPERTY(QVariant result READ result); public: enum Method { Get, Put, Post }; HttpCall(const QUrl& s, const QString& apiPath, const QList >& queryParameters, Method m, const QByteArray& post, bool multipart, QObject* parent); virtual void start() override; QVariant result() const; private slots: void onFinished(); private: QVariant m_result; QNetworkReply* m_reply; QUrl m_requrl; QByteArray m_post; QNetworkAccessManager m_manager; bool m_multipart; Method m_method; }; class ReviewRequest : public KJob { Q_OBJECT public: ReviewRequest(const QUrl& server, const QString& id, QObject* parent) : KJob(parent), m_server(server), m_id(id) {} QString requestId() const { return m_id; } void setRequestId(QString id) { m_id = id; } QUrl server() const { return m_server; } private: QUrl m_server; QString m_id; }; class NewRequest : public ReviewRequest { Q_OBJECT public: NewRequest(const QUrl& server, const QString& project, QObject* parent = 0); virtual void start() override; private slots: void done(); private: HttpCall* m_newreq; QString m_project; }; class UpdateRequest : public ReviewRequest { Q_OBJECT public: UpdateRequest(const QUrl& server, const QString& id, const QVariantMap& newValues, QObject* parent = nullptr); virtual void start() override; private slots: void done(); private: HttpCall* m_req; QString m_project; }; class SubmitPatchRequest : public ReviewRequest { Q_OBJECT public: SubmitPatchRequest(const QUrl &server, const QUrl& patch, const QString& basedir, const QString& id, QObject* parent = 0); virtual void start() override; private slots: void done(); private: HttpCall* m_uploadpatch; QUrl m_patch; QString m_basedir; }; class ProjectsListRequest : public KJob { Q_OBJECT public: ProjectsListRequest(const QUrl &server, QObject* parent = 0); virtual void start() override; QVariantList repositories() const; private slots: void requestRepositoryList(int startIndex); void done(KJob* done); private: QUrl m_server; QVariantList m_repositories; }; class ReviewListRequest : public KJob { Q_OBJECT public: ReviewListRequest(const QUrl& server, const QString& user, const QString& reviewStatus, QObject* parent = 0); virtual void start() override; QVariantList reviews() const; private slots: void requestReviewList(int startIndex); void done(KJob* done); private: QUrl m_server; QString m_user; QString m_reviewStatus; QVariantList m_reviews; }; QByteArray urlToData(const QUrl&); } #endif diff --git a/plugins/reviewboard/reviewboardplugin.cpp b/plugins/reviewboard/reviewboardplugin.cpp index c501e37c62..a12df05762 100644 --- a/plugins/reviewboard/reviewboardplugin.cpp +++ b/plugins/reviewboard/reviewboardplugin.cpp @@ -1,147 +1,145 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "reviewboardplugin.h" -#include + +#include #include +#include -#include -#include -#include +#include #include -#include #include -#include -#include -#include +#include + #include #include #include #include #include #include #include -#include + #include "reviewpatchdialog.h" #include "reviewboardjobs.h" #include "debug.h" using namespace KDevelop; typedef QPair VariantPair; Q_DECLARE_METATYPE(QList); K_PLUGIN_FACTORY_WITH_JSON(KDevReviewBoardFactory, "kdevreviewboard.json", registerPlugin(); ) ReviewBoardPlugin::ReviewBoardPlugin ( QObject* parent, const QVariantList& ) : IPlugin ( "kdevreviewboard", parent ) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IPatchExporter ) } ReviewBoardPlugin::~ReviewBoardPlugin() {} void ReviewBoardPlugin::exportPatch(IPatchSource::Ptr source) { QUrl dirUrl = source->baseDir(); m_source = source; ReviewPatchDialog d(dirUrl); IProject* p = ICore::self()->projectController()->findProjectForUrl(dirUrl.adjusted(QUrl::StripTrailingSlash)); if(p) { KConfigGroup versionedConfig = p->projectConfiguration()->group("ReviewBoard"); if(versionedConfig.hasKey("server")) d.setServer(versionedConfig.readEntry("server", QUrl())); if(versionedConfig.hasKey("username")) d.setUsername(versionedConfig.readEntry("username", QString())); if(versionedConfig.hasKey("baseDir")) d.setBaseDir(versionedConfig.readEntry("baseDir", "/")); if(versionedConfig.hasKey("repository")) d.setRepository(versionedConfig.readEntry("repository", QString())); } int ret = d.exec(); if(ret==QDialog::Accepted) { KJob* job; if (d.isUpdateReview()) { job=new ReviewBoard::SubmitPatchRequest(d.server(), source->file(), d.baseDir(), d.review()); connect(job, &KJob::finished, this, &ReviewBoardPlugin::reviewDone); } else { job=new ReviewBoard::NewRequest(d.server(), d.repository()); job->setProperty("extraData", d.extraData()); connect(job, &KJob::finished, this, &ReviewBoardPlugin::reviewCreated); } job->setProperty("baseDir", d.baseDir()); job->start(); if(p) { KConfigGroup versionedConfig = p->projectConfiguration()->group("ReviewBoard"); // We store username in a diferent field. Unset it from server. QUrl storeServer(d.server()); storeServer.setUserName(QString()); // Don't store password in plaintext inside .kdev4 storeServer.setPassword(QString()); versionedConfig.writeEntry("server", storeServer); versionedConfig.writeEntry("username", d.username()); versionedConfig.writeEntry("baseDir", d.baseDir()); versionedConfig.writeEntry("repository", d.repository()); } } } void ReviewBoardPlugin::reviewDone(KJob* j) { if(j->error()==0) { ReviewBoard::SubmitPatchRequest const * job = qobject_cast(j); QUrl url = job->server(); url.setUserInfo(QString()); QString requrl = QStringLiteral("%1/r/%2/").arg(url.toDisplayString(QUrl::PreferLocalFile)).arg(job->requestId()); KMessageBox::information(0, i18n("You can find the new request at:
%1
", requrl), QString(), QString(), KMessageBox::AllowLink); } else { KMessageBox::error(0, j->errorText()); } } void ReviewBoardPlugin::reviewCreated(KJob* j) { if (j->error()==0) { ReviewBoard::NewRequest const * job = qobject_cast(j); //This will provide things like groups and users for review from .reviewboardrc QVariantMap extraData = job->property("extraData").toMap(); if (!extraData.isEmpty()) { KJob* updateJob = new ReviewBoard::UpdateRequest(job->server(), job->requestId(), extraData); updateJob->start(); } // for git projects, m_source will be a VCSDiffPatchSource instance ReviewBoard::SubmitPatchRequest* submitPatchJob=new ReviewBoard::SubmitPatchRequest(job->server(), m_source->file(), j->property("baseDir").toString(), job->requestId()); connect(submitPatchJob, &ReviewBoard::SubmitPatchRequest::finished, this, &ReviewBoardPlugin::reviewDone); submitPatchJob->start(); } else { KMessageBox::error(0, j->errorText()); } } #include "reviewboardplugin.moc" diff --git a/plugins/reviewboard/reviewpatchdialog.cpp b/plugins/reviewboard/reviewpatchdialog.cpp index 4802653096..d9e04d3497 100644 --- a/plugins/reviewboard/reviewpatchdialog.cpp +++ b/plugins/reviewboard/reviewpatchdialog.cpp @@ -1,263 +1,262 @@ /* * This file is part of KDevelop * Copyright 2010 Aleix Pol Gonzalez * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "reviewpatchdialog.h" #include #include -#include -#include + #include -#include + #include "ui_reviewpatch.h" #include "reviewboardjobs.h" #include "debug.h" ReviewPatchDialog::ReviewPatchDialog(const QUrl& dirUrl, QWidget* parent) : QDialog(parent) { m_ui = new Ui::ReviewPatch; m_ui->setupUi(this); connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewPatchDialog::accept); connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewPatchDialog::reject); connect(m_ui->server, &KUrlRequester::textChanged, this, &ReviewPatchDialog::serverChanged); connect(m_ui->reviewCheckbox, &QCheckBox::stateChanged, this, &ReviewPatchDialog::reviewCheckboxChanged); if (dirUrl.isLocalFile()) { QDir d(dirUrl.toLocalFile()); while(!QFile::exists(d.filePath(".reviewboardrc"))) { if(!d.cdUp()) break; } if(!d.isRoot()) initializeFromRC(d.filePath(".reviewboardrc")); } } ReviewPatchDialog::~ReviewPatchDialog() { delete m_ui; } void ReviewPatchDialog::setBaseDir(const QString& repo) { m_ui->basedir->setText(repo); } void ReviewPatchDialog::setServer(const QUrl& server) { m_ui->server->setUrl(server); } void ReviewPatchDialog::setUsername(const QString& user) { m_ui->username->setText(user); } void ReviewPatchDialog::setRepository(const QString& repo) { m_preferredRepository = repo; } QString ReviewPatchDialog::baseDir() const { return m_ui->basedir->text(); } QUrl ReviewPatchDialog::server() const { QUrl server=m_ui->server->url(); server.setUserName(m_ui->username->text()); server.setPassword(m_ui->password->text()); return server; } QString ReviewPatchDialog::username() const { return m_ui->username->text(); } void ReviewPatchDialog::serverChanged() { m_ui->repositories->clear(); //TODO reviewboards with private repositories don't work. Use user/pass if set. ReviewBoard::ProjectsListRequest* repo = new ReviewBoard::ProjectsListRequest(m_ui->server->url(), this); connect(repo, &ReviewBoard::ProjectsListRequest::finished, this, &ReviewPatchDialog::receivedProjects); repo->start(); } void ReviewPatchDialog::receivedProjects(KJob* job) { // TODO: check error ReviewBoard::ProjectsListRequest* pl=dynamic_cast(job); QVariantList repos = pl->repositories(); // Add default value with no repo selected. m_ui->repositories->addItem(i18n("Repository not selected"), 0); foreach(const QVariant& repo, repos) { QVariantMap repoMap=repo.toMap(); m_ui->repositories->addItem(repoMap["name"].toString(), repoMap["path"]); } connect(m_ui->repositories, static_cast(&QComboBox::currentIndexChanged), this, &ReviewPatchDialog::repositoryChanged); QAbstractItemModel* model = m_ui->repositories->model(); if(!m_preferredRepository.isEmpty()) { QModelIndexList idxs = model->match(model->index(0,0), Qt::UserRole, m_preferredRepository, 1, Qt::MatchExactly); if(idxs.isEmpty()) { idxs = model->match(model->index(0,0), Qt::DisplayRole, QUrl::fromUserInput(m_preferredRepository).fileName(), 1, Qt::MatchExactly); } if(!idxs.isEmpty()) { m_ui->repositories->setCurrentIndex(idxs.first().row()); } else qCDebug(PLUGIN_REVIEWBOARD) << "couldn't fucking find it" << m_preferredRepository; } m_ui->repositoriesBox->setEnabled(job->error()==0); } QString ReviewPatchDialog::repository() const { QComboBox* repositories = m_ui->repositories; if(repositories->currentIndex() != -1) { return repositories->itemData(repositories->currentIndex(), Qt::UserRole).toString(); } return QString(); } void ReviewPatchDialog::repositoryChanged(int index) { m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled((!isUpdateReview() && index > 0) || m_ui->reviews->currentIndex() != -1); } void ReviewPatchDialog::reviewCheckboxChanged(int status) { if (status == Qt::Checked) { m_ui->reviews->setEnabled(true); connect(m_ui->username, &QLineEdit::editingFinished, this, &ReviewPatchDialog::updateReviews); connect(m_ui->password, &QLineEdit::editingFinished, this, &ReviewPatchDialog::updateReviews); connect(m_ui->server, static_cast(&KUrlRequester::returnPressed), this, &ReviewPatchDialog::updateReviews); connect(m_ui->repositories, static_cast(&QComboBox::currentIndexChanged), this, &ReviewPatchDialog::updateReviewsList); } else { m_ui->reviews->setEnabled(false); disconnect(m_ui->username, &QLineEdit::editingFinished, this, &ReviewPatchDialog::updateReviews); disconnect(m_ui->password, &QLineEdit::editingFinished, this, &ReviewPatchDialog::updateReviews); disconnect(m_ui->server, static_cast(&KUrlRequester::returnPressed), this, &ReviewPatchDialog::updateReviews); disconnect(m_ui->repositories, static_cast(&QComboBox::currentIndexChanged), this, &ReviewPatchDialog::updateReviewsList); } updateReviews(); } void ReviewPatchDialog::receivedReviews(KJob* job) { m_reviews.clear(); // TODO: check errors QVariantList reviews = dynamic_cast(job)->reviews(); foreach(const QVariant& review, reviews) { QVariantMap reviewMap = review.toMap(); QVariantMap repoMap = reviewMap["links"].toMap()["repository"].toMap(); m_reviews.insert(repoMap["title"].toString(), qMakePair(reviewMap["summary"].toString(), reviewMap["id"])); } updateReviewsList(); } QString ReviewPatchDialog::review() const { return m_ui->reviews->itemData(m_ui->reviews->currentIndex(), Qt::UserRole).toString(); } void ReviewPatchDialog::updateReviews() { if (isUpdateReview()) { //TODO: reviewboards with private reviews don't work. Use user/pass if set. if (!m_ui->server->text().isEmpty() && !m_ui->username->text().isEmpty()) { ReviewBoard::ReviewListRequest* repo = new ReviewBoard::ReviewListRequest(m_ui->server->url(), username(), "pending", this); connect(repo, &ReviewBoard::ReviewListRequest::finished, this, &ReviewPatchDialog::receivedReviews); repo->start(); } } else { // Clear reviews combobox and enable OK Button if a repository is selected. m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_ui->repositories->currentIndex() != -1); } } bool ReviewPatchDialog::isUpdateReview() { return m_ui->reviewCheckbox->checkState() == Qt::Checked; } void ReviewPatchDialog::updateReviewsList() { QString repo = m_ui->repositories->currentText(); QPair kv; m_ui->reviews->clear(); if (m_ui->repositories->currentIndex() < 1) { // Show all Review foreach (const QString& key, m_reviews.uniqueKeys()) { foreach (kv, m_reviews.values(key)) { m_ui->reviews->addItem(kv.first, kv.second); } } } else { // Filter using actual repository. foreach (kv, m_reviews.values(repo)) { m_ui->reviews->addItem(kv.first, kv.second); } } m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(m_ui->reviews->currentIndex() != -1); } void ReviewPatchDialog::initializeFromRC(const QString& filePath) { //The .reviewboardrc files are python files, we'll read and if it doesn't work //Well bad luck. See: http://www.reviewboard.org/docs/rbtools/dev/rbt/configuration/ QRegExp rx("([\\w_]+) *= *[\"'](.*)[\"']"); QFile f(filePath); if(!f.open(QFile::ReadOnly | QFile::Text)) return; QHash values; QTextStream stream(&f); for(; !stream.atEnd(); ) { if(rx.exactMatch(stream.readLine())) { values.insert(rx.cap(1), rx.cap(2)); } } if(values.contains("REVIEWBOARD_URL")) setServer(QUrl(values["REVIEWBOARD_URL"])); if(values.contains("REPOSITORY")) setRepository(values["REPOSITORY"]); addExtraData(QStringLiteral("target_groups"), values["TARGET_GROUPS"]); addExtraData(QStringLiteral("target_people"), values["TARGET_PEOPLE"]); addExtraData(QStringLiteral("branch"), values["BRANCH"]); qCDebug(PLUGIN_REVIEWBOARD) << "found:" << values; } void ReviewPatchDialog::addExtraData(const QString& key, const QVariant &value) { if (value.isValid()) m_extraData.insert(key, value); } diff --git a/plugins/standardoutputview/outputwidget.cpp b/plugins/standardoutputview/outputwidget.cpp index 50594b6c5e..2c2f978e53 100644 --- a/plugins/standardoutputview/outputwidget.cpp +++ b/plugins/standardoutputview/outputwidget.cpp @@ -1,646 +1,644 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "outputwidget.h" #include "standardoutputview.h" -#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 +#include +#include +#include + +#include +#include +#include +#include #include #include #include "toolviewdata.h" #include "debug.h" Q_LOGGING_CATEGORY(PLUGIN_STANDARDOUTPUTVIEW, "kdevplatform.plugins.standardoutputview") Q_DECLARE_METATYPE(QTreeView*) OutputWidget::OutputWidget(QWidget* parent, const ToolViewData* tvdata) : QWidget( parent ) , tabwidget(0) , stackwidget(0) , data(tvdata) , m_closeButton(0) , m_closeOthersAction(0) , nextAction(0) , previousAction(0) , activateOnSelect(0) , focusOnSelect(0) , filterInput(0) , filterAction(0) { setWindowTitle(i18n("Output View")); setWindowIcon(tvdata->icon); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget = new QTabWidget(this); layout->addWidget( tabwidget ); m_closeButton = new QToolButton( this ); connect( m_closeButton, &QToolButton::clicked, this, &OutputWidget::closeActiveView ); m_closeButton->setIcon( QIcon::fromTheme("tab-close") ); m_closeButton->setToolTip( i18n( "Close the currently active output view") ); m_closeOthersAction = new QAction( this ); connect(m_closeOthersAction, &QAction::triggered, this, &OutputWidget::closeOtherViews); m_closeOthersAction->setIcon(QIcon::fromTheme("tab-close-other")); m_closeOthersAction->setToolTip( i18n( "Close all other output views" ) ); m_closeOthersAction->setText( m_closeOthersAction->toolTip() ); addAction(m_closeOthersAction); tabwidget->setCornerWidget(m_closeButton, Qt::TopRightCorner); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { stackwidget = new QStackedWidget( this ); layout->addWidget( stackwidget ); previousAction = new QAction( QIcon::fromTheme( "arrow-left" ), i18n("Previous Output"), this ); connect(previousAction, &QAction::triggered, this, &OutputWidget::previousOutput); addAction(previousAction); nextAction = new QAction( QIcon::fromTheme( "arrow-right" ), i18n("Next Output"), this ); connect(nextAction, &QAction::triggered, this, &OutputWidget::nextOutput); addAction(nextAction); } addAction(dynamic_cast(data->plugin->actionCollection()->action("prev_error"))); addAction(dynamic_cast(data->plugin->actionCollection()->action("next_error"))); activateOnSelect = new KToggleAction( QIcon(), i18n("Select activated Item"), this ); activateOnSelect->setChecked( true ); focusOnSelect = new KToggleAction( QIcon(), i18n("Focus when selecting Item"), this ); focusOnSelect->setChecked( false ); if( data->option & KDevelop::IOutputView::ShowItemsButton ) { addAction(activateOnSelect); addAction(focusOnSelect); } QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); QAction* action; action = new QAction(QIcon::fromTheme("go-previous"), i18n("Previous Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectPreviousItem); addAction(action); action = new QAction(QIcon::fromTheme("go-next"), i18n("Next Item"), this); connect(action, &QAction::triggered, this, &OutputWidget::selectNextItem); addAction(action); QAction* selectAllAction = KStandardAction::selectAll(this, SLOT(selectAll()), this); selectAllAction->setShortcut(QKeySequence()); //FIXME: why does CTRL-A conflict with Katepart (while CTRL-Cbelow doesn't) ? selectAllAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(selectAllAction); QAction* copyAction = KStandardAction::copy(this, SLOT(copySelection()), this); copyAction->setShortcutContext(Qt::WidgetWithChildrenShortcut); addAction(copyAction); if( data->option & KDevelop::IOutputView::AddFilterAction ) { QAction *separator = new QAction(this); separator->setSeparator(true); addAction(separator); filterInput = new QLineEdit(); filterInput->setMaximumWidth(150); filterInput->setMinimumWidth(100); filterInput->setPlaceholderText(i18n("Search...")); filterInput->setClearButtonEnabled(true); filterInput->setToolTip(i18n("Enter a wild card string to filter the output view")); filterAction = new QWidgetAction(this); filterAction->setDefaultWidget(filterInput); addAction(filterAction); connect(filterInput, &QLineEdit::textEdited, this, &OutputWidget::outputFilter ); if( data->type & KDevelop::IOutputView::MultipleView ) { connect(tabwidget, &QTabWidget::currentChanged, this, &OutputWidget::updateFilter); } else if ( data->type == KDevelop::IOutputView::HistoryView ) { connect(stackwidget, &QStackedWidget::currentChanged, this, &OutputWidget::updateFilter); } } addActions(data->actionList); connect( data, &ToolViewData::outputAdded, this, &OutputWidget::addOutput ); connect( this, &OutputWidget::outputRemoved, data->plugin, &StandardOutputView::outputRemoved ); foreach( int id, data->outputdata.keys() ) { changeModel( id ); changeDelegate( id ); } enableActions(); } void OutputWidget::addOutput( int id ) { QTreeView* listview = createListView(id); setCurrentWidget( listview ); connect( data->outputdata.value(id), &OutputData::modelChanged, this, &OutputWidget::changeModel); connect( data->outputdata.value(id), &OutputData::delegateChanged, this, &OutputWidget::changeDelegate); enableActions(); } void OutputWidget::setCurrentWidget( QTreeView* view ) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setCurrentWidget( view ); } else if( data->type & KDevelop::IOutputView::HistoryView ) { stackwidget->setCurrentWidget( view ); } } void OutputWidget::changeDelegate( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { views.value(id)->setItemDelegate(data->outputdata.value(id)->delegate); } else { addOutput(id); } } void OutputWidget::changeModel( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { OutputData* od = data->outputdata.value(id); views.value( id )->setModel(od->model); } else { addOutput( id ); } } void OutputWidget::removeOutput( int id ) { if( data->outputdata.contains( id ) && views.contains( id ) ) { QTreeView* view = views.value(id); if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( view ); if( idx != -1 ) { tabwidget->removeTab( idx ); if( proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } } } else { int idx = stackwidget->indexOf( view ); if( idx != -1 && proxyModels.contains( idx ) ) { delete proxyModels.take( idx ); filters.remove( idx ); } stackwidget->removeWidget( view ); } delete view; } else { views.value( id )->setModel( 0 ); views.value( id )->setItemDelegate( 0 ); if( proxyModels.contains( 0 ) ) { delete proxyModels.take( 0 ); filters.remove( 0 ); } } views.remove( id ); emit outputRemoved( data->toolViewId, id ); } enableActions(); } void OutputWidget::closeActiveView() { QWidget* widget = tabwidget->currentWidget(); if( !widget ) return; foreach( int id, views.keys() ) { if( views.value(id) == widget ) { OutputData* od = data->outputdata.value(id); if( od->behaviour & KDevelop::IOutputView::AllowUserClose ) { data->plugin->removeOutput( id ); } } } enableActions(); } void OutputWidget::closeOtherViews() { QWidget* widget = tabwidget->currentWidget(); if (!widget) return; foreach (int id, views.keys()) { if (views.value(id) == widget) { continue; // leave the active view open } OutputData* od = data->outputdata.value(id); if (od->behaviour & KDevelop::IOutputView::AllowUserClose) { data->plugin->removeOutput( id ); } } enableActions(); } QWidget* OutputWidget::currentWidget() const { QWidget* widget; if( data->type & KDevelop::IOutputView::MultipleView ) { widget = tabwidget->currentWidget(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { widget = stackwidget->currentWidget(); } else { widget = views.begin().value(); } return widget; } KDevelop::IOutputViewModel *OutputWidget::outputViewModel() const { QWidget* widget = currentWidget(); if( !widget || !widget->isVisible() ) return nullptr; auto view = qobject_cast(widget); if( !view ) return nullptr; QAbstractItemModel *absmodel = view->model(); KDevelop::IOutputViewModel *iface = dynamic_cast(absmodel); if ( ! iface ) { // try if it's a proxy model? if ( QAbstractProxyModel* proxy = qobject_cast(absmodel) ) { iface = dynamic_cast(proxy->sourceModel()); } } return iface; } void OutputWidget::eventuallyDoFocus() { QWidget* widget = currentWidget(); if( focusOnSelect->isChecked() && !widget->hasFocus() ) { widget->setFocus( Qt::OtherFocusReason ); } } QAbstractItemView *OutputWidget::outputView() const { auto widget = currentWidget(); return qobject_cast(widget); } void OutputWidget::activateIndex(const QModelIndex &index, QAbstractItemView *view, KDevelop::IOutputViewModel *iface) { if( ! index.isValid() ) return; int tabIndex = currentOutputIndex(); QModelIndex sourceIndex = index; QModelIndex viewIndex = index; if( QAbstractProxyModel* proxy = proxyModels.value(tabIndex) ) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source sourceIndex = proxy->mapToSource(index); } else if (proxy == view->model()) { // index is from the source, map it to the proxy viewIndex = proxy->mapFromSource(index); } } view->setCurrentIndex( viewIndex ); view->scrollTo( viewIndex ); if( activateOnSelect->isChecked() ) { iface->activate( sourceIndex ); } } void OutputWidget::selectNextItem() { selectItem(Next); } void OutputWidget::selectPreviousItem() { selectItem(Previous); } void OutputWidget::selectItem(Direction direction) { auto view = outputView(); auto iface = outputViewModel(); if ( ! view || ! iface ) return; eventuallyDoFocus(); auto index = view->currentIndex(); if (QAbstractProxyModel* proxy = proxyModels.value(currentOutputIndex())) { if ( index.model() == proxy ) { // index is from the proxy, map it to the source index = proxy->mapToSource(index); } } const auto newIndex = direction == Previous ? iface->previousHighlightIndex( index ) : iface->nextHighlightIndex( index ); qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "old:" << index << "- new:" << newIndex; activateIndex(newIndex, view, iface); } void OutputWidget::activate(const QModelIndex& index) { auto iface = outputViewModel(); auto view = outputView(); if( ! view || ! iface ) return; activateIndex(index, view, iface); } QTreeView* OutputWidget::createListView(int id) { auto createHelper = [&]() -> QTreeView* { KDevelop::FocusedTreeView* listview = new KDevelop::FocusedTreeView(this); listview->setEditTriggers( QAbstractItemView::NoEditTriggers ); listview->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); //Always enable the scrollbar, so it doesn't flash around listview->setHeaderHidden(true); listview->setUniformRowHeights(true); listview->setRootIsDecorated(false); listview->setSelectionMode( QAbstractItemView::ContiguousSelection ); if (data->outputdata.value(id)->behaviour & KDevelop::IOutputView::AutoScroll) { listview->setAutoScrollAtEnd(true); } connect(listview, &QTreeView::activated, this, &OutputWidget::activate); connect(listview, &QTreeView::clicked, this, &OutputWidget::activate); return listview; }; QTreeView* listview = 0; if( !views.contains(id) ) { bool newView = true; if( data->type & KDevelop::IOutputView::MultipleView || data->type & KDevelop::IOutputView::HistoryView ) { qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "creating listview"; listview = createHelper(); if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->addTab( listview, data->outputdata.value(id)->title ); } else { stackwidget->addWidget( listview ); stackwidget->setCurrentWidget( listview ); } } else { if( views.isEmpty() ) { listview = createHelper(); layout()->addWidget( listview ); } else { listview = views.begin().value(); newView = false; } } views[id] = listview; changeModel( id ); changeDelegate( id ); if (newView) listview->scrollToBottom(); } else { listview = views.value(id); } enableActions(); return listview; } void OutputWidget::raiseOutput(int id) { if( views.contains(id) ) { if( data->type & KDevelop::IOutputView::MultipleView ) { int idx = tabwidget->indexOf( views.value(id) ); if( idx >= 0 ) { tabwidget->setCurrentIndex( idx ); } } else if( data->type & KDevelop::IOutputView::HistoryView ) { int idx = stackwidget->indexOf( views.value(id) ); if( idx >= 0 ) { stackwidget->setCurrentIndex( idx ); } } } enableActions(); } void OutputWidget::nextOutput() { if( stackwidget && stackwidget->currentIndex() < stackwidget->count()-1 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()+1 ); } enableActions(); } void OutputWidget::previousOutput() { if( stackwidget && stackwidget->currentIndex() > 0 ) { stackwidget->setCurrentIndex( stackwidget->currentIndex()-1 ); } enableActions(); } void OutputWidget::enableActions() { if( data->type == KDevelop::IOutputView::HistoryView ) { Q_ASSERT(stackwidget); Q_ASSERT(nextAction); Q_ASSERT(previousAction); previousAction->setEnabled( ( stackwidget->currentIndex() > 0 ) ); nextAction->setEnabled( ( stackwidget->currentIndex() < stackwidget->count() - 1 ) ); } } void OutputWidget::scrollToIndex( const QModelIndex& idx ) { QWidget* w = currentWidget(); if( !w ) return; QAbstractItemView *view = dynamic_cast(w); view->scrollTo( idx ); } void OutputWidget::copySelection() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; QClipboard *cb = QApplication::clipboard(); QModelIndexList indexes = view->selectionModel()->selectedRows(); QString content; Q_FOREACH( const QModelIndex& index, indexes) { content += index.data().toString() + '\n'; } cb->setText(content); } void OutputWidget::selectAll() { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; view->selectAll(); } int OutputWidget::currentOutputIndex() { int index = 0; if( data->type & KDevelop::IOutputView::MultipleView ) { index = tabwidget->currentIndex(); } else if( data->type & KDevelop::IOutputView::HistoryView ) { index = stackwidget->currentIndex(); } return index; } void OutputWidget::outputFilter(const QString& filter) { QWidget* widget = currentWidget(); if( !widget ) return; QAbstractItemView *view = dynamic_cast(widget); if( !view ) return; int index = currentOutputIndex(); auto proxyModel = dynamic_cast(view->model()); if( !proxyModel ) { proxyModel = new QSortFilterProxyModel(view->model()); proxyModel->setDynamicSortFilter(true); proxyModel->setSourceModel(view->model()); proxyModels.insert(index, proxyModel); view->setModel(proxyModel); } QRegExp regExp(filter, Qt::CaseInsensitive); proxyModel->setFilterRegExp(regExp); filters[index] = filter; } void OutputWidget::updateFilter(int index) { if(filters.contains(index)) { filterInput->setText(filters[index]); } else { filterInput->clear(); } } void OutputWidget::setTitle(int outputId, const QString& title) { if( data->type & KDevelop::IOutputView::MultipleView ) { tabwidget->setTabText(outputId - 1, title); } } diff --git a/plugins/standardoutputview/outputwidget.h b/plugins/standardoutputview/outputwidget.h index 1f00f053c8..64a7c0eda5 100644 --- a/plugins/standardoutputview/outputwidget.h +++ b/plugins/standardoutputview/outputwidget.h @@ -1,119 +1,115 @@ /* This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #ifndef KDEVPLATFORM_PLUGIN_OUTPUTWIDGET_H #define KDEVPLATFORM_PLUGIN_OUTPUTWIDGET_H +#include #include -#include #include #include #include -class QAbstractProxyModel; +class KToggleAction; +class StandardOutputViewTest; +class QAction; class QAbstractItemView; -class QString; -class StandardOutputView; -class QSignalMapper; +class QLineEdit; +class QModelIndex; +class QSortFilterProxyModel; class QStackedWidget; -class QTreeView; +class QString; +class QTabWidget; class QToolButton; +class QTreeView; class QWidgetAction; -class QSortFilterProxyModel; -class QModelIndex; class ToolViewData; -class QTabWidget; -class KToggleAction; -class QAction; -class QAction; -class QLineEdit; -class StandardOutputViewTest; class OutputWidget : public QWidget, public KDevelop::IToolViewActionListener { Q_OBJECT Q_INTERFACES(KDevelop::IToolViewActionListener) friend class StandardOutputViewTest; public: OutputWidget(QWidget* parent, const ToolViewData* data); void removeOutput( int id ); void raiseOutput( int id ); public Q_SLOTS: void addOutput( int id ); void changeModel( int id ); void changeDelegate( int id ); void closeActiveView(); void closeOtherViews(); void selectNextItem() override; void selectPreviousItem() override; void activate(const QModelIndex&); void scrollToIndex( const QModelIndex& ); void setTitle(int outputId, const QString& title); Q_SIGNALS: void outputRemoved( int, int ); private slots: void nextOutput(); void previousOutput(); void copySelection(); void selectAll(); void outputFilter(const QString& filter); void updateFilter(int index); private: enum Direction { Next, Previous }; void selectItem(Direction direction); QTreeView* createListView(int id); void setCurrentWidget( QTreeView* view ); QWidget* currentWidget() const; void enableActions(); KDevelop::IOutputViewModel* outputViewModel() const; QAbstractItemView* outputView() const; void activateIndex(const QModelIndex& index, QAbstractItemView* view, KDevelop::IOutputViewModel* iface); void eventuallyDoFocus(); int currentOutputIndex(); QMap views; QMap proxyModels; QMap filters; QTabWidget* tabwidget; QStackedWidget* stackwidget; const ToolViewData* data; QToolButton* m_closeButton; QAction* m_closeOthersAction; QAction* nextAction; QAction* previousAction; KToggleAction* activateOnSelect; KToggleAction* focusOnSelect; QLineEdit *filterInput; QWidgetAction* filterAction; }; #endif diff --git a/plugins/standardoutputview/standardoutputview.cpp b/plugins/standardoutputview/standardoutputview.cpp index d38d1af239..d4d4cbe132 100644 --- a/plugins/standardoutputview/standardoutputview.cpp +++ b/plugins/standardoutputview/standardoutputview.cpp @@ -1,317 +1,315 @@ /* KDevelop Standard OutputView * * Copyright 2006-2007 Andreas Pakulat * Copyright 2007 Dukju Ahn * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "standardoutputview.h" #include "outputwidget.h" #include "toolviewdata.h" #include "debug.h" -#include -#include -#include #include +#include +#include +#include #include -#include -#include #include #include #include #include #include #include class OutputViewFactory : public KDevelop::IToolViewFactory{ public: OutputViewFactory(const ToolViewData* data): m_data(data) {} virtual QWidget* create(QWidget *parent = 0) override { return new OutputWidget( parent, m_data ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::BottomDockWidgetArea; } virtual void viewCreated( Sublime::View* view ) override { m_data->views << view; } virtual QString id() const override { //NOTE: id must be unique, see e.g. https://bugs.kde.org/show_bug.cgi?id=287093 return "org.kdevelop.OutputView." + QString::number(m_data->toolViewId); } private: const ToolViewData *m_data; }; StandardOutputView::StandardOutputView(QObject *parent, const QVariantList &) : KDevelop::IPlugin("kdevstandardoutputview", parent) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::IOutputView ) setXMLFile("kdevstandardoutputview.rc"); connect(KDevelop::ICore::self()->uiController()->controller(), &Sublime::Controller::aboutToRemoveView, this, &StandardOutputView::removeSublimeView); } void StandardOutputView::removeSublimeView( Sublime::View* v ) { foreach( ToolViewData* d, toolviews ) { if( d->views.contains(v) ) { if( d->views.count() == 1 ) { toolviews.remove( d->toolViewId ); ids.removeAll( d->toolViewId ); delete d; } else { d->views.removeAll(v); } } } } StandardOutputView::~StandardOutputView() { } int StandardOutputView::standardToolView( KDevelop::IOutputView::StandardToolView view ) { if( standardViews.contains( view ) ) { return standardViews.value( view ); } int ret = -1; switch( view ) { case KDevelop::IOutputView::BuildView: { ret = registerToolView( i18nc("@title:window", "Build"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme("run-build"), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::RunView: { ret = registerToolView( i18nc("@title:window", "Run"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme("system-run"), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::DebugView: { ret = registerToolView( i18nc("@title:window", "Debug"), KDevelop::IOutputView::MultipleView, QIcon::fromTheme("debugger"), KDevelop::IOutputView::AddFilterAction ); break; } case KDevelop::IOutputView::TestView: { ret = registerToolView( i18nc("@title:window", "Test"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme("system-run")); break; } case KDevelop::IOutputView::VcsView: { ret = registerToolView( i18nc("@title:window", "Version Control"), KDevelop::IOutputView::HistoryView, QIcon::fromTheme("system-run")); break; } } Q_ASSERT(ret != -1); standardViews[view] = ret; return ret; } int StandardOutputView::registerToolView( const QString& title, KDevelop::IOutputView::ViewType type, const QIcon& icon, Options option, const QList& actionList ) { // try to reuse existing toolview foreach( ToolViewData* d, toolviews ) { if ( d->type == type && d->title == title ) { return d->toolViewId; } } // register new tool view const int newid = ids.isEmpty() ? 0 : (ids.last() + 1); qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Registering view" << title << "with type:" << type << "id:" << newid; ToolViewData* tvdata = new ToolViewData( this ); tvdata->toolViewId = newid; tvdata->type = type; tvdata->title = title; tvdata->icon = icon; tvdata->plugin = this; tvdata->option = option; tvdata->actionList = actionList; core()->uiController()->addToolView( title, new OutputViewFactory( tvdata ) ); ids << newid; toolviews[newid] = tvdata; return newid; } int StandardOutputView::registerOutputInToolView( int toolViewId, const QString& title, KDevelop::IOutputView::Behaviours behaviour ) { if( !toolviews.contains( toolViewId ) ) return -1; int newid; if( ids.isEmpty() ) { newid = 0; } else { newid = ids.last()+1; } ids << newid; toolviews.value( toolViewId )->addOutput( newid, title, behaviour ); return newid; } void StandardOutputView::raiseOutput(int outputId) { foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { foreach( Sublime::View* v, toolviews.value( _id )->views ) { if( v->hasWidget() ) { OutputWidget* w = qobject_cast( v->widget() ); w->raiseOutput( outputId ); v->requestRaise(); } } } } } void StandardOutputView::setModel( int outputId, QAbstractItemModel* model ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setModel( model ); } } void StandardOutputView::setDelegate( int outputId, QAbstractItemDelegate* delegate ) { int tvid = -1; foreach( int _id, toolviews.keys() ) { if( toolviews.value( _id )->outputdata.contains( outputId ) ) { tvid = _id; break; } } if( tvid == -1 ) qCDebug(PLUGIN_STANDARDOUTPUTVIEW) << "Trying to set model on unknown view-id:" << outputId; else { toolviews.value( tvid )->outputdata.value( outputId )->setDelegate( delegate ); } } void StandardOutputView::removeToolView( int toolviewId ) { if( toolviews.contains(toolviewId) ) { ToolViewData* td = toolviews.value(toolviewId); foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) { OutputWidget* outputWidget = qobject_cast( view->widget() ); foreach( int outid, td->outputdata.keys() ) { outputWidget->removeOutput( outid ); } } foreach( Sublime::Area* area, KDevelop::ICore::self()->uiController()->controller()->allAreas() ) { area->removeToolView( view ); } } delete td; toolviews.remove(toolviewId); emit toolViewRemoved(toolviewId); } } OutputWidget* StandardOutputView::outputWidgetForId( int outputId ) const { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) return qobject_cast( view->widget() ); } } } return 0; } void StandardOutputView::scrollOutputTo( int outputId, const QModelIndex& idx ) { OutputWidget* widget = outputWidgetForId( outputId ); if( widget ) widget->scrollToIndex( idx ); } void StandardOutputView::removeOutput( int outputId ) { foreach( ToolViewData* td, toolviews ) { if( td->outputdata.contains( outputId ) ) { foreach( Sublime::View* view, td->views ) { if( view->hasWidget() ) qobject_cast( view->widget() )->removeOutput( outputId ); } td->outputdata.remove( outputId ); } } } void StandardOutputView::setTitle(int outputId, const QString& title) { outputWidgetForId(outputId)->setTitle(outputId, title); } diff --git a/plugins/testview/testview.cpp b/plugins/testview/testview.cpp index d66612337c..363917e735 100644 --- a/plugins/testview/testview.cpp +++ b/plugins/testview/testview.cpp @@ -1,428 +1,428 @@ /* This file is part of KDevelop Copyright 2012 Miha Čančula This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "testview.h" #include "testviewplugin.h" #include "testviewdebug.h" #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 -#include +#include #include using namespace KDevelop; enum CustomRoles { ProjectRole = Qt::UserRole + 1, SuiteRole, CaseRole }; //BEGIN TestViewFilterAction TestViewFilterAction::TestViewFilterAction( const QString &initialFilter, QObject* parent ) : QAction( parent ) , m_intialFilter(initialFilter) { setIcon(QIcon::fromTheme("view-filter")); setText(i18n("Filter...")); setToolTip(i18n("Insert wildcard patterns to filter the test view" " for matching test suites and cases.")); } QWidget* TestViewFilterAction::createWidget( QWidget* parent ) { QLineEdit* edit = new QLineEdit(parent); edit->setPlaceholderText(i18n("Filter...")); edit->setClearButtonEnabled(true); connect(edit, &QLineEdit::textChanged, this, &TestViewFilterAction::filterChanged); if (!m_intialFilter.isEmpty()) { edit->setText(m_intialFilter); } return edit; } //END TestViwFilterAction static const char sessionConfigGroup[] = "TestView"; static const char filterConfigKey[] = "filter"; TestView::TestView(TestViewPlugin* plugin, QWidget* parent) : QWidget(parent) , m_plugin(plugin) , m_tree(new QTreeView(this)) , m_filter(new KRecursiveFilterProxyModel(this)) { setWindowIcon(QIcon::fromTheme("preflight-verifier")); setWindowTitle(i18n("Unit Tests")); QVBoxLayout* layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); setLayout(layout); layout->addWidget(m_tree); m_tree->setSortingEnabled(true); m_tree->header()->hide(); m_tree->setIndentation(10); m_tree->setEditTriggers(QTreeView::NoEditTriggers); m_tree->setSelectionBehavior(QTreeView::SelectRows); m_tree->setSelectionMode(QTreeView::SingleSelection); m_tree->setExpandsOnDoubleClick(false); m_tree->sortByColumn(0, Qt::AscendingOrder); connect(m_tree, &QTreeView::activated, this, &TestView::doubleClicked); m_model = new QStandardItemModel(this); m_filter->setSourceModel(m_model); m_tree->setModel(m_filter); QAction* showSource = new QAction( QIcon::fromTheme("code-context"), i18n("Show Source"), this ); connect (showSource, &QAction::triggered, this, &TestView::showSource); m_contextMenuActions << showSource; QAction* runSelected = new QAction( QIcon::fromTheme("system-run"), i18n("Run Selected Tests"), this ); connect (runSelected, &QAction::triggered, this, &TestView::runSelectedTests); m_contextMenuActions << runSelected; addAction(plugin->actionCollection()->action("run_all_tests")); addAction(plugin->actionCollection()->action("stop_running_tests")); QString filterText; KConfigGroup config(ICore::self()->activeSession()->config(), sessionConfigGroup); if (config.hasKey(filterConfigKey)) { filterText = config.readEntry(filterConfigKey, QString()); } TestViewFilterAction* filterAction = new TestViewFilterAction(filterText, this); connect(filterAction, &TestViewFilterAction::filterChanged, m_filter, &QSortFilterProxyModel::setFilterFixedString); addAction(filterAction); IProjectController* pc = ICore::self()->projectController(); connect (pc, &IProjectController::projectClosed, this, &TestView::removeProject); ITestController* tc = ICore::self()->testController(); connect (tc, &ITestController::testSuiteAdded, this, &TestView::addTestSuite); connect (tc, &ITestController::testSuiteRemoved, this, &TestView::removeTestSuite); connect (tc, &ITestController::testRunFinished, this, &TestView::updateTestSuite); connect (tc, &ITestController::testRunStarted, this, &TestView::notifyTestCaseStarted); foreach (ITestSuite* suite, tc->testSuites()) { addTestSuite(suite); } } TestView::~TestView() { } void TestView::updateTestSuite(ITestSuite* suite, const TestResult& result) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Updating test suite" << suite->name(); item->setIcon(iconForTestResult(result.suiteResult)); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (result.testCaseResults.contains(caseItem->text())) { TestResult::TestCaseResult caseResult = result.testCaseResults.value(caseItem->text(), TestResult::NotRun); caseItem->setIcon(iconForTestResult(caseResult)); } } } void TestView::notifyTestCaseStarted(ITestSuite* suite, const QStringList& test_cases) { QStandardItem* item = itemForSuite(suite); if (!item) { return; } qCDebug(PLUGIN_TESTVIEW) << "Notify a test of the suite " << suite->name() << " has started"; // Global test suite icon item->setIcon(QIcon::fromTheme("process-idle")); for (int i = 0; i < item->rowCount(); ++i) { qCDebug(PLUGIN_TESTVIEW) << "Found a test case" << item->child(i)->text(); QStandardItem* caseItem = item->child(i); if (test_cases.contains(caseItem->text())) { // Each test case icon caseItem->setIcon(QIcon::fromTheme("process-idle")); } } } QIcon TestView::iconForTestResult(TestResult::TestCaseResult result) { switch (result) { case TestResult::NotRun: return QIcon::fromTheme("code-function"); case TestResult::Skipped: return QIcon::fromTheme("task-delegate"); case TestResult::Passed: return QIcon::fromTheme("dialog-ok-apply"); case TestResult::UnexpectedPass: // This is a very rare occurrence, so the icon should stand out return QIcon::fromTheme("dialog-warning"); case TestResult::Failed: return QIcon::fromTheme("edit-delete"); case TestResult::ExpectedFail: return QIcon::fromTheme("dialog-ok"); case TestResult::Error: return QIcon::fromTheme("dialog-cancel"); default: return QIcon::fromTheme(""); } } QStandardItem* TestView::itemForSuite(ITestSuite* suite) { foreach (QStandardItem* item, m_model->findItems(suite->name(), Qt::MatchRecursive)) { if (item->parent() && item->parent()->text() == suite->project()->name() && !item->parent()->parent()) { return item; } } return 0; } QStandardItem* TestView::itemForProject(IProject* project) { foreach (QStandardItem* item, m_model->findItems(project->name())) { return item; } return addProject(project); } void TestView::runSelectedTests() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } QList jobs; ITestController* tc = ICore::self()->testController(); /* * NOTE: If a test suite or a single test case was selected, * the job is launched in Verbose mode with raised output window. * If a project is selected, it is launched silently. * * This is the somewhat-intuitive approach. Maybe a configuration should be offered. */ foreach (const QModelIndex& idx, indexes) { QModelIndex index = m_filter->mapToSource(idx); if (index.parent().isValid() && indexes.contains(index.parent())) { continue; } QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // A project was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->data(ProjectRole).toString()); foreach (ITestSuite* suite, tc->testSuitesForProject(project)) { jobs << suite->launchAllCases(ITestSuite::Silent); } } else if (item->parent()->parent() == 0) { // A suite was selected IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); jobs << suite->launchAllCases(ITestSuite::Verbose); } else { // This was a single test case IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); const QString testCase = item->data(CaseRole).toString(); jobs << suite->launchCase(testCase, ITestSuite::Verbose); } } if (!jobs.isEmpty()) { KDevelop::ExecuteCompositeJob* compositeJob = new KDevelop::ExecuteCompositeJob(this, jobs); compositeJob->setObjectName(i18np("Run 1 test", "Run %1 tests", jobs.size())); compositeJob->setProperty("test_job", true); ICore::self()->runController()->registerJob(compositeJob); } } void TestView::showSource() { QModelIndexList indexes = m_tree->selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } IndexedDeclaration declaration; ITestController* tc = ICore::self()->testController(); QModelIndex index = m_filter->mapToSource(indexes.first()); QStandardItem* item = m_model->itemFromIndex(index); if (item->parent() == 0) { // No sense in finding source code for projects. return; } else if (item->parent()->parent() == 0) { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->data(SuiteRole).toString()); declaration = suite->declaration(); } else { IProject* project = ICore::self()->projectController()->findProjectByName(item->parent()->parent()->data(ProjectRole).toString()); ITestSuite* suite = tc->findTestSuite(project, item->parent()->data(SuiteRole).toString()); declaration = suite->caseDeclaration(item->data(CaseRole).toString()); } DUChainReadLocker locker(DUChain::lock()); Declaration* d = declaration.data(); if (!d) { return; } QUrl url = d->url().toUrl(); KTextEditor::Cursor cursor = d->rangeInCurrentRevision().start(); locker.unlock(); IDocumentController* dc = ICore::self()->documentController(); qCDebug(PLUGIN_TESTVIEW) << "Activating declaration in" << url; dc->openDocument(url, cursor); } void TestView::addTestSuite(ITestSuite* suite) { QStandardItem* projectItem = itemForProject(suite->project()); Q_ASSERT(projectItem); QStandardItem* suiteItem = new QStandardItem(QIcon::fromTheme("view-list-tree"), suite->name()); suiteItem->setData(suite->name(), SuiteRole); foreach (QString caseName, suite->cases()) { QStandardItem* caseItem = new QStandardItem(iconForTestResult(TestResult::NotRun), caseName); caseItem->setData(caseName, CaseRole); suiteItem->appendRow(caseItem); } projectItem->appendRow(suiteItem); } void TestView::removeTestSuite(ITestSuite* suite) { QStandardItem* item = itemForSuite(suite); item->parent()->removeRow(item->row()); } QStandardItem* TestView::addProject(IProject* project) { QStandardItem* projectItem = new QStandardItem(QIcon::fromTheme("project-development"), project->name()); projectItem->setData(project->name(), ProjectRole); m_model->appendRow(projectItem); return projectItem; } void TestView::removeProject(IProject* project) { QStandardItem* projectItem = itemForProject(project); m_model->removeRow(projectItem->row()); } void TestView::doubleClicked(const QModelIndex& index) { m_tree->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect); runSelectedTests(); } QList< QAction* > TestView::contextMenuActions() { return m_contextMenuActions; } diff --git a/plugins/vcschangesview/vcschangesviewplugin.cpp b/plugins/vcschangesview/vcschangesviewplugin.cpp index 6d5a4f9367..b7229bae37 100644 --- a/plugins/vcschangesview/vcschangesviewplugin.cpp +++ b/plugins/vcschangesview/vcschangesviewplugin.cpp @@ -1,110 +1,107 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcschangesviewplugin.h" -#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 "vcschangesview.h" -#include -#include K_PLUGIN_FACTORY_WITH_JSON(VcsProjectIntegrationFactory, "kdevvcschangesview.json", registerPlugin();) using namespace KDevelop; class VCSProjectToolViewFactory : public KDevelop::IToolViewFactory { public: VCSProjectToolViewFactory(VcsProjectIntegrationPlugin *plugin): m_plugin(plugin) {} virtual QWidget* create(QWidget *parent = 0) override { VcsChangesView* modif = new VcsChangesView(m_plugin, parent); modif->setModel(m_plugin->model()); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, static_cast&)>(&VcsChangesView::reload), m_plugin->model(), static_cast&)>(&ProjectChangesModel::reload)); QObject::connect(modif, &VcsChangesView::activated, m_plugin, &VcsProjectIntegrationPlugin::activated); return modif; } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.VCSProject"; } private: VcsProjectIntegrationPlugin *m_plugin; }; VcsProjectIntegrationPlugin::VcsProjectIntegrationPlugin(QObject* parent, const QVariantList&) : KDevelop::IPlugin("kdevvcsprojectintegration", parent) , m_model(0) { ICore::self()->uiController()->addToolView(i18n("VCS Changes"), new VCSProjectToolViewFactory(this)); - + QAction* synaction = actionCollection()->addAction( "locate_document" ); synaction->setText(i18n("Locate Current Document")); synaction->setIcon(QIcon::fromTheme("dirsync")); synaction->setToolTip(i18n("Locates the current document and selects it.")); - + QAction* reloadaction = actionCollection()->addAction( "reload_view" ); reloadaction->setText(i18n("Reload View")); reloadaction->setIcon(QIcon::fromTheme("view-refresh")); reloadaction->setToolTip(i18n("Refreshes the view for all projects, in case anything changed.")); } void VcsProjectIntegrationPlugin::activated(const QModelIndex& /*idx*/) { } ProjectChangesModel* VcsProjectIntegrationPlugin::model() { if(!m_model) { m_model = ICore::self()->projectController()->changesModel(); connect(actionCollection()->action("reload_view"), &QAction::triggered, m_model, &ProjectChangesModel::reloadAll); } - + return m_model; } #include "vcschangesviewplugin.moc" diff --git a/project/builderjob.cpp b/project/builderjob.cpp index ec78d1aa8b..826f348068 100644 --- a/project/builderjob.cpp +++ b/project/builderjob.cpp @@ -1,269 +1,269 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "builderjob.h" #include "debug.h" -#include +#include #include #include #include #include #include #include #include #include #include using namespace KDevelop; struct SubJobData { BuilderJob::BuildType type; KJob* job; ProjectBaseItem* item; }; Q_DECLARE_TYPEINFO(SubJobData, Q_MOVABLE_TYPE); namespace KDevelop { class BuilderJobPrivate { public: BuilderJobPrivate( BuilderJob* job ) : q(job) , failOnFirstError(true) { } BuilderJob* q; void addJob( BuilderJob::BuildType, ProjectBaseItem* ); bool failOnFirstError; QString buildTypeToString( BuilderJob::BuildType type ) const; bool hasJobForProject( BuilderJob::BuildType type, IProject* project ) const { foreach(const SubJobData& data, m_metadata) { if (data.type == type && data.item->project() == project) { return true; } } return false; } /** * a structure to keep metadata of all registered jobs */ QVector m_metadata; /** * get the subjob list and clear this composite job */ QVector takeJobList(); }; } QString BuilderJobPrivate::buildTypeToString(BuilderJob::BuildType type) const { switch( type ) { case BuilderJob::Build: return i18nc( "@info:status", "build" ); case BuilderJob::Clean: return i18nc( "@info:status", "clean" ); case BuilderJob::Configure: return i18nc( "@info:status", "configure" ); case BuilderJob::Install: return i18nc( "@info:status", "install" ); case BuilderJob::Prune: return i18nc( "@info:status", "prune" ); default: return QString(); } } void BuilderJobPrivate::addJob( BuilderJob::BuildType t, ProjectBaseItem* item ) { Q_ASSERT(item); qCDebug(PROJECT) << "adding build job for item:" << item->text(); Q_ASSERT(item->project()); qCDebug(PROJECT) << "project for item:" << item->project()->name(); Q_ASSERT(item->project()->projectItem()); qCDebug(PROJECT) << "project item for the project:" << item->project()->projectItem()->text(); if( !item->project()->buildSystemManager() ) { qWarning() << "no buildsystem manager for:" << item->text() << item->project()->name(); return; } qCDebug(PROJECT) << "got build system manager"; Q_ASSERT(item->project()->buildSystemManager()->builder()); KJob* j = 0; switch( t ) { case BuilderJob::Build: j = item->project()->buildSystemManager()->builder()->build( item ); break; case BuilderJob::Clean: j = item->project()->buildSystemManager()->builder()->clean( item ); break; case BuilderJob::Install: j = item->project()->buildSystemManager()->builder()->install( item ); break; case BuilderJob::Prune: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->prune( item->project() ); } break; case BuilderJob::Configure: if (!hasJobForProject(t, item->project())) { j = item->project()->buildSystemManager()->builder()->configure( item->project() ); } break; default: break; } if( j ) { q->addCustomJob( t, j, item ); } } BuilderJob::BuilderJob() : d( new BuilderJobPrivate( this ) ) { } BuilderJob::~BuilderJob() { delete d; } void BuilderJob::addItems( BuildType t, const QList& items ) { foreach( ProjectBaseItem* item, items ) { d->addJob( t, item ); } } void BuilderJob::addProjects( BuildType t, const QList& projects ) { foreach( IProject* project, projects ) { d->addJob( t, project->projectItem() ); } } void BuilderJob::addItem( BuildType t, ProjectBaseItem* item ) { d->addJob( t, item ); } void BuilderJob::addCustomJob( BuilderJob::BuildType type, KJob* job, ProjectBaseItem* item ) { if( BuilderJob* builderJob = dynamic_cast( job ) ) { // If a subjob is a builder job itself, re-own its job list to avoid having recursive composite jobs. QVector subjobs = builderJob->d->takeJobList(); builderJob->deleteLater(); foreach( const SubJobData& subjob, subjobs ) { addSubjob( subjob.job ); } d->m_metadata << subjobs; } else { addSubjob( job ); SubJobData data; data.type = type; data.job = job; data.item = item; d->m_metadata << data; } } QVector< SubJobData > BuilderJobPrivate::takeJobList() { QVector< SubJobData > ret = m_metadata; m_metadata.clear(); q->clearSubjobs(); q->setObjectName( QString() ); return ret; } void BuilderJob::updateJobName() { // Which items are mentioned in the set // Make it a list to preserve ordering; search overhead (n^2) isn't too big QList< ProjectBaseItem* > registeredItems; // Which build types are mentioned in the set // (Same rationale applies) QList< BuildType > buildTypes; // Whether there are jobs without any specific item bool hasNullItems = false; foreach( const SubJobData& subjob, d->m_metadata ) { if( subjob.item ) { if( !registeredItems.contains( subjob.item ) ) { registeredItems.append( subjob.item ); } if( !buildTypes.contains( subjob.type ) ) { buildTypes.append( subjob.type ); } } else { hasNullItems = true; } } QString itemNames; if( !hasNullItems ) { QStringList itemNamesList; foreach( ProjectBaseItem* item, registeredItems ) { itemNamesList << item->text(); } itemNames = itemNamesList.join(", "); } else { itemNames = i18nc( "Unspecified set of build items (e. g. projects, targets)", "Various items" ); } QString methodNames; QStringList methodNamesList; foreach( BuildType type, buildTypes ) { methodNamesList << d->buildTypeToString( type ); } methodNames = methodNamesList.join( ", " ); QString jobName = QStringLiteral( "%1: %2" ).arg( itemNames ).arg( methodNames ); setObjectName( jobName ); } void BuilderJob::start() { // Automatically save all documents before starting to build // might need an option to turn off at some point // Also should be moved into the builder and there try to find target(s) for the given item and then just save the documents of that target -> list?? if( ICore::self()->activeSession()->config()->group("Project Manager").readEntry( "Save All Documents Before Building", true ) ) { ICore::self()->documentController()->saveAllDocuments( IDocument::Silent ); } ExecuteCompositeJob::start(); } diff --git a/project/helper.cpp b/project/helper.cpp index e34b146966..0ea52862a7 100644 --- a/project/helper.cpp +++ b/project/helper.cpp @@ -1,194 +1,196 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "helper.h" #include "path.h" -#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 #include bool KDevelop::removeUrl(const KDevelop::IProject* project, const QUrl& url, const bool isFolder) { QWidget* window(ICore::self()->uiController()->activeMainWindow()->window()); auto job = KIO::stat(url, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(job, window); if (!job->exec()) { qWarning() << "tried to remove non-existing url:" << url << project << isFolder; return true; } IPlugin* vcsplugin=project->versionControlPlugin(); if(vcsplugin) { IBasicVersionControl* vcs=vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if(vcs->isVersionControlled(url)) { VcsJob* job=vcs->remove(QList() << url); if(job) { return job->exec(); } } } //if we didn't find a VCS, we remove using KIO (if the file still exists, the vcs plugin might have simply deleted the url without returning a job auto deleteJob = KIO::del(url); KJobWidgets::setWindow(deleteJob, window); if (!deleteJob->exec() && url.isLocalFile() && (QFileInfo(url.toLocalFile())).exists()) { KMessageBox::error( window, isFolder ? i18n( "Cannot remove folder %1.", url.toDisplayString(QUrl::PreferLocalFile) ) : i18n( "Cannot remove file %1.", url.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } return true; } bool KDevelop::removePath(const KDevelop::IProject* project, const KDevelop::Path& path, const bool isFolder) { return removeUrl(project, path.toUrl(), isFolder); } bool KDevelop::createFile(const QUrl& file) { auto statJob = KIO::stat(file, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, QApplication::activeWindow()); if (statJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "The file %1 already exists.", file.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } { auto uploadJob = KIO::storedPut(QByteArray("\n"), file, -1); KJobWidgets::setWindow(uploadJob, QApplication::activeWindow()); if (!uploadJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot create file %1.", file.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } } return true; } bool KDevelop::createFile(const KDevelop::Path& file) { return createFile(file.toUrl()); } bool KDevelop::createFolder(const QUrl& folder) { auto mkdirJob = KIO::mkdir(folder); KJobWidgets::setWindow(mkdirJob, QApplication::activeWindow()); if (!mkdirJob->exec()) { KMessageBox::error( QApplication::activeWindow(), i18n( "Cannot create folder %1.", folder.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } return true; } bool KDevelop::createFolder(const KDevelop::Path& folder) { return createFolder(folder.toUrl()); } bool KDevelop::renameUrl(const KDevelop::IProject* project, const QUrl& oldname, const QUrl& newname) { bool wasVcsMoved = false; IPlugin* vcsplugin = project->versionControlPlugin(); if (vcsplugin) { IBasicVersionControl* vcs = vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if (vcs->isVersionControlled(oldname)) { VcsJob* job = vcs->move(oldname, newname); if (job && !job->exec()) { return false; } wasVcsMoved = true; } } // Fallback for the case of no vcs, or not-vcs-managed file/folder // try to save-as the text document, so users can directly continue to work // on the renamed url as well as keeping the undo-stack intact IDocument* document = ICore::self()->documentController()->documentForUrl(oldname); if (document && document->textDocument()) { if (!document->textDocument()->saveAs(newname)) { return false; } if (!wasVcsMoved) { // unlink the old file removeUrl(project, oldname, false); } return true; } else if (!wasVcsMoved) { // fallback for non-textdocuments (also folders e.g.) KIO::CopyJob* job = KIO::move(oldname, newname); return job->exec(); } else { return true; } } bool KDevelop::renamePath(const KDevelop::IProject* project, const KDevelop::Path& oldName, const KDevelop::Path& newName) { return renameUrl(project, oldName.toUrl(), newName.toUrl()); } bool KDevelop::copyUrl(const KDevelop::IProject* project, const QUrl& source, const QUrl& target) { IPlugin* vcsplugin=project->versionControlPlugin(); if(vcsplugin) { IBasicVersionControl* vcs=vcsplugin->extension(); // We have a vcs and the file/folder is controller, need to make the rename through vcs if(vcs->isVersionControlled(source)) { VcsJob* job=vcs->copy(source, target); if(job) { return job->exec(); } } } // Fallback for the case of no vcs, or not-vcs-managed file/folder auto job = KIO::copy(source, target); return job->exec(); } bool KDevelop::copyPath(const KDevelop::IProject* project, const KDevelop::Path& source, const KDevelop::Path& target) { return copyUrl(project, source.toUrl(), target.toUrl()); } diff --git a/project/importprojectjob.cpp b/project/importprojectjob.cpp index 2c8989aded..090d319d87 100644 --- a/project/importprojectjob.cpp +++ b/project/importprojectjob.cpp @@ -1,116 +1,116 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "importprojectjob.h" #include "projectmodel.h" #include #include #include - +#include #include #include #include #include -#include + #include namespace KDevelop { class ImportProjectJobPrivate { public: ImportProjectJobPrivate() : cancel(false) {} ProjectFolderItem *m_folder; IProjectFileManager *m_importer; QFutureWatcher *m_watcher; QPointer m_project; bool cancel; void import(ProjectFolderItem* folder) { foreach(ProjectFolderItem* sub, m_importer->parse(folder)) { if(!cancel) { import(sub); } } } }; ImportProjectJob::ImportProjectJob(ProjectFolderItem *folder, IProjectFileManager *importer) : KJob(0), d(new ImportProjectJobPrivate ) { d->m_importer = importer; d->m_folder = folder; d->m_project = folder->project(); setObjectName(i18n("Project Import: %1", d->m_project->name())); connect(ICore::self(), &ICore::aboutToShutdown, this, &ImportProjectJob::aboutToShutdown); } ImportProjectJob::~ImportProjectJob() { delete d; } void ImportProjectJob::start() { d->m_watcher = new QFutureWatcher(); connect(d->m_watcher, &QFutureWatcher::finished, this, &ImportProjectJob::importDone); connect(d->m_watcher, &QFutureWatcher::canceled, this, &ImportProjectJob::importCanceled); QFuture f = QtConcurrent::run(d, &ImportProjectJobPrivate::import, d->m_folder); d->m_watcher->setFuture(f); } void ImportProjectJob::importDone() { d->m_watcher->deleteLater(); /* Goodbye to the QFutureWatcher */ emitResult(); } bool ImportProjectJob::doKill() { d->m_watcher->cancel(); d->cancel=true; setError(1); setErrorText(i18n("Project import canceled.")); d->m_watcher->waitForFinished(); return true; } void ImportProjectJob::aboutToShutdown() { kill(); } void ImportProjectJob::importCanceled() { d->m_watcher->deleteLater(); } } diff --git a/project/importprojectjob.h b/project/importprojectjob.h index 0504981b32..35357d097c 100644 --- a/project/importprojectjob.h +++ b/project/importprojectjob.h @@ -1,56 +1,56 @@ /* This file is part of KDevelop Copyright 2004 Roberto Raggi Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_IMPORTPROJECTJOB_H #define KDEVPLATFORM_IMPORTPROJECTJOB_H -#include +#include #include "projectexport.h" namespace KDevelop { class ProjectFolderItem; class IProjectFileManager; class KDEVPLATFORMPROJECT_EXPORT ImportProjectJob: public KJob { Q_OBJECT public: ImportProjectJob(ProjectFolderItem *folder, IProjectFileManager *importer); virtual ~ImportProjectJob(); public: void start() override; virtual bool doKill() override; private Q_SLOTS: void importDone(); void importCanceled(); void aboutToShutdown(); private: class ImportProjectJobPrivate* const d; friend class ImportProjectJobPrivate; }; } #endif // KDEVPLATFORM_IMPORTPROJECTJOB_H diff --git a/project/projectconfigskeleton.cpp b/project/projectconfigskeleton.cpp index 0720210c79..7dd4dfa87d 100644 --- a/project/projectconfigskeleton.cpp +++ b/project/projectconfigskeleton.cpp @@ -1,169 +1,169 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectconfigskeleton.h" #include "debug.h" #include #include -#include +#include using namespace KDevelop; struct KDevelop::ProjectConfigSkeletonPrivate { QString m_developerTempFile; QString m_projectTempFile; Path m_projectFile; Path m_developerFile; bool mUseDefaults; }; ProjectConfigSkeleton::ProjectConfigSkeleton( const QString & configname ) : KConfigSkeleton( configname ), d( new ProjectConfigSkeletonPrivate ) { d->m_developerTempFile = configname; } ProjectConfigSkeleton::ProjectConfigSkeleton( KSharedConfigPtr config ) : KConfigSkeleton( config ), d( new ProjectConfigSkeletonPrivate ) { } void ProjectConfigSkeleton::setDeveloperTempFile( const QString& cfg ) { d->m_developerTempFile = cfg; setSharedConfig( KSharedConfig::openConfig( cfg ) ); } void ProjectConfigSkeleton::setProjectTempFile( const QString& cfg ) { d->m_projectTempFile = cfg; config()->addConfigSources( QStringList() << cfg ); readConfig(); } void ProjectConfigSkeleton::setProjectFile( const Path& cfg ) { d->m_projectFile = cfg; } void ProjectConfigSkeleton::setDeveloperFile( const Path& cfg ) { d->m_developerFile = cfg; } Path ProjectConfigSkeleton::projectFile() const { return d->m_projectFile; } Path ProjectConfigSkeleton::developerFile() const { return d->m_developerFile; } void ProjectConfigSkeleton::setDefaults() { qCDebug(PROJECT) << "Setting Defaults"; KConfig cfg( d->m_projectTempFile ); Q_FOREACH( KConfigSkeletonItem* item, items() ) { item->swapDefault(); if( cfg.hasGroup( item->group() ) ) { KConfigGroup grp = cfg.group( item->group() ); if( grp.hasKey( item->key() ) ) item->setProperty( grp.readEntry( item->key(), item->property() ) ); } } } bool ProjectConfigSkeleton::useDefaults( bool b ) { if( b == d->mUseDefaults ) return d->mUseDefaults; if( b ) { KConfig cfg( d->m_projectTempFile ); Q_FOREACH( KConfigSkeletonItem* item, items() ) { item->swapDefault(); if( cfg.hasGroup( item->group() ) ) { qCDebug(PROJECT) << "reading"; KConfigGroup grp = cfg.group( item->group() ); - if( grp.hasKey( item->key() ) ) + if( grp.hasKey( item->key() ) ) item->setProperty( grp.readEntry( item->key(), item->property() ) ); } } - }else + } else { KConfig cfg( d->m_developerTempFile ); KConfig defCfg( d->m_projectTempFile ); Q_FOREACH( KConfigSkeletonItem* item, items() ) { if( cfg.hasGroup( item->group() ) ) { KConfigGroup grp = cfg.group( item->group() ); - if( grp.hasKey( item->key() ) ) + if( grp.hasKey( item->key() ) ) item->setProperty( grp.readEntry( item->key(), item->property() ) ); - else - { + else + { KConfigGroup grp = defCfg.group( item->group() ); item->setProperty( grp.readEntry( item->key(), item->property() ) ); - } - }else + } + } else { KConfigGroup grp = defCfg.group( item->group() ); item->setProperty( grp.readEntry( item->key(), item->property() ) ); } } } d->mUseDefaults = b; return !d->mUseDefaults; } bool ProjectConfigSkeleton::writeConfig() { KConfigSkeletonItem::List myitems = items(); KConfigSkeletonItem::List::ConstIterator it; for( it = myitems.constBegin(); it != myitems.constEnd(); ++it ) { (*it)->writeConfig( config() ); } config()->sync(); readConfig(); auto copyJob = KIO::copy(QUrl::fromLocalFile(d->m_developerTempFile), d->m_developerFile.toUrl()); copyJob ->exec(); emit configChanged(); return true; } ProjectConfigSkeleton::~ProjectConfigSkeleton() { delete d; } diff --git a/project/projectitemlineedit.cpp b/project/projectitemlineedit.cpp index 0c3447bee8..c21b57137c 100644 --- a/project/projectitemlineedit.cpp +++ b/project/projectitemlineedit.cpp @@ -1,243 +1,241 @@ /*************************************************************************** * Copyright 2008 Aleix Pol * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectitemlineedit.h" +#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 "projectproxymodel.h" -#include -#include -#include -#include -#include -#include -#include -#include static const QChar sep = '/'; static const QChar escape = '\\'; class ProjectItemCompleter : public QCompleter { Q_OBJECT public: ProjectItemCompleter(QObject* parent=0); - + QString separator() const { return sep; } QStringList splitPath(const QString &path) const override; QString pathFromIndex(const QModelIndex& index) const override; - + void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } - + private: KDevelop::ProjectModel* mModel; KDevelop::ProjectBaseItem* mBase; }; class ProjectItemValidator : public QValidator { Q_OBJECT public: ProjectItemValidator(QObject* parent = 0 ); QValidator::State validate( QString& input, int& pos ) const override; - + void setBaseItem( KDevelop::ProjectBaseItem* item ) { mBase = item; } - + private: KDevelop::ProjectBaseItem* mBase; }; ProjectItemCompleter::ProjectItemCompleter(QObject* parent) : QCompleter(parent), mModel(KDevelop::ICore::self()->projectController()->projectModel()), mBase( 0 ) { setModel(mModel); setCaseSensitivity( Qt::CaseInsensitive ); } QStringList ProjectItemCompleter::splitPath(const QString& path) const { - return joinProjectBasePath( KDevelop::splitWithEscaping( path, sep, escape ), mBase ); + return joinProjectBasePath( KDevelop::splitWithEscaping( path, sep, escape ), mBase ); } QString ProjectItemCompleter::pathFromIndex(const QModelIndex& index) const { QString postfix; if(mModel->itemFromIndex(index)->folder()) postfix=sep; return KDevelop::joinWithEscaping(removeProjectBasePath( mModel->pathFromIndex(index), mBase ), sep, escape)+postfix; } ProjectItemValidator::ProjectItemValidator(QObject* parent): QValidator(parent), mBase(0) { } QValidator::State ProjectItemValidator::validate(QString& input, int& pos) const { Q_UNUSED( pos ); KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList path = joinProjectBasePath( KDevelop::splitWithEscaping( input, sep, escape ), mBase ); QModelIndex idx = model->pathToIndex( path ); QValidator::State state = input.isEmpty() ? QValidator::Intermediate : QValidator::Invalid; if( idx.isValid() ) { state = QValidator::Acceptable; } else if( path.count() > 1 ) { // Check beginning of path and if that is ok, then try to find a child QString end = path.takeLast(); idx = model->pathToIndex( path ); if( idx.isValid() ) { for( int i = 0; i < model->rowCount( idx ); i++ ) { if( model->data( model->index( i, 0, idx ) ).toString().startsWith( end, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } } else if( path.count() == 1 ) { // Check for a project whose name beings with the input QString first = path.first(); foreach( KDevelop::IProject* project, KDevelop::ICore::self()->projectController()->projects() ) { if( project->name().startsWith( first, Qt::CaseInsensitive ) ) { state = QValidator::Intermediate; break; } } } return state; } ProjectItemLineEdit::ProjectItemLineEdit(QWidget* parent) : QLineEdit(parent), - m_base(0), - m_completer( new ProjectItemCompleter( this ) ), + m_base(0), + m_completer( new ProjectItemCompleter( this ) ), m_validator( new ProjectItemValidator( this ) ) { setCompleter( m_completer ); setValidator( m_validator ); setPlaceholderText( i18n("Enter the path to an item from the projects tree" ) ); - + QAction* selectItemAction = new QAction(QIcon::fromTheme("folder-document"), i18n("Select..."), this); connect(selectItemAction, &QAction::triggered, this, &ProjectItemLineEdit::selectItemDialog); addAction(selectItemAction); - + setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &ProjectItemLineEdit::customContextMenuRequested, this, &ProjectItemLineEdit::showCtxMenu); } void ProjectItemLineEdit::showCtxMenu(const QPoint& p) { QScopedPointer menu(createStandardContextMenu()); menu->addActions(actions()); menu->exec(mapToGlobal(p)); } bool ProjectItemLineEdit::selectItemDialog() { KDevelop::ProjectModel* model=KDevelop::ICore::self()->projectController()->projectModel(); - + QWidget* w=new QWidget; w->setLayout(new QVBoxLayout(w)); QTreeView* view = new QTreeView(w); ProjectProxyModel* proxymodel = new ProjectProxyModel(view); proxymodel->setSourceModel(model); view->header()->hide(); view->setModel(proxymodel); view->setSelectionMode(QAbstractItemView::SingleSelection); w->layout()->addWidget(new QLabel(i18n("Select the item you want to get the path from."))); w->layout()->addWidget(view); - + QDialog dialog; dialog.setWindowTitle(i18n("Select an item...")); QVBoxLayout *mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(w); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); int res = dialog.exec(); - + if(res==QDialog::Accepted && view->selectionModel()->hasSelection()) { QModelIndex idx=proxymodel->mapToSource(view->selectionModel()->selectedIndexes().first()); - + setText(KDevelop::joinWithEscaping(model->pathFromIndex(idx), sep, escape)); selectAll(); return true; } return false; } void ProjectItemLineEdit::setItemPath(const QStringList& list) { - setText( KDevelop::joinWithEscaping( removeProjectBasePath( list, m_base ), sep, escape ) ); + setText( KDevelop::joinWithEscaping( removeProjectBasePath( list, m_base ), sep, escape ) ); } QStringList ProjectItemLineEdit::itemPath() const { return joinProjectBasePath( KDevelop::splitWithEscaping( text(), sep, escape ), m_base ); } void ProjectItemLineEdit::setBaseItem(KDevelop::ProjectBaseItem* item) { m_base = item; m_validator->setBaseItem( m_base ); m_completer->setBaseItem( m_base ); } KDevelop::ProjectBaseItem* ProjectItemLineEdit::baseItem() const { return m_base; } KDevelop::ProjectBaseItem* ProjectItemLineEdit::currentItem() const { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); return model->itemFromIndex(model->pathToIndex(KDevelop::splitWithEscaping(text(),'/', '\\'))); } #include "projectitemlineedit.moc" #include "moc_projectitemlineedit.cpp" diff --git a/project/projectmodel.cpp b/project/projectmodel.cpp index 49cd984bdc..a2eca52460 100644 --- a/project/projectmodel.cpp +++ b/project/projectmodel.cpp @@ -1,1209 +1,1201 @@ /* This file is part of KDevelop Copyright 2005 Roberto Raggi Copyright 2007 Andreas Pakulat Copyright 2007 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectmodel.h" -#include -#include -#include -#include -#include -#include #include +#include +#include +#include + +#include +#include #include #include #include #include "interfaces/iprojectfilemanager.h" #include -#include -#include -#include -#include -#include -#include -#include #include "path.h" namespace KDevelop { QStringList removeProjectBasePath( const QStringList& fullpath, KDevelop::ProjectBaseItem* item ) { QStringList result = fullpath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QStringList basePath = model->pathFromIndex( model->indexFromItem( item ) ); if( basePath.count() >= fullpath.count() ) { return QStringList(); } for( int i = 0; i < basePath.count(); i++ ) { result.takeFirst(); } } return result; } QStringList joinProjectBasePath( const QStringList& partialpath, KDevelop::ProjectBaseItem* item ) { QStringList basePath; if( item ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); basePath = model->pathFromIndex( model->indexFromItem( item ) ); } return basePath + partialpath; } inline uint indexForPath( const Path& path ) { return IndexedString::indexForString(path.pathOrUrl()); } class ProjectModelPrivate { public: ProjectModelPrivate( ProjectModel* model ): model( model ) { } ProjectBaseItem* rootItem; ProjectModel* model; ProjectBaseItem* itemFromIndex( const QModelIndex& idx ) { if( !idx.isValid() ) { return rootItem; } if( idx.model() != model ) { return 0; } return model->itemFromIndex( idx ); } // a hash of IndexedString::indexForString(path) <-> ProjectBaseItem for fast lookup QMultiHash pathLookupTable; }; class ProjectBaseItemPrivate { public: ProjectBaseItemPrivate() : project(0), parent(0), row(-1), model(0), m_pathIndex(0) {} IProject* project; ProjectBaseItem* parent; int row; QList children; QString text; ProjectBaseItem::ProjectItemType type; Qt::ItemFlags flags; ProjectModel* model; Path m_path; uint m_pathIndex; QString iconName; ProjectBaseItem::RenameStatus renameBaseItem(ProjectBaseItem* item, const QString& newName) { if (item->parent()) { foreach(ProjectBaseItem* sibling, item->parent()->children()) { if (sibling->text() == newName) { return ProjectBaseItem::ExistingItemSameName; } } } item->setText( newName ); return ProjectBaseItem::RenameOk; } ProjectBaseItem::RenameStatus renameFileOrFolder(ProjectBaseItem* item, const QString& newName) { Q_ASSERT(item->file() || item->folder()); if (newName.contains('/')) { return ProjectBaseItem::InvalidNewName; } if (item->text() == newName) { return ProjectBaseItem::RenameOk; } Path newPath = item->path(); newPath.setLastPathSegment(newName); - KIO::UDSEntry entry; auto job = KIO::stat(newPath.toUrl(), KIO::StatJob::SourceSide, 0); if (job->exec()) { // file/folder exists already return ProjectBaseItem::ExistingItemSameName; } if( !item->project() || !item->project()->projectFileManager() ) { return renameBaseItem(item, newName); } else if( item->folder() && item->project()->projectFileManager()->renameFolder(item->folder(), newPath) ) { return ProjectBaseItem::RenameOk; } else if ( item->file() && item->project()->projectFileManager()->renameFile(item->file(), newPath) ) { return ProjectBaseItem::RenameOk; } else { return ProjectBaseItem::ProjectManagerRenameFailed; } } }; ProjectBaseItem::ProjectBaseItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : d_ptr(new ProjectBaseItemPrivate) { Q_ASSERT(!name.isEmpty() || !parent); Q_D(ProjectBaseItem); d->project = project; d->text = name; d->flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; if( parent ) { parent->appendRow( this ); } } ProjectBaseItem::~ProjectBaseItem() { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } if( parent() ) { parent()->takeRow( d->row ); } else if( model() ) { model()->takeRow( d->row ); } removeRows(0, d->children.size()); delete d; } ProjectBaseItem* ProjectBaseItem::child( int row ) const { Q_D(const ProjectBaseItem); if( row < 0 || row >= d->children.length() ) { return 0; } return d->children.at( row ); } QList< ProjectBaseItem* > ProjectBaseItem::children() const { Q_D(const ProjectBaseItem); return d->children; } ProjectBaseItem* ProjectBaseItem::takeRow(int row) { Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row < d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row); } ProjectBaseItem* olditem = d->children.takeAt( row ); olditem->d_func()->parent = 0; olditem->d_func()->row = -1; olditem->setModel( 0 ); for(int i=row; id_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } if( model() ) { model()->endRemoveRows(); } return olditem; } void ProjectBaseItem::removeRow( int row ) { delete takeRow( row ); } void ProjectBaseItem::removeRows(int row, int count) { if (!count) { return; } Q_D(ProjectBaseItem); Q_ASSERT(row >= 0 && row + count <= d->children.size()); if( model() ) { model()->beginRemoveRows(index(), row, row + count - 1); } //NOTE: we unset parent, row and model manually to speed up the deletion if (row == 0 && count == d->children.size()) { // optimize if we want to delete all foreach(ProjectBaseItem* item, d->children) { item->d_func()->parent = 0; item->d_func()->row = -1; item->setModel( 0 ); delete item; } d->children.clear(); } else { for (int i = row; i < count; ++i) { ProjectBaseItem* item = d->children.at(i); item->d_func()->parent = 0; item->d_func()->row = -1; item->setModel( 0 ); delete d->children.takeAt( row ); } for(int i = row; i < d->children.size(); ++i) { d->children.at(i)->d_func()->row--; Q_ASSERT(child(i)->d_func()->row==i); } } if( model() ) { model()->endRemoveRows(); } } QModelIndex ProjectBaseItem::index() const { if( model() ) { return model()->indexFromItem( this ); } return QModelIndex(); } int ProjectBaseItem::rowCount() const { Q_D(const ProjectBaseItem); return d->children.count(); } int ProjectBaseItem::type() const { return ProjectBaseItem::BaseItem; } ProjectModel* ProjectBaseItem::model() const { Q_D(const ProjectBaseItem); return d->model; } ProjectBaseItem* ProjectBaseItem::parent() const { Q_D(const ProjectBaseItem); if( model() && model()->d->rootItem == d->parent ) { return 0; } return d->parent; } int ProjectBaseItem::row() const { Q_D(const ProjectBaseItem); return d->row; } QString ProjectBaseItem::text() const { Q_D(const ProjectBaseItem); if( project() && !parent() ) { return project()->name(); } else { return d->text; } } void ProjectBaseItem::setModel( ProjectModel* model ) { Q_D(ProjectBaseItem); if (model == d->model) { return; } if (d->model && d->m_pathIndex) { d->model->d->pathLookupTable.remove(d->m_pathIndex, this); } d->model = model; if (model && d->m_pathIndex) { model->d->pathLookupTable.insert(d->m_pathIndex, this); } foreach( ProjectBaseItem* item, d->children ) { item->setModel( model ); } } void ProjectBaseItem::setRow( int row ) { Q_D(ProjectBaseItem); d->row = row; } void ProjectBaseItem::setText( const QString& text ) { Q_ASSERT(!text.isEmpty() || !parent()); Q_D(ProjectBaseItem); d->text = text; if( d->model ) { QModelIndex idx = index(); QMetaObject::invokeMethod( d->model, "dataChanged", Qt::DirectConnection, Q_ARG(QModelIndex, idx), Q_ARG(QModelIndex, idx) ); } } ProjectBaseItem::RenameStatus ProjectBaseItem::rename(const QString& newName) { Q_D(ProjectBaseItem); return d->renameBaseItem(this, newName); } KDevelop::ProjectBaseItem::ProjectItemType baseType( int type ) { if( type == KDevelop::ProjectBaseItem::Folder || type == KDevelop::ProjectBaseItem::BuildFolder ) return KDevelop::ProjectBaseItem::Folder; if( type == KDevelop::ProjectBaseItem::Target || type == KDevelop::ProjectBaseItem::ExecutableTarget || type == KDevelop::ProjectBaseItem::LibraryTarget) return KDevelop::ProjectBaseItem::Target; return static_cast( type ); } bool ProjectBaseItem::lessThan( const KDevelop::ProjectBaseItem* item ) const { if(item->type() >= KDevelop::ProjectBaseItem::CustomProjectItemType ) { // For custom types we want to make sure that if they override lessThan, then we // prefer their lessThan implementation return !item->lessThan( this ); } KDevelop::ProjectBaseItem::ProjectItemType leftType=baseType(type()), rightType=baseType(item->type()); if(leftType==rightType) { if(leftType==KDevelop::ProjectBaseItem::File) { return file()->fileName().compare(item->file()->fileName(), Qt::CaseInsensitive) < 0; } return this->text()text(); } else { return leftTypepath() < item2->path(); } IProject* ProjectBaseItem::project() const { Q_D(const ProjectBaseItem); return d->project; } void ProjectBaseItem::appendRow( ProjectBaseItem* item ) { Q_D(ProjectBaseItem); if( !item ) { return; } if( item->parent() ) { // Proper way is to first removeRow() on the original parent, then appendRow on this one qWarning() << "Ignoring double insertion of item" << item; return; } // this is too slow... O(n) and thankfully not a problem anyways // Q_ASSERT(!d->children.contains(item)); int startrow,endrow; if( model() ) { startrow = endrow = d->children.count(); model()->beginInsertRows(index(), startrow, endrow); } d->children.append( item ); item->setRow( d->children.count() - 1 ); item->d_func()->parent = this; item->setModel( model() ); if( model() ) { model()->endInsertRows(); } } QUrl ProjectBaseItem::url( ) const { Q_D(const ProjectBaseItem); QUrl url = d->m_path.toUrl(); if (folder()) { // FIXME: is this required? // url.adjustPath(QUrl::AddTrailingSlash); } return url; } Path ProjectBaseItem::path() const { Q_D(const ProjectBaseItem); return d->m_path; } QString ProjectBaseItem::baseName() const { return text(); } void ProjectBaseItem::setPath( const Path& path) { Q_D(ProjectBaseItem); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.remove(d->m_pathIndex, this); } d->m_path = path; d->m_pathIndex = indexForPath(path); setText( path.lastPathSegment() ); if (model() && d->m_pathIndex) { model()->d->pathLookupTable.insert(d->m_pathIndex, this); } } Qt::ItemFlags ProjectBaseItem::flags() { Q_D(ProjectBaseItem); return d->flags; } Qt::DropActions ProjectModel::supportedDropActions() const { return (Qt::DropActions)(Qt::MoveAction); } void ProjectBaseItem::setFlags(Qt::ItemFlags flags) { Q_D(ProjectBaseItem); d->flags = flags; if(d->model) d->model->dataChanged(index(), index()); } QString ProjectBaseItem::iconName() const { return ""; } ProjectFolderItem *ProjectBaseItem::folder() const { return 0; } ProjectTargetItem *ProjectBaseItem::target() const { return 0; } ProjectExecutableTargetItem *ProjectBaseItem::executable() const { return 0; } ProjectFileItem *ProjectBaseItem::file() const { return 0; } QList ProjectBaseItem::folderList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Folder || item->type() == BuildFolder ) { ProjectFolderItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::targetList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); if ( item->type() == Target || item->type() == LibraryTarget || item->type() == ExecutableTarget ) { ProjectTargetItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } QList ProjectBaseItem::fileList() const { QList lst; for ( int i = 0; i < rowCount(); ++i ) { ProjectBaseItem* item = child( i ); Q_ASSERT(item); if ( item && item->type() == File ) { ProjectFileItem *kdevitem = dynamic_cast( item ); if ( kdevitem ) lst.append( kdevitem ); } } return lst; } void ProjectModel::clear() { d->rootItem->removeRows(0, d->rootItem->rowCount()); } ProjectFolderItem::ProjectFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setPath( path ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project && project->path() != path) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::ProjectFolderItem( const QString & name, ProjectBaseItem * parent ) : ProjectBaseItem( parent->project(), name, parent ) { setPath( Path(parent->path(), name) ); setFlags(flags() | Qt::ItemIsDropEnabled); if (project() && project()->path() != path()) setFlags(flags() | Qt::ItemIsDragEnabled); } ProjectFolderItem::~ProjectFolderItem() { } void ProjectFolderItem::setPath( const Path& path ) { ProjectBaseItem::setPath(path); propagateRename(path); } ProjectFolderItem *ProjectFolderItem::folder() const { return const_cast(this); } int ProjectFolderItem::type() const { return ProjectBaseItem::Folder; } QString ProjectFolderItem::folderName() const { return baseName(); } void ProjectFolderItem::propagateRename( const Path& newBase ) const { Path path = newBase; path.addPath("dummy"); foreach( KDevelop::ProjectBaseItem* child, children() ) { path.setLastPathSegment( child->text() ); child->setPath( path ); const ProjectFolderItem* folder = child->folder(); if ( folder ) { folder->propagateRename( path ); } } } ProjectBaseItem::RenameStatus ProjectFolderItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } bool ProjectFolderItem::hasFileOrFolder(const QString& name) const { foreach ( ProjectBaseItem* item, children() ) { if ( (item->type() == Folder || item->type() == File || item->type() == BuildFolder) && name == item->baseName() ) { return true; } } return false; } bool ProjectBaseItem::isProjectRoot() const { return parent()==0; } ProjectBuildFolderItem::ProjectBuildFolderItem(IProject* project, const Path& path, ProjectBaseItem *parent) : ProjectFolderItem( project, path, parent ) { } ProjectBuildFolderItem::ProjectBuildFolderItem( const QString& name, ProjectBaseItem* parent ) : ProjectFolderItem( name, parent ) { } QString ProjectFolderItem::iconName() const { return "folder"; } int ProjectBuildFolderItem::type() const { return ProjectBaseItem::BuildFolder; } QString ProjectBuildFolderItem::iconName() const { return "folder-development"; } ProjectFileItem::ProjectFileItem( IProject* project, const Path& path, ProjectBaseItem* parent ) : ProjectBaseItem( project, path.lastPathSegment(), parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( path ); } ProjectFileItem::ProjectFileItem( const QString& name, ProjectBaseItem* parent ) : ProjectBaseItem( parent->project(), name, parent ) { setFlags(flags() | Qt::ItemIsDragEnabled); setPath( Path(parent->path(), name) ); } ProjectFileItem::~ProjectFileItem() { if( project() && d_ptr->m_pathIndex ) { project()->removeFromFileSet( this ); } } IndexedString ProjectFileItem::indexedPath() const { return IndexedString::fromIndex( d_ptr->m_pathIndex ); } ProjectBaseItem::RenameStatus ProjectFileItem::rename(const QString& newName) { return d_ptr->renameFileOrFolder(this, newName); } QString ProjectFileItem::fileName() const { return baseName(); } // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap static const int maximumCacheExtensionLength = 3; bool isNumeric(const QStringRef& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str.at(a).isNumber()) return false; return true; } class IconNameCache { public: QString iconNameForPath(const Path& path, const QString& fileName) { // find icon name based on file extension, if possible QString extension; int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); if( extensionStart != -1 && fileName.length() - extensionStart - 1 <= maximumCacheExtensionLength ) { QStringRef extRef = fileName.midRef(extensionStart + 1); if( isNumeric(extRef) ) { // don't cache numeric extensions extRef.clear(); } if( !extRef.isEmpty() ) { extension = extRef.toString(); QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = fileExtensionToIcon.constFind( extension ); if( it != fileExtensionToIcon.constEnd() ) { return *it; } } } QMimeType mime = QMimeDatabase().mimeTypeForFile(path.lastPathSegment(), QMimeDatabase::MatchExtension); // no I/O QMutexLocker lock(&mutex); QHash< QString, QString >::const_iterator it = mimeToIcon.constFind(mime.name()); QString iconName; if ( it == mimeToIcon.constEnd() ) { iconName = mime.iconName(); mimeToIcon.insert(mime.name(), iconName); } else { iconName = *it; } if ( !extension.isEmpty() ) { fileExtensionToIcon.insert(extension, iconName); } return iconName; } QMutex mutex; QHash mimeToIcon; QHash fileExtensionToIcon; }; Q_GLOBAL_STATIC(IconNameCache, s_cache); QString ProjectFileItem::iconName() const { // think of d_ptr->iconName as mutable, possible since d_ptr is not const if (d_ptr->iconName.isEmpty()) { // lazy load implementation of icon lookup d_ptr->iconName = s_cache->iconNameForPath( d_ptr->m_path, d_ptr->text ); // we should always get *some* icon name back Q_ASSERT(!d_ptr->iconName.isEmpty()); } return d_ptr->iconName; } void ProjectFileItem::setPath( const Path& path ) { if (path == d_ptr->m_path) { return; } if( project() && d_ptr->m_pathIndex ) { // remove from fileset if we are in there project()->removeFromFileSet( this ); } ProjectBaseItem::setPath( path ); if( project() && d_ptr->m_pathIndex ) { // add to fileset with new path project()->addToFileSet( this ); } // invalidate icon name for future lazy-loaded updated d_ptr->iconName.clear(); } int ProjectFileItem::type() const { return ProjectBaseItem::File; } ProjectFileItem *ProjectFileItem::file() const { return const_cast( this ); } ProjectTargetItem::ProjectTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectBaseItem( project, name, parent ) { setFlags(flags() | Qt::ItemIsDropEnabled); } QString ProjectTargetItem::iconName() const { return "system-run"; } void ProjectTargetItem::setPath( const Path& path ) { // don't call base class, it calls setText with the new path's filename // which we do not want for target items d_ptr->m_path = path; } int ProjectTargetItem::type() const { return ProjectBaseItem::Target; } ProjectTargetItem *ProjectTargetItem::target() const { return const_cast( this ); } ProjectExecutableTargetItem::ProjectExecutableTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) { } ProjectExecutableTargetItem *ProjectExecutableTargetItem::executable() const { return const_cast( this ); } int ProjectExecutableTargetItem::type() const { return ProjectBaseItem::ExecutableTarget; } ProjectLibraryTargetItem::ProjectLibraryTargetItem( IProject* project, const QString &name, ProjectBaseItem *parent ) : ProjectTargetItem(project, name, parent) {} int ProjectLibraryTargetItem::type() const { return ProjectBaseItem::LibraryTarget; } QModelIndex ProjectModel::pathToIndex(const QStringList& tofetch_) const { if(tofetch_.isEmpty()) return QModelIndex(); QStringList tofetch(tofetch_); if(tofetch.last().isEmpty()) tofetch.takeLast(); QModelIndex current=index(0,0, QModelIndex()); QModelIndex ret; for(int a = 0; a < tofetch.size(); ++a) { const QString& currentName = tofetch[a]; bool matched = false; QModelIndexList l = match(current, Qt::DisplayRole, currentName, -1, Qt::MatchExactly); foreach(const QModelIndex& idx, l) { //If this is not the last item, only match folders, as there may be targets and folders with the same name if(a == tofetch.size()-1 || itemFromIndex(idx)->folder()) { ret = idx; current = index(0,0, ret); matched = true; break; } } if(!matched) { ret = QModelIndex(); break; } } Q_ASSERT(!ret.isValid() || data(ret).toString()==tofetch.last()); return ret; } QStringList ProjectModel::pathFromIndex(const QModelIndex& index) const { if (!index.isValid()) return QStringList(); QModelIndex idx = index; QStringList list; do { QString t = data(idx, Qt::DisplayRole).toString(); list.prepend(t); QModelIndex parent = idx.parent(); idx = parent.sibling(parent.row(), index.column()); } while (idx.isValid()); return list; } int ProjectModel::columnCount( const QModelIndex& ) const { return 1; } int ProjectModel::rowCount( const QModelIndex& parent ) const { ProjectBaseItem* item = d->itemFromIndex( parent ); return item ? item->rowCount() : 0; } QModelIndex ProjectModel::parent( const QModelIndex& child ) const { if( child.isValid() ) { ProjectBaseItem* item = static_cast( child.internalPointer() ); return indexFromItem( item ); } return QModelIndex(); } QModelIndex ProjectModel::indexFromItem( const ProjectBaseItem* item ) const { if( item && item->d_func()->parent ) { return createIndex( item->row(), 0, item->d_func()->parent ); } return QModelIndex(); } ProjectBaseItem* ProjectModel::itemFromIndex( const QModelIndex& index ) const { if( index.row() >= 0 && index.column() == 0 && index.model() == this ) { ProjectBaseItem* parent = static_cast( index.internalPointer() ); if( parent ) { return parent->child( index.row() ); } } return 0; } QSet supportedRoles() { QSet ret; ret << Qt::DisplayRole; ret << Qt::ToolTipRole; ret << Qt::DecorationRole; ret << KDevelop::ProjectModel::ProjectItemRole; ret << KDevelop::ProjectModel::ProjectRole; return ret; } QVariant ProjectModel::data( const QModelIndex& index, int role ) const { static QSet allowedRoles = supportedRoles(); if( allowedRoles.contains(role) && index.isValid() ) { ProjectBaseItem* item = itemFromIndex( index ); if( item ) { switch(role) { case Qt::DecorationRole: return QIcon::fromTheme(item->iconName()); case Qt::ToolTipRole: return item->path().pathOrUrl(); case Qt::DisplayRole: return item->text(); case ProjectItemRole: return QVariant::fromValue(item); case ProjectRole: return QVariant::fromValue(item->project()); } } } return QVariant(); } ProjectModel::ProjectModel( QObject *parent ) : QAbstractItemModel( parent ), d( new ProjectModelPrivate( this ) ) { d->rootItem = new ProjectBaseItem( 0, "", 0 ); d->rootItem->setModel( this ); } ProjectModel::~ProjectModel() { delete d; } ProjectVisitor::ProjectVisitor() { } QModelIndex ProjectModel::index( int row, int column, const QModelIndex& parent ) const { ProjectBaseItem* parentItem = d->itemFromIndex( parent ); if( parentItem && row >= 0 && row < parentItem->rowCount() && column == 0 ) { return createIndex( row, column, parentItem ); } return QModelIndex(); } void ProjectModel::appendRow( ProjectBaseItem* item ) { d->rootItem->appendRow( item ); } void ProjectModel::removeRow( int row ) { d->rootItem->removeRow( row ); } ProjectBaseItem* ProjectModel::takeRow( int row ) { return d->rootItem->takeRow( row ); } ProjectBaseItem* ProjectModel::itemAt(int row) const { return d->rootItem->child(row); } QList< ProjectBaseItem* > ProjectModel::topItems() const { return d->rootItem->children(); } Qt::ItemFlags ProjectModel::flags(const QModelIndex& index) const { ProjectBaseItem* item = itemFromIndex( index ); if(item) return item->flags(); else return 0; } bool ProjectModel::insertColumns(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::insertRows(int, int, const QModelIndex&) { // Not supported return false; } bool ProjectModel::setData(const QModelIndex&, const QVariant&, int) { // Not supported return false; } QList ProjectModel::itemsForPath(const IndexedString& path) const { return d->pathLookupTable.values(path.index()); } ProjectBaseItem* ProjectModel::itemForPath(const IndexedString& path) const { return d->pathLookupTable.value(path.index()); } void ProjectVisitor::visit( ProjectModel* model ) { foreach( ProjectBaseItem* item, model->topItems() ) { visit( item->project() ); } } void ProjectVisitor::visit ( IProject* prj ) { visit( prj->projectItem() ); } void ProjectVisitor::visit ( ProjectBuildFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectExecutableTargetItem* exec ) { foreach( ProjectFileItem* item, exec->fileList() ) { visit( item ); } } void ProjectVisitor::visit ( ProjectFolderItem* folder ) { foreach( ProjectFileItem* item, folder->fileList() ) { visit( item ); } foreach( ProjectTargetItem* item, folder->targetList() ) { if( item->type() == ProjectBaseItem::LibraryTarget ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::ExecutableTarget ) { visit( dynamic_cast( item ) ); } } foreach( ProjectFolderItem* item, folder->folderList() ) { if( item->type() == ProjectBaseItem::BuildFolder ) { visit( dynamic_cast( item ) ); } else if( item->type() == ProjectBaseItem::Folder ) { visit( dynamic_cast( item ) ); } } } void ProjectVisitor::visit ( ProjectFileItem* ) { } void ProjectVisitor::visit ( ProjectLibraryTargetItem* lib ) { foreach( ProjectFileItem* item, lib->fileList() ) { visit( item ); } } ProjectVisitor::~ProjectVisitor() { } } diff --git a/project/projectproxymodel.cpp b/project/projectproxymodel.cpp index 6b0a356bc0..995c29b200 100644 --- a/project/projectproxymodel.cpp +++ b/project/projectproxymodel.cpp @@ -1,54 +1,51 @@ /* This file is part of KDevelop Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectproxymodel.h" #include -#include -#include -#include ProjectProxyModel::ProjectProxyModel(QObject * parent) : QSortFilterProxyModel(parent) { setDynamicSortFilter(true); sort(0); //initiate sorting regardless of the view } KDevelop::ProjectModel * ProjectProxyModel::projectModel() const { return qobject_cast( sourceModel() ); } bool ProjectProxyModel::lessThan(const QModelIndex & left, const QModelIndex & right) const { KDevelop::ProjectBaseItem *iLeft=projectModel()->itemFromIndex(left), *iRight=projectModel()->itemFromIndex(right); if(!iLeft || !iRight) return false; return( iLeft->lessThan( iRight ) ); } QModelIndex ProjectProxyModel::proxyIndexFromItem(KDevelop::ProjectBaseItem* item) const { return mapFromSource(projectModel()->indexFromItem(item)); } KDevelop::ProjectBaseItem* ProjectProxyModel::itemFromProxyIndex( const QModelIndex& idx ) const { return static_cast( projectModel()->itemFromIndex( mapToSource( idx ) ) ); } diff --git a/project/tests/projectmodelperformancetest.cpp b/project/tests/projectmodelperformancetest.cpp index b3dc3b1af6..6adcc836f5 100644 --- a/project/tests/projectmodelperformancetest.cpp +++ b/project/tests/projectmodelperformancetest.cpp @@ -1,207 +1,207 @@ /*************************************************************************** * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "projectmodelperformancetest.h" -#include -#include -#include -#include + #include #include +#include +#include +#include +#include +#include #include #include #include #include #include -#include -#include // Knobs to increase/decrease the amount of items being generated #define SMALL_DEPTH 2 #define SMALL_WIDTH 10 #define BIG_DEPTH 3 #define BIG_WIDTH 10 #define INIT_WIDTH 10 #define INIT_DEPTH 3 #include using KDevelop::ProjectModel; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::Path; void generateChilds( ProjectBaseItem* parent, int count, int depth ) { for( int i = 0; i < 10; i++ ) { if( depth > 0 ) { ProjectFolderItem* item = new ProjectFolderItem( QStringLiteral( "f%1" ).arg( i ), parent ); generateChilds( item, count, depth - 1 ); } else { new ProjectFileItem( QStringLiteral( "f%1" ).arg( i ), parent ); } } } ProjectModelPerformanceTest::ProjectModelPerformanceTest(QWidget* parent ) : QWidget(parent) { QGridLayout * l = new QGridLayout( this ); setLayout( l ); view = new QTreeView( this ); // This is used so the treeview layout performance is not influencing the test view->setUniformRowHeights( true ); QPushButton* b = new QPushButton( "Expand All", this ); connect( b, &QPushButton::clicked, view, &QTreeView::expandAll ); l->addWidget( b, 0, 0 ); b = new QPushButton( "Collapse All", this ); connect( b, &QPushButton::clicked, view, &QTreeView::collapseAll ); l->addWidget( b, 0, 1 ); b = new QPushButton( "Add Small Subtree", this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addSmallTree ); l->addWidget( b, 0, 2 ); b = new QPushButton( "Add Big Subtree", this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTree ); l->addWidget( b, 0, 3 ); b = new QPushButton( "Add Big Subtree in Chunks", this ); connect( b, &QPushButton::clicked, this, &ProjectModelPerformanceTest::addBigTreeDelayed ); l->addWidget( b, 0, 4 ); l->addWidget( view, 1, 0, 1, 6 ); } void ProjectModelPerformanceTest::init() { QElapsedTimer timer; timer.start(); KDevelop::AutoTestShell::init(); KDevelop::TestCore* core = new KDevelop::TestCore; core->setPluginController(new KDevelop::TestPluginController(core)); core->initialize(); qDebug() << "init core" << timer.elapsed(); timer.start(); model = new KDevelop::ProjectModel( this ); qDebug() << "create model" << timer.elapsed(); timer.start(); for( int i = 0; i < INIT_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, INIT_WIDTH, INIT_DEPTH ); model->appendRow( item ); } qDebug() << "init model" << timer.elapsed(); timer.start(); view->setModel( model ); qDebug() << "set model" << timer.elapsed(); timer.start(); } ProjectModelPerformanceTest::~ProjectModelPerformanceTest() { KDevelop::TestCore::shutdown(); QApplication::quit(); } void ProjectModelPerformanceTest::addBigTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < BIG_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path( QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) ) ) ); generateChilds( item, BIG_WIDTH, BIG_DEPTH ); model->appendRow( item ); } qDebug() << "addBigTree" << timer.elapsed(); } void ProjectModelPerformanceTest::addBigTreeDelayed() { originalWidth = model->rowCount(); QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } void ProjectModelPerformanceTest::addItemDelayed() { QElapsedTimer timer; timer.start(); ProjectBaseItem* parent = 0; Path path; if( !currentParent.isEmpty() ) { parent = currentParent.top(); path = Path(parent->path(), QStringLiteral("f%1").arg(parent->rowCount())); } else { path = Path(QUrl::fromLocalFile(QStringLiteral("/f%1").arg(model->rowCount()))); } ProjectBaseItem* item = 0; if( currentParent.size() < BIG_DEPTH ) { item = new ProjectFolderItem(0, path, parent); } else { item = new ProjectFileItem( 0, path, parent ); } if( currentParent.isEmpty() ) { model->appendRow( item ); } // Abort/Continue conditions are: // Go one level deeper (by pushing item on stack) as long as we haven't reached the max depth or the max width // else if we've reached the max width then pop, i.e go one level up // else the next run will add a sibling to the just-generated item if( currentParent.size() < BIG_DEPTH && ( currentParent.isEmpty() || currentParent.top()->rowCount() < BIG_WIDTH ) ) { currentParent.push( item ); } else if( !currentParent.isEmpty() && currentParent.top()->rowCount() >= BIG_WIDTH ) { currentParent.pop(); } if( ( currentParent.isEmpty() && ( model->rowCount() - originalWidth ) < BIG_WIDTH ) || !currentParent.isEmpty() ) { QTimer::singleShot( 0, this, SLOT(addItemDelayed()) ); } qDebug() << "addBigTreeDelayed" << timer.elapsed(); } void ProjectModelPerformanceTest::addSmallTree() { QElapsedTimer timer; timer.start(); for( int i = 0; i < SMALL_WIDTH; i++ ) { ProjectFolderItem* item = new ProjectFolderItem( 0, Path(QUrl::fromLocalFile( QStringLiteral( "/f%1" ).arg( i ) )) ); generateChilds( item, SMALL_WIDTH, SMALL_DEPTH ); model->appendRow( item ); } qDebug() << "addSmallTree" << timer.elapsed(); } int main( int argc, char** argv ) { QApplication a( argc, argv ); ProjectModelPerformanceTest* w = new ProjectModelPerformanceTest; w->show(); w->setAttribute(Qt::WA_DeleteOnClose); QMetaObject::invokeMethod(w, "init"); return a.exec(); } diff --git a/serialization/itemrepository.h b/serialization/itemrepository.h index 1d9612aadb..9f58763b54 100644 --- a/serialization/itemrepository.h +++ b/serialization/itemrepository.h @@ -1,2450 +1,2449 @@ /* Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ITEMREPOSITORY_H #define KDEVPLATFORM_ITEMREPOSITORY_H -#include -#include -#include +#include +#include +#include #include #include #include "referencecounting.h" #include "abstractitemrepository.h" #include "repositorymanager.h" #include "itemrepositoryregistry.h" //#define DEBUG_MONSTERBUCKETS // #define DEBUG_ITEMREPOSITORY_LOADING // #define ifDebugInfiniteRecursion(x) x #define ifDebugInfiniteRecursion(x) // #define ifDebugLostSpace(x) x #define ifDebugLostSpace(x) // #define DEBUG_INCORRECT_DELETE //Makes sure that all items stay reachable through the basic hash // #define DEBUG_ITEM_REACHABILITY ///@todo Dynamic bucket hash size #ifdef DEBUG_ITEM_REACHABILITY #define ENSURE_REACHABLE(bucket) Q_ASSERT(allItemsReachable(bucket)); #define IF_ENSURE_REACHABLE(x) x #else #define ENSURE_REACHABLE(bucket) #define IF_ENSURE_REACHABLE(x) #endif ///Do not enable this #define, the issue it catches is non-critical and happens on a regular basis // #define DEBUG_HASH_SEQUENCES #define ITEMREPOSITORY_USE_MMAP_LOADING //Assertion macro that prevents warnings if debugging is disabled //Only use it to verify values, it should not call any functions, since else the function will even be called in release mode #ifdef QT_NO_DEBUG #define VERIFY(X) if(!(X)) {qWarning() << "Failed to verify expression" << #X;} #else #define VERIFY(X) Q_ASSERT(X) #endif ///When this is uncommented, a 64-bit test-value is written behind the area an item is allowed to write into before ///createItem(..) is called, and an assertion triggers when it was changed during createItem(), which means createItem wrote too long. ///The problem: This temporarily overwrites valid data in the following item, so it will cause serious problems if that data is accessed ///during the call to createItem(). // #define DEBUG_WRITING_EXTENTS namespace KDevelop { /** * This file implements a generic bucket-based indexing repository, that can be used for example to index strings. * * All you need to do is define your item type that you want to store into the repository, and create a request item * similar to ExampleItemRequest that compares and fills the defined item type. * * For example the string repository uses "unsigned short" as item-type, uses that actual value to store the length of the string, * and uses the space behind to store the actual string content. * * @see AbstractItemRepository * @see ItemRepository * * @see ExampleItem * @see ExampleItemRequest * * @see typerepository.h * @see stringrepository.h * @see indexedstring.h */ enum { ItemRepositoryBucketSize = 1<<16, ItemRepositoryBucketLimit = 1<<16 }; /** * Buckets are the memory-units that are used to store the data in an ItemRepository. * * About monster buckets: Normally a bucket has a size of 64kb, but when an item is * allocated that is larger than that, a "monster bucket" is allocated, which spans the * space of multiple buckets. */ template class Bucket { public: enum { AdditionalSpacePerItem = 2 }; enum { ObjectMapSize = ((ItemRepositoryBucketSize / ItemRequest::AverageSize) * 3) / 2 + 1, MaxFreeItemsForHide = 0, //When less than this count of free items in one buckets is reached, the bucket is removed from the global list of buckets with free items MaxFreeSizeForHide = fixedItemSize ? fixedItemSize : 0, //Only when the largest free size is smaller then this, the bucket is taken from the free list MinFreeItemsForReuse = 10,//When this count of free items in one bucket is reached, consider re-assigning them to new requests MinFreeSizeForReuse = ItemRepositoryBucketSize/20 //When the largest free item is bigger then this, the bucket is automatically added to the free list }; enum { NextBucketHashSize = ObjectMapSize, //Affects the average count of bucket-chains that need to be walked in ItemRepository::index. Must be a multiple of ObjectMapSize DataSize = sizeof(char) + sizeof(unsigned int) * 3 + ItemRepositoryBucketSize + sizeof(short unsigned int) * (ObjectMapSize + NextBucketHashSize + 1) }; enum { CheckStart = 0xff00ff1, CheckEnd = 0xfafcfb }; Bucket() : m_monsterBucketExtent(0) , m_available(0) , m_data(0) , m_mappedData(0) , m_objectMap(0) , m_objectMapSize(0) , m_largestFreeItem(0) , m_freeItemCount(0) , m_nextBucketHash(0) , m_dirty(false) , m_changed(false) , m_lastUsed(0) { } ~Bucket() { if(m_data != m_mappedData) { delete[] m_data; delete[] m_nextBucketHash; delete[] m_objectMap; } } void initialize(int monsterBucketExtent) { if(!m_data) { m_monsterBucketExtent = monsterBucketExtent; m_available = ItemRepositoryBucketSize; m_data = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; memset(m_data, 0, (ItemRepositoryBucketSize + monsterBucketExtent * DataSize) * sizeof(char)); //The bigger we make the map, the lower the probability of a clash(and thus bad performance). However it increases memory usage. m_objectMapSize = ObjectMapSize; m_objectMap = new short unsigned int[m_objectMapSize]; memset(m_objectMap, 0, m_objectMapSize * sizeof(short unsigned int)); m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memset(m_nextBucketHash, 0, NextBucketHashSize * sizeof(short unsigned int)); m_changed = true; m_dirty = false; m_lastUsed = 0; } } template void readValue(char*& from, T& to) { to = *reinterpret_cast(from); from += sizeof(T); } void initializeFromMap(char* current) { if(!m_data) { char* start = current; readValue(current, m_monsterBucketExtent); Q_ASSERT(current - start == 4); readValue(current, m_available); m_objectMapSize = ObjectMapSize; m_objectMap = reinterpret_cast(current); current += sizeof(short unsigned int) * m_objectMapSize; m_nextBucketHash = reinterpret_cast(current); current += sizeof(short unsigned int) * NextBucketHashSize; readValue(current, m_largestFreeItem); readValue(current, m_freeItemCount); readValue(current, m_dirty); m_data = current; m_mappedData = current; m_changed = false; m_lastUsed = 0; VERIFY(current - start == (DataSize - ItemRepositoryBucketSize)); } } void store(QFile* file, size_t offset) { if(!m_data) return; if(static_cast(file->size()) < offset + (1+m_monsterBucketExtent)*DataSize) file->resize(offset + (1+m_monsterBucketExtent)*DataSize); file->seek(offset); file->write((char*)&m_monsterBucketExtent, sizeof(unsigned int)); file->write((char*)&m_available, sizeof(unsigned int)); file->write((char*)m_objectMap, sizeof(short unsigned int) * m_objectMapSize); file->write((char*)m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize); file->write((char*)&m_largestFreeItem, sizeof(short unsigned int)); file->write((char*)&m_freeItemCount, sizeof(unsigned int)); file->write((char*)&m_dirty, sizeof(bool)); file->write(m_data, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); if(static_cast(file->pos()) != offset + (1+m_monsterBucketExtent)*DataSize) { KMessageBox::error(0, i18n("Failed writing to %1, probably the disk is full", file->fileName())); abort(); } m_changed = false; #ifdef DEBUG_ITEMREPOSITORY_LOADING { file->flush(); file->seek(offset); uint available, freeItemCount, monsterBucketExtent; short unsigned int largestFree; bool dirty; short unsigned int* m = new short unsigned int[m_objectMapSize]; short unsigned int* h = new short unsigned int[NextBucketHashSize]; file->read((char*)&monsterBucketExtent, sizeof(unsigned int)); char* d = new char[ItemRepositoryBucketSize + monsterBucketExtent * DataSize]; file->read((char*)&available, sizeof(unsigned int)); file->read((char*)m, sizeof(short unsigned int) * m_objectMapSize); file->read((char*)h, sizeof(short unsigned int) * NextBucketHashSize); file->read((char*)&largestFree, sizeof(short unsigned int)); file->read((char*)&freeItemCount, sizeof(unsigned int)); file->read((char*)&dirty, sizeof(bool)); file->read(d, ItemRepositoryBucketSize); Q_ASSERT(monsterBucketExtent == m_monsterBucketExtent); Q_ASSERT(available == m_available); Q_ASSERT(memcmp(d, m_data, ItemRepositoryBucketSize + monsterBucketExtent * DataSize) == 0); Q_ASSERT(memcmp(m, m_objectMap, sizeof(short unsigned int) * m_objectMapSize) == 0); Q_ASSERT(memcmp(h, m_nextBucketHash, sizeof(short unsigned int) * NextBucketHashSize) == 0); Q_ASSERT(m_largestFreeItem == largestFree); Q_ASSERT(m_freeItemCount == freeItemCount); Q_ASSERT(m_dirty == dirty); Q_ASSERT(static_cast(file->pos()) == offset + DataSize + m_monsterBucketExtent * DataSize); delete[] d; delete[] m; delete[] h; } #endif } inline char* data() { return m_data; } inline uint dataSize() const { return ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize; } //Tries to find the index this item has in this bucket, or returns zero if the item isn't there yet. unsigned short findIndex(const ItemRequest& request) const { m_lastUsed = 0; unsigned short localHash = request.hash() % m_objectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short follower = 0; //Walk the chain of items with the same local hash while(index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if(index && request.equals(itemFromIndex(index))) { return index; //We have found the item } return 0; } //Tries to get the index within this bucket, or returns zero. Will put the item into the bucket if there is room. //Created indices will never begin with 0xffff____, so you can use that index-range for own purposes. unsigned short index(const ItemRequest& request, unsigned int itemSize) { m_lastUsed = 0; unsigned short localHash = request.hash() % m_objectMapSize; unsigned short index = m_objectMap[localHash]; unsigned short insertedAt = 0; unsigned short follower = 0; //Walk the chain of items with the same local hash while(index && (follower = followerIndex(index)) && !(request.equals(itemFromIndex(index)))) index = follower; if(index && request.equals(itemFromIndex(index))) return index; //We have found the item ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) prepareChange(); unsigned int totalSize = itemSize + AdditionalSpacePerItem; if(m_monsterBucketExtent) { ///This is a monster-bucket. Other rules are applied here. Only one item can be allocated, and that must be bigger than the bucket data Q_ASSERT(totalSize > ItemRepositoryBucketSize); Q_ASSERT(m_available); m_available = 0; insertedAt = AdditionalSpacePerItem; setFollowerIndex(insertedAt, 0); Q_ASSERT(m_objectMap[localHash] == 0); m_objectMap[localHash] = insertedAt; if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); return insertedAt; } //The second condition is needed, else we can get problems with zero-length items and an overflow in insertedAt to zero if(totalSize > m_available || (!itemSize && totalSize == m_available)) { //Try finding the smallest freed item that can hold the data unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short freeChunkSize = 0; ///@todo Achieve this without full iteration while(currentIndex && freeSize(currentIndex) > itemSize) { unsigned short follower = followerIndex(currentIndex); if(follower && freeSize(follower) >= itemSize) { //The item also fits into the smaller follower, so use that one previousIndex = currentIndex; currentIndex = follower; }else{ //The item fits into currentIndex, but not into the follower. So use currentIndex freeChunkSize = freeSize(currentIndex) - itemSize; //We need 2 bytes to store the free size if(freeChunkSize != 0 && freeChunkSize < AdditionalSpacePerItem+2) { //we can not manage the resulting free chunk as a separate item, so we cannot use this position. //Just pick the biggest free item, because there we can be sure that //either we can manage the split, or we cannot do anything at all in this bucket. freeChunkSize = freeSize(m_largestFreeItem) - itemSize; if(freeChunkSize == 0 || freeChunkSize >= AdditionalSpacePerItem+2) { previousIndex = 0; currentIndex = m_largestFreeItem; }else{ currentIndex = 0; } } break; } } if(!currentIndex || freeSize(currentIndex) < (totalSize-AdditionalSpacePerItem)) return 0; if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //Took one free item out of the chain ifDebugLostSpace( Q_ASSERT((uint)lostSpace() == (uint)(freeSize(currentIndex) + AdditionalSpacePerItem)); ) if(freeChunkSize) { Q_ASSERT(freeChunkSize >= AdditionalSpacePerItem+2); unsigned short freeItemSize = freeChunkSize - AdditionalSpacePerItem; unsigned short freeItemPosition; //Insert the resulting free chunk into the list of free items, so we don't lose it if(isBehindFreeSpace(currentIndex)) { //Create the free item at the beginning of currentIndex, so it can be merged with the free space in front freeItemPosition = currentIndex; currentIndex += freeItemSize + AdditionalSpacePerItem; }else{ //Create the free item behind currentIndex freeItemPosition = currentIndex + itemSize + AdditionalSpacePerItem; } setFreeSize(freeItemPosition, freeItemSize); insertFreeItem(freeItemPosition); } insertedAt = currentIndex; Q_ASSERT((bool)m_freeItemCount == (bool)m_largestFreeItem); }else{ //We have to insert the item insertedAt = ItemRepositoryBucketSize - m_available; insertedAt += AdditionalSpacePerItem; //Room for the prepended follower-index m_available -= totalSize; } ifDebugLostSpace( Q_ASSERT(lostSpace() == totalSize); ) Q_ASSERT(!index || !followerIndex(index)); Q_ASSERT(!m_objectMap[localHash] || index); if(index) setFollowerIndex(index, insertedAt); setFollowerIndex(insertedAt, 0); if(m_objectMap[localHash] == 0) m_objectMap[localHash] = insertedAt; #ifdef DEBUG_CREATEITEM_EXTENTS char* borderBehind = m_data + insertedAt + (totalSize-AdditionalSpacePerItem); quint64 oldValueBehind = 0; if(m_available >= 8) { oldValueBehind = *(quint64*)borderBehind; *((quint64*)borderBehind) = 0xfafafafafafafafaLLU; } #endif //Last thing we do, because createItem may recursively do even more transformation of the repository if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); request.createItem(reinterpret_cast(m_data + insertedAt)); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); #ifdef DEBUG_CREATEITEM_EXTENTS if(m_available >= 8) { //If this assertion triggers, then the item writes a bigger range than it advertised in Q_ASSERT(*((quint64*)borderBehind) == 0xfafafafafafafafaLLU); *((quint64*)borderBehind) = oldValueBehind; } #endif Q_ASSERT(itemFromIndex(insertedAt)->hash() == request.hash()); Q_ASSERT(itemFromIndex(insertedAt)->itemSize() == itemSize); ifDebugLostSpace( if(lostSpace()) qDebug() << "lost space:" << lostSpace(); Q_ASSERT(!lostSpace()); ) return insertedAt; } ///@param modulo Returns whether this bucket contains an item with (hash % modulo) == (item.hash % modulo) /// The default-parameter is the size of the next-bucket hash that is used by setNextBucketForHash and nextBucketForHash /// @param modulo MUST be a multiple of ObjectMapSize, because (b-a) | (x * h1) => (b-a) | h2, where a|b means a is a multiple of b. /// This this allows efficiently computing the clashes using the local object map hash. bool hasClashingItem(uint hash, uint modulo) { Q_ASSERT(modulo % ObjectMapSize == 0); m_lastUsed = 0; uint hashMod = hash % modulo; unsigned short localHash = hash % m_objectMapSize; unsigned short currentIndex = m_objectMap[localHash]; if(currentIndex == 0) return false; while(currentIndex) { uint currentHash = itemFromIndex(currentIndex)->hash(); Q_ASSERT(currentHash % m_objectMapSize == localHash); if(currentHash % modulo == hashMod) return true; //Clash currentIndex = followerIndex(currentIndex); } return false; } struct ClashVisitor { ClashVisitor(int _hash, int _modulo) : result(0), hash(_hash), modulo(_modulo) { } bool operator()(const Item* item) { if((item->hash() % modulo) == (hash % modulo)) result = item; return true; } const Item* result; uint hash, modulo; }; void countFollowerIndexLengths(uint& usedSlots, uint& lengths, uint& slotCount, uint& longestInBucketFollowerChain) { for(uint a = 0; a < m_objectMapSize; ++a) { unsigned short currentIndex = m_objectMap[a]; ++slotCount; uint length = 0; if(currentIndex) { ++usedSlots; while(currentIndex) { ++length; ++lengths; currentIndex = followerIndex(currentIndex); } if(length > longestInBucketFollowerChain) { // qDebug() << "follower-chain at" << a << ":" << length; longestInBucketFollowerChain = length; } } } } //A version of hasClashingItem that visits all items naively without using any assumptions. //This was used to debug hasClashingItem, but should not be used otherwise. bool hasClashingItemReal(uint hash, uint modulo) { m_lastUsed = 0; ClashVisitor visitor(hash, modulo); visitAllItems(visitor); return (bool)visitor.result; } //Returns whether the given item is reachabe within this bucket, through its hash. bool itemReachable(const Item* item, uint hash) const { unsigned short localHash = hash % m_objectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while(currentIndex) { if(itemFromIndex(currentIndex) == item) return true; currentIndex = followerIndex(currentIndex); } return false; } template bool visitItemsWithHash(Visitor& visitor, uint hash, unsigned short bucketIndex) const { m_lastUsed = 0; unsigned short localHash = hash % m_objectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while(currentIndex) { if(!visitor(itemFromIndex(currentIndex), (bucketIndex << 16) + currentIndex)) return false; currentIndex = followerIndex(currentIndex); } return true; } template void deleteItem(unsigned short index, unsigned int hash, Repository& repository) { ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) m_lastUsed = 0; prepareChange(); unsigned int size = itemFromIndex(index)->itemSize(); //Step 1: Remove the item from the data-structures that allow finding it: m_objectMap unsigned short localHash = hash % m_objectMapSize; unsigned short currentIndex = m_objectMap[localHash]; unsigned short previousIndex = 0; //Fix the follower-link by setting the follower of the previous item to the next one, or updating m_objectMap while(currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); //If this assertion triggers, the deleted item was not registered under the given hash Q_ASSERT(currentIndex); } Q_ASSERT(currentIndex == index); if(!previousIndex) //The item was directly in the object map m_objectMap[localHash] = followerIndex(index); else setFollowerIndex(previousIndex, followerIndex(index)); Item* item = const_cast(itemFromIndex(index)); if(markForReferenceCounting) enableDUChainReferenceCounting(m_data, dataSize()); ItemRequest::destroy(item, repository); if(markForReferenceCounting) disableDUChainReferenceCounting(m_data); memset(item, 0, size); //For debugging, so we notice the data is wrong if(m_monsterBucketExtent) { ///This is a monster-bucket. Make it completely empty again. Q_ASSERT(!m_available); m_available = ItemRepositoryBucketSize; //Items are always inserted into monster-buckets at a fixed position Q_ASSERT(currentIndex == AdditionalSpacePerItem); Q_ASSERT(m_objectMap[localHash] == 0); }else{ ///Put the space into the free-set setFreeSize(index, size); //Try merging the created free item to other free items around, else add it into the free list insertFreeItem(index); if(m_freeItemCount == 1 && freeSize(m_largestFreeItem) + m_available == ItemRepositoryBucketSize) { //Everything has been deleted, there is only free space left. Make the bucket empty again, //so it can later also be used as a monster-bucket. m_available = ItemRepositoryBucketSize; m_freeItemCount = 0; m_largestFreeItem = 0; } } Q_ASSERT((bool)m_freeItemCount == (bool)m_largestFreeItem); ifDebugLostSpace( Q_ASSERT(!lostSpace()); ) #ifdef DEBUG_INCORRECT_DELETE //Make sure the item cannot be found any more { unsigned short localHash = hash % m_objectMapSize; unsigned short currentIndex = m_objectMap[localHash]; while(currentIndex && currentIndex != index) { previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } Q_ASSERT(!currentIndex); //The item must not be found } #endif // Q_ASSERT(canAllocateItem(size)); } ///@warning The returned item may be in write-protected memory, so never try doing a const_cast and changing some data /// If you need to change something, use dynamicItemFromIndex ///@warning When using multi-threading, mutex() must be locked as long as you use the returned data inline const Item* itemFromIndex(unsigned short index) const { m_lastUsed = 0; return reinterpret_cast(m_data+index); } bool isEmpty() const { return m_available == ItemRepositoryBucketSize; } ///Returns true if this bucket has no nextBucketForHash links bool noNextBuckets() const { for(int a = 0; a < NextBucketHashSize; ++a) if(m_nextBucketHash[a]) return false; return true; } uint available() const { return m_available; } uint usedMemory() const { return ItemRepositoryBucketSize - m_available; } template bool visitAllItems(Visitor& visitor) const { m_lastUsed = 0; for(uint a = 0; a < m_objectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { //Get the follower early, so there is no problems when the current //index is removed if(!visitor(reinterpret_cast(m_data+currentIndex))) return false; currentIndex = followerIndex(currentIndex); } } return true; } ///Returns whether something was changed template int finalCleanup(Repository& repository) { int changed = 0; while(m_dirty) { m_dirty = false; for(uint a = 0; a < m_objectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { //Get the follower early, so there is no problems when the current //index is removed const Item* item = reinterpret_cast(m_data+currentIndex); if(!ItemRequest::persistent(item)) { changed += item->itemSize(); deleteItem(currentIndex, item->hash(), repository); m_dirty = true; //Set to dirty so we re-iterate break; } currentIndex = followerIndex(currentIndex); } } } return changed; } unsigned short nextBucketForHash(uint hash) const { m_lastUsed = 0; return m_nextBucketHash[hash % NextBucketHashSize]; } void setNextBucketForHash(unsigned int hash, unsigned short bucket) { m_lastUsed = 0; prepareChange(); m_nextBucketHash[hash % NextBucketHashSize] = bucket; } uint freeItemCount() const { return m_freeItemCount; } short unsigned int totalFreeItemsSize() const { short unsigned int ret = 0; short unsigned int currentIndex = m_largestFreeItem; while(currentIndex) { ret += freeSize(currentIndex); currentIndex = followerIndex(currentIndex); } return ret; } //Size of the largest item that could be inserted into this bucket short unsigned int largestFreeSize() const { short unsigned int ret = 0; if(m_largestFreeItem) ret = freeSize(m_largestFreeItem); if(m_available > (uint)(AdditionalSpacePerItem + (uint)ret)) { ret = m_available - AdditionalSpacePerItem; Q_ASSERT(ret == (m_available - AdditionalSpacePerItem)); } return ret; } bool canAllocateItem(unsigned int size) const { short unsigned int currentIndex = m_largestFreeItem; while(currentIndex) { short unsigned int currentFree = freeSize(currentIndex); if(currentFree < size) return false; //Either we need an exact match, or 2 additional bytes to manage the resulting gap if(size == currentFree || currentFree - size >= AdditionalSpacePerItem + 2) return true; currentIndex = followerIndex(currentIndex); } return false; } void tick() const { ++m_lastUsed; } //How many ticks ago the item was last used int lastUsed() const { return m_lastUsed; } //Whether this bucket was changed since it was last stored bool changed() const { return m_changed; } void prepareChange() { m_changed = true; m_dirty = true; makeDataPrivate(); } bool dirty() const { return m_dirty; } ///Returns the count of following buckets that were merged onto this buckets data array int monsterBucketExtent() const { return m_monsterBucketExtent; } //Counts together the space that is neither accessible through m_objectMap nor through the free items uint lostSpace() { if(m_monsterBucketExtent) return 0; uint need = ItemRepositoryBucketSize - m_available; uint found = 0; for(uint a = 0; a < m_objectMapSize; ++a) { uint currentIndex = m_objectMap[a]; while(currentIndex) { found += reinterpret_cast(m_data+currentIndex)->itemSize() + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } } uint currentIndex = m_largestFreeItem; while(currentIndex) { found += freeSize(currentIndex) + AdditionalSpacePerItem; currentIndex = followerIndex(currentIndex); } return need-found; } ///Slow bool isFreeItem(unsigned short index) const { unsigned short currentIndex = m_largestFreeItem; unsigned short currentSize = 0xffff; while(currentIndex) { Q_ASSERT(freeSize(currentIndex) <= currentSize); currentSize = freeSize(currentIndex); if(index == currentIndex) return true; currentIndex = followerIndex(currentIndex); } return false; } private: void makeDataPrivate() { if(m_mappedData == m_data) { short unsigned int* oldObjectMap = m_objectMap; short unsigned int* oldNextBucketHash = m_nextBucketHash; m_data = new char[ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize]; m_objectMap = new short unsigned int[m_objectMapSize]; m_nextBucketHash = new short unsigned int[NextBucketHashSize]; memcpy(m_data, m_mappedData, ItemRepositoryBucketSize + m_monsterBucketExtent * DataSize); memcpy(m_objectMap, oldObjectMap, m_objectMapSize * sizeof(short unsigned int)); memcpy(m_nextBucketHash, oldNextBucketHash, NextBucketHashSize * sizeof(short unsigned int)); } } ///Merges the given index item, which must have a freeSize() set, to surrounding free items, and inserts the result. ///The given index itself should not be in the free items chain yet. ///Returns whether the item was inserted somewhere. void insertFreeItem(unsigned short index) { //If the item-size is fixed, we don't need to do any management. Just keep a list of free items. Items of other size will never be requested. if(!fixedItemSize) { unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; while(currentIndex) { Q_ASSERT(currentIndex != index); #ifndef QT_NO_DEBUG unsigned short currentFreeSize = freeSize(currentIndex); #endif ///@todo Achieve this without iterating through all items in the bucket(This is very slow) //Merge behind index if(currentIndex == index + freeSize(index) + AdditionalSpacePerItem) { //Remove currentIndex from the free chain, since it's merged backwards into index if(previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //currentIndex is directly behind index, touching its space. Merge them. setFreeSize(index, freeSize(index) + AdditionalSpacePerItem + freeSize(currentIndex)); //Recurse to do even more merging insertFreeItem(index); return; } //Merge before index if(index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) { if(previousIndex && followerIndex(currentIndex)) Q_ASSERT(freeSize(previousIndex) >= freeSize(followerIndex(currentIndex))); //Remove currentIndex from the free chain, since insertFreeItem wants //it not to be in the chain, and it will be inserted in another place if(previousIndex) setFollowerIndex(previousIndex, followerIndex(currentIndex)); else m_largestFreeItem = followerIndex(currentIndex); --m_freeItemCount; //One was removed //index is directly behind currentIndex, touching its space. Merge them. setFreeSize(currentIndex, freeSize(currentIndex) + AdditionalSpacePerItem + freeSize(index)); //Recurse to do even more merging insertFreeItem(currentIndex); return; } previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); #ifndef QT_NO_DEBUG if(currentIndex) Q_ASSERT(freeSize(currentIndex) <= currentFreeSize); #endif } } insertToFreeChain(index); } ///Only inserts the item in the correct position into the free chain. index must not be in the chain yet. void insertToFreeChain(unsigned short index) { if(!fixedItemSize) { ///@todo Use some kind of tree to find the correct position in the chain(This is very slow) //Insert the free item into the chain opened by m_largestFreeItem unsigned short currentIndex = m_largestFreeItem; unsigned short previousIndex = 0; unsigned short size = freeSize(index); while(currentIndex && freeSize(currentIndex) > size) { Q_ASSERT(currentIndex != index); //must not be in the chain yet previousIndex = currentIndex; currentIndex = followerIndex(currentIndex); } if(currentIndex) Q_ASSERT(freeSize(currentIndex) <= size); setFollowerIndex(index, currentIndex); if(previousIndex) { Q_ASSERT(freeSize(previousIndex) >= size); setFollowerIndex(previousIndex, index); } else //This item is larger than all already registered free items, or there are none. m_largestFreeItem = index; }else{ Q_ASSERT(freeSize(index) == fixedItemSize); //When all items have the same size, just prepent to the front. setFollowerIndex(index, m_largestFreeItem); m_largestFreeItem = index; } ++m_freeItemCount; } ///Returns true if the given index is right behind free space, and thus can be merged to the free space. bool isBehindFreeSpace(unsigned short index) const { ///@todo Without iteration! unsigned short currentIndex = m_largestFreeItem; while(currentIndex) { if(index == currentIndex + freeSize(currentIndex) + AdditionalSpacePerItem) return true; currentIndex = followerIndex(currentIndex); } return false; } ///@param index the index of an item @return The index of the next item in the chain of items with a same local hash, or zero inline unsigned short followerIndex(unsigned short index) const { Q_ASSERT(index >= 2); return *reinterpret_cast(m_data+(index-2)); } void setFollowerIndex(unsigned short index, unsigned short follower) { Q_ASSERT(index >= 2); *reinterpret_cast(m_data+(index-2)) = follower; } // Only returns the current value if the item is actually free inline unsigned short freeSize(unsigned short index) const { return *reinterpret_cast(m_data+index); } //Convenience function to set the free-size, only for freed items void setFreeSize(unsigned short index, unsigned short size) { *reinterpret_cast(m_data+index) = size; } int m_monsterBucketExtent; //If this is a monster-bucket, this contains the count of follower-buckets that belong to this one unsigned int m_available; char* m_data; //Structure of the data: (2 byte), (item.size() byte) char* m_mappedData; //Read-only memory-mapped data. If this equals m_data, m_data must not be written short unsigned int* m_objectMap; //Points to the first object in m_data with (hash % m_objectMapSize) == index. Points to the item itself, so subtract 1 to get the pointer to the next item with same local hash. uint m_objectMapSize; short unsigned int m_largestFreeItem; //Points to the largest item that is currently marked as free, or zero. That one points to the next largest one through followerIndex unsigned int m_freeItemCount; unsigned short* m_nextBucketHash; bool m_dirty; //Whether the data was changed since the last finalCleanup bool m_changed; //Whether this bucket was changed since it was last stored to disk mutable int m_lastUsed; //How many ticks ago this bucket was last accessed }; template struct Locker { //This is a dummy that does nothing template Locker(const T& /*t*/) { } }; template<> struct Locker { Locker(QMutex* mutex) : m_mutex(mutex) { m_mutex->lock(); } ~Locker() { m_mutex->unlock(); } QMutex* m_mutex; }; ///This object needs to be kept alive as long as you change the contents of an item ///stored in the repository. It is needed to correctly track the reference counting ///within disk-storage. ///@warning You can not freely copy this around, when you create a copy, the copy source /// becomes invalid template class DynamicItem { public: DynamicItem(Item* i, void* start, uint size) : m_item(i), m_start(start) { if(markForReferenceCounting) enableDUChainReferenceCounting(m_start, size); // qDebug() << "enabling" << i << "to" << (void*)(((char*)i)+size); } ~DynamicItem() { if(m_start) { // qDebug() << "destructor-disabling" << m_item; if(markForReferenceCounting) disableDUChainReferenceCounting(m_start); } } DynamicItem(const DynamicItem& rhs) : m_item(rhs.m_item), m_start(rhs.m_start) { // qDebug() << "stealing" << m_item; Q_ASSERT(rhs.m_start); rhs.m_start = 0; } Item* operator->() { return m_item; } Item* m_item; private: mutable void* m_start; DynamicItem& operator=(const DynamicItem&); }; ///@param Item @see ExampleItem ///@param ItemRequest @see ExampleReqestItem ///@param fixedItemSize When this is true, all inserted items must have the same size. /// This greatly simplifies and speeds up the task of managing free items within the buckets. ///@param markForReferenceCounting Whether the data within the repository should be marked for reference-counting. /// This costs a bit of performance, but must be enabled if there may be data in the repository /// that does on-disk reference counting, like IndexedString, IndexedIdentifier, etc. ///@param threadSafe Whether class access should be thread-safe. Disabling this is dangerous when you do multi-threading. /// You have to make sure that mutex() is locked whenever the repository is accessed. template class ItemRepository : public AbstractItemRepository { typedef Locker ThisLocker; typedef Bucket MyBucket; enum { //Must be a multiple of Bucket::ObjectMapSize, so Bucket::hasClashingItem can be computed //Must also be a multiple of Bucket::NextBucketHashSize, for the same reason.(Currently those are same) bucketHashSize = (targetBucketHashSize / MyBucket::ObjectMapSize) * MyBucket::ObjectMapSize }; enum { BucketStartOffset = sizeof(uint) * 7 + sizeof(short unsigned int) * bucketHashSize //Position in the data where the bucket array starts }; public: ///@param registry May be zero, then the repository will not be registered at all. Else, the repository will register itself to that registry. /// If this is zero, you have to care about storing the data using store() and/or close() by yourself. It does not happen automatically. /// For the global standard registry, the storing/loading is triggered from within duchain, so you don't need to care about it. ItemRepository(const QString& repositoryName, ItemRepositoryRegistry* registry = &globalItemRepositoryRegistry(), uint repositoryVersion = 1, AbstractRepositoryManager* manager = 0) : m_ownMutex(QMutex::Recursive) , m_mutex(&m_ownMutex) , m_repositoryName(repositoryName) , m_registry(registry) , m_file(0) , m_dynamicFile(0) , m_repositoryVersion(repositoryVersion) , m_manager(manager) { m_unloadingEnabled = true; m_metaDataChanged = true; m_buckets.resize(10); m_buckets.fill(0); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_statBucketHashClashes = m_statItemCount = 0; m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes if(m_registry) m_registry->registerRepository(this, m_manager); } ~ItemRepository() { if(m_registry) m_registry->unRegisterRepository(this); close(); } ///Unloading of buckets is enabled by default. Use this to disable it. When unloading is enabled, the data ///gotten from must only itemFromIndex must not be used for a long time. void setUnloadingEnabled(bool enabled) { m_unloadingEnabled = enabled; } ///Returns the index for the given item. If the item is not in the repository yet, it is inserted. ///The index can never be zero. Zero is reserved for your own usage as invalid ///@param dynamic will be applied to the dynamic data of the found item unsigned int index(const ItemRequest& request) { ThisLocker lock(m_mutex); unsigned int hash = request.hash(); unsigned int size = request.itemSize(); short unsigned int* bucketHashPosition = m_firstBucketForHash + (hash % bucketHashSize); short unsigned int previousBucketNumber = *bucketHashPosition; int useBucket = m_currentBucket; bool pickedBucketInChain = false; //Whether a bucket was picked for re-use that already is in the hash chain int reOrderFreeSpaceBucketIndex = -1; while(previousBucketNumber) { //We have a bucket that contains an item with the given hash % bucketHashSize, so check if the item is already there MyBucket* bucketPtr = m_buckets[previousBucketNumber]; if(!bucketPtr) { initializeBucket(previousBucketNumber); bucketPtr = m_buckets[previousBucketNumber]; } unsigned short indexInBucket = bucketPtr->findIndex(request); if(indexInBucket) { //We've found the item, it's already there uint index = (previousBucketNumber << 16) + indexInBucket; verifyIndex(index); return index; //Combine the index in the bucket, and the bucker number into one index } else { #ifdef DEBUG_HASH_SEQUENCES if(previousBucketNumber==*bucketHashPosition) Q_ASSERT(bucketPtr->hasClashingItem(hash, bucketHashSize)); else Q_ASSERT(bucketPtr->hasClashingItem(hash, MyBucket::NextBucketHashSize)); #endif if(!pickedBucketInChain && bucketPtr->canAllocateItem(size)) { //Remember that this bucket has enough space to store the item, if it isn't already stored. //This gives a better structure, and saves us from cyclic follower structures. useBucket = previousBucketNumber; reOrderFreeSpaceBucketIndex = m_freeSpaceBuckets.indexOf(useBucket); pickedBucketInChain = true; } //The item isn't in bucket previousBucketNumber, but maybe the bucket has a pointer to the next bucket that might contain the item //Should happen rarely short unsigned int next = bucketPtr->nextBucketForHash(hash); if(next) { previousBucketNumber = next; } else break; } } m_metaDataChanged = true; if(!pickedBucketInChain && useBucket == m_currentBucket) { //Try finding an existing bucket with deleted space to store the data into for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); Q_ASSERT(bucketPtr->largestFreeSize()); if(bucketPtr->canAllocateItem(size)) { //The item fits into the bucket. useBucket = m_freeSpaceBuckets[a]; reOrderFreeSpaceBucketIndex = a; break; } } } //The item isn't in the repository yet, find a new bucket for it while(1) { if(useBucket >= m_buckets.size()) { if(m_buckets.size() >= 0xfffe) { //We have reserved the last bucket index 0xffff for special purposes //the repository has overflown. qWarning() << "Found no room for an item in" << m_repositoryName << "size of the item:" << request.itemSize(); return 0; }else{ //Allocate new buckets m_buckets.resize(m_buckets.size() + 10); } } MyBucket* bucketPtr = m_buckets[useBucket]; if(!bucketPtr) { initializeBucket(useBucket); bucketPtr = m_buckets[useBucket]; } ENSURE_REACHABLE(useBucket); Q_ASSERT_X(!bucketPtr->findIndex(request), Q_FUNC_INFO, "found item in unexpected bucket, ensure your ItemRequest::equals method is correct. Note: For custom AbstractType's e.g. ensure you have a proper equals() override"); unsigned short indexInBucket = bucketPtr->index(request, size); //If we could not allocate the item in an empty bucket, then we need to create a monster-bucket that //can hold the data. if(bucketPtr->isEmpty() && !indexInBucket) { ///@todo Move this compound statement into an own function uint totalSize = size + MyBucket::AdditionalSpacePerItem; Q_ASSERT((totalSize > ItemRepositoryBucketSize)); useBucket = 0; //The item did not fit in, we need a monster-bucket(Merge consecutive buckets) ///Step one: Search whether we can merge multiple empty buckets in the free-list into one monster-bucket int rangeStart = -1; int rangeEnd = -1; for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { MyBucket* bucketPtr = bucketForIndex(m_freeSpaceBuckets[a]); if(bucketPtr->isEmpty()) { //This bucket is a candidate for monster-bucket merging int index = (int)m_freeSpaceBuckets[a]; if(rangeEnd != index) { rangeStart = index; rangeEnd = index+1; }else{ ++rangeEnd; } if(rangeStart != rangeEnd) { uint extent = rangeEnd - rangeStart - 1; uint totalAvailableSpace = bucketForIndex(rangeStart)->available() + MyBucket::DataSize * (rangeEnd - rangeStart - 1); if(totalAvailableSpace > totalSize) { Q_ASSERT(extent); ///We can merge these buckets into one monster-bucket that can hold the data Q_ASSERT((uint)m_freeSpaceBuckets[a-extent] == (uint)rangeStart); m_freeSpaceBuckets.remove(a-extent, extent+1); useBucket = rangeStart; convertMonsterBucket(rangeStart, extent); break; } } } } if(!useBucket) { //Create a new monster-bucket at the end of the data int needMonsterExtent = (totalSize - ItemRepositoryBucketSize) / MyBucket::DataSize + 1; Q_ASSERT(needMonsterExtent); if(m_currentBucket + needMonsterExtent + 1 > m_buckets.size()) { m_buckets.resize(m_buckets.size() + 10 + needMonsterExtent + 1); } useBucket = m_currentBucket; convertMonsterBucket(useBucket, needMonsterExtent); m_currentBucket += 1 + needMonsterExtent; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); Q_ASSERT(m_buckets[m_currentBucket - 1 - needMonsterExtent] && m_buckets[m_currentBucket - 1 - needMonsterExtent]->monsterBucketExtent() == needMonsterExtent); } Q_ASSERT(useBucket); bucketPtr = bucketForIndex(useBucket); indexInBucket = bucketPtr->index(request, size); Q_ASSERT(indexInBucket); } if(indexInBucket) { ++m_statItemCount; if(!(*bucketHashPosition)) { Q_ASSERT(!previousBucketNumber); (*bucketHashPosition) = useBucket; ENSURE_REACHABLE(useBucket); } else if(!pickedBucketInChain && previousBucketNumber && previousBucketNumber != useBucket) { //Should happen rarely ++m_statBucketHashClashes; ///Debug: Detect infinite recursion ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, previousBucketNumber));) //Find the position where the paths of useBucket and *bucketHashPosition intersect, and insert useBucket //there. That way, we don't create loops. QPair intersect = hashChainIntersection(*bucketHashPosition, useBucket, hash); Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); if(!intersect.second) { ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, previousBucketNumber));) Q_ASSERT(m_buckets[previousBucketNumber]->nextBucketForHash(hash) == 0); m_buckets[previousBucketNumber]->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(previousBucketNumber); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket));) } else if(intersect.first) { ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second);) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.first));) ifDebugInfiniteRecursion(Q_ASSERT(bucketForIndex(intersect.first)->nextBucketForHash(hash) == intersect.second);) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, intersect.second));) ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(useBucket, hash, intersect.first));) bucketForIndex(intersect.first)->setNextBucketForHash(hash, useBucket); ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(intersect.second); ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, intersect.second));) } else { //State: intersect.first == 0 && intersect.second != 0. This means that whole compleet //chain opened by *bucketHashPosition with the hash-value is also following useBucket, //so useBucket can just be inserted at the top ifDebugInfiniteRecursion(Q_ASSERT(!walkBucketLinks(*bucketHashPosition, hash, useBucket));) ifDebugInfiniteRecursion(Q_ASSERT(walkBucketLinks(useBucket, hash, *bucketHashPosition));) unsigned short oldStart = *bucketHashPosition; *bucketHashPosition = useBucket; ENSURE_REACHABLE(useBucket); ENSURE_REACHABLE(oldStart); Q_UNUSED(oldStart); } } if(reOrderFreeSpaceBucketIndex != -1) updateFreeSpaceOrder(reOrderFreeSpaceBucketIndex); //Combine the index in the bucket, and the bucker number into one index uint index = (useBucket << 16) + indexInBucket; verifyIndex(index); return index; }else{ //This should never happen when we picked a bucket for re-use Q_ASSERT(!pickedBucketInChain); Q_ASSERT(reOrderFreeSpaceBucketIndex == -1); Q_ASSERT(useBucket == m_currentBucket); if(!bucketForIndex(useBucket)->isEmpty()) putIntoFreeList(useBucket, bucketPtr); ++m_currentBucket; Q_ASSERT(m_currentBucket < ItemRepositoryBucketLimit); useBucket = m_currentBucket; } } //We haven't found a bucket that already contains the item, so append it to the current bucket qWarning() << "Found no bucket for an item in" << m_repositoryName; return 0; } ///Returns zero if the item is not in the repository yet unsigned int findIndex(const ItemRequest& request) { ThisLocker lock(m_mutex); unsigned int hash = request.hash(); short unsigned int* bucketHashPosition = m_firstBucketForHash + (hash % bucketHashSize); short unsigned int previousBucketNumber = *bucketHashPosition; while(previousBucketNumber) { //We have a bucket that contains an item with the given hash % bucketHashSize, so check if the item is already there MyBucket* bucketPtr = m_buckets[previousBucketNumber]; if(!bucketPtr) { initializeBucket(previousBucketNumber); bucketPtr = m_buckets[previousBucketNumber]; } unsigned short indexInBucket = bucketPtr->findIndex(request); if(indexInBucket) { //We've found the item, it's already there uint index = (previousBucketNumber << 16) + indexInBucket; //Combine the index in the bucket, and the bucker number into one index verifyIndex(index); return index; } else { //The item isn't in bucket previousBucketNumber, but maybe the bucket has a pointer to the next bucket that might contain the item //Should happen rarely short unsigned int next = bucketPtr->nextBucketForHash(hash); if(next) previousBucketNumber = next; else break; } } return 0; } ///Deletes the item from the repository. void deleteItem(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); m_metaDataChanged = true; unsigned short bucket = (index >> 16); unsigned int hash = itemFromIndex(index)->hash(); short unsigned int* bucketHashPosition = m_firstBucketForHash + (hash % bucketHashSize); short unsigned int previousBucketNumber = *bucketHashPosition; Q_ASSERT(previousBucketNumber); if(previousBucketNumber == bucket) previousBucketNumber = 0; MyBucket* previousBucketPtr = 0; //Apart from removing the item itself, we may have to recreate the nextBucketForHash link, so we need the previous bucket while(previousBucketNumber) { //We have a bucket that contains an item with the given hash % bucketHashSize, so check if the item is already there previousBucketPtr = m_buckets[previousBucketNumber]; if(!previousBucketPtr) { initializeBucket(previousBucketNumber); previousBucketPtr = m_buckets[previousBucketNumber]; } short unsigned int nextBucket = previousBucketPtr->nextBucketForHash(hash); Q_ASSERT(nextBucket); if(nextBucket == bucket) break; //Now previousBucketNumber else previousBucketNumber = nextBucket; } //Make sure the index was reachable through the hashes Q_ASSERT(previousBucketNumber || *bucketHashPosition == bucket); MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } --m_statItemCount; bucketPtr->deleteItem(index, hash, *this); /** * Now check whether the link previousBucketNumber -> bucket is still needed. */ ///@todo Clear the nextBucketForHash links when not needed any more, by doing setNextBucketForHash(hash, 0); //return; ///@todo Find out what this problem is about. If we don't return here, some items become undiscoverable through hashes after some time if(previousBucketNumber == 0) { //The item is directly in the m_firstBucketForHash hash //Put the next item in the nextBucketForHash chain into m_firstBucketForHash that has a hash clashing in that array. Q_ASSERT(*bucketHashPosition == bucket); IF_ENSURE_REACHABLE(unsigned short previous = bucket;) auto nextBucket = bucketPtr; while(!nextBucket->hasClashingItem(hash, bucketHashSize)) { // Q_ASSERT(!bucketPtr->hasClashingItemReal(hash, bucketHashSize)); unsigned short next = nextBucket->nextBucketForHash(hash); ENSURE_REACHABLE(next); ENSURE_REACHABLE(previous); *bucketHashPosition = next; ENSURE_REACHABLE(previous); ENSURE_REACHABLE(next); IF_ENSURE_REACHABLE(previous = next;) if(next) { nextBucket = m_buckets[next]; if(!nextBucket) { initializeBucket(next); nextBucket = m_buckets[next]; } }else{ break; } } }else{ if(!bucketPtr->hasClashingItem(hash, MyBucket::NextBucketHashSize)) { // Q_ASSERT(!bucketPtr->hasClashingItemReal(hash, MyBucket::NextBucketHashSize)); ///Debug: Check for infinite recursion walkBucketLinks(*bucketHashPosition, hash); Q_ASSERT(previousBucketPtr->nextBucketForHash(hash) == bucket); ENSURE_REACHABLE(bucket); previousBucketPtr->setNextBucketForHash(hash, bucketPtr->nextBucketForHash(hash)); ENSURE_REACHABLE(bucket); ///Debug: Check for infinite recursion Q_ASSERT(walkBucketLinks(*bucketHashPosition, hash, bucketPtr->nextBucketForHash(hash))); Q_ASSERT(bucketPtr->nextBucketForHash(hash) != previousBucketNumber); } } ENSURE_REACHABLE(bucket); if(bucketPtr->monsterBucketExtent()) { //Convert the monster-bucket back to multiple normal buckets, and put them into the free list uint newBuckets = bucketPtr->monsterBucketExtent()+1; Q_ASSERT(bucketPtr->isEmpty()); if (!previousBucketNumber) { // see https://bugs.kde.org/show_bug.cgi?id=272408 // the monster bucket will be deleted and new smaller ones created // the next bucket for this hash is invalid anyways as done above // but calling the below unconditionally leads to other issues... bucketPtr->setNextBucketForHash(hash, 0); } convertMonsterBucket(bucket, 0); for(uint created = bucket; created < bucket + newBuckets; ++created) { putIntoFreeList(created, bucketForIndex(created)); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.indexOf(created) != -1); #endif } }else{ putIntoFreeList(bucket, bucketPtr); } #ifdef DEBUG_HASH_SEQUENCES Q_ASSERT(*bucketHashPosition == 0 || bucketForIndex(*bucketHashPosition)->hasClashingItem(hash, bucketHashSize)); #endif } ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. typedef DynamicItem MyDynamicItem; MyDynamicItem dynamicItemFromIndex(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return MyDynamicItem(const_cast(bucketPtr->itemFromIndex(indexInBucket)), bucketPtr->data(), bucketPtr->dataSize()); } ///This returns an editable version of the item. @warning: Never change an entry that affects the hash, ///or the equals(..) function. That would completely destroy the consistency. ///@param index The index. It must be valid(match an existing item), and nonzero. ///@warning If you use this, make sure you lock mutex() before calling, /// and hold it until you're ready using/changing the data.. ///@warning If you change contained complex items that depend on reference-counting, you /// must use dynamicItemFromIndex(..) instead of dynamicItemFromIndexSimple(..) Item* dynamicItemFromIndexSimple(unsigned int index) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; return const_cast(bucketPtr->itemFromIndex(indexInBucket)); } ///@param Action Must be an object that has an "operator()(Item&)" function. ///That function is allowed to do any action on the item, except for anything that ///changes its identity/hash-value template void dynamicAction(unsigned int index, Action& action) { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } bucketPtr->prepareChange(); unsigned short indexInBucket = index & 0xffff; if(markForReferenceCounting) enableDUChainReferenceCounting(bucketPtr->data(), bucketPtr->dataSize()); action(const_cast(*bucketPtr->itemFromIndex(indexInBucket, 0))); if(markForReferenceCounting) disableDUChainReferenceCounting(bucketPtr->data()); } ///@param index The index. It must be valid(match an existing item), and nonzero. ///@param dynamic will be applied to the item. const Item* itemFromIndex(unsigned int index) const { verifyIndex(index); ThisLocker lock(m_mutex); unsigned short bucket = (index >> 16); const MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } unsigned short indexInBucket = index & 0xffff; return bucketPtr->itemFromIndex(indexInBucket); } struct Statistics { Statistics() : loadedBuckets(-1), currentBucket(-1), usedMemory(-1), loadedMonsterBuckets(-1), usedSpaceForBuckets(-1), freeSpaceInBuckets(-1), lostSpace(-1), freeUnreachableSpace(-1), hashClashedItems(-1), totalItems(-1), hashSize(-1), hashUse(-1), averageInBucketHashSize(-1), averageInBucketUsedSlotCount(-1), averageInBucketSlotChainLength(-1), longestInBucketChain(-1), longestNextBucketChain(-1), totalBucketFollowerSlots(-1), averageNextBucketForHashSequenceLength(-1) { } uint loadedBuckets; uint currentBucket; uint usedMemory; uint loadedMonsterBuckets; uint usedSpaceForBuckets; uint freeSpaceInBuckets; uint lostSpace; uint freeUnreachableSpace; uint hashClashedItems; uint totalItems; uint emptyBuckets; uint hashSize; //How big the hash is uint hashUse; //How many slots in the hash are used uint averageInBucketHashSize; uint averageInBucketUsedSlotCount; float averageInBucketSlotChainLength; uint longestInBucketChain; uint longestNextBucketChain; uint totalBucketFollowerSlots; //Total count of used slots in the nextBucketForHash structure float averageNextBucketForHashSequenceLength; //Average sequence length of a nextBucketForHash sequence(If not empty) QString print() const { QString ret; ret += QStringLiteral("loaded buckets: %1 current bucket: %2 used memory: %3 loaded monster buckets: %4").arg(loadedBuckets).arg(currentBucket).arg(usedMemory).arg(loadedMonsterBuckets); ret += QStringLiteral("\nbucket hash clashed items: %1 total items: %2").arg(hashClashedItems).arg(totalItems); ret += QStringLiteral("\nused space for buckets: %1 free space in buckets: %2 lost space: %3").arg(usedSpaceForBuckets).arg(freeSpaceInBuckets).arg(lostSpace); ret += QStringLiteral("\nfree unreachable space: %1 empty buckets: %2").arg(freeUnreachableSpace).arg(emptyBuckets); ret += QStringLiteral("\nhash size: %1 hash slots used: %2").arg(hashSize).arg(hashUse); ret += QStringLiteral("\naverage in-bucket hash size: %1 average in-bucket used hash slot count: %2 average in-bucket slot chain length: %3 longest in-bucket follower chain: %4").arg(averageInBucketHashSize).arg(averageInBucketUsedSlotCount).arg(averageInBucketSlotChainLength).arg(longestInBucketChain); ret += QStringLiteral("\ntotal count of used next-bucket-for-hash slots: %1 average next-bucket-for-hash sequence length: %2 longest next-bucket chain: %3").arg(totalBucketFollowerSlots).arg(averageNextBucketForHashSequenceLength).arg(longestNextBucketChain); return ret; } operator QString() const { return print(); } }; QString printStatistics() const override { return statistics().print(); } Statistics statistics() const { Statistics ret; uint loadedBuckets = 0; for(int a = 0; a < m_buckets.size(); ++a) if(m_buckets[a]) ++loadedBuckets; #ifdef DEBUG_MONSTERBUCKETS for(int a = 0; a < m_freeSpaceBuckets.size(); ++a) { if(a > 0) { uint prev = a-1; uint prevLargestFree = bucketForIndex(m_freeSpaceBuckets[prev])->largestFreeSize(); uint largestFree = bucketForIndex(m_freeSpaceBuckets[a])->largestFreeSize(); Q_ASSERT( (prevLargestFree < largestFree) || (prevLargestFree == largestFree && m_freeSpaceBuckets[prev] < m_freeSpaceBuckets[a]) ); } } #endif ret.hashSize = bucketHashSize; ret.hashUse = 0; for(uint a = 0; a < bucketHashSize; ++a) if(m_firstBucketForHash[a]) ++ret.hashUse; ret.emptyBuckets = 0; uint loadedMonsterBuckets = 0; for(int a = 0; a < m_buckets.size(); ++a) if(m_buckets[a] && m_buckets[a]->monsterBucketExtent()) loadedMonsterBuckets += m_buckets[a]->monsterBucketExtent()+1; uint usedBucketSpace = MyBucket::DataSize * m_currentBucket; uint freeBucketSpace = 0, freeUnreachableSpace = 0; uint lostSpace = 0; //Should be zero, else something is wrong uint totalInBucketHashSize = 0, totalInBucketUsedSlotCount = 0, totalInBucketChainLengths = 0; uint bucketCount = 0; ret.totalBucketFollowerSlots = 0; ret.averageNextBucketForHashSequenceLength = 0; ret.longestNextBucketChain = 0; ret.longestInBucketChain = 0; for(int a = 1; a < m_currentBucket+1; ++a) { MyBucket* bucket = bucketForIndex(a); if(bucket) { ++bucketCount; bucket->countFollowerIndexLengths(totalInBucketUsedSlotCount, totalInBucketChainLengths, totalInBucketHashSize, ret.longestInBucketChain); for(uint aa = 0; aa < MyBucket::NextBucketHashSize; ++aa) { uint length = 0; uint next = bucket->nextBucketForHash(aa); if(next) { ++ret.totalBucketFollowerSlots; while(next) { ++length; ++ret.averageNextBucketForHashSequenceLength; next = bucketForIndex(next)->nextBucketForHash(aa); } } if(length > ret.longestNextBucketChain) { // qDebug() << "next-bucket-chain in" << a << aa << ":" << length; ret.longestNextBucketChain = length; } } uint bucketFreeSpace = bucket->totalFreeItemsSize() + bucket->available(); freeBucketSpace += bucketFreeSpace; if(m_freeSpaceBuckets.indexOf(a) == -1) freeUnreachableSpace += bucketFreeSpace; if(bucket->isEmpty()) { ++ret.emptyBuckets; Q_ASSERT(!bucket->monsterBucketExtent()); #ifdef DEBUG_MONSTERBUCKETS Q_ASSERT(m_freeSpaceBuckets.contains(a)); #endif } lostSpace += bucket->lostSpace(); a += bucket->monsterBucketExtent(); } } if(ret.totalBucketFollowerSlots) ret.averageNextBucketForHashSequenceLength /= ret.totalBucketFollowerSlots; ret.loadedBuckets = loadedBuckets; ret.currentBucket = m_currentBucket; ret.usedMemory = usedMemory(); ret.loadedMonsterBuckets = loadedMonsterBuckets; ret.hashClashedItems = m_statBucketHashClashes; ret.totalItems = m_statItemCount; ret.usedSpaceForBuckets = usedBucketSpace; ret.freeSpaceInBuckets = freeBucketSpace; ret.lostSpace = lostSpace; ret.freeUnreachableSpace = freeUnreachableSpace; ret.averageInBucketHashSize = totalInBucketHashSize / bucketCount; ret.averageInBucketUsedSlotCount = totalInBucketUsedSlotCount / bucketCount; ret.averageInBucketSlotChainLength = float(totalInBucketChainLengths) / totalInBucketUsedSlotCount; //If m_statBucketHashClashes is high, the bucket-hash needs to be bigger return ret; } uint usedMemory() const { uint used = 0; for(int a = 0; a < m_buckets.size(); ++a) { if(m_buckets[a]) { used += m_buckets[a]->usedMemory(); } } return used; } ///This can be used to safely iterate through all items in the repository ///@param visitor Should be an instance of a class that has a bool operator()(const Item*) member function, /// that returns whether more items are wanted. ///@param onlyInMemory If this is true, only items are visited that are currently in memory. template void visitAllItems(Visitor& visitor, bool onlyInMemory = false) const { ThisLocker lock(m_mutex); for(int a = 1; a <= m_currentBucket; ++a) { if(!onlyInMemory || m_buckets[a]) { if(bucketForIndex(a) && !bucketForIndex(a)->visitAllItems(visitor)) return; } } } ///Visits all items that have the given hash-value, plus an arbitrary count of other items with a clashing hash. ///@param visitor Should be an instance of a class that has a bool operator()(const Item*, uint index) member function, /// that returns whether more items are wanted. template void visitItemsWithHash(Visitor& visitor, uint hash) const { ThisLocker lock(m_mutex); short unsigned int bucket = *(m_firstBucketForHash + (hash % bucketHashSize)); while(bucket) { MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } if(!bucketPtr->visitItemsWithHash(visitor, hash, bucket)) return; bucket = bucketPtr->nextBucketForHash(hash); } } ///Synchronizes the state on disk to the one in memory, and does some memory-management. ///Should be called on a regular basis. Can be called centrally from the global item repository registry. virtual void store() override { QMutexLocker lock(m_mutex); if(m_file) { if(!m_file->open( QFile::ReadWrite ) || !m_dynamicFile->open( QFile::ReadWrite )) { qFatal("cannot re-open repository file for storing"); return; } for(int a = 0; a < m_buckets.size(); ++a) { if(m_buckets[a]) { if(m_buckets[a]->changed()) { storeBucket(a); } if(m_unloadingEnabled) { const int unloadAfterTicks = 2; if(m_buckets[a]->lastUsed() > unloadAfterTicks) { delete m_buckets[a]; m_buckets[a] = 0; }else{ m_buckets[a]->tick(); } } } } if(m_metaDataChanged) { Q_ASSERT(m_dynamicFile); m_file->seek(0); m_file->write((char*)&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write((char*)&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write((char*)&itemRepositoryVersion, sizeof(uint)); m_file->write((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->write((char*)&m_statItemCount, sizeof(uint)); const uint bucketCount = static_cast(m_buckets.size()); m_file->write((char*)&bucketCount, sizeof(uint)); m_file->write((char*)&m_currentBucket, sizeof(uint)); m_file->write((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); m_dynamicFile->seek(0); const uint freeSpaceBucketsSize = static_cast(m_freeSpaceBuckets.size()); m_dynamicFile->write((char*)&freeSpaceBucketsSize, sizeof(uint)); m_dynamicFile->write((char*)m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } //To protect us from inconsistency due to crashes. flush() is not enough. We need to close. m_file->close(); m_dynamicFile->close(); Q_ASSERT(!m_file->isOpen()); Q_ASSERT(!m_dynamicFile->isOpen()); } } ///This mutex is used for the thread-safe locking when threadSafe is true. Even if threadSafe is false, it is ///always locked before storing to or loading from disk. ///@warning If threadSafe is false, and you sometimes call store() from within another thread(As happens in duchain), /// you must always make sure that this mutex is locked before you access this repository. /// Else you will get crashes and inconsistencies. /// In KDevelop This means: Make sure you _always_ lock this mutex before accessing the repository. QMutex* mutex() const { return m_mutex; } ///With this, you can replace the internal mutex with another one. void setMutex(QMutex* mutex) { m_mutex = mutex; } virtual QString repositoryName() const override { return m_repositoryName; } private: ///Makes sure the order within m_freeSpaceBuckets is correct, after largestFreeSize has been changed for m_freeSpaceBuckets[index]. ///If too few space is free within the given bucket, it is removed from m_freeSpaceBuckets. void updateFreeSpaceOrder(uint index) { m_metaDataChanged = true; unsigned int* freeSpaceBuckets = m_freeSpaceBuckets.data(); Q_ASSERT(index < static_cast(m_freeSpaceBuckets.size())); MyBucket* bucketPtr = bucketForIndex(freeSpaceBuckets[index]); unsigned short largestFreeSize = bucketPtr->largestFreeSize(); if(largestFreeSize == 0 || (bucketPtr->freeItemCount() <= MyBucket::MaxFreeItemsForHide && largestFreeSize <= MyBucket::MaxFreeSizeForHide)) { //Remove the item from freeSpaceBuckets m_freeSpaceBuckets.remove(index); }else{ while(1) { int prev = index-1; int next = index+1; if(prev >= 0 && (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() > largestFreeSize || (bucketForIndex(freeSpaceBuckets[prev])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] < freeSpaceBuckets[prev])) ) { //This item should be behind the successor, either because it has a lower largestFreeSize, or because the index is lower uint oldPrevValue = freeSpaceBuckets[prev]; freeSpaceBuckets[prev] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldPrevValue; index = prev; }else if(next < m_freeSpaceBuckets.size() && (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() < largestFreeSize || (bucketForIndex(freeSpaceBuckets[next])->largestFreeSize() == largestFreeSize && freeSpaceBuckets[index] > freeSpaceBuckets[next]))) { //This item should be behind the successor, either because it has a higher largestFreeSize, or because the index is higher uint oldNextValue = freeSpaceBuckets[next]; freeSpaceBuckets[next] = freeSpaceBuckets[index]; freeSpaceBuckets[index] = oldNextValue; index = next; }else { break; } } } } ///Does conversion from monster-bucket to normal bucket and from normal bucket to monster-bucket ///The bucket @param bucketNumber must already be loaded and empty. the "extent" buckets behind must also be loaded, ///and also be empty. ///The created buckets are not registered anywhere. When converting from monster-bucket to normal bucket, ///oldExtent+1 normal buckets are created, that must be registered somewhere. ///@warning During conversion, all the touched buckets are deleted and re-created ///@param extent When this is zero, the bucket is converted from monster-bucket to normal bucket. /// When it is nonzero, it is converted to a monster-bucket. MyBucket* convertMonsterBucket(int bucketNumber, int extent) { Q_ASSERT(bucketNumber); MyBucket* bucketPtr = m_buckets[bucketNumber]; if(!bucketPtr) { initializeBucket(bucketNumber); bucketPtr = m_buckets[bucketNumber]; } if(extent) { //Convert to monster-bucket #ifdef DEBUG_MONSTERBUCKETS for(int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(bucketPtr->isEmpty()); Q_ASSERT(!bucketPtr->monsterBucketExtent()); Q_ASSERT(m_freeSpaceBuckets.indexOf(index) == -1); } #endif for(int index = bucketNumber; index < bucketNumber + 1 + extent; ++index) deleteBucket(index); m_buckets[bucketNumber] = new MyBucket(); m_buckets[bucketNumber]->initialize(extent); #ifdef DEBUG_MONSTERBUCKETS for(uint index = bucketNumber+1; index < bucketNumber + 1 + extent; ++index) { Q_ASSERT(!m_buckets[index]); } #endif }else{ Q_ASSERT(bucketPtr->monsterBucketExtent()); Q_ASSERT(bucketPtr->isEmpty()); const int oldExtent = bucketPtr->monsterBucketExtent(); deleteBucket(bucketNumber); //Delete the monster-bucket for(int index = bucketNumber; index < bucketNumber + 1 + oldExtent; ++index) { Q_ASSERT(!m_buckets[index]); m_buckets[index] = new MyBucket(); m_buckets[index]->initialize(0); Q_ASSERT(!m_buckets[index]->monsterBucketExtent()); } } return m_buckets[bucketNumber]; } MyBucket* bucketForIndex(short unsigned int index) const { MyBucket* bucketPtr = m_buckets[index]; if(!bucketPtr) { initializeBucket(index); bucketPtr = m_buckets[index]; } return bucketPtr; } virtual bool open(const QString& path) override { QMutexLocker lock(m_mutex); close(); m_currentOpenPath = path; //qDebug() << "opening repository" << m_repositoryName << "at" << path; QDir dir(path); m_file = new QFile(dir.absoluteFilePath( m_repositoryName )); m_dynamicFile = new QFile(dir.absoluteFilePath( m_repositoryName + "_dynamic" )); if(!m_file->open( QFile::ReadWrite ) || !m_dynamicFile->open( QFile::ReadWrite ) ) { delete m_file; m_file = 0; delete m_dynamicFile; m_dynamicFile = 0; return false; } m_metaDataChanged = true; if(m_file->size() == 0) { m_file->resize(0); m_file->write((char*)&m_repositoryVersion, sizeof(uint)); uint hashSize = bucketHashSize; m_file->write((char*)&hashSize, sizeof(uint)); uint itemRepositoryVersion = staticItemRepositoryVersion(); m_file->write((char*)&itemRepositoryVersion, sizeof(uint)); m_statBucketHashClashes = m_statItemCount = 0; m_file->write((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->write((char*)&m_statItemCount, sizeof(uint)); m_buckets.resize(10); m_buckets.fill(0); uint bucketCount = m_buckets.size(); m_file->write((char*)&bucketCount, sizeof(uint)); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); m_currentBucket = 1; //Skip the first bucket, we won't use it so we have the zero indices for special purposes m_file->write((char*)&m_currentBucket, sizeof(uint)); m_file->write((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); //We have completely initialized the file now if(m_file->pos() != BucketStartOffset) { KMessageBox::error(0, i18n("Failed writing to %1, probably the disk is full", m_file->fileName())); abort(); } const uint freeSpaceBucketsSize = 0; m_dynamicFile->write((char*)&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.clear(); }else{ m_file->close(); bool res = m_file->open( QFile::ReadOnly ); //Re-open in read-only mode, so we create a read-only m_fileMap VERIFY(res); //Check that the version is correct uint storedVersion = 0, hashSize = 0, itemRepositoryVersion = 0; m_file->read((char*)&storedVersion, sizeof(uint)); m_file->read((char*)&hashSize, sizeof(uint)); m_file->read((char*)&itemRepositoryVersion, sizeof(uint)); m_file->read((char*)&m_statBucketHashClashes, sizeof(uint)); m_file->read((char*)&m_statItemCount, sizeof(uint)); if(storedVersion != m_repositoryVersion || hashSize != bucketHashSize || itemRepositoryVersion != staticItemRepositoryVersion()) { qDebug() << "repository" << m_repositoryName << "version mismatch in" << m_file->fileName() << ", stored: version " << storedVersion << "hashsize" << hashSize << "repository-version" << itemRepositoryVersion << " current: version" << m_repositoryVersion << "hashsize" << bucketHashSize << "repository-version" << staticItemRepositoryVersion(); delete m_file; m_file = 0; delete m_dynamicFile; m_dynamicFile = 0; return false; } m_metaDataChanged = false; uint bucketCount = 0; m_file->read((char*)&bucketCount, sizeof(uint)); m_buckets.resize(bucketCount); m_file->read((char*)&m_currentBucket, sizeof(uint)); m_file->read((char*)m_firstBucketForHash, sizeof(short unsigned int) * bucketHashSize); Q_ASSERT(m_file->pos() == BucketStartOffset); uint freeSpaceBucketsSize = 0; m_dynamicFile->read((char*)&freeSpaceBucketsSize, sizeof(uint)); m_freeSpaceBuckets.resize(freeSpaceBucketsSize); m_dynamicFile->read((char*)m_freeSpaceBuckets.data(), sizeof(uint) * freeSpaceBucketsSize); } m_fileMapSize = 0; m_fileMap = 0; #ifdef ITEMREPOSITORY_USE_MMAP_LOADING if(m_file->size() > BucketStartOffset){ m_fileMap = m_file->map(BucketStartOffset, m_file->size() - BucketStartOffset); Q_ASSERT(m_file->isOpen()); Q_ASSERT(m_file->size() >= BucketStartOffset); if(m_fileMap){ m_fileMapSize = m_file->size() - BucketStartOffset; }else{ qWarning() << "mapping" << m_file->fileName() << "FAILED!"; } } #endif //To protect us from inconsistency due to crashes. flush() is not enough. m_file->close(); m_dynamicFile->close(); return true; } ///@warning by default, this does not store the current state to disk. virtual void close(bool doStore = false) override { m_currentOpenPath.clear(); if(doStore) store(); if(m_file) m_file->close(); delete m_file; m_file = 0; m_fileMap = 0; m_fileMapSize = 0; if(m_dynamicFile) m_dynamicFile->close(); delete m_dynamicFile; m_dynamicFile = 0; // FIXME: We don't delete the buckets here, as their contained memory may be referenced // in static variables e.g. // qDeleteAll(m_buckets); m_buckets.clear(); memset(m_firstBucketForHash, 0, bucketHashSize * sizeof(short unsigned int)); } struct AllItemsReachableVisitor { AllItemsReachableVisitor(ItemRepository* rep) : repository(rep) { } bool operator()(const Item* item) { return repository->itemReachable(item); } ItemRepository* repository; }; //Returns whether the given item is reachable through its hash bool itemReachable(const Item* item) const { uint hash = item->hash(); short unsigned int bucket = *(m_firstBucketForHash + (hash % bucketHashSize)); while(bucket) { MyBucket* bucketPtr = m_buckets[bucket]; if(!bucketPtr) { initializeBucket(bucket); bucketPtr = m_buckets[bucket]; } if(bucketPtr->itemReachable(item, hash)) return true; bucket = bucketPtr->nextBucketForHash(hash); } return false; } //Returns true if all items in the given bucket are reachable through their hashes bool allItemsReachable(unsigned short bucket) { if(!bucket) return true; MyBucket* bucketPtr = bucketForIndex(bucket); AllItemsReachableVisitor visitor(this); return bucketPtr->visitAllItems(visitor); } struct CleanupVisitor { bool operator()(const Item* item) { if(!ItemRequest::persistent(item)) { //Delete the item } return true; } }; virtual int finalCleanup() override { ThisLocker lock(m_mutex); int changed = 0; for(int a = 1; a <= m_currentBucket; ++a) { MyBucket* bucket = bucketForIndex(a); if(bucket && bucket->dirty()) { ///@todo Faster dirty check, without loading bucket changed += bucket->finalCleanup(*this); } a += bucket->monsterBucketExtent(); //Skip buckets that are attached as tail to monster-buckets } return changed; } inline void initializeBucket(int bucketNumber) const { Q_ASSERT(bucketNumber); #ifdef DEBUG_MONSTERBUCKETS for(uint offset = 1; offset < 5; ++offset) { int test = bucketNumber - offset; if(test >= 0 && m_buckets[test]) { Q_ASSERT(m_buckets[test]->monsterBucketExtent() < offset); } } #endif if(!m_buckets[bucketNumber]) { m_buckets[bucketNumber] = new MyBucket(); bool doMMapLoading = (bool)m_fileMap; uint offset = ((bucketNumber-1) * MyBucket::DataSize); if(m_file && offset < m_fileMapSize && doMMapLoading && *reinterpret_cast(m_fileMap + offset) == 0) { // qDebug() << "loading bucket mmap:" << bucketNumber; m_buckets[bucketNumber]->initializeFromMap(reinterpret_cast(m_fileMap + offset)); } else if(m_file) { - //Either memory-mapping is disabled, or the item is not in the existing memory-map, //Either memory-mapping is disabled, or the item is not in the existing memory-map, //so we have to load it the classical way. bool res = m_file->open( QFile::ReadOnly ); if(offset + BucketStartOffset < m_file->size()) { VERIFY(res); offset += BucketStartOffset; m_file->seek(offset); uint monsterBucketExtent; m_file->read((char*)(&monsterBucketExtent), sizeof(unsigned int));; m_file->seek(offset); ///FIXME: use the data here instead of copying it again in prepareChange QByteArray data = m_file->read((1+monsterBucketExtent) * MyBucket::DataSize); m_buckets[bucketNumber]->initializeFromMap(data.data()); m_buckets[bucketNumber]->prepareChange(); }else{ m_buckets[bucketNumber]->initialize(0); } m_file->close(); }else{ m_buckets[bucketNumber]->initialize(0); } }else{ m_buckets[bucketNumber]->initialize(0); } } ///Can only be called on empty buckets void deleteBucket(int bucketNumber) { Q_ASSERT(bucketForIndex(bucketNumber)->isEmpty()); Q_ASSERT(bucketForIndex(bucketNumber)->noNextBuckets()); delete m_buckets[bucketNumber]; m_buckets[bucketNumber] = 0; } //m_file must be opened void storeBucket(int bucketNumber) const { if(m_file && m_buckets[bucketNumber]) { m_buckets[bucketNumber]->store(m_file, BucketStartOffset + (bucketNumber-1) * MyBucket::DataSize); } } ///Returns whether @param mustFindBucket was found ///If mustFindBucket is zero, the whole chain is just walked. This is good for debugging for infinite recursion. bool walkBucketLinks(uint checkBucket, uint hash, uint mustFindBucket = 0) const { bool found = false; while(checkBucket) { if(checkBucket == mustFindBucket) found = true; checkBucket = bucketForIndex(checkBucket)->nextBucketForHash(hash); } return found || (mustFindBucket == 0); } ///Computes the bucket where the chains opened by the buckets @param mainHead and @param intersectorHead ///with hash @param hash meet each other. ///@return QPair hashChainIntersection(uint mainHead, uint intersectorHead, uint hash) const { uint previous = 0; uint current = mainHead; while(current) { ///@todo Make this more efficient if(walkBucketLinks(intersectorHead, hash, current)) return qMakePair(previous, current); previous = current; current = bucketForIndex(current)->nextBucketForHash(hash); } return qMakePair(0u, 0u); } void putIntoFreeList(unsigned short bucket, MyBucket* bucketPtr) { Q_ASSERT(!bucketPtr->monsterBucketExtent()); int indexInFree = m_freeSpaceBuckets.indexOf(bucket); if(indexInFree == -1 && (bucketPtr->freeItemCount() >= MyBucket::MinFreeItemsForReuse || bucketPtr->largestFreeSize() >= MyBucket::MinFreeSizeForReuse)) { //Add the bucket to the list of buckets from where to re-assign free space //We only do it when a specific threshold of empty items is reached, because that way items can stay "somewhat" semantically ordered. Q_ASSERT(bucketPtr->largestFreeSize()); int insertPos; for(insertPos = 0; insertPos < m_freeSpaceBuckets.size(); ++insertPos) { if(bucketForIndex(m_freeSpaceBuckets[insertPos])->largestFreeSize() > bucketPtr->largestFreeSize()) break; } m_freeSpaceBuckets.insert(insertPos, bucket); updateFreeSpaceOrder(insertPos); }else if(indexInFree != -1) { ///Re-order so the order in m_freeSpaceBuckets is correct(sorted by largest free item size) updateFreeSpaceOrder(indexInFree); } #ifdef DEBUG_MONSTERBUCKETS if(bucketPtr->isEmpty()) { Q_ASSERT(m_freeSpaceBuckets.contains(bucket)); } #endif } void verifyIndex(uint index) const { // We don't use zero indices Q_ASSERT(index); int bucket = (index >> 16); // nor zero buckets Q_ASSERT(bucket); Q_ASSERT_X(bucket < m_buckets.size(), Q_FUNC_INFO, qPrintable(QStringLiteral("index %1 gives invalid bucket number %2, current count is: %3") .arg(index) .arg(bucket) .arg(m_buckets.size()))); // don't trigger compile warnings in release mode Q_UNUSED(bucket); Q_UNUSED(index); } bool m_metaDataChanged; mutable QMutex m_ownMutex; mutable QMutex* m_mutex; QString m_repositoryName; unsigned int m_size; mutable int m_currentBucket; //List of buckets that have free space available that can be assigned. Sorted by size: Smallest space first. Second order sorting: Bucket index QVector m_freeSpaceBuckets; mutable QVector m_buckets; uint m_statBucketHashClashes, m_statItemCount; //Maps hash-values modulo 1< * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "debugcontroller.h" -#include +#include -#include #include -#include -#include -#include +#include #include #include +#include #include "../interfaces/idocument.h" #include "../interfaces/icore.h" #include "../interfaces/idocumentcontroller.h" #include "../interfaces/ipartcontroller.h" #include "../interfaces/contextmenuextension.h" #include "../interfaces/context.h" #include "../language/interfaces/editorcontext.h" #include "../sublime/view.h" #include "../sublime/mainwindow.h" #include "../sublime/area.h" -#include "core.h" -#include "uicontroller.h" #include "../debugger/breakpoint/breakpointmodel.h" #include "../debugger/breakpoint/breakpointwidget.h" #include "../debugger/variable/variablewidget.h" #include "../debugger/framestack/framestackmodel.h" #include "../debugger/framestack/framestackwidget.h" +#include "core.h" #include "debug.h" -#include +#include "uicontroller.h" namespace KDevelop { template class DebuggerToolFactory : public KDevelop::IToolViewFactory { public: DebuggerToolFactory(DebugController* controller, const QString &id, Qt::DockWidgetArea defaultArea) : m_controller(controller), m_id(id), m_defaultArea(defaultArea) {} virtual QWidget* create(QWidget *parent = 0) override { return new T(m_controller, parent); } virtual QString id() const override { return m_id; } virtual Qt::DockWidgetArea defaultPosition() override { return m_defaultArea; } virtual void viewCreated(Sublime::View* view) override { if (view->widget()->metaObject()->indexOfSignal("requestRaise()") != -1) QObject::connect(view->widget(), SIGNAL(requestRaise()), view, SLOT(requestRaise())); } /* At present, some debugger widgets (e.g. breakpoint) contain actions so that shortcuts work, but they don't need any toolbar. So, suppress toolbar action. */ virtual QList toolBarActions( QWidget* viewWidget ) const override { Q_UNUSED(viewWidget); return QList(); } private: DebugController* m_controller; QString m_id; Qt::DockWidgetArea m_defaultArea; }; DebugController::DebugController(QObject *parent) : IDebugController(parent), KXMLGUIClient(), m_continueDebugger(0), m_stopDebugger(0), m_interruptDebugger(0), m_runToCursor(0), m_jumpToCursor(0), m_stepOver(0), m_stepIntoInstruction(0), m_stepInto(0), m_stepOverInstruction(0), m_stepOut(0), m_toggleBreakpoint(0), m_breakpointModel(new BreakpointModel(this)), m_variableCollection(new VariableCollection(this)), m_uiInitialized(false) { setComponentName("kdevdebugger", "kdevdebugger"); setXMLFile("kdevdebuggershellui.rc"); } void DebugController::initialize() { m_breakpointModel->load(); } void DebugController::initializeUi() { if (m_uiInitialized) return; m_uiInitialized = true; if((Core::self()->setupFlags() & Core::NoUi)) return; setupActions(); ICore::self()->uiController()->addToolView( i18n("Frame Stack"), new DebuggerToolFactory( this, "org.kdevelop.debugger.StackView", Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Breakpoints"), new DebuggerToolFactory( this, "org.kdevelop.debugger.BreakpointsView", Qt::BottomDockWidgetArea)); ICore::self()->uiController()->addToolView( i18n("Variables"), new DebuggerToolFactory( this, "org.kdevelop.debugger.VariablesView", Qt::LeftDockWidgetArea)); foreach(KParts::Part* p, KDevelop::ICore::self()->partController()->parts()) partAdded(p); connect(KDevelop::ICore::self()->partController(), &IPartController::partAdded, this, &DebugController::partAdded); ICore::self()->uiController()->activeMainWindow()->guiFactory()->addClient(this); stateChanged("ended"); } void DebugController::cleanup() { if (m_currentSession) m_currentSession.data()->stopDebugger(); } DebugController::~DebugController() { } BreakpointModel* DebugController::breakpointModel() { return m_breakpointModel; } VariableCollection* DebugController::variableCollection() { return m_variableCollection; } void DebugController::partAdded(KParts::Part* part) { if (KTextEditor::Document* doc = dynamic_cast(part)) { KTextEditor::MarkInterface *iface = dynamic_cast(doc); if( !iface ) return; iface->setMarkPixmap(KTextEditor::MarkInterface::Execution, *executionPointPixmap()); } } IDebugSession* DebugController::currentSession() { return m_currentSession.data(); } void DebugController::setupActions() { KActionCollection* ac = actionCollection(); QAction* action = m_continueDebugger = new QAction(QIcon::fromTheme("media-playback-start"), i18n("&Continue"), this); action->setToolTip( i18n("Continue application execution") ); action->setWhatsThis( i18n("Continues the execution of your application in the " "debugger. This only takes effect when the application " "has been halted by the debugger (i.e. a breakpoint has " "been activated or the interrupt was pressed).") ); ac->addAction("debug_continue", action); connect(action, &QAction::triggered, this, &DebugController::run); #if 0 m_restartDebugger = action = new QAction(QIcon::fromTheme("media-seek-backward"), i18n("&Restart"), this); action->setToolTip( i18n("Restart program") ); action->setWhatsThis( i18n("Restarts applications from the beginning.") ); action->setEnabled(false); connect(action, SIGNAL(triggered(bool)), this, SLOT(restartDebugger())); ac->addAction("debug_restart", action); #endif m_interruptDebugger = action = new QAction(QIcon::fromTheme("media-playback-pause"), i18n("Interrupt"), this); action->setToolTip( i18n("Interrupt application") ); action->setWhatsThis(i18n("Interrupts the debugged process or current debugger command.")); connect(action, &QAction::triggered, this, &DebugController::interruptDebugger); ac->addAction("debug_pause", action); m_runToCursor = action = new QAction(QIcon::fromTheme("debug-run-cursor"), i18n("Run to &Cursor"), this); action->setToolTip( i18n("Run to cursor") ); action->setWhatsThis(i18n("Continues execution until the cursor position is reached.")); connect(action, &QAction::triggered, this, &DebugController::runToCursor); ac->addAction("debug_runtocursor", action); m_jumpToCursor = action = new QAction(QIcon::fromTheme("debug-execute-to-cursor"), i18n("Set E&xecution Position to Cursor"), this); action->setToolTip( i18n("Jump to cursor") ); action->setWhatsThis(i18n("Continue execution from the current cursor position.")); connect(action, &QAction::triggered, this, &DebugController::jumpToCursor); ac->addAction("debug_jumptocursor", action); m_stepOver = action = new QAction(QIcon::fromTheme("debug-step-over"), i18n("Step &Over"), this); ac->setDefaultShortcut( action, Qt::Key_F10); action->setToolTip( i18n("Step over the next line") ); action->setWhatsThis( i18n("Executes one line of source in the current source file. " "If the source line is a call to a function the whole " "function is executed and the app will stop at the line " "following the function call.") ); connect(action, &QAction::triggered, this, &DebugController::stepOver); ac->addAction("debug_stepover", action); m_stepOverInstruction = action = new QAction(QIcon::fromTheme("debug-step-instruction"), i18n("Step over Ins&truction"), this); action->setToolTip( i18n("Step over instruction") ); action->setWhatsThis(i18n("Steps over the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepOverInstruction); ac->addAction("debug_stepoverinst", action); m_stepInto = action = new QAction(QIcon::fromTheme("debug-step-into"), i18n("Step &Into"), this); ac->setDefaultShortcut( action, Qt::Key_F11); action->setToolTip( i18n("Step into the next statement") ); action->setWhatsThis( i18n("Executes exactly one line of source. If the source line " "is a call to a function then execution will stop after " "the function has been entered.") ); connect(action, &QAction::triggered, this, &DebugController::stepInto); ac->addAction("debug_stepinto", action); m_stepIntoInstruction = action = new QAction(QIcon::fromTheme("debug-step-into-instruction"), i18n("Step into I&nstruction"), this); action->setToolTip( i18n("Step into instruction") ); action->setWhatsThis(i18n("Steps into the next assembly instruction.")); connect(action, &QAction::triggered, this, &DebugController::stepIntoInstruction); ac->addAction("debug_stepintoinst", action); m_stepOut = action = new QAction(QIcon::fromTheme("debug-step-out"), i18n("Step O&ut"), this); ac->setDefaultShortcut( action, Qt::Key_F12); action->setToolTip( i18n("Step out of the current function") ); action->setWhatsThis( i18n("Executes the application until the currently executing " "function is completed. The debugger will then display " "the line after the original call to that function. If " "program execution is in the outermost frame (i.e. in " "main()) then this operation has no effect.") ); connect(action, &QAction::triggered, this, &DebugController::stepOut); ac->addAction("debug_stepout", action); m_toggleBreakpoint = action = new QAction(QIcon::fromTheme("script-error"), i18n("Toggle Breakpoint"), this); ac->setDefaultShortcut( action, i18n("Ctrl+Alt+B") ); action->setToolTip(i18n("Toggle breakpoint")); action->setWhatsThis(i18n("Toggles the breakpoint at the current line in editor.")); connect(action, &QAction::triggered, this, &DebugController::toggleBreakpoint); ac->addAction("debug_toggle_breakpoint", action); } void DebugController::addSession(IDebugSession* session) { qCDebug(SHELL) << session; Q_ASSERT(session->variableController()); Q_ASSERT(session->breakpointController()); Q_ASSERT(session->frameStackModel()); //TODO support multiple sessions if (m_currentSession) { m_currentSession.data()->stopDebugger(); } m_currentSession = session; connect(session, &IDebugSession::stateChanged, this, &DebugController::debuggerStateChanged); connect(session, &IDebugSession::showStepInSource, this, &DebugController::showStepInSource); connect(session, &IDebugSession::clearExecutionPoint, this, &DebugController::clearExecutionPoint); connect(session, &IDebugSession::raiseFramestackViews, this, &DebugController::raiseFramestackViews); updateDebuggerState(session->state(), session); emit currentSessionChanged(session); if((Core::self()->setupFlags() & Core::NoUi)) return; Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow->area()->objectName() != "debug") { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea("debug", IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); connect(mainWindow, &Sublime::MainWindow::areaChanged, this, &DebugController::areaChanged); } } void DebugController::clearExecutionPoint() { qCDebug(SHELL); foreach (KDevelop::IDocument* document, KDevelop::ICore::self()->documentController()->openDocuments()) { KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if (!iface) continue; QHashIterator it = iface->marks(); while (it.hasNext()) { KTextEditor::Mark* mark = it.next().value(); if( mark->type & KTextEditor::MarkInterface::Execution ) iface->removeMark( mark->line, KTextEditor::MarkInterface::Execution ); } } } void DebugController::showStepInSource(const QUrl &url, int lineNum) { if((Core::self()->setupFlags() & Core::NoUi)) return; clearExecutionPoint(); qCDebug(SHELL) << url << lineNum; Q_ASSERT(dynamic_cast(sender())); QPair openUrl = static_cast(sender())->convertToLocalUrl(qMakePair( url, lineNum )); KDevelop::IDocument* document = KDevelop::ICore::self() ->documentController() ->openDocument(openUrl.first, KTextEditor::Cursor(openUrl.second, 0), IDocumentController::DoNotFocus); if( !document ) return; KTextEditor::MarkInterface *iface = dynamic_cast(document->textDocument()); if( !iface ) return; document->textDocument()->blockSignals(true); iface->addMark( lineNum, KTextEditor::MarkInterface::Execution ); document->textDocument()->blockSignals(false); } void DebugController::debuggerStateChanged(KDevelop::IDebugSession::DebuggerState state) { Q_ASSERT(dynamic_cast(sender())); IDebugSession* session = static_cast(sender()); qCDebug(SHELL) << session << state << "current" << m_currentSession.data(); if (session == m_currentSession.data()) { updateDebuggerState(state, session); } if (state == IDebugSession::EndedState) { if (session == m_currentSession.data()) { m_currentSession.clear(); emit currentSessionChanged(0); if (!Core::self()->shuttingDown()) { Sublime::MainWindow* mainWindow = Core::self()->uiControllerInternal()->activeSublimeWindow(); if (mainWindow && mainWindow->area()->objectName() != "code") { QString workingSet = mainWindow->area()->workingSet(); ICore::self()->uiController()->switchToArea("code", IUiController::ThisWindow); mainWindow->area()->setWorkingSet(workingSet); } ICore::self()->uiController()->findToolView(i18n("Debug"), 0, IUiController::Raise); } } session->deleteLater(); } } void DebugController::updateDebuggerState(IDebugSession::DebuggerState state, IDebugSession *session) { Q_UNUSED(session); if((Core::self()->setupFlags() & Core::NoUi)) return; qCDebug(SHELL) << state; switch (state) { case IDebugSession::StoppedState: case IDebugSession::NotStartedState: case IDebugSession::StoppingState: qCDebug(SHELL) << "new state: stopped"; stateChanged("stopped"); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::StartingState: case IDebugSession::PausedState: qCDebug(SHELL) << "new state: paused"; stateChanged("paused"); //m_restartDebugger->setEnabled(session->restartAvailable()); break; case IDebugSession::ActiveState: qCDebug(SHELL) << "new state: active"; stateChanged("active"); //m_restartDebugger->setEnabled(false); break; case IDebugSession::EndedState: qCDebug(SHELL) << "new state: ended"; stateChanged("ended"); //m_restartDebugger->setEnabled(false); break; } if (state == IDebugSession::PausedState && ICore::self()->uiController()->activeMainWindow()) { ICore::self()->uiController()->activeMainWindow()->activateWindow(); } } ContextMenuExtension DebugController::contextMenuExtension( Context* context ) { ContextMenuExtension menuExt; if( context->type() != Context::EditorContext ) return menuExt; KDevelop::EditorContext *econtext = dynamic_cast(context); if (!econtext) return menuExt; if (m_currentSession && m_currentSession.data()->isRunning()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_runToCursor); } if (econtext->url().isLocalFile()) { menuExt.addAction( KDevelop::ContextMenuExtension::DebugGroup, m_toggleBreakpoint); } return menuExt; } #if 0 void DebugController::restartDebugger() { if (m_currentSession) { m_currentSession.data()->restartDebugger(); } } #endif void DebugController::stopDebugger() { if (m_currentSession) { m_currentSession.data()->stopDebugger(); } } void DebugController::interruptDebugger() { if (m_currentSession) { m_currentSession.data()->interruptDebugger(); } } void DebugController::run() { if (m_currentSession) { m_currentSession.data()->run(); } } void DebugController::runToCursor() { if (m_currentSession) { m_currentSession.data()->runToCursor(); } } void DebugController::jumpToCursor() { if (m_currentSession) { m_currentSession.data()->jumpToCursor(); } } void DebugController::stepOver() { if (m_currentSession) { m_currentSession.data()->stepOver(); } } void DebugController::stepIntoInstruction() { if (m_currentSession) { m_currentSession.data()->stepIntoInstruction(); } } void DebugController::stepInto() { if (m_currentSession) { m_currentSession.data()->stepInto(); } } void DebugController::stepOverInstruction() { if (m_currentSession) { m_currentSession.data()->stepOverInstruction(); } } void DebugController::stepOut() { if (m_currentSession) { m_currentSession.data()->stepOut(); } } void DebugController::areaChanged(Sublime::Area* newArea) { if (newArea->objectName()!="debug") { stopDebugger(); } } void DebugController::toggleBreakpoint() { if (KDevelop::IDocument* document = KDevelop::ICore::self()->documentController()->activeDocument()) { KTextEditor::Cursor cursor = document->cursorPosition(); if (!cursor.isValid()) return; breakpointModel()->toggleBreakpoint(document->url(), cursor); } } const QPixmap* DebugController::executionPointPixmap() { static QPixmap pixmap=QIcon::fromTheme("go-next").pixmap(QSize(22,22), QIcon::Normal, QIcon::Off); return &pixmap; } } diff --git a/shell/documentationcontroller.cpp b/shell/documentationcontroller.cpp index 675eda9763..032900c4f1 100644 --- a/shell/documentationcontroller.cpp +++ b/shell/documentationcontroller.cpp @@ -1,233 +1,234 @@ /* Copyright 2009 Aleix Pol Gonzalez Copyright 2010 Benjamin Port This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentationcontroller.h" #include "debug.h" #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 using namespace KDevelop; namespace { /** * Return a "more useful" declaration that documentation providers can look-up * * @code * QPoint point; * ^-- cursor here * @endcode * * In this case, this method returns a Declaration pointer to the *type* * instead of a pointer to the instance, which is more useful when looking for help * * @return A more appropriate Declaration pointer or the given parameter @p decl */ Declaration* usefulDeclaration(Declaration* decl) { if (!decl) return nullptr; // First: Attempt to find the declaration of a definition decl = DUChainUtils::declarationForDefinition(decl); // Convenience feature: Retrieve the type declaration of instances, // it makes no sense to pass the declaration pointer of instances of types if (decl->kind() == Declaration::Instance) { AbstractType::Ptr type = TypeUtils::targetTypeKeepAliases(decl->abstractType(), decl->topContext()); IdentifiedType* idType = dynamic_cast(type.data()); Declaration* idDecl = idType ? idType->declaration(decl->topContext()) : 0; if (idDecl) { decl = idDecl; } } return decl; } } class DocumentationViewFactory: public KDevelop::IToolViewFactory { public: DocumentationViewFactory() : mProvidersModel(0) {} virtual QWidget* create( QWidget *parent = 0 ) override { return new DocumentationView( parent, providers() ); } virtual Qt::DockWidgetArea defaultPosition() override { return Qt::RightDockWidgetArea; } virtual QString id() const override { return "org.kdevelop.DocumentationView"; } private: ProvidersModel* providers() { if(!mProvidersModel) mProvidersModel = new ProvidersModel; return mProvidersModel; } ProvidersModel* mProvidersModel; }; DocumentationController::DocumentationController(Core* core) : m_factory(new DocumentationViewFactory) { m_showDocumentation = core->uiController()->activeMainWindow()->actionCollection()->addAction("showDocumentation"); m_showDocumentation->setText(i18n("Show Documentation")); m_showDocumentation->setIcon(QIcon::fromTheme("documentation")); connect(m_showDocumentation, &QAction::triggered, this, &DocumentationController::doShowDocumentation); } void DocumentationController::initialize() { if(!documentationProviders().isEmpty() && !(Core::self()->setupFlags() & Core::NoUi)) { Core::self()->uiController()->addToolView( i18n("Documentation"), m_factory ); } } void KDevelop::DocumentationController::doShowDocumentation() { KTextEditor::View* view = ICore::self()->documentController()->activeTextDocumentView(); if(!view) return; KDevelop::DUChainReadLocker lock( DUChain::lock() ); Declaration* decl = usefulDeclaration(DUChainUtils::itemUnderCursor(view->document()->url(), KTextEditor::Cursor(view->cursorPosition()))); auto documentation = documentationForDeclaration(decl); if (documentation) { showDocumentation(documentation); } } KDevelop::ContextMenuExtension KDevelop::DocumentationController::contextMenuExtension ( Context* context ) { ContextMenuExtension menuExt; DeclarationContext* ctx = dynamic_cast(context); if(ctx) { DUChainReadLocker lock(DUChain::lock()); if(!ctx->declaration().data()) return menuExt; auto doc = documentationForDeclaration(ctx->declaration().data()); if (doc) { menuExt.addAction(ContextMenuExtension::ExtensionGroup, m_showDocumentation);; } } return menuExt; } IDocumentation::Ptr DocumentationController::documentationForDeclaration(Declaration* decl) { if (!decl) return {}; foreach (IDocumentationProvider* doc, documentationProviders()) { qCDebug(SHELL) << "Documentation provider found:" << doc; auto ret = doc->documentationForDeclaration(decl); qCDebug(SHELL) << "Documentation proposed: " << ret.data(); if (ret) { return ret; } } return {}; } QList< IDocumentationProvider* > DocumentationController::documentationProviders() const { QList plugins=ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IDocumentationProvider"); QList pluginsProvider=ICore::self()->pluginController()->allPluginsForExtension("org.kdevelop.IDocumentationProviderProvider"); QList ret; foreach(IPlugin* p, pluginsProvider) { IDocumentationProviderProvider *docProvider=p->extension(); if (!docProvider) { qWarning() << "plugin" << p << "does not implement ProviderProvider extension, rerun kbuildsycoca5"; continue; } ret.append(docProvider->providers()); } foreach(IPlugin* p, plugins) { IDocumentationProvider *doc=p->extension(); if (!doc) { qWarning() << "plugin" << p << "does not implement Provider extension, rerun kbuildsycoca5"; continue; } ret.append(doc); } return ret; } void KDevelop::DocumentationController::showDocumentation(const IDocumentation::Ptr& doc) { QWidget* w = ICore::self()->uiController()->findToolView(i18n("Documentation"), m_factory, KDevelop::IUiController::CreateAndRaise); if(!w) { qWarning() << "Could not add documentation toolview"; return; } DocumentationView* view = dynamic_cast(w); if( !view ) { qWarning() << "Could not cast toolview" << w << "to DocumentationView class!"; return; } view->showDocumentation(doc); } void DocumentationController::changedDocumentationProviders() { emit providersChanged(); } diff --git a/shell/documentcontroller.cpp b/shell/documentcontroller.cpp index 915bc2ac08..1738aaf94f 100644 --- a/shell/documentcontroller.cpp +++ b/shell/documentcontroller.cpp @@ -1,1235 +1,1237 @@ /* This file is part of the KDE project Copyright 2002 Matthias Hoelzer-Kluepfel Copyright 2002 Bernd Gehrmann Copyright 2003 Roberto Raggi Copyright 2003-2008 Hamish Rodda Copyright 2003 Harald Fernengel Copyright 2003 Jens Dagerbo Copyright 2005 Adam Treat Copyright 2004-2007 Alexander Dymo Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "documentcontroller.h" -#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 #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "textdocument.h" #include "uicontroller.h" #include "partcontroller.h" #include "savedialog.h" #include "debug.h" -#include -#include -#include -#include + #include "workingsetcontroller.h" #include #include #include #include #define EMPTY_DOCUMENT_URL i18n("Untitled") namespace KDevelop { struct DocumentControllerPrivate { DocumentControllerPrivate(DocumentController* c) : controller(c) , fileOpenRecent(0) , globalTextEditorInstance(0) { } ~DocumentControllerPrivate() { //delete temporary files so they are removed from disk foreach (QTemporaryFile *temp, tempFiles) delete temp; } QString presetEncoding; // used to map urls to open docs QHash< QUrl, IDocument* > documents; QHash< QString, IDocumentFactory* > factories; QList tempFiles; struct HistoryEntry { HistoryEntry() {} HistoryEntry( const QUrl & u, const KTextEditor::Cursor& cursor ); QUrl url; KTextEditor::Cursor cursor; int id; }; void removeDocument(Sublime::Document *doc) { QList urlsForDoc = documents.keys(dynamic_cast(doc)); foreach (const QUrl &url, urlsForDoc) { qCDebug(SHELL) << "destroying document" << doc; documents.remove(url); } } KEncodingFileDialog::Result showOpenFile() const { QUrl dir; if ( controller->activeDocument() ) { dir = controller->activeDocument()->url().adjusted(QUrl::RemoveFilename); } else { const auto cfg = KSharedConfig::openConfig()->group("Open File"); dir = cfg.readEntry( "Last Open File Directory", Core::self()->projectController()->projectsBaseDirectory() ); } return KEncodingFileDialog::getOpenUrlsAndEncoding( controller->encoding(), dir, i18n( "*|Text File\n" ), Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Open File" ) ); } void chooseDocument() { const auto res = showOpenFile(); if( !res.URLs.isEmpty() ) { QString encoding = res.encoding; foreach( const QUrl& u, res.URLs ) { openDocumentInternal(u, QString(), KTextEditor::Range::invalid(), encoding ); } } } void changeDocumentUrl(KDevelop::IDocument* document) { QMutableHashIterator it = documents; while (it.hasNext()) { if (it.next().value() == document) { if (documents.contains(document->url())) { // Weird situation (saving as a file that is aready open) IDocument* origDoc = documents[document->url()]; if (origDoc->state() & IDocument::Modified) { // given that the file has been saved, close the saved file as the other instance will become conflicted on disk document->close(); controller->activateDocument( origDoc ); break; } // Otherwise close the original document origDoc->close(); } else { // Remove the original document it.remove(); } documents.insert(document->url(), document); if (!controller->isEmptyDocumentUrl(document->url())) { fileOpenRecent->addUrl(document->url()); } break; } } } KDevelop::IDocument* findBuddyDocument(const QUrl &url, IBuddyDocumentFinder* finder) { QList allDocs = controller->openDocuments(); foreach( KDevelop::IDocument* doc, allDocs ) { if(finder->areBuddies(url, doc->url())) { return doc; } } return 0; } IDocument* openDocumentInternal( const QUrl & inputUrl, const QString& prefName = QString(), const KTextEditor::Range& range = KTextEditor::Range::invalid(), const QString& encoding = "", DocumentController::DocumentActivationParams activationParams = 0, IDocument* buddy = 0) { Q_ASSERT(!inputUrl.isRelative()); Q_ASSERT(!inputUrl.fileName().isEmpty()); KTextEditor::View* previousActiveTextView = controller->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QString _encoding = encoding; QUrl url = inputUrl; if ( url.isEmpty() && (!activationParams.testFlag(IDocumentController::DoNotCreateView)) ) { const auto res = showOpenFile(); if( !res.URLs.isEmpty() ) url = res.URLs.first(); _encoding = res.encoding; if ( url.isEmpty() ) //still no url return 0; } KSharedConfig::openConfig()->group("Open File").writeEntry( "Last Open File Directory", url.adjusted(QUrl::RemoveFilename) ); // clean it and resolve possible symlink url = url.adjusted( QUrl::NormalizePathSegments ); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url = QUrl::fromLocalFile( path ); } //get a part document IDocument* doc = documents.value(url); if (!doc) { auto existJob = [](const QUrl& url) { auto job = KIO::stat(url, KIO::StatJob::SourceSide, 0); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); return job; }; QMimeType mimeType; if (DocumentController::isEmptyDocumentUrl(url)) { mimeType = QMimeDatabase().mimeTypeForName("text/plain"); } else if (!url.isValid()) { // Exit if the url is invalid (should not happen) // If the url is valid and the file does not already exist, // kate creates the file and gives a message saying so qCDebug(SHELL) << "invalid URL:" << url.url(); return 0; } else if (KProtocolInfo::isKnownProtocol(url.scheme()) && !existJob(url)->exec()) { //Don't create a new file if we are not in the code mode. if (static_cast(ICore::self()->uiController()->activeMainWindow())->area()->objectName() != "code") { return 0; } // enfore text mime type in order to create a kate part editor which then can be used to create the file // otherwise we could end up opening e.g. okteta which then crashes, see: https://bugs.kde.org/id=326434 mimeType = QMimeDatabase().mimeTypeForName("text/plain"); } else if(!url.isLocalFile() && mimeType.isDefault()) { // fall back to text/plain, for remote files without extension, i.e. COPYING, LICENSE, ... // using a synchronous KIO::MimetypeJob is hazardous and may lead to repeated calls to // this function without it having returned in the first place // and this function is *not* reentrant, see assert below: // Q_ASSERT(!documents.contains(url) || documents[url]==doc); mimeType = QMimeDatabase().mimeTypeForName("text/plain"); } else { mimeType = QMimeDatabase().mimeTypeForUrl(url); } // is the URL pointing to a directory? if (mimeType.inherits(QStringLiteral("inode/directory"))) { qCDebug(SHELL) << "cannot open directory:" << url.url(); return 0; } if( prefName.isEmpty() ) { // Try to find a plugin that handles this mimetype QVariantMap constraints; constraints.insert("X-KDevelop-SupportedMimeTypes", mimeType.name()); Core::self()->pluginController()->pluginForExtension(QString(), QString(), constraints); } if( IDocumentFactory* factory = factories.value(mimeType.name())) { doc = factory->create(url, Core::self()); } if(!doc) { if( !prefName.isEmpty() ) { doc = new PartDocument(url, Core::self(), prefName); } else if ( Core::self()->partControllerInternal()->isTextType(mimeType)) { doc = new TextDocument(url, Core::self(), _encoding); } else if( Core::self()->partControllerInternal()->canCreatePart(url) ) { doc = new PartDocument(url, Core::self()); } else { int openAsText = KMessageBox::questionYesNo(0, i18n("KDevelop could not find the editor for file '%1' of type %2.\nDo you want to open it as plain text?", url.fileName(), mimeType.name()), i18nc("@title:window", "Could Not Find Editor"), KStandardGuiItem::yes(), KStandardGuiItem::no(), "AskOpenWithTextEditor"); if (openAsText == KMessageBox::Yes) doc = new TextDocument(url, Core::self(), _encoding); else return 0; } } } // The url in the document must equal the current url, else the housekeeping will get broken Q_ASSERT(!doc || doc->url() == url); if(doc && openDocumentInternal(doc, range, activationParams, buddy)) return doc; else return 0; } bool openDocumentInternal(IDocument* doc, const KTextEditor::Range& range, DocumentController::DocumentActivationParams activationParams, IDocument* buddy = 0) { IDocument* previousActiveDocument = controller->activeDocument(); KTextEditor::View* previousActiveTextView = ICore::self()->documentController()->activeTextDocumentView(); KTextEditor::Cursor previousActivePosition; if(previousActiveTextView) previousActivePosition = previousActiveTextView->cursorPosition(); QUrl url=doc->url(); UiController *uiController = Core::self()->uiControllerInternal(); Sublime::Area *area = uiController->activeArea(); //We can't have the same url in many documents //so we check it's already the same if it exists //contains=>it's the same Q_ASSERT(!documents.contains(url) || documents[url]==doc); Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { documents.remove(url); delete doc; return false; } //react on document deletion - we need to cleanup controller structures QObject::connect(sdoc, &Sublime::Document::aboutToDelete, controller, &DocumentController::notifyDocumentClosed); //We check if it was already opened before bool emitOpened = !documents.contains(url); if(emitOpened) documents[url]=doc; if (!activationParams.testFlag(IDocumentController::DoNotCreateView)) { //find a view if there's one already opened in this area Sublime::View *partView = 0; Sublime::AreaIndex* activeViewIdx = area->indexOf(uiController->activeSublimeWindow()->activeView()); foreach (Sublime::View *view, sdoc->views()) { Sublime::AreaIndex* areaIdx = area->indexOf(view); if (areaIdx && areaIdx == activeViewIdx) { partView = view; break; } } bool addView = false; if (!partView) { //no view currently shown for this url partView = sdoc->createView(); addView = true; } if(addView) { // This code is never executed when restoring session on startup, // only when opening a file manually Sublime::View* buddyView = 0; bool placeAfterBuddy = true; if(Core::self()->uiControllerInternal()->arrangeBuddies()) { // If buddy is not set, look for a (usually) plugin which handles this URL's mimetype // and use its IBuddyDocumentFinder, if available, to find a buddy document if(!buddy && doc->mimeType().isValid()) { QString mime = doc->mimeType().name(); IBuddyDocumentFinder* buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); if(buddyFinder) { buddy = findBuddyDocument(url, buddyFinder); if(buddy) { placeAfterBuddy = buddyFinder->buddyOrder(buddy->url(), doc->url()); } } } if(buddy) { Sublime::Document* sublimeDocBuddy = dynamic_cast(buddy); if(sublimeDocBuddy) { Sublime::AreaIndex *pActiveViewIndex = area->indexOf(uiController->activeSublimeWindow()->activeView()); if(pActiveViewIndex) { // try to find existing View of buddy document in current active view's tab foreach (Sublime::View *pView, pActiveViewIndex->views()) { if(sublimeDocBuddy->views().contains(pView)) { buddyView = pView; break; } } } } } } // add view to the area if(buddyView && area->indexOf(buddyView)) { if(placeAfterBuddy) { // Adding new view after buddy view, simple case area->addView(partView, area->indexOf(buddyView), buddyView); } else { // First new view, then buddy view area->addView(partView, area->indexOf(buddyView), buddyView); // move buddyView tab after the new document area->removeView(buddyView); area->addView(buddyView, area->indexOf(partView), partView); } } else { // no buddy found for new document / plugin does not support buddies / buddy feature disabled Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); Sublime::UrlDocument *activeDoc = 0; IBuddyDocumentFinder *buddyFinder = 0; if(activeView) activeDoc = dynamic_cast(activeView->document()); if(activeDoc && Core::self()->uiControllerInternal()->arrangeBuddies()) { QString mime = QMimeDatabase().mimeTypeForUrl(activeDoc->url()).name(); buddyFinder = IBuddyDocumentFinder::finderForMimeType(mime); } if(Core::self()->uiControllerInternal()->openAfterCurrent() && Core::self()->uiControllerInternal()->arrangeBuddies() && buddyFinder) { // Check if active document's buddy is directly next to it. // For example, we have the already-open tabs | *foo.h* | foo.cpp | , foo.h is active. // When we open a new document here (and the buddy feature is enabled), // we do not want to separate foo.h and foo.cpp, so we take care and avoid this. Sublime::AreaIndex *activeAreaIndex = area->indexOf(activeView); int pos = activeAreaIndex->views().indexOf(activeView); Sublime::View *afterActiveView = activeAreaIndex->views().value(pos+1, 0); Sublime::UrlDocument *activeDoc = 0, *afterActiveDoc = 0; if(activeView && afterActiveView) { activeDoc = dynamic_cast(activeView->document()); afterActiveDoc = dynamic_cast(afterActiveView->document()); } if(activeDoc && afterActiveDoc && buddyFinder->areBuddies(activeDoc->url(), afterActiveDoc->url())) { // don't insert in between of two buddies, but after them area->addView(partView, activeAreaIndex, afterActiveView); } else { // The active document's buddy is not directly after it // => no ploblem, insert after active document area->addView(partView, activeView); } } else { // Opening as last tab won't disturb our buddies // Same, if buddies are disabled, we needn't care about them. // this method places the tab according to openAfterCurrent() area->addView(partView, activeView); } } } if (!activationParams.testFlag(IDocumentController::DoNotActivate)) { uiController->activeSublimeWindow()->activateView( partView, !activationParams.testFlag(IDocumentController::DoNotFocus)); } if (!controller->isEmptyDocumentUrl(url)) { fileOpenRecent->addUrl( url ); } if( range.isValid() ) { if (range.isEmpty()) doc->setCursorPosition( range.start() ); else doc->setTextSelection( range ); } } // Deferred signals, wait until it's all ready first if( emitOpened ) { emit controller->documentOpened( doc ); } if (!activationParams.testFlag(IDocumentController::DoNotActivate) && doc != controller->activeDocument()) emit controller->documentActivated( doc ); saveAll->setEnabled(true); revertAll->setEnabled(true); close->setEnabled(true); closeAll->setEnabled(true); closeAllOthers->setEnabled(true); KTextEditor::Cursor activePosition; if(range.isValid()) activePosition = range.start(); else if(KTextEditor::View* v = doc->activeTextView()) activePosition = v->cursorPosition(); if (doc != previousActiveDocument || activePosition != previousActivePosition) emit controller->documentJumpPerformed(doc, activePosition, previousActiveDocument, previousActivePosition); return true; } DocumentController* controller; QList backHistory; QList forwardHistory; bool isJumping; QPointer saveAll; QPointer revertAll; QPointer close; QPointer closeAll; QPointer closeAllOthers; KRecentFilesAction* fileOpenRecent; KTextEditor::Document* globalTextEditorInstance; }; DocumentController::DocumentController( QObject *parent ) : IDocumentController( parent ) { setObjectName("DocumentController"); d = new DocumentControllerPrivate(this); QDBusConnection::sessionBus().registerObject( "/org/kdevelop/DocumentController", this, QDBusConnection::ExportScriptableSlots ); connect(this, &DocumentController::documentUrlChanged, this, [&] (IDocument* document) { d->changeDocumentUrl(document); }); if(!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } void DocumentController::initialize() { } void DocumentController::cleanup() { if (d->fileOpenRecent) d->fileOpenRecent->saveEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); // Close all documents without checking if they should be saved. // This is because the user gets a chance to save them during MainWindow::queryClose. foreach (IDocument* doc, openDocuments()) doc->close(IDocument::Discard); } DocumentController::~DocumentController() { delete d; } void DocumentController::setupActions() { KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = ac->addAction( "file_open" ); action->setIcon(QIcon::fromTheme("document-open")); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_O ); action->setText(i18n( "&Open..." ) ); connect( action, &QAction::triggered, this, [&] { d->chooseDocument(); } ); action->setToolTip( i18n( "Open file" ) ); action->setWhatsThis( i18n( "Opens a file for editing." ) ); d->fileOpenRecent = KStandardAction::openRecent(this, SLOT(slotOpenDocument(QUrl)), ac); d->fileOpenRecent->setWhatsThis(i18n("This lists files which you have opened recently, and allows you to easily open them again.")); d->fileOpenRecent->loadEntries( KConfigGroup(KSharedConfig::openConfig(), "Recent Files" ) ); action = d->saveAll = ac->addAction( "file_save_all" ); action->setIcon(QIcon::fromTheme("document-save")); action->setText(i18n( "Save Al&l" ) ); connect( action, &QAction::triggered, this, &DocumentController::slotSaveAllDocuments ); action->setToolTip( i18n( "Save all open documents" ) ); action->setWhatsThis( i18n( "Save all open documents, prompting for additional information when necessary." ) ); ac->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_L) ); action->setEnabled(false); action = d->revertAll = ac->addAction( "file_revert_all" ); action->setIcon(QIcon::fromTheme("document-revert")); action->setText(i18n( "Reload All" ) ); connect( action, &QAction::triggered, this, &DocumentController::reloadAllDocuments ); action->setToolTip( i18n( "Revert all open documents" ) ); action->setWhatsThis( i18n( "Revert all open documents, returning to the previously saved state." ) ); action->setEnabled(false); action = d->close = ac->addAction( "file_close" ); action->setIcon(QIcon::fromTheme("window-close")); ac->setDefaultShortcut(action, Qt::CTRL + Qt::Key_W ); action->setText( i18n( "&Close" ) ); connect( action, &QAction::triggered, this, &DocumentController::fileClose ); action->setToolTip( i18n( "Close file" ) ); action->setWhatsThis( i18n( "Closes current file." ) ); action->setEnabled(false); action = d->closeAll = ac->addAction( "file_close_all" ); action->setIcon(QIcon::fromTheme("window-close")); action->setText(i18n( "Clos&e All" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllDocuments ); action->setToolTip( i18n( "Close all open documents" ) ); action->setWhatsThis( i18n( "Close all open documents, prompting for additional information when necessary." ) ); action->setEnabled(false); action = d->closeAllOthers = ac->addAction( "file_closeother" ); action->setIcon(QIcon::fromTheme("window-close")); ac->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_W ); action->setText(i18n( "Close All Ot&hers" ) ); connect( action, &QAction::triggered, this, &DocumentController::closeAllOtherDocuments ); action->setToolTip( i18n( "Close all other documents" ) ); action->setWhatsThis( i18n( "Close all open documents, with the exception of the currently active document." ) ); action->setEnabled(false); action = ac->addAction( "vcsannotate_current_document" ); connect( action, &QAction::triggered, this, &DocumentController::vcsAnnotateCurrentDocument ); action->setText( i18n( "Show Annotate on current document") ); action->setIconText( i18n( "Annotate" ) ); action->setIcon( QIcon::fromTheme("user-properties") ); } void DocumentController::setEncoding( const QString &encoding ) { d->presetEncoding = encoding; } QString KDevelop::DocumentController::encoding() const { return d->presetEncoding; } void DocumentController::slotOpenDocument(const QUrl &url) { openDocument(url); } IDocument* DocumentController::openDocumentFromText( const QString& data ) { IDocument* d = openDocument(nextEmptyDocumentUrl()); Q_ASSERT(d->textDocument()); d->textDocument()->setText( data ); return d; } bool DocumentController::openDocumentFromTextSimple( QString text ) { return (bool)openDocumentFromText( text ); } bool DocumentController::openDocumentSimple( QString url, int line, int column ) { return (bool)openDocument( QUrl::fromUserInput(url), KTextEditor::Cursor( line, column ) ); } IDocument* DocumentController::openDocument( const QUrl& inputUrl, const QString& prefName ) { return d->openDocumentInternal( inputUrl, prefName ); } IDocument* DocumentController::openDocument( const QUrl & inputUrl, const KTextEditor::Range& range, DocumentActivationParams activationParams, const QString& encoding, IDocument* buddy) { return d->openDocumentInternal( inputUrl, "", range, encoding, activationParams, buddy); } bool DocumentController::openDocument(IDocument* doc, const KTextEditor::Range& range, DocumentActivationParams activationParams, IDocument* buddy) { return d->openDocumentInternal( doc, range, activationParams, buddy); } void DocumentController::fileClose() { IDocument *activeDoc = activeDocument(); if (activeDoc) { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::View *activeView = uiController->activeSublimeWindow()->activeView(); uiController->activeArea()->closeView(activeView); } } void DocumentController::closeDocument( const QUrl &url ) { if( !d->documents.contains(url) ) return; //this will remove all views and after the last view is removed, the //document will be self-destructed and removeDocument() slot will catch that //and clean up internal data structures d->documents[url]->close(); } void DocumentController::notifyDocumentClosed(Sublime::Document* doc_) { IDocument* doc = dynamic_cast(doc_); Q_ASSERT(doc); d->removeDocument(doc_); if (d->documents.isEmpty()) { if (d->saveAll) d->saveAll->setEnabled(false); if (d->revertAll) d->revertAll->setEnabled(false); if (d->close) d->close->setEnabled(false); if (d->closeAll) d->closeAll->setEnabled(false); if (d->closeAllOthers) d->closeAllOthers->setEnabled(false); } emit documentClosed(doc); } IDocument * DocumentController::documentForUrl( const QUrl & dirtyUrl ) const { if (dirtyUrl.isEmpty()) { return nullptr; } Q_ASSERT(!dirtyUrl.isRelative()); Q_ASSERT(!dirtyUrl.fileName().isEmpty()); //Fix urls that might not be normalized return d->documents.value( dirtyUrl.adjusted( QUrl::NormalizePathSegments ), 0 ); } QList DocumentController::openDocuments() const { QList opened; foreach (IDocument *doc, d->documents) { Sublime::Document *sdoc = dynamic_cast(doc); if( !sdoc ) { continue; } if (!sdoc->views().isEmpty()) opened << doc; } return opened; } void DocumentController::activateDocument( IDocument * document, const KTextEditor::Range& range ) { // TODO avoid some code in openDocument? Q_ASSERT(document); openDocument(document->url(), range); } void DocumentController::slotSaveAllDocuments() { saveAllDocuments(IDocument::Silent); } bool DocumentController::saveAllDocuments(IDocument::DocumentSaveMode mode) { return saveSomeDocuments(openDocuments(), mode); } bool KDevelop::DocumentController::saveSomeDocuments(const QList< IDocument * > & list, IDocument::DocumentSaveMode mode) { if (mode & IDocument::Silent) { foreach (IDocument* doc, modifiedDocuments(list)) { if( !DocumentController::isEmptyDocumentUrl(doc->url()) && !doc->save(mode) ) { if( doc ) qWarning() << "!! Could not save document:" << doc->url(); else qWarning() << "!! Could not save document as its NULL"; } // TODO if (!ret) showErrorDialog() ? } } else { // Ask the user which documents to save QList checkSave = modifiedDocuments(list); if (!checkSave.isEmpty()) { KSaveSelectDialog dialog(checkSave, qApp->activeWindow()); if (dialog.exec() == QDialog::Rejected) return false; } } return true; } QList< IDocument * > KDevelop::DocumentController::visibleDocumentsInWindow(MainWindow * mw) const { // Gather a list of all documents which do have a view in the given main window // Does not find documents which are open in inactive areas QList list; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { foreach (Sublime::View* view, sdoc->views()) { if (view->hasWidget() && view->widget()->window() == mw) { list.append(doc); break; } } } } return list; } QList< IDocument * > KDevelop::DocumentController::documentsExclusivelyInWindow(MainWindow * mw, bool currentAreaOnly) const { // Gather a list of all documents which have views only in the given main window QList checkSave; foreach (IDocument* doc, openDocuments()) { if (Sublime::Document* sdoc = dynamic_cast(doc)) { bool inOtherWindow = false; foreach (Sublime::View* view, sdoc->views()) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) if(window->containsView(view) && (window != mw || (currentAreaOnly && window == mw && !mw->area()->views().contains(view)))) inOtherWindow = true; } if (!inOtherWindow) checkSave.append(doc); } } return checkSave; } QList< IDocument * > KDevelop::DocumentController::modifiedDocuments(const QList< IDocument * > & list) const { QList< IDocument * > ret; foreach (IDocument* doc, list) if (doc->state() == IDocument::Modified || doc->state() == IDocument::DirtyAndModified) ret.append(doc); return ret; } bool DocumentController::saveAllDocumentsForWindow(KParts::MainWindow* mw, KDevelop::IDocument::DocumentSaveMode mode, bool currentAreaOnly) { QList checkSave = documentsExclusivelyInWindow(dynamic_cast(mw), currentAreaOnly); return saveSomeDocuments(checkSave, mode); } void DocumentController::reloadAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) if(!isEmptyDocumentUrl(doc->url())) doc->reload(); } } void DocumentController::closeAllDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { QList views = visibleDocumentsInWindow(dynamic_cast(mw)); if (!saveSomeDocuments(views, IDocument::Default)) // User cancelled or other error return; foreach (IDocument* doc, views) doc->close(IDocument::Discard); } } void DocumentController::closeAllOtherDocuments() { if (Sublime::MainWindow* mw = Core::self()->uiControllerInternal()->activeSublimeWindow()) { Sublime::View* activeView = mw->activeView(); if (!activeView) { qWarning() << "Shouldn't there always be an active view when this function is called?"; return; } // Deal with saving unsaved solo views QList soloViews = documentsExclusivelyInWindow(dynamic_cast(mw)); soloViews.removeAll(dynamic_cast(activeView->document())); if (!saveSomeDocuments(soloViews, IDocument::Default)) // User cancelled or other error return; foreach (Sublime::View* view, mw->area()->views()) { if (view != activeView) mw->area()->closeView(view); } activeView->widget()->setFocus(); } } IDocument* DocumentController::activeDocument() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return 0; return dynamic_cast(mw->activeView()->document()); } KTextEditor::View* DocumentController::activeTextDocumentView() const { UiController *uiController = Core::self()->uiControllerInternal(); Sublime::MainWindow* mw = uiController->activeSublimeWindow(); if( !mw || !mw->activeView() ) return 0; TextView* view = qobject_cast(mw->activeView()); if(!view) return 0; return view->textView(); } QString DocumentController::activeDocumentPath( QString target ) const { if(target.size()) { foreach(IProject* project, Core::self()->projectController()->projects()) { if(project->name().startsWith(target, Qt::CaseInsensitive)) { return project->path().pathOrUrl() + "/."; } } } IDocument* doc = activeDocument(); if(!doc || target == "[selection]") { Context* selection = ICore::self()->selectionController()->currentSelection(); if(selection && selection->type() == Context::ProjectItemContext && static_cast(selection)->items().size()) { QString ret = static_cast(selection)->items()[0]->path().pathOrUrl(); if(static_cast(selection)->items()[0]->folder()) ret += "/."; return ret; } return QString(); } return doc->url().toString(); } QStringList DocumentController::activeDocumentPaths() const { UiController *uiController = Core::self()->uiControllerInternal(); if( !uiController->activeSublimeWindow() ) return QStringList(); QSet documents; foreach(Sublime::View* view, uiController->activeSublimeWindow()->area()->views()) documents.insert(view->document()->documentSpecifier()); return documents.toList(); } void DocumentController::registerDocumentForMimetype( const QString& mimetype, KDevelop::IDocumentFactory* factory ) { if( !d->factories.contains( mimetype ) ) d->factories[mimetype] = factory; } QStringList DocumentController::documentTypes() const { return QStringList() << "Text"; } static const QRegularExpression& emptyDocumentPattern() { static const QRegularExpression pattern(QStringLiteral("^/%1(?:\\s\\((\\d+)\\))?$").arg(EMPTY_DOCUMENT_URL)); return pattern; } bool DocumentController::isEmptyDocumentUrl(const QUrl &url) { return emptyDocumentPattern().match(url.toDisplayString(QUrl::PreferLocalFile)).hasMatch(); } QUrl DocumentController::nextEmptyDocumentUrl() { int nextEmptyDocNumber = 0; const auto& pattern = emptyDocumentPattern(); foreach (IDocument *doc, Core::self()->documentControllerInternal()->openDocuments()) { if (DocumentController::isEmptyDocumentUrl(doc->url())) { const auto match = pattern.match(doc->url().toDisplayString(QUrl::PreferLocalFile)); if (match.hasMatch()) { const int num = match.captured(1).toInt(); nextEmptyDocNumber = qMax(nextEmptyDocNumber, num + 1); } else { nextEmptyDocNumber = qMax(nextEmptyDocNumber, 1); } } } QUrl url; if (nextEmptyDocNumber > 0) url = QUrl::fromLocalFile(QStringLiteral("/%1 (%2)").arg(EMPTY_DOCUMENT_URL).arg(nextEmptyDocNumber)); else url = QUrl::fromLocalFile('/' + EMPTY_DOCUMENT_URL); return url; } IDocumentFactory* DocumentController::factory(const QString& mime) const { return d->factories.value(mime); } KTextEditor::Document* DocumentController::globalTextEditorInstance() { if(!d->globalTextEditorInstance) d->globalTextEditorInstance = Core::self()->partControllerInternal()->createTextPart(); return d->globalTextEditorInstance; } bool DocumentController::openDocumentsSimple( QStringList urls ) { Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); Sublime::AreaIndex* areaIndex = area->rootIndex(); QList topViews = static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->getTopViews(); if(Sublime::View* activeView = Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()) areaIndex = area->indexOf(activeView); qCDebug(SHELL) << "opening " << urls << " to area " << area << " index " << areaIndex << " with children " << areaIndex->first() << " " << areaIndex->second(); bool isFirstView = true; bool ret = openDocumentsWithSplitSeparators( areaIndex, urls, isFirstView ); qCDebug(SHELL) << "area arch. after opening: " << areaIndex->print(); // Required because sublime sometimes doesn't update correctly when the area-index contents has been changed // (especially when views have been moved to other indices, through unsplit, split, etc.) static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->reconstructViews(topViews); return ret; } bool DocumentController::openDocumentsWithSplitSeparators( Sublime::AreaIndex* index, QStringList urlsWithSeparators, bool& isFirstView ) { qCDebug(SHELL) << "opening " << urlsWithSeparators << " index " << index << " with children " << index->first() << " " << index->second() << " view-count " << index->viewCount(); if(urlsWithSeparators.isEmpty()) return true; Sublime::Area* area = Core::self()->uiControllerInternal()->activeArea(); QList topLevelSeparators; // Indices of the top-level separators (with groups skipped) QStringList separators = QStringList() << "/" << "-"; QList groups; bool ret = true; { int parenDepth = 0; int groupStart = 0; for(int pos = 0; pos < urlsWithSeparators.size(); ++pos) { QString item = urlsWithSeparators[pos]; if(separators.contains(item)) { if(parenDepth == 0) topLevelSeparators << pos; }else if(item == "[") { if(parenDepth == 0) groupStart = pos+1; ++parenDepth; } else if(item == "]") { if(parenDepth > 0) { --parenDepth; if(parenDepth == 0) groups << urlsWithSeparators.mid(groupStart, pos-groupStart); } else{ qCDebug(SHELL) << "syntax error in " << urlsWithSeparators << ": parens do not match"; ret = false; } }else if(parenDepth == 0) { groups << (QStringList() << item); } } } if(topLevelSeparators.isEmpty()) { if(urlsWithSeparators.size() > 1) { foreach(QStringList group, groups) ret &= openDocumentsWithSplitSeparators( index, group, isFirstView ); }else{ while(index->isSplit()) index = index->first(); // Simply open the document into the area index IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(urlsWithSeparators.front()), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *sublimeDoc = dynamic_cast(doc); if (sublimeDoc) { Sublime::View* view = sublimeDoc->createView(); area->addView(view, index); if(isFirstView) { static_cast(Core::self()->uiControllerInternal()->activeMainWindow())->activateView(view); isFirstView = false; } }else{ ret = false; } } return ret; } // Pick a separator in the middle int pickSeparator = topLevelSeparators[topLevelSeparators.size()/2]; bool activeViewToSecondChild = false; if(pickSeparator == urlsWithSeparators.size()-1) { // There is no right child group, so the right side should be filled with the currently active views activeViewToSecondChild = true; }else{ QStringList separatorsAndParens = separators; separatorsAndParens << "[" << "]"; // Check if the second child-set contains an unterminated separator, which means that the active views should end up there for(int pos = pickSeparator+1; pos < urlsWithSeparators.size(); ++pos) if( separators.contains(urlsWithSeparators[pos]) && (pos == urlsWithSeparators.size()-1 || separatorsAndParens.contains(urlsWithSeparators[pos-1]) || separatorsAndParens.contains(urlsWithSeparators[pos-1])) ) activeViewToSecondChild = true; } Qt::Orientation orientation = urlsWithSeparators[pickSeparator] == "/" ? Qt::Horizontal : Qt::Vertical; if(!index->isSplit()) { qCDebug(SHELL) << "splitting " << index << "orientation" << orientation << "to second" << activeViewToSecondChild; index->split(orientation, activeViewToSecondChild); }else{ index->setOrientation(orientation); qCDebug(SHELL) << "WARNING: Area is already split (shouldn't be)" << urlsWithSeparators; } openDocumentsWithSplitSeparators( index->first(), urlsWithSeparators.mid(0, pickSeparator) , isFirstView ); if(pickSeparator != urlsWithSeparators.size() - 1) openDocumentsWithSplitSeparators( index->second(), urlsWithSeparators.mid(pickSeparator+1, urlsWithSeparators.size() - (pickSeparator+1) ), isFirstView ); // Clean up the child-indices, because document-loading may fail if(!index->first()->viewCount() && !index->first()->isSplit()) { qCDebug(SHELL) << "unsplitting first"; index->unsplit(index->first()); } else if(!index->second()->viewCount() && !index->second()->isSplit()) { qCDebug(SHELL) << "unsplitting second"; index->unsplit(index->second()); } return ret; } void DocumentController::vcsAnnotateCurrentDocument() { IDocument* doc = activeDocument(); QUrl url = doc->url(); IProject* project = KDevelop::ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IBasicVersionControl* iface = 0; iface = project->versionControlPlugin()->extension(); auto helper = new VcsPluginHelper(project->versionControlPlugin(), iface); connect(doc->textDocument(), &KTextEditor::Document::aboutToClose, helper, static_cast(&VcsPluginHelper::disposeEventually)); Q_ASSERT(qobject_cast(doc->activeTextView())); // can't use new signal slot syntax here, AnnotationViewInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationBorderVisibilityChanged(View*,bool)), helper, SLOT(disposeEventually(View*, bool))); helper->addContextDocument(url); helper->annotation(); } else { KMessageBox::error(0, i18n("Could not annotate the document because it is not " "part of a version-controlled project.")); } } } #include "moc_documentcontroller.cpp" diff --git a/shell/environmentconfigurebutton.cpp b/shell/environmentconfigurebutton.cpp index d0ce5ff231..fcb988faba 100644 --- a/shell/environmentconfigurebutton.cpp +++ b/shell/environmentconfigurebutton.cpp @@ -1,113 +1,108 @@ /* This file is part of KDevelop Copyright 2010 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentconfigurebutton.h" #include #include #include "settings/environmentpreferences.h" -#include -#include -#include -#include - #include #include #include #include #include -#include +#include namespace KDevelop { class EnvironmentConfigureButtonPrivate { public: EnvironmentConfigureButtonPrivate(EnvironmentConfigureButton* _q) : q(_q), selectionWidget(0) { } void showDialog() { QDialog dlg(qApp->activeWindow()); QString selected; if (selectionWidget) { selected = selectionWidget->effectiveProfileName(); } EnvironmentPreferences prefs(selected, q); // TODO: This should be implicit when constructing EnvironmentPreferences prefs.initConfigManager(); prefs.reset(); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dlg, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dlg, &QDialog::reject); auto layout = new QVBoxLayout; layout->addWidget(&prefs); layout->addWidget(buttonBox); dlg.setLayout(layout); dlg.setWindowTitle(prefs.fullName()); dlg.setWindowIcon(prefs.icon()); dlg.resize(480, 320); if (dlg.exec() == QDialog::Accepted) { prefs.apply(); emit q->environmentConfigured(); } } EnvironmentConfigureButton *q; EnvironmentSelectionWidget *selectionWidget; }; EnvironmentConfigureButton::EnvironmentConfigureButton(QWidget* parent) : QPushButton(parent), d(new EnvironmentConfigureButtonPrivate(this)) { setText(QString()); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); setIcon(QIcon::fromTheme("configure")); setToolTip(i18n("Configure environment variables")); connect(this, &EnvironmentConfigureButton::clicked, this, [&] { d->showDialog(); }); } EnvironmentConfigureButton::~EnvironmentConfigureButton() { delete d; } void EnvironmentConfigureButton::setSelectionWidget(EnvironmentSelectionWidget* widget) { connect(this, &EnvironmentConfigureButton::environmentConfigured, widget, &EnvironmentSelectionWidget::reconfigure); d->selectionWidget = widget; } } #include "moc_environmentconfigurebutton.cpp" diff --git a/shell/languagecontroller.cpp b/shell/languagecontroller.cpp index 7bc1a9bdc7..ef8686cf0c 100644 --- a/shell/languagecontroller.cpp +++ b/shell/languagecontroller.cpp @@ -1,388 +1,388 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "languagecontroller.h" #include -#include #include +#include +#include #include #include #include #include #include #include #include #include #include "core.h" #include "settings/ccpreferences.h" #include "completionsettings.h" #include "debug.h" -#include namespace { // Maximum length of a string to still consider it as a file extension which we cache // This has to be a slow value, so that we don't fill our file extension cache with crap const int maximumCacheExtensionLength = 3; const QString KEY_SupportedMimeTypes = QStringLiteral("X-KDevelop-SupportedMimeTypes"); const QString KEY_ILanguageSupport = QStringLiteral("ILanguageSupport"); } inline uint qHash(const QMimeType& mime, uint seed = 0) { return qHash(mime.name(), seed); } namespace KDevelop { typedef QHash LanguageHash; typedef QHash > LanguageCache; struct LanguageControllerPrivate { LanguageControllerPrivate(LanguageController *controller) : dataMutex(QMutex::Recursive) , backgroundParser(new BackgroundParser(controller)) , staticAssistantsManager(nullptr) , m_cleanedUp(false) , m_controller(controller) {} void documentActivated(KDevelop::IDocument *document) { QUrl url = document->url(); if (!url.isValid()) { return; } activeLanguages.clear(); QList languages = m_controller->languagesForUrl(url); foreach (auto lang, languages) { activeLanguages << lang; } } QList activeLanguages; mutable QMutex dataMutex; LanguageHash languages; //Maps language-names to languages LanguageCache languageCache; //Maps mimetype-names to languages typedef QMultiHash MimeTypeCache; MimeTypeCache mimeTypeCache; //Maps mimetypes to languages // fallback cache for file extensions not handled by any pattern typedef QMap > FileExtensionCache; FileExtensionCache fileExtensionCache; BackgroundParser *backgroundParser; StaticAssistantsManager* staticAssistantsManager; bool m_cleanedUp; void addLanguageSupport(ILanguageSupport* support, const QStringList& mimetypes); void addLanguageSupport(ILanguageSupport* support); private: LanguageController *m_controller; }; void LanguageControllerPrivate::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { Q_ASSERT(!languages.contains(languageSupport->name())); languages.insert(languageSupport->name(), languageSupport); foreach(const QString& mimeTypeName, mimetypes) { qCDebug(SHELL) << "adding supported mimetype:" << mimeTypeName << "language:" << languageSupport->name(); languageCache[mimeTypeName] << languageSupport; QMimeType mime = QMimeDatabase().mimeTypeForName(mimeTypeName); if (mime.isValid()) { mimeTypeCache.insert(mime, languageSupport); } else { qWarning() << "could not create mime-type" << mimeTypeName; } } } void LanguageControllerPrivate::addLanguageSupport(KDevelop::ILanguageSupport* languageSupport) { if (languages.contains(languageSupport->name())) return; Q_ASSERT(dynamic_cast(languageSupport)); KPluginMetaData info = Core::self()->pluginController()->pluginInfo(dynamic_cast(languageSupport)); QStringList mimetypes = KPluginMetaData::readStringList(info.rawData(), KEY_SupportedMimeTypes); addLanguageSupport(languageSupport, mimetypes); } LanguageController::LanguageController(QObject *parent) : ILanguageController(parent) { setObjectName("LanguageController"); d = new LanguageControllerPrivate(this); } LanguageController::~LanguageController() { delete d; } void LanguageController::initialize() { d->backgroundParser->loadSettings(); d->staticAssistantsManager = new StaticAssistantsManager(this); // make sure the DUChain is setup before we try to access it from different threads at the same time DUChain::self(); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, [&] (IDocument* document) { d->documentActivated(document); }); } void LanguageController::cleanup() { QMutexLocker lock(&d->dataMutex); d->m_cleanedUp = true; } QList LanguageController::activeLanguages() { QMutexLocker lock(&d->dataMutex); return d->activeLanguages; } StaticAssistantsManager* LanguageController::staticAssistantsManager() const { return d->staticAssistantsManager; } ICompletionSettings *LanguageController::completionSettings() const { return &CompletionSettings::self(); } QList LanguageController::loadedLanguages() const { QMutexLocker lock(&d->dataMutex); QList ret; if(d->m_cleanedUp) return ret; foreach(ILanguageSupport* lang, d->languages) ret << lang; return ret; } ILanguageSupport* LanguageController::language(const QString &name) const { QMutexLocker lock(&d->dataMutex); if(d->m_cleanedUp) return 0; if(d->languages.contains(name)) return d->languages[name]; QVariantMap constraints; constraints.insert("X-KDevelop-Language", name); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport, constraints); if(!supports.isEmpty()) { ILanguageSupport *languageSupport = supports[0]->extension(); if(languageSupport) { d->addLanguageSupport(languageSupport); return languageSupport; } } return nullptr; } bool isNumeric(const QString& str) { int len = str.length(); if(len == 0) return false; for(int a = 0; a < len; ++a) if(!str[a].isNumber()) return false; return true; } QList LanguageController::languagesForUrl(const QUrl &url) { QMutexLocker lock(&d->dataMutex); QList languages; if(d->m_cleanedUp) return languages; const QString fileName = url.fileName(); ///TODO: cache regexp or simple string pattern for endsWith matching QRegExp exp("", Qt::CaseInsensitive, QRegExp::Wildcard); ///non-crashy part: Use the mime-types of known languages for(LanguageControllerPrivate::MimeTypeCache::const_iterator it = d->mimeTypeCache.constBegin(); it != d->mimeTypeCache.constEnd(); ++it) { foreach(const QString& pattern, it.key().globPatterns()) { if(pattern.startsWith('*')) { const QStringRef subPattern = pattern.midRef(1); if (!subPattern.contains('*')) { //optimize: we can skip the expensive QRegExp in this case //and do a simple string compare (much faster) if (fileName.endsWith(subPattern)) { languages << *it; } continue; } } exp.setPattern(pattern); if(int position = exp.indexIn(fileName)) { if(position != -1 && exp.matchedLength() + position == fileName.length()) languages << *it; } } } if(!languages.isEmpty()) return languages; // no pattern found, try the file extension cache int extensionStart = fileName.lastIndexOf(QLatin1Char('.')); QString extension; if(extensionStart != -1) { extension = fileName.mid(extensionStart+1); if(extension.size() > maximumCacheExtensionLength || isNumeric(extension)) extension = QString(); } if(!extension.isEmpty()) { languages = d->fileExtensionCache.value(extension); if(languages.isEmpty() && d->fileExtensionCache.contains(extension)) return languages; // Nothing found, but was in the cache } //Never use findByUrl from within a background thread, and never load a language support //from within the backgruond thread. Both is unsafe, and can lead to crashes if(!languages.isEmpty() || QThread::currentThread() != thread()) return languages; QMimeType mimeType; if (url.isLocalFile()) { // If we have recognized a file extension, allow using the file-contents // to look up the type. We will cache it after all. // If we have not recognized a file extension, do not allow using the file-contents // to look up the type. We cannot cache the result, and thus we might end up reading // the contents of every single file, which can make the application very unresponsive. if (!extension.isEmpty()) { mimeType = QMimeDatabase().mimeTypeForFile(url.toLocalFile()); } else { // this will not be cached -> don't bother reading the contents mimeType = QMimeDatabase().mimeTypeForFile(url.toLocalFile(), QMimeDatabase::MatchExtension); } } else { // remote file, only look at the extension mimeType = QMimeDatabase().mimeTypeForUrl(url); } if (mimeType.isDefault()) { // ask the document controller about a more concrete mimetype IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (doc) { mimeType = doc->mimeType(); } } languages = languagesForMimetype(mimeType.name()); if(!extension.isEmpty()) { d->fileExtensionCache.insert(extension, languages); } return languages; } QList LanguageController::languagesForMimetype(const QString& mimetype) { QMutexLocker lock(&d->dataMutex); QList languages; LanguageCache::ConstIterator it = d->languageCache.constFind(mimetype); if (it != d->languageCache.constEnd()) { languages = it.value(); } else { QVariantMap constraints; constraints.insert(KEY_SupportedMimeTypes, mimetype); QList supports = Core::self()->pluginController()->allPluginsForExtension(KEY_ILanguageSupport, constraints); if (supports.isEmpty()) { qCDebug(SHELL) << "no languages for mimetype:" << mimetype; d->languageCache.insert(mimetype, QList()); } else { foreach (IPlugin *support, supports) { ILanguageSupport* languageSupport = support->extension(); qCDebug(SHELL) << "language-support:" << languageSupport; if(languageSupport) { d->addLanguageSupport(languageSupport); languages << languageSupport; } } } } return languages; } QList LanguageController::mimetypesForLanguageName(const QString& languageName) { QMutexLocker lock(&d->dataMutex); QList mimetypes; for (LanguageCache::ConstIterator iter = d->languageCache.constBegin(); iter != d->languageCache.constEnd(); ++iter) { foreach (ILanguageSupport* language, iter.value()) { if (language->name() == languageName) { mimetypes << iter.key(); break; } } } return mimetypes; } BackgroundParser *LanguageController::backgroundParser() const { return d->backgroundParser; } void LanguageController::addLanguageSupport(ILanguageSupport* languageSupport, const QStringList& mimetypes) { d->addLanguageSupport(languageSupport, mimetypes); } } #include "moc_languagecontroller.cpp" diff --git a/shell/launchconfigurationdialog.cpp b/shell/launchconfigurationdialog.cpp index 134ca9f152..dccc7cd4b6 100644 --- a/shell/launchconfigurationdialog.cpp +++ b/shell/launchconfigurationdialog.cpp @@ -1,1027 +1,1022 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "launchconfigurationdialog.h" -#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 "core.h" #include "runcontroller.h" #include "launchconfiguration.h" #include "debug.h" #include #include #include -#include -#include -#include -#include -#include -#include namespace KDevelop { bool launchConfigGreaterThan(KDevelop::LaunchConfigurationType* a, KDevelop::LaunchConfigurationType* b) { return a->name()>b->name(); } //TODO: Maybe use KPageDialog instead, might make the model stuff easier and the default-size stuff as well LaunchConfigurationDialog::LaunchConfigurationDialog(QWidget* parent) : QDialog(parent) , currentPageChanged(false) { setWindowTitle( i18n( "Launch Configurations" ) ); QWidget *mainWidget = new QWidget(this); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); setupUi(mainWidget); mainLayout->setContentsMargins( 0, 0, 0, 0 ); splitter->setSizes(QList() << 260 << 620); addConfig->setIcon( QIcon::fromTheme("list-add") ); addConfig->setEnabled( false ); addConfig->setToolTip(i18nc("@info:tooltip", "Add a new launch configuration.")); deleteConfig->setIcon( QIcon::fromTheme("list-remove") ); deleteConfig->setEnabled( false ); deleteConfig->setToolTip(i18nc("@info:tooltip", "Delete selected launch configuration.")); model = new LaunchConfigurationsModel( tree ); tree->setModel( model ); tree->setExpandsOnDoubleClick( true ); tree->setSelectionBehavior( QAbstractItemView::SelectRows ); tree->setSelectionMode( QAbstractItemView::SingleSelection ); tree->setUniformRowHeights( true ); tree->setItemDelegate( new LaunchConfigurationModelDelegate() ); tree->setColumnHidden(1, true); for(int row=0; rowrowCount(); row++) { tree->setExpanded(model->index(row, 0), true); } tree->setContextMenuPolicy(Qt::CustomContextMenu); connect( tree, &QTreeView::customContextMenuRequested, this, &LaunchConfigurationDialog::doTreeContextMenu ); connect( deleteConfig, &QToolButton::clicked, this, &LaunchConfigurationDialog::deleteConfiguration); connect( model, &LaunchConfigurationsModel::dataChanged, this, &LaunchConfigurationDialog::modelChanged ); connect( tree->selectionModel(), &QItemSelectionModel::selectionChanged, this, &LaunchConfigurationDialog::selectionChanged); QModelIndex idx = model->indexForConfig( Core::self()->runControllerInternal()->defaultLaunch() ); qCDebug(SHELL) << "selecting index:" << idx; if( !idx.isValid() ) { for( int i = 0; i < model->rowCount(); i++ ) { if( model->rowCount( model->index( i, 0, QModelIndex() ) ) > 0 ) { idx = model->index( 1, 0, model->index( i, 0, QModelIndex() ) ); break; } } if( !idx.isValid() ) { idx = model->index( 0, 0, QModelIndex() ); } } tree->selectionModel()->select( QItemSelection( idx, idx ), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( idx, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); // Unfortunately tree->resizeColumnToContents() only looks at the top-level // items, instead of all open ones. Hence we're calculating it ourselves like // this: // Take the selected index, check if it has childs, if so take the first child // Then count the level by going up, then let the tree calculate the width // for the selected or its first child index and add indentation*level // // If Qt Software ever fixes resizeColumnToContents, the following line // can be enabled and the rest be removed // tree->resizeColumnToContents( 0 ); int level = 0; QModelIndex widthidx = idx; if( model->rowCount( idx ) > 0 ) { widthidx = idx.child( 0, 0 ); } QModelIndex parentidx = widthidx.parent(); while( parentidx.isValid() ) { level++; parentidx = parentidx.parent(); } // make sure the base column width is honored, e.g. when no launch configs exist tree->resizeColumnToContents(0); int width = tree->columnWidth( 0 ); while ( widthidx.isValid() ) { width = qMax( width, level*tree->indentation() + tree->indentation() + tree->sizeHintForIndex( widthidx ).width() ); widthidx = widthidx.parent(); } tree->setColumnWidth( 0, width ); QMenu* m = new QMenu(this); QList types = Core::self()->runController()->launchConfigurationTypes(); std::sort(types.begin(), types.end(), launchConfigGreaterThan); //we want it in reverse order foreach(LaunchConfigurationType* type, types) { connect(type, &LaunchConfigurationType::signalAddLaunchConfiguration, this, &LaunchConfigurationDialog::addConfiguration); QMenu* suggestionsMenu = type->launcherSuggestions(); if(suggestionsMenu) { m->addMenu(suggestionsMenu); } } // Simplify menu structure to get rid of 1-entry levels while (m->actions().count() == 1) { QMenu* subMenu = m->actions().first()->menu(); if (subMenu && subMenu->isEnabled() && subMenu->actions().count()<5) { m = subMenu; } else { break; } } if(!m->isEmpty()) { QAction* separator = new QAction(m); separator->setSeparator(true); m->insertAction(m->actions().first(), separator); } foreach(LaunchConfigurationType* type, types) { QAction* action = new QAction(type->icon(), type->name(), m); action->setProperty("configtype", qVariantFromValue(type)); connect(action, &QAction::triggered, this, &LaunchConfigurationDialog::createEmptyLauncher); if(!m->actions().isEmpty()) m->insertAction(m->actions().first(), action); else m->addAction(action); } addConfig->setMenu(m); connect(debugger, static_cast(&QComboBox::currentIndexChanged), this, &LaunchConfigurationDialog::launchModeChanged); connect(buttonBox, &QDialogButtonBox::accepted, this, &LaunchConfigurationDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LaunchConfigurationDialog::reject); connect(buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, this, static_cast(&LaunchConfigurationDialog::saveConfig) ); connect(buttonBox->button(QDialogButtonBox::Apply), &QPushButton::clicked, this, static_cast(&LaunchConfigurationDialog::saveConfig) ); mainLayout->addWidget(buttonBox); resize( QSize(qMax(700, sizeHint().width()), qMax(500, sizeHint().height())) ); } void LaunchConfigurationDialog::doTreeContextMenu(QPoint point) { if ( ! tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex selected = tree->selectionModel()->selectedRows().first(); if ( selected.parent().isValid() && ! selected.parent().parent().isValid() ) { // only display the menu if a launch config is clicked QMenu menu; QAction* rename = new QAction(QIcon::fromTheme("edit-rename"), i18n("Rename configuration"), &menu); QAction* delete_ = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete configuration"), &menu); connect(rename, &QAction::triggered, this, &LaunchConfigurationDialog::renameSelected); connect(delete_, &QAction::triggered, this, &LaunchConfigurationDialog::deleteConfiguration); menu.addAction(rename); menu.addAction(delete_); menu.exec(tree->mapToGlobal(point)); } } } void LaunchConfigurationDialog::renameSelected() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex parent = tree->selectionModel()->selectedRows().first(); if( parent.parent().isValid() ) { parent = parent.parent(); } QModelIndex index = model->index(tree->selectionModel()->selectedRows().first().row(), 0, parent); tree->edit( index ); } } QSize LaunchConfigurationDialog::sizeHint() const { QSize s = QDialog::sizeHint(); return s.expandedTo(QSize(880, 520)); } void LaunchConfigurationDialog::createEmptyLauncher() { QAction* action = qobject_cast(sender()); Q_ASSERT(action); LaunchConfigurationType* type = qobject_cast(action->property("configtype").value()); Q_ASSERT(type); IProject* p = model->projectForIndex(tree->currentIndex()); QPair< QString, QString > launcher( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); ILaunchConfiguration* l = ICore::self()->runController()->createLaunchConfiguration(type, launcher, p); addConfiguration(l); } void LaunchConfigurationDialog::selectionChanged(QItemSelection selected, QItemSelection deselected ) { if( !deselected.indexes().isEmpty() ) { LaunchConfiguration* l = model->configForIndex( deselected.indexes().first() ); if( l ) { disconnect(l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel); if( currentPageChanged ) { if( KMessageBox::questionYesNo( this, i18n("Selected Launch Configuration has unsaved changes. Do you want to save it?"), i18n("Unsaved Changes") ) == KMessageBox::Yes ) { saveConfig( deselected.indexes().first() ); } else { LaunchConfigPagesContainer* tab = qobject_cast( stack->currentWidget() ); tab->setLaunchConfiguration( l ); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } } } updateNameLabel(0); for( int i = 1; i < stack->count(); i++ ) { QWidget* w = stack->widget(i); stack->removeWidget(w); delete w; } debugger->clear(); if( !selected.indexes().isEmpty() ) { QModelIndex idx = selected.indexes().first(); LaunchConfiguration* l = model->configForIndex( idx ); ILaunchMode* lm = model->modeForIndex( idx ); if( l ) { updateNameLabel( l ); tree->expand( model->indexForConfig( l ) ); connect( l, &LaunchConfiguration::nameChanged, this, &LaunchConfigurationDialog::updateNameLabel ); if( lm ) { bool b = debugger->blockSignals(true); QList launchers = l->type()->launchers(); for( QList::const_iterator it = launchers.constBegin(); it != launchers.constEnd(); it++ ) { if( ((*it)->supportedModes().contains( lm->id() ) ) ) { debugger->addItem( (*it)->name(), (*it)->id() ); } } debugger->blockSignals(b); debugger->setVisible(debugger->count()>0); debugLabel->setVisible(debugger->count()>0); QVariant currentLaunchMode = idx.sibling(idx.row(), 1).data(Qt::EditRole); debugger->setCurrentIndex(debugger->findData(currentLaunchMode)); ILauncher* launcher = l->type()->launcherForId( currentLaunchMode.toString() ); if( launcher ) { LaunchConfigPagesContainer* tab = launcherWidgets.value( launcher ); if(!tab) { QList pages = launcher->configPages(); if(!pages.isEmpty()) { tab = new LaunchConfigPagesContainer( launcher->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } } if(tab) { tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); } else { QLabel* label = new QLabel(i18nc("%1 is a launcher name", "No configuration is needed for '%1'", launcher->name()), stack); label->setAlignment(Qt::AlignCenter); QFont font = label->font(); font.setItalic(true); label->setFont(font); stack->addWidget(label); stack->setCurrentWidget(label); } updateNameLabel( l ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); } else { addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } else { //TODO: enable removal button LaunchConfigurationType* type = l->type(); LaunchConfigPagesContainer* tab = typeWidgets.value( type ); if( !tab ) { tab = new LaunchConfigPagesContainer( type->configPages(), stack ); connect( tab, &LaunchConfigPagesContainer::changed, this, &LaunchConfigurationDialog::pageChanged ); stack->addWidget( tab ); } qCDebug(SHELL) << "created pages, setting config up"; tab->setLaunchConfiguration( l ); stack->setCurrentWidget( tab ); addConfig->setEnabled( true ); deleteConfig->setEnabled( true ); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { addConfig->setEnabled( true ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); QLabel* l = new QLabel(i18n("Select a configuration to edit from the left,
" "or click the \"Add New\" button to add a new one.
"), stack); l->setAlignment(Qt::AlignCenter); stack->addWidget(l); stack->setCurrentWidget(l); debugger->setVisible( false ); debugLabel->setVisible( false ); } } else { debugger->setVisible( false ); debugLabel->setVisible( false ); addConfig->setEnabled( false ); deleteConfig->setEnabled( false ); stack->setCurrentIndex( 0 ); } } void LaunchConfigurationDialog::saveConfig( const QModelIndex& idx ) { Q_UNUSED( idx ); LaunchConfigPagesContainer* tab = qobject_cast( stack->currentWidget() ); if( tab ) { tab->save(); buttonBox->button(QDialogButtonBox::Apply)->setEnabled( false ); currentPageChanged = false; } } void LaunchConfigurationDialog::saveConfig() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { saveConfig( tree->selectionModel()->selectedRows().first() ); } } void LaunchConfigurationDialog::pageChanged() { currentPageChanged = true; buttonBox->button(QDialogButtonBox::Apply)->setEnabled( true ); } void LaunchConfigurationDialog::modelChanged(QModelIndex topLeft, QModelIndex bottomRight) { if (tree->selectionModel()) { QModelIndex index = tree->selectionModel()->selectedRows().first(); if (index.row() >= topLeft.row() && index.row() <= bottomRight.row() && bottomRight.column() == 1) selectionChanged(tree->selectionModel()->selection(), tree->selectionModel()->selection()); } } void LaunchConfigurationDialog::deleteConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { model->deleteConfiguration( tree->selectionModel()->selectedRows().first() ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::updateNameLabel( LaunchConfiguration* l ) { if( l ) { configName->setText( i18n("Editing %2: %1", l->name(), l->type()->name() ) ); } else { configName->clear(); } } void LaunchConfigurationDialog::createConfiguration() { if( !tree->selectionModel()->selectedRows().isEmpty() ) { QModelIndex idx = tree->selectionModel()->selectedRows().first(); if( idx.parent().isValid() ) { idx = idx.parent(); } model->createConfiguration( idx ); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } } void LaunchConfigurationDialog::addConfiguration(ILaunchConfiguration* _launch) { LaunchConfiguration* launch = dynamic_cast(_launch); Q_ASSERT(launch); int row = launch->project() ? model->findItemForProject(launch->project())->row : 0; QModelIndex idx = model->index(row, 0); model->addConfiguration(launch, idx); QModelIndex newindex = model->index( model->rowCount( idx ) - 1, 0, idx ); tree->selectionModel()->select( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->selectionModel()->setCurrentIndex( newindex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows ); tree->edit( newindex ); tree->resizeColumnToContents( 0 ); } LaunchConfigurationsModel::LaunchConfigurationsModel(QObject* parent): QAbstractItemModel(parent) { GenericPageItem* global = new GenericPageItem; global->text = i18n("Global"); global->row = 0; topItems << global; foreach( IProject* p, Core::self()->projectController()->projects() ) { ProjectItem* t = new ProjectItem; t->project = p; t->row = topItems.count(); topItems << t; } foreach( LaunchConfiguration* l, Core::self()->runControllerInternal()->launchConfigurationsInternal() ) { addItemForLaunchConfig( l ); } } void LaunchConfigurationsModel::addItemForLaunchConfig( LaunchConfiguration* l ) { LaunchItem* t = new LaunchItem; t->launch = l; TreeItem* parent; if( l->project() ) { parent = findItemForProject( l->project() ); } else { parent = topItems.at(0); } t->parent = parent; t->row = parent->children.count(); parent->children.append( t ); addLaunchModeItemsForLaunchConfig ( t ); } void LaunchConfigurationsModel::addLaunchModeItemsForLaunchConfig ( LaunchItem* t ) { QList items; QSet modes; foreach( ILauncher* launcher, t->launch->type()->launchers() ) { foreach( const QString& mode, launcher->supportedModes() ) { if( !modes.contains( mode ) && launcher->configPages().count() > 0 ) { modes.insert( mode ); LaunchModeItem* lmi = new LaunchModeItem; lmi->mode = Core::self()->runController()->launchModeForId( mode ); lmi->parent = t; lmi->row = t->children.count(); items.append( lmi ); } } } if( !items.isEmpty() ) { QModelIndex p = indexForConfig( t->launch ); beginInsertRows( p, t->children.count(), t->children.count() + items.count() - 1 ); t->children.append( items ); endInsertRows(); } } LaunchConfigurationsModel::ProjectItem* LaunchConfigurationsModel::findItemForProject( IProject* p ) { foreach( TreeItem* t, topItems ) { ProjectItem* pi = dynamic_cast( t ); if( pi && pi->project == p ) { return pi; } } Q_ASSERT(false); return 0; } int LaunchConfigurationsModel::columnCount(const QModelIndex& parent) const { Q_UNUSED( parent ); return 2; } QVariant LaunchConfigurationsModel::data(const QModelIndex& index, int role) const { if( index.isValid() && index.column() >= 0 && index.column() < 2 ) { TreeItem* t = static_cast( index.internalPointer() ); switch( role ) { case Qt::DisplayRole: { LaunchItem* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if( index.column() == 1 ) { return li->launch->type()->name(); } } ProjectItem* pi = dynamic_cast( t ); if( pi && index.column() == 0 ) { return pi->project->name(); } GenericPageItem* gpi = dynamic_cast( t ); if( gpi && index.column() == 0 ) { return gpi->text; } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi ) { if( index.column() == 0 ) { return lmi->mode->name(); } else if( index.column() == 1 ) { LaunchConfiguration* l = configForIndex( index ); return l->type()->launcherForId( l->launcherForMode( lmi->mode->id() ) )->name(); } } break; } case Qt::DecorationRole: { LaunchItem* li = dynamic_cast( t ); if( index.column() == 0 && li ) { return li->launch->type()->icon(); } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi && index.column() == 0 ) { return lmi->mode->icon(); } if ( index.column() == 0 && !index.parent().isValid() ) { if (index.row() == 0) { // global item return QIcon::fromTheme("folder"); } else { // project item return QIcon::fromTheme("folder-development"); } } } case Qt::EditRole: { LaunchItem* li = dynamic_cast( t ); if( li ) { if( index.column() == 0 ) { return li->launch->name(); } else if ( index.column() == 1 ) { return li->launch->type()->id(); } } LaunchModeItem* lmi = dynamic_cast( t ); if( lmi && index.column() == 1 ) { return configForIndex( index )->launcherForMode( lmi->mode->id() ); } break; } default: break; } } return QVariant(); } QModelIndex LaunchConfigurationsModel::index(int row, int column, const QModelIndex& parent) const { if( !hasIndex( row, column, parent ) ) return QModelIndex(); TreeItem* tree; if( !parent.isValid() ) { tree = topItems.at( row ); } else { TreeItem* t = static_cast( parent.internalPointer() ); tree = t->children.at( row ); } if( tree ) { return createIndex( row, column, tree ); } return QModelIndex(); } QModelIndex LaunchConfigurationsModel::parent(const QModelIndex& child) const { if( child.isValid() ) { TreeItem* t = static_cast( child.internalPointer() ); if( t->parent ) { return createIndex( t->parent->row, 0, t->parent ); } } return QModelIndex(); } int LaunchConfigurationsModel::rowCount(const QModelIndex& parent) const { if( parent.column() > 0 ) return 0; if( parent.isValid() ) { TreeItem* t = static_cast( parent.internalPointer() ); return t->children.count(); } else { return topItems.count(); } return 0; } QVariant LaunchConfigurationsModel::headerData(int section, Qt::Orientation orientation, int role) const { if( orientation == Qt::Horizontal && role == Qt::DisplayRole ) { if( section == 0 ) { return i18nc("Name of the Launch Configurations", "Name"); } else if( section == 1 ) { return i18nc("The type of the Launch Configurations (i.e. Python Application, C++ Application)", "Type"); } } return QVariant(); } Qt::ItemFlags LaunchConfigurationsModel::flags(const QModelIndex& index) const { if( index.isValid() && index.column() >= 0 && index.column() < columnCount( QModelIndex() ) ) { TreeItem* t = static_cast( index.internalPointer() ); if( t && ( dynamic_cast( t ) || ( dynamic_cast( t ) && index.column() == 1 ) ) ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable ); } else if( t ) { return Qt::ItemFlags( Qt::ItemIsEnabled | Qt::ItemIsSelectable ); } } return Qt::NoItemFlags; } bool LaunchConfigurationsModel::setData(const QModelIndex& index, const QVariant& value, int role) { if( index.isValid() && index.parent().isValid() && role == Qt::EditRole ) { if( index.row() >= 0 && index.row() < rowCount( index.parent() ) ) { LaunchItem* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( t ) { if( index.column() == 0 ) { t->launch->setName( value.toString() ); } else if( index.column() == 1 ) { if (t->launch->type()->id() != value.toString()) { t->launch->setType( value.toString() ); QModelIndex p = indexForConfig(t->launch); qCDebug(SHELL) << data(p); beginRemoveRows( p, 0, t->children.count() ); qDeleteAll( t->children ); t->children.clear(); endRemoveRows(); addLaunchModeItemsForLaunchConfig( t ); } } emit dataChanged(index, index); return true; } LaunchModeItem* lmi = dynamic_cast( static_cast( index.internalPointer() ) ); if( lmi ) { if( index.column() == 1 && index.data(Qt::EditRole)!=value) { LaunchConfiguration* l = configForIndex( index ); l->setLauncherForMode( lmi->mode->id(), value.toString() ); emit dataChanged(index, index); return true; } } } } return false; } ILaunchMode* LaunchConfigurationsModel::modeForIndex( const QModelIndex& idx ) const { if( idx.isValid() ) { LaunchModeItem* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->mode; } } return 0; } LaunchConfiguration* LaunchConfigurationsModel::configForIndex(const QModelIndex& idx ) const { if( idx.isValid() ) { LaunchItem* item = dynamic_cast( static_cast( idx.internalPointer() ) ); if( item ) { return item->launch; } LaunchModeItem* lmitem = dynamic_cast( static_cast( idx.internalPointer() ) ); if( lmitem ) { return dynamic_cast( lmitem->parent )->launch; } } return 0; } QModelIndex LaunchConfigurationsModel::indexForConfig( LaunchConfiguration* l ) const { if( l ) { TreeItem* tparent = topItems.at( 0 ); if( l->project() ) { foreach( TreeItem* t, topItems ) { ProjectItem* pi = dynamic_cast( t ); if( pi && pi->project == l->project() ) { tparent = t; break; } } } if( tparent ) { foreach( TreeItem* c, tparent->children ) { LaunchItem* li = dynamic_cast( c ); if( li->launch && li->launch == l ) { return index( c->row, 0, index( tparent->row, 0, QModelIndex() ) ); } } } } return QModelIndex(); } void LaunchConfigurationsModel::deleteConfiguration( const QModelIndex& index ) { LaunchItem* t = dynamic_cast( static_cast( index.internalPointer() ) ); if( !t ) return; beginRemoveRows( parent( index ), index.row(), index.row() ); t->parent->children.removeAll( t ); Core::self()->runControllerInternal()->removeLaunchConfiguration( t->launch ); endRemoveRows(); } void LaunchConfigurationsModel::createConfiguration(const QModelIndex& parent ) { if(!Core::self()->runController()->launchConfigurationTypes().isEmpty()) { TreeItem* t = static_cast( parent.internalPointer() ); ProjectItem* ti = dynamic_cast( t ); LaunchConfigurationType* type = Core::self()->runController()->launchConfigurationTypes().at(0); QPair launcher = qMakePair( type->launchers().at( 0 )->supportedModes().at(0), type->launchers().at( 0 )->id() ); IProject* p = ( ti ? ti->project : 0 ); ILaunchConfiguration* l = Core::self()->runController()->createLaunchConfiguration( type, launcher, p ); addConfiguration(l, parent); } } void LaunchConfigurationsModel::addConfiguration(ILaunchConfiguration* l, const QModelIndex& parent) { if( parent.isValid() ) { beginInsertRows( parent, rowCount( parent ), rowCount( parent ) ); addItemForLaunchConfig( dynamic_cast( l ) ); endInsertRows(); } else { delete l; Q_ASSERT(false && "could not add the configuration"); } } IProject* LaunchConfigurationsModel::projectForIndex(const QModelIndex& idx) { if(idx.parent().isValid()) { return projectForIndex(idx.parent()); } else { const ProjectItem* item = dynamic_cast(topItems[idx.row()]); return item ? item->project : 0; } } LaunchConfigPagesContainer::LaunchConfigPagesContainer( const QList& factories, QWidget* parent ) : QWidget(parent) { setLayout( new QVBoxLayout( this ) ); layout()->setContentsMargins( 0, 0, 0, 0 ); QWidget* parentwidget = this; QTabWidget* tab = 0; if( factories.count() > 1 ) { tab = new QTabWidget( this ); parentwidget = tab; layout()->addWidget( tab ); } foreach( LaunchConfigurationPageFactory* fac, factories ) { LaunchConfigurationPage* page = fac->createWidget( parentwidget ); if ( page->layout() ) { page->layout()->setContentsMargins( 0, 0, 0, 0 ); } pages.append( page ); connect( page, &LaunchConfigurationPage::changed, this, &LaunchConfigPagesContainer::changed ); if( tab ) { tab->addTab( page, page->icon(), page->title() ); } else { layout()->addWidget( page ); } } } void LaunchConfigPagesContainer::setLaunchConfiguration( KDevelop::LaunchConfiguration* l ) { config = l; foreach( LaunchConfigurationPage* p, pages ) { p->loadFromConfiguration( config->config(), config->project() ); } } void LaunchConfigPagesContainer::save() { foreach( LaunchConfigurationPage* p, pages ) { p->saveToConfiguration( config->config() ); } config->config().sync(); } QWidget* LaunchConfigurationModelDelegate::createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const { const LaunchConfigurationsModel* model = dynamic_cast( index.model() ); ILaunchMode* mode = model->modeForIndex( index ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && mode && config ) { KComboBox* box = new KComboBox( parent ); QList launchers = config->type()->launchers(); for( QList::const_iterator it = launchers.constBegin(); it != launchers.constEnd(); it++ ) { if( ((*it)->supportedModes().contains( mode->id() ) ) ) { box->addItem( (*it)->name(), (*it)->id() ); } } return box; } else if( !mode && config && index.column() == 1 ) { KComboBox* box = new KComboBox( parent ); const QList types = Core::self()->runController()->launchConfigurationTypes(); for( QList::const_iterator it = types.begin(); it != types.end(); it++ ) { box->addItem( (*it)->name(), (*it)->id() ); } return box; } return QStyledItemDelegate::createEditor ( parent, option, index ); } LaunchConfigurationModelDelegate::LaunchConfigurationModelDelegate() { } void LaunchConfigurationModelDelegate::setEditorData ( QWidget* editor, const QModelIndex& index ) const { const LaunchConfigurationsModel* model = dynamic_cast( index.model() ); LaunchConfiguration* config = model->configForIndex( index ); if( index.column() == 1 && config ) { KComboBox* box = qobject_cast( editor ); box->setCurrentIndex( box->findData( index.data( Qt::EditRole ) ) ); } else { QStyledItemDelegate::setEditorData ( editor, index ); } } void LaunchConfigurationModelDelegate::setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const { LaunchConfigurationsModel* lmodel = dynamic_cast( model ); LaunchConfiguration* config = lmodel->configForIndex( index ); if( index.column() == 1 && config ) { KComboBox* box = qobject_cast( editor ); lmodel->setData( index, box->itemData( box->currentIndex() ) ); } else { QStyledItemDelegate::setModelData ( editor, model, index ); } } void LaunchConfigurationDialog::launchModeChanged(int item) { QModelIndex index = tree->currentIndex(); if(debugger->isVisible() && item>=0) tree->model()->setData(index.sibling(index.row(), 1), debugger->itemData(item), Qt::EditRole); } } diff --git a/shell/launchconfigurationdialog.h b/shell/launchconfigurationdialog.h index d092cbecec..799c844ccc 100644 --- a/shell/launchconfigurationdialog.h +++ b/shell/launchconfigurationdialog.h @@ -1,174 +1,166 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_LAUNCHCONFIGURATIONDIALOG_H #define KDEVPLATFORM_LAUNCHCONFIGURATIONDIALOG_H -#include -#include -#include -#include - +#include #include -#include -#include +#include +#include +#include #include "ui_launchconfigurationdialog.h" -#include -class QTreeView; -class QStackedWidget; -class QToolButton; class QItemSelection; -class QTabWidget; namespace Ui { class LaunchConfigTypePage; } namespace KDevelop { class ILauncher; class LaunchConfigurationPageFactory; class ILaunchMode; class LaunchConfigurationType; class LaunchConfiguration; class LaunchConfigurationPage; class ILaunchConfiguration; class IProject; class LaunchConfigurationModelDelegate : public QStyledItemDelegate { public: LaunchConfigurationModelDelegate(); virtual QWidget* createEditor ( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const override; virtual void setEditorData ( QWidget* editor, const QModelIndex& index ) const override; virtual void setModelData ( QWidget* editor, QAbstractItemModel* model, const QModelIndex& index ) const override; }; class LaunchConfigurationsModel : public QAbstractItemModel { Q_OBJECT public: LaunchConfigurationsModel(QObject* parent = 0); virtual int columnCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; virtual QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; virtual QModelIndex parent(const QModelIndex& child) const override; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; virtual Qt::ItemFlags flags(const QModelIndex& index) const override; virtual bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; void createConfiguration( const QModelIndex& ); void deleteConfiguration( const QModelIndex& index ); LaunchConfiguration* configForIndex( const QModelIndex& ) const; ILaunchMode* modeForIndex( const QModelIndex& idx ) const; QModelIndex indexForConfig( LaunchConfiguration* ) const; void addConfiguration(KDevelop::ILaunchConfiguration* launch, const QModelIndex& idx); KDevelop::IProject* projectForIndex(const QModelIndex& idx); - + private: class TreeItem { public: TreeItem() : parent(0) {} virtual ~TreeItem() {} TreeItem* parent; int row; QList children; }; class ProjectItem : public TreeItem { public: IProject* project; }; class LaunchItem : public TreeItem { public: LaunchConfiguration* launch; }; class LaunchModeItem : public TreeItem { public: ILaunchMode* mode; }; class GenericPageItem : public TreeItem { public: QString text; }; void addItemForLaunchConfig( LaunchConfiguration* l ); void addLaunchModeItemsForLaunchConfig ( KDevelop::LaunchConfigurationsModel::LaunchItem* l ); QList topItems; - + public: ProjectItem* findItemForProject( IProject* ); }; class LaunchConfigPagesContainer : public QWidget { Q_OBJECT public: LaunchConfigPagesContainer( const QList &, QWidget* parent = 0 ); void setLaunchConfiguration( LaunchConfiguration* ); void save(); signals: void changed(); private: LaunchConfiguration* config; QList pages; }; class LaunchConfigurationDialog : public QDialog, public Ui::LaunchConfigurationDialog { Q_OBJECT public: LaunchConfigurationDialog(QWidget* parent = 0 ); virtual QSize sizeHint() const override; private slots: void deleteConfiguration(); void createConfiguration(); void addConfiguration(KDevelop::ILaunchConfiguration*); void selectionChanged(QItemSelection,QItemSelection); void modelChanged(QModelIndex,QModelIndex); void pageChanged(); void saveConfig(); void updateNameLabel( LaunchConfiguration* l ); void createEmptyLauncher(); void launchModeChanged(int index); private: void saveConfig( const QModelIndex& ); LaunchConfigurationsModel* model; QMap typeWidgets; QMap launcherWidgets; bool currentPageChanged; private slots: void doTreeContextMenu(QPoint point); void renameSelected(); }; } #endif diff --git a/shell/loadedpluginsdialog.cpp b/shell/loadedpluginsdialog.cpp index d977b6f8cb..e0e070baef 100644 --- a/shell/loadedpluginsdialog.cpp +++ b/shell/loadedpluginsdialog.cpp @@ -1,312 +1,311 @@ /************************************************************************** * Copyright 2009 Andreas Pakulat * * Copyright 2010 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "loadedpluginsdialog.h" #include +#include +#include #include -#include #include -#include +#include +#include +#include +#include +#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include #include "core.h" #include "plugincontroller.h" #define MARGIN 5 namespace { KPluginMetaData pluginInfo(KDevelop::IPlugin* plugin) { return KDevelop::Core::self()->pluginControllerInternal()->pluginInfo(plugin); }; QString displayName(KDevelop::IPlugin* plugin) { const auto name = pluginInfo(plugin).name(); return !name.isEmpty() ? name : plugin->componentName(); } bool sortPlugins(KDevelop::IPlugin* l, KDevelop::IPlugin* r) { return displayName(l) < displayName(r); } } class PluginsModel : public QAbstractListModel { public: enum ExtraRoles { DescriptionRole = Qt::UserRole+1 }; PluginsModel(QObject* parent = 0) : QAbstractListModel(parent) { m_plugins = KDevelop::Core::self()->pluginControllerInternal()->loadedPlugins(); std::sort(m_plugins.begin(), m_plugins.end(), sortPlugins); } KDevelop::IPlugin *pluginForIndex(const QModelIndex& index) const { if (!index.isValid()) return 0; if (index.parent().isValid()) return 0; if (index.column() != 0) return 0; if (index.row() >= m_plugins.count()) return 0; return m_plugins[index.row()]; } virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override { KDevelop::IPlugin* plugin = pluginForIndex(index); if (!plugin) return QVariant(); switch (role) { case Qt::DisplayRole: return displayName(plugin); case DescriptionRole: return pluginInfo(plugin).description(); case Qt::DecorationRole: return pluginInfo(plugin).iconName(); default: return QVariant(); }; } virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override { if (!parent.isValid()) { return m_plugins.count(); } return 0; } private: QList m_plugins; }; class LoadedPluginsDelegate : public KWidgetItemDelegate { Q_OBJECT public: LoadedPluginsDelegate(QAbstractItemView *itemView, QObject *parent = 0) : KWidgetItemDelegate(itemView, parent) , pushButton(new QPushButton) { pushButton->setIcon(QIcon::fromTheme("dialog-information")); // only for getting size matters } ~LoadedPluginsDelegate() { delete pushButton; } virtual QList createItemWidgets(const QModelIndex &/*index*/) const override { return QList(); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { int i = 5; int j = 1; QFont font = titleFont(option.font); QFontMetrics fmTitle(font); return QSize(qMax(fmTitle.width(index.model()->data(index, Qt::DisplayRole).toString()), option.fontMetrics.width(index.model()->data(index, PluginsModel::DescriptionRole).toString())) + KIconLoader::SizeMedium + MARGIN * i + pushButton->sizeHint().width() * j, qMax(KIconLoader::SizeMedium + MARGIN * 2, fmTitle.height() + option.fontMetrics.height() + MARGIN * 2)); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { if (!index.isValid()) { return; } painter->save(); QApplication::style()->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, 0); int iconSize = option.rect.height() - MARGIN * 2; QPixmap pixmap = KIconLoader::global()->loadIcon(index.model()->data(index, Qt::DecorationRole).toString(), KIconLoader::Desktop, iconSize, KIconLoader::DefaultState); painter->drawPixmap(QRect(dependantLayoutValue(MARGIN + option.rect.left(), iconSize, option.rect.width()), MARGIN + option.rect.top(), iconSize, iconSize), pixmap, QRect(0, 0, iconSize, iconSize)); QRect contentsRect(dependantLayoutValue(MARGIN * 2 + iconSize + option.rect.left(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.width()), MARGIN + option.rect.top(), option.rect.width() - MARGIN * 3 - iconSize, option.rect.height() - MARGIN * 2); int lessHorizontalSpace = MARGIN * 2 + pushButton->sizeHint().width(); contentsRect.setWidth(contentsRect.width() - lessHorizontalSpace); if (option.state & QStyle::State_Selected) { painter->setPen(option.palette.highlightedText().color()); } if (itemView()->layoutDirection() == Qt::RightToLeft) { contentsRect.translate(lessHorizontalSpace, 0); } painter->save(); painter->save(); QFont font = titleFont(option.font); QFontMetrics fmTitle(font); painter->setFont(font); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignTop, fmTitle.elidedText(index.model()->data(index, Qt::DisplayRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->drawText(contentsRect, Qt::AlignLeft | Qt::AlignBottom, option.fontMetrics.elidedText(index.model()->data(index, PluginsModel::DescriptionRole).toString(), Qt::ElideRight, contentsRect.width())); painter->restore(); painter->restore(); } QList createItemWidgets() const { QPushButton *button = new QPushButton(); button->setIcon(QIcon::fromTheme("dialog-information")); setBlockedEventTypes(button, QList() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick); connect(button, &QPushButton::clicked, this, &LoadedPluginsDelegate::info); return QList() << button; } void updateItemWidgets(const QList widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const override { Q_UNUSED(index); if ( widgets.isEmpty() ) { qDebug() << "Fixme: missing button?"; return; } QPushButton *aboutPushButton = static_cast(widgets[0]); QSize aboutPushButtonSizeHint = aboutPushButton->sizeHint(); aboutPushButton->resize(aboutPushButtonSizeHint); aboutPushButton->move(dependantLayoutValue(option.rect.width() - MARGIN - aboutPushButtonSizeHint.width(), aboutPushButtonSizeHint.width(), option.rect.width()), option.rect.height() / 2 - aboutPushButtonSizeHint.height() / 2); } int dependantLayoutValue(int value, int width, int totalWidth) const { if (itemView()->layoutDirection() == Qt::LeftToRight) { return value; } return totalWidth - width - value; } QFont titleFont(const QFont &baseFont) const { QFont retFont(baseFont); retFont.setBold(true); return retFont; } private Q_SLOTS: void info() { PluginsModel *m = static_cast(itemView()->model()); KDevelop::IPlugin *p = m->pluginForIndex(focusedIndex()); if (p) { // TODO KF5: Port // const K4AboutData *aboutData = p->componentData().aboutData(); // if (!aboutData->programName().isEmpty()) { // Be sure the about data is not completely empty // KAboutApplicationDialog aboutPlugin(aboutData, itemView()); // aboutPlugin.exec(); // return; // } } } private: QPushButton *pushButton; }; class PluginsView : public QListView { public: PluginsView(QWidget* parent = 0) :QListView(parent) { setModel(new PluginsModel()); setItemDelegate(new LoadedPluginsDelegate(this)); setVerticalScrollMode(QListView::ScrollPerPixel); } virtual ~PluginsView() { // explicitly delete the delegate here since otherwise // we get spammed by warnings that the QPushButton we return // in createItemWidgets is deleted before the delegate // *sigh* - even dfaure says KWidgetItemDelegate is a crude hack delete itemDelegate(); } virtual QSize sizeHint() const override { QSize ret = QListView::sizeHint(); ret.setWidth(qMax(ret.width(), sizeHintForColumn(0) + 30)); return ret; } }; LoadedPluginsDialog::LoadedPluginsDialog( QWidget* parent ) : QDialog( parent ) { setWindowTitle(i18n("Loaded Plugins")); QVBoxLayout* vbox = new QVBoxLayout(this); KTitleWidget* title = new KTitleWidget(this); title->setPixmap(QIcon::fromTheme(KAboutData::applicationData().programIconName()), KTitleWidget::ImageLeft); title->setText(i18n("Plugins loaded for %1", KAboutData::applicationData().displayName())); vbox->addWidget(title); vbox->addWidget(new PluginsView()); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &LoadedPluginsDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &LoadedPluginsDialog::reject); buttonBox->button(QDialogButtonBox::Close)->setDefault(true); vbox->addWidget(buttonBox); } #include "moc_loadedpluginsdialog.cpp" #include "loadedpluginsdialog.moc" diff --git a/shell/mainwindow.cpp b/shell/mainwindow.cpp index 8757942cc5..841f5f28be 100644 --- a/shell/mainwindow.cpp +++ b/shell/mainwindow.cpp @@ -1,387 +1,383 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include "mainwindow_p.h" -#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 #include #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "uicontroller.h" #include "documentcontroller.h" #include "debugcontroller.h" #include "workingsetcontroller.h" #include "sessioncontroller.h" #include "sourceformattercontroller.h" #include "areadisplay.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { void MainWindow::applyMainWindowSettings(const KConfigGroup& config) { if(!d->changingActiveView()) KXmlGuiWindow::applyMainWindowSettings(config); } MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags ) : Sublime::MainWindow( parent, flags ) { QDBusConnection::sessionBus().registerObject( "/kdevelop/MainWindow", this, QDBusConnection::ExportScriptableSlots ); setAcceptDrops( true ); KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); qCDebug(SHELL) << "Bottom Left:" << bottomleft; qCDebug(SHELL) << "Bottom Right:" << bottomright; // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) if( bottomleft == 0 ) setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); else if( bottomleft == 1 ) setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); if( bottomright == 0 ) setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); else if( bottomright == 1 ) setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); setObjectName( "MainWindow" ); d = new MainWindowPrivate(this); setStandardToolBarMenuEnabled( true ); d->setupActions(); if( !ShellExtension::getInstance()->xmlFile().isEmpty() ) { setXMLFile( ShellExtension::getInstance() ->xmlFile() ); } menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner); } MainWindow::~ MainWindow() { Core::self()->uiControllerInternal()->mainWindowDeleted(this); if (memberList().count() == 1) { // We're closing down... Core::self()->shutdown(); } delete d; } void MainWindow::ensureVisible() { if (isMinimized()) { if (isMaximized()) { showMaximized(); } else { showNormal(); } } KWindowSystem::forceActiveWindow(winId()); } QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element) { QAction* before = 0L; if (index > 0 && index < parent->actions().count()) before = parent->actions().at(index); //KDevelop needs to ensure that separators defined as //are always shown in the menubar. For those, we create special disabled actions //instead of calling QMenuBar::addSeparator() because menubar separators are ignored if (element.tagName().toLower() == QLatin1String("separator") && element.attribute("style") == QLatin1String("visible")) { if ( QMenuBar* bar = qobject_cast( parent ) ) { QAction *separatorAction = new QAction("|", this); bar->insertAction( before, separatorAction ); separatorAction->setDisabled(true); return separatorAction; } } return KXMLGUIBuilder::createCustomElement(parent, index, element); } void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) { if( ev->mimeData()->hasFormat( "text/uri-list" ) && ev->mimeData()->hasUrls() ) { ev->acceptProposedAction(); } } void MainWindow::dropEvent( QDropEvent* ev ) { Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); if(dropToView) activateView(dropToView); foreach( const QUrl& u, ev->mimeData()->urls() ) { Core::self()->documentController()->openDocument( u ); } ev->acceptProposedAction(); } void MainWindow::loadSettings() { qCDebug(SHELL) << "Loading Settings"; KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); // dock widget corner layout int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); qCDebug(SHELL) << "Bottom Left:" << bottomleft; qCDebug(SHELL) << "Bottom Right:" << bottomright; // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) if( bottomleft == 0 ) setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); else if( bottomleft == 1 ) setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); if( bottomright == 0 ) setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); else if( bottomright == 1 ) setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); Sublime::MainWindow::loadSettings(); } void MainWindow::saveSettings() { Sublime::MainWindow::saveSettings(); } void MainWindow::configureShortcuts() { ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated ///settings into the other windows // We need to bring up the shortcut dialog ourself instead of // Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts(); // so we can connect to the saved() signal to propagate changes in the editor shortcuts KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); foreach (KXMLGUIClient *client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { if(client && !client->xmlFile().isEmpty()) dlg.addCollection( client->actionCollection() ); } connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged); dlg.configure(true); QMap shortcuts; foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { if(!action->objectName().isEmpty()) { shortcuts[action->objectName()] = action->shortcut(); } } } for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) { foreach(KXMLGUIClient* client, Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients()) { foreach(QAction* action, client->actionCollection()->actions()) { qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName(); if(shortcuts.contains(action->objectName())) { action->setShortcut(shortcuts[action->objectName()]); } } } } } void MainWindow::shortcutsChanged() { KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView(); if(!activeClient) return; foreach(IDocument * doc, Core::self()->documentController()->openDocuments()) { KTextEditor::Document *textDocument = doc->textDocument(); if (textDocument) { foreach(KTextEditor::View *client, textDocument->views()) { if (client != activeClient) { client->reloadXML(); } } } } } void MainWindow::initialize() { KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection()); setupGUI( KXmlGuiWindow::ToolBar | KXmlGuiWindow::Create | KXmlGuiWindow::Save ); Core::self()->partController()->addManagedTopLevelWidget(this); qCDebug(SHELL) << "Adding plugin-added connection"; connect( Core::self()->pluginController(), &IPluginController::pluginLoaded, d, &MainWindowPrivate::addPlugin); connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded, d, &MainWindowPrivate::removePlugin); connect( Core::self()->partController(), &IPartController::activePartChanged, d, &MainWindowPrivate::activePartChanged); connect( this, &MainWindow::activeViewChanged, d, &MainWindowPrivate::changeActiveView); foreach(IPlugin* plugin, Core::self()->pluginController()->loadedPlugins()) d->addPlugin(plugin); guiFactory()->addClient(Core::self()->sessionController()); guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't // seem to remember which actions where plugged in. Core::self()->sessionController()->plugActions(); d->setupGui(); //Queued so we process it with some delay, to make sure the rest of the UI has already adapted connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption); updateCaption(); } void MainWindow::cleanup() { } void MainWindow::setVisible( bool visible ) { KXmlGuiWindow::setVisible( visible ); emit finishedLoading(); } bool MainWindow::queryClose() { if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default)) return false; return Sublime::MainWindow::queryClose(); } void MainWindow::updateCaption() { QString title = Core::self()->sessionController()->activeSession()->description(); if(area()->activeView()) { if(!title.isEmpty()) title += " - [ "; Sublime::Document* doc = area()->activeView()->document(); Sublime::UrlDocument* urlDoc = dynamic_cast(doc); if(urlDoc) title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain); else title += doc->title(); title += " ]"; } setCaption(title); } void MainWindow::registerStatus(QObject* status) { d->registerStatus(status); } void MainWindow::initializeStatusBar() { d->setupStatusBar(); } void MainWindow::showErrorMessage(const QString& message, int timeout) { d->showErrorMessage(message, timeout); } void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { Sublime::MainWindow::tabContextMenuRequested(view, menu); d->tabContextMenuRequested(view, menu); } void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { d->tabToolTipRequested(view, container, tab); } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { d->dockBarContextMenuRequested(area, position); } void MainWindow::newTabRequested() { Sublime::MainWindow::newTabRequested(); d->fileNew(); } } diff --git a/shell/mainwindow_actions.cpp b/shell/mainwindow_actions.cpp index 8196afa85f..296e0b4d9e 100644 --- a/shell/mainwindow_actions.cpp +++ b/shell/mainwindow_actions.cpp @@ -1,252 +1,254 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include -#include -#include -#include -#include +#include + +#include +#include +#include +#include +#include #include #include #include "core.h" #include "documentcontroller.h" #include "mainwindow_p.h" #include "sessiondialog.h" #include "uicontroller.h" #include "mainwindow.h" #include "loadedpluginsdialog.h" #include #include namespace KDevelop { // merge the gotoNext and gotoPrev code, to prevent copy/paste errors static void gotoPrevNextWindow(bool next) { UiController* ui = Core::self()->uiControllerInternal(); if( !ui->activeSublimeWindow() ) return; Sublime::Area* activeArea = ui->activeArea(); if (!activeArea) return; Sublime::View* activeView = ui->activeSublimeWindow()->activeView(); Sublime::AreaIndex* index = activeArea->indexOf(activeView); if (!index) return; int viewIndex = index->views().indexOf(activeView); viewIndex = next ? viewIndex + 1 : viewIndex -1; if (viewIndex < 0) viewIndex = index->views().count() - 1; else if (viewIndex >= index->views().count()) viewIndex = 0; if (viewIndex >= 0 && viewIndex < index->views().count()) ui->activeSublimeWindow()->activateView(index->views().at(viewIndex)); } void MainWindowPrivate::gotoNextWindow() { gotoPrevNextWindow(true); } void MainWindowPrivate::gotoPreviousWindow() { gotoPrevNextWindow(false); } void MainWindowPrivate::selectPrevItem() { auto actionListener = qobject_cast( Core::self()->uiControllerInternal()->activeToolViewActionListener()); if (actionListener) { actionListener->selectPreviousItem(); } } void MainWindowPrivate::selectNextItem() { auto actionListener = qobject_cast( Core::self()->uiControllerInternal()->activeToolViewActionListener()); if (actionListener) { actionListener->selectNextItem(); } } void MainWindowPrivate::newToolbarConfig() { m_mainWindow->applyMainWindowSettings( KConfigGroup(KSharedConfig::openConfig(), "MainWindow") ); } void MainWindowPrivate::settingsDialog() { Core::self()->uiControllerInternal()->showSettingsDialog(); } void MainWindowPrivate::newWindow() { Core::self()->uiController()->switchToArea(m_mainWindow->area()->objectName(), UiController::NewWindow); } void MainWindowPrivate::splitHorizontal() { split(Qt::Vertical); } void MainWindowPrivate::splitVertical() { split(Qt::Horizontal); } void MainWindowPrivate::split(Qt::Orientation orientation) { if (!m_mainWindow->area()) return; Sublime::View *view = m_mainWindow->activeView(); if (!view) return; Sublime::View *newView = view->document()->createView(); m_mainWindow->area()->addView(newView, view, orientation); m_mainWindow->activateView(newView); } static void gotoPrevNextSplit(bool next) { UiController* ui = Core::self()->uiControllerInternal(); if( !ui->activeSublimeWindow() ) return; Sublime::Area* area = ui->activeSublimeWindow()->area(); if (!area) return; QList topViews = ui->activeSublimeWindow()->getTopViews(); Sublime::View *activeView = ui->activeSublimeWindow()->activeView(); if (!activeView) return; int viewIndex = topViews.indexOf(activeView); viewIndex = next ? viewIndex + 1 : viewIndex -1; if (viewIndex < 0) viewIndex = topViews.count() - 1; else if (viewIndex >= topViews.count()) viewIndex = 0; if (viewIndex >= 0 && viewIndex < topViews.count()) ui->activeSublimeWindow()->activateView(topViews.at(viewIndex)); } void MainWindowPrivate::gotoNextSplit() { gotoPrevNextSplit(true); } void MainWindowPrivate::gotoPreviousSplit() { gotoPrevNextSplit(false); } void MainWindowPrivate::toggleFullScreen(bool fullScreen) { KToggleFullScreenAction::setFullScreen( m_mainWindow, fullScreen ); } void MainWindowPrivate::fileNew() { Core::self()->documentControllerInternal()->openDocument(DocumentController::nextEmptyDocumentUrl()); } void MainWindowPrivate::viewAddNewToolView() { Core::self()->uiControllerInternal()->selectNewToolViewToAdd(m_mainWindow); } void MainWindowPrivate::quitAll() { s_quitRequested = true; QApplication::closeAllWindows(); //if (Core::self()->documentController()->saveAllDocuments(IDocument::Default)) // return qApp->exit(); s_quitRequested = false; } void MainWindowPrivate::configureNotifications() { KNotifyConfigWidget::configure(m_mainWindow); } void MainWindowPrivate::showAboutPlatform() { KAboutApplicationDialog dlg(Core::self()->aboutData(), m_mainWindow ); dlg.exec(); } void MainWindowPrivate::showLoadedPlugins() { LoadedPluginsDialog dlg(m_mainWindow); dlg.exec(); } void MainWindowPrivate::contextMenuFileNew() { m_mainWindow->activateView(m_tabView); fileNew(); } void MainWindowPrivate::contextMenuSplitHorizontal() { m_mainWindow->activateView(m_tabView); splitHorizontal(); } void MainWindowPrivate::contextMenuSplitVertical() { m_mainWindow->activateView(m_tabView); splitVertical(); } void MainWindowPrivate::reloadAll() { foreach ( IDocument* doc, Core::self()->documentController()->openDocuments() ) { doc->reload(); } } } diff --git a/shell/mainwindow_p.cpp b/shell/mainwindow_p.cpp index 516bc01ac9..91b3f00c04 100644 --- a/shell/mainwindow_p.cpp +++ b/shell/mainwindow_p.cpp @@ -1,458 +1,455 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow_p.h" #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 #include #include "core.h" #include "partdocument.h" #include "partcontroller.h" #include "uicontroller.h" #include "statusbar.h" #include "mainwindow.h" #include "textdocument.h" #include "sessioncontroller.h" #include "debug.h" #include #include #include #include #include namespace KDevelop { bool MainWindowPrivate::s_quitRequested = false; MainWindowPrivate::MainWindowPrivate(MainWindow *mainWindow) : m_mainWindow(mainWindow), m_statusBar(0), lastXMLGUIClientView(0), m_changingActiveView(false) { } void MainWindowPrivate::setupGui() { m_statusBar = new KDevelop::StatusBar(m_mainWindow); setupStatusBar(); } void MainWindowPrivate::setupStatusBar() { QWidget *location = m_mainWindow->statusBarLocation(); if (m_statusBar) location->layout()->addWidget(m_statusBar); } void MainWindowPrivate::addPlugin( IPlugin *plugin ) { qCDebug(SHELL) << "add plugin" << plugin << plugin->componentName(); Q_ASSERT( plugin ); //The direct plugin client can only be added to the first mainwindow if(m_mainWindow == Core::self()->uiControllerInternal()->mainWindows()[0]) m_mainWindow->guiFactory()->addClient( plugin ); Q_ASSERT(!m_pluginCustomClients.contains(plugin)); KXMLGUIClient* ownClient = plugin->createGUIForMainWindow(m_mainWindow); if(ownClient) { m_pluginCustomClients[plugin] = ownClient; connect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); m_mainWindow->guiFactory()->addClient(ownClient); } } void MainWindowPrivate::pluginDestroyed(QObject* pluginObj) { IPlugin* plugin = static_cast(pluginObj); Q_ASSERT(m_pluginCustomClients.contains(plugin)); m_mainWindow->guiFactory()->removeClient( m_pluginCustomClients[plugin] ); delete m_pluginCustomClients[plugin]; m_pluginCustomClients.remove(plugin); } MainWindowPrivate::~MainWindowPrivate() { foreach(KXMLGUIClient* client, m_pluginCustomClients.values()) delete client; } void MainWindowPrivate::removePlugin( IPlugin *plugin ) { Q_ASSERT( plugin ); if(m_pluginCustomClients.contains(plugin)) { m_mainWindow->guiFactory()->removeClient( m_pluginCustomClients[plugin] ); delete m_pluginCustomClients[plugin]; m_pluginCustomClients.remove(plugin); disconnect(plugin, &IPlugin::destroyed, this, &MainWindowPrivate::pluginDestroyed); } m_mainWindow->guiFactory()->removeClient( plugin ); } void MainWindowPrivate::activePartChanged(KParts::Part *part) { if ( Core::self()->uiController()->activeMainWindow() == m_mainWindow) m_mainWindow->createGUI(part); } void MainWindowPrivate::changeActiveView(Sublime::View *view) { //disable updates on a window to avoid toolbar flickering on xmlgui client change Sublime::HoldUpdates s(m_mainWindow); mergeView(view); if(!view) return; IDocument *doc = dynamic_cast(view->document()); if (doc) { doc->activate(view, m_mainWindow); } else { //activated view is not a part document so we need to remove active part gui ///@todo adymo: only this window needs to remove GUI // KParts::Part *activePart = Core::self()->partController()->activePart(); // if (activePart) // guiFactory()->removeClient(activePart); } } void MainWindowPrivate::mergeView(Sublime::View* view) { PushPositiveValue block(m_changingActiveView, true); // If the previous view was KXMLGUIClient, remove its actions // In the case that that view was removed, lastActiveView // will auto-reset, and xmlguifactory will disconnect that // client, I think. if (lastXMLGUIClientView) { qCDebug(SHELL) << "clearing last XML GUI client" << lastXMLGUIClientView; m_mainWindow->guiFactory()->removeClient(dynamic_cast(lastXMLGUIClientView)); disconnect (lastXMLGUIClientView, &QWidget::destroyed, this, 0); lastXMLGUIClientView = NULL; } if (!view) return; QWidget* viewWidget = view->widget(); Q_ASSERT(viewWidget); qCDebug(SHELL) << "changing active view to" << view << "doc" << view->document() << "mw" << m_mainWindow; // If the new view is KXMLGUIClient, add it. if (KXMLGUIClient* c = dynamic_cast(viewWidget)) { qCDebug(SHELL) << "setting new XMLGUI client" << viewWidget; lastXMLGUIClientView = viewWidget; m_mainWindow->guiFactory()->addClient(c); connect(viewWidget, &QWidget::destroyed, this, &MainWindowPrivate::xmlguiclientDestroyed); } } void MainWindowPrivate::xmlguiclientDestroyed(QObject* obj) { /* We're informed the QWidget for the active view that is also KXMLGUIclient is dying. KXMLGUIFactory will not like deleted clients, really. Unfortunately, there's nothing we can do at this point. For example, KateView derives from QWidget and KXMLGUIClient. The destroyed() signal is emitted by ~QWidget. At this point, event attempt to cross-cast to KXMLGUIClient is undefined behaviour. We hope to catch view deletion a bit later, but if we fail, we better report it now, rather than get a weird crash a bit later. */ Q_ASSERT(obj == lastXMLGUIClientView); Q_ASSERT(false && "xmlgui clients management is messed up"); Q_UNUSED(obj); } void MainWindowPrivate::setupActions() { connect(Core::self()->sessionController(), &SessionController::quitSession, this, &MainWindowPrivate::quitAll); QAction* action; QString app = qApp->applicationName(); QString text = i18nc( "%1 = application name", "Configure %1", app ); action = KStandardAction::preferences( this, SLOT(settingsDialog()), actionCollection()); action->setToolTip( text ); action->setWhatsThis( i18n( "Lets you customize %1.", app ) ); action = KStandardAction::configureNotifications(this, SLOT(configureNotifications()), actionCollection()); action->setText( i18n("Configure Notifications...") ); action->setToolTip( i18nc("@info:tooltip", "Configure notifications") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog that lets you configure notifications." ) ); action = actionCollection()->addAction( "about_platform", this, SLOT(showAboutPlatform()) ); action->setText( i18n("About KDevelop Platform") ); action->setStatusTip( i18n("Show Information about KDevelop Platform") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about KDevelop Platform." ) ); action = actionCollection()->addAction( "loaded_plugins", this, SLOT(showLoadedPlugins()) ); action->setText( i18n("Loaded Plugins") ); action->setStatusTip( i18n("Show a list of all loaded plugins") ); action->setWhatsThis( i18nc( "@info:whatsthis", "Shows a dialog with information about all loaded plugins." ) ); action = actionCollection()->addAction( "view_next_window" ); action->setText( i18n( "&Next Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Right ); action->setToolTip( i18nc( "@info:tooltip", "Next window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next window." ) ); action->setIcon(QIcon::fromTheme("go-next")); action = actionCollection()->addAction( "view_previous_window" ); action->setText( i18n( "&Previous Window" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousWindow ); actionCollection()->setDefaultShortcut(action, Qt::ALT + Qt::SHIFT + Qt::Key_Left ); action->setToolTip( i18nc( "@info:tooltip", "Previous window" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous window." ) ); action->setIcon(QIcon::fromTheme("go-previous")); action = actionCollection()->addAction("next_error"); action->setText(i18n("Jump to Next Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::Key_F4) ); action->setIcon(QIcon::fromTheme("arrow-right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextItem); action = actionCollection()->addAction("prev_error"); action->setText(i18n("Jump to Previous Outputmark")); actionCollection()->setDefaultShortcut( action, QKeySequence(Qt::SHIFT | Qt::Key_F4) ); action->setIcon(QIcon::fromTheme("arrow-left")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPrevItem); action = actionCollection()->addAction( "split_horizontal" ); action->setIcon(QIcon::fromTheme( "view-split-top-bottom" )); action->setText( i18n( "Split View &Top/Bottom" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_T ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitHorizontal ); action->setToolTip( i18nc( "@info:tooltip", "Split horizontal" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view horizontally." ) ); action = actionCollection()->addAction( "split_vertical" ); action->setIcon(QIcon::fromTheme( "view-split-left-right" )); action->setText( i18n( "Split View &Left/Right" ) ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_L ); connect( action, &QAction::triggered, this, &MainWindowPrivate::splitVertical ); action->setToolTip( i18nc( "@info:tooltip", "Split vertical" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Splits the current view vertically." ) ); action = actionCollection()->addAction( "view_next_split" ); action->setText( i18n( "&Next Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoNextSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_N ); action->setToolTip( i18nc( "@info:tooltip", "Next split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the next split view." ) ); action->setIcon(QIcon::fromTheme("go-next")); action = actionCollection()->addAction( "view_previous_split" ); action->setText( i18n( "&Previous Split View" ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::gotoPreviousSplit ); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_P ); action->setToolTip( i18nc( "@info:tooltip", "Previous split view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Switches to the previous split view." ) ); action->setIcon(QIcon::fromTheme("go-previous")); action = KStandardAction::fullScreen( this, SLOT(toggleFullScreen(bool)), m_mainWindow, actionCollection() ); action = actionCollection()->addAction( "file_new" ); action->setIcon(QIcon::fromTheme("document-new")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::Key_N ); action->setText( i18n( "&New" ) ); action->setIconText( i18nc( "Shorter Text for 'New File' shown in the toolbar", "New") ); connect( action, &QAction::triggered, this, &MainWindowPrivate::fileNew ); action->setToolTip( i18nc( "@info:tooltip", "New file" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Creates an empty file." ) ); action = actionCollection()->addAction( "add_toolview" ); action->setIcon(QIcon::fromTheme("window-new")); actionCollection()->setDefaultShortcut(action, Qt::CTRL + Qt::SHIFT + Qt::Key_V ); action->setText( i18n( "&Add Tool View..." ) ); connect( action, &QAction::triggered, this, &MainWindowPrivate::viewAddNewToolView ); action->setToolTip( i18nc( "@info:tooltip", "Add tool view" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Adds a new tool view to this window." ) ); } void MainWindowPrivate::toggleArea(bool b) { if (!b) return; QAction* action = qobject_cast(sender()); if (!action) return; m_mainWindow->controller()->showArea(action->data().toString(), m_mainWindow); } KActionCollection * MainWindowPrivate::actionCollection() { return m_mainWindow->actionCollection(); } bool MainWindowPrivate::applicationQuitRequested() const { return s_quitRequested; } void MainWindowPrivate::registerStatus(QObject* status) { m_statusBar->registerStatus(status); } void MainWindowPrivate::showErrorMessage(QString message, int timeout) { m_statusBar->showErrorMessage(message, timeout); } void MainWindowPrivate::tabContextMenuRequested(Sublime::View* view, QMenu* menu) { m_tabView = view; QAction* action; action = menu->addAction(QIcon::fromTheme("view-split-top-bottom"), i18n("Split View Top/Bottom")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitHorizontal); action = menu->addAction(QIcon::fromTheme("view-split-left-right"), i18n("Split View Left/Right")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuSplitVertical); menu->addSeparator(); action = menu->addAction(QIcon::fromTheme("document-new"), i18n("New File")); connect(action, &QAction::triggered, this, &MainWindowPrivate::contextMenuFileNew); if (view) { if (TextDocument* doc = dynamic_cast(view->document())) { action = menu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload")); connect(action, &QAction::triggered, doc, &TextDocument::reload); action = menu->addAction(QIcon::fromTheme("view-refresh"), i18n("Reload All")); connect(action, &QAction::triggered, this, &MainWindowPrivate::reloadAll); } } } void MainWindowPrivate::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) { if (m_tabTooltip.second) { if (m_tabTooltip.first == view) { // tooltip already shown, don't do anything. prevents flicker when moving mouse over same tab return; } else { m_tabTooltip.second.data()->close(); } } DUChainReadLocker lock; Sublime::UrlDocument* urlDoc = dynamic_cast(view->document()); if (urlDoc) { TopDUContext* top = DUChainUtils::standardContextForUrl(urlDoc->url()); if (top) { if ( QWidget* navigationWidget = top->createNavigationWidget() ) { NavigationToolTip* tooltip = new KDevelop::NavigationToolTip(m_mainWindow, QCursor::pos() + QPoint(20, 20), navigationWidget); tooltip->resize(navigationWidget->sizeHint() + QSize(10, 10)); tooltip->addExtendRect(container->tabRect(tab)); m_tabTooltip.first = view; m_tabTooltip.second = tooltip; ActiveToolTip::showToolTip(m_tabTooltip.second.data()); } } } } void MainWindowPrivate::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) { QMenu menu; menu.addSection(QIcon::fromTheme("window-new"), i18n("Add Tool View")); QMap factories = Core::self()->uiControllerInternal()->factoryDocuments(); QMap actionToFactory; if ( !factories.isEmpty() ) { // sorted actions QMap actionMap; for (QMap::const_iterator it = factories.constBegin(); it != factories.constEnd(); ++it) { QAction* action = new QAction(it.value()->statusIcon(), it.value()->title(), &menu); action->setIcon(it.value()->statusIcon()); if (!it.key()->allowMultiple() && Core::self()->uiControllerInternal()->toolViewPresent(it.value(), m_mainWindow->area())) { action->setDisabled(true); } actionToFactory.insert(action, it.key()); actionMap[action->text()] = action; } menu.addActions(actionMap.values()); } QAction* triggered = menu.exec(position); if ( !triggered ) { return; } Core::self()->uiControllerInternal()->addToolViewToDockArea( actionToFactory[triggered], area ); } bool MainWindowPrivate::changingActiveView() const { return m_changingActiveView; } } #include "mainwindow_actions.cpp" diff --git a/shell/mainwindow_p.h b/shell/mainwindow_p.h index 9cc4b235c2..b4f0ba06bb 100644 --- a/shell/mainwindow_p.h +++ b/shell/mainwindow_p.h @@ -1,150 +1,152 @@ /* This file is part of the KDevelop project Copyright 2002 Falk Brettschneider Copyright 2003 John Firebaugh Copyright 2006 Adam Treat Copyright 2006, 2007 Alexander Dymo This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_MAINWINDOW_PRIVATE_H #define KDEVPLATFORM_MAINWINDOW_PRIVATE_H #include #include -#include -#include +#include + +#include + #include class KActionCollection; class QMenu; namespace Sublime { class View; class Container; } namespace KParts { class Part; } namespace KTextEditor { class View; } namespace KDevelop { class IPlugin; class MainWindow; class StatusBar; class MainWindowPrivate: public QObject { Q_OBJECT public: MainWindowPrivate(MainWindow *mainWindow); ~MainWindowPrivate(); QPointer centralPlugin; void setupActions(); void setupGui(); void setupStatusBar(); void registerStatus(QObject*); void tabContextMenuRequested(Sublime::View *view, QMenu* menu); void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab); void dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position); public Q_SLOTS: void addPlugin( KDevelop::IPlugin *plugin ); void removePlugin( KDevelop::IPlugin *plugin ); void activePartChanged(KParts::Part *part); void mergeView(Sublime::View *view); void changeActiveView(Sublime::View *view); void xmlguiclientDestroyed(QObject* obj); //actions void fileNew(); void gotoNextWindow(); void gotoPreviousWindow(); void selectPrevItem(); void selectNextItem(); void viewAddNewToolView(); void newWindow(); void splitHorizontal(); void splitVertical(); void split(Qt::Orientation orientation); void toggleFullScreen(bool fullScreen); void gotoNextSplit(); void gotoPreviousSplit(); void newToolbarConfig(); void settingsDialog(); void quitAll(); // void fixToolbar(); ///Returns true if we're currently changing the active view through changeActiveView() bool changingActiveView() const ; bool applicationQuitRequested() const; void configureNotifications(); void showAboutPlatform(); void showLoadedPlugins(); void toggleArea(bool b); void showErrorMessage(QString message, int timeout); void pluginDestroyed(QObject*); /// the following slots always activate the m_tabView before calling the normal slot above /// @see m_tabView /// @see tabContextMenuRequested void contextMenuFileNew(); void contextMenuSplitHorizontal(); void contextMenuSplitVertical(); /// reload all open documents void reloadAll(); private: KActionCollection *actionCollection(); MainWindow *m_mainWindow; StatusBar* m_statusBar; QWidget* lastXMLGUIClientView; QPointer m_workingSetCornerWidget; - + QMap m_pluginCustomClients; - + static bool s_quitRequested; bool m_changingActiveView; /// the view of the tab that got it's context menu connected Sublime::View* m_tabView; QPair > m_tabTooltip; }; } #endif diff --git a/shell/openprojectdialog.cpp b/shell/openprojectdialog.cpp index 0d0ff619af..7922234034 100644 --- a/shell/openprojectdialog.cpp +++ b/shell/openprojectdialog.cpp @@ -1,248 +1,240 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat -#include +#include -#include -#include -#include #include - -#include -#include -#include -#include - -#include +#include +#include +#include +#include #include "core.h" #include "uicontroller.h" #include "mainwindow.h" #include "shellextension.h" #include "projectsourcepage.h" #include -#include namespace KDevelop { OpenProjectDialog::OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent ) : KAssistantDialog( parent ) , sourcePage(nullptr) , openPage(nullptr) , projectInfoPage(nullptr) { resize(QSize(700, 500)); - + QUrl start = startUrl.isValid() ? startUrl : Core::self()->projectController()->projectsBaseDirectory(); start = start.adjusted(QUrl::NormalizePathSegments); KPageWidgetItem* currentPage = 0; if( fetch ) { sourcePageWidget = new ProjectSourcePage( start, this ); connect( sourcePageWidget, &ProjectSourcePage::isCorrect, this, &OpenProjectDialog::validateSourcePage ); sourcePage = addPage( sourcePageWidget, i18n("Select Source") ); currentPage = sourcePage; } - + openPageWidget = new OpenProjectPage( start, this ); connect( openPageWidget, &OpenProjectPage::urlSelected, this, &OpenProjectDialog::validateOpenUrl ); connect( openPageWidget, &OpenProjectPage::accepted, this, &OpenProjectDialog::openPageAccepted ); openPage = addPage( openPageWidget, i18n("Select a build system setup file, existing KDevelop project, " "or any folder to open as a project") ); - + if( !fetch ) { currentPage = openPage; } ProjectInfoPage* page = new ProjectInfoPage( this ); connect( page, &ProjectInfoPage::projectNameChanged, this, &OpenProjectDialog::validateProjectName ); connect( page, &ProjectInfoPage::projectManagerChanged, this, &OpenProjectDialog::validateProjectManager ); projectInfoPage = addPage( page, i18n("Project Information") ); - + setValid( sourcePage, false ); setValid( openPage, false ); setValid( projectInfoPage, false); setAppropriate( projectInfoPage, false ); setCurrentPage( currentPage ); setWindowTitle(i18n("Open Project")); } void OpenProjectDialog::validateSourcePage(bool valid) { setValid(sourcePage, valid); openPageWidget->setUrl(sourcePageWidget->workingDir()); } void OpenProjectDialog::validateOpenUrl( const QUrl& url_ ) { bool isDir = false; QString extension; bool isValid = false; const QUrl url = url_.adjusted(QUrl::StripTrailingSlash); if( url.isLocalFile() ) { QFileInfo info( url.toLocalFile() ); isValid = info.exists(); if ( isValid ) { isDir = info.isDir(); extension = info.suffix(); } } else if ( url.isValid() ) { KIO::StatJob* statJob = KIO::stat( url, KIO::HideProgressInfo ); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->defaultMainWindow() ); isValid = statJob->exec(); // TODO: do this asynchronously so that the user isn't blocked while typing every letter of the hostname in sftp://hostname if ( isValid ) { KIO::UDSEntry entry = statJob->statResult(); isDir = entry.isDir(); extension = QFileInfo( entry.stringValue( KIO::UDSEntry::UDS_NAME ) ).suffix(); } } if ( isValid ) { // reset header openPage->setHeader(i18n("Open \"%1\" as project", url.fileName())); } else { // report error KColorScheme scheme(palette().currentColorGroup()); const QString errorMsg = i18n("Selected URL is invalid"); openPage->setHeader(QStringLiteral("%2") .arg(scheme.foreground(KColorScheme::NegativeText).color().name()) .arg(errorMsg) ); setAppropriate( projectInfoPage, false ); setAppropriate( openPage, true ); setValid( openPage, false ); return; } - if( isDir || extension != ShellExtension::getInstance()->projectFileExtension() ) + if( isDir || extension != ShellExtension::getInstance()->projectFileExtension() ) { setAppropriate( projectInfoPage, true ); m_url = url; if( !isDir ) { m_url = m_url.adjusted(QUrl::StripTrailingSlash | QUrl::RemoveFilename); } ProjectInfoPage* page = qobject_cast( projectInfoPage->widget() ); if( page ) { page->setProjectName( m_url.fileName() ); OpenProjectPage* page2 = qobject_cast( openPage->widget() ); if( page2 ) { // Default manager page->setProjectManager( "Generic Project Manager" ); // clear the filelist m_fileList.clear(); if( isDir ) { // If a dir was selected fetch all files in it KIO::ListJob* job = KIO::listDir( m_url ); - connect( job, &KIO::ListJob::entries, + connect( job, &KIO::ListJob::entries, this, &OpenProjectDialog::storeFileList); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } else { // Else we'lll just take the given file m_fileList << url.fileName(); } // Now find a manager for the file(s) in our filelist. bool managerFound = false; foreach( const QString& manager, page2->projectFilters().keys() ) { foreach( const QString& filterexp, page2->projectFilters()[manager] ) { - if( !m_fileList.filter( QRegExp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ) ).isEmpty() ) + if( !m_fileList.filter( QRegExp( filterexp, Qt::CaseSensitive, QRegExp::Wildcard ) ).isEmpty() ) { managerFound = true; break; } } if( managerFound ) { page->setProjectManager( manager ); break; } } } } m_url.setPath( m_url.path() + '/' + m_url.fileName() + '.' + ShellExtension::getInstance()->projectFileExtension() ); } else { setAppropriate( projectInfoPage, false ); m_url = url; } validateProjectInfo(); setValid( openPage, true ); } void OpenProjectDialog::openPageAccepted() { if ( isValid( openPage ) ) { next(); } } void OpenProjectDialog::validateProjectName( const QString& name ) { m_projectName = name; validateProjectInfo(); } void OpenProjectDialog::validateProjectInfo() { setValid( projectInfoPage, (!projectName().isEmpty() && !projectManager().isEmpty()) ); } void OpenProjectDialog::validateProjectManager( const QString& manager ) { m_projectManager = manager; validateProjectInfo(); } QUrl OpenProjectDialog::projectFileUrl() { return m_url; } QString OpenProjectDialog::projectName() { return m_projectName; } QString OpenProjectDialog::projectManager() { return m_projectManager; } void OpenProjectDialog::storeFileList(KIO::Job*, const KIO::UDSEntryList& list) { foreach( const KIO::UDSEntry& entry, list ) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if( name != "." && name != ".." && !entry.isDir() ) { m_fileList << name; } } } } diff --git a/shell/openprojectdialog.h b/shell/openprojectdialog.h index 72200ebe6f..9276d701d4 100644 --- a/shell/openprojectdialog.h +++ b/shell/openprojectdialog.h @@ -1,64 +1,64 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat -#include -#include +#include +#include class KPageWidgetItem; namespace KIO { class Job; } namespace KDevelop { class ProjectSourcePage; class OpenProjectPage; class OpenProjectDialog : public KAssistantDialog { Q_OBJECT public: OpenProjectDialog( bool fetch, const QUrl& startUrl, QWidget* parent = 0 ); QUrl projectFileUrl(); QString projectName(); QString projectManager(); private slots: void validateSourcePage( bool ); void validateOpenUrl( const QUrl& ); void validateProjectName( const QString& ); void validateProjectManager( const QString& ); void storeFileList(KIO::Job*, const KIO::UDSEntryList&); void openPageAccepted(); private: void validateProjectInfo(); QUrl m_url; QString m_projectName; QString m_projectManager; KPageWidgetItem* sourcePage; KPageWidgetItem* openPage; KPageWidgetItem* projectInfoPage; QStringList m_fileList; KDevelop::OpenProjectPage* openPageWidget; KDevelop::ProjectSourcePage* sourcePageWidget; }; } #endif diff --git a/shell/openprojectpage.cpp b/shell/openprojectpage.cpp index 40695c0bfd..9d1a050a4b 100644 --- a/shell/openprojectpage.cpp +++ b/shell/openprojectpage.cpp @@ -1,136 +1,128 @@ /*************************************************************************** * Copyright (C) 2008 by Andreas Pakulat -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include +#include + +#include +#include +#include #include +#include #include "shellextension.h" #include "core.h" #include "plugincontroller.h" -#include -#include namespace KDevelop { OpenProjectPage::OpenProjectPage( const QUrl& startUrl, QWidget* parent ) : QWidget( parent ) { QHBoxLayout* layout = new QHBoxLayout( this ); fileWidget = new KFileWidget( startUrl, this); QStringList filters; QStringList allEntry; allEntry << "*."+ShellExtension::getInstance()->projectFileExtension(); filters << QStringLiteral( "%1|%2 (%1)").arg("*."+ShellExtension::getInstance()->projectFileExtension()).arg(ShellExtension::getInstance()->projectFileDescription()); foreach(const KPluginMetaData& info, ICore::self()->pluginController()->queryExtensionPlugins( "org.kdevelop.IProjectFileManager" ) ) { QStringList filter = KPluginMetaData::readStringList(info.rawData(), "X-KDevelop-ProjectFilesFilter"); QString desc = info.value("X-KDevelop-ProjectFilesFilterDescription"); QString filterline; if(!filter.isEmpty() && !desc.isEmpty()) { m_projectFilters.insert(info.name(), filter); allEntry += filter; filters << QStringLiteral("%1|%2 (%1)").arg(filter.join(" ")).arg(desc); } } filters.prepend( i18n( "%1|All Project Files (%1)", allEntry.join(" ") ) ); fileWidget->setFilter( filters.join("\n") ); fileWidget->setMode( KFile::Modes( KFile::File | KFile::Directory | KFile::ExistingOnly ) ); layout->addWidget( fileWidget ); KDirOperator* ops = fileWidget->dirOperator(); // Emitted for changes in the places view, the url navigator and when using the back/forward/up buttons connect(ops, &KDirOperator::urlEntered, this, &OpenProjectPage::opsEntered); // Emitted when selecting an entry from the "Name" box or editing in there connect( fileWidget->locationEdit(), &KUrlComboBox::editTextChanged, this, &OpenProjectPage::comboTextChanged); // Emitted when clicking on a file in the fileview area connect( fileWidget, &KFileWidget::fileHighlighted, this, &OpenProjectPage::highlightFile ); connect( fileWidget->dirOperator()->dirLister(), static_cast(&KDirLister::completed), this, &OpenProjectPage::dirChanged); connect( fileWidget, &KFileWidget::accepted, this, &OpenProjectPage::accepted); } QUrl OpenProjectPage::getAbsoluteUrl( const QString& file ) const { QUrl u(file); if( u.isRelative() ) { u = fileWidget->baseUrl().resolved( u ); } return u; } void OpenProjectPage::setUrl(const QUrl& url) { fileWidget->setUrl(url, false); } void OpenProjectPage::dirChanged(const QUrl& /*url*/) { if(fileWidget->selectedFiles().isEmpty()) { KFileItemList items=fileWidget->dirOperator()->dirLister()->items(); foreach(const KFileItem& item, items) { if(item.url().path().endsWith(ShellExtension::getInstance()->projectFileExtension()) && item.isFile()) fileWidget->setSelection(item.url().url()); } } } void OpenProjectPage::showEvent(QShowEvent* ev) { fileWidget->locationEdit()->setFocus(); QWidget::showEvent(ev); } void OpenProjectPage::highlightFile(const QUrl& file) { emit urlSelected(file); } void OpenProjectPage::opsEntered(const QUrl& url) { emit urlSelected(url); } void OpenProjectPage::comboTextChanged( const QString& file ) { emit urlSelected( getAbsoluteUrl( file ) ); } QMap OpenProjectPage::projectFilters() const { return m_projectFilters; } } diff --git a/shell/partcontroller.cpp b/shell/partcontroller.cpp index dae1b5e63e..332fc4ab78 100644 --- a/shell/partcontroller.cpp +++ b/shell/partcontroller.cpp @@ -1,333 +1,327 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "partcontroller.h" #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 #include "core.h" #include "textdocument.h" #include "debug.h" #include "uicontroller.h" #include "mainwindow.h" #include #include #include #include namespace KDevelop { class PartControllerPrivate { public: PartControllerPrivate() {} bool m_showTextEditorStatusBar = false; QString m_editor; QStringList m_textTypes; Core *m_core; }; PartController::PartController(Core *core, QWidget *toplevel) : IPartController( toplevel ), d(new PartControllerPrivate) { setObjectName("PartController"); d->m_core = core; //Cache this as it is too expensive when creating parts // KConfig * config = Config::standard(); // config->setGroup( "General" ); // // d->m_textTypes = config->readEntry( "TextTypes", QStringList() ); // // config ->setGroup( "Editor" ); // d->m_editor = config->readPathEntry( "EmbeddedKTextEditor", QString() ); // required early because some actions are checkable and need to be initialized loadSettings(false); if (!(Core::self()->setupFlags() & Core::NoUi)) setupActions(); } PartController::~PartController() { delete d; } bool PartController::showTextEditorStatusBar() const { return d->m_showTextEditorStatusBar; } void PartController::setShowTextEditorStatusBar(bool show) { if (d->m_showTextEditorStatusBar == show) return; d->m_showTextEditorStatusBar = show; // update foreach (Sublime::Area* area, Core::self()->uiControllerInternal()->allAreas()) { foreach (Sublime::View* view, area->views()) { if (!view->hasWidget()) continue; auto textView = qobject_cast(view->widget()); if (textView) { textView->setStatusBarEnabled(show); } } } // also notify active view that it should update the "view status" TextView* textView = qobject_cast(Core::self()->uiControllerInternal()->activeSublimeWindow()->activeView()); if (textView) { emit textView->statusChanged(textView); } } //MOVE BACK TO DOCUMENTCONTROLLER OR MULTIBUFFER EVENTUALLY bool PartController::isTextType(const QMimeType& mimeType) { bool isTextType = false; if (d->m_textTypes.contains(mimeType.name())) { isTextType = true; } // is this regular text - open in editor return ( isTextType || mimeType.inherits("text/plain") || mimeType.inherits("text/html") || mimeType.inherits("application/x-zerosize")); } KTextEditor::Editor* PartController::editorPart() const { return KTextEditor::Editor::instance(); } KTextEditor::Document* PartController::createTextPart(const QString &encoding) { KTextEditor::Document* doc = editorPart()->createDocument(this); if ( !encoding.isNull() ) { KParts::OpenUrlArguments args = doc->arguments(); args.setMimeType( QString::fromLatin1( "text/plain;" ) + encoding ); doc->setArguments( args ); } return doc; } KParts::Part* PartController::createPart( const QString & mimeType, const QString & partType, const QString & className, const QString & preferredName ) { KPluginFactory * editorFactory = findPartFactory( mimeType, partType, preferredName ); if ( !className.isEmpty() && editorFactory ) { return editorFactory->create( 0, this, className.toLatin1() ); } return 0; } bool PartController::canCreatePart(const QUrl& url) { if (!url.isValid()) return false; QString mimeType; if ( url.isEmpty() ) mimeType = QString::fromLatin1("text/plain"); else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KService::List offers = KMimeTypeTrader::self()->query( mimeType, "KParts/ReadOnlyPart" ); return offers.count() > 0; } KParts::Part* PartController::createPart( const QUrl & url, const QString& preferredPart ) { qCDebug(SHELL) << "creating part with url" << url << "and pref part:" << preferredPart; QString mimeType; if ( url.isEmpty() ) //create a part for empty text file mimeType = QString::fromLatin1("text/plain"); else if ( !url.isValid() ) return 0; else mimeType = QMimeDatabase().mimeTypeForUrl(url).name(); KParts::Part* part = createPart( mimeType, preferredPart ); if( part ) { readOnly( part ) ->openUrl( url ); return part; } return 0; } KParts::ReadOnlyPart* PartController::activeReadOnly() const { return readOnly( activePart() ); } KParts::ReadWritePart* PartController::activeReadWrite() const { return readWrite( activePart() ); } KParts::ReadOnlyPart* PartController::readOnly( KParts::Part * part ) const { return qobject_cast( part ); } KParts::ReadWritePart* PartController::readWrite( KParts::Part * part ) const { return qobject_cast( part ); } void PartController::loadSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); d->m_showTextEditorStatusBar = cg.readEntry("ShowTextEditorStatusBar", false); } void PartController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); cg.writeEntry("ShowTextEditorStatusBar", d->m_showTextEditorStatusBar); } void PartController::initialize() { } void PartController::cleanup() { saveSettings(false); } void PartController::setupActions() { KActionCollection* actionCollection = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction* action; action = KStandardAction::showStatusbar(this, SLOT(setShowTextEditorStatusBar(bool)), actionCollection); action->setWhatsThis(i18n("Use this command to show or hide the view's statusbar")); action->setChecked(showTextEditorStatusBar()); } //BEGIN KTextEditor::MdiContainer void PartController::setActiveView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented } KTextEditor::View *PartController::activeView() { TextView* textView = dynamic_cast(Core::self()->uiController()->activeArea()->activeView()); if (textView) { return textView->textView(); } return 0; } KTextEditor::Document *PartController::createDocument() { // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return 0; } bool PartController::closeDocument(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return false; } KTextEditor::View *PartController::createView(KTextEditor::Document *doc) { Q_UNUSED(doc) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return 0; } bool PartController::closeView(KTextEditor::View *view) { Q_UNUSED(view) // NOTE: not implemented qWarning() << "WARNING: interface call not implemented"; return false; } //END KTextEditor::MdiContainer } diff --git a/shell/partcontroller.h b/shell/partcontroller.h index 72c1c47242..5c6d9d6b0a 100644 --- a/shell/partcontroller.h +++ b/shell/partcontroller.h @@ -1,114 +1,111 @@ /*************************************************************************** * Copyright 2006 Adam Treat * * Copyright 2007 Alexander Dymo * * Copyright 2015 Kevin Funk * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef __KDEVPARTCONTROLLER_H__ #define __KDEVPARTCONTROLLER_H__ #include -#include -#include -#include -#include #include +#include #include #include "core.h" namespace KParts { class Part; class PartManager; class ReadOnlyPart; class ReadWritePart; } namespace KTextEditor { class Document; class Editor; class View; } class QMimeType; Q_DECLARE_METATYPE(KSharedConfigPtr) namespace KDevelop { class KDEVPLATFORMSHELL_EXPORT PartController : public IPartController { friend class Core; friend class CorePrivate; Q_OBJECT public: PartController(Core *core, QWidget *toplevel); virtual ~PartController(); bool showTextEditorStatusBar() const; KTextEditor::Document* createTextPart( const QString &encoding = QString() ); virtual KTextEditor::Editor* editorPart() const override; bool canCreatePart( const QUrl &url ); using IPartController::createPart; KParts::Part* createPart( const QUrl &url, const QString& prefName = QString() ); KParts::Part* createPart( const QString &mimeType, const QString &partType, const QString &className, const QString &preferredName = QString() ); KParts::ReadOnlyPart* activeReadOnly() const; KParts::ReadWritePart* activeReadWrite() const; KParts::ReadOnlyPart* readOnly( KParts::Part *part ) const; KParts::ReadWritePart* readWrite( KParts::Part *part ) const; bool isTextType(const QMimeType& mimeType); virtual void setActiveView( KTextEditor::View * view ); virtual KTextEditor::View * activeView(); virtual KTextEditor::Document * createDocument(); virtual bool closeDocument( KTextEditor::Document * doc ); virtual KTextEditor::View * createView( KTextEditor::Document * doc ); virtual bool closeView( KTextEditor::View * view ); public Q_SLOTS: void setShowTextEditorStatusBar(bool show); protected: virtual void loadSettings( bool projectIsLoaded ); virtual void saveSettings( bool projectIsLoaded ); virtual void initialize(); virtual void cleanup(); private: void setupActions(); class PartControllerPrivate* const d; }; } #endif diff --git a/shell/plugincontroller.cpp b/shell/plugincontroller.cpp index ad2b2afe9c..e71699ff67 100644 --- a/shell/plugincontroller.cpp +++ b/shell/plugincontroller.cpp @@ -1,759 +1,751 @@ /* This file is part of the KDE project Copyright 2004, 2007 Alexander Dymo Copyright 2006 Matt Rogers Based on code from Kopete Copyright (c) 2002-2003 Martijn Klingens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "plugincontroller.h" +#include #include -#include -#include -#include #include -#include -#include -#include -#include -#include +#include #include -#include -#include -#include -#include #include // TODO: remove once we no longer support old style plugins -#include +#include +#include +#include #include #include #include #include #include #include #include "mainwindow.h" #include "core.h" #include "shellextension.h" #include "runcontroller.h" #include "debugcontroller.h" #include "documentationcontroller.h" #include "sourceformattercontroller.h" #include "projectcontroller.h" #include "debug.h" namespace { const QString KEY_Plugins = QStringLiteral("Plugins"); const QString KEY_Suffix_Enabled = QStringLiteral("Enabled"); const QString KEY_LoadMode = QStringLiteral("X-KDevelop-LoadMode"); const QString KEY_Category = QStringLiteral("X-KDevelop-Category"); const QString KEY_Mode = QStringLiteral("X-KDevelop-Mode"); const QString KEY_Version = QStringLiteral("X-KDevelop-Version"); const QString KEY_Interfaces = QStringLiteral("X-KDevelop-Interfaces"); const QString KEY_Required = QStringLiteral("X-KDevelop-IRequired"); const QString KEY_Optional = QStringLiteral("X-KDevelop-IOptional"); const QString KEY_Global = QStringLiteral("Global"); const QString KEY_Project = QStringLiteral("Project"); const QString KEY_Gui = QStringLiteral("GUI"); const QString KEY_AlwaysOn = QStringLiteral("AlwaysOn"); const QString KEY_UserSelectable = QStringLiteral("UserSelectable"); bool isUserSelectable( const KPluginMetaData& info ) { QString loadMode = info.value(KEY_LoadMode); return loadMode.isEmpty() || loadMode == KEY_UserSelectable; } bool isGlobalPlugin( const KPluginMetaData& info ) { return info.value(KEY_Category) == KEY_Global; } bool hasMandatoryProperties( const KPluginMetaData& info ) { QString mode = info.value(KEY_Mode); if (mode.isEmpty()) { return false; } // when the plugin is installed into the versioned plugin path, it's good to go if (info.fileName().contains(QLatin1String("/kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION) "/"))) { return true; } // the version property is only required when the plugin is not installed into the right directory QVariant version = info.rawData().value(KEY_Version).toVariant(); if (version.isValid() && version.value() == KDEVELOP_PLUGIN_VERSION) { return true; } return false; } bool constraintsMatch( const KPluginMetaData& info, const QVariantMap& constraints) { for (auto it = constraints.begin(); it != constraints.end(); ++it) { const auto property = info.rawData().value(it.key()).toVariant(); if (!property.isValid()) { return false; } else if (property.canConvert()) { QSet values = property.toStringList().toSet(); QSet expected = it.value().toStringList().toSet(); if (!values.contains(expected)) { return false; } } else if (it.value() != property) { return false; } } return true; } struct Dependency { Dependency(const QString &dependency) : interface(dependency) { if (dependency.contains('@')) { const auto list = dependency.split('@', QString::SkipEmptyParts); if (list.size() == 2) { interface = list.at(0); pluginName = list.at(1); } } } QString interface; QString pluginName; }; } namespace KDevelop { class PluginControllerPrivate { public: QVector plugins; //map plugin infos to currently loaded plugins typedef QHash InfoToPluginMap; InfoToPluginMap loadedPlugins; // The plugin manager's mode. The mode is StartingUp until loadAllPlugins() // has finished loading the plugins, after which it is set to Running. // ShuttingDown and DoneShutdown are used during shutdown by the // async unloading of plugins. enum CleanupMode { Running /**< the plugin manager is running */, CleaningUp /**< the plugin manager is cleaning up for shutdown */, CleanupDone /**< the plugin manager has finished cleaning up */ }; CleanupMode cleanupMode; bool canUnload(const KPluginMetaData& plugin) { qCDebug(SHELL) << "checking can unload for:" << plugin.name() << plugin.value(KEY_LoadMode); if (plugin.value(KEY_LoadMode) == KEY_AlwaysOn) { return false; } const QStringList interfaces = KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces); qCDebug(SHELL) << "checking dependencies:" << interfaces; foreach (const KPluginMetaData& info, loadedPlugins.keys()) { if (info.pluginId() != plugin.pluginId()) { QStringList dependencies = KPluginMetaData::readStringList(plugin.rawData(), KEY_Required); dependencies += KPluginMetaData::readStringList(plugin.rawData(), KEY_Optional); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!dependency.pluginName.isEmpty() && dependency.pluginName != plugin.pluginId()) { continue; } if (interfaces.contains(dependency.interface) && !canUnload(info)) { return false; } } } } return true; } KPluginMetaData infoForId( const QString& id ) const { foreach (const KPluginMetaData& info, plugins) { if (info.pluginId() == id) { return info; } } return KPluginMetaData(); } /** * Iterate over all cached plugin infos, and call the functor for every enabled plugin. * * If an extension and/or pluginName is given, the functor will only be called for * those plugins matching this information. * * The functor should return false when the iteration can be stopped, and true if it * should be continued. */ template void foreachEnabledPlugin(F func, const QString &extension = {}, const QVariantMap& constraints = QVariantMap(), const QString &pluginName = {}) { foreach (const auto& info, plugins) { if ((pluginName.isEmpty() || info.pluginId() == pluginName) && (extension.isEmpty() || KPluginMetaData::readStringList(info.rawData(), KEY_Interfaces).contains(extension)) && constraintsMatch(info, constraints) && isEnabled(info)) { if (!func(info)) { break; } } } } bool isEnabled(const KPluginMetaData& info) const { static const QStringList disabledPlugins = QString::fromLatin1(qgetenv("KDEV_DISABLE_PLUGINS")).split(';'); if (disabledPlugins.contains(info.pluginId())) { return false; } if (!isGlobalPlugin( info ) || !isUserSelectable( info )) { return true; } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins ); const bool isDefaultPlugin = ShellExtension::getInstance()->defaultPlugins().isEmpty() || ShellExtension::getInstance()->defaultPlugins().contains(info.pluginId()); bool isEnabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled, isDefaultPlugin); //qCDebug(SHELL) << "read config:" << isEnabled << "is global plugin:" << isGlobalPlugin( info ) << "default:" << ShellExtension::getInstance()->defaultPlugins().isEmpty() << ShellExtension::getInstance()->defaultPlugins().contains( info.pluginId() ); return isEnabled; } Core *core; }; PluginController::PluginController(Core *core) : IPluginController(), d(new PluginControllerPrivate) { setObjectName("PluginController"); d->core = core; auto newPlugins = KPluginLoader::findPlugins("kdevplatform/" QT_STRINGIFY(KDEVELOP_PLUGIN_VERSION), [](const KPluginMetaData& meta) { if (meta.serviceTypes().contains(QStringLiteral("KDevelop/Plugin"))) { return true; } else { qWarning() << "Plugin" << meta.fileName() << "is installed into the kdevplatform plugin directory, but does not have" " \"KDevelop/Plugin\" set as the service type. This plugin will not be loaded."; return false; } }); qCDebug(SHELL) << "Found" << newPlugins.size() << " plugins using the new search method."; d->plugins = newPlugins; //qCDebug(SHELL) << "Fetching plugin info which matches:" << QStringLiteral( "[X-KDevelop-Version] == %1" ).arg(KDEVELOP_PLUGIN_VERSION); const KPluginInfo::List oldStylePlugins = KPluginInfo::fromServices( KServiceTypeTrader::self()->query( QStringLiteral( "KDevelop/Plugin" ), QStringLiteral( "[X-KDevelop-Version] == %1" ).arg(KDEVELOP_PLUGIN_VERSION) ) ); qCDebug(SHELL) << "Found" << oldStylePlugins.size() << " plugins using the old search method."; if (!oldStylePlugins.isEmpty()) { foreach (const KPluginInfo& info, oldStylePlugins) { qWarning() << "Plugin" << info.pluginName() << "still uses the old .desktop file based metadata." " It must be ported to JSON metadata or it will no longer work with future kdevplatform versions."; d->plugins.append(info.toMetaData()); } } d->cleanupMode = PluginControllerPrivate::Running; // Register the KDevelop::IPlugin* metatype so we can properly unload it qRegisterMetaType( "KDevelop::IPlugin*" ); } PluginController::~PluginController() { if ( d->cleanupMode != PluginControllerPrivate::CleanupDone ) { qCWarning(SHELL) << "Destructing plugin controller without going through the shutdown process!"; } delete d; } KPluginMetaData PluginController::pluginInfo( const IPlugin* plugin ) const { return d->loadedPlugins.key(const_cast(plugin)); } void PluginController::cleanup() { if(d->cleanupMode != PluginControllerPrivate::Running) { //qCDebug(SHELL) << "called when not running. state =" << d->cleanupMode; return; } d->cleanupMode = PluginControllerPrivate::CleaningUp; // Ask all plugins to unload while ( !d->loadedPlugins.isEmpty() ) { //Let the plugin do some stuff before unloading unloadPlugin(d->loadedPlugins.begin().value(), Now); } d->cleanupMode = PluginControllerPrivate::CleanupDone; } IPlugin* PluginController::loadPlugin( const QString& pluginName ) { return loadPluginInternal( pluginName ); } bool PluginController::isEnabled( const KPluginMetaData& info ) { return d->isEnabled(info); } void PluginController::initialize() { QMap pluginMap; if( ShellExtension::getInstance()->defaultPlugins().isEmpty() ) { foreach( const KPluginMetaData& pi, d->plugins ) { pluginMap.insert( pi.pluginId(), true ); } } else { // Get the default from the ShellExtension foreach( const QString& s, ShellExtension::getInstance()->defaultPlugins() ) { pluginMap.insert( s, true ); } } KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins ); QMap entries = grp.entryMap(); QMap::Iterator it; for ( it = entries.begin(); it != entries.end(); ++it ) { QString key = it.key(); if (key.endsWith(KEY_Suffix_Enabled)) { QString pluginid = key.left( key.length() - 7 ); bool defValue; QMap::const_iterator entry = pluginMap.constFind( pluginid ); if( entry != pluginMap.constEnd() ) { defValue = entry.value(); } else { defValue = false; } pluginMap.insert( key.left(key.length() - 7), grp.readEntry(key,defValue) ); } } foreach( const KPluginMetaData& pi, d->plugins ) { if( isGlobalPlugin( pi ) ) { QMap::const_iterator it = pluginMap.constFind( pi.pluginId() ); if( it != pluginMap.constEnd() && ( it.value() || !isUserSelectable( pi ) ) ) { // Plugin is mentioned in pluginmap and the value is true, so try to load it loadPluginInternal( pi.pluginId() ); if(!grp.hasKey(pi.pluginId() + KEY_Suffix_Enabled)) { if( isUserSelectable( pi ) ) { // If plugin isn't listed yet, add it with true now grp.writeEntry(pi.pluginId() + KEY_Suffix_Enabled, true); } } else if( grp.hasKey( pi.pluginId() + "Disabled" ) && !isUserSelectable( pi ) ) { // Remove now-obsolete entries grp.deleteEntry( pi.pluginId() + "Disabled" ); } } } } // Synchronize so we're writing out to the file. grp.sync(); } QList PluginController::loadedPlugins() const { return d->loadedPlugins.values(); } bool PluginController::unloadPlugin( const QString & pluginId ) { IPlugin *thePlugin = plugin( pluginId ); bool canUnload = d->canUnload( d->infoForId( pluginId ) ); qCDebug(SHELL) << "Unloading plugin:" << pluginId << "?" << thePlugin << canUnload; if( thePlugin && canUnload ) { return unloadPlugin(thePlugin, Later); } return (canUnload && thePlugin); } bool PluginController::unloadPlugin(IPlugin* plugin, PluginDeletion deletion) { qCDebug(SHELL) << "unloading plugin:" << plugin << pluginInfo( plugin ).name(); emit unloadingPlugin(plugin); plugin->unload(); emit pluginUnloaded(plugin); //Remove the plugin from our list of plugins so we create a new //instance when we're asked for it again. //This is important to do right here, not later when the plugin really //vanishes. For example project re-opening might try to reload the plugin //and then would get the "old" pointer which will be deleted in the next //event loop run and thus causing crashes. for ( PluginControllerPrivate::InfoToPluginMap::Iterator it = d->loadedPlugins.begin(); it != d->loadedPlugins.end(); ++it ) { if ( it.value() == plugin ) { d->loadedPlugins.erase( it ); break; } } if (deletion == Later) plugin->deleteLater(); else delete plugin; return true; } KPluginMetaData PluginController::infoForPluginId( const QString &pluginId ) const { foreach (const KPluginMetaData& info, d->plugins) { if (info.pluginId() == pluginId) { return info; } } return KPluginMetaData(); } IPlugin *PluginController::loadPluginInternal( const QString &pluginId ) { QElapsedTimer timer; timer.start(); KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) { qCWarning(SHELL) << "Unable to find a plugin named '" << pluginId << "'!" ; return nullptr; } if ( IPlugin* plugin = d->loadedPlugins.value( info ) ) { return plugin; } if ( !isEnabled( info ) ) { // Do not load disabled plugins qWarning() << "Not loading plugin named" << pluginId << "because it has been disabled!"; return nullptr; } if ( !hasMandatoryProperties( info ) ) { qWarning() << "Unable to load plugin named" << pluginId << "because not all mandatory properties are set."; return nullptr; } if ( info.value(KEY_Mode) == KEY_Gui && Core::self()->setupFlags() == Core::NoUi ) { qCDebug(SHELL) << "Not loading plugin named" << pluginId << "- Running in No-Ui mode, but the plugin says it needs a GUI"; return nullptr; } qCDebug(SHELL) << "Attempting to load" << pluginId << "- name:" << info.name(); emit loadingPlugin( info.pluginId() ); // first, ensure all dependencies are available and not disabled // this is unrelated to whether they are loaded already or not. // when we depend on e.g. A and B, but B cannot be found, then we // do not want to load A first and then fail on B and leave A loaded. // this would happen if we'd skip this step here and directly loadDependencies. QStringList missingInterfaces; if ( !hasUnresolvedDependencies( info, missingInterfaces ) ) { qWarning() << "Can't load plugin" << pluginId << "some of its required dependencies could not be fulfilled:" << missingInterfaces.join(","); return nullptr; } // now ensure all dependencies are loaded QString failedDependency; if( !loadDependencies( info, failedDependency ) ) { qWarning() << "Can't load plugin" << pluginId << "because a required dependency could not be loaded:" << failedDependency; return nullptr; } // same for optional dependencies, but don't error out if anything fails loadOptionalDependencies( info ); // now we can finally load the plugin itself KPluginLoader loader(info.fileName()); auto factory = loader.factory(); if (!factory) { qWarning() << "Can't load plugin" << pluginId << "because a factory to load the plugin could not be obtained:" << loader.errorString(); return nullptr; } // now create it auto plugin = factory->create(d->core); if (!plugin) { qWarning() << "Creating plugin" << pluginId << "failed."; return nullptr; } KConfigGroup group = Core::self()->activeSession()->config()->group(KEY_Plugins); // runtime errors such as missing executables on the system or such get checked now if (plugin->hasError()) { qWarning() << "Could not load plugin" << pluginId << ", it reported the error:" << plugin->errorDescription() << "Disabling the plugin now."; group.writeEntry(info.pluginId() + KEY_Suffix_Enabled, false); // do the same as KPluginInfo did group.sync(); unloadPlugin(pluginId); return nullptr; } // yay, it all worked - the plugin is loaded d->loadedPlugins.insert(info, plugin); group.writeEntry(info.pluginId() + KEY_Suffix_Enabled, true); // do the same as KPluginInfo did group.sync(); qCDebug(SHELL) << "Successfully loaded plugin" << pluginId << "from" << loader.fileName() << "- took:" << timer.elapsed() << "ms"; emit pluginLoaded( plugin ); return plugin; } IPlugin* PluginController::plugin( const QString& pluginId ) { KPluginMetaData info = infoForPluginId( pluginId ); if ( !info.isValid() ) return 0L; return d->loadedPlugins.value( info ); } bool PluginController::hasUnresolvedDependencies( const KPluginMetaData& info, QStringList& missing ) const { QSet required = KPluginMetaData::readStringList(info.rawData(), KEY_Required).toSet(); if (!required.isEmpty()) { d->foreachEnabledPlugin([&required] (const KPluginMetaData& plugin) -> bool { foreach (const QString& iface, KPluginMetaData::readStringList(plugin.rawData(), KEY_Interfaces)) { required.remove(iface); required.remove(iface + '@' + plugin.pluginId()); } return !required.isEmpty(); }); } // if we found all dependencies required should be empty now if (!required.isEmpty()) { missing = required.toList(); return false; } return true; } void PluginController::loadOptionalDependencies( const KPluginMetaData& info ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional); foreach (const QString& dep, dependencies) { Dependency dependency(dep); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { qCDebug(SHELL) << "Couldn't load optional dependency:" << dep << info.pluginId(); } } } bool PluginController::loadDependencies( const KPluginMetaData& info, QString& failedDependency ) { const QStringList dependencies = KPluginMetaData::readStringList(info.rawData(), KEY_Optional); foreach (const QString& value, dependencies) { Dependency dependency(value); if (!pluginForExtension(dependency.interface, dependency.pluginName)) { failedDependency = value; return false; } } return true; } IPlugin *PluginController::pluginForExtension(const QString &extension, const QString &pluginName, const QVariantMap& constraints) { IPlugin* plugin = nullptr; d->foreachEnabledPlugin([this, &plugin] (const KPluginMetaData& info) -> bool { plugin = d->loadedPlugins.value( info ); if( !plugin ) { plugin = loadPluginInternal( info.pluginId() ); } return !plugin; }, extension, constraints, pluginName); return plugin; } QList PluginController::allPluginsForExtension(const QString &extension, const QVariantMap& constraints) { //qCDebug(SHELL) << "Finding all Plugins for Extension:" << extension << "|" << constraints; QList plugins; d->foreachEnabledPlugin([this, &plugins] (const KPluginMetaData& info) -> bool { IPlugin* plugin = d->loadedPlugins.value( info ); if( !plugin) { plugin = loadPluginInternal( info.pluginId() ); } if (plugin) plugins << plugin; return true; }, extension, constraints); return plugins; } QVector PluginController::queryExtensionPlugins(const QString& extension, const QVariantMap& constraints) const { QVector plugins; d->foreachEnabledPlugin([&plugins] (const KPluginMetaData& info) -> bool { plugins << info; return true; }, extension, constraints); return plugins; } QStringList PluginController::allPluginNames() { QStringList names; Q_FOREACH( const KPluginMetaData& info , d->plugins ) { names << info.pluginId(); } return names; } QList PluginController::queryPluginsForContextMenuExtensions( KDevelop::Context* context ) const { QList exts; for( auto it=d->loadedPlugins.constBegin(), itEnd = d->loadedPlugins.constEnd(); it!=itEnd; ++it ) { IPlugin* plug = it.value(); exts << plug->contextMenuExtension( context ); } exts << Core::self()->debugControllerInternal()->contextMenuExtension( context ); exts << Core::self()->documentationControllerInternal()->contextMenuExtension( context ); exts << Core::self()->sourceFormatterControllerInternal()->contextMenuExtension( context ); exts << Core::self()->runControllerInternal()->contextMenuExtension( context ); exts << Core::self()->projectControllerInternal()->contextMenuExtension( context ); return exts; } QStringList PluginController::projectPlugins() { QStringList names; foreach (const KPluginMetaData& info, d->plugins) { if (info.value(KEY_Category) == KEY_Project) { names << info.pluginId(); } } return names; } void PluginController::loadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { loadPluginInternal( name ); } } void PluginController::unloadProjectPlugins() { Q_FOREACH( const QString& name, projectPlugins() ) { unloadPlugin( name ); } } QVector PluginController::allPluginInfos() const { return d->plugins; } void PluginController::updateLoadedPlugins() { QStringList defaultPlugins = ShellExtension::getInstance()->defaultPlugins(); KConfigGroup grp = Core::self()->activeSession()->config()->group( KEY_Plugins ); foreach( const KPluginMetaData& info, d->plugins ) { if( isGlobalPlugin( info ) ) { bool enabled = grp.readEntry(info.pluginId() + KEY_Suffix_Enabled, ( defaultPlugins.isEmpty() || defaultPlugins.contains( info.pluginId() ) ) ) || !isUserSelectable( info ); bool loaded = d->loadedPlugins.contains( info ); if( loaded && !enabled ) { qCDebug(SHELL) << "unloading" << info.pluginId(); if( !unloadPlugin( info.pluginId() ) ) { grp.writeEntry( info.pluginId() + KEY_Suffix_Enabled, false ); } } else if( !loaded && enabled ) { loadPluginInternal( info.pluginId() ); } } } } void PluginController::resetToDefaults() { KSharedConfigPtr cfg = Core::self()->activeSession()->config(); cfg->deleteGroup( KEY_Plugins ); cfg->sync(); KConfigGroup grp = cfg->group( KEY_Plugins ); QStringList plugins = ShellExtension::getInstance()->defaultPlugins(); if( plugins.isEmpty() ) { foreach( const KPluginMetaData& info, d->plugins ) { plugins << info.pluginId(); } } foreach( const QString& s, plugins ) { grp.writeEntry(s + KEY_Suffix_Enabled, true); } grp.sync(); } } diff --git a/shell/progresswidget/progressdialog.cpp b/shell/progresswidget/progressdialog.cpp index df210ebec1..a9d048a132 100644 --- a/shell/progresswidget/progressdialog.cpp +++ b/shell/progresswidget/progressdialog.cpp @@ -1,419 +1,419 @@ /** -*- c++ -*- * progressdialog.cpp * * Copyright (c) 2004 Till Adam , * David Faure * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 2 of the License * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of this program with any edition of * the Qt library by Trolltech AS, Norway (or with modified versions * of Qt that use the same license as Qt), and distribute linked * combinations including the two. You must obey the GNU General * Public License in all respects for all of the code used other than * Qt. If you modify this file, you may extend this exception to * your version of the file, but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from * your version. */ #include "progressdialog.h" #include "progressmanager.h" -#include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include namespace KDevelop { static const int MAX_LABEL_WIDTH = 650; class TransactionItem; TransactionItemView::TransactionItemView( QWidget *parent, const char *name ) : QScrollArea( parent ) { setObjectName( name ); setFrameStyle( NoFrame ); mBigBox = new QWidget( this ); auto layout = new QVBoxLayout(mBigBox); layout->setMargin(0); setWidget( mBigBox ); setWidgetResizable( true ); setSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ); } TransactionItem *TransactionItemView::addTransactionItem( ProgressItem *item, bool first ) { TransactionItem *ti = new TransactionItem( mBigBox, item, first ); mBigBox->layout()->addWidget( ti ); resize( mBigBox->width(), mBigBox->height() ); return ti; } void TransactionItemView::resizeEvent ( QResizeEvent *event ) { // Tell the layout in the parent (progressdialog) that our size changed updateGeometry(); QSize sz = parentWidget()->sizeHint(); int currentWidth = parentWidget()->width(); // Don't resize to sz.width() every time when it only reduces a little bit if ( currentWidth < sz.width() || currentWidth > sz.width() + 100 ) { currentWidth = sz.width(); } parentWidget()->resize( currentWidth, sz.height() ); QScrollArea::resizeEvent( event ); } QSize TransactionItemView::sizeHint() const { return minimumSizeHint(); } QSize TransactionItemView::minimumSizeHint() const { int f = 2 * frameWidth(); // Make room for a vertical scrollbar in all cases, to avoid a horizontal one int vsbExt = verticalScrollBar()->sizeHint().width(); int minw = topLevelWidget()->width() / 3; int maxh = topLevelWidget()->height() / 2; QSize sz( mBigBox->minimumSizeHint() ); sz.setWidth( qMax( sz.width(), minw ) + f + vsbExt ); sz.setHeight( qMin( sz.height(), maxh ) + f ); return sz; } void TransactionItemView::slotLayoutFirstItem() { //This slot is called whenever a TransactionItem is deleted, so this is a //good place to call updateGeometry(), so our parent takes the new size //into account and resizes. updateGeometry(); /* The below relies on some details in Qt's behaviour regarding deleting objects. This slot is called from the destroyed signal of an item just going away. That item is at that point still in the list of chilren, but since the vtable is already gone, it will have type QObject. The first one with both the right name and the right class therefor is what will be the first item very shortly. That's the one we want to remove the hline for. */ TransactionItem *ti = mBigBox->findChild( "TransactionItem" ); if ( ti ) { ti->hideHLine(); } } // ---------------------------------------------------------------------------- TransactionItem::TransactionItem( QWidget *parent, ProgressItem *item, bool first ) : QWidget( parent ), mCancelButton( 0 ), mItem( item ) { auto vbox = new QVBoxLayout(this); vbox->setSpacing( 2 ); vbox->setMargin( 2 ); setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mFrame = new QFrame( this ); mFrame->setFrameShape( QFrame::HLine ); mFrame->setFrameShadow( QFrame::Raised ); mFrame->show(); vbox->setStretchFactor( mFrame, 3 ); vbox->addWidget( mFrame ); QWidget *h = new QWidget( this ); auto hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); vbox->addWidget( h ); mItemLabel = new QLabel( fontMetrics().elidedText( item->label(), Qt::ElideRight, MAX_LABEL_WIDTH ), h ); h->layout()->addWidget( mItemLabel ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); mProgress = new QProgressBar( h ); hboxLayout->addWidget(mProgress); mProgress->setMaximum( 100 ); mProgress->setValue( item->progress() ); h->layout()->addWidget( mProgress ); if ( item->canBeCanceled() ) { mCancelButton = new QPushButton( QIcon::fromTheme( "dialog-cancel" ), QString(), h ); hboxLayout->addWidget(mCancelButton); mCancelButton->setToolTip( i18n( "Cancel this operation." ) ); connect ( mCancelButton, &QPushButton::clicked, this, &TransactionItem::slotItemCanceled); h->layout()->addWidget( mCancelButton ); } h = new QWidget( this ); hboxLayout = new QHBoxLayout(h); hboxLayout->setMargin(0); hboxLayout->setSpacing( 5 ); h->setSizePolicy( QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); vbox->addWidget( h ); mItemStatus = new QLabel( h ); hboxLayout->addWidget(mItemStatus); mItemStatus->setTextFormat( Qt::RichText ); mItemStatus->setText( fontMetrics().elidedText( item->status(), Qt::ElideRight, MAX_LABEL_WIDTH ) ); h->layout()->addWidget( mItemStatus ); if ( first ) { hideHLine(); } } TransactionItem::~TransactionItem() { } void TransactionItem::hideHLine() { mFrame->hide(); } void TransactionItem::setProgress( int progress ) { mProgress->setValue( progress ); } void TransactionItem::setLabel( const QString &label ) { mItemLabel->setText( fontMetrics().elidedText( label, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setStatus( const QString &status ) { mItemStatus->setText( fontMetrics().elidedText( status, Qt::ElideRight, MAX_LABEL_WIDTH ) ); } void TransactionItem::setTotalSteps( int totalSteps ) { mProgress->setMaximum( totalSteps ); } void TransactionItem::slotItemCanceled() { if ( mItem ) { mItem->cancel(); } } void TransactionItem::addSubTransaction( ProgressItem *item ) { Q_UNUSED( item ); } // --------------------------------------------------------------------------- ProgressDialog::ProgressDialog( QWidget *alignWidget, QWidget *parent, const char *name ) : OverlayWidget( alignWidget, parent, name ), mWasLastShown( false ) { setAutoFillBackground( true ); mScrollView = new TransactionItemView( this, "ProgressScrollView" ); layout()->addWidget( mScrollView ); // No more close button for now, since there is no more autoshow /* QVBox* rightBox = new QVBox( this ); QToolButton* pbClose = new QToolButton( rightBox ); pbClose->setAutoRaise(true); pbClose->setSizePolicy( QSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ) ); pbClose->setFixedSize( 16, 16 ); pbClose->setIcon( KIconLoader::global()->loadIconSet( "window-close", KIconLoader::Small, 14 ) ); pbClose->setToolTip( i18n( "Hide detailed progress window" ) ); connect(pbClose, SIGNAL(clicked()), this, SLOT(slotClose())); QWidget* spacer = new QWidget( rightBox ); // don't let the close button take up all the height rightBox->setStretchFactor( spacer, 100 ); */ /* * Get the singleton ProgressManager item which will inform us of * appearing and vanishing items. */ ProgressManager *pm = ProgressManager::instance(); connect ( pm, &ProgressManager::progressItemAdded, this, &ProgressDialog::slotTransactionAdded ); connect ( pm, &ProgressManager::progressItemCompleted, this, &ProgressDialog::slotTransactionCompleted ); connect ( pm, &ProgressManager::progressItemProgress, this, &ProgressDialog::slotTransactionProgress ); connect ( pm, &ProgressManager::progressItemStatus, this, &ProgressDialog::slotTransactionStatus ); connect ( pm, &ProgressManager::progressItemLabel, this, &ProgressDialog::slotTransactionLabel ); connect ( pm, &ProgressManager::progressItemUsesBusyIndicator, this, &ProgressDialog::slotTransactionUsesBusyIndicator ); connect ( pm, &ProgressManager::showProgressDialog, this, &ProgressDialog::slotShow ); } void ProgressDialog::closeEvent( QCloseEvent *e ) { e->accept(); hide(); } /* * Destructor */ ProgressDialog::~ProgressDialog() { // no need to delete child widgets. } void ProgressDialog::slotTransactionAdded( ProgressItem *item ) { if ( item->parent() ) { if ( mTransactionsToListviewItems.contains( item->parent() ) ) { TransactionItem * parent = mTransactionsToListviewItems[ item->parent() ]; parent->addSubTransaction( item ); } } else { const bool first = mTransactionsToListviewItems.empty(); TransactionItem *ti = mScrollView->addTransactionItem( item, first ); if ( ti ) { mTransactionsToListviewItems.insert( item, ti ); } if ( first && mWasLastShown ) { QTimer::singleShot( 1000, this, SLOT(slotShow()) ); } } } void ProgressDialog::slotTransactionCompleted( ProgressItem *item ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; mTransactionsToListviewItems.remove( item ); ti->setItemComplete(); QTimer::singleShot( 3000, ti, SLOT(deleteLater()) ); // see the slot for comments as to why that works connect ( ti, &TransactionItem::destroyed, mScrollView, &TransactionItemView::slotLayoutFirstItem ); } // This was the last item, hide. if ( mTransactionsToListviewItems.empty() ) { QTimer::singleShot( 3000, this, SLOT(slotHide()) ); } } void ProgressDialog::slotTransactionCanceled( ProgressItem * ) { } void ProgressDialog::slotTransactionProgress( ProgressItem *item, unsigned int progress ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setProgress( progress ); } } void ProgressDialog::slotTransactionStatus( ProgressItem *item, const QString &status ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setStatus( status ); } } void ProgressDialog::slotTransactionLabel( ProgressItem *item, const QString &label ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; ti->setLabel( label ); } } void ProgressDialog::slotTransactionUsesBusyIndicator( KDevelop::ProgressItem *item, bool value ) { if ( mTransactionsToListviewItems.contains( item ) ) { TransactionItem *ti = mTransactionsToListviewItems[ item ]; if ( value ) { ti->setTotalSteps( 0 ); } else { ti->setTotalSteps( 100 ); } } } void ProgressDialog::slotShow() { setVisible( true ); } void ProgressDialog::slotHide() { // check if a new item showed up since we started the timer. If not, hide if ( mTransactionsToListviewItems.isEmpty() ) { setVisible( false ); } } void ProgressDialog::slotClose() { mWasLastShown = false; setVisible( false ); } void ProgressDialog::setVisible( bool b ) { OverlayWidget::setVisible( b ); emit visibilityChanged( b ); } void ProgressDialog::slotToggleVisibility() { /* Since we are only hiding with a timeout, there is a short period of * time where the last item is still visible, but clicking on it in * the statusbarwidget should not display the dialog, because there * are no items to be shown anymore. Guard against that. */ mWasLastShown = isHidden(); if ( !isHidden() || !mTransactionsToListviewItems.isEmpty() ) { setVisible( isHidden() ); } } } diff --git a/shell/progresswidget/progressdialog.h b/shell/progresswidget/progressdialog.h index 68eb56e0a6..d4ced74243 100644 --- a/shell/progresswidget/progressdialog.h +++ b/shell/progresswidget/progressdialog.h @@ -1,141 +1,139 @@ /*************************************************************************** * Copyright (c) 2004 Till Adam * * based on imapprogressdialog.cpp ,which is * * Copyright (c) 2002-2003 Klar�vdalens Datakonsult AB * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_PROGRESSDIALOG_H #define KDEVPLATFORM_PROGRESSDIALOG_H #include "overlaywidget.h" -#include - -#include #include -#include +#include +#include class QProgressBar; class QFrame; class QLabel; class QPushButton; namespace KDevelop { class ProgressItem; class TransactionItem; class SSLLabel; class TransactionItemView : public QScrollArea { Q_OBJECT public: explicit TransactionItemView( QWidget * parent = 0, const char * name = 0 ); virtual ~TransactionItemView() {} TransactionItem *addTransactionItem( ProgressItem *item, bool first ); QSize sizeHint() const override; QSize minimumSizeHint() const override; public Q_SLOTS: void slotLayoutFirstItem(); protected: virtual void resizeEvent ( QResizeEvent *event ) override; private: QWidget *mBigBox; }; class TransactionItem : public QWidget { Q_OBJECT public: TransactionItem( QWidget *parent, ProgressItem *item, bool first ); ~TransactionItem(); void hideHLine(); void setProgress( int progress ); void setLabel( const QString & ); // the given text is interpreted as RichText, so you might need to // Qt::escape() it before passing void setStatus( const QString & ); void setTotalSteps( int totalSteps ); ProgressItem *item() const { return mItem; } void addSubTransaction( ProgressItem *item ); // The progressitem is deleted immediately, we take 5s to go out, // so better not use mItem during this time. void setItemComplete() { mItem = 0; } public Q_SLOTS: void slotItemCanceled(); protected: QProgressBar *mProgress; QPushButton *mCancelButton; QLabel *mItemLabel; QLabel *mItemStatus; QFrame *mFrame; ProgressItem *mItem; }; class ProgressDialog : public OverlayWidget { Q_OBJECT public: ProgressDialog( QWidget *alignWidget, QWidget *parent, const char *name = 0 ); ~ProgressDialog(); void setVisible( bool b ) override; public Q_SLOTS: void slotToggleVisibility(); protected Q_SLOTS: void slotTransactionAdded( KDevelop::ProgressItem *item ); void slotTransactionCompleted( KDevelop::ProgressItem *item ); void slotTransactionCanceled( KDevelop::ProgressItem *item ); void slotTransactionProgress( KDevelop::ProgressItem *item, unsigned int progress ); void slotTransactionStatus( KDevelop::ProgressItem *item, const QString & ); void slotTransactionLabel( KDevelop::ProgressItem *item, const QString & ); void slotTransactionUsesBusyIndicator( KDevelop::ProgressItem *, bool ); void slotClose(); void slotShow(); void slotHide(); Q_SIGNALS: void visibilityChanged( bool ); protected: virtual void closeEvent( QCloseEvent * ) override; TransactionItemView *mScrollView; QMap mTransactionsToListviewItems; bool mWasLastShown; }; } // namespace KDevelop #endif // __KDevelop_PROGRESSDIALOG_H__ diff --git a/shell/project.cpp b/shell/project.cpp index 473efecf7c..765c594112 100644 --- a/shell/project.cpp +++ b/shell/project.cpp @@ -1,684 +1,680 @@ /* This file is part of the KDE project Copyright 2001 Matthias Hoelzer-Kluepfel Copyright 2002-2003 Roberto Raggi Copyright 2002 Simon Hausmann Copyright 2003 Jens Dagerbo Copyright 2003 Mario Scalas Copyright 2003-2004 Alexander Dymo Copyright 2006 Matt Rogers Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "project.h" +#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 #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "projectcontroller.h" #include "uicontroller.h" #include "debug.h" namespace KDevelop { class ProjectProgress : public QObject, public IStatus { Q_OBJECT Q_INTERFACES(KDevelop::IStatus) public: ProjectProgress(); virtual ~ProjectProgress(); virtual QString statusName() const override; /*! Show indeterminate mode progress bar */ void setBuzzy(); /*! Hide progress bar */ void setDone(); QString projectName; private Q_SLOTS: void slotClean(); Q_SIGNALS: void clearMessage(KDevelop::IStatus*) override; void showMessage(KDevelop::IStatus*,const QString & message, int timeout = 0) override; void showErrorMessage(const QString & message, int timeout = 0) override; void hideProgress(KDevelop::IStatus*) override; void showProgress(KDevelop::IStatus*,int minimum, int maximum, int value) override; private: QTimer* m_timer; }; ProjectProgress::ProjectProgress() { m_timer = new QTimer(this); m_timer->setSingleShot( true ); m_timer->setInterval( 1000 ); connect(m_timer, &QTimer::timeout,this, &ProjectProgress::slotClean); } ProjectProgress::~ProjectProgress() { } QString ProjectProgress::statusName() const { return i18n("Loading Project %1", projectName); } void ProjectProgress::setBuzzy() { qCDebug(SHELL) << "showing busy progress" << statusName(); // show an indeterminate progressbar emit showProgress(this, 0,0,0); emit showMessage(this, i18nc("%1: Project name", "Loading %1", projectName)); } void ProjectProgress::setDone() { qCDebug(SHELL) << "showing done progress" << statusName(); // first show 100% bar for a second, then hide. emit showProgress(this, 0,1,1); m_timer->start(); } void ProjectProgress::slotClean() { emit hideProgress(this); emit clearMessage(this); } class ProjectPrivate { public: Path projectPath; Path projectFile; Path developerFile; QString developerTempFile; QTemporaryFile projectTempFile; IPlugin* manager; QPointer vcsPlugin; ProjectFolderItem* topItem; QString name; KSharedConfigPtr m_cfg; IProject *project; QSet fileSet; bool loading; bool fullReload; bool scheduleReload; ProjectProgress* progress; void reloadDone(KJob* job) { progress->setDone(); loading = false; ProjectController* projCtrl = Core::self()->projectControllerInternal(); if (job->error() == 0 && !Core::self()->shuttingDown()) { if(fullReload) projCtrl->projectModel()->appendRow(topItem); if (scheduleReload) { scheduleReload = false; project->reloadModel(); } } else { projCtrl->abortOpeningProject(project); } } QList itemsForPath( const IndexedString& path ) const { if ( path.isEmpty() ) { return QList(); } if (!topItem->model()) { // this gets hit when the project has not yet been added to the model // i.e. during import phase // TODO: should we handle this somehow? // possible idea: make the item<->path hash per-project return QList(); } Q_ASSERT(topItem->model()); QList items = topItem->model()->itemsForPath(path); QList::iterator it = items.begin(); while(it != items.end()) { if ((*it)->project() != project) { it = items.erase(it); } else { ++it; } } return items; } void importDone( KJob* job) { progress->setDone(); ProjectController* projCtrl = Core::self()->projectControllerInternal(); if(job->error() == 0 && !Core::self()->shuttingDown()) { loading=false; projCtrl->projectModel()->appendRow(topItem); projCtrl->projectImportingFinished( project ); } else { projCtrl->abortOpeningProject(project); } } void initProject(const Path& projectFile_) { // helper method for open() projectFile = projectFile_; } bool initProjectFiles() { KIO::StatJob* statJob = KIO::stat( projectFile.toUrl(), KIO::HideProgressInfo ); if ( !statJob->exec() ) //be sync for right now { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Unable to load the project file %1.
" "The project has been removed from the session.", projectFile.pathOrUrl() ) ); return false; } // developerfile == dirname(projectFileUrl) ."/.kdev4/". basename(projectfileUrl) developerFile = projectFile; developerFile.setLastPathSegment( ".kdev4" ); developerFile.addPath( projectFile.lastPathSegment() ); statJob = KIO::stat( developerFile.toUrl(), KIO::HideProgressInfo ); if( !statJob->exec() ) { // the developerfile does not exist yet, check if its folder exists // the developerfile itself will get created below QUrl dir = developerFile.parent().toUrl(); statJob = KIO::stat( dir, KIO::HideProgressInfo ); if( !statJob->exec() ) { KIO::SimpleJob* mkdirJob = KIO::mkdir( dir ); if( !mkdirJob->exec() ) { KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to create hidden dir (%1) for developer file", dir.toDisplayString(QUrl::PreferLocalFile) ) ); return false; } } } projectTempFile.open(); auto copyJob = KIO::file_copy(projectFile.toUrl(), QUrl::fromLocalFile(projectTempFile.fileName()), -1, KIO::Overwrite); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry( Core::self()->uiController()->activeMainWindow(), i18n("Unable to get project file: %1", projectFile.pathOrUrl() ) ); return false; } if(developerFile.isLocalFile()) { developerTempFile = developerFile.toLocalFile(); } else { QTemporaryFile tmp; tmp.open(); developerTempFile = tmp.fileName(); auto job = KIO::file_copy(developerFile.toUrl(), QUrl::fromLocalFile(developerTempFile), -1, KIO::HideProgressInfo | KIO::Overwrite); KJobWidgets::setWindow(job, Core::self()->uiController()->activeMainWindow()); job->exec(); } return true; } KConfigGroup initKConfigObject() { // helper method for open() qCDebug(SHELL) << "Creating KConfig object for project files" << developerTempFile << projectTempFile.fileName(); m_cfg = KSharedConfig::openConfig( developerTempFile ); m_cfg->addConfigSources( QStringList() << projectTempFile.fileName() ); KConfigGroup projectGroup( m_cfg, "Project" ); return projectGroup; } bool projectNameUsed(const KConfigGroup& projectGroup) { // helper method for open() name = projectGroup.readEntry( "Name", projectFile.lastPathSegment() ); progress->projectName = name; if( Core::self()->projectController()->isProjectNameUsed( name ) ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load %1, a project with the same name '%2' is already open.", projectFile.pathOrUrl(), name ) ); qWarning() << "Trying to open a project with a name thats already used by another open project"; return true; } return false; } IProjectFileManager* fetchFileManager(const KConfigGroup& projectGroup) { if (manager) { IProjectFileManager* iface = 0; iface = manager->extension(); Q_ASSERT(iface); return iface; } // helper method for open() QString managerSetting = projectGroup.readEntry( "Manager", "KDevGenericManager" ); //Get our importer IPluginController* pluginManager = Core::self()->pluginController(); manager = pluginManager->pluginForExtension( "org.kdevelop.IProjectFileManager", managerSetting ); IProjectFileManager* iface = 0; if ( manager ) iface = manager->extension(); else { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "Could not load project management plugin %1.", managerSetting ) ); manager = 0; return 0; } if (iface == 0) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n( "project importing plugin (%1) does not support the IProjectFileManager interface.", managerSetting ) ); delete manager; manager = 0; } return iface; } void loadVersionControlPlugin(KConfigGroup& projectGroup) { // helper method for open() IPluginController* pluginManager = Core::self()->pluginController(); if( projectGroup.hasKey( "VersionControlSupport" ) ) { QString vcsPluginName = projectGroup.readEntry("VersionControlSupport", ""); if( !vcsPluginName.isEmpty() ) { vcsPlugin = pluginManager->pluginForExtension( "org.kdevelop.IBasicVersionControl", vcsPluginName ); } } else { foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IBasicVersionControl" ) ) { IBasicVersionControl* iface = p->extension(); if( iface && iface->isVersionControlled( topItem->path().toUrl() ) ) { vcsPlugin = p; projectGroup.writeEntry("VersionControlSupport", pluginManager->pluginInfo(p).pluginId()); projectGroup.sync(); } } } } bool importTopItem(IProjectFileManager* fileManager) { if (!fileManager) { return false; } topItem = fileManager->import( project ); if( !topItem ) { KMessageBox::sorry( Core::self()->uiControllerInternal()->defaultMainWindow(), i18n("Could not open project") ); return false; } return true; } }; Project::Project( QObject *parent ) : IProject( parent ) , d( new ProjectPrivate ) { QDBusConnection::sessionBus().registerObject( "/org/kdevelop/Project", this, QDBusConnection::ExportScriptableSlots ); d->project = this; d->manager = 0; d->topItem = 0; d->loading = false; d->scheduleReload = false; d->progress = new ProjectProgress; Core::self()->uiController()->registerStatus( d->progress ); } Project::~Project() { delete d->progress; delete d; } QString Project::name() const { return d->name; } QString Project::developerTempFile() const { return d->developerTempFile; } QString Project::projectTempFile() const { return d->projectTempFile.fileName(); } KSharedConfigPtr Project::projectConfiguration() const { return d->m_cfg; } const QUrl Project::folder() const { QUrl url = d->projectPath.toUrl(); // FIXME: is something like this required here? // url.adjustPath(QUrl::AddTrailingSlash); return url; } Path Project::path() const { return d->projectPath; } void Project::reloadModel() { if (d->loading) { d->scheduleReload = true; return; } d->loading = true; d->fileSet.clear(); // delete topItem and remove it from model ProjectModel* model = Core::self()->projectController()->projectModel(); model->removeRow( d->topItem->row() ); d->topItem = 0; IProjectFileManager* iface = d->manager->extension(); if (!d->importTopItem(iface)) { d->loading = false; d->scheduleReload = false; return; } KJob* importJob = iface->createImportJob(d->topItem ); setReloadJob(importJob); d->fullReload = true; Core::self()->runController()->registerJob( importJob ); } void Project::setReloadJob(KJob* job) { d->loading = true; d->fullReload = false; d->progress->setBuzzy(); connect(job, &KJob::finished, this, [&] (KJob* job) { d->reloadDone(job); }); } bool Project::open( const Path& projectFile ) { d->initProject(projectFile); if (!d->initProjectFiles()) return false; KConfigGroup projectGroup = d->initKConfigObject(); if (d->projectNameUsed(projectGroup)) return false; d->projectPath = d->projectFile.parent(); IProjectFileManager* iface = d->fetchFileManager(projectGroup); if (!iface) return false; Q_ASSERT(d->manager); emit aboutToOpen(this); if (!d->importTopItem(iface) ) { return false; } d->loading=true; d->loadVersionControlPlugin(projectGroup); d->progress->setBuzzy(); KJob* importJob = iface->createImportJob(d->topItem ); connect( importJob, &KJob::result, this, [&] (KJob* job) { d->importDone(job); } ); Core::self()->runController()->registerJob( importJob ); return true; } void Project::close() { Q_ASSERT(d->topItem); if (d->topItem->row() == -1) { qWarning() << "Something went wrong. ProjectFolderItem detached. Project closed during reload?"; return; } Core::self()->projectController()->projectModel()->removeRow( d->topItem->row() ); if (!d->developerFile.isLocalFile()) { auto copyJob = KIO::file_copy(QUrl::fromLocalFile(d->developerTempFile), d->developerFile.toUrl()); KJobWidgets::setWindow(copyJob, Core::self()->uiController()->activeMainWindow()); if (!copyJob->exec()) { qCDebug(SHELL) << "Job failed:" << copyJob->errorString(); KMessageBox::sorry(Core::self()->uiController()->activeMainWindow(), i18n("Could not store developer specific project configuration.\n" "Attention: The project settings you changed will be lost.")); } } } bool Project::inProject( const IndexedString& path ) const { if (d->fileSet.contains( path )) { return true; } return !d->itemsForPath( path ).isEmpty(); } QList< ProjectBaseItem* > Project::itemsForUrl(const QUrl &url) const { return d->itemsForPath(IndexedString(url)); } QList< ProjectBaseItem* > Project::itemsForPath(const IndexedString& path) const { return d->itemsForPath(path); } QList Project::filesForUrl(const QUrl &url) const { return filesForPath(IndexedString(url)); } QList< ProjectFileItem* > Project::filesForPath(const IndexedString& file) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( file ) ) { if( item->type() == ProjectBaseItem::File ) items << dynamic_cast( item ); } return items; } QList< ProjectFolderItem* > Project::foldersForUrl(const QUrl &url) const { return foldersForPath(IndexedString(url)); } QList Project::foldersForPath(const IndexedString& folder) const { QList items; foreach(ProjectBaseItem* item, d->itemsForPath( folder ) ) { if( item->type() == ProjectBaseItem::Folder || item->type() == ProjectBaseItem::BuildFolder ) items << dynamic_cast( item ); } return items; } IProjectFileManager* Project::projectFileManager() const { return d->manager->extension(); } IBuildSystemManager* Project::buildSystemManager() const { return d->manager->extension(); } IPlugin* Project::managerPlugin() const { return d->manager; } void Project::setManagerPlugin( IPlugin* manager ) { d->manager = manager; } Path Project::projectFile() const { return d->projectFile; } QUrl Project::projectFileUrl() const { return d->projectFile.toUrl(); } Path Project::developerFile() const { return d->developerFile; } ProjectFolderItem* Project::projectItem() const { return d->topItem; } IPlugin* Project::versionControlPlugin() const { return d->vcsPlugin.data(); } void Project::addToFileSet( ProjectFileItem* file ) { if (d->fileSet.contains(file->indexedPath())) { return; } d->fileSet.insert( file->indexedPath() ); emit fileAddedToSet( file ); } void Project::removeFromFileSet( ProjectFileItem* file ) { QSet::iterator it = d->fileSet.find(file->indexedPath()); if (it == d->fileSet.end()) { return; } d->fileSet.erase( it ); emit fileRemovedFromSet( file ); } QSet Project::fileSet() const { return d->fileSet; } bool Project::isReady() const { return !d->loading; } } // namespace KDevelop #include "project.moc" #include "moc_project.cpp" diff --git a/shell/projectcontroller.cpp b/shell/projectcontroller.cpp index c8e89e8725..97b8e9b0d1 100644 --- a/shell/projectcontroller.cpp +++ b/shell/projectcontroller.cpp @@ -1,1183 +1,1178 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "projectcontroller.h" -#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 +#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 #include #include #include #include #include #include #include #include +#include +#include +#include +#include #include "core.h" #include "project.h" #include "mainwindow.h" #include "shellextension.h" #include "plugincontroller.h" #include "configdialog.h" #include "uicontroller.h" #include "documentcontroller.h" #include "openprojectdialog.h" -#include -#include -#include -#include #include "sessioncontroller.h" #include "session.h" #include "debug.h" -#include -#include -#include -#include -#include namespace KDevelop { class ProjectControllerPrivate { public: QList m_projects; QMap< IProject*, QList > m_projectPlugins; QPointer m_recentAction; Core* m_core; // IProject* m_currentProject; ProjectModel* model; QItemSelectionModel* selectionModel; QPointer m_openProject; QPointer m_fetchProject; QPointer m_closeProject; QPointer m_openConfig; IProjectDialogProvider* dialog; QList m_currentlyOpening; // project-file urls that are being opened IProject* m_configuringProject; ProjectController* q; ProjectBuildSetModel* buildset; bool m_foundProjectFile; //Temporary flag used while searching the hierarchy for a project file bool m_cleaningUp; //Temporary flag enabled while destroying the project-controller QPointer m_changesModel; QHash< IProject*, QPointer > m_parseJobs; // parse jobs that add files from the project to the background parser. ProjectControllerPrivate( ProjectController* p ) : m_core(0), model(0), selectionModel(0), dialog(0), m_configuringProject(0), q(p), buildset(0), m_foundProjectFile(false), m_cleaningUp(false) { } void unloadAllProjectPlugins() { if( m_projects.isEmpty() ) m_core->pluginControllerInternal()->unloadProjectPlugins(); } void projectConfig( QObject * obj ) { if( !obj ) return; Project* proj = qobject_cast(obj); if( !proj ) return; QVector configPages; auto mainWindow = m_core->uiController()->activeMainWindow(); ProjectConfigOptions options; options.developerFile = proj->developerFile(); options.developerTempFile = proj->developerTempFile(); options.projectTempFile = proj->projectTempFile(); options.project = proj; for (IPlugin* plugin : findPluginsForProject(proj)) { for (int i = 0; i < plugin->perProjectConfigPages(); ++i) { configPages.append(plugin->perProjectConfigPage(i, options, mainWindow)); } } Q_ASSERT(!m_configuringProject); m_configuringProject = proj; KDevelop::ConfigDialog cfgDlg(configPages, mainWindow); QObject::connect(&cfgDlg, &ConfigDialog::configSaved, &cfgDlg, [this](ConfigPage* page) { Q_UNUSED(page) Q_ASSERT_X(m_configuringProject, Q_FUNC_INFO, "ConfigDialog signalled project config change, but no project set for configuring!"); emit q->projectConfigurationChanged(m_configuringProject); }); cfgDlg.setWindowTitle(i18n("Configure Project %1", proj->name())); cfgDlg.exec(); proj->projectConfiguration()->sync(); m_configuringProject = nullptr; } void saveListOfOpenedProjects() { auto activeSession = Core::self()->activeSession(); if (!activeSession) { return; } QList openProjects; openProjects.reserve( m_projects.size() ); foreach( IProject* project, m_projects ) { openProjects.append(project->projectFile().toUrl()); } activeSession->setContainedProjects( openProjects ); } // Recursively collects builder dependencies for a project. static void collectBuilders( QList< IProjectBuilder* >& destination, IProjectBuilder* topBuilder, IProject* project ) { QList< IProjectBuilder* > auxBuilders = topBuilder->additionalBuilderPlugins( project ); destination.append( auxBuilders ); foreach( IProjectBuilder* auxBuilder, auxBuilders ) { collectBuilders( destination, auxBuilder, project ); } } QVector findPluginsForProject( IProject* project ) { QList plugins = m_core->pluginController()->loadedPlugins(); QVector projectPlugins; QList buildersForKcm; // Important to also include the "top" builder for the project, so // projects with only one such builder are kept working. Otherwise the project config // dialog is empty for such cases. if( IBuildSystemManager* buildSystemManager = project->buildSystemManager() ) { buildersForKcm << buildSystemManager->builder(); collectBuilders( buildersForKcm, buildSystemManager->builder(), project ); } for(auto plugin : plugins) { auto info = m_core->pluginController()->pluginInfo(plugin); IProjectFileManager* manager = plugin->extension(); if( manager && manager != project->projectFileManager() ) { // current plugin is a manager but does not apply to given project, skip continue; } IProjectBuilder* builder = plugin->extension(); if ( builder && !buildersForKcm.contains( builder ) ) { continue; } qCDebug(SHELL) << "Using plugin" << info.pluginId() << "for project" << project->name(); projectPlugins << plugin; } return projectPlugins; } void updateActionStates( Context* ctx ) { ProjectItemContext* itemctx = dynamic_cast(ctx); m_openConfig->setEnabled( itemctx && itemctx->items().count() == 1 ); m_closeProject->setEnabled( itemctx && itemctx->items().count() > 0 ); } void openProjectConfig() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() == 1 ) { q->configureProject( ctx->items().at(0)->project() ); } } void closeSelectedProjects() { ProjectItemContext* ctx = dynamic_cast( Core::self()->selectionController()->currentSelection() ); if( ctx && ctx->items().count() > 0 ) { QSet projects; foreach( ProjectBaseItem* item, ctx->items() ) { projects.insert( item->project() ); } foreach( IProject* project, projects ) { q->closeProject( project ); } } } void importProject(const QUrl& url_) { QUrl url(url_); if ( url.isLocalFile() ) { QString path = QFileInfo( url.toLocalFile() ).canonicalFilePath(); if ( !path.isEmpty() ) url.setPath( path ); } if ( !url.isValid() ) { KMessageBox::error(Core::self()->uiControllerInternal()->activeMainWindow(), i18n("Invalid Location: %1", url.toDisplayString(QUrl::PreferLocalFile))); return; } if ( m_currentlyOpening.contains(url)) { qCDebug(SHELL) << "Already opening " << url << ". Aborting."; KPassivePopup::message( i18n( "Project already being opened"), i18n( "Already opening %1, not opening again", url.toDisplayString(QUrl::PreferLocalFile) ), m_core->uiController()->activeMainWindow() ); return; } foreach( IProject* project, m_projects ) { if ( url == project->projectFileUrl() ) { if ( dialog->userWantsReopen() ) { // close first, then open again by falling through q->closeProject(project); } else { // abort return; } } } m_currentlyOpening += url; m_core->pluginControllerInternal()->loadProjectPlugins(); Project* project = new Project(); QObject::connect(project, &Project::aboutToOpen, q, &ProjectController::projectAboutToBeOpened); if ( !project->open( Path(url) ) ) { m_currentlyOpening.removeAll(url); q->abortOpeningProject(project); project->deleteLater(); } } void areaChanged(Sublime::Area* area) { KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); ac->action("commit_current_project")->setEnabled(area->objectName() == "code"); ac->action("commit_current_project")->setVisible(area->objectName() == "code"); } }; IProjectDialogProvider::IProjectDialogProvider() {} IProjectDialogProvider::~IProjectDialogProvider() {} ProjectDialogProvider::ProjectDialogProvider(ProjectControllerPrivate* const p) : d(p) {} ProjectDialogProvider::~ProjectDialogProvider() {} bool writeNewProjectFile( const QString& localConfigFile, const QString& name, const QString& manager ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( localConfigFile, KConfig::SimpleConfig ); if (!cfg->isConfigWritable(true)) { qCDebug(SHELL) << "can't write to configfile"; return false; } KConfigGroup grp = cfg->group( "Project" ); grp.writeEntry( "Name", name ); grp.writeEntry( "Manager", manager ); cfg->sync(); return true; } bool writeProjectSettingsToConfigFile(const QUrl& projectFileUrl, const QString& projectName, const QString& projectManager) { if ( !projectFileUrl.isLocalFile() ) { QTemporaryFile tmp; if ( !tmp.open() ) { return false; } if ( !writeNewProjectFile( tmp.fileName(), projectName, projectManager ) ) { return false; } // explicitly close file before uploading it, see also: https://bugs.kde.org/show_bug.cgi?id=254519 tmp.close(); auto uploadJob = KIO::file_copy(QUrl::fromLocalFile(tmp.fileName()), projectFileUrl); KJobWidgets::setWindow(uploadJob, Core::self()->uiControllerInternal()->defaultMainWindow()); return uploadJob->exec(); } return writeNewProjectFile( projectFileUrl.toLocalFile(),projectName, projectManager ); } bool projectFileExists( const QUrl& u ) { if( u.isLocalFile() ) { return QFileInfo( u.toLocalFile() ).exists(); } else { auto statJob = KIO::stat(u, KIO::StatJob::DestinationSide, 0); KJobWidgets::setWindow(statJob, Core::self()->uiControllerInternal()->activeMainWindow()); return statJob->exec(); } } bool equalProjectFile( const QString& configPath, OpenProjectDialog* dlg ) { KSharedConfigPtr cfg = KSharedConfig::openConfig( configPath, KConfig::SimpleConfig ); KConfigGroup grp = cfg->group( "Project" ); QString defaultName = dlg->projectFileUrl().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash).fileName(); return (grp.readEntry( "Name", QString() ) == dlg->projectName() || dlg->projectName() == defaultName) && grp.readEntry( "Manager", QString() ) == dlg->projectManager(); } QUrl ProjectDialogProvider::askProjectConfigLocation(bool fetch, const QUrl& startUrl) { Q_ASSERT(d); OpenProjectDialog dlg( fetch, startUrl, Core::self()->uiController()->activeMainWindow() ); if(dlg.exec() == QDialog::Rejected) return QUrl(); QUrl projectFileUrl = dlg.projectFileUrl(); qCDebug(SHELL) << "selected project:" << projectFileUrl << dlg.projectName() << dlg.projectManager(); // controls if existing project file should be saved bool writeProjectConfigToFile = true; if( projectFileExists( projectFileUrl ) ) { // check whether config is equal bool shouldAsk = true; if( projectFileUrl.isLocalFile() ) { shouldAsk = !equalProjectFile( projectFileUrl.toLocalFile(), &dlg ); } else { shouldAsk = false; QTemporaryFile tmpFile; if (tmpFile.open()) { auto downloadJob = KIO::file_copy(projectFileUrl, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(downloadJob, qApp->activeWindow()); if (downloadJob->exec()) { shouldAsk = !equalProjectFile(tmpFile.fileName(), &dlg); } } } if ( shouldAsk ) { KGuiItem yes = KStandardGuiItem::yes(); yes.setText(i18n("Override")); yes.setToolTip(i18nc("@info:tooltip", "Continue to open the project and use the just provided project configuration.")); yes.setIcon(QIcon()); KGuiItem no = KStandardGuiItem::no(); no.setText(i18n("Open Existing File")); no.setToolTip(i18nc("@info:tooltip", "Continue to open the project but use the existing project configuration.")); no.setIcon(QIcon()); KGuiItem cancel = KStandardGuiItem::cancel(); cancel.setToolTip(i18nc("@info:tooltip", "Cancel and do not open the project.")); int ret = KMessageBox::questionYesNoCancel(qApp->activeWindow(), i18n("There already exists a project configuration file at %1.\n" "Do you want to override it or open the existing file?", projectFileUrl.toDisplayString(QUrl::PreferLocalFile)), i18n("Override existing project configuration"), yes, no, cancel ); if ( ret == KMessageBox::No ) { writeProjectConfigToFile = false; } else if ( ret == KMessageBox::Cancel ) { return QUrl(); } // else fall through and write new file } else { writeProjectConfigToFile = false; } } if (writeProjectConfigToFile) { if (!writeProjectSettingsToConfigFile(projectFileUrl, dlg.projectName(), dlg.projectManager())) { KMessageBox::error(d->m_core->uiControllerInternal()->defaultMainWindow(), i18n("Unable to create configuration file %1", projectFileUrl.url())); return QUrl(); } } return projectFileUrl; } bool ProjectDialogProvider::userWantsReopen() { Q_ASSERT(d); return (KMessageBox::questionYesNo( d->m_core->uiControllerInternal()->defaultMainWindow(), i18n( "Reopen the current project?" ) ) == KMessageBox::No) ? false : true; } void ProjectController::setDialogProvider(IProjectDialogProvider* dialog) { Q_ASSERT(d->dialog); delete d->dialog; d->dialog = dialog; } ProjectController::ProjectController( Core* core ) : IProjectController( core ), d( new ProjectControllerPrivate( this ) ) { qRegisterMetaType>(); setObjectName("ProjectController"); d->m_core = core; d->model = new ProjectModel(); //NOTE: this is required to be called here, such that the // actions are available when the UI controller gets // initialized *before* the project controller if (Core::self()->setupFlags() != Core::NoUi) { setupActions(); } } void ProjectController::setupActions() { KActionCollection * ac = d->m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); QAction*action; d->m_openProject = action = ac->addAction( "project_open" ); action->setText(i18nc( "@action", "Open / Import Project..." ) ); action->setToolTip( i18nc( "@info:tooltip", "Open or import project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Open an existing KDevelop 4 project or import " "an existing Project into KDevelop 4. This entry " "allows to select a KDevelop4 project file or an " "existing directory to open it in KDevelop. " "When opening an existing directory that does " "not yet have a KDevelop4 project file, the file " "will be created." ) ); action->setIcon(QIcon::fromTheme("project-open")); connect(action, &QAction::triggered, this, [&] { openProject(); }); d->m_fetchProject = action = ac->addAction( "project_fetch" ); action->setText(i18nc( "@action", "Fetch Project..." ) ); action->setIcon( QIcon::fromTheme( "download" ) ); action->setToolTip( i18nc( "@info:tooltip", "Fetch project" ) ); action->setWhatsThis( i18nc( "@info:whatsthis", "Guides the user through the project fetch " "and then imports it into KDevelop 4." ) ); // action->setIcon(QIcon::fromTheme("project-open")); connect( action, &QAction::triggered, this, &ProjectController::fetchProject ); // action = ac->addAction( "project_close" ); // action->setText( i18n( "C&lose Project" ) ); // connect( action, SIGNAL(triggered(bool)), SLOT(closeProject()) ); // action->setToolTip( i18n( "Close project" ) ); // action->setWhatsThis( i18n( "Closes the current project." ) ); // action->setEnabled( false ); d->m_closeProject = action = ac->addAction( "project_close" ); connect( action, &QAction::triggered, this, [&] { d->closeSelectedProjects(); } ); action->setText( i18nc( "@action", "Close Project(s)" ) ); action->setIcon( QIcon::fromTheme( "project-development-close" ) ); action->setToolTip( i18nc( "@info:tooltip", "Closes all currently selected projects" ) ); action->setEnabled( false ); d->m_openConfig = action = ac->addAction( "project_open_config" ); connect( action, &QAction::triggered, this, [&] { d->openProjectConfig(); } ); action->setText( i18n( "Open Configuration..." ) ); action->setIcon( QIcon::fromTheme("configure") ); action->setEnabled( false ); action = ac->addAction( "commit_current_project" ); connect( action, &QAction::triggered, this, &ProjectController::commitCurrentProject ); action->setText( i18n( "Commit Current Project..." ) ); action->setIconText( i18n( "Commit..." ) ); action->setIcon( QIcon::fromTheme("svn-commit") ); connect(d->m_core->uiControllerInternal()->defaultMainWindow(), &MainWindow::areaChanged, this, [&] (Sublime::Area* area) { d->areaChanged(area); }); d->m_core->uiControllerInternal()->area(0, "code")->addAction(action); KSharedConfig * config = KSharedConfig::openConfig().data(); // KConfigGroup group = config->group( "General Options" ); d->m_recentAction = KStandardAction::openRecent(this, SLOT(openProject(QUrl)), this); ac->addAction( "project_open_recent", d->m_recentAction ); d->m_recentAction->setText( i18n( "Open Recent Project" ) ); d->m_recentAction->setWhatsThis( i18nc( "@info:whatsthis", "Opens recently opened project." ) ); d->m_recentAction->loadEntries( KConfigGroup(config, "RecentProjects") ); QAction* openProjectForFileAction = new QAction( this ); ac->addAction("project_open_for_file", openProjectForFileAction); openProjectForFileAction->setText(i18n("Open Project for Current File")); connect( openProjectForFileAction, &QAction::triggered, this, &ProjectController::openProjectForUrlSlot); } ProjectController::~ProjectController() { delete d->model; delete d->dialog; delete d; } void ProjectController::cleanup() { if ( d->m_currentlyOpening.isEmpty() ) { d->saveListOfOpenedProjects(); } d->m_cleaningUp = true; if( buildSetModel() ) { buildSetModel()->storeToSession( Core::self()->activeSession() ); } foreach( IProject* project, d->m_projects ) { closeProject( project ); } } void ProjectController::initialize() { d->buildset = new ProjectBuildSetModel( this ); buildSetModel()->loadFromSession( Core::self()->activeSession() ); connect( this, &ProjectController::projectOpened, d->buildset, &ProjectBuildSetModel::loadFromProject ); connect( this, &ProjectController::projectClosing, d->buildset, &ProjectBuildSetModel::saveToProject ); connect( this, &ProjectController::projectClosed, d->buildset, &ProjectBuildSetModel::projectClosed ); d->selectionModel = new QItemSelectionModel(d->model); loadSettings(false); d->dialog = new ProjectDialogProvider(d); QDBusConnection::sessionBus().registerObject( "/org/kdevelop/ProjectController", this, QDBusConnection::ExportScriptableSlots ); KSharedConfigPtr config = Core::self()->activeSession()->config(); KConfigGroup group = config->group( "General Options" ); QList openProjects = group.readEntry( "Open Projects", QList() ); QMetaObject::invokeMethod(this, "openProjects", Qt::QueuedConnection, Q_ARG(QList, openProjects)); connect( Core::self()->selectionController(), &ISelectionController::selectionChanged, this, [&] (Context* ctx) { d->updateActionStates(ctx); } ); } void ProjectController::openProjects(const QList& projects) { foreach (const QUrl& url, projects) openProject(url); } void ProjectController::loadSettings( bool projectIsLoaded ) { Q_UNUSED(projectIsLoaded) } void ProjectController::saveSettings( bool projectIsLoaded ) { Q_UNUSED( projectIsLoaded ); } int ProjectController::projectCount() const { return d->m_projects.count(); } IProject* ProjectController::projectAt( int num ) const { if( !d->m_projects.isEmpty() && num >= 0 && num < d->m_projects.count() ) return d->m_projects.at( num ); return 0; } QList ProjectController::projects() const { return d->m_projects; } void ProjectController::eventuallyOpenProjectFile(KIO::Job* _job, KIO::UDSEntryList entries ) { KIO::SimpleJob* job(dynamic_cast(_job)); Q_ASSERT(job); foreach(const KIO::UDSEntry& entry, entries) { if(d->m_foundProjectFile) break; if(!entry.isDir()) { QString name = entry.stringValue( KIO::UDSEntry::UDS_NAME ); if(name.endsWith(".kdev4")) { //We have found a project-file, open it openProject(Path(Path(job->url()), name).toUrl()); d->m_foundProjectFile = true; } } } } void ProjectController::openProjectForUrlSlot(bool) { if(ICore::self()->documentController()->activeDocument()) { QUrl url = ICore::self()->documentController()->activeDocument()->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(!project) { openProjectForUrl(url); }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("Project already open: %1", project->name())); } }else{ KMessageBox::error(Core::self()->uiController()->activeMainWindow(), i18n("No active document")); } } void ProjectController::openProjectForUrl(const QUrl& sourceUrl) { Q_ASSERT(!sourceUrl.isRelative()); QUrl dirUrl = sourceUrl.adjusted(QUrl::RemoveFilename); QUrl testAt = dirUrl; d->m_foundProjectFile = false; while(!testAt.path().isEmpty()) { QUrl testProjectFile(testAt); KIO::ListJob* job = KIO::listDir(testAt); connect(job, &KIO::ListJob::entries, this, &ProjectController::eventuallyOpenProjectFile); KJobWidgets::setWindow(job, ICore::self()->uiController()->activeMainWindow()); job->exec(); if(d->m_foundProjectFile) { //Fine! We have directly opened the project d->m_foundProjectFile = false; return; } QUrl oldTest = testAt.adjusted(QUrl::RemoveFilename); if(oldTest == testAt) break; } QUrl askForOpen = d->dialog->askProjectConfigLocation(false, dirUrl); if(askForOpen.isValid()) openProject(askForOpen); } void ProjectController::openProject( const QUrl &projectFile ) { QUrl url = projectFile; if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); if ( url.isEmpty() ) { return; } } Q_ASSERT(!url.isRelative()); QList existingSessions; if(!Core::self()->sessionController()->activeSession()->containedProjects().contains(url)) { foreach( const Session* session, Core::self()->sessionController()->sessions()) { if(session->containedProjects().contains(url)) { existingSessions << session; #if 0 ///@todo Think about this! Problem: The session might already contain files, the debugger might be active, etc. //If this session is empty, close it if(Core::self()->sessionController()->activeSession()->description().isEmpty()) { //Terminate this instance of kdevelop if the user agrees foreach(Sublime::MainWindow* window, Core::self()->uiController()->controller()->mainWindows()) window->close(); } #endif } } } if ( ! existingSessions.isEmpty() ) { QDialog dialog(Core::self()->uiControllerInternal()->activeMainWindow()); dialog.setWindowTitle(i18n("Project Already Open")); auto mainLayout = new QVBoxLayout(&dialog); mainLayout->addWidget(new QLabel(i18n("The project you're trying to open is already open in at least one " "other session.
What do you want to do?"))); QGroupBox sessions; sessions.setLayout(new QVBoxLayout); QRadioButton* newSession = new QRadioButton(i18n("Add project to current session")); sessions.layout()->addWidget(newSession); newSession->setChecked(true); foreach ( const Session* session, existingSessions ) { QRadioButton* button = new QRadioButton(i18n("Open session %1", session->description())); button->setProperty("sessionid", session->id().toString()); sessions.layout()->addWidget(button); } sessions.layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding)); mainLayout->addWidget(&sessions); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Abort); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); mainLayout->addWidget(buttonBox); bool success = dialog.exec(); if (!success) return; foreach ( const QObject* obj, sessions.children() ) { if ( const QRadioButton* button = qobject_cast(obj) ) { QString sessionid = button->property("sessionid").toString(); if ( button->isChecked() && ! sessionid.isEmpty() ) { Core::self()->sessionController()->loadSession(sessionid); return; } } } } if ( url.isEmpty() ) { url = d->dialog->askProjectConfigLocation(false); } if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::fetchProject() { QUrl url = d->dialog->askProjectConfigLocation(true); if ( !url.isEmpty() ) { d->importProject(url); } } void ProjectController::projectImportingFinished( IProject* project ) { if( !project ) { qWarning() << "OOOPS: 0-pointer project"; return; } IPlugin *managerPlugin = project->managerPlugin(); QList pluglist; pluglist.append( managerPlugin ); d->m_projectPlugins.insert( project, pluglist ); d->m_projects.append( project ); d->saveListOfOpenedProjects(); if (Core::self()->setupFlags() != Core::NoUi) { d->m_recentAction->addUrl( project->projectFileUrl() ); KSharedConfig * config = KSharedConfig::openConfig().data(); KConfigGroup recentGroup = config->group("RecentProjects"); d->m_recentAction->saveEntries( recentGroup ); config->sync(); } Q_ASSERT(d->m_currentlyOpening.contains(project->projectFileUrl())); d->m_currentlyOpening.removeAll(project->projectFileUrl()); emit projectOpened( project ); reparseProject(project); } // helper method for closeProject() void ProjectController::unloadUnusedProjectPlugins(IProject* proj) { QList pluginsForProj = d->m_projectPlugins.value( proj ); d->m_projectPlugins.remove( proj ); QList otherProjectPlugins; Q_FOREACH( const QList& _list, d->m_projectPlugins ) { otherProjectPlugins << _list; } QSet pluginsForProjSet = QSet::fromList( pluginsForProj ); QSet otherPrjPluginsSet = QSet::fromList( otherProjectPlugins ); // loaded - target = tobe unloaded. QSet tobeRemoved = pluginsForProjSet.subtract( otherPrjPluginsSet ); Q_FOREACH( IPlugin* _plugin, tobeRemoved ) { KPluginMetaData _plugInfo = Core::self()->pluginController()->pluginInfo( _plugin ); if( _plugInfo.isValid() ) { QString _plugName = _plugInfo.pluginId(); qCDebug(SHELL) << "about to unloading :" << _plugName; Core::self()->pluginController()->unloadPlugin( _plugName ); } } } // helper method for closeProject() void ProjectController::closeAllOpenedFiles(IProject* proj) { foreach(IDocument* doc, Core::self()->documentController()->openDocuments()) { if (proj->inProject(IndexedString(doc->url()))) { doc->close(); } } } // helper method for closeProject() void ProjectController::initializePluginCleanup(IProject* proj) { // Unloading (and thus deleting) these plugins is not a good idea just yet // as we're being called by the view part and it gets deleted when we unload the plugin(s) // TODO: find a better place to unload connect(proj, &IProject::destroyed, this, [&] { d->unloadAllProjectPlugins(); }); } void ProjectController::closeProject(IProject* proj_) { if (!proj_) { return; } // loading might have failed d->m_currentlyOpening.removeAll(proj_->projectFileUrl()); Project* proj = dynamic_cast( proj_ ); if( !proj ) { qWarning() << "Unknown Project subclass found!"; return; } d->m_projects.removeAll(proj); emit projectClosing(proj); //Core::self()->saveSettings(); // The project file is being closed. // Now we can save settings for all of the Core // objects including this one!! unloadUnusedProjectPlugins(proj); closeAllOpenedFiles(proj); proj->close(); proj->deleteLater(); //be safe when deleting if (d->m_projects.isEmpty()) { initializePluginCleanup(proj); } if(!d->m_cleaningUp) d->saveListOfOpenedProjects(); emit projectClosed(proj); return; } void ProjectController::abortOpeningProject(IProject* proj) { d->m_currentlyOpening.removeAll(proj->projectFileUrl()); emit projectOpeningAborted(proj); } ProjectModel* ProjectController::projectModel() { return d->model; } IProject* ProjectController::findProjectForUrl( const QUrl& url ) const { if (d->m_projects.isEmpty()) { return 0; } ProjectBaseItem* item = d->model->itemForPath(IndexedString(url)); if (item) { return item->project(); } return 0; } IProject* ProjectController::findProjectByName( const QString& name ) { Q_FOREACH( IProject* proj, d->m_projects ) { if( proj->name() == name ) { return proj; } } return 0; } void ProjectController::configureProject( IProject* project ) { d->projectConfig( project ); } void ProjectController::addProject(IProject* project) { d->m_projects.append( project ); } QItemSelectionModel* ProjectController::projectSelectionModel() { return d->selectionModel; } bool ProjectController::isProjectNameUsed( const QString& name ) const { foreach( IProject* p, projects() ) { if( p->name() == name ) { return true; } } return false; } QUrl ProjectController::projectsBaseDirectory() const { KConfigGroup group = ICore::self()->activeSession()->config()->group( "Project Manager" ); return group.readEntry( "Projects Base Directory", QUrl::fromLocalFile( QDir::homePath() + "/projects" ) ); } QString ProjectController::prettyFilePath(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(!project) { // Find a project with the correct base directory at least foreach(IProject* candidateProject, Core::self()->projectController()->projects()) { if(candidateProject->folder().isParentOf(url)) { project = candidateProject; break; } } } Path parent = Path(url).parent(); QString prefixText; if (project) { if (format == FormatHtml) { prefixText = "" + project->name() + "/"; } else { prefixText = project->name() + '/'; } QString relativePath = project->path().relativePath(parent); if(relativePath.startsWith("./")) { relativePath = relativePath.mid(2); } if (!relativePath.isEmpty()) { prefixText += relativePath + '/'; } } else { prefixText = parent.pathOrUrl() + '/'; } return prefixText; } QString ProjectController::prettyFileName(const QUrl& url, FormattingOptions format) const { IProject* project = Core::self()->projectController()->findProjectForUrl(url); if(project && project->path() == Path(url)) { if (format == FormatHtml) { return "" + project->name() + ""; } else { return project->name(); } } QString prefixText = prettyFilePath( url, format ); if (format == FormatHtml) { return prefixText + "" + url.fileName() + ""; } else { return prefixText + url.fileName(); } } ContextMenuExtension ProjectController::contextMenuExtension ( Context* ctx ) { ContextMenuExtension ext; if ( ctx->type() != Context::ProjectItemContext || !static_cast(ctx)->items().isEmpty() ) { return ext; } ext.addAction(ContextMenuExtension::ProjectGroup, d->m_openProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_fetchProject); ext.addAction(ContextMenuExtension::ProjectGroup, d->m_recentAction); return ext; } ProjectBuildSetModel* ProjectController::buildSetModel() { return d->buildset; } ProjectChangesModel* ProjectController::changesModel() { if(!d->m_changesModel) d->m_changesModel=new ProjectChangesModel(this); return d->m_changesModel; } void ProjectController::commitCurrentProject() { IDocument* doc=ICore::self()->documentController()->activeDocument(); if(!doc) return; QUrl url=doc->url(); IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if(project && project->versionControlPlugin()) { IPlugin* plugin = project->versionControlPlugin(); IBasicVersionControl* vcs=plugin->extension(); if(vcs) { ICore::self()->documentController()->saveAllDocuments(KDevelop::IDocument::Silent); const Path basePath = project->path(); VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(vcs, basePath.toUrl())); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } } } QString ProjectController::mapSourceBuild( const QString& path_, bool reverse, bool fallbackRoot ) const { Path path(path_); IProject* sourceDirProject = 0, *buildDirProject = 0; Q_FOREACH(IProject* proj, d->m_projects) { if(proj->path().isParentOf(path)) sourceDirProject = proj; if(proj->buildSystemManager()) { Path buildDir = proj->buildSystemManager()->buildDirectory(proj->projectItem()); if(buildDir.isValid() && buildDir.isParentOf(path)) buildDirProject = proj; } } if(!reverse) { // Map-target is the build directory if(sourceDirProject && sourceDirProject->buildSystemManager()) { // We're in the source, map into the build directory QString relativePath = sourceDirProject->path().relativePath(path); Path build = sourceDirProject->buildSystemManager()->buildDirectory(sourceDirProject->projectItem()); build.addPath(relativePath); while(!QFile::exists(build.path())) build = build.parent(); return build.pathOrUrl(); }else if(buildDirProject && fallbackRoot) { // We're in the build directory, map to the build directory root return buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()).pathOrUrl(); } }else{ // Map-target is the source directory if(buildDirProject) { Path build = buildDirProject->buildSystemManager()->buildDirectory(buildDirProject->projectItem()); // We're in the source, map into the build directory QString relativePath = build.relativePath(path); Path source = buildDirProject->path(); source.addPath(relativePath); while(!QFile::exists(source.path())) source = source.parent(); return source.pathOrUrl(); }else if(sourceDirProject && fallbackRoot) { // We're in the source directory, map to the root return sourceDirProject->path().pathOrUrl(); } } return QString(); } void ProjectController::reparseProject( IProject* project, bool forceUpdate ) { if (auto job = d->m_parseJobs.value(project)) { job->kill(); } d->m_parseJobs[project] = new KDevelop::ParseProjectJob(project, forceUpdate); ICore::self()->runController()->registerJob(d->m_parseJobs[project]); } } #include "moc_projectcontroller.cpp" diff --git a/shell/projectcontroller.h b/shell/projectcontroller.h index 062e20061e..30db2854cb 100644 --- a/shell/projectcontroller.h +++ b/shell/projectcontroller.h @@ -1,167 +1,169 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_PROJECTCONTROLLER_H #define KDEVPLATFORM_PROJECTCONTROLLER_H #include -#include + #include +#include + #include "shellexport.h" namespace Sublime { class Area; } namespace KIO { class Job; } namespace KDevelop { class IProject; class Core; class Context; class ContextMenuExtension; class IPlugin; class KDEVPLATFORMSHELL_EXPORT IProjectDialogProvider : public QObject { Q_OBJECT public: IProjectDialogProvider(); virtual ~IProjectDialogProvider(); public Q_SLOTS: /** * Displays some UI to ask the user for the project location. - * + * * @param fetch will tell the UI that the user might want to fetch the project first * @param startUrl tells where to look first */ virtual QUrl askProjectConfigLocation(bool fetch, const QUrl& startUrl = QUrl()) = 0; virtual bool userWantsReopen() = 0; }; class KDEVPLATFORMSHELL_EXPORT ProjectController : public IProjectController { Q_OBJECT Q_CLASSINFO( "D-Bus Interface", "org.kdevelop.ProjectController" ) friend class Core; friend class CorePrivate; friend class ProjectPreferences; public: ProjectController( Core* core ); virtual ~ProjectController(); virtual IProject* projectAt( int ) const override; virtual int projectCount() const override; virtual QList projects() const override; virtual ProjectBuildSetModel* buildSetModel() override; virtual ProjectModel* projectModel() override; virtual ProjectChangesModel* changesModel() override; virtual QItemSelectionModel* projectSelectionModel(); virtual IProject* findProjectByName( const QString& name ) override; IProject* findProjectForUrl( const QUrl& ) const override; void addProject(IProject*); // IProject* currentProject() const; virtual bool isProjectNameUsed( const QString& name ) const override; void setDialogProvider(IProjectDialogProvider*); QUrl projectsBaseDirectory() const override; QString prettyFileName(const QUrl& url, FormattingOptions format = FormatHtml) const override; QString prettyFilePath(const QUrl& url, FormattingOptions format = FormatHtml) const override; ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: virtual void openProjectForUrl( const QUrl &sourceUrl ) override; virtual void fetchProject(); virtual void openProject( const QUrl &KDev4ProjectFile = QUrl() ) override; virtual void abortOpeningProject( IProject* ); void projectImportingFinished( IProject* ); virtual void closeProject( IProject* ) override; virtual void configureProject( IProject* ) override; virtual void reparseProject( IProject* project, bool forceUpdate = false ) override; void eventuallyOpenProjectFile(KIO::Job*,KIO::UDSEntryList); void openProjectForUrlSlot(bool); // void changeCurrentProject( ProjectBaseItem* ); void openProjects(const QList& projects); void commitCurrentProject(); // Maps the given path from the source to the equivalent path within the build directory // of the corresponding project. If the path is already in the build directory and fallbackRoot is true, // then it is mapped to the root of the build directory. // If reverse is true, maps the opposite direction, from build to source. [ Used in kdevplatform_shell_environment.sh ] Q_SCRIPTABLE QString mapSourceBuild( const QString& path, bool reverse = false, bool fallbackRoot = true ) const; protected: virtual void loadSettings( bool projectIsLoaded ); virtual void saveSettings( bool projectIsLoaded ); virtual void initialize(); private: //FIXME Do not load all of this just for the project being opened... //void legacyLoading(); void setupActions(); void cleanup(); // helper methods for closeProject() void unloadUnusedProjectPlugins(IProject* proj); void disableProjectCloseAction(); void closeAllOpenedFiles(IProject* proj); void initializePluginCleanup(IProject* proj); private: Q_PRIVATE_SLOT(d, void projectConfig( QObject* ) ) Q_PRIVATE_SLOT(d, void unloadAllProjectPlugins() ) Q_PRIVATE_SLOT(d, void updateActionStates( KDevelop::Context* ) ) Q_PRIVATE_SLOT(d, void closeSelectedProjects() ) Q_PRIVATE_SLOT(d, void openProjectConfig() ) Q_PRIVATE_SLOT(d, void areaChanged(Sublime::Area*) ) class ProjectControllerPrivate* const d; friend class ProjectControllerPrivate; }; class ProjectDialogProvider : public IProjectDialogProvider { Q_OBJECT public: ProjectDialogProvider(ProjectControllerPrivate* const p); virtual ~ProjectDialogProvider(); ProjectControllerPrivate* const d; public Q_SLOTS: virtual QUrl askProjectConfigLocation(bool fetch, const QUrl& sta) override; virtual bool userWantsReopen() override; }; } #endif diff --git a/shell/projectsourcepage.cpp b/shell/projectsourcepage.cpp index a5f53ca659..62ffbb6dee 100644 --- a/shell/projectsourcepage.cpp +++ b/shell/projectsourcepage.cpp @@ -1,266 +1,269 @@ /*************************************************************************** * Copyright (C) 2010 by Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "projectsourcepage.h" #include "ui_projectsourcepage.h" #include #include #include #include #include #include #include + #include -#include + #include #include +#include + #include using namespace KDevelop; static const int FROM_FILESYSTEM_SOURCE_INDEX = 0; ProjectSourcePage::ProjectSourcePage(const QUrl& initial, QWidget* parent) : QWidget(parent) { m_ui = new Ui::ProjectSourcePage; m_ui->setupUi(this); - + m_ui->workingDir->setUrl(initial); m_ui->workingDir->setMode(KFile::Directory); m_ui->remoteWidget->setLayout(new QVBoxLayout(m_ui->remoteWidget)); - + m_ui->sources->addItem(QIcon::fromTheme("folder"), i18n("From File System")); m_plugins.append(0); - + IPluginController* pluginManager = ICore::self()->pluginController(); foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IBasicVersionControl" ) ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } - + foreach( IPlugin* p, pluginManager->allPluginsForExtension( "org.kdevelop.IProjectProvider" ) ) { m_plugins.append(p); m_ui->sources->addItem(QIcon::fromTheme(pluginManager->pluginInfo(p).iconName()), p->extension()->name()); } - + connect(m_ui->workingDir, &KUrlRequester::textChanged, this, &ProjectSourcePage::reevaluateCorrection); connect(m_ui->sources, static_cast(&QComboBox::currentIndexChanged), this, &ProjectSourcePage::setSourceIndex); connect(m_ui->get, &QPushButton::clicked, this, &ProjectSourcePage::checkoutVcsProject); - + emit isCorrect(false); setSourceIndex(FROM_FILESYSTEM_SOURCE_INDEX); - + if(!m_plugins.isEmpty()) m_ui->sources->setCurrentIndex(1); } ProjectSourcePage::~ProjectSourcePage() { delete m_ui; } void ProjectSourcePage::setSourceIndex(int index) { m_locationWidget = 0; m_providerWidget = 0; QLayout* remoteWidgetLayout = m_ui->remoteWidget->layout(); QLayoutItem *child; while ((child = remoteWidgetLayout->takeAt(0)) != 0) { delete child->widget(); delete child; } - + IBasicVersionControl* vcIface = vcsPerIndex(index); IProjectProvider* providerIface; bool found=false; if(vcIface) { found=true; m_locationWidget=vcIface->vcsLocation(m_ui->sourceBox); connect(m_locationWidget, &VcsLocationWidget::changed, this, &ProjectSourcePage::locationChanged); - + remoteWidgetLayout->addWidget(m_locationWidget); } else { providerIface = providerPerIndex(index); if(providerIface) { found=true; m_providerWidget=providerIface->providerWidget(m_ui->sourceBox); connect(m_providerWidget, &IProjectProviderWidget::changed, this, &ProjectSourcePage::projectChanged); - - remoteWidgetLayout->addWidget(m_providerWidget); + + remoteWidgetLayout->addWidget(m_providerWidget); } } reevaluateCorrection(); - + m_ui->sourceBox->setVisible(found); } IBasicVersionControl* ProjectSourcePage::vcsPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return 0; else return p->extension(); } IProjectProvider* ProjectSourcePage::providerPerIndex(int index) { IPlugin* p = m_plugins.value(index); if(!p) return 0; else return p->extension(); } VcsJob* ProjectSourcePage::jobPerCurrent() { QUrl url=m_ui->workingDir->url(); IPlugin* p=m_plugins[m_ui->sources->currentIndex()]; VcsJob* job=0; - + if(IBasicVersionControl* iface=p->extension()) { Q_ASSERT(iface && m_locationWidget); job=iface->createWorkingCopy(m_locationWidget->location(), url); } else if(m_providerWidget) { job=m_providerWidget->createWorkingCopy(url); } return job; } void ProjectSourcePage::checkoutVcsProject() { QUrl url=m_ui->workingDir->url(); QDir d(url.toLocalFile()); if(!url.isLocalFile() && !d.exists()) { bool corr = d.mkpath(d.path()); if(!corr) { KMessageBox::error(0, i18n("Could not create the directory: %1", d.path())); return; } } - + VcsJob* job=jobPerCurrent(); if (!job) { return; } - + m_ui->sources->setEnabled(false); m_ui->sourceBox->setEnabled(false); m_ui->workingDir->setEnabled(false); m_ui->get->setEnabled(false); m_ui->creationProgress->setValue(m_ui->creationProgress->minimum()); connect(job, &VcsJob::result, this, &ProjectSourcePage::projectReceived); // Can't use new signal-slot syntax, KJob::percent is private :/ connect(job, SIGNAL(percent(KJob*,ulong)), SLOT(progressChanged(KJob*,ulong))); connect(job, &VcsJob::infoMessage, this, &ProjectSourcePage::infoMessage); ICore::self()->runController()->registerJob(job); } void ProjectSourcePage::progressChanged(KJob*, unsigned long value) { m_ui->creationProgress->setValue(value); } void ProjectSourcePage::infoMessage(KJob* , const QString& text, const QString& /*rich*/) { m_ui->creationProgress->setFormat(i18nc("Format of the progress bar text. progress and info", "%1 : %p%", text)); } void ProjectSourcePage::projectReceived(KJob* job) { if (job->error()) { m_ui->creationProgress->setValue(0); } else { m_ui->creationProgress->setValue(m_ui->creationProgress->maximum()); } reevaluateCorrection(); m_ui->creationProgress->setFormat("%p%"); } void ProjectSourcePage::reevaluateCorrection() { //TODO: Probably we should just ignore remote URL's, I don't think we're ever going //to support checking out to remote directories const QUrl cwd = m_ui->workingDir->url(); const QDir dir = cwd.toLocalFile(); // case where we import a project from local file system if (m_ui->sources->currentIndex() == FROM_FILESYSTEM_SOURCE_INDEX) { emit isCorrect(dir.exists()); return; } // all other cases where remote locations need to be specified bool correct=!cwd.isRelative() && (!cwd.isLocalFile() || QDir(cwd.adjusted(QUrl::RemoveFilename).toLocalFile()).exists()); emit isCorrect(correct && m_ui->creationProgress->value() == m_ui->creationProgress->maximum()); bool validWidget = ((m_locationWidget && m_locationWidget->isCorrect()) || (m_providerWidget && m_providerWidget->isCorrect())); bool validToCheckout = correct && validWidget; //To checkout, if it exists, it should be an empty dir if (validToCheckout && cwd.isLocalFile() && dir.exists()) { validToCheckout = dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot).isEmpty(); } - + m_ui->get->setEnabled(validToCheckout); m_ui->creationProgress->setEnabled(validToCheckout); - + if(!correct) setStatus(i18n("You need to specify a valid or nonexistent directory to check out a project")); else if(!m_ui->get->isEnabled() && m_ui->workingDir->isEnabled()) setStatus(i18n("You need to specify a valid project location")); else clearStatus(); } void ProjectSourcePage::locationChanged() { Q_ASSERT(m_locationWidget); if(m_locationWidget->isCorrect()) { QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); - + QUrl current = QUrl::fromUserInput(currentUrl + "/" + m_locationWidget->projectName()); m_ui->workingDir->setUrl(current); } else reevaluateCorrection(); } void ProjectSourcePage::projectChanged(const QString& name) { Q_ASSERT(m_providerWidget); QString currentUrl = m_ui->workingDir->text(); currentUrl = currentUrl.left(currentUrl.lastIndexOf('/')+1); - + QUrl current = QUrl::fromUserInput(currentUrl + "/" + name); m_ui->workingDir->setUrl(current); } void ProjectSourcePage::setStatus(const QString& message) { KColorScheme scheme(QPalette::Normal); m_ui->status->setText(QStringLiteral("%2").arg(scheme.foreground(KColorScheme::NegativeText).color().name()).arg(message)); } void ProjectSourcePage::clearStatus() { m_ui->status->clear(); } QUrl ProjectSourcePage::workingDir() const { return m_ui->workingDir->url(); } diff --git a/shell/runcontroller.cpp b/shell/runcontroller.cpp index e18d96b630..ae147aa991 100644 --- a/shell/runcontroller.cpp +++ b/shell/runcontroller.cpp @@ -1,999 +1,997 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda Copyright 2008 Aleix Pol This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runcontroller.h" -#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 #include #include "core.h" #include "plugincontroller.h" #include "uicontroller.h" #include "projectcontroller.h" #include "mainwindow.h" #include "launchconfiguration.h" #include "launchconfigurationdialog.h" #include "debug.h" #include -#include + #include #include #include using namespace KDevelop; QString RunController::LaunchConfigurationsGroup = "Launch"; QString RunController::LaunchConfigurationsListEntry = "Launch Configurations"; static QString CurrentLaunchConfigProjectEntry = "Current Launch Config Project"; static QString CurrentLaunchConfigNameEntry = "Current Launch Config GroupName"; static QString ConfiguredFromProjectItemEntry = "Configured from ProjectItem"; typedef QPair Target; Q_DECLARE_METATYPE(Target) //TODO: Doesn't handle add/remove of launch configs in the dialog or renaming of configs //TODO: Doesn't auto-select launch configs opened from projects class DebugMode : public ILaunchMode { public: DebugMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("tools-report-bug"); } virtual QString id() const override { return "debug"; } virtual QString name() const override { return i18n("Debug"); } }; class ProfileMode : public ILaunchMode { public: ProfileMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("office-chart-area"); } virtual QString id() const override { return "profile"; } virtual QString name() const override { return i18n("Profile"); } }; class ExecuteMode : public ILaunchMode { public: ExecuteMode() {} virtual QIcon icon() const override { return QIcon::fromTheme("system-run"); } virtual QString id() const override { return "execute"; } virtual QString name() const override { return i18n("Execute"); } }; class RunController::RunControllerPrivate { public: QItemDelegate* delegate; IRunController::State state; RunController* q; QHash jobs; QAction* stopAction; KActionMenu* stopJobsMenu; QAction* runAction; QAction* dbgAction; KSelectAction* currentTargetAction; QMap launchConfigurationTypes; QList launchConfigurations; QMap launchModes; QSignalMapper* launchChangeMapper; QSignalMapper* launchAsMapper; QMap > launchAsInfo; KDevelop::ProjectBaseItem* contextItem; DebugMode* debugMode; ExecuteMode* executeMode; ProfileMode* profileMode; bool hasLaunchConfigType( const QString& typeId ) { return launchConfigurationTypes.contains( typeId ); } void saveCurrentLaunchAction() { if (!currentTargetAction) return; if( currentTargetAction->currentAction() ) { KConfigGroup grp = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); LaunchConfiguration* l = static_cast( currentTargetAction->currentAction()->data().value() ); grp.writeEntry( CurrentLaunchConfigProjectEntry, l->project() ? l->project()->name() : "" ); grp.writeEntry( CurrentLaunchConfigNameEntry, l->configGroupName() ); grp.sync(); } } void configureLaunches() { LaunchConfigurationDialog dlg; dlg.exec(); } QString launchActionText( LaunchConfiguration* l ) { QString label; if( l->project() ) { label = QStringLiteral("%1 : %2").arg( l->project()->name()).arg(l->name()); } else { label = l->name(); } return label; } void launchAs( int id ) { //qCDebug(SHELL) << "Launching id:" << id; QPair info = launchAsInfo[id]; //qCDebug(SHELL) << "fetching type and mode:" << info.first << info.second; LaunchConfigurationType* type = launchConfigurationTypeForId( info.first ); ILaunchMode* mode = q->launchModeForId( info.second ); //qCDebug(SHELL) << "got mode and type:" << type << type->id() << mode << mode->id(); if( type && mode ) { ILauncher* launcher = 0; foreach (ILauncher *l, type->launchers()) { //qCDebug(SHELL) << "available launcher" << l << l->id() << l->supportedModes(); if (l->supportedModes().contains(mode->id())) { launcher = l; break; } } if (launcher) { QStringList itemPath = Core::self()->projectController()->projectModel()->pathFromIndex(contextItem->index()); ILaunchConfiguration* ilaunch = 0; foreach (LaunchConfiguration *l, launchConfigurations) { QStringList path = l->config().readEntry(ConfiguredFromProjectItemEntry, QStringList()); if (l->type() == type && path == itemPath) { qCDebug(SHELL) << "already generated ilaunch" << path; ilaunch = l; break; } } if (!ilaunch) { ilaunch = q->createLaunchConfiguration( type, qMakePair( mode->id(), launcher->id() ), contextItem->project(), contextItem->text() ); LaunchConfiguration* launch = dynamic_cast( ilaunch ); type->configureLaunchFromItem( launch->config(), contextItem ); launch->config().writeEntry(ConfiguredFromProjectItemEntry, itemPath); //qCDebug(SHELL) << "created config, launching"; } else { //qCDebug(SHELL) << "reusing generated config, launching"; } q->setDefaultLaunch(ilaunch); q->execute( mode->id(), ilaunch ); } } } void updateCurrentLaunchAction() { if (!currentTargetAction) return; KConfigGroup launchGrp = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); QString currentLaunchProject = launchGrp.readEntry( CurrentLaunchConfigProjectEntry, "" ); QString currentLaunchName = launchGrp.readEntry( CurrentLaunchConfigNameEntry, "" ); LaunchConfiguration* l = 0; if( currentTargetAction->currentAction() ) { l = static_cast( currentTargetAction->currentAction()->data().value() ); } else if( !launchConfigurations.isEmpty() ) { l = launchConfigurations.at( 0 ); } if( l && ( ( !currentLaunchProject.isEmpty() && ( !l->project() || l->project()->name() != currentLaunchProject ) ) || l->configGroupName() != currentLaunchName ) ) { foreach( QAction* a, currentTargetAction->actions() ) { LaunchConfiguration* l = static_cast( qvariant_cast( a->data() ) ); if( currentLaunchName == l->configGroupName() && ( ( currentLaunchProject.isEmpty() && !l->project() ) || ( l->project() && l->project()->name() == currentLaunchProject ) ) ) { a->setChecked( true ); break; } } } if( !currentTargetAction->currentAction() ) { qCDebug(SHELL) << "oops no current action, using first if list is non-empty"; if( !currentTargetAction->actions().isEmpty() ) { currentTargetAction->actions().first()->setChecked( true ); } } } void addLaunchAction( LaunchConfiguration* l ) { if (!currentTargetAction) return; QAction* action = currentTargetAction->addAction(launchActionText( l )); action->setData(qVariantFromValue(l)); } void readLaunchConfigs( KSharedConfigPtr cfg, IProject* prj ) { KConfigGroup group(cfg, RunController::LaunchConfigurationsGroup); QStringList configs = group.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); foreach( const QString& cfg, configs ) { KConfigGroup grp = group.group( cfg ); if( launchConfigurationTypeForId( grp.readEntry( LaunchConfiguration::LaunchConfigurationTypeEntry, "" ) ) ) { q->addLaunchConfiguration( new LaunchConfiguration( grp, prj ) ); } } } LaunchConfigurationType* launchConfigurationTypeForId( const QString& id ) { QMap::iterator it = launchConfigurationTypes.find( id ); if( it != launchConfigurationTypes.end() ) { return it.value(); } else { qWarning() << "couldn't find type for id:" << id << ". Known types:" << launchConfigurationTypes.keys(); } return 0; } }; RunController::RunController(QObject *parent) : IRunController(parent) , d(new RunControllerPrivate) { setObjectName("RunController"); // TODO: need to implement compile only if needed before execute // TODO: need to implement abort all running programs when project closed d->currentTargetAction = 0; d->state = Idle; d->q = this; d->delegate = new RunDelegate(this); d->launchChangeMapper = new QSignalMapper( this ); d->launchAsMapper = 0; d->contextItem = 0; d->executeMode = 0; d->debugMode = 0; d->profileMode = 0; if(!(Core::self()->setupFlags() & Core::NoUi)) { // Note that things like registerJob() do not work without the actions, it'll simply crash. setupActions(); } } RunController::~RunController() { delete d; } void KDevelop::RunController::launchChanged( LaunchConfiguration* l ) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setText( d->launchActionText( l ) ); break; } } } void RunController::cleanup() { delete d->executeMode; d->executeMode = 0; delete d->profileMode; d->profileMode = 0; delete d->debugMode; d->debugMode = 0; stopAllProcesses(); d->saveCurrentLaunchAction(); } void RunController::initialize() { d->executeMode = new ExecuteMode(); addLaunchMode( d->executeMode ); d->profileMode = new ProfileMode(); addLaunchMode( d->profileMode ); d->debugMode = new DebugMode; addLaunchMode( d->debugMode ); d->readLaunchConfigs( Core::self()->activeSession()->config(), 0 ); foreach (IProject* project, Core::self()->projectController()->projects()) { slotProjectOpened(project); } connect(Core::self()->projectController(), &IProjectController::projectOpened, this, &RunController::slotProjectOpened); connect(Core::self()->projectController(), &IProjectController::projectClosing, this, &RunController::slotProjectClosing); connect(Core::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &RunController::slotRefreshProject); if( (Core::self()->setupFlags() & Core::NoUi) == 0 ) { // Only do this in GUI mode d->updateCurrentLaunchAction(); } } KJob* RunController::execute(const QString& runMode, ILaunchConfiguration* launch) { if( !launch ) { qCDebug(SHELL) << "execute called without launch config!"; return 0; } LaunchConfiguration *run = dynamic_cast(launch); //TODO: Port to launch framework, probably needs to be part of the launcher //if(!run.dependencies().isEmpty()) // ICore::self()->documentController()->saveAllDocuments(IDocument::Silent); //foreach(KJob* job, run.dependencies()) //{ // jobs.append(job); //} qCDebug(SHELL) << "mode:" << runMode; QString launcherId = run->launcherForMode( runMode ); qCDebug(SHELL) << "launcher id:" << launcherId; ILauncher* launcher = run->type()->launcherForId( launcherId ); if( !launcher ) { KMessageBox::error( qApp->activeWindow(), i18n("The current launch configuration does not support the '%1' mode.", runMode), ""); return 0; } KJob* launchJob = launcher->start(runMode, run); registerJob(launchJob); return launchJob; } void RunController::setupActions() { QAction* action; // TODO not multi-window friendly, FIXME KActionCollection* ac = Core::self()->uiControllerInternal()->defaultMainWindow()->actionCollection(); action = new QAction(i18n("Configure Launches..."), this); ac->addAction("configure_launches", action); action->setStatusTip(i18n("Open Launch Configuration Dialog")); action->setToolTip(i18nc("@info:tooltip", "Open Launch Configuration Dialog")); action->setWhatsThis(i18nc("@info:whatsthis", "Opens a dialog to setup new launch configurations, or to change the existing ones.")); connect(action, &QAction::triggered, this, [&] { d->configureLaunches(); }); d->runAction = new QAction( QIcon::fromTheme("system-run"), i18n("Execute Launch"), this); d->runAction->setIconText( i18nc("Short text for 'Execute launch' used in the toolbar", "Execute") ); ac->setDefaultShortcut( d->runAction, Qt::SHIFT + Qt::Key_F9); d->runAction->setToolTip(i18nc("@info:tooltip", "Execute current launch")); d->runAction->setStatusTip(i18n("Execute current launch")); d->runAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration.")); ac->addAction("run_execute", d->runAction); connect(d->runAction, &QAction::triggered, this, &RunController::slotExecute); d->dbgAction = new QAction( QIcon::fromTheme("debug-run"), i18n("Debug Launch"), this); ac->setDefaultShortcut( d->dbgAction, Qt::Key_F9); d->dbgAction->setIconText( i18nc("Short text for 'Debug launch' used in the toolbar", "Debug") ); d->dbgAction->setToolTip(i18nc("@info:tooltip", "Debug current launch")); d->dbgAction->setStatusTip(i18n("Debug current launch")); d->dbgAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Debugger.")); ac->addAction("run_debug", d->dbgAction); connect(d->dbgAction, &QAction::triggered, this, &RunController::slotDebug); Core::self()->uiControllerInternal()->area(0, "code")->addAction(d->dbgAction); // TODO: at least get a profile target, it's sad to have the menu entry without a profiler // QAction* profileAction = new QAction( QIcon::fromTheme(""), i18n("Profile Launch"), this); // profileAction->setToolTip(i18nc("@info:tooltip", "Profile current launch")); // profileAction->setStatusTip(i18n("Profile current launch")); // profileAction->setWhatsThis(i18nc("@info:whatsthis", "Executes the target or the program specified in currently active launch configuration inside a Profiler.")); // ac->addAction("run_profile", profileAction); // connect(profileAction, SIGNAL(triggered(bool)), this, SLOT(slotProfile())); action = d->stopAction = new QAction( QIcon::fromTheme("process-stop"), i18n("Stop All Jobs"), this); action->setIconText(i18nc("Short text for 'Stop All Jobs' used in the toolbar", "Stop All")); // Ctrl+Escape would be nicer, but thats taken by the ksysguard desktop shortcut ac->setDefaultShortcut( action, QKeySequence("Ctrl+Shift+Escape")); action->setToolTip(i18nc("@info:tooltip", "Stop all currently running jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "Requests that all running jobs are stopped.")); action->setEnabled(false); ac->addAction("run_stop_all", action); connect(action, &QAction::triggered, this, &RunController::stopAllProcesses); Core::self()->uiControllerInternal()->area(0, "debug")->addAction(action); action = d->stopJobsMenu = new KActionMenu( QIcon::fromTheme("process-stop"), i18n("Stop"), this); action->setIconText(i18nc("Short text for 'Stop' used in the toolbar", "Stop")); action->setToolTip(i18nc("@info:tooltip", "Menu allowing to stop individual jobs")); action->setWhatsThis(i18nc("@info:whatsthis", "List of jobs that can be stopped individually.")); action->setEnabled(false); ac->addAction("run_stop_menu", action); d->currentTargetAction = new KSelectAction( i18n("Current Launch Configuration"), this); d->currentTargetAction->setToolTip(i18nc("@info:tooltip", "Current launch configuration")); d->currentTargetAction->setStatusTip(i18n("Current launch Configuration")); d->currentTargetAction->setWhatsThis(i18nc("@info:whatsthis", "Select which launch configuration to run when run is invoked.")); ac->addAction("run_default_target", d->currentTargetAction); } LaunchConfigurationType* RunController::launchConfigurationTypeForId( const QString& id ) { return d->launchConfigurationTypeForId( id ); } void KDevelop::RunController::slotProjectOpened(KDevelop::IProject * project) { d->readLaunchConfigs( project->projectConfiguration(), project ); d->updateCurrentLaunchAction(); } void KDevelop::RunController::slotProjectClosing(KDevelop::IProject * project) { if (!d->currentTargetAction) return; foreach (QAction* action, d->currentTargetAction->actions()) { LaunchConfiguration* l = static_cast(qvariant_cast(action->data())); if ( project == l->project() ) { l->save(); d->launchConfigurations.removeAll(l); delete l; bool wasSelected = action->isChecked(); delete action; if (wasSelected && !d->currentTargetAction->actions().isEmpty()) d->currentTargetAction->actions().first()->setChecked(true); } } } void KDevelop::RunController::slotRefreshProject(KDevelop::IProject* project) { slotProjectClosing(project); slotProjectOpened(project); } void RunController::slotDebug() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "debug" ); } void RunController::slotProfile() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "profile" ); } void RunController::slotExecute() { if(d->launchConfigurations.isEmpty()) { LaunchConfigurationDialog d; d.exec(); } if(!d->launchConfigurations.isEmpty()) executeDefaultLaunch( "execute" ); } LaunchConfiguration* KDevelop::RunController::defaultLaunch() const { QAction* projectAction = d->currentTargetAction->currentAction(); if( projectAction ) return static_cast(qvariant_cast(projectAction->data())); return 0; } void KDevelop::RunController::registerJob(KJob * job) { if (!job) return; if (!(job->capabilities() & KJob::Killable)) { // see e.g. https://bugs.kde.org/show_bug.cgi?id=314187 qWarning() << "non-killable job" << job << "registered - this might lead to crashes on shutdown."; } if (!d->jobs.contains(job)) { QAction* stopJobAction = 0; if (Core::self()->setupFlags() != Core::NoUi) { stopJobAction = new QAction(job->objectName().isEmpty() ? i18n("<%1> Unnamed job", job->staticMetaObject.className()) : job->objectName(), this); stopJobAction->setData(QVariant::fromValue(static_cast(job))); d->stopJobsMenu->addAction(stopJobAction); connect (stopJobAction, &QAction::triggered, this, &RunController::slotKillJob); job->setUiDelegate( new KDialogJobUiDelegate() ); } d->jobs.insert(job, stopJobAction); connect( job, &KJob::finished, this, &RunController::finished ); connect( job, &KJob::destroyed, this, &RunController::jobDestroyed ); IRunController::registerJob(job); emit jobRegistered(job); } job->start(); checkState(); } void KDevelop::RunController::unregisterJob(KJob * job) { IRunController::unregisterJob(job); Q_ASSERT(d->jobs.contains(job)); // Delete the stop job action QAction *action = d->jobs.take(job); if (action) action->deleteLater(); checkState(); emit jobUnregistered(job); } void KDevelop::RunController::checkState() { bool running = false; foreach (KJob* job, d->jobs.keys()) { if (!job->isSuspended()) { running = true; break; } } if ( ( d->state != Running ? false : true ) == running ) { d->state = running ? Running : Idle; emit runStateChanged(d->state); } if (Core::self()->setupFlags() != Core::NoUi) { d->stopAction->setEnabled(running); d->stopJobsMenu->setEnabled(running); } } void KDevelop::RunController::stopAllProcesses() { // composite jobs might remove child jobs, see also: // https://bugs.kde.org/show_bug.cgi?id=258904 // foreach already iterates over a copy foreach (KJob* job, d->jobs.keys()) { // now we check the real list whether it was deleted if (!d->jobs.contains(job)) continue; if (job->capabilities() & KJob::Killable) { job->kill(KJob::EmitResult); } else { qWarning() << "cannot stop non-killable job: " << job; } } } void KDevelop::RunController::slotKillJob() { QAction* action = dynamic_cast(sender()); Q_ASSERT(action); KJob* job = static_cast(qvariant_cast(action->data())); if (job->capabilities() & KJob::Killable) job->kill(); } void KDevelop::RunController::finished(KJob * job) { unregisterJob(job); switch (job->error()) { case KJob::NoError: case KJob::KilledJobError: case OutputJob::FailedShownError: break; default: { ///WARNING: do *not* use a nested event loop here, it might cause /// random crashes later on, see e.g.: /// https://bugs.kde.org/show_bug.cgi?id=309811 auto dialog = new QDialog(qApp->activeWindow()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Process Error")); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, dialog); KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Warning, job->errorString(), QStringList(), QString(), 0, KMessageBox::NoExec); dialog->show(); } } } void RunController::jobDestroyed(QObject* job) { KJob* kjob = static_cast(job); if (d->jobs.contains(kjob)) { qWarning() << "job destroyed without emitting finished signal!"; unregisterJob(kjob); } } void KDevelop::RunController::suspended(KJob * job) { Q_UNUSED(job); checkState(); } void KDevelop::RunController::resumed(KJob * job) { Q_UNUSED(job); checkState(); } QList< KJob * > KDevelop::RunController::currentJobs() const { return d->jobs.keys(); } QList RunController::launchConfigurations() const { QList configs; foreach (LaunchConfiguration *config, launchConfigurationsInternal()) configs << config; return configs; } QList RunController::launchConfigurationsInternal() const { return d->launchConfigurations; } QList RunController::launchConfigurationTypes() const { return d->launchConfigurationTypes.values(); } void RunController::addConfigurationType( LaunchConfigurationType* type ) { if( !d->launchConfigurationTypes.contains( type->id() ) ) { d->launchConfigurationTypes.insert( type->id(), type ); } } void RunController::removeConfigurationType( LaunchConfigurationType* type ) { foreach( LaunchConfiguration* l, d->launchConfigurations ) { if( l->type() == type ) { d->launchConfigurations.removeAll( l ); delete l; } } d->launchConfigurationTypes.remove( type->id() ); } void KDevelop::RunController::addLaunchMode(KDevelop::ILaunchMode* mode) { if( !d->launchModes.contains( mode->id() ) ) { d->launchModes.insert( mode->id(), mode ); } } QList< KDevelop::ILaunchMode* > KDevelop::RunController::launchModes() const { return d->launchModes.values(); } void KDevelop::RunController::removeLaunchMode(KDevelop::ILaunchMode* mode) { d->launchModes.remove( mode->id() ); } KDevelop::ILaunchMode* KDevelop::RunController::launchModeForId(const QString& id) const { QMap::iterator it = d->launchModes.find( id ); if( it != d->launchModes.end() ) { return it.value(); } return 0; } void KDevelop::RunController::addLaunchConfiguration(KDevelop::LaunchConfiguration* l) { if( !d->launchConfigurations.contains( l ) ) { d->addLaunchAction( l ); d->launchConfigurations << l; if( !d->currentTargetAction->currentAction() ) { if( !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } } connect( l, &LaunchConfiguration::nameChanged, this, &RunController::launchChanged ); } } void KDevelop::RunController::removeLaunchConfiguration(KDevelop::LaunchConfiguration* l) { KConfigGroup launcherGroup; if( l->project() ) { launcherGroup = l->project()->projectConfiguration()->group( LaunchConfigurationsGroup ); } else { launcherGroup = Core::self()->activeSession()->config()->group( LaunchConfigurationsGroup ); } QStringList configs = launcherGroup.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); configs.removeAll( l->configGroupName() ); launcherGroup.deleteGroup( l->configGroupName() ); launcherGroup.writeEntry( RunController::LaunchConfigurationsListEntry, configs ); launcherGroup.sync(); foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { bool wasSelected = a->isChecked(); d->currentTargetAction->removeAction( a ); if( wasSelected && !d->currentTargetAction->actions().isEmpty() ) { d->currentTargetAction->actions().first()->setChecked( true ); } break; } } d->launchConfigurations.removeAll( l ); delete l; } void KDevelop::RunController::executeDefaultLaunch(const QString& runMode) { if( !defaultLaunch() ) { qWarning() << "no default launch!"; return; } execute( runMode, defaultLaunch() ); } void RunController::setDefaultLaunch(ILaunchConfiguration* l) { foreach( QAction* a, d->currentTargetAction->actions() ) { if( static_cast( a->data().value() ) == l ) { a->setChecked(true); break; } } } bool launcherNameExists(const QString& name) { foreach(ILaunchConfiguration* config, Core::self()->runControllerInternal()->launchConfigurations()) { if(config->name()==name) return true; } return false; } QString makeUnique(const QString& name) { if(launcherNameExists(name)) { for(int i=2; ; i++) { QString proposed = QStringLiteral("%1 (%2)").arg(name).arg(i); if(!launcherNameExists(proposed)) { return proposed; } } } return name; } ILaunchConfiguration* RunController::createLaunchConfiguration ( LaunchConfigurationType* type, const QPair& launcher, IProject* project, const QString& name ) { KConfigGroup launchGroup; if( project ) { launchGroup = project->projectConfiguration()->group( RunController::LaunchConfigurationsGroup ); } else { launchGroup = Core::self()->activeSession()->config()->group( RunController::LaunchConfigurationsGroup ); } QStringList configs = launchGroup.readEntry( RunController::LaunchConfigurationsListEntry, QStringList() ); uint num = 0; QString baseName = "Launch Configuration"; while( configs.contains( QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ) ) ) { num++; } QString groupName = QStringLiteral( "%1 %2" ).arg( baseName ).arg( num ); KConfigGroup launchConfigGroup = launchGroup.group( groupName ); QString cfgName = name; if( name.isEmpty() ) { cfgName = i18n("New %1 Launcher", type->name() ); cfgName = makeUnique(cfgName); } launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationNameEntry, cfgName ); launchConfigGroup.writeEntry(LaunchConfiguration::LaunchConfigurationTypeEntry, type->id() ); launchConfigGroup.sync(); configs << groupName; launchGroup.writeEntry( RunController::LaunchConfigurationsListEntry, configs ); launchGroup.sync(); LaunchConfiguration* l = new LaunchConfiguration( launchConfigGroup, project ); l->setLauncherForMode( launcher.first, launcher.second ); Core::self()->runControllerInternal()->addLaunchConfiguration( l ); return l; } QItemDelegate * KDevelop::RunController::delegate() const { return d->delegate; } ContextMenuExtension RunController::contextMenuExtension ( Context* ctx ) { delete d->launchAsMapper; d->launchAsMapper = new QSignalMapper( this ); connect( d->launchAsMapper, static_cast(&QSignalMapper::mapped), this, [&] (int id) { d->launchAs(id); } ); d->launchAsInfo.clear(); d->contextItem = 0; ContextMenuExtension ext; if( ctx->type() == Context::ProjectItemContext ) { KDevelop::ProjectItemContext* prjctx = dynamic_cast( ctx ); if( prjctx->items().count() == 1 ) { ProjectBaseItem* itm = prjctx->items().at( 0 ); int i = 0; foreach( ILaunchMode* mode, d->launchModes.values() ) { KActionMenu* menu = new KActionMenu( i18n("%1 As...", mode->name() ), this ); foreach( LaunchConfigurationType* type, launchConfigurationTypes() ) { bool hasLauncher = false; foreach( ILauncher* launcher, type->launchers() ) { if( launcher->supportedModes().contains( mode->id() ) ) { hasLauncher = true; } } if( hasLauncher && type->canLaunch(itm) ) { d->launchAsInfo[i] = qMakePair( type->id(), mode->id() ); QAction* act = new QAction( d->launchAsMapper ); act->setText( type->name() ); qCDebug(SHELL) << "Setting up mapping for:" << i << "for action" << act->text() << "in mode" << mode->name(); d->launchAsMapper->setMapping( act, i ); connect( act, &QAction::triggered, d->launchAsMapper, static_cast(&QSignalMapper::map) ); menu->addAction(act); i++; } } if( menu->menu()->actions().count() > 0 ) { ext.addAction( ContextMenuExtension::RunGroup, menu); } } if( ext.actions( ContextMenuExtension::RunGroup ).count() > 0 ) { d->contextItem = itm; } } } return ext; } RunDelegate::RunDelegate( QObject* parent ) : QItemDelegate(parent), runProviderBrush( KColorScheme::View, KColorScheme::PositiveText ), errorBrush( KColorScheme::View, KColorScheme::NegativeText ) { } void RunDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const { QStyleOptionViewItem opt = option; QVariant status = index.data(Qt::UserRole+1); // if( status.isValid() && status.canConvert() ) // { // IRunProvider::OutputTypes type = status.value(); // if( type == IRunProvider::RunProvider ) // { // opt.palette.setBrush( QPalette::Text, runProviderBrush.brush( option.palette ) ); // } else if( type == IRunProvider::StandardError ) // { // opt.palette.setBrush( QPalette::Text, errorBrush.brush( option.palette ) ); // } // } QItemDelegate::paint(painter, opt, index); } #include "moc_runcontroller.cpp" diff --git a/shell/runcontroller.h b/shell/runcontroller.h index bdd2073a88..b3c69ec504 100644 --- a/shell/runcontroller.h +++ b/shell/runcontroller.h @@ -1,167 +1,165 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_RUNCONTROLLER_H #define KDEVPLATFORM_RUNCONTROLLER_H -#include #include -#include - -#include +#include #include +#include #include "shellexport.h" class QStyleOptionViewItem; class QPainter; class QModelIndex; class KStatefulBrush; namespace KDevelop { class Context; class ContextMenuExtension; class IPlugin; class IProject; class LaunchConfiguration; class LaunchConfigurationType; class KDEVPLATFORMSHELL_EXPORT RunController : public IRunController { Q_OBJECT public: RunController(QObject *parent); ~RunController(); - + static QString LaunchConfigurationsGroup; static QString LaunchConfigurationsListEntry; virtual void registerJob(KJob *job) override; virtual void unregisterJob(KJob *job) override; virtual QList currentJobs() const override; KJob* execute(const QString& launchMode, ILaunchConfiguration* launch) override; LaunchConfiguration* defaultLaunch() const; QList launchModes() const override; - + /** * @copydoc IRunController::addLaunchMode */ virtual void addLaunchMode( ILaunchMode* mode ) override; - + /** * @copydoc IRunController::removeLaunchMode */ virtual void removeLaunchMode( ILaunchMode* mode ) override; /** * @copydoc IRunController::launchModeForId() */ virtual KDevelop::ILaunchMode* launchModeForId(const QString& id) const override; void initialize(); void cleanup(); QItemDelegate* delegate() const; - + void addLaunchConfiguration( LaunchConfiguration* l ); void removeLaunchConfiguration( LaunchConfiguration* l ); - + QList launchConfigurationsInternal() const; virtual QList launchConfigurations() const override; /** * @copydoc IRunController::launchConfigurationTypes() */ virtual QList launchConfigurationTypes() const override; /** * @copydoc IRunController::addConfigurationType() */ virtual void addConfigurationType( LaunchConfigurationType* type ) override; /** * @copydoc IRunController::removeConfigurationType() */ virtual void removeConfigurationType( LaunchConfigurationType* type ) override; /** * Find the launch configuration type for the given @p id. * @returns the launch configuration type having the id, or 0 if no such type is known */ LaunchConfigurationType* launchConfigurationTypeForId( const QString& ) override; virtual ILaunchConfiguration* createLaunchConfiguration ( LaunchConfigurationType* type, - const QPair& launcher, + const QPair& launcher, IProject* project = 0, const QString& name = QString() ) override; - + virtual void executeDefaultLaunch(const QString& runMode) override; void setDefaultLaunch(ILaunchConfiguration* l); - + ContextMenuExtension contextMenuExtension( KDevelop::Context* ctx ); public Q_SLOTS: virtual void stopAllProcesses() override; protected Q_SLOTS: virtual void finished(KJob *job) override; virtual void suspended(KJob *job) override; virtual void resumed(KJob *job) override; private Q_SLOTS: void slotRefreshProject(KDevelop::IProject* project); void slotExecute(); void slotDebug(); void slotProfile(); void slotProjectOpened(KDevelop::IProject* project); void slotProjectClosing(KDevelop::IProject* project); void slotKillJob(); void launchChanged(LaunchConfiguration*); void jobDestroyed(QObject* job); private: void setupActions(); void checkState(); Q_PRIVATE_SLOT(d, void configureLaunches()) Q_PRIVATE_SLOT(d, void launchAs(int)) class RunControllerPrivate; RunControllerPrivate* const d; }; class RunDelegate : public QItemDelegate { Q_OBJECT public: RunDelegate( QObject* = 0 ); void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; private: KStatefulBrush runProviderBrush; KStatefulBrush errorBrush; }; } #endif diff --git a/shell/sessionchooserdialog.cpp b/shell/sessionchooserdialog.cpp index 23ad60b939..10c7872211 100644 --- a/shell/sessionchooserdialog.cpp +++ b/shell/sessionchooserdialog.cpp @@ -1,236 +1,236 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessionchooserdialog.h" #include "sessioncontroller.h" #include "core.h" +#include +#include #include +#include #include +#include + #include #include -#include -#include -#include -#include -#include using namespace KDevelop; SessionChooserDialog::SessionChooserDialog(QListView* view, QAbstractItemModel* model, QLineEdit* filter) : m_view(view), m_model(model), m_filter(filter), m_deleteCandidateRow(-1) { m_updateStateTimer.setInterval(5000); m_updateStateTimer.setSingleShot(false); m_updateStateTimer.start(); connect(&m_updateStateTimer, &QTimer::timeout, this, &SessionChooserDialog::updateState); connect(view, &QListView::doubleClicked, this, &SessionChooserDialog::doubleClicked); connect(view, &QListView::entered, this, &SessionChooserDialog::itemEntered); m_deleteButton = new QPushButton(view->viewport()); m_deleteButton->setIcon(QIcon::fromTheme("edit-delete")); m_deleteButton->setToolTip(i18nc("@info", "Delete session")); m_deleteButton->hide(); connect(m_deleteButton, &QPushButton::clicked, this, &SessionChooserDialog::deleteButtonPressed); m_deleteButtonTimer.setInterval(500); m_deleteButtonTimer.setSingleShot(true); connect(&m_deleteButtonTimer, &QTimer::timeout, this, &SessionChooserDialog::showDeleteButton); view->setMouseTracking(true); view->installEventFilter(this); filter->installEventFilter(this); connect(filter, &QLineEdit::textChanged, this, &SessionChooserDialog::filterTextChanged); setWindowTitle(i18n("Pick a Session")); m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close); auto mainLayout = new QVBoxLayout(this); m_mainWidget = new QWidget(this); mainLayout->addWidget(m_mainWidget); QPushButton *okButton = m_buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::Key_Return); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &SessionChooserDialog::accept); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &SessionChooserDialog::reject); mainLayout->addWidget(m_buttonBox); okButton->setText(i18n("Run")); okButton->setIcon(QIcon::fromTheme("media-playback-start")); } void SessionChooserDialog::filterTextChanged() { m_view->selectionModel()->setCurrentIndex(m_model->index(0, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); bool enabled = m_view->model()->rowCount(QModelIndex())>0; m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enabled); m_deleteButton->setVisible(false); } void SessionChooserDialog::doubleClicked(const QModelIndex& index) { if(m_model->flags(index) & Qt::ItemIsEnabled) accept(); } void SessionChooserDialog::updateState() { // Sometimes locking may take some time, so we stop the timer, to prevent an 'avalanche' of events m_updateStateTimer.stop(); for(int row = 0; row < m_model->rowCount(); ++row) { QString session = m_model->index(row, 0).data().toString(); if(session.isEmpty()) //create new session continue; QString state, tooltip; SessionRunInfo info = SessionController::sessionRunInfo(session); if(info.isRunning) { tooltip = i18n("Active session.\npid %1, app %2, host %3", info.holderPid, info.holderApp, info.holderHostname); state = i18n("Running"); } m_model->setData(m_model->index(row, 1), !info.isRunning ? QIcon::fromTheme("") : QIcon::fromTheme("media-playback-start"), Qt::DecorationRole); m_model->setData(m_model->index(row, 1), tooltip, Qt::ToolTipRole); m_model->setData(m_model->index(row, 2), state, Qt::DisplayRole); } m_updateStateTimer.start(); } void SessionChooserDialog::itemEntered(const QModelIndex& index) { // The last row says "Create new session", we don't want to delete that if(index.row() == m_model->rowCount()-1) { m_deleteButton->hide(); m_deleteButtonTimer.stop(); return; } // align the delete-button to stay on the right border of the item // we need the right most column's index QModelIndex in = m_model->index( index.row(), 1 ); const QRect rect = m_view->visualRect(in); m_deleteButton->resize(rect.height(), rect.height()); QPoint p(rect.right() - m_deleteButton->size().width(), rect.top()+rect.height()/2-m_deleteButton->height()/2); m_deleteButton->move(p); m_deleteCandidateRow = index.row(); m_deleteButtonTimer.start(); } void SessionChooserDialog::showDeleteButton() { m_deleteButton->show(); } bool SessionChooserDialog::eventFilter(QObject* object, QEvent* event) { if(object == m_view && event->type() == QEvent::Leave ) { m_deleteButtonTimer.stop(); m_deleteButton->hide(); } if(object == m_filter && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if(keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_Return) { QModelIndex currentIndex = m_view->selectionModel()->currentIndex(); int selectRow = -1; switch (keyEvent->key()) { case Qt::Key_Up: if(!currentIndex.isValid()) { selectRow = m_model->rowCount()-1; } else if(currentIndex.row()-1 >= 0) { selectRow = currentIndex.row()-1; } break; case Qt::Key_Down: if(!currentIndex.isValid()) { selectRow = 0; } else if(currentIndex.row()+1 < m_model->rowCount()) { selectRow = currentIndex.row()+1; } break; case Qt::Key_Return: accept(); return false; default: return false; } if (selectRow != -1) { m_view->selectionModel()->setCurrentIndex(m_model->index(selectRow, 0), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } return true; } } return false; } QWidget* SessionChooserDialog::mainWidget() const { return m_mainWidget; } void SessionChooserDialog::deleteButtonPressed() { if(m_deleteCandidateRow == -1) return; QModelIndex index = m_model->index(m_deleteCandidateRow, 0); const QString uuid = m_model->data(index, Qt::DisplayRole).toString(); TryLockSessionResult result = SessionController::tryLockSession( uuid ); if( !result.lock ) { const QString errCaption = i18nc("@title", "Cannot Delete Session"); QString errText = i18nc("@info", "

Cannot delete a locked session."); if( result.runInfo.holderPid != -1 ) { errText += i18nc("@info", "

The session is locked by %1 on %2 (PID %3).", result.runInfo.holderApp, result.runInfo.holderHostname, result.runInfo.holderPid); } KMessageBox::error( this, errText, errCaption ); return; } const QString text = i18nc("@info", "The session and all contained settings will be deleted. The projects will stay unaffected. Do you really want to continue?"); const QString caption = i18nc("@title", "Delete Session"); const KGuiItem deleteItem = KStandardGuiItem::del(); const KGuiItem cancelItem = KStandardGuiItem::cancel(); if(KMessageBox::warningYesNo(this, text, caption, deleteItem, cancelItem) == KMessageBox::Yes) { QModelIndex index = m_model->index(m_deleteCandidateRow, 0); const QString uuid = m_model->data(index, Qt::DisplayRole).toString(); SessionController::deleteSessionFromDisk(result.lock); m_model->removeRows( m_deleteCandidateRow, 1 ); m_deleteCandidateRow = -1; } } diff --git a/shell/sessioncontroller.h b/shell/sessioncontroller.h index 269a90a8d1..db5c7d0c1e 100644 --- a/shell/sessioncontroller.h +++ b/shell/sessioncontroller.h @@ -1,178 +1,178 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_SESSIONCONTROLLER_H #define KDEVPLATFORM_SESSIONCONTROLLER_H #include "shellexport.h" #include "session.h" #include -#include +#include -#include +#include namespace KDevelop { struct SessionRunInfo { SessionRunInfo() : isRunning(false) , holderPid(-1) {} bool operator==(const SessionRunInfo& o) const { return isRunning == o.isRunning && holderPid == o.holderPid && holderApp == o.holderApp && holderHostname == o.holderHostname; } bool operator!=(const SessionRunInfo& o) const { return !(operator==(o)); } // if this is true, this session is currently running in an external process bool isRunning; // if the session is running, this contains the PID of its process qint64 holderPid; // if the session is running, this contains the name of its process QString holderApp; // if the session is running, this contains the host name where the process runs QString holderHostname; }; struct TryLockSessionResult { TryLockSessionResult(const ISessionLock::Ptr& _lock) : lock(_lock) {} TryLockSessionResult(const SessionRunInfo& _runInfo) : runInfo(_runInfo) {} // if this is non-null then the session was locked ISessionLock::Ptr lock; // otherwise this contains information about who is locking the session SessionRunInfo runInfo; }; class KDEVPLATFORMSHELL_EXPORT SessionController : public QObject, public KXMLGUIClient { Q_OBJECT public: SessionController( QObject *parent = 0 ); virtual ~SessionController(); void initialize( const QString& session ); void cleanup(); /// Returns whether the given session can be locked (i. e., is not locked currently). /// @param doLocking whether to really lock the session or just "dry-run" the locking process static TryLockSessionResult tryLockSession(const QString& id); /** * @return true when the given session is currently running, false otherwise */ static bool isSessionRunning(const QString& id); /** * @return information about whether the session @p id is running */ static SessionRunInfo sessionRunInfo(const QString& id); /// The application should call this on startup to tell the /// session-controller about the received arguments. /// Some of them may need to be passed to newly opened sessions. static void setArguments(int argc, char** argv); ///Finds a session by its name or by its UUID Session* session( const QString& nameOrId ) const; virtual ISession* activeSession() const; ISessionLock::Ptr activeSessionLock() const; QList sessionNames() const; Session* createSession( const QString& name ); - + QList sessions() const; - + void loadDefaultSession( const QString& session ); void startNewSession(); - + void loadSession( const QString& nameOrId ); void deleteSession( const ISessionLock::Ptr& lock ); static void deleteSessionFromDisk( const ISessionLock::Ptr& lock ); QString cloneSession( const QString& nameOrid ); /** * Path to session directory for the session with the given @p sessionId. */ static QString sessionDirectory( const QString& sessionId ); static QString cfgSessionGroup(); static QString cfgActiveSessionEntry(); static QList< SessionInfo > availableSessionInfo(); - + /** * Shows a dialog where the user can choose the session * @param headerText an additional text that will be shown at the top in a label * @param onlyRunning whether only currently running sessions should be shown * @return UUID on success, empty string in any other case */ static QString showSessionChooserDialog(QString headerText = QString(), bool onlyRunning = false); /// Should be called if session to be opened is locked. /// It attempts to bring existing instance's window up via a DBus call; if that succeeds, empty string is returned. /// Otherwise (if the app did not respond) it shows a dialog where the user may choose /// 1) to force-remove the lockfile and continue, /// 2) to select another session via \ref showSessionChooserDialog, /// 3) to quit the current (starting-up) instance. /// @param sessionName session name (for the message) /// @param sessionId current session GUID (to return if user chooses force-removal) /// @param runInfo the run information about the session /// @return new session GUID to try or an empty string if application startup shall be aborted static QString handleLockedSession( const QString& sessionName, const QString& currentSessionId, const SessionRunInfo& runInfo ); void plugActions(); - + void emitQuitSession() { emit quitSession(); } - + public Q_SLOTS: // Returns the pretty name of the currently active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionName(); // Returns the directory associated to the active session (used in the shell integration) virtual Q_SCRIPTABLE QString sessionDir(); Q_SIGNALS: void sessionLoaded( ISession* ); void sessionDeleted( const QString& id); void quitSession(); private: Q_PRIVATE_SLOT( d, void newSession() ) Q_PRIVATE_SLOT( d, void configureSessions() ) Q_PRIVATE_SLOT( d, void deleteCurrentSession() ) Q_PRIVATE_SLOT( d, void renameSession() ) Q_PRIVATE_SLOT( d, void loadSessionFromAction( QAction* ) ) class SessionControllerPrivate* const d; }; } #endif diff --git a/shell/sessiondialog.cpp b/shell/sessiondialog.cpp index c7b8febb08..1e17290de5 100644 --- a/shell/sessiondialog.cpp +++ b/shell/sessiondialog.cpp @@ -1,275 +1,273 @@ /* This file is part of KDevelop Copyright 2008 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sessiondialog.h" #include "ui_sessiondialog.h" +#include #include -#include -#include -#include + #include #include -#include #include "core.h" #include "sessioncontroller.h" #include "session.h" namespace KDevelop { const QString newSessionName = "New Session"; SessionModel::SessionModel( QObject* parent ) : QAbstractListModel( parent ) { } int SessionModel::rowCount( const QModelIndex& parent ) const { if( parent.isValid() ) return 0; return Core::self()->sessionController()->sessionNames().count(); } QVariant SessionModel::headerData( int, Qt::Orientation, int ) const { return QVariant(); } QVariant SessionModel::data( const QModelIndex& idx, int role ) const { - if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() + if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() || ( role != Qt::DisplayRole && role != Qt::BackgroundRole && role != Qt::EditRole && role != Qt::FontRole ) ) { return QVariant(); } const Session* s = Core::self()->sessionController()->sessions().at( idx.row() ); if( role == Qt::DisplayRole ) { return s->description(); } else if( role == Qt::EditRole ) { return s->name(); } else if( role == Qt::FontRole ) { QFont f = QFontDatabase::systemFont(QFontDatabase::GeneralFont); if( Core::self()->activeSession()->name() == s->name() ) { f.setBold( true ); } return QVariant::fromValue( f ); - } else + } else { if( Core::self()->activeSession()->name() == s->name() ) { return KColorScheme( QPalette::Active ).background( KColorScheme::ActiveBackground ); } else { return KColorScheme( QPalette::Active ).background( KColorScheme::NormalBackground ); } } } Qt::ItemFlags SessionModel::flags( const QModelIndex& idx ) const { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) { return 0; } return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } bool SessionModel::setData( const QModelIndex& idx, const QVariant& value, int role ) { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() || role != Qt::EditRole || !value.isValid() || !value.canConvert( QVariant::String ) ) { return false; } QString sname = Core::self()->sessionController()->sessionNames().at( idx.row() ); Session* s = Core::self()->sessionController()->session( sname ); s->setName( value.toString() ); emit dataChanged( idx, idx ); return true; } void SessionModel::addSession() { beginInsertRows( QModelIndex(), rowCount(), rowCount() ); Core::self()->sessionController()->createSession( newSessionName ); endInsertRows(); } void SessionModel::deleteSessions( const QList& indexes ) { if( indexes.isEmpty() ) { return; } QVector deleteSessions; int startRow = rowCount(), endRow = -1; foreach( const QModelIndex& idx, indexes ) { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) { continue; } QString sname = Core::self()->sessionController()->sessionNames().at( idx.row() ); TryLockSessionResult locked = SessionController::tryLockSession( sname ); if (!locked.lock) { continue; } if( idx.row() < startRow ) startRow = idx.row(); if( idx.row() > endRow ) endRow = idx.row(); deleteSessions << locked.lock; } beginRemoveRows( QModelIndex(), startRow, endRow ); foreach( const ISessionLock::Ptr& session, deleteSessions ) { Core::self()->sessionController()->deleteSessionFromDisk( session ); } endRemoveRows(); } void SessionModel::activateSession( const QModelIndex& idx ) { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) { return; } QStringList sessionList = Core::self()->sessionController()->sessionNames(); QString sname = sessionList.at( idx.row() ); QString aname = Core::self()->activeSession()->name(); if( sname == aname ) { return; } int activerow = sessionList.indexOf( aname ); Core::self()->sessionController()->loadSession( sname ); emit dataChanged( index( activerow, 0, QModelIndex() ), index( activerow, 0, QModelIndex() ) ); emit dataChanged( idx, idx ); } void SessionModel::cloneSession( const QModelIndex& idx ) { if( !idx.isValid() || idx.row() < 0 || idx.row() >= rowCount() ) { - return; + return; } beginInsertRows( QModelIndex(), rowCount(), rowCount() ); Core::self()->sessionController()->cloneSession( Core::self()->sessionController()->sessions().at( idx.row() )->id().toString() ); endInsertRows(); } SessionDialog::SessionDialog( QWidget* parent ) : QDialog( parent ), m_ui( new Ui::SessionDialog ), m_model( new SessionModel( this ) ) { setWindowTitle( i18n( "Configure Sessions" ) ); m_ui->setupUi(this); m_ui->sessionList->setModel( m_model ); connect( m_ui->newButton, &QPushButton::clicked, this, &SessionDialog::createSession ); connect( m_ui->deleteButton, &QPushButton::clicked, this, &SessionDialog::deleteSession ); connect( m_ui->activateButton, &QPushButton::clicked, this, &SessionDialog::activateSession ); connect( m_ui->cloneButton, &QPushButton::clicked, this, &SessionDialog::cloneSession ); - connect( m_ui->sessionList->selectionModel(), &QItemSelectionModel::selectionChanged, + connect( m_ui->sessionList->selectionModel(), &QItemSelectionModel::selectionChanged, this, static_cast(&SessionDialog::enableButtons) ); connect( m_ui->sessionList->selectionModel(), &QItemSelectionModel::currentChanged, this, static_cast(&SessionDialog::enableButtons) ); connect( m_model, &SessionModel::rowsRemoved, this, static_cast(&SessionDialog::enableButtons) ); enableButtons( m_ui->sessionList->selectionModel()->selection(), QItemSelection() ); enableButtons(); } SessionDialog::~SessionDialog() { delete m_ui; } void SessionDialog::enableButtons() { m_ui->activateButton->setEnabled( m_model->rowCount() > 1 ); m_ui->deleteButton->setEnabled( m_model->rowCount() > 1 ); } void SessionDialog::enableButtons( const QModelIndex& current, const QModelIndex& previous ) { Q_UNUSED( previous ); if( m_model->data( current ).toString() == Core::self()->activeSession()->name() ) { m_ui->activateButton->setEnabled( false ); m_ui->deleteButton->setEnabled( false ); - } else + } else { m_ui->activateButton->setEnabled( true ); m_ui->deleteButton->setEnabled( true ); } } void SessionDialog::enableButtons( const QItemSelection& selected, const QItemSelection& ) { m_ui->deleteButton->setEnabled( !selected.isEmpty() ); m_ui->activateButton->setEnabled( !selected.isEmpty() ); m_ui->cloneButton->setEnabled( !selected.isEmpty() ); QString activeName = Core::self()->activeSession()->name(); foreach( const QModelIndex& idx, m_ui->sessionList->selectionModel()->selectedRows() ) { if( m_model->data( idx ).toString() == activeName ) { m_ui->deleteButton->setEnabled( false ); m_ui->activateButton->setEnabled( false ); break; } } } void SessionDialog::createSession() { m_model->addSession(); m_ui->sessionList->edit( m_model->index( m_model->rowCount() - 1, 0, QModelIndex() ) ); m_ui->deleteButton->setEnabled( true ); m_ui->activateButton->setEnabled( true ); } void SessionDialog::deleteSession() { m_model->deleteSessions( m_ui->sessionList->selectionModel()->selectedRows() ); } void SessionDialog::activateSession() { m_model->activateSession( m_ui->sessionList->selectionModel()->selectedRows().at( 0 ) ); } void SessionDialog::cloneSession() { m_model->cloneSession( m_ui->sessionList->selectionModel()->selectedRows().at( 0 ) ); m_ui->sessionList->edit( m_model->index( m_model->rowCount() - 1, 0, QModelIndex() ) ); } } diff --git a/shell/settings/environmentwidget.cpp b/shell/settings/environmentwidget.cpp index f91665bdf3..9002685130 100644 --- a/shell/settings/environmentwidget.cpp +++ b/shell/settings/environmentwidget.cpp @@ -1,230 +1,223 @@ /* This file is part of KDevelop Copyright 2006 Adam Treat Copyright 2007 Dukju Ahn Copyright 2008 Andreas Pakuat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentwidget.h" -#include -#include -#include -#include -#include -#include #include #include +#include #include -#include +#include #include +#include -#include -#include -#include #include #include "environmentgroupmodel.h" #include "placeholderitemproxymodel.h" #include "../debug.h" namespace KDevelop { EnvironmentWidget::EnvironmentWidget( QWidget *parent ) : QWidget( parent ), groupModel( new EnvironmentGroupModel() ), proxyModel( new QSortFilterProxyModel() ) { // setup ui ui.setupUi( this ); ui.variableTable->verticalHeader()->hide(); proxyModel->setSourceModel( groupModel ); PlaceholderItemProxyModel* topProxyModel = new PlaceholderItemProxyModel(this); topProxyModel->setSourceModel(proxyModel); topProxyModel->setColumnHint(0, i18n("Enter variable ...")); connect(topProxyModel, &PlaceholderItemProxyModel::dataInserted, this, &EnvironmentWidget::handleVariableInserted); ui.variableTable->setModel( topProxyModel ); ui.variableTable->horizontalHeader()->setSectionResizeMode( 1, QHeaderView::Stretch ); ui.addgrpBtn->setIcon(QIcon::fromTheme("list-add")); ui.removegrpBtn->setIcon(QIcon::fromTheme("list-remove")); ui.deleteButton->setIcon(QIcon::fromTheme("list-remove")); ui.deleteButton->setShortcut(Qt::Key_Delete); ui.newMultipleButton->setIcon(QIcon::fromTheme("format-list-unordered")); connect( ui.deleteButton, &QPushButton::clicked, this, &EnvironmentWidget::deleteButtonClicked ); connect( ui.newMultipleButton, &QPushButton::clicked, this, &EnvironmentWidget::newMultipleButtonClicked ); connect( ui.addgrpBtn, &QPushButton::clicked, this, &EnvironmentWidget::addGroupClicked ); connect( ui.addgrpBtn, &QPushButton::clicked, this, &EnvironmentWidget::changed ); connect( ui.removegrpBtn, &QPushButton::clicked, this, &EnvironmentWidget::removeGroupClicked ); connect( ui.removegrpBtn, &QPushButton::clicked, this, &EnvironmentWidget::changed ); connect( ui.setAsDefaultBtn, &QPushButton::clicked, this, &EnvironmentWidget::setAsDefault ); connect( ui.setAsDefaultBtn, &QPushButton::clicked, this, &EnvironmentWidget::changed ); connect( ui.activeCombo, static_cast(&KComboBox::currentIndexChanged), this, &EnvironmentWidget::activeGroupChanged ); connect( ui.activeCombo, &KComboBox::editTextChanged, this, &EnvironmentWidget::enableButtons); connect( groupModel, &EnvironmentGroupModel::dataChanged, this, &EnvironmentWidget::changed ); connect( groupModel, &EnvironmentGroupModel::rowsRemoved, this, &EnvironmentWidget::changed ); connect( groupModel, &EnvironmentGroupModel::rowsInserted, this, &EnvironmentWidget::changed ); connect( groupModel, &EnvironmentGroupModel::rowsRemoved, this, &EnvironmentWidget::enableDeleteButton ); connect( groupModel, &EnvironmentGroupModel::rowsInserted, this, &EnvironmentWidget::enableDeleteButton ); connect( groupModel, &EnvironmentGroupModel::modelReset, this, &EnvironmentWidget::enableDeleteButton ); } void EnvironmentWidget::setActiveGroup( const QString& group ) { ui.activeCombo->setCurrentItem(group); } void EnvironmentWidget::enableDeleteButton() { ui.deleteButton->setEnabled( groupModel->rowCount() > 0 ); } void EnvironmentWidget::setAsDefault() { groupModel->changeDefaultGroup( ui.activeCombo->currentText() ); enableButtons( ui.activeCombo->currentText() ); emit changed(); } void EnvironmentWidget::loadSettings( KConfig* config ) { qCDebug(SHELL) << "Loading groups from config"; groupModel->loadFromConfig( config ); ui.activeCombo->clear(); QStringList groupList = groupModel->groups(); qCDebug(SHELL) << "Grouplist:" << groupList << "default group:" << groupModel->defaultGroup(); ui.activeCombo->addItems( groupList ); int idx = ui.activeCombo->findText( groupModel->defaultGroup() ); ui.activeCombo->setCurrentIndex( idx ); } void EnvironmentWidget::saveSettings( KConfig* config ) { groupModel->saveToConfig( config ); } void EnvironmentWidget::defaults( KConfig* config ) { loadSettings( config ); } void EnvironmentWidget::deleteButtonClicked() { QModelIndexList selected = ui.variableTable->selectionModel()->selectedRows(); if( selected.isEmpty() ) return; QStringList variables; foreach( const QModelIndex &idx, selected ) { const QString variable = idx.data(EnvironmentGroupModel::VariableRole).toString(); variables << variable; } groupModel->removeVariables(variables); } void EnvironmentWidget::handleVariableInserted(int /*column*/, const QVariant& value) { groupModel->addVariable(value.toString(), QString()); } void EnvironmentWidget::newMultipleButtonClicked() { QDialog * dialog = new QDialog( this ); dialog->setWindowTitle( i18n( "New Environment Variables" ) ); QVBoxLayout *layout = new QVBoxLayout(dialog); QTextEdit *edit = new QTextEdit; edit->setPlaceholderText("VARIABLE1=VALUE1\nVARIABLE2=VALUE2"); layout->addWidget( edit ); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dialog->connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); dialog->connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); layout->addWidget(buttonBox); if ( dialog->exec() != QDialog::Accepted ) { return; } QStringList lines = edit->toPlainText().split( "\n", QString::SkipEmptyParts ); foreach(const QString &line, lines) { QString name = line.section('=', 0, 0); QString value = line.section('=', 1, -1).trimmed(); if (!name.isEmpty() && !value.isEmpty()) { groupModel->addVariable( name, value ); } } } void EnvironmentWidget::addGroupClicked() { QString curText = ui.activeCombo->currentText(); if( groupModel->groups().contains( curText ) ) { return; // same group name cannot be added twice. } ui.activeCombo->addItem( curText ); ui.activeCombo->setCurrentItem( curText ); } void EnvironmentWidget::removeGroupClicked() { int idx = ui.activeCombo->currentIndex(); if( idx < 0 || ui.activeCombo->count() == 1 ) { return; } QString curText = ui.activeCombo->currentText(); groupModel->removeGroup( curText ); ui.activeCombo->removeItem( idx ); ui.activeCombo->setCurrentItem( groupModel->defaultGroup() ); } void EnvironmentWidget::activeGroupChanged( int /*idx*/ ) { groupModel->setCurrentGroup( ui.activeCombo->currentText() ); enableButtons( ui.activeCombo->currentText() ); } void EnvironmentWidget::enableButtons( const QString& txt ) { ui.addgrpBtn->setEnabled( !groupModel->groups().contains( txt ) ); ui.removegrpBtn->setEnabled( ( groupModel->groups().contains( txt ) && groupModel->defaultGroup() != txt ) ); ui.setAsDefaultBtn->setEnabled( ( groupModel->groups().contains( txt ) && groupModel->defaultGroup() != txt ) ); } } #include "moc_environmentwidget.cpp" diff --git a/shell/settings/projectpreferences.cpp b/shell/settings/projectpreferences.cpp index 46bacb2a50..690e6a47af 100644 --- a/shell/settings/projectpreferences.cpp +++ b/shell/settings/projectpreferences.cpp @@ -1,79 +1,76 @@ /* KDevelop Project Settings * * Copyright 2006 Matt Rogers * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. */ #include "projectpreferences.h" #include -#include -#include - #include "projectconfig.h" #include "../core.h" #include "../projectcontroller.h" #include "ui_projectpreferences.h" namespace KDevelop { ProjectPreferences::ProjectPreferences(QWidget* parent) : ConfigPage(nullptr, ProjectSettings::self(), parent) { QVBoxLayout * l = new QVBoxLayout( this ); QWidget* w = new QWidget; preferencesDialog = new Ui::ProjectPreferences; preferencesDialog->setupUi( w ); l->addWidget( w ); } ProjectPreferences::~ProjectPreferences( ) { delete preferencesDialog; } void ProjectPreferences::apply() { ConfigPage::apply(); Core::self()->projectControllerInternal()->loadSettings(false); } void ProjectPreferences::slotSettingsChanged() { emit changed(); } QString ProjectPreferences::name() const { return i18n("Projects"); } QString ProjectPreferences::fullName() const { return i18n("Configure Projects"); } QIcon ProjectPreferences::icon() const { return QIcon::fromTheme(QStringLiteral("project-open")); } } diff --git a/shell/sourceformattercontroller.cpp b/shell/sourceformattercontroller.cpp index 44acf91028..ce3b36a49d 100644 --- a/shell/sourceformattercontroller.cpp +++ b/shell/sourceformattercontroller.cpp @@ -1,643 +1,642 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright (C) 2008 Cédric Pasteur This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sourceformattercontroller.h" -#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 #include -#include "core.h" -#include "debug.h" -#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "core.h" +#include "debug.h" #include "plugincontroller.h" -#include namespace KDevelop { const QString SourceFormatterController::kateModeLineConfigKey = "ModelinesEnabled"; const QString SourceFormatterController::kateOverrideIndentationConfigKey = "OverrideKateIndentation"; const QString SourceFormatterController::styleCaptionKey = "Caption"; const QString SourceFormatterController::styleContentKey = "Content"; const QString SourceFormatterController::styleMimeTypesKey = "MimeTypes"; const QString SourceFormatterController::styleSampleKey = "StyleSample"; SourceFormatterController::SourceFormatterController(QObject *parent) : ISourceFormatterController(parent), m_enabled(true) { setObjectName("SourceFormatterController"); setComponentName("kdevsourceformatter", "kdevsourceformatter"); setXMLFile("kdevsourceformatter.rc"); if (Core::self()->setupFlags() & Core::NoUi) return; m_formatTextAction = actionCollection()->addAction("edit_reformat_source"); m_formatTextAction->setText(i18n("&Reformat Source")); m_formatTextAction->setToolTip(i18n("Reformat source using AStyle")); m_formatTextAction->setWhatsThis(i18n("Source reformatting functionality using astyle library.")); connect(m_formatTextAction, &QAction::triggered, this, &SourceFormatterController::beautifySource); m_formatLine = actionCollection()->addAction("edit_reformat_line"); m_formatLine->setText(i18n("Reformat Line")); m_formatLine->setToolTip(i18n("Reformat current line using AStyle")); m_formatLine->setWhatsThis(i18n("Source reformatting of line under cursor using astyle library.")); connect(m_formatLine, &QAction::triggered, this, &SourceFormatterController::beautifyLine); m_formatFilesAction = actionCollection()->addAction("tools_astyle"); m_formatFilesAction->setText(i18n("Format Files")); m_formatFilesAction->setToolTip(i18n("Format file(s) using the current theme")); m_formatFilesAction->setWhatsThis(i18n("Formatting functionality using astyle library.")); connect(m_formatFilesAction, &QAction::triggered, this, static_cast(&SourceFormatterController::formatFiles)); m_formatTextAction->setEnabled(false); m_formatFilesAction->setEnabled(true); connect(Core::self()->documentController(), &IDocumentController::documentActivated, this, &SourceFormatterController::activeDocumentChanged); // Use a queued connection, because otherwise the view is not yet fully set up connect(Core::self()->documentController(), &IDocumentController::documentLoaded, this, &SourceFormatterController::documentLoaded, Qt::QueuedConnection); activeDocumentChanged(Core::self()->documentController()->activeDocument()); } void SourceFormatterController::documentLoaded( IDocument* doc ) { // NOTE: explicitly check this here to prevent crashes on shutdown // when this slot gets called (note: delayed connection) // but the text document was already destroyed // there have been unit tests that failed due to that... if (!doc->textDocument()) { return; } QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); adaptEditorIndentationMode( doc->textDocument(), formatterForMimeType(mime) ); } void SourceFormatterController::initialize() { } SourceFormatterController::~SourceFormatterController() { } ISourceFormatter* SourceFormatterController::formatterForUrl(const QUrl &url) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(url); return formatterForMimeType(mime); } KConfigGroup SourceFormatterController::configuration() const { return Core::self()->activeSession()->config()->group( "SourceFormatter" ); } ISourceFormatter* SourceFormatterController::findFirstFormatterForMimeType(const QMimeType& mime ) const { static QHash knownFormatters; if (knownFormatters.contains(mime.name())) return knownFormatters[mime.name()]; foreach( IPlugin* p, Core::self()->pluginController()->allPluginsForExtension( "org.kdevelop.ISourceFormatter" ) ) { ISourceFormatter *iformatter = p->extension(); QSharedPointer formatter(createFormatterForPlugin(iformatter)); if( formatter->supportedMimeTypes().contains(mime.name()) ) { knownFormatters[mime.name()] = iformatter; return iformatter; } } knownFormatters[mime.name()] = 0; return 0; } static void populateStyleFromConfigGroup(SourceFormatterStyle* s, const KConfigGroup& stylegrp) { s->setCaption( stylegrp.readEntry( SourceFormatterController::styleCaptionKey, QString() ) ); s->setContent( stylegrp.readEntry( SourceFormatterController::styleContentKey, QString() ) ); s->setMimeTypes( stylegrp.readEntry( SourceFormatterController::styleMimeTypesKey, QStringList() ) ); s->setOverrideSample( stylegrp.readEntry( SourceFormatterController::styleSampleKey, QString() ) ); } SourceFormatter* SourceFormatterController::createFormatterForPlugin(ISourceFormatter *ifmt) const { SourceFormatter* formatter = new SourceFormatter(); formatter->formatter = ifmt; // Inserted a new formatter. Now fill it with styles foreach( const KDevelop::SourceFormatterStyle& style, ifmt->predefinedStyles() ) { formatter->styles[ style.name() ] = new SourceFormatterStyle(style); } KConfigGroup grp = configuration(); if( grp.hasGroup( ifmt->name() ) ) { KConfigGroup fmtgrp = grp.group( ifmt->name() ); foreach( const QString& subgroup, fmtgrp.groupList() ) { SourceFormatterStyle* s = new SourceFormatterStyle( subgroup ); KConfigGroup stylegrp = fmtgrp.group( subgroup ); populateStyleFromConfigGroup(s, stylegrp); formatter->styles[ s->name() ] = s; } } return formatter; } ISourceFormatter* SourceFormatterController::formatterForMimeType(const QMimeType& mime) { if( !m_enabled || !isMimeTypeSupported( mime ) ) { return 0; } QString formatter = configuration().readEntry( mime.name(), "" ); if( formatter.isEmpty() ) { return findFirstFormatterForMimeType( mime ); } QStringList formatterinfo = formatter.split( "||", QString::SkipEmptyParts ); if( formatterinfo.size() != 2 ) { qCDebug(SHELL) << "Broken formatting entry for mime:" << mime.name() << "current value:" << formatter; return 0; } return Core::self()->pluginControllerInternal()->extensionForPlugin( "org.kdevelop.ISourceFormatter", formatterinfo.at(0) ); } bool SourceFormatterController::isMimeTypeSupported(const QMimeType& mime) { if( findFirstFormatterForMimeType( mime ) ) { return true; } return false; } QString SourceFormatterController::indentationMode(const QMimeType& mime) { if (mime.inherits("text/x-c++src") || mime.inherits("text/x-chdr") || mime.inherits("text/x-c++hdr") || mime.inherits("text/x-csrc") || mime.inherits("text/x-java") || mime.inherits("text/x-csharp")) { return "cstyle"; } return "none"; } QString SourceFormatterController::addModelineForCurrentLang(QString input, const QUrl& url, const QMimeType& mime) { if( !isMimeTypeSupported(mime) ) return input; QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // If there already is a modeline in the document, adapt it while formatting, even // if "add modeline" is disabled. if( !configuration().readEntry( SourceFormatterController::kateModeLineConfigKey, false ) && kateModelineWithNewline.indexIn( input ) == -1 ) return input; ISourceFormatter* fmt = formatterForMimeType( mime ); ISourceFormatter::Indentation indentation = fmt->indentation(url); if( !indentation.isValid() ) return input; QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); Q_ASSERT(fmt); QString modeline("// kate: "); QString indentLength = QString::number(indentation.indentWidth); QString tabLength = QString::number(indentation.indentationTabWidth); // add indentation style modeline.append("indent-mode ").append(indentationMode(mime).append("; ")); if(indentation.indentWidth) // We know something about indentation-width modeline.append(QStringLiteral("indent-width %1; ").arg(indentation.indentWidth)); if(indentation.indentationTabWidth != 0) // We know something about tab-usage { modeline.append(QStringLiteral("replace-tabs %1; ").arg((indentation.indentationTabWidth == -1) ? "on" : "off")); if(indentation.indentationTabWidth > 0) modeline.append(QStringLiteral("tab-width %1; ").arg(indentation.indentationTabWidth)); } qCDebug(SHELL) << "created modeline: " << modeline << endl; QRegExp kateModeline("^\\s*//\\s*kate:(.*)$"); bool modelinefound = false; QRegExp knownOptions("\\s*(indent-width|space-indent|tab-width|indent-mode|replace-tabs)"); while (!is.atEnd()) { QString line = is.readLine(); // replace only the options we care about if (kateModeline.indexIn(line) >= 0) { // match qCDebug(SHELL) << "Found a kate modeline: " << line << endl; modelinefound = true; QString options = kateModeline.cap(1); QStringList optionList = options.split(';', QString::SkipEmptyParts); os << modeline; foreach(QString s, optionList) { if (knownOptions.indexIn(s) < 0) { // unknown option, add it if(s.startsWith(' ')) s=s.mid(1); os << s << ";"; qCDebug(SHELL) << "Found unknown option: " << s << endl; } } os << endl; } else os << line << endl; } if (!modelinefound) os << modeline << endl; return output; } void SourceFormatterController::cleanup() { } void SourceFormatterController::activeDocumentChanged(IDocument* doc) { bool enabled = false; if (doc) { QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); if (isMimeTypeSupported(mime)) enabled = true; } m_formatTextAction->setEnabled(enabled); } void SourceFormatterController::beautifySource() { IDocument* idoc = KDevelop::ICore::self()->documentController()->activeDocument(); KTextEditor::View* view = idoc->activeTextView(); if (!view) return; KTextEditor::Document* doc = view->document(); // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } // Ignore the modeline, as the modeline will be changed anyway adaptEditorIndentationMode( doc, formatter, true ); bool has_selection = view->selection(); if (has_selection) { QString original = view->selectionText(); QString output = formatter->formatSource(view->selectionText(), doc->url(), mime, doc->text(KTextEditor::Range(KTextEditor::Cursor(0,0),view->selectionRange().start())), doc->text(KTextEditor::Range(view->selectionRange().end(), doc->documentRange().end()))); //remove the final newline character, unless it should be there if (!original.endsWith('\n') && output.endsWith('\n')) output.resize(output.length() - 1); //there was a selection, so only change the part of the text related to it // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code( dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( view->selectionRange(), original, output ); } else { formatDocument(idoc, formatter, mime); } } void SourceFormatterController::beautifyLine() { KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->activeDocument(); if (!doc || !doc->isTextDocument()) return; KTextEditor::Document *tDoc = doc->textDocument(); KTextEditor::View* view = doc->activeTextView(); if (!view) return; // load the appropriate formatter QMimeType mime = QMimeDatabase().mimeTypeForUrl(doc->url()); ISourceFormatter *formatter = formatterForMimeType(mime); if( !formatter ) { qCDebug(SHELL) << "no formatter available for" << mime.name(); return; } const KTextEditor::Cursor cursor = view->cursorPosition(); const QString line = tDoc->line(cursor.line()); const QString prev = tDoc->text(KTextEditor::Range(0, 0, cursor.line(), 0)); const QString post = '\n' + tDoc->text(KTextEditor::Range(KTextEditor::Cursor(cursor.line() + 1, 0), tDoc->documentEnd())); const QString formatted = formatter->formatSource(line, doc->url(), mime, prev, post); // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop DynamicCodeRepresentation::Ptr code(dynamic_cast( KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ).data() ) ); Q_ASSERT( code ); code->replace( KTextEditor::Range(cursor.line(), 0, cursor.line(), line.length()), line, formatted ); // advance cursor one line view->setCursorPosition(KTextEditor::Cursor(cursor.line() + 1, 0)); } void SourceFormatterController::formatDocument(KDevelop::IDocument* doc, ISourceFormatter* formatter, const QMimeType& mime) { // We don't use KTextEditor::Document directly, because CodeRepresentation transparently works // around a possible tab-replacement incompatibility between kate and kdevelop CodeRepresentation::Ptr code = KDevelop::createCodeRepresentation( IndexedString( doc->url() ) ); KTextEditor::Cursor cursor = doc->cursorPosition(); QString text = formatter->formatSource(code->text(), doc->url(), mime); text = addModelineForCurrentLang(text, doc->url(), mime); code->setText(text); doc->setCursorPosition(cursor); } void SourceFormatterController::settingsChanged() { if( configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, false ) ) foreach( KDevelop::IDocument* doc, ICore::self()->documentController()->openDocuments() ) adaptEditorIndentationMode( doc->textDocument(), formatterForUrl(doc->url()) ); } /** * Kate commands: * Use spaces for indentation: * "set-replace-tabs 1" * Use tabs for indentation (eventually mixed): * "set-replace-tabs 0" * Indent width: * "set-indent-width X" * Tab width: * "set-tab-width X" * */ void SourceFormatterController::adaptEditorIndentationMode(KTextEditor::Document *doc, ISourceFormatter *formatter, bool ignoreModeline ) { if( !formatter || !configuration().readEntry( SourceFormatterController::kateOverrideIndentationConfigKey, false ) || !doc ) return; qCDebug(SHELL) << "adapting mode for" << doc->url(); QRegExp kateModelineWithNewline("\\s*\\n//\\s*kate:(.*)$"); // modelines should always take precedence if( !ignoreModeline && kateModelineWithNewline.indexIn( doc->text() ) != -1 ) { qCDebug(SHELL) << "ignoring because a kate modeline was found"; return; } ISourceFormatter::Indentation indentation = formatter->indentation(doc->url()); if(indentation.isValid()) { struct CommandCaller { CommandCaller(KTextEditor::Document* _doc) : doc(_doc), editor(KTextEditor::Editor::instance()) { Q_ASSERT(editor); } void operator()(QString cmd) { KTextEditor::Command* command = editor->queryCommand( cmd ); Q_ASSERT(command); QString msg; qCDebug(SHELL) << "calling" << cmd; foreach(KTextEditor::View* view, doc->views()) if( !command->exec( view, cmd, msg ) ) qWarning() << "setting indentation width failed: " << msg; } KTextEditor::Document* doc; KTextEditor::Editor* editor; } call(doc); if( indentation.indentWidth ) // We know something about indentation-width call( QStringLiteral("set-indent-width %1").arg(indentation.indentWidth ) ); if( indentation.indentationTabWidth != 0 ) // We know something about tab-usage { call( QStringLiteral("set-replace-tabs %1").arg( (indentation.indentationTabWidth == -1) ? 1 : 0 ) ); if( indentation.indentationTabWidth > 0 ) call( QStringLiteral("set-tab-width %1").arg(indentation.indentationTabWidth ) ); } }else{ qCDebug(SHELL) << "found no valid indentation"; } } void SourceFormatterController::formatFiles() { if (m_prjItems.isEmpty()) return; //get a list of all files in this folder recursively QList folders; foreach(KDevelop::ProjectBaseItem *item, m_prjItems) { if (!item) continue; if (item->folder()) folders.append(item->folder()); else if (item->file()) m_urls.append(item->file()->path().toUrl()); else if (item->target()) { foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } } while (!folders.isEmpty()) { KDevelop::ProjectFolderItem *item = folders.takeFirst(); foreach(KDevelop::ProjectFolderItem *f, item->folderList()) folders.append(f); foreach(KDevelop::ProjectTargetItem *f, item->targetList()) { foreach(KDevelop::ProjectFileItem *child, f->fileList()) m_urls.append(child->path().toUrl()); } foreach(KDevelop::ProjectFileItem *f, item->fileList()) m_urls.append(f->path().toUrl()); } formatFiles(m_urls); } void SourceFormatterController::formatFiles(QList &list) { //! \todo IStatus for (int fileCount = 0; fileCount < list.size(); fileCount++) { // check mimetype QMimeType mime = QMimeDatabase().mimeTypeForUrl(list[fileCount]); qCDebug(SHELL) << "Checking file " << list[fileCount] << " of mime type " << mime.name() << endl; ISourceFormatter *formatter = formatterForMimeType(mime); if (!formatter) // unsupported mime type continue; // if the file is opened in the editor, format the text in the editor without saving it KDevelop::IDocumentController *docController = KDevelop::ICore::self()->documentController(); KDevelop::IDocument *doc = docController->documentForUrl(list[fileCount]); if (doc) { qCDebug(SHELL) << "Processing file " << list[fileCount] << "opened in editor" << endl; formatDocument(doc, formatter, mime); continue; } qCDebug(SHELL) << "Processing file " << list[fileCount] << endl; KIO::StoredTransferJob *job = KIO::storedGet(list[fileCount]); if (job->exec()) { QByteArray data = job->data(); QString output = formatter->formatSource(data, list[fileCount], mime); data += addModelineForCurrentLang(output, list[fileCount], mime).toUtf8(); job = KIO::storedPut(data, list[fileCount], -1); if (!job->exec()) KMessageBox::error(0, job->errorString()); } else KMessageBox::error(0, job->errorString()); } } KDevelop::ContextMenuExtension SourceFormatterController::contextMenuExtension(KDevelop::Context* context) { KDevelop::ContextMenuExtension ext; m_urls.clear(); m_prjItems.clear(); if (context->hasType(KDevelop::Context::EditorContext)) { if(m_formatTextAction->isEnabled()) ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatTextAction); } else if (context->hasType(KDevelop::Context::FileContext)) { KDevelop::FileContext* filectx = dynamic_cast(context); m_urls = filectx->urls(); ext.addAction(KDevelop::ContextMenuExtension::EditGroup, m_formatFilesAction); } else if (context->hasType(KDevelop::Context::CodeContext)) { } else if (context->hasType(KDevelop::Context::ProjectItemContext)) { KDevelop::ProjectItemContext* prjctx = dynamic_cast(context); m_prjItems = prjctx->items(); if ( !m_prjItems.isEmpty() ) { ext.addAction(KDevelop::ContextMenuExtension::ExtensionGroup, m_formatFilesAction); } } return ext; } SourceFormatterStyle SourceFormatterController::styleForMimeType(const QMimeType& mime) { QStringList formatter = configuration().readEntry( mime.name(), "" ).split( "||", QString::SkipEmptyParts ); if( formatter.count() == 2 ) { SourceFormatterStyle s( formatter.at( 1 ) ); KConfigGroup fmtgrp = configuration().group( formatter.at(0) ); if( fmtgrp.hasGroup( formatter.at(1) ) ) { KConfigGroup stylegrp = fmtgrp.group( formatter.at(1) ); populateStyleFromConfigGroup(&s, stylegrp); } return s; } return SourceFormatterStyle(); } void SourceFormatterController::disableSourceFormatting(bool disable) { m_enabled = !disable; } bool SourceFormatterController::sourceFormattingEnabled() { return m_enabled; } /* Code copied from source formatter plugin, unused currently but shouldn't be just thrown away QString SourceFormatterPlugin::replaceSpacesWithTab(const QString &input, ISourceFormatter *formatter) { QString output(input); int wsCount = formatter->indentationLength(); ISourceFormatter::IndentationType type = formatter->indentationType(); if (type == ISourceFormatter::IndentWithTabs) { // tabs and wsCount spaces to be a tab QString replace; for (int i = 0; i < wsCount;i++) replace += ' '; output = output.replace(replace, QChar('\t')); // input = input.remove(' '); } else if (type == ISourceFormatter::IndentWithSpacesAndConvertTabs) { //convert tabs to spaces QString replace; for (int i = 0;i < wsCount;i++) replace += ' '; output = output.replace(QChar('\t'), replace); } return output; } QString SourceFormatterPlugin::addIndentation(QString input, const QString indentWith) { QString output; QTextStream os(&output, QIODevice::WriteOnly); QTextStream is(&input, QIODevice::ReadOnly); while (!is.atEnd()) os << indentWith << is.readLine() << endl; return output; } */ } // kate: indent-mode cstyle; space-indent off; tab-width 4; diff --git a/shell/textdocument.cpp b/shell/textdocument.cpp index 70aeb9b6e6..4c533281bb 100644 --- a/shell/textdocument.cpp +++ b/shell/textdocument.cpp @@ -1,815 +1,809 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "textdocument.h" +#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 -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include #include #include #include #include #include #include #include #include "core.h" #include "mainwindow.h" #include "uicontroller.h" #include "partcontroller.h" #include "plugincontroller.h" #include "documentcontroller.h" #include "debug.h" #include namespace KDevelop { const int MAX_DOC_SETTINGS = 20; // This sets cursor position and selection on the view to the given // range. Selection is set only for non-empty ranges // Factored into a function since its needed in 3 places already static void selectAndReveal( KTextEditor::View* view, const KTextEditor::Range& range ) { Q_ASSERT(view); if (range.isValid()) { view->setCursorPosition(range.start()); if (!range.isEmpty()) { view->setSelection(range); } } } struct TextDocumentPrivate { TextDocumentPrivate(TextDocument *textDocument) : encoding(""), m_textDocument(textDocument) , m_loaded(false), m_addedContextMenu(0) { document = 0; state = IDocument::Clean; } ~TextDocumentPrivate() { if (m_addedContextMenu) { delete m_addedContextMenu; m_addedContextMenu = 0; } if (document) { saveSessionConfig(); delete document; } } QPointer document; IDocument::DocumentState state; QString encoding; void newDocumentStatus(KTextEditor::Document *document) { bool dirty = (state == IDocument::Dirty || state == IDocument::DirtyAndModified); setStatus(document, dirty); } void textChanged(KTextEditor::Document *document) { Q_UNUSED(document); m_textDocument->notifyContentChanged(); } void populateContextMenu( KTextEditor::View* v, QMenu* menu ) { if (m_addedContextMenu) { foreach ( QAction* action, m_addedContextMenu->actions() ) { menu->removeAction(action); } delete m_addedContextMenu; } m_addedContextMenu = new QMenu(); Context* c = new EditorContext( v, v->cursorPosition() ); QList extensions = Core::self()->pluginController()->queryPluginsForContextMenuExtensions( c ); ContextMenuExtension::populateMenu(m_addedContextMenu, extensions); { QUrl url = v->document()->url(); QList< ProjectBaseItem* > items = Core::self()->projectController()->projectModel()->itemsForPath( IndexedString(url) ); if (!items.isEmpty()) { populateParentItemsMenu( items.front(), m_addedContextMenu ); } } foreach ( QAction* action, m_addedContextMenu->actions() ) { menu->addAction(action); } } void modifiedOnDisk(KTextEditor::Document *document, bool /*isModified*/, KTextEditor::ModificationInterface::ModifiedOnDiskReason reason) { bool dirty = false; switch (reason) { case KTextEditor::ModificationInterface::OnDiskUnmodified: break; case KTextEditor::ModificationInterface::OnDiskModified: case KTextEditor::ModificationInterface::OnDiskCreated: case KTextEditor::ModificationInterface::OnDiskDeleted: dirty = true; break; } // In some cases, the VCS (e.g. git) can know whether the old contents are "valuable", i.e. // not retrieveable from the VCS. If that is not the case, then the document can safely be // reloaded without displaying a dialog asking the user. if ( dirty ) { queryCanRecreateFromVcs(document); } setStatus(document, dirty); } // Determines whether the current contents of this document in the editor // could be retrieved from the VCS if they were dismissed. void queryCanRecreateFromVcs(KTextEditor::Document* document) const { IProject* project = 0; // Find projects by checking which one contains the file's parent directory, // to avoid issues with the cmake manager temporarily removing files from a project // during reloading. KDevelop::Path path(document->url()); foreach ( KDevelop::IProject* current, Core::self()->projectController()->projects() ) { if ( current->path().isParentOf(path) ) { project = current; break; } } if (!project) { return; } IContentAwareVersionControl* iface; iface = qobject_cast< KDevelop::IContentAwareVersionControl* >(project->versionControlPlugin()); if (!iface) { return; } if ( !qobject_cast( document ) ) { return; } CheckInRepositoryJob* req = iface->isInRepository( document ); if ( !req ) { return; } QObject::connect(req, &CheckInRepositoryJob::finished, m_textDocument, [&] (bool canRecreate) { m_textDocument->d->repositoryCheckFinished(canRecreate); }); // Abort the request when the user edits the document QObject::connect(m_textDocument->textDocument(), &KTextEditor::Document::textChanged, req, &CheckInRepositoryJob::abort); } void repositoryCheckFinished(bool canRecreate) { if ( state != IDocument::Dirty && state != IDocument::DirtyAndModified ) { // document is not dirty for whatever reason, nothing to do. return; } if ( ! canRecreate ) { return; } KTextEditor::ModificationInterface* modIface = qobject_cast( document ); Q_ASSERT(modIface); // Ok, all safe, we can clean up the document. Close it if the file is gone, // and reload if it's still there. setStatus(document, false); modIface->setModifiedOnDisk(KTextEditor::ModificationInterface::OnDiskUnmodified); if ( QFile::exists(document->url().path()) ) { m_textDocument->reload(); } else { m_textDocument->close(KDevelop::IDocument::Discard); } } void setStatus(KTextEditor::Document* document, bool dirty) { QIcon statusIcon; if (document->isModified()) if (dirty) { state = IDocument::DirtyAndModified; statusIcon = QIcon::fromTheme("edit-delete"); } else { state = IDocument::Modified; statusIcon = QIcon::fromTheme("document-save"); } else if (dirty) { state = IDocument::Dirty; statusIcon = QIcon::fromTheme("document-revert"); } else { state = IDocument::Clean; } m_textDocument->notifyStateChanged(); Core::self()->uiControllerInternal()->setStatusIcon(m_textDocument, statusIcon); } void documentUrlChanged(KTextEditor::Document* document) { if (m_textDocument->url() != document->url()) m_textDocument->setUrl(document->url()); } void slotDocumentLoaded() { if (m_loaded) return; // Tell the editor integrator first m_loaded = true; m_textDocument->notifyLoaded(); } void documentSaved(KTextEditor::Document* document, bool saveAs) { Q_UNUSED(document); Q_UNUSED(saveAs); m_textDocument->notifySaved(); m_textDocument->notifyStateChanged(); } inline KConfigGroup katePartSettingsGroup() const { return KSharedConfig::openConfig()->group("KatePart Settings"); } inline QString docConfigGroupName() const { return document->url().toDisplayString(QUrl::PreferLocalFile); } inline KConfigGroup docConfigGroup() const { return katePartSettingsGroup().group(docConfigGroupName()); } void saveSessionConfig() { if(document->url().isValid()) { // make sure only MAX_DOC_SETTINGS entries are stored KConfigGroup katePartSettings = katePartSettingsGroup(); // ordered list of documents QStringList documents = katePartSettings.readEntry("documents", QStringList()); // ensure this document is "new", i.e. at the end of the list documents.removeOne(docConfigGroupName()); documents.append(docConfigGroupName()); // remove "old" documents + their group while(documents.size() >= MAX_DOC_SETTINGS) { katePartSettings.group(documents.takeFirst()).deleteGroup(); } // update order katePartSettings.writeEntry("documents", documents); // actually save session config KConfigGroup group = docConfigGroup(); document->writeSessionConfig(group); } } void loadSessionConfig() { if (!document || !katePartSettingsGroup().hasGroup(docConfigGroupName())) { return; } document->readSessionConfig(docConfigGroup(), {"SkipUrl"}); } private: TextDocument *m_textDocument; bool m_loaded; // we want to remove the added stuff when the menu hides QMenu* m_addedContextMenu; }; struct TextViewPrivate { TextViewPrivate(TextView* q) : q(q) {} void sendStatusChanged(); TextView* const q; QPointer view; KTextEditor::Range initialRange; }; TextDocument::TextDocument(const QUrl &url, ICore* core, const QString& encoding) :PartDocument(url, core), d(new TextDocumentPrivate(this)) { d->encoding = encoding; } TextDocument::~TextDocument() { delete d; } bool TextDocument::isTextDocument() const { if( !d->document ) { /// @todo Somehow it can happen that d->document is zero, which makes /// code relying on "isTextDocument() == (bool)textDocument()" crash qWarning() << "Broken text-document: " << url(); return false; } return true; } KTextEditor::Document *TextDocument::textDocument() const { return d->document; } QWidget *TextDocument::createViewWidget(QWidget *parent) { KTextEditor::View* view = 0L; if (!d->document) { d->document = Core::self()->partControllerInternal()->createTextPart(Core::self()->documentController()->encoding()); // Connect to the first text changed signal, it occurs before the completed() signal connect(d->document.data(), &KTextEditor::Document::textChanged, this, [&] { d->slotDocumentLoaded(); }); // Also connect to the completed signal, sometimes the first text changed signal is missed because the part loads too quickly (? TODO - confirm this is necessary) connect(d->document.data(), static_cast(&KTextEditor::Document::completed), this, [&] { d->slotDocumentLoaded(); }); // force a reparse when a document gets reloaded connect(d->document.data(), &KTextEditor::Document::reloaded, this, [] (KTextEditor::Document* document) { ICore::self()->languageController()->backgroundParser()->addDocument(IndexedString(document->url()), (TopDUContext::Features) ( TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdate ), BackgroundParser::BestPriority, 0); }); // Set encoding passed via constructor // Needs to be done before openUrl, else katepart won't use the encoding // @see KTextEditor::Document::setEncoding if (!d->encoding.isEmpty()) d->document->setEncoding(d->encoding); if (!url().isEmpty() && !DocumentController::isEmptyDocumentUrl(url())) d->document->openUrl( url() ); d->setStatus(d->document, false); /* It appears, that by default a part will be deleted the first view containing it is deleted. Since we do want to have several views, disable that behaviour. */ d->document->setAutoDeletePart(false); Core::self()->partController()->addPart(d->document, false); d->loadSessionConfig(); connect(d->document.data(), &KTextEditor::Document::modifiedChanged, this, [&] (KTextEditor::Document* document) { d->newDocumentStatus(document); }); connect(d->document.data(), &KTextEditor::Document::textChanged, this, [&] (KTextEditor::Document* document) { d->textChanged(document); }); connect(d->document.data(), &KTextEditor::Document::documentUrlChanged, this, [&] (KTextEditor::Document* document) { d->documentUrlChanged(document); }); connect(d->document.data(), &KTextEditor::Document::documentSavedOrUploaded, this, [&] (KTextEditor::Document* document, bool saveAs) { d->documentSaved(document, saveAs); }); if (qobject_cast(d->document)) { // can't use new signal/slot syntax here, MarkInterface is not a QObject connect(d->document, SIGNAL(marksChanged(KTextEditor::Document*)), this, SLOT(saveSessionConfig())); } if (auto iface = qobject_cast(d->document)) { iface->setModifiedOnDiskWarning(true); // can't use new signal/slot syntax here, ModificationInterface is not a QObject connect(d->document, SIGNAL(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason)), this, SLOT(modifiedOnDisk(KTextEditor::Document*,bool,KTextEditor::ModificationInterface::ModifiedOnDiskReason))); } notifyTextDocumentCreated(); } view = d->document->createView(parent); view->setStatusBarEnabled(Core::self()->partControllerInternal()->showTextEditorStatusBar()); if (view) { connect(view, &KTextEditor::View::contextMenuAboutToShow, this, [&] (KTextEditor::View* v, QMenu* menu) { d->populateContextMenu(v, menu); }); //in KDE >= 4.4 we can use KXMLGuiClient::replaceXMLFile to provide //katepart with our own restructured UI configuration const QString uiFile = QCoreApplication::applicationName() + "/katepartui.rc"; QStringList katePartUIs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, uiFile); if (!katePartUIs.isEmpty()) { const QString katePartUI = katePartUIs.last(); const QString katePartLocalUI = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+ '/' + uiFile; if (!QFile::exists(katePartLocalUI)) { // prevent warning: // kdevelop/kdeui (kdelibs): No such XML file ".../.kde/share/apps/kdevelop/katepartui.rc" QFile::copy(katePartUI, katePartLocalUI); } view->replaceXMLFile(katePartUI, katePartLocalUI); } } if (KTextEditor::CodeCompletionInterface* cc = dynamic_cast(view)) cc->setAutomaticInvocationEnabled(core()->languageController()->completionSettings()->automaticCompletionEnabled()); if (KTextEditor::ConfigInterface *config = qobject_cast(view)) { config->setConfigValue("allow-mark-menu", false); config->setConfigValue("default-mark-type", KTextEditor::MarkInterface::BreakpointActive); } return view; } KParts::Part *TextDocument::partForView(QWidget *view) const { if (d->document && d->document->views().contains((KTextEditor::View*)view)) return d->document; return 0; } // KDevelop::IDocument implementation void TextDocument::reload() { if (!d->document) return; KTextEditor::ModificationInterface* modif=0; if(d->state==Dirty) { modif = qobject_cast(d->document); modif->setModifiedOnDiskWarning(false); } d->document->documentReload(); if(modif) modif->setModifiedOnDiskWarning(true); } bool TextDocument::save(DocumentSaveMode mode) { if (!d->document) return true; if (mode & Discard) return true; switch (d->state) { case IDocument::Clean: return true; case IDocument::Modified: break; if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The document \"%1\" has unsaved changes. Would you like to save them?", d->document->url().toLocalFile()), i18nc("@title:window", "Close Document")); if (code != KMessageBox::Yes) return false; } break; case IDocument::Dirty: case IDocument::DirtyAndModified: if (!(mode & Silent)) { int code = KMessageBox::warningYesNoCancel( Core::self()->uiController()->activeMainWindow(), i18n("The file \"%1\" is modified on disk.\n\nAre " "you sure you want to overwrite it? (External " "changes will be lost.)", d->document->url().toLocalFile()), i18nc("@title:window", "Document Externally Modified")); if (code != KMessageBox::Yes) return false; } break; } QUrl urlBeforeSave = d->document->url(); if (d->document->documentSave()) { if (d->document->url() != urlBeforeSave) notifyUrlChanged(); return true; } return false; } IDocument::DocumentState TextDocument::state() const { return d->state; } KTextEditor::Cursor KDevelop::TextDocument::cursorPosition() const { if (!d->document) { return KTextEditor::Cursor::invalid(); } KTextEditor::View *view = activeTextView(); if (view) return view->cursorPosition(); return KTextEditor::Cursor::invalid(); } void TextDocument::setCursorPosition(const KTextEditor::Cursor &cursor) { if (!cursor.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); // Rodda: Cursor must be accurate here, to the definition of accurate for KTextEditor::Cursor. // ie, starting from 0,0 if (view) view->setCursorPosition(cursor); } KTextEditor::Range TextDocument::textSelection() const { if (!d->document) { return KTextEditor::Range::invalid(); } KTextEditor::View *view = activeTextView(); if (view && view->selection()) { return view->selectionRange(); } return PartDocument::textSelection(); } QString TextDocument::textLine() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { return d->document->line( view->cursorPosition().line() ); } return PartDocument::textLine(); } QString TextDocument::textWord() const { if (!d->document) { return QString(); } KTextEditor::View *view = activeTextView(); if (view) { KTextEditor::Cursor start = view->cursorPosition(); qCDebug(SHELL) << "got start position from view:" << start.line() << start.column(); QString linestr = textLine(); int startPos = qMax( qMin( start.column(), linestr.length() - 1 ), 0 ); int endPos = startPos; startPos --; while( startPos >= 0 && ( linestr[startPos].isLetterOrNumber() || linestr[startPos] == '_' || linestr[startPos] == '~' ) ) { --startPos; } while( endPos < linestr.length() && ( linestr[endPos].isLetterOrNumber() || linestr[endPos] == '_' || linestr[endPos] == '~' ) ) { ++endPos; } if( startPos != endPos ) { qCDebug(SHELL) << "found word" << startPos << endPos << linestr.mid( startPos+1, endPos - startPos - 1 ); return linestr.mid( startPos + 1, endPos - startPos - 1 ); } } return PartDocument::textWord(); } void TextDocument::setTextSelection(const KTextEditor::Range &range) { if (!range.isValid() || !d->document) return; KTextEditor::View *view = activeTextView(); if (view) { selectAndReveal(view, range); } } bool TextDocument::close(DocumentSaveMode mode) { if (!PartDocument::close(mode)) return false; if ( d->document ) { d->saveSessionConfig(); delete d->document; //We have to delete the document right now, to prevent random crashes in the event handler } return true; } Sublime::View* TextDocument::newView(Sublime::Document* doc) { Q_UNUSED(doc); emit viewNumberChanged(this); return new TextView(this); } } KDevelop::TextView::TextView(TextDocument * doc) : View(doc, View::TakeOwnership), d(new TextViewPrivate(this)) { } KDevelop::TextView::~TextView() { delete d; } QWidget * KDevelop::TextView::createWidget(QWidget * parent) { auto textDocument = qobject_cast(document()); Q_ASSERT(textDocument); QWidget* widget = textDocument->createViewWidget(parent); d->view = qobject_cast(widget); Q_ASSERT(d->view); if (d->view) { connect(d->view.data(), &KTextEditor::View::cursorPositionChanged, this, [&] { d->sendStatusChanged(); }); } return widget; } QString KDevelop::TextView::viewState() const { if (d->view) { if (d->view->selection()) { KTextEditor::Range selection = d->view->selectionRange(); return QStringLiteral("Selection=%1,%2,%3,%4").arg(selection.start().line()) .arg(selection.start().column()) .arg(selection.end().line()) .arg(selection.end().column()); } else { KTextEditor::Cursor cursor = d->view->cursorPosition(); return QStringLiteral("Cursor=%1,%2").arg(cursor.line()).arg(cursor.column()); } } else { qCDebug(SHELL) << "TextView's internal KTE view disappeared!"; return QString(); } } void KDevelop::TextView::setInitialRange(const KTextEditor::Range& range) { if (d->view) { selectAndReveal(d->view, range); } else { d->initialRange = range; } } KTextEditor::Range KDevelop::TextView::initialRange() const { return d->initialRange; } void KDevelop::TextView::setState(const QString & state) { static QRegExp reCursor("Cursor=([\\d]+),([\\d]+)"); static QRegExp reSelection("Selection=([\\d]+),([\\d]+),([\\d]+),([\\d]+)"); if (reCursor.exactMatch(state)) { setInitialRange(KTextEditor::Range(KTextEditor::Cursor(reCursor.cap(1).toInt(), reCursor.cap(2).toInt()), 0)); } else if (reSelection.exactMatch(state)) { KTextEditor::Range range(reSelection.cap(1).toInt(), reSelection.cap(2).toInt(), reSelection.cap(3).toInt(), reSelection.cap(4).toInt()); setInitialRange(range); } } QString KDevelop::TextDocument::documentType() const { return "Text"; } QIcon KDevelop::TextDocument::defaultIcon() const { if (d->document) { QMimeType mime = QMimeDatabase().mimeTypeForName(d->document->mimeType()); QIcon icon = QIcon::fromTheme(mime.iconName()); if (!icon.isNull()) { return icon; } } return PartDocument::defaultIcon(); } KTextEditor::View *KDevelop::TextView::textView() const { return d->view; } QString KDevelop::TextView::viewStatus() const { // only show status when KTextEditor's own status bar isn't already enabled const bool showStatus = !Core::self()->partControllerInternal()->showTextEditorStatusBar(); if (!showStatus) { return QString(); } const KTextEditor::Cursor pos = d->view ? d->view->cursorPosition() : KTextEditor::Cursor::invalid(); return i18n(" Line: %1 Col: %2 ", pos.line() + 1, pos.column() + 1); } void KDevelop::TextViewPrivate::sendStatusChanged() { emit q->statusChanged(q); } KTextEditor::View* KDevelop::TextDocument::activeTextView() const { KTextEditor::View* fallback = nullptr; for (auto view : views()) { auto textView = qobject_cast(view)->textView(); if (!textView) { continue; } if (textView->hasFocus()) { return textView; } else if (textView->isVisible()) { fallback = textView; } else if (!fallback) { fallback = textView; } } return fallback; } #include "moc_textdocument.cpp" diff --git a/shell/uicontroller.cpp b/shell/uicontroller.cpp index 2108ff2193..92abfc8e62 100644 --- a/shell/uicontroller.cpp +++ b/shell/uicontroller.cpp @@ -1,738 +1,732 @@ /*************************************************************************** * Copyright 2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "uicontroller.h" -#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 "core.h" #include "configpage.h" #include "configdialog.h" #include "debug.h" #include "editorconfigpage.h" #include "shellextension.h" #include "partcontroller.h" #include "plugincontroller.h" #include "mainwindow.h" #include "partdocument.h" #include "textdocument.h" #include "documentcontroller.h" #include "assistantpopup.h" -#include #include #include "workingsetcontroller.h" #include "workingsets/workingset.h" #include "settings/bgpreferences.h" #include "settings/ccpreferences.h" #include "settings/environmentpreferences.h" #include "settings/pluginpreferences.h" #include "settings/projectpreferences.h" #include "settings/sourceformattersettings.h" #include "settings/uipreferences.h" #include "settings/templateconfig.h" namespace KDevelop { class UiControllerPrivate { public: UiControllerPrivate(UiController *controller) : areasRestored(false), m_controller(controller) { if (Core::self()->workingSetControllerInternal()) Core::self()->workingSetControllerInternal()->initializeController(m_controller); QMap desired; desired["org.kdevelop.ClassBrowserView"] = Sublime::Left; desired["org.kdevelop.DocumentsView"] = Sublime::Left; desired["org.kdevelop.ProjectsView"] = Sublime::Left; desired["org.kdevelop.FileManagerView"] = Sublime::Left; desired["org.kdevelop.ProblemReporterView"] = Sublime::Bottom; desired["org.kdevelop.OutputView"] = Sublime::Bottom; desired["org.kdevelop.ContextBrowser"] = Sublime::Bottom; desired["org.kdevelop.KonsoleView"] = Sublime::Bottom; desired["org.kdevelop.SnippetView"] = Sublime::Right; desired["org.kdevelop.ExternalScriptView"] = Sublime::Right; Sublime::Area* a = new Sublime::Area(m_controller, "code", i18n("Code")); a->setDesiredToolViews(desired); a->setIconName("document-edit"); m_controller->addDefaultArea(a); desired.clear(); desired["org.kdevelop.debugger.VariablesView"] = Sublime::Left; desired["org.kdevelop.debugger.BreakpointsView"] = Sublime::Bottom; desired["org.kdevelop.debugger.StackView"] = Sublime::Bottom; desired["org.kdevelop.debugger.ConsoleView"] = Sublime::Bottom; desired["org.kdevelop.KonsoleView"] = Sublime::Bottom; a = new Sublime::Area(m_controller, "debug", i18n("Debug")); a->setDesiredToolViews(desired); a->setIconName("tools-report-bug"); m_controller->addDefaultArea(a); desired.clear(); desired["org.kdevelop.ProjectsView"] = Sublime::Left; desired["org.kdevelop.PatchReview"] = Sublime::Bottom; a = new Sublime::Area(m_controller, "review", i18n("Review")); a->setDesiredToolViews(desired); a->setIconName("applications-engineering"); m_controller->addDefaultArea(a); if(!(Core::self()->setupFlags() & Core::NoUi)) { defaultMainWindow = new MainWindow(m_controller); m_controller->addMainWindow(defaultMainWindow); activeSublimeWindow = defaultMainWindow; } else { activeSublimeWindow = defaultMainWindow = 0; } m_assistantTimer.setSingleShot(true); m_assistantTimer.setInterval(100); } void widgetChanged(QWidget*, QWidget* now) { if (now) { Sublime::MainWindow* win = qobject_cast(now->window()); if( win ) { activeSublimeWindow = win; } } } Core *core; MainWindow* defaultMainWindow; QMap factoryDocuments; Sublime::MainWindow* activeSublimeWindow; bool areasRestored; /// Currently shown assistant popup. QPointer currentShownAssistant; /// QWidget implementing IToolViewActionListener interface, or null QPointer activeActionListener; QTimer m_assistantTimer; private: UiController *m_controller; }; class UiToolViewFactory: public Sublime::ToolFactory { public: UiToolViewFactory(IToolViewFactory *factory): m_factory(factory) {} ~UiToolViewFactory() { delete m_factory; } virtual QWidget* create(Sublime::ToolDocument *doc, QWidget *parent = 0) override { Q_UNUSED( doc ); return m_factory->create(parent); } virtual QList< QAction* > contextMenuActions(QWidget* viewWidget) const override { return m_factory->contextMenuActions( viewWidget ); } QList toolBarActions( QWidget* viewWidget ) const override { return m_factory->toolBarActions( viewWidget ); } QString id() const override { return m_factory->id(); } private: IToolViewFactory *m_factory; }; class ViewSelectorItem: public QListWidgetItem { public: ViewSelectorItem(const QString &text, QListWidget *parent = 0, int type = Type) :QListWidgetItem(text, parent, type) {} IToolViewFactory *factory; }; class NewToolViewListWidget: public QListWidget { Q_OBJECT public: NewToolViewListWidget(MainWindow *mw, QWidget* parent = 0) :QListWidget(parent), m_mw(mw) { connect(this, &NewToolViewListWidget::doubleClicked, this, &NewToolViewListWidget::addNewToolViewByDoubleClick); } Q_SIGNALS: void addNewToolView(MainWindow *mw, QListWidgetItem *item); private Q_SLOTS: void addNewToolViewByDoubleClick(QModelIndex index) { QListWidgetItem *item = itemFromIndex(index); // Disable item so that the toolview can not be added again. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); emit addNewToolView(m_mw, item); } private: MainWindow *m_mw; }; UiController::UiController(Core *core) :Sublime::Controller(0), IUiController(), d(new UiControllerPrivate(this)) { setObjectName("UiController"); d->core = core; if (!defaultMainWindow() || (Core::self()->setupFlags() & Core::NoUi)) return; connect(qApp, &QApplication::focusChanged, this, [&] (QWidget* old, QWidget* now) { d->widgetChanged(old, now); } ); setupActions(); } UiController::~UiController() { delete d; } void UiController::setupActions() { } void UiController::mainWindowDeleted(MainWindow* mw) { if (d->defaultMainWindow == mw) d->defaultMainWindow = 0L; if (d->activeSublimeWindow == mw) d->activeSublimeWindow = 0L; } // FIXME: currently, this always create new window. Probably, // should just rename it. void UiController::switchToArea(const QString &areaName, SwitchMode switchMode) { if (switchMode == ThisWindow) { showArea(areaName, activeSublimeWindow()); return; } MainWindow *main = new MainWindow(this); addMainWindow(main); showArea(areaName, main); main->initialize(); // WTF? First, enabling this code causes crashes since we // try to disconnect some already-deleted action, or something. // Second, this code will disconnection the clients from guiFactory // of the previous main window. Ick! #if 0 //we need to add all existing guiclients to the new mainwindow //@todo adymo: add only ones that belong to the area (when the area code is there) foreach (KXMLGUIClient *client, oldMain->guiFactory()->clients()) main->guiFactory()->addClient(client); #endif main->show(); } QWidget* UiController::findToolView(const QString& name, IToolViewFactory *factory, FindFlags flags) { if(!d->areasRestored || !activeArea()) return 0; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { Sublime::ToolDocument *doc = dynamic_cast(view->document()); if(doc && doc->title() == name && view->widget()) { if(flags & Raise) view->requestRaise(); return view->widget(); } } QWidget* ret = 0; if(flags & Create) { if(!d->factoryDocuments.contains(factory)) d->factoryDocuments[factory] = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); Sublime::ToolDocument *doc = d->factoryDocuments[factory]; Sublime::View* view = addToolViewToArea(factory, doc, activeArea()); if(view) ret = view->widget(); if(flags & Raise) findToolView(name, factory, Raise); } return ret; } void UiController::raiseToolView(QWidget* toolViewWidget) { if(!d->areasRestored) return; QList< Sublime::View* > views = activeArea()->toolViews(); foreach(Sublime::View* view, views) { if(view->widget() == toolViewWidget) { view->requestRaise(); return; } } } void UiController::addToolView(const QString & name, IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; Sublime::ToolDocument *doc = new Sublime::ToolDocument(name, this, new UiToolViewFactory(factory)); d->factoryDocuments[factory] = doc; /* Until areas are restored, we don't know which views should be really added, and which not, so we just record view availability. */ if (d->areasRestored) { foreach (Sublime::Area* area, allAreas()) { addToolViewToArea(factory, doc, area); } } } void KDevelop::UiController::raiseToolView(Sublime::View * view) { foreach( Sublime::Area* area, allAreas() ) { if( area->toolViews().contains( view ) ) area->raiseToolView( view ); } if (qobject_cast(view->widget())) { d->activeActionListener = view->widget(); } } void KDevelop::UiController::removeToolView(IToolViewFactory *factory) { if (!factory) return; qCDebug(SHELL) ; //delete the tooldocument Sublime::ToolDocument *doc = d->factoryDocuments[factory]; ///@todo adymo: on document deletion all its views shall be also deleted foreach (Sublime::View *view, doc->views()) { foreach (Sublime::Area *area, allAreas()) if (area->removeToolView(view)) view->deleteLater(); } d->factoryDocuments.remove(factory); delete doc; } Sublime::Area *UiController::activeArea() { Sublime::MainWindow *m = activeSublimeWindow(); if (m) return activeSublimeWindow()->area(); return 0; } Sublime::MainWindow *UiController::activeSublimeWindow() { return d->activeSublimeWindow; } MainWindow *UiController::defaultMainWindow() { return d->defaultMainWindow; } void UiController::initialize() { defaultMainWindow()->initialize(); } void UiController::cleanup() { foreach (Sublime::MainWindow* w, mainWindows()) w->saveSettings(); saveAllAreas(KSharedConfig::openConfig()); } void UiController::selectNewToolViewToAdd(MainWindow *mw) { if (!mw || !mw->area()) return; QDialog *dia = new QDialog(mw); dia->setWindowTitle(i18n("Select Tool View to Add")); auto mainLayout = new QVBoxLayout(dia); NewToolViewListWidget *list = new NewToolViewListWidget(mw, dia); list->setSelectionMode(QAbstractItemView::ExtendedSelection); list->setSortingEnabled(true); for (QMap::const_iterator it = d->factoryDocuments.constBegin(); it != d->factoryDocuments.constEnd(); ++it) { ViewSelectorItem *item = new ViewSelectorItem(it.value()->title(), list); item->factory = it.key(); if (!item->factory->allowMultiple() && toolViewPresent(it.value(), mw->area())) { // Disable item if the toolview is already present. item->setFlags(item->flags() & ~Qt::ItemIsEnabled); } list->addItem(item); } list->setFocus(); connect(list, &NewToolViewListWidget::addNewToolView, this, &UiController::addNewToolView); mainLayout->addWidget(list); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dia->connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); dia->connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); mainLayout->addWidget(buttonBox); if (dia->exec() == QDialog::Accepted) { foreach (QListWidgetItem* item, list->selectedItems()) { addNewToolView(mw, item); } } delete dia; } void UiController::addNewToolView(MainWindow *mw, QListWidgetItem* item) { ViewSelectorItem *current = static_cast(item); Sublime::ToolDocument *doc = d->factoryDocuments[current->factory]; Sublime::View *view = doc->createView(); mw->area()->addToolView(view, Sublime::dockAreaToPosition(current->factory->defaultPosition())); current->factory->viewCreated(view); } void UiController::showSettingsDialog() { auto editorConfigPage = new EditorConfigPage(activeMainWindow()); auto configPages = QVector { new UiPreferences(activeMainWindow()), new PluginPreferences(activeMainWindow()), new SourceFormatterSettings(activeMainWindow()), new ProjectPreferences(activeMainWindow()), new EnvironmentPreferences(QString(), activeMainWindow()), new CCPreferences(activeMainWindow()), new BGPreferences(activeMainWindow()), new TemplateConfig(activeMainWindow()), editorConfigPage }; ConfigDialog cfgDlg(configPages, activeMainWindow()); auto addPluginPages = [&](IPlugin* plugin) { for (int i = 0, numPages = plugin->configPages(); i < numPages; ++i) { // insert them before the editor config page cfgDlg.addConfigPage(plugin->configPage(i, &cfgDlg), editorConfigPage); } }; for (IPlugin* plugin : ICore::self()->pluginController()->loadedPlugins()) { addPluginPages(plugin); } // TODO: only load settings if a UI related page was changed? connect(&cfgDlg, &ConfigDialog::configSaved, activeSublimeWindow(), &Sublime::MainWindow::loadSettings); // make sure that pages get added whenever a new plugin is loaded (probably from the plugin selection dialog) // removal on plugin unload is already handled in ConfigDialog connect(ICore::self()->pluginController(), &IPluginController::pluginLoaded, &cfgDlg, addPluginPages); cfgDlg.exec(); } Sublime::Controller* UiController::controller() { return this; } KParts::MainWindow *UiController::activeMainWindow() { return activeSublimeWindow(); } void UiController::saveArea(Sublime::Area * area, KConfigGroup & group) { area->save(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); set->saveFromArea(area, area->rootIndex()); } } void UiController::loadArea(Sublime::Area * area, const KConfigGroup & group) { area->load(group); if (!area->workingSet().isEmpty()) { WorkingSet* set = Core::self()->workingSetControllerInternal()->getWorkingSet(area->workingSet()); Q_ASSERT(set->isConnected(area)); Q_UNUSED(set); } } void UiController::saveAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = mainWindows().size(); uiConfig.writeEntry("Main Windows Count", wc); for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); foreach (Sublime::Area* defaultArea, defaultAreas()) { // FIXME: using object name seems ugly. QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); areaConfig.deleteGroup(); areaConfig.writeEntry("id", type); saveArea(area, areaConfig); areaConfig.sync(); } } uiConfig.sync(); } void UiController::loadAllAreas(KSharedConfigPtr config) { KConfigGroup uiConfig(config, "User Interface"); int wc = uiConfig.readEntry("Main Windows Count", 1); /* It is expected the main windows are restored before restoring areas. */ if (wc > mainWindows().size()) wc = mainWindows().size(); QList changedAreas; /* Offer all toolviews to the default areas. */ foreach (Sublime::Area *area, defaultAreas()) { QMap::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } /* Restore per-windows areas. */ for (int w = 0; w < wc; ++w) { KConfigGroup mainWindowConfig(&uiConfig, QStringLiteral("Main Window %1").arg(w)); Sublime::MainWindow *mw = mainWindows()[w]; /* We loop over default areas. This means that if the config file has an area of some type that is not in default set, we'd just ignore it. I think it's fine -- the model were a given mainwindow can has it's own area types not represented in the default set is way too complex. */ foreach (Sublime::Area* defaultArea, defaultAreas()) { QString type = defaultArea->objectName(); Sublime::Area* area = this->area(w, type); KConfigGroup areaConfig(&mainWindowConfig, "Area " + type); qCDebug(SHELL) << "Trying to restore area " << type; /* This is just an easy check that a group exists, to avoid "restoring" area from empty config group, wiping away programmatically installed defaults. */ if (areaConfig.readEntry("id", "") == type) { qCDebug(SHELL) << "Restoring area " << type; loadArea(area, areaConfig); } // At this point we know which toolviews the area wants. // Tender all tool views we have. QMap::const_iterator i, e; for (i = d->factoryDocuments.constBegin(), e = d->factoryDocuments.constEnd(); i != e; ++i) { addToolViewIfWanted(i.key(), i.value(), area); } } // Force reload of the changes. showAreaInternal(mw->area(), mw); mw->enableAreaSettingsSave(); } d->areasRestored = true; } void UiController::addToolViewToDockArea(IToolViewFactory* factory, Qt::DockWidgetArea area) { addToolViewToArea(factory, d->factoryDocuments[factory], activeArea(), Sublime::dockAreaToPosition(area)); } bool UiController::toolViewPresent(Sublime::ToolDocument* doc, Sublime::Area* area) { foreach (Sublime::View *view, doc->views()) { if( area->toolViews().contains( view ) ) return true; } return false; } void UiController::addToolViewIfWanted(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area) { if (area->wantToolView(factory->id())) { addToolViewToArea(factory, doc, area); } } Sublime::View* UiController::addToolViewToArea(IToolViewFactory* factory, Sublime::ToolDocument* doc, Sublime::Area* area, Sublime::Position p) { Sublime::View* view = doc->createView(); area->addToolView( view, p == Sublime::AllPositions ? Sublime::dockAreaToPosition(factory->defaultPosition()) : p); connect(view, &Sublime::View::raise, this, static_cast(&UiController::raiseToolView)); factory->viewCreated(view); return view; } void UiController::registerStatus(QObject* status) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; mw->registerStatus(status); } void UiController::showErrorMessage(const QString& message, int timeout) { Sublime::MainWindow* w = activeSublimeWindow(); if (!w) return; MainWindow* mw = qobject_cast(w); if (!mw) return; QMetaObject::invokeMethod(mw, "showErrorMessage", Q_ARG(QString, message), Q_ARG(int, timeout)); } void UiController::hideAssistant() { if (d->currentShownAssistant) { d->currentShownAssistant->hide(); } } void UiController::popUpAssistant(const KDevelop::IAssistant::Ptr& assistant) { if(!assistant) return; Sublime::View* view = d->activeSublimeWindow->activeView(); if( !view ) { qCDebug(SHELL) << "no active view in mainwindow"; return; } auto editorView = qobject_cast(view->widget()); Q_ASSERT(editorView); if (editorView) { if ( !d->currentShownAssistant ) { d->currentShownAssistant = new AssistantPopup; } d->currentShownAssistant->reset(editorView, assistant); } } const QMap< IToolViewFactory*, Sublime::ToolDocument* >& UiController::factoryDocuments() const { return d->factoryDocuments; } QWidget* UiController::activeToolViewActionListener() const { return d->activeActionListener; } } #include "uicontroller.moc" #include "moc_uicontroller.cpp" diff --git a/shell/workingsets/workingset.cpp b/shell/workingsets/workingset.cpp index b2ed6c02d0..d5331e8914 100644 --- a/shell/workingsets/workingset.cpp +++ b/shell/workingsets/workingset.cpp @@ -1,573 +1,573 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "workingset.h" #include "../debug.h" #include #include -#include -#include -#include - #include +#include + +#include +#include #include #include #include #include #include #include #include #define SYNC_OFTEN using namespace KDevelop; bool WorkingSet::m_loading = false; namespace { QIcon generateIcon(const WorkingSetIconParameters& params) { QImage pixmap(16, 16, QImage::Format_ARGB32); // fill the background with a transparent color pixmap.fill(QColor::fromRgba(qRgba(0, 0, 0, 0))); const uint coloredCount = params.coloredCount; // coordinates of the rectangles to draw, for 16x16 icons specifically QList rects; rects << QRect(1, 1, 5, 5) << QRect(1, 9, 5, 5) << QRect(9, 1, 5, 5) << QRect(9, 9, 5, 5); if ( params.swapDiagonal ) { rects.swap(1, 2); } QPainter painter(&pixmap); // color for non-colored squares, paint them brighter if the working set is the active one const int inact = 40; QColor darkColor = QColor::fromRgb(inact, inact, inact); // color for colored squares // this code is not fragile, you can just tune the magic formulas at random and see what looks good. // just make sure to keep it within the 0-360 / 0-255 / 0-255 space of the HSV model QColor brightColor = QColor::fromHsv(params.hue, qMin(255, 215 + (params.setId*5) % 150), 205 + (params.setId*11) % 50); // Y'UV "Y" value, the approximate "lightness" of the color // If it is above 0.6, then making the color darker a bit is okay, // if it is below 0.35, then the color should be a bit brighter. float brightY = 0.299 * brightColor.redF() + 0.587 * brightColor.greenF() + 0.114 * brightColor.blueF(); if ( brightY > 0.6 ) { if ( params.setId % 7 < 2 ) { // 2/7 chance to make the color significantly darker brightColor = brightColor.darker(120 + (params.setId*7) % 35); } else if ( params.setId % 5 == 0 ) { // 1/5 chance to make it a bit darker brightColor = brightColor.darker(110 + (params.setId*3) % 10); } } if ( brightY < 0.35 ) { // always make the color brighter to avoid very dark colors (like rgb(0, 0, 255)) brightColor = brightColor.lighter(120 + (params.setId*13) % 55); } int at = 0; foreach ( const QRect& rect, rects ) { QColor currentColor; // pick the colored squares; you can get different patterns by re-ordering the "rects" list if ( (at + params.setId*7) % 4 < coloredCount ) { currentColor = brightColor; } else { currentColor = darkColor; } // draw the filling of the square painter.setPen(QColor(currentColor)); painter.setBrush(QBrush(currentColor)); painter.drawRect(rect); // draw a slight set-in shadow for the square -- it's barely recognizeable, // but it looks way better than without painter.setBrush(Qt::NoBrush); painter.setPen(QColor(0, 0, 0, 50)); painter.drawRect(rect); painter.setPen(QColor(0, 0, 0, 25)); painter.drawRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2); at += 1; } return QIcon(QPixmap::fromImage(pixmap)); } } WorkingSet::WorkingSet(const QString& id) : QObject() , m_id(id) , m_icon(generateIcon(id)) { } void WorkingSet::saveFromArea( Sublime::Area* a, Sublime::AreaIndex * area, KConfigGroup setGroup, KConfigGroup areaGroup ) { if (area->isSplit()) { setGroup.writeEntry("Orientation", area->orientation() == Qt::Horizontal ? "Horizontal" : "Vertical"); if (area->first()) { saveFromArea(a, area->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0")); } if (area->second()) { saveFromArea(a, area->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1")); } } else { setGroup.writeEntry("View Count", area->viewCount()); areaGroup.writeEntry("View Count", area->viewCount()); int index = 0; foreach (Sublime::View* view, area->views()) { //The working set config gets an updated list of files QString docSpec = view->document()->documentSpecifier(); //only save the documents from protocols KIO understands //otherwise we try to load kdev:// too early if (!KProtocolInfo::isKnownProtocol(QUrl(docSpec))) { continue; } setGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); setGroup.writeEntry(QStringLiteral("View %1 Type").arg(index), view->document()->documentType()); //The area specific config stores the working set documents in order along with their state areaGroup.writeEntry(QStringLiteral("View %1").arg(index), docSpec); areaGroup.writeEntry(QStringLiteral("View %1 State").arg(index), view->viewState()); ++index; } } } bool WorkingSet::isEmpty() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return !group.hasKey("Orientation") && group.readEntry("View Count", 0) == 0; } struct DisableMainWindowUpdatesFromArea { DisableMainWindowUpdatesFromArea(Sublime::Area* area) : m_area(area) { if(area) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { if(window->updatesEnabled()) { wasUpdatesEnabled.insert(window); window->setUpdatesEnabled(false); } } } } } ~DisableMainWindowUpdatesFromArea() { if(m_area) { foreach(Sublime::MainWindow* window, wasUpdatesEnabled) { window->setUpdatesEnabled(wasUpdatesEnabled.contains(window)); } } } Sublime::Area* m_area; QSet wasUpdatesEnabled; }; void loadFileList(QStringList& ret, KConfigGroup group) { if (group.hasKey("Orientation")) { QStringList subgroups = group.groupList(); if (subgroups.contains("0")) { { KConfigGroup subgroup(&group, "0"); loadFileList(ret, subgroup); } if (subgroups.contains("1")) { KConfigGroup subgroup(&group, "1"); loadFileList(ret, subgroup); } } } else { int viewCount = group.readEntry("View Count", 0); for (int i = 0; i < viewCount; ++i) { QString type = group.readEntry(QStringLiteral("View %1 Type").arg(i), ""); QString specifier = group.readEntry(QStringLiteral("View %1").arg(i), ""); ret << specifier; } } } QStringList WorkingSet::fileList() const { QStringList ret; KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); loadFileList(ret, group); return ret; } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { PushValue enableLoading(m_loading, true); /// We cannot disable the updates here, because (probably) due to a bug in Qt, /// which causes the updates to stay disabled forever after some complex operations /// on the sub-views. This could be reproduced by creating two working-sets with complex /// split-view configurations and switching between them. Re-enabling the updates doesn't help. // DisableMainWindowUpdatesFromArea updatesDisabler(area); qCDebug(SHELL) << "loading working-set" << m_id << "into area" << area; QMultiMap recycle; foreach( Sublime::View* view, area->views() ) recycle.insert( view->document()->documentSpecifier(), area->removeView(view) ); qCDebug(SHELL) << "recycling" << recycle.size() << "old views"; Q_ASSERT( area->views().empty() ); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); loadToArea(area, areaIndex, setGroup, areaGroup, recycle); // Delete views which were not recycled qCDebug(SHELL) << "deleting " << recycle.size() << " old views"; qDeleteAll( recycle.values() ); area->setActiveView(0); //activate view in the working set /// @todo correctly select one out of multiple equal views QString activeView = areaGroup.readEntry("Active View", QString()); foreach (Sublime::View *v, area->views()) { if (v->document()->documentSpecifier() == activeView) { area->setActiveView(v); break; } } if( !area->activeView() && area->views().size() ) area->setActiveView( area->views()[0] ); if( area->activeView() ) { foreach(Sublime::MainWindow* window, Core::self()->uiControllerInternal()->mainWindows()) { if(window->area() == area) { window->activateView( area->activeView() ); } } } } void WorkingSet::loadToArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex, KConfigGroup setGroup, KConfigGroup areaGroup, QMultiMap& recycle) { Q_ASSERT( !areaIndex->isSplit() ); if (setGroup.hasKey("Orientation")) { QStringList subgroups = setGroup.groupList(); /// @todo also save and restore the ratio if (subgroups.contains("0") && subgroups.contains("1")) { // qCDebug(SHELL) << "has zero, split:" << split; Qt::Orientation orientation = setGroup.readEntry("Orientation", "Horizontal") == "Vertical" ? Qt::Vertical : Qt::Horizontal; if(!areaIndex->isSplit()){ areaIndex->split(orientation); }else{ areaIndex->setOrientation(orientation); } loadToArea(area, areaIndex->first(), KConfigGroup(&setGroup, "0"), KConfigGroup(&areaGroup, "0"), recycle); loadToArea(area, areaIndex->second(), KConfigGroup(&setGroup, "1"), KConfigGroup(&areaGroup, "1"), recycle); if( areaIndex->first()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->first()); else if( areaIndex->second()->viewCount() == 0 ) areaIndex->unsplit(areaIndex->second()); } } else { //Load all documents from the workingset into this areaIndex int viewCount = setGroup.readEntry("View Count", 0); QMap createdViews; for (int i = 0; i < viewCount; ++i) { QString type = setGroup.readEntry(QStringLiteral("View %1 Type").arg(i), QString()); QString specifier = setGroup.readEntry(QStringLiteral("View %1").arg(i), QString()); if (type.isEmpty() || specifier.isEmpty()) { continue; } Sublime::View* previousView = area->views().empty() ? 0 : area->views().back(); QMultiMap::iterator it = recycle.find( specifier ); if( it != recycle.end() ) { area->addView( *it, areaIndex, previousView ); recycle.erase( it ); continue; } IDocument* doc = Core::self()->documentControllerInternal()->openDocument(QUrl::fromUserInput(specifier), KTextEditor::Cursor::invalid(), IDocumentController::DoNotActivate | IDocumentController::DoNotCreateView); Sublime::Document *document = dynamic_cast(doc); if (document) { Sublime::View* view = document->createView(); area->addView(view, areaIndex, previousView); createdViews[i] = view; } else { qWarning() << "Unable to create view of type " << type; } } //Load state for (int i = 0; i < viewCount; ++i) { QString state = areaGroup.readEntry(QStringLiteral("View %1 State").arg(i)); if (state.length() && createdViews.contains(i)) createdViews[i]->setState(state); } } } void deleteGroupRecursive(KConfigGroup group) { // qCDebug(SHELL) << "deleting" << group.name(); foreach(const QString& entry, group.entryMap().keys()) { group.deleteEntry(entry); } Q_ASSERT(group.entryMap().isEmpty()); foreach(const QString& subGroup, group.groupList()) { deleteGroupRecursive(group.group(subGroup)); group.deleteGroup(subGroup); } //Why doesn't this work? // Q_ASSERT(group.groupList().isEmpty()); group.deleteGroup(); #ifdef SYNC_OFTEN group.sync(); #endif } void WorkingSet::deleteSet(bool force, bool silent) { if(m_areas.isEmpty() || force) { emit aboutToRemove(this); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); deleteGroupRecursive(group); #ifdef SYNC_OFTEN setConfig.sync(); #endif if(!silent) emit setChangedSignificantly(); } } void WorkingSet::saveFromArea(Sublime::Area* area, Sublime::AreaIndex* areaIndex) { qCDebug(SHELL) << "saving" << m_id << "from area"; bool wasPersistent = isPersistent(); KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup setGroup = setConfig.group(m_id); deleteGroupRecursive(setGroup); KConfigGroup areaGroup = setConfig.group(m_id + '|' + area->title()); QString lastActiveView = areaGroup.readEntry("Active View", ""); deleteGroupRecursive(areaGroup); if (area->activeView() && area->activeView()->document()) areaGroup.writeEntry("Active View", area->activeView()->document()->documentSpecifier()); else areaGroup.writeEntry("Active View", lastActiveView); saveFromArea(area, areaIndex, setGroup, areaGroup); if(isEmpty()) { deleteGroupRecursive(setGroup); deleteGroupRecursive(areaGroup); } setPersistent(wasPersistent); #ifdef SYNC_OFTEN setConfig.sync(); #endif emit setChangedSignificantly(); } void WorkingSet::areaViewAdded(Sublime::AreaIndex*, Sublime::View*) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "added view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } changed(area); } void WorkingSet::areaViewRemoved(Sublime::AreaIndex*, Sublime::View* view) { Sublime::Area* area = qobject_cast(sender()); Q_ASSERT(area); Q_ASSERT(area->workingSet() == m_id); qCDebug(SHELL) << "removed view in" << area << ", id" << m_id; if (m_loading) { qCDebug(SHELL) << "doing nothing because loading"; return; } foreach(Sublime::Area* otherArea, m_areas) { if(otherArea == area) continue; bool hadDocument = false; foreach(Sublime::View* areaView, otherArea->views()) if(view->document() == areaView->document()) hadDocument = true; if(!hadDocument) { // We do this to prevent UI flicker. The view has already been removed from // one of the connected areas, so the working-set has already recorded the change. return; } } changed(area); } void WorkingSet::setPersistent(bool persistent) { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); group.writeEntry("persistent", persistent); #ifdef SYNC_OFTEN group.sync(); #endif qCDebug(SHELL) << "setting" << m_id << "persistent:" << persistent; } bool WorkingSet::isPersistent() const { KConfigGroup setConfig(Core::self()->activeSession()->config(), "Working File Sets"); KConfigGroup group = setConfig.group(m_id); return group.readEntry("persistent", false); } QIcon WorkingSet::icon() const { return m_icon; } bool WorkingSet::isConnected( Sublime::Area* area ) { return m_areas.contains( area ); } QString WorkingSet::id() const { return m_id; } bool WorkingSet::hasConnectedAreas() const { return !m_areas.isEmpty(); } bool WorkingSet::hasConnectedAreas( QList< Sublime::Area* > areas ) const { foreach( Sublime::Area* area, areas ) if ( m_areas.contains( area ) ) return true; return false; } void WorkingSet::connectArea( Sublime::Area* area ) { if ( m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to double-connect area"; return; } qCDebug(SHELL) << "connecting" << m_id << "to area" << area; // Q_ASSERT(area->workingSet() == m_id); m_areas.push_back( area ); connect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); connect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); } void WorkingSet::disconnectArea( Sublime::Area* area ) { if ( !m_areas.contains( area ) ) { qCDebug(SHELL) << "tried to disconnect not connected area"; return; } qCDebug(SHELL) << "disconnecting" << m_id << "from area" << area; // Q_ASSERT(area->workingSet() == m_id); disconnect( area, &Sublime::Area::viewAdded, this, &WorkingSet::areaViewAdded ); disconnect( area, &Sublime::Area::viewRemoved, this, &WorkingSet::areaViewRemoved ); m_areas.removeAll( area ); } void WorkingSet::changed( Sublime::Area* area ) { if ( m_loading ) { return; } { //Do not capture changes done while loading PushValue enableLoading( m_loading, true ); qCDebug(SHELL) << "recording change done to" << m_id; saveFromArea( area, area->rootIndex() ); for ( QList< QPointer< Sublime::Area > >::iterator it = m_areas.begin(); it != m_areas.end(); ++it ) { if (( *it ) != area ) { loadToArea(( *it ), ( *it )->rootIndex() ); } } } emit setChangedSignificantly(); } diff --git a/shell/workingsets/workingsettoolbutton.cpp b/shell/workingsets/workingsettoolbutton.cpp index 9678e5566d..2efadb9f37 100644 --- a/shell/workingsets/workingsettoolbutton.cpp +++ b/shell/workingsets/workingsettoolbutton.cpp @@ -1,178 +1,175 @@ /* Copyright David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "workingsettoolbutton.h" -#include #include - -#include -#include +#include #include #include #include "core.h" #include "mainwindow.h" #include "workingset.h" #include "workingsetcontroller.h" #include "workingsethelpers.h" #include "documentcontroller.h" #include #include #include using namespace KDevelop; WorkingSetToolButton::WorkingSetToolButton(QWidget* parent, WorkingSet* set) : QToolButton(parent), m_set(set), m_toolTipEnabled(true) { setFocusPolicy(Qt::NoFocus); setWorkingSet(set); setAutoRaise(true); connect(this, &WorkingSetToolButton::clicked, this, &WorkingSetToolButton::buttonTriggered); } WorkingSet* WorkingSetToolButton::workingSet() const { return m_set; } void WorkingSetToolButton::setWorkingSet(WorkingSet* set) { m_set = set; setIcon(set ? set->icon() : QIcon()); } void WorkingSetToolButton::contextMenuEvent(QContextMenuEvent* ev) { showTooltip(); ev->accept(); } void WorkingSetToolButton::intersectSet() { Q_ASSERT(m_set); m_set->setPersistent(true); filterViews(Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet() & m_set->fileList().toSet()); } void WorkingSetToolButton::subtractSet() { Q_ASSERT(m_set); m_set->setPersistent(true); filterViews(Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet() - m_set->fileList().toSet()); } void WorkingSetToolButton::mergeSet() { Q_ASSERT(m_set); QSet< QString > loadFiles = m_set->fileList().toSet() - Core::self()->workingSetControllerInternal()->getWorkingSet(mainWindow()->area()->workingSet())->fileList().toSet(); foreach(const QString& file, loadFiles) Core::self()->documentController()->openDocument(QUrl::fromUserInput(file)); } void WorkingSetToolButton::duplicateSet() { Q_ASSERT(m_set); if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; WorkingSet* set = Core::self()->workingSetControllerInternal()->newWorkingSet("clone"); set->setPersistent(true); set->saveFromArea(mainWindow()->area(), mainWindow()->area()->rootIndex()); mainWindow()->area()->setWorkingSet(set->id()); } void WorkingSetToolButton::loadSet() { Q_ASSERT(m_set); m_set->setPersistent(true); if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; mainWindow()->area()->setWorkingSet(QString(m_set->id())); } void WorkingSetToolButton::closeSet(bool ask) { Q_ASSERT(m_set); m_set->setPersistent(true); m_set->saveFromArea(mainWindow()->area(), mainWindow()->area()->rootIndex()); if(ask && !Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; mainWindow()->area()->setWorkingSet(QString()); } bool WorkingSetToolButton::event(QEvent* e) { if(m_toolTipEnabled && e->type() == QEvent::ToolTip) { showTooltip(); e->accept(); return true; } - + return QToolButton::event(e); } void WorkingSetToolButton::showTooltip() { Q_ASSERT(m_set); static WorkingSetToolButton* oldTooltipButton; WorkingSetController* controller = Core::self()->workingSetControllerInternal(); if(controller->tooltip() && oldTooltipButton == this) return; oldTooltipButton = this; controller->showToolTip(m_set, QCursor::pos() + QPoint(10, 20)); QRect extended(parentWidget()->mapToGlobal(geometry().topLeft()), parentWidget()->mapToGlobal(geometry().bottomRight())); controller->tooltip()->addExtendRect(extended); } void WorkingSetToolButton::buttonTriggered() { Q_ASSERT(m_set); if(mainWindow()->area()->workingSet() == m_set->id()) { showTooltip(); }else{ //Only close the working-set if the file was saved before if(!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(mainWindow(), KDevelop::IDocument::Default, true)) return; m_set->setPersistent(true); mainWindow()->area()->setWorkingSet(m_set->id()); } } diff --git a/sublime/container.cpp b/sublime/container.cpp index 4444077efe..28bc24f86d 100644 --- a/sublime/container.cpp +++ b/sublime/container.cpp @@ -1,578 +1,577 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "container.h" -#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 #include "view.h" #include "document.h" -#include -#include #include "urldocument.h" #include namespace Sublime { // struct ContainerPrivate class ContainerTabBar : public QTabBar { Q_OBJECT public: ContainerTabBar(Container* container) : QTabBar(container), m_container(container) { } - + virtual bool event(QEvent* ev) override { if(ev->type() == QEvent::ToolTip) { ev->accept(); - + int tab = tabAt(mapFromGlobal(QCursor::pos())); - + if(tab != -1) { m_container->showTooltipForTab(tab); } - + return true; } - + return QTabBar::event(ev); } virtual void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::MidButton) { // just close on midbutton, drag can still be done with left mouse button int tab = tabAt(mapFromGlobal(QCursor::pos())); if (tab != -1) { emit tabCloseRequested(tab); } return; } QTabBar::mousePressEvent(event); } Q_SIGNALS: void newTabRequested(); private: Container* m_container; }; bool sortViews(const View* const lhs, const View* const rhs) { return lhs->document()->title().compare(rhs->document()->title(), Qt::CaseInsensitive) < 0; } struct ContainerPrivate { QBoxLayout* layout; QMap viewForWidget; ContainerTabBar *tabBar; QStackedWidget *stack; KSqueezedTextLabel *fileNameCorner; QLabel *fileStatus; KSqueezedTextLabel *statusCorner; QPointer leftCornerWidget; QToolButton* documentListButton; QMenu* documentListMenu; QMap documentListActionForView; /** * Updates the context menu which is shown when * the document list button in the tab bar is clicked. * * It shall build a popup menu which contains all currently * enabled views using the title their document provides. */ void updateDocumentListPopupMenu() { qDeleteAll(documentListActionForView); documentListActionForView.clear(); documentListMenu->clear(); // create a lexicographically sorted list QVector views; views.reserve(viewForWidget.size()); - foreach(View* view, viewForWidget){ + foreach(View* view, viewForWidget){ views << view; } std::sort(views.begin(), views.end(), sortViews); foreach(View* view, views) { QAction* action = documentListMenu->addAction(view->document()->title()); action->setData(QVariant::fromValue(view)); documentListActionForView[view] = action; action->setIcon(view->document()->icon()); ///FIXME: push this code somehow into shell, such that we can access the project model for /// icons and also get a neat, short path like the document switcher. } } }; class UnderlinedLabel: public KSqueezedTextLabel { public: UnderlinedLabel(QTabBar *tabBar, QWidget* parent = 0) :KSqueezedTextLabel(parent), m_tabBar(tabBar) { } protected: virtual void paintEvent(QPaintEvent *ev) override { if (m_tabBar->isVisible() && m_tabBar->count() > 0) { QStylePainter p(this); QStyleOptionTabBarBase optTabBase; optTabBase.init(m_tabBar); optTabBase.shape = m_tabBar->shape(); optTabBase.tabBarRect = m_tabBar->rect(); optTabBase.tabBarRect.moveRight(0); QStyleOptionTab tabOverlap; tabOverlap.shape = m_tabBar->shape(); int overlap = style()->pixelMetric(QStyle::PM_TabBarBaseOverlap, &tabOverlap, m_tabBar); - if( overlap > 0 ) + if( overlap > 0 ) { QRect rect; rect.setRect(0, height()-overlap, width(), overlap); optTabBase.rect = rect; } if( m_tabBar->drawBase() ) { p.drawPrimitive(QStyle::PE_FrameTabBarBase, optTabBase); } } KSqueezedTextLabel::paintEvent(ev); } QTabBar *m_tabBar; }; class StatusLabel: public UnderlinedLabel { public: StatusLabel(QTabBar *tabBar, QWidget* parent = 0): UnderlinedLabel(tabBar, parent) { setAlignment(Qt::AlignRight | Qt::AlignVCenter); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed); } virtual QSize minimumSizeHint() const override { QRect rect = style()->itemTextRect(fontMetrics(), QRect(), Qt::AlignRight, true, i18n("Line: 00000 Col: 000")); rect.setHeight(m_tabBar->height()); return rect.size(); } }; // class Container Container::Container(QWidget *parent) :QWidget(parent), d(new ContainerPrivate()) { KAcceleratorManager::setNoAccel(this); QBoxLayout *l = new QBoxLayout(QBoxLayout::TopToBottom, this); l->setMargin(0); l->setSpacing(0); d->layout = new QBoxLayout(QBoxLayout::LeftToRight); d->layout->setMargin(0); d->layout->setSpacing(0); d->documentListMenu = new QMenu(this); d->documentListButton = new QToolButton(this); d->documentListButton->setIcon(QIcon::fromTheme("format-list-unordered")); d->documentListButton->setMenu(d->documentListMenu); d->documentListButton->setPopupMode(QToolButton::InstantPopup); d->documentListButton->setAutoRaise(true); d->documentListButton->setToolTip(i18n("Show sorted list of opened documents")); d->documentListButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); d->layout->addWidget(d->documentListButton); d->tabBar = new ContainerTabBar(this); d->tabBar->setContextMenuPolicy(Qt::CustomContextMenu); d->layout->addWidget(d->tabBar); d->fileStatus = new QLabel( this ); d->fileStatus->setFixedSize( QSize( 16, 16 ) ); d->layout->addWidget(d->fileStatus); d->fileNameCorner = new UnderlinedLabel(d->tabBar, this); d->layout->addWidget(d->fileNameCorner); d->statusCorner = new StatusLabel(d->tabBar, this); d->layout->addWidget(d->statusCorner); l->addLayout(d->layout); d->stack = new QStackedWidget(this); l->addWidget(d->stack); connect(d->tabBar, &ContainerTabBar::currentChanged, this, &Container::widgetActivated); connect(d->tabBar, &ContainerTabBar::tabCloseRequested, this, static_cast(&Container::requestClose)); connect(d->tabBar, &ContainerTabBar::newTabRequested, this, &Container::newTabRequested); connect(d->tabBar, &ContainerTabBar::tabMoved, this, &Container::tabMoved); connect(d->tabBar, &ContainerTabBar::customContextMenuRequested, this, &Container::contextMenu); connect(d->tabBar, &ContainerTabBar::tabBarDoubleClicked, this, &Container::doubleClickTriggered); connect(d->documentListMenu, &QMenu::triggered, this, &Container::documentListActionTriggered); KConfigGroup group = KSharedConfig::openConfig()->group("UiSettings"); setTabBarHidden(group.readEntry("TabBarVisibility", 1) == 0); d->tabBar->setTabsClosable(true); d->tabBar->setMovable(true); d->tabBar->setExpanding(false); d->tabBar->setSelectionBehaviorOnRemove(QTabBar::SelectPreviousTab); } void Container::setLeftCornerWidget(QWidget* widget) { if(d->leftCornerWidget.data() == widget) { if(d->leftCornerWidget) d->leftCornerWidget.data()->setParent(0); }else{ delete d->leftCornerWidget.data(); d->leftCornerWidget.clear(); } d->leftCornerWidget = widget; if(!widget) return; widget->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); d->layout->insertWidget(0, widget); widget->show(); } Container::~Container() { delete d; } void Container::requestClose(int idx) { emit requestClose(widget(idx)); } void Container::widgetActivated(int idx) { if (idx < 0) return; if (QWidget* w = d->stack->widget(idx)) { Sublime::View* view = d->viewForWidget.value(w); if(view) emit activateView(view); } } void Container::addWidget(View *view, int position) { QWidget *w = view->widget(this); int idx = 0; if (position != -1) { idx = d->stack->insertWidget(position, w); } else idx = d->stack->addWidget(w); d->tabBar->insertTab(idx, view->document()->statusIcon(), view->document()->title()); Q_ASSERT(view); d->viewForWidget[w] = view; // Update document list context menu. This has to be called before // setCurrentWidget, because we call the status icon and title update slots // already, which in turn need the document list menu to be setup. d->updateDocumentListPopupMenu(); setCurrentWidget(d->stack->currentWidget()); // This fixes a strange layouting bug, that could be reproduced like this: Open a few files in KDevelop, activate the rightmost tab. // Then temporarily switch to another area, and then switch back. After that, the tab-bar was gone. // The problem could only be fixed by closing/opening another view. d->tabBar->setMinimumHeight(d->tabBar->sizeHint().height()); connect(view, &View::statusChanged, this, &Container::statusChanged); connect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); connect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); } void Container::statusChanged(Sublime::View* view) { d->statusCorner->setText(view->viewStatus()); } void Container::statusIconChanged(Document* doc) { QMapIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { d->fileStatus->setPixmap( doc->statusIcon().pixmap( QSize( 16,16 ) ) ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabIcon(tabIndex, doc->statusIcon()); } // Update the document title's menu associated action // using the View* index map Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setIcon(doc->icon()); break; } } } void Container::documentTitleChanged(Sublime::Document* doc) { QMapIterator it = d->viewForWidget; while (it.hasNext()) { if (it.next().value()->document() == doc) { QString txt = doc->title(); //TODO: Maybe add new virtual in Document to support supplying this // extended information from subclasses like IDocument which can use // the rest of the kdevplatform API UrlDocument* udoc = dynamic_cast( doc ); if( udoc ) { txt = txt + " (" + udoc->url().toDisplayString(QUrl::PreferLocalFile) + ')'; } d->fileNameCorner->setText( txt ); int tabIndex = d->stack->indexOf(it.key()); if (tabIndex != -1) { d->tabBar->setTabText(tabIndex, doc->title()); } // Update document list popup title Q_ASSERT(d->documentListActionForView.contains(it.value())); d->documentListActionForView[it.value()]->setText(doc->title()); break; } } } int Container::count() const { return d->stack->count(); } QWidget* Container::currentWidget() const { return d->stack->currentWidget(); } void Container::setCurrentWidget(QWidget* w) { d->stack->setCurrentWidget(w); //prevent from emitting activateView() signal on tabbar active tab change //this function is called from MainWindow::activateView() //which does the activation without any additional signals d->tabBar->blockSignals(true); d->tabBar->setCurrentIndex(d->stack->indexOf(w)); d->tabBar->blockSignals(false); if (View* view = viewForWidget(w)) { statusChanged(view); if (!d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us statusIconChanged( view->document() ); documentTitleChanged( view->document() ); } } } QWidget* Container::widget(int i) const { return d->stack->widget(i); } int Container::indexOf(QWidget* w) const { return d->stack->indexOf(w); } void Sublime::Container::removeWidget(QWidget *w) { if (w) { int widgetIdx = d->stack->indexOf(w); d->stack->removeWidget(w); d->tabBar->removeTab(widgetIdx); if (d->tabBar->currentIndex() != -1 && !d->tabBar->isVisible()) { // repaint icon and document title only in tabbar-less mode // tabbar will do repainting for us QWidget* w = widget( d->tabBar->currentIndex() ); if( w ) { statusIconChanged( d->viewForWidget[w]->document() ); documentTitleChanged( d->viewForWidget[w]->document() ); } } View* view = d->viewForWidget.take(w); if (view) { disconnect(view->document(), &Document::titleChanged, this, &Container::documentTitleChanged); disconnect(view->document(), &Document::statusIconChanged, this, &Container::statusIconChanged); disconnect(view, &View::statusChanged, this, &Container::statusChanged); // Update document list context menu Q_ASSERT(d->documentListActionForView.contains(view)); delete d->documentListActionForView.take(view); } } } bool Container::hasWidget(QWidget *w) { return d->stack->indexOf(w) != -1; } View *Container::viewForWidget(QWidget *w) const { return d->viewForWidget.value(w); } void Container::setTabBarHidden(bool hide) { if (hide) { d->tabBar->hide(); d->fileNameCorner->show(); d->fileStatus->show(); } else { d->fileNameCorner->hide(); d->fileStatus->hide(); d->tabBar->show(); } } void Container::tabMoved(int from, int to) { QWidget *w = d->stack->widget(from); d->stack->removeWidget(w); d->stack->insertWidget(to, w); d->viewForWidget[w]->notifyPositionChanged(to); } void Container::contextMenu( const QPoint& pos ) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); int currentTab = d->tabBar->tabAt(pos); QMenu menu; Sublime::View* view = viewForWidget(widget(currentTab)); emit tabContextMenuRequested(view, &menu); menu.addSeparator(); QAction* closeTabAction = nullptr; QAction* closeOtherTabsAction = nullptr; if (view) { closeTabAction = menu.addAction(QIcon::fromTheme("document-close"), i18n("Close File")); closeOtherTabsAction = menu.addAction(QIcon::fromTheme("document-close"), i18n("Close Other Files")); } QAction* closeAllTabsAction = menu.addAction( QIcon::fromTheme("document-close"), i18n( "Close All Files" ) ); QAction* triggered = menu.exec(senderWidget->mapToGlobal(pos)); if (triggered) { if ( triggered == closeTabAction ) { requestClose(currentTab); } else if ( triggered == closeOtherTabsAction ) { // activate the remaining tab widgetActivated(currentTab); // first get the widgets to be closed since otherwise the indices will be wrong QList otherTabs; for ( int i = 0; i < count(); ++i ) { if ( i != currentTab ) { otherTabs << widget(i); } } // finally close other tabs foreach( QWidget* tab, otherTabs ) { requestClose(tab); } } else if ( triggered == closeAllTabsAction ) { // activate last tab widgetActivated(count() - 1); // close all QList tabs; for ( int i = 0; i < count(); ++i ) { requestClose(widget(i)); } } // else the action was handled by someone else } } void Container::showTooltipForTab(int tab) { emit tabToolTipRequested(viewForWidget(widget(tab)), this, tab); } bool Container::isCurrentTab(int tab) const { return d->tabBar->currentIndex() == tab; } QRect Container::tabRect(int tab) const { return d->tabBar->tabRect(tab).translated(d->tabBar->mapToGlobal(QPoint(0, 0))); } void Container::doubleClickTriggered(int tab) { if (tab == -1) { emit newTabRequested(); } else { emit tabDoubleClicked(viewForWidget(widget(tab))); } } void Container::documentListActionTriggered(QAction* action) { Sublime::View* view = action->data().value< Sublime::View* >(); Q_ASSERT(view); QWidget* widget = d->viewForWidget.key(view); Q_ASSERT(widget); setCurrentWidget(widget); } } #include "container.moc" diff --git a/sublime/idealcontroller.cpp b/sublime/idealcontroller.cpp index d87487100a..9ddb18ba4b 100644 --- a/sublime/idealcontroller.cpp +++ b/sublime/idealcontroller.cpp @@ -1,516 +1,511 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealcontroller.h" -#include -#include #include -#include #include +#include +#include #include #include -#include -#include #include "area.h" #include "view.h" #include "document.h" #include "mainwindow.h" #include "ideallayout.h" #include "idealtoolbutton.h" #include "idealdockwidget.h" #include "idealbuttonbarwidget.h" -#include -#include using namespace Sublime; IdealController::IdealController(Sublime::MainWindow* mainWindow): QObject(mainWindow), m_mainWindow(mainWindow) { leftBarWidget = new IdealButtonBarWidget(Qt::LeftDockWidgetArea, this, m_mainWindow); connect(leftBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); rightBarWidget = new IdealButtonBarWidget(Qt::RightDockWidgetArea, this, m_mainWindow); connect(rightBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); bottomBarWidget = new IdealButtonBarWidget(Qt::BottomDockWidgetArea, this, m_mainWindow); bottomStatusBarLocation = bottomBarWidget->corner(); connect(bottomBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); topBarWidget = new IdealButtonBarWidget(Qt::TopDockWidgetArea, this, m_mainWindow); connect(topBarWidget, &IdealButtonBarWidget::customContextMenuRequested, this, &IdealController::slotDockBarContextMenuRequested); m_docks = qobject_cast(mainWindow->action("docks_submenu")); m_showLeftDock = qobject_cast(m_mainWindow->action("show_left_dock")); m_showRightDock = qobject_cast(m_mainWindow->action("show_right_dock")); m_showBottomDock = qobject_cast(m_mainWindow->action("show_bottom_dock")); m_showTopDock = qobject_cast(m_mainWindow->action("show_top_dock")); connect(m_mainWindow, &MainWindow::settingsLoaded, this, &IdealController::loadSettings); } void IdealController::addView(Qt::DockWidgetArea area, View* view) { IdealDockWidget *dock = new IdealDockWidget(this, m_mainWindow); // dock object name is used to store toolview settings QString dockObjectName = view->document()->title(); // support different configuration for same docks opened in different areas if (m_mainWindow->area()) dockObjectName += '_' + m_mainWindow->area()->objectName(); dock->setObjectName(dockObjectName); KAcceleratorManager::setNoAccel(dock); QWidget *w = view->widget(dock); if (w->parent() == 0) { /* Could happen when we're moving the widget from one IdealDockWidget to another. See moveView below. In this case, we need to reparent the widget. */ w->setParent(dock); } QList toolBarActions = view->toolBarActions(); if (toolBarActions.isEmpty()) { dock->setWidget(w); } else { QMainWindow *toolView = new QMainWindow(); QToolBar *toolBar = new QToolBar(toolView); int iconSize = m_mainWindow->style()->pixelMetric(QStyle::PM_SmallIconSize); toolBar->setIconSize(QSize(iconSize, iconSize)); toolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); toolBar->setWindowTitle(i18n("%1 Tool Bar", w->windowTitle())); toolBar->setFloatable(false); toolBar->setMovable(false); toolBar->addActions(toolBarActions); toolView->setCentralWidget(w); toolView->addToolBar(toolBar); dock->setWidget(toolView); } dock->setWindowTitle(view->widget()->windowTitle()); dock->setWindowIcon(view->widget()->windowIcon()); dock->setFocusProxy(dock->widget()); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget( view->document()->title(), dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; m_docks->addAction(action); connect(dock, &IdealDockWidget::closeRequested, action, &QAction::toggle); } connect(dock, &IdealDockWidget::dockLocationChanged, this, &IdealController::dockLocationChanged); dock->hide(); docks.insert(dock); } void IdealController::dockLocationChanged(Qt::DockWidgetArea area) { IdealDockWidget *dock = qobject_cast(sender()); View *view = dock->view(); QAction* action = m_view_to_action.value(view); if (dock->dockWidgetArea() == area) { // this event can happen even when dock changes its location within the same area // usecases: // 1) user drags to the same area // 2) user rearranges toolviews inside the same area // 3) state restoration shows the dock widget // in 3rd case we need to show dock if we don't want it to be shown // TODO: adymo: invent a better solution for the restoration problem if (!action->isChecked() && dock->isVisible()) { dock->hide(); } - return; + return; } if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); docks.insert(dock); if (IdealButtonBarWidget* bar = barForDockArea(area)) { QAction* action = bar->addWidget( view->document()->title(), dock, static_cast(parent())->area(), view); m_dockwidget_to_action[dock] = m_view_to_action[view] = action; // at this point the dockwidget is visible (user dragged it) // properly set up UI state bar->showWidget(action, true); // the dock should now be the "last" opened in a new area, not in the old area for (auto it = lastDockWidget.begin(); it != lastDockWidget.end(); ++it) { if (it->data() == dock) it->clear(); } lastDockWidget[area] = dock; // after drag, the toolview loses focus, so focus it again dock->setFocus(Qt::ShortcutFocusReason); m_docks->addAction(action); } if (area == Qt::BottomDockWidgetArea || area == Qt::TopDockWidgetArea) dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | IdealDockWidget::DockWidgetVerticalTitleBar | QDockWidget::DockWidgetMovable); else dock->setFeatures( QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetMovable ); } IdealButtonBarWidget* IdealController::barForDockArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: return leftBarWidget; case Qt::TopDockWidgetArea: return topBarWidget; case Qt::RightDockWidgetArea: return rightBarWidget; case Qt::BottomDockWidgetArea: return bottomBarWidget; default: Q_ASSERT(false); return 0; } } void IdealController::slotDockBarContextMenuRequested(QPoint position) { IdealButtonBarWidget* bar = qobject_cast(sender()); Q_ASSERT(bar); emit dockBarContextMenuRequested(bar->area(), bar->mapToGlobal(position)); } void IdealController::raiseView(View* view, RaiseMode mode) { /// @todo GroupWithOtherViews is disabled for now by forcing "mode = HideOtherViews". /// for the release of KDevelop 4.3. /// Reason: Inherent bugs which need significant changes to be fixed. /// Example: Open two equal toolviews (for example 2x konsole), /// activate one, switch area, switch back, -> Both are active instead of one. /// The problem is that views are identified purely by their factory-id, which is equal /// for toolviews of the same type. mode = HideOtherViews; - + QAction* action = m_view_to_action.value(view); Q_ASSERT(action); QWidget *focusWidget = m_mainWindow->focusWidget(); action->setProperty("raise", mode); action->setChecked(true); // TODO: adymo: hack: focus needs to stay inside the previously // focused widget (setChecked will focus the toolview) if (focusWidget) focusWidget->setFocus(Qt::ShortcutFocusReason); } QList< IdealDockWidget* > IdealController::allDockWidgets() { return docks.toList(); } void IdealController::showDockWidget(IdealDockWidget* dock, bool show) { Q_ASSERT(docks.contains(dock)); Qt::DockWidgetArea area = dock->dockWidgetArea(); if (show) { m_mainWindow->addDockWidget(area, dock); dock->show(); } else { m_mainWindow->removeDockWidget(dock); } setShowDockStatus(area, show); emit dockShown(dock->view(), Sublime::dockAreaToPosition(area), show); if (!show) // Put the focus back on the editor if a dock was hidden focusEditor(); else { // focus the dock dock->setFocus(Qt::ShortcutFocusReason); } } void IdealController::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } QWidget* IdealController::statusBarLocation() const { return bottomStatusBarLocation; } QAction* IdealController::actionForView(View* view) const { return m_view_to_action.value(view); } void IdealController::setShowDockStatus(Qt::DockWidgetArea area, bool checked) { QAction* action = actionForArea(area); if (action->isChecked() != checked) { bool blocked = action->blockSignals(true); action->setChecked(checked); action->blockSignals(blocked); } } QAction* IdealController::actionForArea(Qt::DockWidgetArea area) const { switch (area) { case Qt::LeftDockWidgetArea: default: return m_showLeftDock; case Qt::RightDockWidgetArea: return m_showRightDock; case Qt::TopDockWidgetArea: return m_showTopDock; case Qt::BottomDockWidgetArea: return m_showBottomDock; } } void IdealController::removeView(View* view, bool nondestructive) { Q_ASSERT(m_view_to_action.contains(view)); QAction* action = m_view_to_action.value(view); QWidget *viewParent = view->widget()->parentWidget(); IdealDockWidget *dock = qobject_cast(viewParent); if (!dock) { // toolviews with a toolbar live in a QMainWindow which lives in a Dock Q_ASSERT(qobject_cast(viewParent)); viewParent = viewParent->parentWidget(); dock = qobject_cast(viewParent); } Q_ASSERT(dock); /* Hide the view, first. This is a workaround -- if we try to remove IdealDockWidget without this, then eventually a call to IdealMainLayout::takeAt will be made, which method asserts immediately. */ action->setChecked(false); if (IdealButtonBarWidget* bar = barForDockArea(dock->dockWidgetArea())) bar->removeAction(action); m_view_to_action.remove(view); m_dockwidget_to_action.remove(dock); if (nondestructive) view->widget()->setParent(0); delete dock; } void IdealController::moveView(View *view, Qt::DockWidgetArea area) { removeView(view); addView(area, view); } void IdealController::showBottomDock(bool show) { showDock(Qt::BottomDockWidgetArea, show); } void IdealController::showLeftDock(bool show) { showDock(Qt::LeftDockWidgetArea, show); } void IdealController::showRightDock(bool show) { showDock(Qt::RightDockWidgetArea, show); } void IdealController::showDock(Qt::DockWidgetArea area, bool show) { IdealButtonBarWidget *bar = barForDockArea(area); if (!bar) return; IdealDockWidget *lastDock = lastDockWidget[area].data(); if (lastDock && lastDock->isVisible() && !lastDock->hasFocus()) { lastDock->setFocus(Qt::ShortcutFocusReason); // re-sync action state given we may have asked for the dock to be hidden QAction* action = actionForArea(area); if (!action->isChecked()) { action->blockSignals(true); action->setChecked(true); action->blockSignals(false); } return; } if (!show) { // close all toolviews foreach (QAction *action, bar->actions()) { if (action->isChecked()) action->setChecked(false); } focusEditor(); } else { // open the last opened toolview (or the first one) and focus it if (lastDock) { if (QAction *action = m_dockwidget_to_action.value(lastDock)) action->setChecked(show); lastDock->setFocus(Qt::ShortcutFocusReason); return; } if (barForDockArea(area)->actions().count()) barForDockArea(area)->actions().first()->setChecked(show); } } // returns currently focused dock widget (if any) IdealDockWidget* IdealController::currentDockWidget() { QWidget *w = m_mainWindow->focusWidget(); while (true) { if (!w) break; IdealDockWidget *dockCandidate = qobject_cast(w); if (dockCandidate) return dockCandidate; w = w->parentWidget(); } return 0; } void IdealController::goPrevNextDock(IdealController::Direction direction) { IdealDockWidget *currentDock = currentDockWidget(); if (!currentDock) return; IdealButtonBarWidget *bar = barForDockArea(currentDock->dockWidgetArea()); int index = bar->actions().indexOf(m_dockwidget_to_action.value(currentDock)); if (direction == NextDock) { if (index < 1) index = bar->actions().count() - 1; else --index; } else { if (index == -1 || index == bar->actions().count() - 1) index = 0; else ++index; } if (index < bar->actions().count()) { QAction* action = bar->actions().at(index); action->setChecked(true); } } void IdealController::toggleDocksShown() { QList allActions; allActions += leftBarWidget->actions(); allActions += bottomBarWidget->actions(); allActions += rightBarWidget->actions(); bool show = true; foreach (QAction *action, allActions) { if (action->isChecked()) { show = false; break; } } toggleDocksShown(leftBarWidget, show); toggleDocksShown(bottomBarWidget, show); toggleDocksShown(rightBarWidget, show); } void IdealController::toggleDocksShown(IdealButtonBarWidget* bar, bool show) { if (!show) { foreach (QAction *action, bar->actions()) { if (action->isChecked()) action->setChecked(false); } focusEditor(); } else { IdealDockWidget *lastDock = lastDockWidget[bar->area()].data(); if (lastDock) m_dockwidget_to_action[lastDock]->setChecked(true); } } void IdealController::loadSettings() { KConfigGroup cg(KSharedConfig::openConfig(), "UiSettings"); int bottomOwnsBottomLeft = cg.readEntry("BottomLeftCornerOwner", 0); if (bottomOwnsBottomLeft) m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea); int bottomOwnsBottomRight = cg.readEntry("BottomRightCornerOwner", 0); if (bottomOwnsBottomRight) m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::BottomDockWidgetArea); else m_mainWindow->setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea); } void IdealController::setWidthForArea(Qt::DockWidgetArea area, int width) { m_widthsForAreas[area] = width; } void IdealController::emitWidgetResized(Qt::DockWidgetArea dockArea, int thickness) { emit widgetResized(dockArea, thickness); } diff --git a/sublime/idealdockwidget.cpp b/sublime/idealdockwidget.cpp index 1807e4b4ba..7ea81e3d0d 100644 --- a/sublime/idealdockwidget.cpp +++ b/sublime/idealdockwidget.cpp @@ -1,206 +1,205 @@ /* Copyright 2007 Roberto Raggi Copyright 2007 Hamish Rodda Copyright 2011 Alexander Dymo Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE KDEVELOP TEAM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "idealdockwidget.h" #include "mainwindow.h" #include "area.h" #include "document.h" #include "view.h" +#include #include -#include -#include #include +#include #include +#include +#include #include #include -#include -#include -#include using namespace Sublime; IdealDockWidget::IdealDockWidget(IdealController *controller, Sublime::MainWindow *parent) : QDockWidget(parent), m_area(0), m_view(0), m_docking_area(Qt::NoDockWidgetArea), m_controller(controller) { setAutoFillBackground(true); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &IdealDockWidget::customContextMenuRequested, this, &IdealDockWidget::contextMenuRequested); QAbstractButton *closeButton = findChild(QLatin1String("qt_dockwidget_closebutton")); if (closeButton) { disconnect(closeButton, &QAbstractButton::clicked, 0, 0); connect(closeButton, &QAbstractButton::clicked, this, &IdealDockWidget::closeRequested); } setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); // do not allow to move docks to the top dock area (no buttonbar there in our current UI) setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea); } IdealDockWidget::~IdealDockWidget() { } Area *IdealDockWidget::area() const { return m_area; } void IdealDockWidget::setArea(Area *area) { m_area = area; } View *IdealDockWidget::view() const { return m_view; } void IdealDockWidget::setView(View *view) { m_view = view; } Qt::DockWidgetArea IdealDockWidget::dockWidgetArea() const { return m_docking_area; } void IdealDockWidget::setDockWidgetArea(Qt::DockWidgetArea dockingArea) { m_docking_area = dockingArea; } void IdealDockWidget::slotRemove() { m_area->removeToolView(m_view); } void IdealDockWidget::contextMenuRequested(const QPoint &point) { QWidget* senderWidget = qobject_cast(sender()); Q_ASSERT(senderWidget); QMenu menu; menu.addSection(windowIcon(), windowTitle()); QList< QAction* > viewActions = m_view->contextMenuActions(); if(!viewActions.isEmpty()) { menu.addActions(viewActions); menu.addSeparator(); } ///TODO: can this be cleaned up? if(QToolBar* toolBar = widget()->findChild()) { menu.addAction(toolBar->toggleViewAction()); menu.addSeparator(); } /// start position menu QMenu* positionMenu = menu.addMenu(i18n("Toolview Position")); QActionGroup *g = new QActionGroup(this); QAction *left = new QAction(i18nc("toolview position", "Left"), g); QAction *bottom = new QAction(i18nc("toolview position", "Bottom"), g); QAction *right = new QAction(i18nc("toolview position", "Right"), g); QAction *detach = new QAction(i18nc("toolview position", "Detached"), g); for (auto action : {left, bottom, right, detach}) { positionMenu->addAction(action); action->setCheckable(true); } if (isFloating()) { detach->setChecked(true); } else if (m_docking_area == Qt::BottomDockWidgetArea) bottom->setChecked(true); else if (m_docking_area == Qt::LeftDockWidgetArea) left->setChecked(true); else if (m_docking_area == Qt::RightDockWidgetArea) right->setChecked(true); /// end position menu menu.addSeparator(); QAction *setShortcut = menu.addAction(QIcon::fromTheme("configure-shortcuts"), i18n("Assign Shortcut...")); setShortcut->setToolTip(i18n("Use this shortcut to trigger visibility of the toolview.")); menu.addSeparator(); QAction* remove = menu.addAction(QIcon::fromTheme("dialog-close"), i18n("Remove Toolview")); QAction* triggered = menu.exec(senderWidget->mapToGlobal(point)); if (triggered) { if ( triggered == remove ) { slotRemove(); return; } else if ( triggered == setShortcut ) { QDialog* dialog(new QDialog(this)); dialog->setWindowTitle(i18n("Assign Shortcut For '%1' Tool View", m_view->document()->title())); KShortcutWidget *w = new KShortcutWidget(dialog); w->setShortcut(m_controller->actionForView(m_view)->shortcuts()); QVBoxLayout* dialogLayout = new QVBoxLayout(dialog); dialogLayout->addWidget(w); QDialogButtonBox* buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel ); dialogLayout->addWidget(buttonBox); connect(buttonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); if (dialog->exec() == QDialog::Accepted) { m_controller->actionForView(m_view)->setShortcuts(w->shortcut()); //save shortcut config KConfigGroup config = KSharedConfig::openConfig()->group("UI"); QStringList shortcuts; shortcuts << w->shortcut().value(0).toString(); shortcuts << w->shortcut().value(1).toString(); config.writeEntry(QStringLiteral("Shortcut for %1").arg(m_view->document()->title()), shortcuts); config.sync(); } delete dialog; return; } else if ( triggered == detach ) { setFloating(true); m_area->raiseToolView(m_view); return; } if (isFloating()) { setFloating(false); } Sublime::Position pos; if (triggered == left) pos = Sublime::Left; else if (triggered == bottom) pos = Sublime::Bottom; else if (triggered == right) pos = Sublime::Right; else return; Area *area = m_area; View *view = m_view; /* This call will delete *this, so we no longer can access member variables. */ m_area->moveToolView(m_view, pos); area->raiseToolView(view); } } diff --git a/sublime/mainwindow.cpp b/sublime/mainwindow.cpp index 32ef797eec..3d398365fa 100644 --- a/sublime/mainwindow.cpp +++ b/sublime/mainwindow.cpp @@ -1,432 +1,431 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow.h" #include "mainwindow_p.h" #include #include -#include #include +#include -#include -#include #include -#include #include +#include +#include #include "area.h" #include "view.h" #include "controller.h" #include "container.h" #include "idealcontroller.h" #include "holdupdates.h" #include "sublimedebug.h" Q_LOGGING_CATEGORY(SUBLIME, "kdevplatform.sublime") namespace Sublime { MainWindow::MainWindow(Controller *controller, Qt::WindowFlags flags) : KParts::MainWindow(0, flags), d(new MainWindowPrivate(this, controller)) { connect(this, &MainWindow::destroyed, controller, static_cast(&Controller::areaReleased)); loadGeometry(KSharedConfig::openConfig()->group("Main Window")); // don't allow AllowTabbedDocks - that doesn't make sense for "ideal" UI setDockOptions(QMainWindow::AnimatedDocks); } bool MainWindow::containsView(View* view) const { foreach(Area* area, areas()) if(area->views().contains(view)) return true; return false; } QList< Area* > MainWindow::areas() const { QList< Area* > areas = controller()->areas(const_cast(this)); if(areas.isEmpty()) areas = controller()->defaultAreas(); return areas; } MainWindow::~MainWindow() { qCDebug(SUBLIME) << "destroying mainwindow"; delete d; } void MainWindow::reconstructViews(QList topViews) { d->reconstructViews(topViews); } QList MainWindow::getTopViews() const { QList topViews; foreach(View* view, d->area->views()) { if(view->hasWidget()) { QWidget* widget = view->widget(); if(widget->parent() && widget->parent()->parent()) { Container* container = qobject_cast(widget->parent()->parent()); if(container->currentWidget() == widget) topViews << view; } } } return topViews; } void MainWindow::setArea(Area *area) { if (d->area) disconnect(d->area, 0, d, 0); bool differentArea = (area != d->area); /* All views will be removed from dock area now. However, this does not mean those are removed from area, so prevent slotDockShown from recording those views as no longer shown in the area. */ d->ignoreDockShown = true; if (d->autoAreaSettingsSave && differentArea) saveSettings(); HoldUpdates hu(this); if (d->area) clearArea(); d->area = area; d->reconstruct(); if(d->area->activeView()) activateView(d->area->activeView()); else d->activateFirstVisibleView(); initializeStatusBar(); emit areaChanged(area); d->ignoreDockShown = false; hu.stop(); loadSettings(); connect(area, &Area::viewAdded, d, &MainWindowPrivate::viewAdded); connect(area, &Area::viewRemoved, d, &MainWindowPrivate::viewRemovedInternal); connect(area, &Area::requestToolViewRaise, d, &MainWindowPrivate::raiseToolView); connect(area, &Area::aboutToRemoveView, d, &MainWindowPrivate::aboutToRemoveView); connect(area, &Area::toolViewAdded, d, &MainWindowPrivate::toolViewAdded); connect(area, &Area::aboutToRemoveToolView, d, &MainWindowPrivate::aboutToRemoveToolView); connect(area, &Area::toolViewMoved, d, &MainWindowPrivate::toolViewMoved); } void MainWindow::initializeStatusBar() { //nothing here, reimplement in the subclasses if you want to have status bar //inside the bottom toolview buttons row } void MainWindow::resizeEvent(QResizeEvent* event) { return KParts::MainWindow::resizeEvent(event); } void MainWindow::clearArea() { emit areaCleared(d->area); d->clearArea(); } QList MainWindow::toolDocks() const { return d->docks; } Area *Sublime::MainWindow::area() const { return d->area; } Controller *MainWindow::controller() const { return d->controller; } View *MainWindow::activeView() { return d->activeView; } View *MainWindow::activeToolView() { return d->activeToolView; } void MainWindow::activateView(Sublime::View* view, bool focus) { if (!d->viewContainers.contains(view)) return; d->viewContainers[view]->setCurrentWidget(view->widget()); setActiveView(view, focus); d->area->setActiveView(view); } void MainWindow::setActiveView(View *view, bool focus) { View* oldActiveView = d->activeView; d->activeView = view; if (focus && view && !view->widget()->hasFocus()) view->widget()->setFocus(); if(d->activeView != oldActiveView) emit activeViewChanged(view); } void Sublime::MainWindow::setActiveToolView(View *view) { d->activeToolView = view; emit activeToolViewChanged(view); } void MainWindow::saveSettings() { QString group = "MainWindow"; if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); /* This will try to save window size, too. But it's OK, since we won't use this information when loading. */ saveMainWindowSettings(cg); //debugToolBar visibility is stored separately to allow a area dependent default value foreach (KToolBar* toolbar, toolBars()) { if (toolbar->objectName() == "debugToolBar") { cg.writeEntry("debugToolBarVisibility", toolbar->isVisibleTo(this)); } } cg.sync(); } void MainWindow::loadSettings() { HoldUpdates hu(this); qCDebug(SUBLIME) << "loading settings for " << (area() ? area()->objectName() : ""); QString group = "MainWindow"; if (area()) group += '_' + area()->objectName(); KConfigGroup cg = KSharedConfig::openConfig()->group(group); // What follows is copy-paste from applyMainWindowSettings. Unfortunately, // we don't really want that one to try restoring window size, and we also // cannot stop it from doing that in any clean way. QStatusBar* sb = findChild(); if (sb) { QString entry = cg.readEntry("StatusBar", "Enabled"); if ( entry == "Disabled" ) sb->hide(); else sb->show(); } QMenuBar* mb = findChild(); if (mb) { QString entry = cg.readEntry ("MenuBar", "Enabled"); if ( entry == "Disabled" ) mb->hide(); else mb->show(); } if ( !autoSaveSettings() || cg.name() == autoSaveGroup() ) { QString entry = cg.readEntry ("ToolBarsMovable", "Enabled"); if ( entry == "Disabled" ) KToolBar::setToolBarsLocked(true); else KToolBar::setToolBarsLocked(false); } // Utilise the QMainWindow::restoreState() functionality // Note that we're fixing KMainWindow bug here -- the original // code has this fragment above restoring toolbar properties. // As result, each save/restore would move the toolbar a bit to // the left. if (cg.hasKey("State")) { QByteArray state; state = cg.readEntry("State", state); state = QByteArray::fromBase64(state); // One day will need to load the version number, but for now, assume 0 // restoreState(state); } else { // If there's no state we use a default size of 870x650 // Resize only when showing "code" area. If we do that for other areas, // then we'll hit bug https://bugs.kde.org/show_bug.cgi?id=207990 // TODO: adymo: this is more like a hack, we need a proper first-start initialization if (area() && area()->objectName() == "code") resize(870,650); } int n = 1; // Toolbar counter. toolbars are counted from 1, foreach (KToolBar* toolbar, toolBars()) { QString group("Toolbar"); // Give a number to the toolbar, but prefer a name if there is one, // because there's no real guarantee on the ordering of toolbars group += (toolbar->objectName().isEmpty() ? QString::number(n) : QStringLiteral(" ")+toolbar->objectName()); KConfigGroup toolbarGroup(&cg, group); toolbar->applySettings(toolbarGroup); if (toolbar->objectName() == "debugToolBar") { //debugToolBar visibility is stored separately to allow a area dependent default value bool visibility = cg.readEntry("debugToolBarVisibility", area()->objectName() == "debug"); toolbar->setVisible(visibility); } n++; } KConfigGroup uiGroup = KSharedConfig::openConfig()->group("UiSettings"); foreach (Container *container, findChildren()) { container->setTabBarHidden(uiGroup.readEntry("TabBarVisibility", 1) == 0); } cg.sync(); hu.stop(); emit settingsLoaded(); } bool MainWindow::queryClose() { // saveSettings(); KConfigGroup config(KSharedConfig::openConfig(), "Main Window"); saveGeometry(config); config.sync(); return KParts::MainWindow::queryClose(); } void MainWindow::saveGeometry(KConfigGroup &config) { int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); QString key = QString::fromLatin1("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); config.writeEntry(key, geometry()); } void MainWindow::loadGeometry(const KConfigGroup &config) { // The below code, essentially, is copy-paste from // KMainWindow::restoreWindowSize. Right now, that code is buggy, // as per http://permalink.gmane.org/gmane.comp.kde.devel.core/52423 // so we implement a less theoretically correct, but working, version // below const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->screenGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) desk = QApplication::desktop()->screenGeometry(QApplication::desktop()->screen()); QString key = QString::fromLatin1("Desktop %1 %2") .arg(desk.width()).arg(desk.height()); QRect g = config.readEntry(key, QRect()); if (!g.isEmpty()) setGeometry(g); } void MainWindow::enableAreaSettingsSave() { d->autoAreaSettingsSave = true; } QWidget *MainWindow::statusBarLocation() { return d->idealController->statusBarLocation(); } void MainWindow::setTabBarLeftCornerWidget(QWidget* widget) { d->setTabBarLeftCornerWidget(widget); } void MainWindow::tabDoubleClicked(View* view) { Q_UNUSED(view); d->toggleDocksShown(); } void MainWindow::tabContextMenuRequested(View* , QMenu* ) { // do nothing } void MainWindow::tabToolTipRequested(View*, Container*, int) { // do nothing } void MainWindow::newTabRequested() { } void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea , const QPoint& ) { // do nothing } View* MainWindow::viewForPosition(QPoint globalPos) const { foreach(Container* container, d->viewContainers.values()) { QRect globalGeom = QRect(container->mapToGlobal(QPoint(0,0)), container->mapToGlobal(QPoint(container->width(), container->height()))); if(globalGeom.contains(globalPos)) { return d->widgetToView[container->currentWidget()]; } } return 0; } void MainWindow::setBackgroundCentralWidget(QWidget* w) { d->setBackgroundCentralWidget(w); } } #include "moc_mainwindow.cpp" diff --git a/sublime/mainwindow.h b/sublime/mainwindow.h index f2f109cf94..2fad013a88 100644 --- a/sublime/mainwindow.h +++ b/sublime/mainwindow.h @@ -1,185 +1,187 @@ /*************************************************************************** * Copyright 2006-2007 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef KDEVPLATFORM_SUBLIMEMAINWINDOW_H #define KDEVPLATFORM_SUBLIMEMAINWINDOW_H -#include -#include +#include +#include + +#include #include "sublimeexport.h" class QDockWidget; namespace Sublime { class Container; class Area; class View; class Controller; class MainWindowOperator; /** @short Sublime Main Window The area-enabled mainwindow to show Sublime views and toolviews. To use, a controller and constructed areas are necessary: @code MainWindow w(controller); controller->showArea(area, &w); @endcode */ class KDEVPLATFORMSUBLIME_EXPORT MainWindow: public KParts::MainWindow { Q_OBJECT public: /**Creates a mainwindow and adds it to the controller.*/ explicit MainWindow(Controller *controller, Qt::WindowFlags flags = KDE_DEFAULT_WINDOWFLAGS); ~MainWindow(); /**@return the list of dockwidgets that contain area's toolviews.*/ QList toolDocks() const; /**@return area which mainwindow currently shows or 0 if no area has been set.*/ Area *area() const; /**@return controller for this mainwindow.*/ Controller *controller() const; /**@return active view inside this mainwindow.*/ View *activeView(); /**@return active toolview inside this mainwindow.*/ View *activeToolView(); - /**Enable saving of per-area UI settings (like toolbar properties + /**Enable saving of per-area UI settings (like toolbar properties and position) whenever area is changed. This should be called after all areas are restored, and main window area is set, to prevent saving a half-broken state. */ void enableAreaSettingsSave(); /** Allows setting an additional widget that will be inserted left to the document tab-bar. * The ownership goes to the target. */ void setTabBarLeftCornerWidget(QWidget* widget); - + /**Sets the area of main window and fills it with views. *The contents is reconstructed, even if the area equals the currently set area. */ void setArea(Area *area); - + /** * Reconstruct the view structure. This is required after significant untracked changes to the * area-index structure. * Views listed in topViews will be on top of their view stacks. * */ void reconstructViews(QList topViews = QList()); - + /**Returns a list of all views which are on top of their corresponding view stacks*/ QList getTopViews() const; - + /**Returns the view that is closest to the given global position, or zero.*/ View* viewForPosition(QPoint globalPos) const; - + /**Returns true if this main-window contains this view*/ bool containsView(View* view) const; - + /**Returns all areas that belong to this main-window*/ QList areas() const; - + /** Sets a @p w widget that will be shown when there are no opened documents. * This method takes the ownership of @p w. */ void setBackgroundCentralWidget(QWidget* w); - + public Q_SLOTS: /**Shows the @p view and makes it active, focusing it by default).*/ void activateView(Sublime::View *view, bool focus = true); /**Loads size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to load more and don't forget to call inherited method.*/ virtual void loadSettings(); Q_SIGNALS: /**Emitted before the area is cleared from this mainwindow.*/ void areaCleared(Sublime::Area*); /**Emitted after the new area has been shown in this mainwindow.*/ void areaChanged(Sublime::Area*); /**Emitted when the active view is changed.*/ void activeViewChanged(Sublime::View*); /**Emitted when the active toolview is changed.*/ void activeToolViewChanged(Sublime::View*); /**Emitted when the user interface settings have changed.*/ void settingsLoaded(); - + /**Emitted when a new view is added to the mainwindow.*/ void viewAdded(Sublime::View*); /**Emitted when a view is going to be removed from the mainwindow.*/ void aboutToRemoveView(Sublime::View*); protected: QWidget *statusBarLocation(); virtual void initializeStatusBar(); protected Q_SLOTS: virtual void tabDoubleClicked(Sublime::View* view); virtual void tabContextMenuRequested(Sublime::View*, QMenu*); virtual void tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab); virtual void newTabRequested(); /**Called whenever the user requests a context menu on a dockwidget bar. You can then e.g. add actions to add dockwidgets. Default implementation does nothing.**/ virtual void dockBarContextMenuRequested(Qt::DockWidgetArea, const QPoint&); public: // FIXME? /**Saves size/toolbar/menu/statusbar settings to the global configuration file. Reimplement in subclasses to save more and don't forget to call inherited method.*/ virtual void saveSettings(); /**Reimplemented to save settings.*/ virtual bool queryClose() override; /** Allow connecting to activateView without the need for a lambda for the default parameter */ void activateViewAndFocus(Sublime::View *view) { activateView(view, true); } private: - + Q_PRIVATE_SLOT(d, void viewAdded(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void viewRemovedInternal(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void aboutToRemoveView(Sublime::AreaIndex*, Sublime::View*)) Q_PRIVATE_SLOT(d, void toolViewAdded(Sublime::View*, Sublime::Position)) Q_PRIVATE_SLOT(d, void raiseToolView(Sublime::View*)) Q_PRIVATE_SLOT(d, void aboutToRemoveToolView(Sublime::View*, Sublime::Position)) Q_PRIVATE_SLOT(d, void toolViewMoved(Sublime::View*, Sublime::Position)) //Inherit MainWindowOperator to access four methods below /**Unsets the area clearing main window.*/ void clearArea(); /**Sets the active view.*/ void setActiveView(Sublime::View* view, bool focus = true); /**Sets the active toolview and focuses it.*/ void setActiveToolView(View *view); void resizeEvent(QResizeEvent* event) override; void saveGeometry(KConfigGroup &config); void loadGeometry(const KConfigGroup &config); class MainWindowPrivate *const d; friend class MainWindowOperator; friend class MainWindowPrivate; }; } #endif diff --git a/sublime/mainwindow_p.cpp b/sublime/mainwindow_p.cpp index 981811b101..30916618e3 100644 --- a/sublime/mainwindow_p.cpp +++ b/sublime/mainwindow_p.cpp @@ -1,747 +1,737 @@ /*************************************************************************** * Copyright 2006-2009 Alexander Dymo * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "mainwindow_p.h" -#include #include #include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include #include -#include -#include -#include #include "area.h" #include "view.h" #include "areaindex.h" #include "document.h" #include "container.h" #include "controller.h" #include "mainwindow.h" #include "idealcontroller.h" #include "holdupdates.h" #include "idealbuttonbarwidget.h" #include "sublimedebug.h" -#include -#include -#include class IdealToolBar : public QToolBar { Q_OBJECT public: explicit IdealToolBar(const QString& title, Sublime::IdealButtonBarWidget* buttons, QMainWindow* parent) : QToolBar(title, parent), m_buttons(buttons) { setMovable(false); setFloatable(false); setObjectName(title); layout()->setMargin(0); addWidget(m_buttons); } void hideWhenEmpty() { refresh(); QTimer* t = new QTimer(this); t->setInterval(100); t->setSingleShot(true); connect(t, &QTimer::timeout, this, &IdealToolBar::refresh); connect(this, &IdealToolBar::visibilityChanged, t, static_cast(&QTimer::start)); connect(m_buttons, &Sublime::IdealButtonBarWidget::emptyChanged, t, static_cast(&QTimer::start)); } public slots: void refresh() { if(m_buttons->isEmpty()==isVisible()) { setVisible(!m_buttons->isEmpty()); } } private: Sublime::IdealButtonBarWidget* m_buttons; }; namespace Sublime { MainWindowPrivate::MainWindowPrivate(MainWindow *w, Controller* controller) :controller(controller), area(0), activeView(0), activeToolView(0), bgCentralWidget(0), ignoreDockShown(false), autoAreaSettingsSave(false), m_mainWindow(w) { KActionCollection *ac = m_mainWindow->actionCollection(); QAction* action = new QAction(i18n("Show Left Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_L); connect(action, &QAction::toggled, this, &MainWindowPrivate::showLeftDock); ac->addAction("show_left_dock", action); action = new QAction(i18n("Show Right Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_R); connect(action, &QAction::toggled, this, &MainWindowPrivate::showRightDock); ac->addAction("show_right_dock", action); action = new QAction(i18n("Show Bottom Dock"), this); action->setCheckable(true); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_B); connect(action, &QAction::toggled, this, &MainWindowPrivate::showBottomDock); ac->addAction("show_bottom_dock", action); action = new QAction(i18nc("@action", "Focus Editor"), this); ac->setDefaultShortcuts(action, QList() << (Qt::META | Qt::CTRL | Qt::Key_E) << Qt::META + Qt::Key_C); connect(action, &QAction::triggered, this, &MainWindowPrivate::focusEditor); ac->addAction("focus_editor", action); action = new QAction(i18n("Hide/Restore Docks"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_H); connect(action, &QAction::triggered, this, &MainWindowPrivate::toggleDocksShown); ac->addAction("hide_all_docks", action); action = new QAction(i18n("Next Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_N); action->setIcon(QIcon::fromTheme("go-next")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectNextDock); ac->addAction("select_next_dock", action); action = new QAction(i18n("Previous Tool View"), this); ac->setDefaultShortcut(action, Qt::META | Qt::CTRL | Qt::Key_P); action->setIcon(QIcon::fromTheme("go-previous")); connect(action, &QAction::triggered, this, &MainWindowPrivate::selectPreviousDock); ac->addAction("select_previous_dock", action); action = new KActionMenu(i18n("Tool Views"), this); ac->addAction("docks_submenu", action); idealController = new IdealController(m_mainWindow); IdealToolBar* leftToolBar = new IdealToolBar(i18n("Left Button Bar"), idealController->leftBarWidget, m_mainWindow); leftToolBar->hideWhenEmpty(); m_mainWindow->addToolBar(Qt::LeftToolBarArea, leftToolBar); IdealToolBar* rightToolBar = new IdealToolBar(i18n("Right Button Bar"), idealController->rightBarWidget, m_mainWindow); rightToolBar->hideWhenEmpty(); m_mainWindow->addToolBar(Qt::RightToolBarArea, rightToolBar); IdealToolBar* bottomToolBar = new IdealToolBar(i18n("Bottom Button Bar"), idealController->bottomBarWidget, m_mainWindow); m_mainWindow->addToolBar(Qt::BottomToolBarArea, bottomToolBar); // adymo: intentionally do not add a toolbar for top buttonbar // this doesn't work well with toolbars added via xmlgui centralWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(centralWidget); centralWidget->setLayout(layout); layout->setMargin(0); splitterCentralWidget = new QSplitter(centralWidget); layout->addWidget(splitterCentralWidget); m_mainWindow->setCentralWidget(centralWidget); connect(idealController, &IdealController::dockShown, this, &MainWindowPrivate::slotDockShown); connect(idealController, &IdealController::widgetResized, this, &MainWindowPrivate::widgetResized); connect(idealController, &IdealController::dockBarContextMenuRequested, m_mainWindow, &MainWindow::dockBarContextMenuRequested); } MainWindowPrivate::~MainWindowPrivate() { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } void MainWindowPrivate::showLeftDock(bool b) { idealController->showLeftDock(b); } void MainWindowPrivate::showBottomDock(bool b) { idealController->showBottomDock(b); } void MainWindowPrivate::showRightDock(bool b) { idealController->showRightDock(b); } void MainWindowPrivate::setBackgroundCentralWidget(QWidget* w) { delete bgCentralWidget; QLayout* l=m_mainWindow->centralWidget()->layout(); l->addWidget(w); bgCentralWidget=w; setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::setBackgroundVisible(bool v) { if(!bgCentralWidget) return; bgCentralWidget->setVisible(v); splitterCentralWidget->setVisible(!v); } void MainWindowPrivate::focusEditor() { if (View* view = m_mainWindow->activeView()) if (view->hasWidget()) view->widget()->setFocus(Qt::ShortcutFocusReason); } void MainWindowPrivate::toggleDocksShown() { idealController->toggleDocksShown(); } void MainWindowPrivate::selectNextDock() { idealController->goPrevNextDock(IdealController::NextDock); } void MainWindowPrivate::selectPreviousDock() { idealController->goPrevNextDock(IdealController::PrevDock); } Area::WalkerMode MainWindowPrivate::IdealToolViewCreator::operator() (View *view, Sublime::Position position) { if (!d->docks.contains(view)) { d->docks << view; //add view d->idealController->addView(d->positionToDockArea(position), view); } return Area::ContinueWalker; } Area::WalkerMode MainWindowPrivate::ViewCreator::operator() (AreaIndex *index) { QSplitter *splitter = d->m_indexSplitters.value(index); if (!splitter) { //no splitter - we shall create it and populate with views if (!index->parent()) { qCDebug(SUBLIME) << "reconstructing root area"; //this is root area splitter = d->splitterCentralWidget; d->m_indexSplitters[index] = splitter; d->centralWidget->layout()->addWidget(splitter); } else { if (!d->m_indexSplitters.value(index->parent())) { // can happen in working set code, as that adds a view to a child index first // hence, recursively reconstruct the parent indizes first operator()(index->parent()); } QSplitter *parent = d->m_indexSplitters.value(index->parent()); splitter = new QSplitter(parent); d->m_indexSplitters[index] = splitter; if(index == index->parent()->first()) parent->insertWidget(0, splitter); else parent->addWidget(splitter); } Q_ASSERT(splitter); } if (index->isSplit()) //this is a visible splitter splitter->setOrientation(index->orientation()); else { Container *container = 0; while(splitter->count() && qobject_cast(splitter->widget(0))) { // After unsplitting, we might have to remove old splitters QWidget* widget = splitter->widget(0); qCDebug(SUBLIME) << "deleting" << widget; widget->setParent(0); delete widget; } if (!splitter->widget(0)) { //we need to create view container container = new Container(splitter); connect(container, &Container::activateView, d->m_mainWindow, &MainWindow::activateViewAndFocus); connect(container, &Container::tabDoubleClicked, d->m_mainWindow, &MainWindow::tabDoubleClicked); connect(container, &Container::tabContextMenuRequested, d->m_mainWindow, &MainWindow::tabContextMenuRequested); connect(container, &Container::tabToolTipRequested, d->m_mainWindow, &MainWindow::tabToolTipRequested); connect(container, static_cast(&Container::requestClose), d, &MainWindowPrivate::widgetCloseRequest, Qt::QueuedConnection); connect(container, &Container::newTabRequested, d->m_mainWindow, &MainWindow::newTabRequested); splitter->addWidget(container); } else container = qobject_cast(splitter->widget(0)); container->show(); int position = 0; bool hadActiveView = false; Sublime::View* activeView = d->activeView; foreach (View *view, index->views()) { QWidget *widget = view->widget(container); if (widget) { if(!container->hasWidget(widget)) { container->addWidget(view, position); d->viewContainers[view] = container; d->widgetToView[widget] = view; } if(activeView == view) { hadActiveView = true; container->setCurrentWidget(widget); }else if(topViews.contains(view) && !hadActiveView) container->setCurrentWidget(widget); } position++; } } return Area::ContinueWalker; } void MainWindowPrivate::reconstructViews(QList topViews) { ViewCreator viewCreator(this, topViews); area->walkViews(viewCreator, area->rootIndex()); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::reconstruct() { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } idealController->setWidthForArea(Qt::LeftDockWidgetArea, area->thickness(Sublime::Left)); idealController->setWidthForArea(Qt::BottomDockWidgetArea, area->thickness(Sublime::Bottom)); idealController->setWidthForArea(Qt::RightDockWidgetArea, area->thickness(Sublime::Right)); IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, Sublime::AllPositions); reconstructViews(); m_mainWindow->blockSignals(true); qCDebug(SUBLIME) << "RECONSTRUCT" << area << " " << area->shownToolViews(Sublime::Left) << "\n"; foreach (View *view, area->toolViews()) { QString id = view->document()->documentSpecifier(); if (!id.isEmpty()) { Sublime::Position pos = area->toolViewPosition(view); if (area->shownToolViews(pos).contains(id)) idealController->raiseView(view, IdealController::GroupWithOtherViews); } } m_mainWindow->blockSignals(false); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::clearArea() { if(m_leftTabbarCornerWidget) m_leftTabbarCornerWidget->setParent(0); //reparent toolview widgets to 0 to prevent their deletion together with dockwidgets foreach (View *view, area->toolViews()) { // FIXME should we really delete here?? bool nonDestructive = true; idealController->removeView(view, nonDestructive); if (view->hasWidget()) view->widget()->setParent(0); } docks.clear(); //reparent all view widgets to 0 to prevent their deletion together with central //widget. this reparenting is necessary when switching areas inside the same mainwindow foreach (View *view, area->views()) { if (view->hasWidget()) view->widget()->setParent(0); } cleanCentralWidget(); m_mainWindow->setActiveView(0); m_indexSplitters.clear(); area = 0; viewContainers.clear(); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); } void MainWindowPrivate::cleanCentralWidget() { while(splitterCentralWidget->count()) delete splitterCentralWidget->widget(0); setBackgroundVisible(true); } struct ShownToolViewFinder { ShownToolViewFinder() {} Area::WalkerMode operator()(View *v, Sublime::Position /*position*/) { if (v->hasWidget() && v->widget()->isVisible()) views << v; return Area::ContinueWalker; } QList views; }; void MainWindowPrivate::slotDockShown(Sublime::View* /*view*/, Sublime::Position pos, bool /*shown*/) { if (ignoreDockShown) return; ShownToolViewFinder finder; m_mainWindow->area()->walkToolViews(finder, pos); QStringList ids; foreach (View *v, finder.views) { ids << v->document()->documentSpecifier(); } area->setShownToolViews(pos, ids); } void MainWindowPrivate::viewRemovedInternal(AreaIndex* index, View* view) { Q_UNUSED(index); Q_UNUSED(view); setBackgroundVisible(area->views().isEmpty()); } void MainWindowPrivate::viewAdded(Sublime::AreaIndex *index, Sublime::View *view) { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } // Remove container objects in the hierarchy from the parents, // because they are not needed anymore, and might lead to broken splitter hierarchy and crashes. for(Sublime::AreaIndex* current = index; current; current = current->parent()) { QSplitter *splitter = m_indexSplitters[current]; if (current->isSplit() && splitter) { // Also update the orientation splitter->setOrientation(current->orientation()); for(int w = 0; w < splitter->count(); ++w) { Container *container = qobject_cast(splitter->widget(w)); //we need to remove extra container before reconstruction //first reparent widgets in container so that they are not deleted if(container) { while (container->count()) { container->widget(0)->setParent(0); } //and then delete the container delete container; } } } } ViewCreator viewCreator(this); area->walkViews(viewCreator, index); emit m_mainWindow->viewAdded( view ); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); setBackgroundVisible(false); } void Sublime::MainWindowPrivate::raiseToolView(Sublime::View * view) { idealController->raiseView(view); } void MainWindowPrivate::aboutToRemoveView(Sublime::AreaIndex *index, Sublime::View *view) { QSplitter *splitter = m_indexSplitters[index]; if (!splitter) return; qCDebug(SUBLIME) << "index " << index << " root " << area->rootIndex(); qCDebug(SUBLIME) << "splitter " << splitter << " container " << splitter->widget(0); qCDebug(SUBLIME) << "structure: " << index->print() << " whole structure: " << area->rootIndex()->print(); //find the container for the view and remove the widget Container *container = qobject_cast(splitter->widget(0)); if (!container) { qWarning() << "Splitter does not have a left widget!"; return; } emit m_mainWindow->aboutToRemoveView( view ); if (view->widget()) widgetToView.remove(view->widget()); viewContainers.remove(view); const bool wasActive = m_mainWindow->activeView() == view; if (container->count() > 1) { //container is not empty or this is a root index //just remove a widget if( view->widget() ) { container->removeWidget(view->widget()); view->widget()->setParent(0); //activate what is visible currently in the container if the removed view was active if (wasActive) return m_mainWindow->setActiveView(container->viewForWidget(container->currentWidget())); } } else { if(m_leftTabbarCornerWidget) { m_leftTabbarCornerWidget->hide(); m_leftTabbarCornerWidget->setParent(0); } // We've about to remove the last view of this container. It will // be empty, so have to delete it, as well. // If we have a container, then it should be the only child of // the splitter. Q_ASSERT(splitter->count() == 1); container->removeWidget(view->widget()); if (view->widget()) view->widget()->setParent(0); else qWarning() << "View does not have a widget!"; Q_ASSERT(container->count() == 0); // We can be called from signal handler of container // (which is tab widget), so defer deleting it. container->deleteLater(); container->setParent(0); /* If we're not at the top level, we get to collapse split views. */ if (index->parent()) { /* The splitter used to have container as the only child, now it's time to get rid of it. Make sure deleting splitter does not delete container -- per above comment, we'll delete it later. */ container->setParent(0); m_indexSplitters.remove(index); delete splitter; AreaIndex *parent = index->parent(); QSplitter *parentSplitter = m_indexSplitters[parent]; AreaIndex *sibling = parent->first() == index ? parent->second() : parent->first(); QSplitter *siblingSplitter = m_indexSplitters[sibling]; if(siblingSplitter) { HoldUpdates du(parentSplitter); //save sizes and orientation of the sibling splitter parentSplitter->setOrientation(siblingSplitter->orientation()); QList sizes = siblingSplitter->sizes(); /* Parent has two children -- 'index' that we've deleted and 'sibling'. We move all children of 'sibling' into parent, and delete 'sibling'. sibling either contains a single Container instance, or a bunch of further QSplitters. */ while (siblingSplitter->count() > 0) { //reparent contents into parent splitter QWidget *siblingWidget = siblingSplitter->widget(0); siblingWidget->setParent(parentSplitter); parentSplitter->addWidget(siblingWidget); } m_indexSplitters.remove(sibling); delete siblingSplitter; parentSplitter->setSizes(sizes); } qCDebug(SUBLIME) << "after deleation " << parent << " has " << parentSplitter->count() << " elements"; //find the container somewhere to activate Container *containerToActivate = parentSplitter->findChild(); //activate the current view there if (containerToActivate) { m_mainWindow->setActiveView(containerToActivate->viewForWidget(containerToActivate->currentWidget())); setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); return; } } } setTabBarLeftCornerWidget(m_leftTabbarCornerWidget.data()); if ( wasActive ) { m_mainWindow->setActiveView(0L); } } void MainWindowPrivate::toolViewAdded(Sublime::View* /*toolView*/, Sublime::Position position) { IdealToolViewCreator toolViewCreator(this); area->walkToolViews(toolViewCreator, position); } void MainWindowPrivate::aboutToRemoveToolView(Sublime::View *toolView, Sublime::Position /*position*/) { if (!docks.contains(toolView)) return; idealController->removeView(toolView); // TODO are Views unique? docks.removeAll(toolView); } void MainWindowPrivate::toolViewMoved( Sublime::View *toolView, Sublime::Position position) { if (!docks.contains(toolView)) return; idealController->moveView(toolView, positionToDockArea(position)); } Qt::DockWidgetArea MainWindowPrivate::positionToDockArea(Position position) { switch (position) { case Sublime::Left: return Qt::LeftDockWidgetArea; case Sublime::Right: return Qt::RightDockWidgetArea; case Sublime::Bottom: return Qt::BottomDockWidgetArea; case Sublime::Top: return Qt::TopDockWidgetArea; default: return Qt::LeftDockWidgetArea; } } void MainWindowPrivate::switchToArea(QAction *action) { qCDebug(SUBLIME) << "for" << action; controller->showArea(m_actionAreas.value(action), m_mainWindow); } void MainWindowPrivate::updateAreaSwitcher(Sublime::Area *area) { QAction* action = m_areaActions.value(area); if (action) action->setChecked(true); } void MainWindowPrivate::activateFirstVisibleView() { QList views = area->views(); if (views.count() > 0) m_mainWindow->activateView(views.first()); } void MainWindowPrivate::widgetResized(Qt::DockWidgetArea /*dockArea*/, int /*thickness*/) { //TODO: adymo: remove all thickness business } void MainWindowPrivate::widgetCloseRequest(QWidget* widget) { if (View *view = widgetToView.value(widget)) { area->closeView(view); } } void MainWindowPrivate::setTabBarLeftCornerWidget(QWidget* widget) { if(widget != m_leftTabbarCornerWidget.data()) { delete m_leftTabbarCornerWidget.data(); m_leftTabbarCornerWidget.clear(); } m_leftTabbarCornerWidget = widget; if(!widget || !area || viewContainers.isEmpty()) return; AreaIndex* putToIndex = area->rootIndex(); QSplitter* splitter = m_indexSplitters[putToIndex]; while(putToIndex->isSplit()) { putToIndex = putToIndex->first(); splitter = m_indexSplitters[putToIndex]; } // Q_ASSERT(splitter || putToIndex == area->rootIndex()); Container* c = 0; if(splitter) { c = qobject_cast(splitter->widget(0)); }else{ c = viewContainers.values()[0]; } Q_ASSERT(c); c->setLeftCornerWidget(widget); } } #include "mainwindow_p.moc" #include "moc_mainwindow_p.cpp" diff --git a/util/activetooltip.cpp b/util/activetooltip.cpp index 8b8de2dc8b..46d0464186 100644 --- a/util/activetooltip.cpp +++ b/util/activetooltip.cpp @@ -1,328 +1,329 @@ /* This file is part of the KDE project Copyright 2007 Vladimir Prus Copyright 2009-2010 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "activetooltip.h" #include "debug.h" -#include -#include #include +#include #include +#include #include +#include +#include #include +#include +#include + #include -#include -#include -#include -#include namespace KDevelop { class ActiveToolTipPrivate { public: uint previousDistance_; QRect rect_; QRegion rectExtensions_; QList > friendWidgets_; }; ActiveToolTip::ActiveToolTip(QWidget *parent, const QPoint& position) : QWidget(parent, Qt::ToolTip), d(new ActiveToolTipPrivate) { Q_ASSERT(parent); d->previousDistance_ = std::numeric_limits::max(); setMouseTracking(true); d->rect_ = QRect(position, position); d->rect_.adjust(-10, -10, 10, 10); move(position); QPalette p; // adjust background color to use tooltip colors p.setColor(backgroundRole(), p.color(QPalette::ToolTipBase)); p.setColor(QPalette::Base, p.color(QPalette::ToolTipBase)); // adjust foreground color to use tooltip colors p.setColor(foregroundRole(), p.color(QPalette::ToolTipText)); p.setColor(QPalette::Text, p.color(QPalette::ToolTipText)); setPalette(p); setWindowFlags(Qt::WindowDoesNotAcceptFocus | windowFlags()); qApp->installEventFilter(this); } ActiveToolTip::~ActiveToolTip() { delete d; } bool ActiveToolTip::eventFilter(QObject *object, QEvent *e) { switch (e->type()) { case QEvent::MouseMove: if (underMouse() || insideThis(object)) { return false; } else { QPoint globalPos = static_cast(e)->globalPos(); int distance = (d->rect_.center() - globalPos).manhattanLength(); if(distance > (int)d->previousDistance_) { // Close if the widget under the mouse is not a child widget of the tool-tip qCDebug(UTIL) << "closing because of mouse move outside the widget"; close(); } else { d->previousDistance_ = distance; } } break; case QEvent::WindowActivate: if (insideThis(object)) { return false; } close(); break; case QEvent::WindowBlocked: // Modal dialog activated somewhere, it is the only case where a cursor // move may be missed and the popup has to be force-closed close(); break; default: break; } return false; } void ActiveToolTip::addFriendWidget(QWidget* widget) { d->friendWidgets_.append((QObject*)widget); } bool ActiveToolTip::insideThis(QObject* object) { while (object) { if(dynamic_cast(object)) return true; if (object == this || object == (QObject*)this->windowHandle() || d->friendWidgets_.contains(object)) { return true; } object = object->parent(); } // If the object clicked is inside a QQuickWidget, its parent is null even // if it is part of a tool-tip. This check ensures that a tool-tip is never // closed while the mouse is in it return underMouse(); } void ActiveToolTip::showEvent(QShowEvent*) { adjustRect(); } void ActiveToolTip::updateMouseDistance() { d->previousDistance_ = (d->rect_.center() - QCursor::pos()).manhattanLength(); } void ActiveToolTip::moveEvent(QMoveEvent* ev) { QWidget::moveEvent(ev); updateMouseDistance(); } void ActiveToolTip::resizeEvent(QResizeEvent*) { adjustRect(); // set mask from style QStyleOptionFrame opt; opt.init(this); QStyleHintReturnMask mask; if( style()->styleHint( QStyle::SH_ToolTip_Mask, &opt, this, &mask ) && !mask.region.isEmpty() ) { setMask( mask.region ); } emit resized(); updateMouseDistance(); } void ActiveToolTip::paintEvent(QPaintEvent* event) { QStylePainter painter( this ); painter.setClipRegion( event->region() ); QStyleOptionFrame opt; opt.init(this); painter.drawPrimitive(QStyle::PE_PanelTipLabel, opt); } void ActiveToolTip::addExtendRect(const QRect& rect) { d->rectExtensions_ += rect; } void ActiveToolTip::adjustRect() { // For tooltip widget, geometry() returns global coordinates. QRect r = geometry(); r.adjust(-10, -10, 10, 10); d->rect_ = r; updateMouseDistance(); } void ActiveToolTip::setBoundingGeometry(const QRect& geometry) { d->rect_ = geometry; d->rect_.adjust(-10, -10, 10, 10); } namespace { typedef QMultiMap, QString> > ToolTipPriorityMap; static ToolTipPriorityMap registeredToolTips; ActiveToolTipManager manager; QWidget* masterWidget(QWidget* w) { while(w && w->parent() && qobject_cast(w->parent())) w = qobject_cast(w->parent()); return w; } } void ActiveToolTipManager::doVisibility() { bool exclusive = false; int lastBottomPosition = -1; int lastLeftPosition = -1; QRect fullGeometry; //Geometry of all visible tooltips together for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) { QPointer< ActiveToolTip > w = (*it).first; if(w) { if(exclusive) { (w.data())->hide(); }else{ QRect geom = (w.data())->geometry(); if((w.data())->geometry().top() < lastBottomPosition) { geom.moveTop(lastBottomPosition); } if(lastLeftPosition != -1) geom.moveLeft(lastLeftPosition); (w.data())->setGeometry(geom); // (w.data())->show(); lastBottomPosition = (w.data())->geometry().bottom(); lastLeftPosition = (w.data())->geometry().left(); if(it == registeredToolTips.constBegin()) fullGeometry = (w.data())->geometry(); else fullGeometry = fullGeometry.united((w.data())->geometry()); } if(it.key() == 0) { exclusive = true; } } } if(!fullGeometry.isEmpty()) { QRect oldFullGeometry = fullGeometry; QRect screenGeometry = QApplication::desktop()->screenGeometry(fullGeometry.topLeft()); if(fullGeometry.bottom() > screenGeometry.bottom()) { //Move up, avoiding the mouse-cursor fullGeometry.moveBottom(fullGeometry.top()-10); if(fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos())) fullGeometry.moveBottom(QCursor::pos().y() - 20); } if(fullGeometry.right() > screenGeometry.right()) { //Move to left, avoiding the mouse-cursor fullGeometry.moveRight(fullGeometry.left()-10); if(fullGeometry.adjusted(-20, -20, 20, 20).contains(QCursor::pos())) fullGeometry.moveRight(QCursor::pos().x() - 20); } // Now fit this to screen if (fullGeometry.left() < 0) { fullGeometry.setLeft(0); } if (fullGeometry.top() < 0) { fullGeometry.setTop(0); } QPoint offset = fullGeometry.topLeft() - oldFullGeometry.topLeft(); if(!offset.isNull()) { for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) if((*it).first) { (*it).first.data()->move((*it).first.data()->pos() + offset); } } } //Set bounding geometry, and remove old tooltips for(ToolTipPriorityMap::iterator it = registeredToolTips.begin(); it != registeredToolTips.end(); ) { if(!(*it).first) { it = registeredToolTips.erase(it); }else{ (*it).first.data()->setBoundingGeometry(fullGeometry); ++it; } } //Final step: Show tooltips for(ToolTipPriorityMap::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) { if(it->first.data() && masterWidget(it->first.data())->isActiveWindow()) (*it).first.data()->show(); if(exclusive) break; } } void ActiveToolTip::showToolTip(KDevelop::ActiveToolTip* tooltip, float priority, QString uniqueId) { if(!uniqueId.isEmpty()) { for(QMap< float, QPair< QPointer< ActiveToolTip >, QString > >::const_iterator it = registeredToolTips.constBegin(); it != registeredToolTips.constEnd(); ++it) { if((*it).second == uniqueId) delete (*it).first.data(); } } registeredToolTips.insert(priority, qMakePair(QPointer(tooltip), uniqueId)); connect(tooltip, &ActiveToolTip::resized, &manager, &ActiveToolTipManager::doVisibility); QMetaObject::invokeMethod(&manager, "doVisibility", Qt::QueuedConnection); } void ActiveToolTip::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); deleteLater(); } } diff --git a/util/convenientfreelist.h b/util/convenientfreelist.h index 6c5e5b97b7..91eafe74cd 100644 --- a/util/convenientfreelist.h +++ b/util/convenientfreelist.h @@ -1,743 +1,743 @@ /* This file is part of KDevelop Copyright 2008 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_CONVENIENTFREELIST_H #define KDEVPLATFORM_CONVENIENTFREELIST_H -#include -#include +#include +#include #include "embeddedfreetree.h" #include "kdevvarlengtharray.h" namespace KDevelop { template class ConvenientEmbeddedSetIterator; template class ConvenientEmbeddedSetFilterIterator; ///A convenience-class for accessing the data in a set managed by the EmbeddedFreeTree algorithms. template class ConstantConvenientEmbeddedSet { public: ConstantConvenientEmbeddedSet() : m_data(0), m_dataSize(0), m_centralFreeItem(-1) { } ConstantConvenientEmbeddedSet(const Data* data, uint count, int centralFreeItem) : m_data(data), m_dataSize(count), m_centralFreeItem(centralFreeItem) { } ///Returns whether the item is contained in this set bool contains(const Data& data) const { return indexOf(data) != -1; } ///Returns the position of the item in the underlying array, or -1 if it is not contained int indexOf(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.indexOf(data); } ///Returns the size of the underlying array uint dataSize() const { return m_dataSize; } uint countFreeItems() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.countFreeItems(); } void verify() { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); alg.verifyTreeConsistent(); alg.verifyOrder(); } ///Returns the underlying array. That array may contain invalid/free items. const Data* data() const { return m_data; } ///Returns the first valid index that has a data-value larger or equal to @param data. ///Returns -1 if nothing is found. int lowerBound(const Data& data) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, 0, m_dataSize); } ///Returns the first valid index that has a data-value larger or equal to @param data, ///and that is in the range [start, end). ///Returns -1 if nothing is found. int lowerBound(const Data& data, uint start, uint end) const { EmbeddedTreeAlgorithms alg(m_data, m_dataSize, m_centralFreeItem); return alg.lowerBound(data, start, end); } ///Finds a valid most central in the range [start, end). ///Returns -1 if no such item exists. int validMiddle(uint start, uint end) { if(end <= start) return -1; int firstTry = ((end-start)/2) + start; int thisTry = firstTry; while(thisTry < (int)end && Handler::isFree(m_data[thisTry])) ++thisTry; if(thisTry != (int)end) return thisTry; //Nothing find on right side of middle, try the other direction thisTry = firstTry-1; while(thisTry >= (int)start && Handler::isFree(m_data[thisTry])) --thisTry; if(thisTry >= (int)start) return thisTry; else return -1; } ///Returns the first valid item in the range [pos, end), or -1 int firstValidItem(int start, int end = -1) const { if(end == -1) end = (int)m_dataSize; for(; start < end; ++start) if(!Handler::isFree(m_data[start])) return start; return -1; } ///Returns the last valid item in the range [pos, end), or -1 int lastValidItem(int start = 0, int end = -1) const { if(end == -1) end = (int)m_dataSize; --end; for(; end >= start; --end) if(!Handler::isFree(m_data[end])) return end; return -1; } typedef ConvenientEmbeddedSetIterator Iterator; ConvenientEmbeddedSetIterator iterator() const; // protected: const Data* m_data; uint m_dataSize; int m_centralFreeItem; }; ///Convenient iterator that automatically skips invalid/free items in the array. template class ConvenientEmbeddedSetIterator : public ConstantConvenientEmbeddedSet { public: ConvenientEmbeddedSetIterator(const Data* data = 0, uint count = 0, int centralFreeItem = -1) : ConstantConvenientEmbeddedSet(data, count, centralFreeItem), m_pos(0) { //Move to first valid position moveToValid(); } ///Returns true of this iterator has a value to return operator bool() const { return m_pos != this->m_dataSize; } const Data* operator->() const { return &this->m_data[m_pos]; } const Data& operator*() const { return this->m_data[m_pos]; } ConvenientEmbeddedSetIterator& operator++() { ++m_pos; moveToValid(); return *this; } protected: inline void moveToValid() { while(this->m_pos < this->m_dataSize && (Handler::isFree(this->m_data[this->m_pos]))) ++this->m_pos; } uint m_pos; }; ///An iterator that allows efficient matching between two lists with different data type. ///Important: It must be possible to extract the data-type of the second list from the items in the first list ///The second list must be sorted by that data. ///The first list must primarily be sorted by that data, but is allowed to ///be sub-ordered by something else, and multiple items in the first list are allowed to match one item in the second. ///This iterator iterates through all items in the first list that have extracted key-data that is in represented in the second. template class ConvenientEmbeddedSetFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetFilterIterator() : m_match(-1) { } ConvenientEmbeddedSetFilterIterator(const ConvenientEmbeddedSetIterator& base, const ConvenientEmbeddedSetIterator& rhs) : ConvenientEmbeddedSetIterator(base), m_rhs(rhs), m_match(-1) { boundStack.append( qMakePair( qMakePair(0u, this->m_dataSize), qMakePair(0u, rhs.m_dataSize) ) ); go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT(boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && boundStack.back().second.second < 10000 ); private: void go() { m_match = -1; boundsUp: if(boundStack.isEmpty()) return; CHECK_BOUNDS QPair, QPair > currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; uint rhsStart = currentBounds.second.first, rhsEnd = currentBounds.second.second; #if 0 //This code works, but it doesn't give a speedup int ownFirstValid = this->firstValidItem(ownStart, ownEnd), ownLastValid = this->lastValidItem(ownStart, ownEnd); int rhsFirstValid = m_rhs.firstValidItem(rhsStart, rhsEnd), rhsLastValid = m_rhs.lastValidItem(rhsStart, rhsEnd); if(ownFirstValid == -1 || rhsFirstValid == -1) goto boundsUp; Data2 ownFirstValidData = KeyExtractor::extract(this->m_data[ownFirstValid]); Data2 ownLastValidData = KeyExtractor::extract(this->m_data[ownLastValid]); Data2 commonStart = ownFirstValidData; Data2 commonLast = ownLastValidData; //commonLast is also still valid if(commonStart < m_rhs.m_data[rhsFirstValid]) commonStart = m_rhs.m_data[rhsFirstValid]; if(m_rhs.m_data[rhsLastValid] < commonLast) commonLast = m_rhs.m_data[rhsLastValid]; if(commonLast < commonStart) goto boundsUp; #endif while(true) { if(ownStart == ownEnd) goto boundsUp; int ownMiddle = this->validMiddle(ownStart, ownEnd); Q_ASSERT(ownMiddle < 100000); if(ownMiddle == -1) goto boundsUp; //No valid items in the range Data2 currentData2 = KeyExtractor::extract(this->m_data[ownMiddle]); Q_ASSERT(!Handler2::isFree(currentData2)); int bound = m_rhs.lowerBound(currentData2, rhsStart, rhsEnd); if(bound == -1) { //Release second half of the own range // Q_ASSERT(ownEnd > ownMiddle); ownEnd = ownMiddle; continue; } if(currentData2 == m_rhs.m_data[bound]) { //We have a match this->m_match = ownMiddle; //Append the ranges that need to be matched next, without the matched item boundStack.append( qMakePair( qMakePair( (uint)ownMiddle+1, ownEnd ), qMakePair((uint)bound, rhsEnd)) ); if(ownMiddle) boundStack.append( qMakePair( qMakePair( ownStart, (uint)ownMiddle ), qMakePair(rhsStart, (uint)bound+1)) ); return; } if(bound == m_rhs.firstValidItem(rhsStart)) { //The bound is the first valid item of the second range. //Discard left side and the matched left item, and continue. ownStart = ownMiddle+1; rhsStart = bound; continue; } //Standard: Split both sides into 2 ranges that will be checked next boundStack.append( qMakePair( qMakePair( (uint)ownMiddle+1, ownEnd ), qMakePair((uint)bound, rhsEnd)) ); // Q_ASSERT(ownMiddle <= ownEnd); ownEnd = ownMiddle; //We loose the item at 'middle' here, but that's fine, since it hasn't found a match. rhsEnd = bound+1; } } //Bounds that yet need to be matched. KDevVarLengthArray, QPair > > boundStack; ConvenientEmbeddedSetIterator m_rhs; int m_match; }; ///Filters a list-embedded set by a binary tree set as managed by the SetRepository data structures template class ConvenientEmbeddedSetTreeFilterIterator : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterIterator() : m_match(-1) { } ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterIterator(const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator(base), m_rhs(rhs), m_match(-1), m_noFiltering(noFiltering) { if(rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if(ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if(ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; boundStack.append( qMakePair( qMakePair((uint)ownStart, (uint)ownEnd), rhs.node() ) ); } go(); } operator bool() const { return m_match != -1; } const Data* operator->() const { Q_ASSERT(m_match != -1); return &this->m_data[m_match]; } const Data& operator*() const { Q_ASSERT(m_match != -1); return this->m_data[m_match]; } ConvenientEmbeddedSetTreeFilterIterator& operator++() { Q_ASSERT(m_match != -1); go(); return *this; } #define CHECK_BOUNDS Q_ASSERT(boundStack.back().first.first < 100000 && boundStack.back().first.second < 10000 && boundStack.back().second.first < 100000 && boundStack.back().second.second < 10000 ); private: void go() { if(m_noFiltering) { ++m_match; if((uint)m_match >= this->m_dataSize) m_match = -1; return; } if(m_match != -1) { //Match multiple items in this list to one in the tree m_match = this->firstValidItem(m_match+1, this->m_dataSize); if(m_match != -1 && KeyExtractor::extract(this->m_data[m_match]) == m_matchingTo) return; } m_match = -1; boundsUp: if(boundStack.isEmpty()) return; QPair, typename TreeSet::Node > currentBounds = boundStack.back(); boundStack.pop_back(); uint ownStart = currentBounds.first.first, ownEnd = currentBounds.first.second; typename TreeSet::Node currentNode = currentBounds.second; if(ownStart >= ownEnd) goto boundsUp; if(!currentNode.isValid()) goto boundsUp; while(true) { if(ownStart == ownEnd) goto boundsUp; if(currentNode.isFinalNode()) { // qCDebug(UTIL) << ownStart << ownEnd << "final node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //Check whether the item is contained int bound = lowerBound(*currentNode, ownStart, ownEnd); // qCDebug(UTIL) << "bound:" << bound << (KeyExtractor::extract(this->m_data[bound]) == *currentNode); if(bound != -1 && KeyExtractor::extract(this->m_data[bound]) == *currentNode) { //Got a match m_match = bound; m_matchingTo = *currentNode; m_matchBound = ownEnd; return; }else{ //Mismatch goto boundsUp; } }else{ // qCDebug(UTIL) << ownStart << ownEnd << "node" << currentNode.start() * extractor_div_with << currentNode.end() * extractor_div_with; //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = currentNode.leftChild(); typename TreeSet::Node rightNode = currentNode.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), ownStart, ownEnd); if(rightSearchStart == -1) rightSearchStart = ownEnd; int leftSearchLast = lowerBound(leftLastItem, ownStart, rightSearchStart != -1 ? rightSearchStart : ownEnd); if(leftSearchLast == -1) leftSearchLast = rightSearchStart-1; bool recurseLeft = false; if(leftSearchLast > (int)ownStart) { recurseLeft = true; //There must be something in the range ownStart -> leftSearchLast that matches the range }else if((int)ownStart == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[ownStart]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if(rightSearchStart < (int)ownEnd) recurseRight = true; if(recurseLeft && recurseRight) { //Push the right branch onto the stack, and work in the left one boundStack.append( qMakePair( qMakePair( (uint)rightSearchStart, ownEnd ), rightNode) ); } if(recurseLeft) { currentNode = leftNode; if(leftSearchLast != -1) ownEnd = leftSearchLast+1; }else if(recurseRight) { currentNode = rightNode; ownStart = rightSearchStart; }else{ goto boundsUp; } } } } ///Returns the first valid index that has an extracted data-value larger or equal to @param data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while(1) { if(start >= end) return currentBound; int center = (start + end)/2; //Skip free items, since they cannot be used for ordering for(; center < end; ) { if(!Handler::isFree(this->m_data[center])) break; ++center; } if(center == end) { end = (start + end)/2; //No non-free items found in second half, so continue search in the other }else{ Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if(data == centerData || data < centerData) { currentBound = center; end = (start + end)/2; }else{ //Continue search in second half start = center+1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in KDevVarLengthArray, typename TreeSet::Node > > boundStack; TreeSet m_rhs; int m_match, m_matchBound; Data2 m_matchingTo; bool m_noFiltering; }; ///Same as above, except that it visits all filtered items with a visitor, instead of iterating over them. ///This is more efficient. The visiting is done directly from within the constructor. template class ConvenientEmbeddedSetTreeFilterVisitor : public ConvenientEmbeddedSetIterator { public: ConvenientEmbeddedSetTreeFilterVisitor() { } typedef QPair, typename TreeSet::Node > Bounds; struct Bound { inline Bound(uint s, uint e, const typename TreeSet::Node& n) : start(s), end(e), node(n) { } Bound() { } uint start; uint end; typename TreeSet::Node node; }; ///@param noFiltering whether the given input is pre-filtered. If this is true, base will be iterated without skipping any items. ConvenientEmbeddedSetTreeFilterVisitor(Visitor& visitor, const ConvenientEmbeddedSetIterator& base, const TreeSet& rhs, bool noFiltering = false) : ConvenientEmbeddedSetIterator(base), m_visitor(visitor), m_rhs(rhs), m_noFiltering(noFiltering) { if(m_noFiltering) { for(uint a = 0; a < this->m_dataSize; ++a) visitor(this->m_data[a]); return; } if(rhs.node().isValid()) { //Correctly initialize the initial bounds int ownStart = lowerBound(rhs.node().firstItem(), 0, this->m_dataSize); if(ownStart == -1) return; int ownEnd = lowerBound(rhs.node().lastItem(), ownStart, this->m_dataSize); if(ownEnd == -1) ownEnd = this->m_dataSize; else ownEnd += 1; go( Bound((uint)ownStart, (uint)ownEnd, rhs.node()) ); } } private: void go( Bound bound ) { KDevVarLengthArray bounds; while(true) { if(bound.start >= bound.end) goto nextBound; if(bound.node.isFinalNode()) { //Check whether the item is contained int b = lowerBound(*bound.node, bound.start, bound.end); if(b != -1) { const Data2& matchTo(*bound.node); if(KeyExtractor::extract(this->m_data[b]) == matchTo) { while(1) { m_visitor(this->m_data[b]); b = this->firstValidItem(b+1, this->m_dataSize); if(b < (int)this->m_dataSize && b != -1 && KeyExtractor::extract(this->m_data[b]) == matchTo) continue; else break; } } } goto nextBound; }else{ //This is not a final node, split up the search into the sub-nodes typename TreeSet::Node leftNode = bound.node.leftChild(); typename TreeSet::Node rightNode = bound.node.rightChild(); Q_ASSERT(leftNode.isValid()); Q_ASSERT(rightNode.isValid()); Data2 leftLastItem = leftNode.lastItem(); int rightSearchStart = lowerBound(rightNode.firstItem(), bound.start, bound.end); if(rightSearchStart == -1) rightSearchStart = bound.end; int leftSearchLast = lowerBound(leftLastItem, bound.start, rightSearchStart != -1 ? rightSearchStart : bound.end); if(leftSearchLast == -1) leftSearchLast = rightSearchStart-1; bool recurseLeft = false; if(leftSearchLast > (int)bound.start) { recurseLeft = true; //There must be something in the range bound.start -> leftSearchLast that matches the range }else if((int)bound.start == leftSearchLast) { //Check if the one item item under leftSearchStart is contained in the range Data2 leftFoundStartData = KeyExtractor::extract(this->m_data[bound.start]); recurseLeft = leftFoundStartData < leftLastItem || leftFoundStartData == leftLastItem; } bool recurseRight = false; if(rightSearchStart < (int)bound.end) recurseRight = true; if(recurseLeft && recurseRight) bounds.append( Bound(rightSearchStart, bound.end, rightNode) ); if(recurseLeft) { bound.node = leftNode; if(leftSearchLast != -1) bound.end = leftSearchLast+1; }else if(recurseRight) { bound.node = rightNode; bound.start = rightSearchStart; }else{ goto nextBound; } continue; } nextBound: if(bounds.isEmpty()) { return; }else{ bound = bounds.back(); bounds.pop_back(); } } } ///Returns the first valid index that has an extracted data-value larger or equal to @param data. ///Returns -1 if nothing is found. int lowerBound(const Data2& data, int start, int end) { int currentBound = -1; while(1) { if(start >= end) return currentBound; int center = (start + end)/2; //Skip free items, since they cannot be used for ordering for(; center < end; ) { if(!Handler::isFree(this->m_data[center])) break; ++center; } if(center == end) { end = (start + end)/2; //No non-free items found in second half, so continue search in the other }else{ Data2 centerData = KeyExtractor::extract(this->m_data[center]); //Even if the data equals we must continue searching to the left, since there may be multiple matching if(data == centerData || data < centerData) { currentBound = center; end = (start + end)/2; }else{ //Continue search in second half start = center+1; } } } } //Bounds that yet need to be matched. Always a range in the own vector, and a node that all items in the range are contained in Visitor& m_visitor; TreeSet m_rhs; bool m_noFiltering; }; template ConvenientEmbeddedSetIterator ConstantConvenientEmbeddedSet::iterator() const { return ConvenientEmbeddedSetIterator(m_data, m_dataSize, m_centralFreeItem); } ///This is a simple set implementation based on the embedded free tree algorithms. ///The core advantage of the whole thing is that the wole set is represented by a consecutive ///memory-area, and thus can be stored or copied using a simple memcpy. ///However in many cases it's better using the algorithms directly in such cases. /// ///However even for normal tasks this implementation does have some advantages over std::set: ///- Many times faster iteration through contained data ///- Lower memory-usage if the objects are small, since there is no heap allocation overhead ///- Can be combined with other embedded-free-list based sets using algorithms in ConstantConvenientEmbeddedSet ///Disadvantages: ///- Significantly slower insertion template class ConvenientFreeListSet { public: typedef ConvenientEmbeddedSetIterator Iterator; ConvenientFreeListSet() : m_centralFree(-1) { } ///Re-construct a set from its components ConvenientFreeListSet(int centralFreeItem, QVector data) : m_data(data), m_centralFree(centralFreeItem) { } ///You can use this to store the set to disk and later give it together with data() to the constructor, thus reconstructing it. int centralFreeItem() const { return m_centralFree; } const QVector& data() const { return m_data; } void insert(const Data& item) { if(contains(item)) return; KDevelop::EmbeddedTreeAddItem add(m_data.data(), m_data.size(), m_centralFree, item); if((int)add.newItemCount() != (int)m_data.size()) { QVector newData; newData.resize(add.newItemCount()); add.transferData(newData.data(), newData.size()); m_data = newData; } } Iterator iterator() const { return Iterator(m_data.data(), m_data.size(), m_centralFree); } bool contains(const Data& item) const { KDevelop::EmbeddedTreeAlgorithms alg(m_data.data(), m_data.size(), m_centralFree); return alg.indexOf(Data(item)) != -1; } void remove(const Data& item) { KDevelop::EmbeddedTreeRemoveItem remove(m_data.data(), m_data.size(), m_centralFree, item); if((int)remove.newItemCount() != (int)m_data.size()) { QVector newData; newData.resize(remove.newItemCount()); remove.transferData(newData.data(), newData.size()); m_data = newData; } } private: int m_centralFree; QVector m_data; }; } #endif diff --git a/util/duchainify/main.cpp b/util/duchainify/main.cpp index 15cf495fad..089fc46c02 100644 --- a/util/duchainify/main.cpp +++ b/util/duchainify/main.cpp @@ -1,305 +1,306 @@ /*************************************************************************** * Copyright 2009 David Nolden * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "main.h" #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 #include -#include + #include #include -#include -#include + bool verbose=false, warnings=false; using namespace KDevelop; void messageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) { Q_UNUSED(context); switch (type) { case QtDebugMsg: if(verbose) std::cerr << qPrintable(msg) << std::endl; break; case QtWarningMsg: if(warnings) std::cerr << qPrintable(msg) << std::endl; break; case QtCriticalMsg: std::cerr << qPrintable(msg) << std::endl; break; case QtFatalMsg: std::cerr << qPrintable(msg) << std::endl; abort(); } } Manager::Manager(QCommandLineParser* args) : m_total(0), m_args(args), m_allFilesAdded(0) { } void Manager::init() { QList includes; if(m_args->positionalArguments().isEmpty()) { std::cerr << "Need file or directory to duchainify" << std::endl; QCoreApplication::exit(1); } uint features = TopDUContext::VisibleDeclarationsAndContexts; if(m_args->isSet("features")) { QString featuresStr = m_args->value("features"); if(featuresStr == "visible-declarations") { features = TopDUContext::VisibleDeclarationsAndContexts; } else if(featuresStr == "all-declarations") { features = TopDUContext::AllDeclarationsAndContexts; } else if(featuresStr == "all-declarations-and-uses") { features = TopDUContext::AllDeclarationsContextsAndUses; } else if(featuresStr == "all-declarations-and-uses-and-AST") { features = TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST; } else if(featuresStr == "empty") { features = TopDUContext::Empty; } else if(featuresStr == "simplified-visible-declarations") { features = TopDUContext::SimplifiedVisibleDeclarationsAndContexts; } else{ std::cerr << "Wrong feature-string given\n"; QCoreApplication::exit(2); return; } } if(m_args->isSet("force-update")) features |= TopDUContext::ForceUpdate; if(m_args->isSet("force-update-recursive")) features |= TopDUContext::ForceUpdateRecursive; if(m_args->isSet("threads")) { bool ok = false; int count = m_args->value("threads").toInt(&ok); ICore::self()->languageController()->backgroundParser()->setThreadCount(count); if(!ok) { std::cerr << "bad thread count\n"; QCoreApplication::exit(3); return; } } // quit when everything is done // background parser emits hideProgress() signal in two situations: // when everything is done and when bgparser is suspended // later doesn't happen in duchain, so just rely on hideProgress() // and quit when it's emitted connect(ICore::self()->languageController()->backgroundParser(), &BackgroundParser::hideProgress, this, &Manager::finish); foreach (const auto& file, m_args->positionalArguments()) { addToBackgroundParser(file, (TopDUContext::Features)features); } m_allFilesAdded = 1; if ( m_total ) { std::cerr << "Added " << m_total << " files to the background parser" << std::endl; const int threads = ICore::self()->languageController()->backgroundParser()->threadCount(); std::cerr << "parsing with " << threads << " threads" << std::endl; ICore::self()->languageController()->backgroundParser()->parseDocuments(); } else { std::cerr << "no files added to the background parser" << std::endl; QCoreApplication::exit(0); return; } } void Manager::updateReady(IndexedString url, ReferencedTopDUContext topContext) { qDebug() << "finished" << url.toUrl().toLocalFile() << "success: " << (bool)topContext; m_waiting.remove(url.toUrl()); std::cerr << "processed " << (m_total - m_waiting.size()) << " out of " << m_total << std::endl; if (!topContext) return; std::cerr << std::endl; QTextStream stream(stdout); if (m_args->isSet("dump-definitions")) { DUChainReadLocker lock; std::cerr << "Definitions:" << std::endl; DUChain::definitions()->dump(stream); std::cerr << std::endl; } if (m_args->isSet("dump-symboltable")) { DUChainReadLocker lock; std::cerr << "PersistentSymbolTable:" << std::endl; PersistentSymbolTable::self().dump(stream); std::cerr << std::endl; } DUChainDumper::Features features; if (m_args->isSet("dump-context")) { features |= DUChainDumper::DumpContext; } if (m_args->isSet("dump-errors")) { features |= DUChainDumper::DumpProblems; } if (auto depth = m_args->value("dump-depth").toInt()) { DUChainReadLocker lock; std::cerr << "Context:" << std::endl; DUChainDumper dumpChain(features); dumpChain.dump(topContext, depth); } if (m_args->isSet("dump-graph")) { DUChainReadLocker lock; DumpDotGraph dumpGraph; const QString dotOutput = dumpGraph.dotGraph(topContext); std::cout << qPrintable(dotOutput) << std::endl; } } void Manager::addToBackgroundParser(QString path, TopDUContext::Features features) { QFileInfo info(path); if(info.isFile()) { qDebug() << "adding file" << path; QUrl pathUrl = QUrl::fromLocalFile(info.canonicalFilePath()); m_waiting << pathUrl; ++m_total; KDevelop::DUChain::self()->updateContextForUrl(KDevelop::IndexedString(pathUrl), features, this); }else if(info.isDir()) { QDirIterator contents(path); while(contents.hasNext()) { QString newPath = contents.next(); if(!newPath.endsWith('.')) addToBackgroundParser(newPath, features); } } } QSet< QUrl > Manager::waiting() { return m_waiting; } void Manager::finish() { std::cerr << "ready" << std::endl; QApplication::quit(); } using namespace KDevelop; int main(int argc, char** argv) { KAboutData aboutData( "duchainify", i18n( "duchainify" ), "1", i18n("DUChain builder application"), KAboutLicense::GPL, i18n( "(c) 2009 David Nolden" ), QString(), "http://www.kdevelop.org" ); QApplication app(argc, argv); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.addVersionOption(); parser.addHelpOption(); parser.addPositionalArgument("paths", i18n("file or directory"), "[PATH...]"); parser.addOption(QCommandLineOption{QStringList{"w", "warnings"}, i18n("Show warnings")}); parser.addOption(QCommandLineOption{QStringList{"V", "verbose"}, i18n("Show warnings and debug output")}); parser.addOption(QCommandLineOption{QStringList{"u", "force-update"}, i18n("Enforce an update of the top-contexts corresponding to the given files")}); parser.addOption(QCommandLineOption{QStringList{"r", "force-update-recursive"}, i18n("Enforce an update of the top-contexts corresponding to the given files and all included files")}); parser.addOption(QCommandLineOption{QStringList{"t", "threads"}, i18n("Number of threads to use"), "count"}); parser.addOption(QCommandLineOption{QStringList{"f", "features"}, i18n("Features to build. Options: empty, simplified-visible-declarations, visible-declarations (default), all-declarations, all-declarations-and-uses, all-declarations-and-uses-and-AST"), "features"}); parser.addOption(QCommandLineOption{QStringList{"dump-context"}, i18n("Print complete Definition-Use Chain on successful parse")}); parser.addOption(QCommandLineOption{QStringList{"dump-definitions"}, i18n("Print complete DUChain Definitions repository on successful parse")}); parser.addOption(QCommandLineOption{QStringList{"dump-symboltable"}, i18n("Print complete DUChain PersistentSymbolTable repository on successful parse")}); parser.addOption(QCommandLineOption{QStringList{"depth"}, i18n("Number defining the maximum depth where declaration details are printed"), "depth"}); parser.addOption(QCommandLineOption{QStringList{"dump-graph"}, i18n("Dump DUChain graph (in .dot format)")}); parser.addOption(QCommandLineOption{QStringList{"d", "dump-errors"}, i18n("Print problems encountered during parsing")}); parser.process(app); aboutData.processCommandLine(&parser); verbose = parser.isSet("verbose"); warnings = parser.isSet("warnings"); qInstallMessageHandler(messageOutput); AutoTestShell::init(); TestCore::initialize(Core::NoUi, "duchainify"); Manager manager(&parser); QTimer::singleShot(0, &manager, SLOT(init())); int ret = app.exec(); TestCore::shutdown(); return ret; } diff --git a/util/environmentgrouplist.cpp b/util/environmentgrouplist.cpp index eeb44c1dc9..78e5d69b53 100644 --- a/util/environmentgrouplist.cpp +++ b/util/environmentgrouplist.cpp @@ -1,199 +1,198 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentgrouplist.h" -#include -#include -#include +#include +#include +#include -#include -#include +#include namespace KDevelop { class EnvironmentGroupListPrivate { public: QMap > m_groups; QString m_defaultGroup; }; static const QString defaultEnvGroupKey = "Default Environment Group"; static const QString envGroup = "Environment Settings"; static const QString groupListKey = "Group List"; void decode( KConfigGroup cfg, EnvironmentGroupListPrivate* d ) { d->m_defaultGroup = cfg.readEntry( defaultEnvGroupKey, QStringLiteral( "default" ) ); QStringList grouplist = cfg.readEntry( groupListKey, QStringList() << "default" ); foreach( const QString &envgrpname, grouplist ) { KConfigGroup envgrp( &cfg, envgrpname ); QMap variables; foreach( const QString &varname, envgrp.keyList() ) { variables[varname] = envgrp.readEntry( varname, QStringLiteral("") ); } d->m_groups.insert( envgrpname, variables ); } } void encode( KConfigGroup cfg, EnvironmentGroupListPrivate* d ) { cfg.writeEntry( defaultEnvGroupKey, d->m_defaultGroup ); cfg.writeEntry( groupListKey, d->m_groups.keys() ); - foreach( const QString &group, cfg.groupList() ) + foreach( const QString &group, cfg.groupList() ) { - if( !d->m_groups.keys().contains( group ) ) + if( !d->m_groups.keys().contains( group ) ) { cfg.deleteGroup( group ); } } foreach( const QString &group, d->m_groups.keys() ) { KConfigGroup envgrp( &cfg, group ); envgrp.deleteGroup(); foreach( const QString &var, d->m_groups[group].keys() ) { envgrp.writeEntry( var, d->m_groups[group][var] ); } } cfg.sync(); } EnvironmentGroupList::EnvironmentGroupList( const EnvironmentGroupList& rhs ) : d( new EnvironmentGroupListPrivate( *rhs.d ) ) { } EnvironmentGroupList& EnvironmentGroupList::operator=( const EnvironmentGroupList& rhs ) { *d = *rhs.d; return *this; } EnvironmentGroupList::EnvironmentGroupList( KSharedConfigPtr config ) : d( new EnvironmentGroupListPrivate ) { KConfigGroup cfg( config, envGroup ); decode( cfg, d ); } EnvironmentGroupList::EnvironmentGroupList( KConfig* config ) : d( new EnvironmentGroupListPrivate ) { KConfigGroup cfg( config, envGroup ); decode( cfg, d ); } EnvironmentGroupList::~EnvironmentGroupList() { delete d; } const QMap EnvironmentGroupList::variables( const QString& group ) const { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QMap& EnvironmentGroupList::variables( const QString& group ) { return d->m_groups[group.isEmpty() ? d->m_defaultGroup : group]; } QString EnvironmentGroupList::defaultGroup() const { return d->m_defaultGroup; } void EnvironmentGroupList::setDefaultGroup( const QString& group ) { if( group.isEmpty() ) { return; } if( d->m_groups.contains( group ) ) { d->m_defaultGroup = group; } } void EnvironmentGroupList::saveSettings( KConfig* config ) const { KConfigGroup cfg(config, envGroup ); encode( cfg, d ); config->sync(); } void EnvironmentGroupList::loadSettings( KConfig* config ) { d->m_groups.clear(); KConfigGroup cfg(config, envGroup ); decode( cfg, d ); } QStringList EnvironmentGroupList::groups() const { return d->m_groups.keys(); } void EnvironmentGroupList::removeGroup( const QString& group ) { d->m_groups.remove( group ); } EnvironmentGroupList::EnvironmentGroupList() : d( new EnvironmentGroupListPrivate ) { } QStringList EnvironmentGroupList::createEnvironment( const QString & group, const QStringList & defaultEnvironment ) const { QMap retMap; foreach( const QString &line, defaultEnvironment ) { QString varName = line.section( '=', 0, 0 ); QString varValue = line.section( '=', 1 ); retMap.insert( varName, varValue ); } if( !group.isEmpty() ) { QMap userMap = variables(group); for( QMap::const_iterator it = userMap.constBegin(); it != userMap.constEnd(); ++it ) { retMap.insert( it.key(), it.value() ); } } QStringList env; for( QMap::const_iterator it = retMap.constBegin(); it != retMap.constEnd(); ++it ) { env << it.key() + '=' + it.value(); } return env; } } diff --git a/util/environmentgrouplist.h b/util/environmentgrouplist.h index b3f9441945..0412c5e60f 100644 --- a/util/environmentgrouplist.h +++ b/util/environmentgrouplist.h @@ -1,144 +1,145 @@ /* This file is part of KDevelop Copyright 2007 Andreas Pakulat This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #define KDEVPLATFORM_ENVIRONMENTGROUPLIST_H #include "utilexport.h" -#include + +#include class KConfig; template class QMap; class QString; class QStringList; namespace KDevelop { /** * This class manages a list of environment groups, each group containing a number * of environment variables and their values. * * The class is constructed from a KConfig object for easy usage in the plugins. * * The methods to change the environments is protected to disallow access to those methods * from plugins, only the environment widget is allowed to change them. * * Example Usage * \code * KSharedConfigPtr config = KSharedConfig::openConfig(); * EnvironmentGroupList env(config); * KConfigGroup cfg(config, "QMake Builder"); * QMap myenvVars = env.variables( cfg.readEntry("QMake Environment") ); * \endcode * * Two entries are used by this class: * "Default Environment Group" and "Environment Variables". * * "Default Environment Variables" stores the default group that should be used if the * user didn't select a group via a plugins configuration dialog. * * "Environment Variables" entry stores the actual list of * . The groupname can't contain '%' or '='. * For example, suppose that two configuration, say "release" and "debug" exist. * Then the actual contents of .kdev4 project file will be * * \code * [Environment Settings] * Default Environment Group=debug * Environment Variables=debug_PATH=/home/kde-devel/usr/bin,release_PATH=/usr/bin * \endcode * */ class KDEVPLATFORMUTIL_EXPORT EnvironmentGroupList { public: EnvironmentGroupList( const EnvironmentGroupList& rhs ); EnvironmentGroupList& operator=( const EnvironmentGroupList& rhs ); /** * Creates an a list of EnvironmentGroups from a KConfig object * @param config the KConfig object to read the environment groups from */ EnvironmentGroupList( KSharedConfigPtr config ); EnvironmentGroupList( KConfig* config ); ~EnvironmentGroupList(); /** * Creates a merged environment between the defaults specified by * \a defaultEnvironment and those saved in \a group */ QStringList createEnvironment(const QString& group, const QStringList& defaultEnvironment ) const; /** * returns the variables that are set for a given group. * This function provides read-only access to the environment * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ const QMap variables( const QString& group ) const; /** * returns the default group * The default group should be used by plugins unless the user chooses a different group * @return the name of the default group, defaults to "default" */ QString defaultGroup() const; /** * Fetch the list of known groups from the list * @return the list of groups */ QStringList groups() const; protected: EnvironmentGroupList(); /** * returns the variables that are set for a given group. * This function provides write access to the environment, so new variables can be inserted, existing ones changed or deleted * * If a non-existing group is specified this returns a new empty map and that way this function can be used to add a new group * to the list of environment groups * @param group the name of the group for which the environment should be returned * @return a map containing the environment variables for this group, or an empty map if the group doesn't exist in this list */ QMap& variables( const QString& group ); /** * Changes the default group. * @param group a new groupname, if a group of this name doesn't exist the default group is not changed */ void setDefaultGroup( const QString& group ); /** * Stores the environment groups in this list to the given KConfig object * @param config a KConfig object to which the environment settings should be stored */ void saveSettings( KConfig* config ) const; void loadSettings( KConfig* config ); void removeGroup( const QString& group ); private: class EnvironmentGroupListPrivate* const d; }; } #endif diff --git a/util/environmentselectionwidget.cpp b/util/environmentselectionwidget.cpp index 58d29d2b03..f381bb85a4 100644 --- a/util/environmentselectionwidget.cpp +++ b/util/environmentselectionwidget.cpp @@ -1,103 +1,105 @@ /* This file is part of KDevelop Copyright 2007 Dukju Ahn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "environmentselectionwidget.h" #include "environmentgrouplist.h" #include "environmentselectionmodel.h" + +#include + +#include + #include -#include -#include -#include namespace KDevelop { struct EnvironmentSelectionWidgetPrivate { KComboBox* comboBox; EnvironmentSelectionModel* model; EnvironmentSelectionWidget* owner; EnvironmentSelectionWidgetPrivate( EnvironmentSelectionWidget* _owner ) : comboBox( new KComboBox( _owner ) ) , model( new EnvironmentSelectionModel( _owner ) ) , owner( _owner ) { comboBox->setModel( model ); comboBox->setEditable( false ); } }; EnvironmentSelectionWidget::EnvironmentSelectionWidget( QWidget *parent ) : QWidget( parent ), d( new EnvironmentSelectionWidgetPrivate( this ) ) { // Taken from kdelibs/kdeui/dialogs/kconfigdialogmanager.cpp (no idea whether this is documented) // Commits d44186bce4670d2985fb6aba8dba59bbd2c4c77a and 8edc1932ecc62370d9a31836dfa9b2bd0175a293 // introduced a regression in kdelibs to fix problems running some apps against Qt4.8. Unfortunately // this fix breaks exactly our use-case, which is to store the text-value in kconfig instead of // the index even though the combobox is editable. Since that change the special combobox-code in // kconfigdialogmanager.cpp is run before check a user-property and hence our user-property is // ignored. Setting this special kcfg_property to the name of our user-property again overrides // the hardcoded combobox-behaviour - until the next one breaks things in kdelibs :| setProperty("kcfg_property", QByteArray("currentProfile")); setLayout( new QHBoxLayout( this ) ); layout()->addWidget( d->comboBox ); layout()->setMargin( 0 ); setCurrentProfile( QString() ); // select the default profile connect(d->comboBox, &QComboBox::currentTextChanged, this, &EnvironmentSelectionWidget::currentProfileChanged); } EnvironmentSelectionWidget::~EnvironmentSelectionWidget() { delete d; } QString EnvironmentSelectionWidget::currentProfile() const { return d->model->index( d->comboBox->currentIndex(), 0 ).data( Qt::EditRole ).toString(); } void EnvironmentSelectionWidget::setCurrentProfile( const QString& profile ) { d->comboBox->setCurrentIndex( d->comboBox->findData( profile, Qt::EditRole ) ); emit currentProfileChanged(profile); } void EnvironmentSelectionWidget::reconfigure() { QString selectedProfile = currentProfile(); d->model->reload(); setCurrentProfile( d->model->reloadSelectedItem( selectedProfile ) ); } QString EnvironmentSelectionWidget::effectiveProfileName() const { return d->model->index( d->comboBox->currentIndex(), 0 ).data( EnvironmentSelectionModel::EffectiveNameRole ).toString(); } EnvironmentGroupList EnvironmentSelectionWidget::environment() const { return d->model->environment(); } } diff --git a/util/environmentselectionwidget.h b/util/environmentselectionwidget.h index bd05642244..b0f58eb885 100644 --- a/util/environmentselectionwidget.h +++ b/util/environmentselectionwidget.h @@ -1,89 +1,89 @@ /* This file is part of KDevelop Copyright 2007 Dukju Ahn This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_ENVIRONMENTSELECTIONWIDGET_H #define KDEVPLATFORM_ENVIRONMENTSELECTIONWIDGET_H -#include +#include #include "utilexport.h" #include "environmentgrouplist.h" namespace KDevelop { /** * Simple combobox which allows each plugin to decide which environment * variable group to use. * * Can be used just like a KComboBox in Configuration dialogs including usage * with KConfigXT. * * @note The widget is populated and defaulted automatically. * */ class KDEVPLATFORMUTIL_EXPORT EnvironmentSelectionWidget : public QWidget { Q_OBJECT Q_PROPERTY( QString currentProfile READ currentProfile WRITE setCurrentProfile NOTIFY currentProfileChanged USER true ) public: explicit EnvironmentSelectionWidget( QWidget *parent = 0 ); ~EnvironmentSelectionWidget(); /** * @returns The currently selected environment profile name, as written to KConfigXT */ QString currentProfile() const; /** * Sets the environment profile to be written to KConfigXT and updates the combo-box. * * @param text The environment profile name to select */ void setCurrentProfile( const QString& text ); /** * @returns The currently effective environment profile name (like @ref currentProfile(), * but with empty value resolved to the default profile). */ QString effectiveProfileName() const; /** * @returns The @ref EnvironmentGroupList which has been used to populate this * widget. */ EnvironmentGroupList environment() const; public slots: /** * Makes the widget re-read its environment group list. */ void reconfigure(); Q_SIGNALS: void currentProfileChanged(const QString& currentProfile); private: struct EnvironmentSelectionWidgetPrivate* const d; friend struct EnvironmentSelectionWidgetPrivate; }; } #endif diff --git a/util/executecompositejob.h b/util/executecompositejob.h index 030a7d0e81..26d5617896 100644 --- a/util/executecompositejob.h +++ b/util/executecompositejob.h @@ -1,52 +1,53 @@ /* This file is part of KDevelop Copyright 2007-2008 Hamish Rodda This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVPLATFORM_EXECUTECOMPOSITEJOB_H #define KDEVPLATFORM_EXECUTECOMPOSITEJOB_H -#include +#include + #include "utilexport.h" template class QList; namespace KDevelop { class KDEVPLATFORMUTIL_EXPORT ExecuteCompositeJob : public KCompositeJob { Q_OBJECT public: ExecuteCompositeJob(QObject* parent = 0, const QList& jobs = {}); ~ExecuteCompositeJob(); virtual void start() override; void setAbortOnError(bool abort); public Q_SLOTS: virtual void slotResult(KJob* job) override; protected: virtual bool doKill() override; private: class ExecuteCompositeJobPrivate* const d; }; } #endif diff --git a/util/foregroundlock.cpp b/util/foregroundlock.cpp index 1c6789514d..2d76fe2dc8 100644 --- a/util/foregroundlock.cpp +++ b/util/foregroundlock.cpp @@ -1,240 +1,242 @@ /* Copyright 2010 David Nolden This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "foregroundlock.h" + +#include #include #include -#include + using namespace KDevelop; namespace { QMutex internalMutex; QMutex tryLockMutex; QMutex waitMutex; QMutex finishMutex; QWaitCondition condition; volatile QThread* holderThread = 0; volatile int recursion = 0; void lockForegroundMutexInternal() { if(holderThread == QThread::currentThread()) { // We already have the mutex ++recursion; }else{ internalMutex.lock(); Q_ASSERT(recursion == 0 && holderThread == 0); holderThread = QThread::currentThread(); recursion = 1; } } bool tryLockForegroundMutexInternal(int interval = 0) { if(holderThread == QThread::currentThread()) { // We already have the mutex ++recursion; return true; }else{ if(internalMutex.tryLock(interval)) { Q_ASSERT(recursion == 0 && holderThread == 0); holderThread = QThread::currentThread(); recursion = 1; return true; }else{ return false; } } } void unlockForegroundMutexInternal(bool duringDestruction = false) { /// Note: QThread::currentThread() might already be invalid during destruction. if (!duringDestruction) { Q_ASSERT(holderThread == QThread::currentThread()); } Q_ASSERT(recursion > 0); recursion -= 1; if(recursion == 0) { holderThread = 0; internalMutex.unlock(); } } } ForegroundLock::ForegroundLock(bool lock) : m_locked(false) { if(lock) relock(); } void KDevelop::ForegroundLock::relock() { Q_ASSERT(!m_locked); if(!QApplication::instance() || // Initialization isn't complete yet QThread::currentThread() == QApplication::instance()->thread() || // We're the main thread (deadlock might happen if we'd enter the trylock loop) holderThread == QThread::currentThread()) // We already have the foreground lock (deadlock might happen if we'd enter the trylock loop) { lockForegroundMutexInternal(); }else{ QMutexLocker lock(&tryLockMutex); while(!tryLockForegroundMutexInternal(10)) { // In case an additional event-loop was started from within the foreground, we send // events to the foreground to temporarily release the lock. class ForegroundReleaser : public DoInForeground { public: virtual void doInternal() override { // By locking the mutex, we make sure that the requester is actually waiting for the condition waitMutex.lock(); // Now we release the foreground lock TemporarilyReleaseForegroundLock release; // And signalize to the requester that we've released it condition.wakeAll(); // Allow the requester to actually wake up, by unlocking m_waitMutex waitMutex.unlock(); // Now wait until the requester is ready QMutexLocker lock(&finishMutex); } }; static ForegroundReleaser releaser; QMutexLocker lockWait(&waitMutex); QMutexLocker lockFinish(&finishMutex); QMetaObject::invokeMethod(&releaser, "doInternalSlot", Qt::QueuedConnection); // We limit the waiting time here, because sometimes it may happen that the foreground-lock is released, // and the foreground is waiting without an event-loop running. (For example through TemporarilyReleaseForegroundLock) condition.wait(&waitMutex, 30); if(tryLockForegroundMutexInternal()) { //success break; }else{ //Probably a third thread has creeped in and //got the foreground lock before us. Just try again. } } } m_locked = true; Q_ASSERT(holderThread == QThread::currentThread()); Q_ASSERT(recursion > 0); } bool KDevelop::ForegroundLock::isLockedForThread() { return QThread::currentThread() == holderThread; } bool KDevelop::ForegroundLock::tryLock() { if(tryLockForegroundMutexInternal()) { m_locked = true; return true; } return false; } void KDevelop::ForegroundLock::unlock() { Q_ASSERT(m_locked); unlockForegroundMutexInternal(); m_locked = false; } TemporarilyReleaseForegroundLock::TemporarilyReleaseForegroundLock() { Q_ASSERT(holderThread == QThread::currentThread()); m_recursion = 0; while(holderThread == QThread::currentThread()) { unlockForegroundMutexInternal(); ++m_recursion; } } TemporarilyReleaseForegroundLock::~TemporarilyReleaseForegroundLock() { for(int a = 0; a < m_recursion; ++a) lockForegroundMutexInternal(); Q_ASSERT(recursion == m_recursion && holderThread == QThread::currentThread()); } KDevelop::ForegroundLock::~ForegroundLock() { if(m_locked) unlock(); } bool KDevelop::ForegroundLock::isLocked() const { return m_locked; } namespace KDevelop { void DoInForeground::doIt() { if(QThread::currentThread() == QApplication::instance()->thread()) { // We're already in the foreground, just call the handler code doInternal(); }else{ QMutexLocker lock(&m_mutex); QMetaObject::invokeMethod(this, "doInternalSlot", Qt::QueuedConnection); m_wait.wait(&m_mutex); } } DoInForeground::~DoInForeground() { } DoInForeground::DoInForeground() { moveToThread(QApplication::instance()->thread()); } void DoInForeground::doInternalSlot() { VERIFY_FOREGROUND_LOCKED doInternal(); QMutexLocker lock(&m_mutex); m_wait.wakeAll(); } } // Important: The foreground lock has to be held by default, so lock it during static initialization static struct StaticLock { StaticLock() { lockForegroundMutexInternal(); } ~StaticLock() { unlockForegroundMutexInternal(true); } } staticLock; diff --git a/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp b/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp index 152bd5c248..c60b06b548 100644 --- a/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp +++ b/vcs/dvcs/ui/dvcsimportmetadatawidget.cpp @@ -1,100 +1,98 @@ /*************************************************************************** * Copyright 2007 Robert Gruber * * Copyright 2007 Andreas Pakulat * * * * Adapted for Git * * Copyright 2008 Evgeniy Ivanov * * * * Pimpl-ed and exported * * Copyright 2014 Maciej Poleski * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * * published by the Free Software Foundation; either version 2 of * * the License or (at your option) version 3 or any later version * * accepted by the membership of KDE e.V. (or its successor approved * * by the membership of KDE e.V.), which shall act as a proxy * * defined in Section 14 of version 3 of the license. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program. If not, see . * ***************************************************************************/ #include "dvcsimportmetadatawidget.h" -#include - #include #include "ui_dvcsimportmetadatawidget.h" class DvcsImportMetadataWidgetPrivate { friend class DvcsImportMetadataWidget; DvcsImportMetadataWidgetPrivate(Ui::DvcsImportMetadataWidget* ui) : m_ui(ui) {} ~DvcsImportMetadataWidgetPrivate() { delete m_ui; } Ui::DvcsImportMetadataWidget* m_ui; }; DvcsImportMetadataWidget::DvcsImportMetadataWidget(QWidget *parent) : KDevelop::VcsImportMetadataWidget(parent), d_ptr(new DvcsImportMetadataWidgetPrivate(new Ui::DvcsImportMetadataWidget)) { Q_D(DvcsImportMetadataWidget); d->m_ui->setupUi(this); d->m_ui->sourceLoc->setEnabled( false ); d->m_ui->sourceLoc->setMode( KFile::Directory ); connect( d->m_ui->sourceLoc, &KUrlRequester::textChanged, this, &DvcsImportMetadataWidget::changed ); connect( d->m_ui->sourceLoc, &KUrlRequester::urlSelected, this, &DvcsImportMetadataWidget::changed ); } DvcsImportMetadataWidget::~DvcsImportMetadataWidget() { delete d_ptr; } QUrl DvcsImportMetadataWidget::source() const { Q_D(const DvcsImportMetadataWidget); return d->m_ui->sourceLoc->url(); } KDevelop::VcsLocation DvcsImportMetadataWidget::destination() const { // Used for compatibility with import Q_D(const DvcsImportMetadataWidget); KDevelop::VcsLocation dest; dest.setRepositoryServer(d->m_ui->sourceLoc->url().url()); return dest; } QString DvcsImportMetadataWidget::message( ) const { return QString(); } void DvcsImportMetadataWidget::setSourceLocation( const KDevelop::VcsLocation& url ) { Q_D(const DvcsImportMetadataWidget); d->m_ui->sourceLoc->setUrl( url.localUrl() ); } void DvcsImportMetadataWidget::setSourceLocationEditable( bool enable ) { Q_D(const DvcsImportMetadataWidget); d->m_ui->sourceLoc->setEnabled( enable ); } bool DvcsImportMetadataWidget::hasValidData() const { Q_D(const DvcsImportMetadataWidget); return !d->m_ui->sourceLoc->text().isEmpty(); } diff --git a/vcs/interfaces/ipatchsource.cpp b/vcs/interfaces/ipatchsource.cpp index 02b6324a9b..7e5ac977a4 100644 --- a/vcs/interfaces/ipatchsource.cpp +++ b/vcs/interfaces/ipatchsource.cpp @@ -1,76 +1,77 @@ /* Copyright 2006 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ipatchsource.h" -#include + +#include using namespace KDevelop; QIcon IPatchSource::icon() const { return QIcon(); } IPatchReview::~IPatchReview() { } void IPatchSource::cancelReview() { } bool IPatchSource::finishReview(QList< QUrl > selection) { Q_UNUSED(selection); return true; } bool IPatchSource::canCancel() const { return false; } QMap IPatchSource::additionalSelectableFiles() const { return QMap(); } bool IPatchSource::canSelectFiles() const { return false; } QString IPatchSource::finishReviewCustomText() const { return QString(); } QWidget* IPatchSource::customWidget() const { return 0; } uint IPatchSource::depth() const { return 0; } diff --git a/vcs/models/vcsfilechangesmodel.cpp b/vcs/models/vcsfilechangesmodel.cpp index be93ae041c..e40fddcdd7 100644 --- a/vcs/models/vcsfilechangesmodel.cpp +++ b/vcs/models/vcsfilechangesmodel.cpp @@ -1,222 +1,222 @@ /* This file is part of KDevelop Copyright 2010 Aleix Pol Split into separate class Copyright 2011 Andrey Batyiev This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcsfilechangesmodel.h" #include +#include #include -#include #include #include #include namespace KDevelop { static QString stateToString(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return i18nc("file was added to versioncontrolsystem", "Added"); case KDevelop::VcsStatusInfo::ItemDeleted: return i18nc("file was deleted from versioncontrolsystem", "Deleted"); case KDevelop::VcsStatusInfo::ItemHasConflicts: return i18nc("file is confilicting (versioncontrolsystem)", "Has Conflicts"); case KDevelop::VcsStatusInfo::ItemModified: return i18nc("version controlled file was modified", "Modified"); case KDevelop::VcsStatusInfo::ItemUpToDate: return i18nc("file is up to date in versioncontrolsystem", "Up To Date"); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return i18nc("file is not known to versioncontrolsystem", "Unknown"); } return i18nc("Unknown VCS file status, probably a backend error", "?"); } static QIcon stateToIcon(KDevelop::VcsStatusInfo::State state) { switch(state) { case KDevelop::VcsStatusInfo::ItemAdded: return QIcon::fromTheme("vcs-added"); case KDevelop::VcsStatusInfo::ItemDeleted: return QIcon::fromTheme("vcs-removed"); case KDevelop::VcsStatusInfo::ItemHasConflicts: return QIcon::fromTheme("vcs-conflicting"); case KDevelop::VcsStatusInfo::ItemModified: return QIcon::fromTheme("vcs-locally-modified"); case KDevelop::VcsStatusInfo::ItemUpToDate: return QIcon::fromTheme("vcs-normal"); case KDevelop::VcsStatusInfo::ItemUnknown: case KDevelop::VcsStatusInfo::ItemUserState: return QIcon::fromTheme("unknown"); } return QIcon::fromTheme("dialog-error"); } class VcsFileChangesModelPrivate { public: bool allowSelection; }; VcsFileChangesModel::VcsFileChangesModel(QObject *parent, bool allowSelection) : QStandardItemModel(parent), d(new VcsFileChangesModelPrivate) { setColumnCount(2); setHeaderData(0, Qt::Horizontal, i18n("Filename")); setHeaderData(1, Qt::Horizontal, i18n("Status")); d->allowSelection = allowSelection; } int VcsFileChangesModel::updateState(QStandardItem *parent, const KDevelop::VcsStatusInfo &status) { QStandardItem* it1=fileItemForUrl(parent, status.url()); QStandardItem* itStatus; if(status.state()==VcsStatusInfo::ItemUnknown || status.state()==VcsStatusInfo::ItemUpToDate) { if(it1) parent->removeRow(it1->row()); return -1; } else { if(!it1) { QString path = ICore::self()->projectController()->prettyFileName(status.url(), KDevelop::IProjectController::FormatPlain); QMimeType mime = status.url().isLocalFile() ? QMimeDatabase().mimeTypeForFile(status.url().toLocalFile(), QMimeDatabase::MatchExtension) : QMimeDatabase().mimeTypeForUrl(status.url()); QIcon icon = QIcon::fromTheme(mime.iconName()); it1 = new QStandardItem(icon, path); itStatus = new QStandardItem; if(d->allowSelection) { it1->setCheckable(true); it1->setCheckState(status.state() == VcsStatusInfo::ItemUnknown ? Qt::Unchecked : Qt::Checked); } parent->appendRow(QList() << it1 << itStatus); } else { QStandardItem *parent = it1->parent(); if(parent == 0) parent = invisibleRootItem(); itStatus = parent->child(it1->row(), 1); } QString text = stateToString(status.state()); if(itStatus->text()!=text) { itStatus->setText(text); itStatus->setIcon(stateToIcon(status.state())); } it1->setData(qVariantFromValue(status), VcsStatusInfoRole); return it1->row(); } } QStandardItem* VcsFileChangesModel::fileItemForUrl(QStandardItem* parent, const QUrl& url) { for(int i=0; irowCount(); i++) { QStandardItem* curr=parent->child(i); if(curr->data(VcsStatusInfoRole).value().url()==url) { return curr; } } return 0; } QList VcsFileChangesModel::checkedStatuses(QStandardItem *parent) const { QList ret; if(!d->allowSelection) return ret; for(int i = 0; i < parent->rowCount(); i++) { QStandardItem* item = parent->child(i); if(item->checkState() == Qt::Checked) { ret << statusInfo(item); } } return ret; } void VcsFileChangesModel::setAllChecked(bool checked) { if(!d->allowSelection) return; QStandardItem* parent = invisibleRootItem(); for(int i = 0; i < parent->rowCount(); i++) { QStandardItem* item = parent->child(i); item->setCheckState(checked ? Qt::Checked : Qt::Unchecked); } } QList VcsFileChangesModel::checkedUrls(QStandardItem *parent) const { QList ret; for(int i = 0; i < parent->rowCount(); i++) { QStandardItem* item = parent->child(i); if(!d->allowSelection || item->checkState() == Qt::Checked) { ret << statusInfo(item).url(); } } return ret; } QList VcsFileChangesModel::urls(QStandardItem *parent) const { QList ret; for(int i = 0; i < parent->rowCount(); i++) { ret << statusInfo(parent->child(i)).url(); } return ret; } void VcsFileChangesModel::checkUrls(QStandardItem *parent, const QList& urls) const { QSet urlSet(urls.toSet()); if(!d->allowSelection) return; for(int i = 0; i < parent->rowCount(); i++) { QStandardItem* item = parent->child(i); item->setCheckState(urlSet.contains(statusInfo(item).url()) ? Qt::Checked : Qt::Unchecked); } } void VcsFileChangesModel::setIsCheckbable(bool checkable) { d->allowSelection = checkable; } } diff --git a/vcs/vcspluginhelper.cpp b/vcs/vcspluginhelper.cpp index a82b7205c0..653a3be6af 100644 --- a/vcs/vcspluginhelper.cpp +++ b/vcs/vcspluginhelper.cpp @@ -1,522 +1,509 @@ /*************************************************************************** * Copyright 2008 Andreas Pakulat * * Copyright 2010 Aleix Pol Gonzalez * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vcspluginhelper.h" #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 #include +#include #include -#include -#include #include #include +#include +#include #include +#include #include #include -#include -#include +#include +#include #include #include -#include "interfaces/idistributedversioncontrol.h" -#include #include +#include +#include #include #include #include -#include -#include -#include -#include +#include "interfaces/idistributedversioncontrol.h" #include "vcsstatusinfo.h" -#include -#include +#include "vcsevent.h" #include "widgets/vcsdiffpatchsources.h" #include "widgets/flexibleaction.h" -#include -#include "vcsevent.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include namespace KDevelop { struct VcsPluginHelper::VcsPluginHelperPrivate { IPlugin * plugin; IBasicVersionControl * vcs; QList ctxUrls; QAction* commitAction; QAction* addAction; QAction* updateAction; QAction* historyAction; QAction* annotationAction; QAction* diffToBaseAction; QAction* revertAction; QAction* diffForRevAction; QAction* diffForRevGlobalAction; QAction* pushAction; QAction* pullAction; void createActions(VcsPluginHelper* parent) { commitAction = new QAction(QIcon::fromTheme("svn-commit"), i18n("Commit..."), parent); updateAction = new QAction(QIcon::fromTheme("svn-update"), i18n("Update"), parent); addAction = new QAction(QIcon::fromTheme("list-add"), i18n("Add"), parent); diffToBaseAction = new QAction(QIcon::fromTheme("text-x-patch"), i18n("Show Differences..."), parent); revertAction = new QAction(QIcon::fromTheme("archive-remove"), i18n("Revert"), parent); historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("History..."), parent); annotationAction = new QAction(QIcon::fromTheme("user-properties"), i18n("Annotation..."), parent); diffForRevAction = new QAction(QIcon::fromTheme("text-x-patch"), i18n("Show Diff..."), parent); diffForRevGlobalAction = new QAction(QIcon::fromTheme("text-x-patch"), i18n("Show Diff (all files)..."), parent); pushAction = new QAction(QIcon::fromTheme("arrow-up-double"), i18n("Push"), parent); pullAction = new QAction(QIcon::fromTheme("arrow-down-double"), i18n("Pull"), parent); connect(commitAction, &QAction::triggered, parent, &VcsPluginHelper::commit); connect(addAction, &QAction::triggered, parent, &VcsPluginHelper::add); connect(updateAction, &QAction::triggered, parent, &VcsPluginHelper::update); connect(diffToBaseAction, &QAction::triggered, parent, &VcsPluginHelper::diffToBase); connect(revertAction, &QAction::triggered, parent, &VcsPluginHelper::revert); connect(historyAction, &QAction::triggered, parent, [=] { parent->history(); }); connect(annotationAction, &QAction::triggered, parent, &VcsPluginHelper::annotation); connect(diffForRevAction, &QAction::triggered, parent, static_cast(&VcsPluginHelper::diffForRev)); connect(diffForRevGlobalAction, &QAction::triggered, parent, &VcsPluginHelper::diffForRevGlobal); connect(pullAction, &QAction::triggered, parent, &VcsPluginHelper::pull); connect(pushAction, &QAction::triggered, parent, &VcsPluginHelper::push); } bool allLocalFiles(const QList& urls) { bool ret=true; foreach(const QUrl &url, urls) { QFileInfo info(url.toLocalFile()); ret &= info.isFile(); } return ret; } QMenu* createMenu() { bool allVersioned=true; foreach(const QUrl &url, ctxUrls) { allVersioned=allVersioned && vcs->isVersionControlled(url); if(!allVersioned) break; } QMenu* menu = new QMenu(vcs->name()); menu->setIcon(QIcon::fromTheme(ICore::self()->pluginController()->pluginInfo(plugin).iconName())); menu->addAction(commitAction); if(plugin->extension()) { menu->addAction(pushAction); menu->addAction(pullAction); } else { menu->addAction(updateAction); } menu->addSeparator(); menu->addAction(addAction); menu->addAction(revertAction); menu->addSeparator(); menu->addAction(historyAction); menu->addAction(annotationAction); menu->addAction(diffToBaseAction); const bool singleVersionedFile = ctxUrls.count() == 1 && allVersioned; historyAction->setEnabled(singleVersionedFile); annotationAction->setEnabled(singleVersionedFile && allLocalFiles(ctxUrls)); diffToBaseAction->setEnabled(singleVersionedFile); commitAction->setEnabled(singleVersionedFile); return menu; } }; VcsPluginHelper::VcsPluginHelper(KDevelop::IPlugin* parent, KDevelop::IBasicVersionControl* vcs) : QObject(parent) , d(new VcsPluginHelperPrivate()) { Q_ASSERT(vcs); Q_ASSERT(parent); d->plugin = parent; d->vcs = vcs; d->createActions(this); } VcsPluginHelper::~VcsPluginHelper() {} void VcsPluginHelper::addContextDocument(const QUrl &url) { d->ctxUrls.append(url); } void VcsPluginHelper::disposeEventually(KTextEditor::View *, bool dont) { if ( ! dont ) { deleteLater(); } } void VcsPluginHelper::disposeEventually(KTextEditor::Document *) { deleteLater(); } void VcsPluginHelper::setupFromContext(Context* context) { static const QVector contextTypes = {Context::ProjectItemContext, Context::FileContext, Context::EditorContext}; if (contextTypes.contains(context->type())) { d->ctxUrls = context->urls(); } else { d->ctxUrls.clear(); } } QList VcsPluginHelper::contextUrlList() const { return d->ctxUrls; } QMenu* VcsPluginHelper::commonActions() { /* TODO: the following logic to determine which actions need to be enabled * or disabled does not work properly. What needs to be implemented is that * project items that are vc-controlled enable all except add, project * items that are not vc-controlled enable add action. For urls that cannot * be made into a project item, or if the project has no associated VC * plugin we need to check whether a VC controls the parent dir, if we have * one we assume the urls can be added but are not currently controlled. If * the url is already version controlled then just enable all except add */ return d->createMenu(); } #define EXECUTE_VCS_METHOD( method ) \ d->plugin->core()->runController()->registerJob( d->vcs-> method ( d->ctxUrls ) ) #define SINGLEURL_SETUP_VARS \ KDevelop::IBasicVersionControl* iface = d->vcs;\ const QUrl &url = d->ctxUrls.front(); void VcsPluginHelper::revert() { VcsJob* job=d->vcs->revert(d->ctxUrls); connect(job, &VcsJob::finished, this, &VcsPluginHelper::revertDone); foreach(const QUrl &url, d->ctxUrls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc && doc->textDocument()) { KTextEditor::ModificationInterface* modif = dynamic_cast(doc->textDocument()); if (modif) { modif->setModifiedOnDiskWarning(false); } doc->textDocument()->setModified(false); } } job->setProperty("urls", QVariant::fromValue(d->ctxUrls)); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::revertDone(KJob* job) { QTimer* modificationTimer = new QTimer; modificationTimer->setInterval(100); connect(modificationTimer, &QTimer::timeout, this, &VcsPluginHelper::delayedModificationWarningOn); connect(modificationTimer, &QTimer::timeout, modificationTimer, &QTimer::deleteLater); modificationTimer->setProperty("urls", job->property("urls")); modificationTimer->start(); } void VcsPluginHelper::delayedModificationWarningOn() { QObject* timer = sender(); QList urls = timer->property("urls").value>(); foreach(const QUrl &url, urls) { IDocument* doc=ICore::self()->documentController()->documentForUrl(url); if(doc) { doc->reload(); KTextEditor::ModificationInterface* modif=dynamic_cast(doc->textDocument()); modif->setModifiedOnDiskWarning(true); } } } void VcsPluginHelper::diffJobFinished(KJob* job) { KDevelop::VcsJob* vcsjob = qobject_cast(job); Q_ASSERT(vcsjob); if (vcsjob->status() == KDevelop::VcsJob::JobSucceeded) { KDevelop::VcsDiff d = vcsjob->fetchResults().value(); if(d.isEmpty()) KMessageBox::information(ICore::self()->uiController()->activeMainWindow(), i18n("There are no differences."), i18n("VCS support")); else { VCSDiffPatchSource* patch=new VCSDiffPatchSource(d); showVcsDiff(patch); } } else { KMessageBox::error(ICore::self()->uiController()->activeMainWindow(), vcsjob->errorString(), i18n("Unable to get difference.")); } } void VcsPluginHelper::diffToBase() { SINGLEURL_SETUP_VARS ICore::self()->documentController()->saveAllDocuments(); VCSDiffPatchSource* patch =new VCSDiffPatchSource(new VCSStandardDiffUpdater(iface, url)); showVcsDiff(patch); } void VcsPluginHelper::diffForRev() { if (d->ctxUrls.isEmpty()) { return; } diffForRev(d->ctxUrls.first()); } void VcsPluginHelper::diffForRevGlobal() { if (d->ctxUrls.isEmpty()) { return; } QUrl url = d->ctxUrls.first(); IProject* project = ICore::self()->projectController()->findProjectForUrl( url ); if( project ) { url = project->path().toUrl(); } diffForRev(url); } void VcsPluginHelper::diffForRev(const QUrl& url) { QAction* action = qobject_cast( sender() ); Q_ASSERT(action); Q_ASSERT(action->data().canConvert()); VcsRevision rev = action->data().value(); ICore::self()->documentController()->saveAllDocuments(); VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = d->vcs->diff(url, prev, rev ); connect(job, &VcsJob::finished, this, &VcsPluginHelper::diffJobFinished); d->plugin->core()->runController()->registerJob(job); } void VcsPluginHelper::history(const VcsRevision& rev) { SINGLEURL_SETUP_VARS QDialog* dlg = new QDialog(ICore::self()->uiController()->activeMainWindow()); dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setWindowTitle(i18nc("%1: path or URL, %2: name of a version control system", "%2 History (%1)", url.toDisplayString(QUrl::PreferLocalFile), iface->name())); QVBoxLayout *mainLayout = new QVBoxLayout(dlg); KDevelop::VcsEventWidget* logWidget = new KDevelop::VcsEventWidget(url, rev, iface, dlg); mainLayout->addWidget(logWidget); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsPluginHelper::annotation() { SINGLEURL_SETUP_VARS KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc) doc = ICore::self()->documentController()->openDocument(url); KTextEditor::AnnotationInterface* annotateiface = qobject_cast(doc->textDocument()); KTextEditor::AnnotationViewInterface* viewiface = qobject_cast(doc->activeTextView()); if (viewiface && viewiface->isAnnotationBorderVisible()) { viewiface->setAnnotationBorderVisible(false); return; } if (doc && doc->textDocument() && iface) { KDevelop::VcsJob* job = iface->annotate(url); if( !job ) { qWarning() << "Couldn't create annotate job for:" << url << "with iface:" << iface << dynamic_cast( iface ); return; } QColor foreground(Qt::black); QColor background(Qt::white); if (KTextEditor::View* view = doc->activeTextView()) { KTextEditor::Attribute::Ptr style = view->defaultStyleAttribute(KTextEditor::dsNormal); foreground = style->foreground().color(); if (style->hasProperty(QTextFormat::BackgroundBrush)) { background = style->background().color(); } } if (annotateiface && viewiface) { KDevelop::VcsAnnotationModel* model = new KDevelop::VcsAnnotationModel(job, url, doc->textDocument(), foreground, background); annotateiface->setAnnotationModel(model); viewiface->setAnnotationBorderVisible(true); // can't use new signal slot syntax here, AnnotationInterface is not a QObject connect(doc->activeTextView(), SIGNAL(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int)), this, SLOT(annotationContextMenuAboutToShow(KTextEditor::View*,QMenu*,int))); } else { KMessageBox::error(0, i18n("Cannot display annotations, missing interface KTextEditor::AnnotationInterface for the editor.")); delete job; } } else { KMessageBox::error(0, i18n("Cannot execute annotate action because the " "document was not found, or was not a text document:\n%1", url.toDisplayString(QUrl::PreferLocalFile))); } } class CopyFunction : public AbstractFunction { public: CopyFunction(const QString& tocopy) : m_tocopy(tocopy) {} void operator()() override { QApplication::clipboard()->setText(m_tocopy); } private: QString m_tocopy; }; class HistoryFunction : public AbstractFunction { public: HistoryFunction(VcsPluginHelper* helper, const VcsRevision& rev) : m_helper(helper), m_rev(rev) {} void operator()() override { m_helper->history(m_rev); } private: VcsPluginHelper* m_helper; VcsRevision m_rev; }; void VcsPluginHelper::annotationContextMenuAboutToShow( KTextEditor::View* view, QMenu* menu, int line ) { KTextEditor::AnnotationInterface* annotateiface = qobject_cast(view->document()); VcsAnnotationModel* model = qobject_cast( annotateiface->annotationModel() ); Q_ASSERT(model); VcsRevision rev = model->revisionForLine(line); // check if the user clicked on a row without revision information if (rev.revisionType() == VcsRevision::Invalid) { // in this case, do not action depending on revision informations return; } d->diffForRevAction->setData(QVariant::fromValue(rev)); d->diffForRevGlobalAction->setData(QVariant::fromValue(rev)); menu->addSeparator(); menu->addAction(d->diffForRevAction); menu->addAction(d->diffForRevGlobalAction); menu->addAction(new FlexibleAction(QIcon::fromTheme("edit-copy"), i18n("Copy Revision"), new CopyFunction(rev.revisionValue().toString()), menu)); menu->addAction(new FlexibleAction(QIcon::fromTheme("view-history"), i18n("History..."), new HistoryFunction(this, rev), menu)); } void VcsPluginHelper::update() { EXECUTE_VCS_METHOD(update); } void VcsPluginHelper::add() { EXECUTE_VCS_METHOD(add); } void VcsPluginHelper::commit() { Q_ASSERT(!d->ctxUrls.isEmpty()); ICore::self()->documentController()->saveAllDocuments(); QUrl url = d->ctxUrls.first(); // We start the commit UI no matter whether there is real differences, as it can also be used to commit untracked files VCSCommitDiffPatchSource* patchSource = new VCSCommitDiffPatchSource(new VCSStandardDiffUpdater(d->vcs, url)); bool ret = showVcsDiff(patchSource); if(!ret) { VcsCommitDialog *commitDialog = new VcsCommitDialog(patchSource); commitDialog->setCommitCandidates(patchSource->infos()); commitDialog->exec(); } } void VcsPluginHelper::push() { foreach(const QUrl &url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->push(url, VcsLocation()); ICore::self()->runController()->registerJob(job); } } void VcsPluginHelper::pull() { foreach(const QUrl &url, d->ctxUrls) { VcsJob* job = d->plugin->extension()->pull(VcsLocation(), url); ICore::self()->runController()->registerJob(job); } } } diff --git a/vcs/widgets/vcscommitdialog.cpp b/vcs/widgets/vcscommitdialog.cpp index ecf53baab4..f115d4977d 100644 --- a/vcs/widgets/vcscommitdialog.cpp +++ b/vcs/widgets/vcscommitdialog.cpp @@ -1,116 +1,104 @@ /*************************************************************************** * Copyright 2007 Dukju Ahn * * Copyright 2008 Evgeniy Ivanov * * Copyright 2011 Andrey Batyiev * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "vcscommitdialog.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include +#include +#include #include #include #include #include #include #include #include "../vcsjob.h" #include "../interfaces/ibasicversioncontrol.h" #include "../interfaces/idistributedversioncontrol.h" #include "../interfaces/icentralizedversioncontrol.h" #include "../vcsstatusinfo.h" #include "../models/vcsfilechangesmodel.h" #include "ui_vcscommitdialog.h" #include -#include -#include -#include -#include namespace KDevelop { class VcsCommitDialogPrivate { public: Ui::VcsCommitDialog ui; IPatchSource* m_patchSource; VcsFileChangesModel* m_model; }; VcsCommitDialog::VcsCommitDialog( IPatchSource *patchSource, QWidget *parent ) : QDialog( parent ), d(new VcsCommitDialogPrivate()) { auto mainWidget = new QWidget(this); d->ui.setupUi(mainWidget); QWidget *customWidget = patchSource->customWidget(); if( customWidget ) { d->ui.gridLayout->addWidget( customWidget, 0, 0, 1, 2 ); } auto okButton = d->ui.buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(d->ui.buttonBox, &QDialogButtonBox::accepted, this, &VcsCommitDialog::accept); connect(d->ui.buttonBox, &QDialogButtonBox::rejected, this, &VcsCommitDialog::reject); d->m_patchSource = patchSource; d->m_model = new VcsFileChangesModel( this, true ); d->ui.files->setModel( d->m_model ); } VcsCommitDialog::~VcsCommitDialog() { delete d; } void VcsCommitDialog::setRecursive( bool recursive ) { d->ui.recursiveChk->setChecked( recursive ); } void VcsCommitDialog::setCommitCandidates( const QList& statuses ) { foreach( const VcsStatusInfo& info, statuses ) { d->m_model->updateState( info ); } } bool VcsCommitDialog::recursive() const { return d->ui.recursiveChk->isChecked(); } void VcsCommitDialog::ok() { if( d->m_patchSource->finishReview( d->m_model->checkedUrls() ) ) { deleteLater(); } } void VcsCommitDialog::cancel() { d->m_patchSource->cancelReview(); } } diff --git a/vcs/widgets/vcsdiffpatchsources.cpp b/vcs/widgets/vcsdiffpatchsources.cpp index 1dcae0e4c5..d982fc926a 100644 --- a/vcs/widgets/vcsdiffpatchsources.cpp +++ b/vcs/widgets/vcsdiffpatchsources.cpp @@ -1,321 +1,324 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "vcsdiffpatchsources.h" -#include -#include +#include +#include #include -#include -#include +#include +#include + +#include +#include +#include +#include + +#include #include -#include +#include +#include #include -#include "vcsjob.h" +#include #include "vcsdiff.h" +#include "vcsjob.h" #include "../debug.h" -#include -#include -#include -#include -#include -#include using namespace KDevelop; VCSCommitDiffPatchSource::VCSCommitDiffPatchSource(VCSDiffUpdater* updater) : VCSDiffPatchSource(updater), m_vcs(updater->vcs()) { Q_ASSERT(m_vcs); m_commitMessageWidget = new QWidget; QVBoxLayout* layout = new QVBoxLayout(m_commitMessageWidget.data()); m_commitMessageEdit = new KTextEdit; m_commitMessageEdit.data()->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); m_commitMessageEdit.data()->setLineWrapMode(QTextEdit::NoWrap); m_vcs->setupCommitMessageEditor(updater->url(), m_commitMessageEdit.data()); QHBoxLayout* titleLayout = new QHBoxLayout; titleLayout->addWidget(new QLabel(i18n("Commit Message:"))); m_oldMessages = new KComboBox(m_commitMessageWidget.data()); m_oldMessages->addItem(i18n("Old Messages")); foreach(QString message, oldMessages()) m_oldMessages->addItem(message, message); m_oldMessages->setMaximumWidth(200); connect(m_oldMessages, static_cast(&KComboBox::currentIndexChanged), this, &VCSCommitDiffPatchSource::oldMessageChanged); titleLayout->addWidget(m_oldMessages); layout->addLayout(titleLayout); layout->addWidget(m_commitMessageEdit.data()); connect(this, &VCSCommitDiffPatchSource::reviewCancelled, this, &VCSCommitDiffPatchSource::addMessageToHistory); connect(this, &VCSCommitDiffPatchSource::reviewFinished, this, &VCSCommitDiffPatchSource::addMessageToHistory); } QStringList VCSCommitDiffPatchSource::oldMessages() const { KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); return vcsGroup.readEntry("OldCommitMessages", QStringList()); } void VCSCommitDiffPatchSource::addMessageToHistory(const QString& message) { if(ICore::self()->shuttingDown()) return; KConfigGroup vcsGroup(ICore::self()->activeSession()->config(), "VCS"); const int maxMessages = 10; QStringList oldMessages = vcsGroup.readEntry("OldCommitMessages", QStringList()); oldMessages.removeAll(message); oldMessages.push_front(message); oldMessages = oldMessages.mid(0, maxMessages); vcsGroup.writeEntry("OldCommitMessages", oldMessages); } void VCSCommitDiffPatchSource::oldMessageChanged(QString text) { if(m_oldMessages->currentIndex() != 0) { m_oldMessages->setCurrentIndex(0); m_commitMessageEdit.data()->setText(text); } } void VCSCommitDiffPatchSource::jobFinished(KJob *job) { if (!job || job->error() != 0 ) { QString details = job ? job->errorText() : QString(); if (details.isEmpty()) { //errorText may be empty details = i18n("For more detailed information please see the Version Control toolview"); } KMessageBox::detailedError(0, i18n("Unable to commit"), details, i18n("Commit unsuccessful")); } deleteLater(); } VCSDiffPatchSource::VCSDiffPatchSource(VCSDiffUpdater* updater) : m_updater(updater) { update(); KDevelop::IBasicVersionControl* vcs = m_updater->vcs(); QUrl url = m_updater->url(); QScopedPointer statusJob(vcs->status(QList() << url)); QVariant varlist; if( statusJob->exec() && statusJob->status() == VcsJob::JobSucceeded ) { varlist = statusJob->fetchResults(); foreach( const QVariant &var, varlist.toList() ) { VcsStatusInfo info = var.value(); m_infos += info; if(info.state()!=VcsStatusInfo::ItemUpToDate) m_selectable[info.url()] = info.state(); } } else qCDebug(VCS) << "Couldn't get status for urls: " << url; } VCSDiffPatchSource::VCSDiffPatchSource(const KDevelop::VcsDiff& diff) : m_updater(0) { updateFromDiff(diff); } VCSDiffPatchSource::~VCSDiffPatchSource() { QFile::remove(m_file.toLocalFile()); delete m_updater; } QUrl VCSDiffPatchSource::baseDir() const { return m_base; } QUrl VCSDiffPatchSource::file() const { return m_file; } QString VCSDiffPatchSource::name() const { return m_name; } uint VCSDiffPatchSource::depth() const { return m_depth; } void VCSDiffPatchSource::updateFromDiff(VcsDiff vcsdiff) { if(!m_file.isValid()) { QTemporaryFile temp2(QDir::tempPath() + QLatin1String("/kdevelop_XXXXXX.patch")); temp2.setAutoRemove(false); temp2.open(); QTextStream t2(&temp2); t2 << vcsdiff.diff(); qCDebug(VCS) << "filename:" << temp2.fileName(); m_file = QUrl::fromLocalFile(temp2.fileName()); temp2.close(); }else{ QFile file(m_file.path()); file.open(QIODevice::WriteOnly); QTextStream t2(&file); t2 << vcsdiff.diff(); } qCDebug(VCS) << "using file" << m_file << vcsdiff.diff() << "base" << vcsdiff.baseDiff(); m_name = "VCS Diff"; m_base = vcsdiff.baseDiff(); m_depth = vcsdiff.depth(); emit patchChanged(); } void VCSDiffPatchSource::update() { if(!m_updater) return; updateFromDiff(m_updater->update()); } VCSCommitDiffPatchSource::~VCSCommitDiffPatchSource() { delete m_commitMessageWidget.data(); } bool VCSCommitDiffPatchSource::canSelectFiles() const { return true; } QMap< QUrl, KDevelop::VcsStatusInfo::State> VCSDiffPatchSource::additionalSelectableFiles() const { return m_selectable; } QWidget* VCSCommitDiffPatchSource::customWidget() const { return m_commitMessageWidget.data(); } QString VCSCommitDiffPatchSource::finishReviewCustomText() const { return i18nc("@action:button To make a commit", "Commit"); } bool VCSCommitDiffPatchSource::canCancel() const { return true; } void VCSCommitDiffPatchSource::cancelReview() { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); emit reviewCancelled(message); deleteLater(); } bool VCSCommitDiffPatchSource::finishReview(QList< QUrl > selection) { QString message; if (m_commitMessageEdit) message = m_commitMessageEdit.data()->toPlainText(); qCDebug(VCS) << "Finishing with selection" << selection; QString files; foreach(const QUrl& url, selection) files += "

  • "+ICore::self()->projectController()->prettyFileName(url, KDevelop::IProjectController::FormatPlain) + "
  • "; QString text = i18n("Files will be committed:\n
      %1
    \nWith message:\n
    %2
    ", files, message); int res = KMessageBox::warningContinueCancel(0, text, i18n("About to commit to repository"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), "ShouldAskConfirmCommit"); if (res != KMessageBox::Continue) { return false; } emit reviewFinished(message, selection); VcsJob* job = m_vcs->commit(message, selection, KDevelop::IBasicVersionControl::NonRecursive); if (!job) { return false; } connect (job, &VcsJob::finished, this, &VCSCommitDiffPatchSource::jobFinished); ICore::self()->runController()->registerJob(job); return true; } static KDevelop::IPatchSource::Ptr currentShownDiff; bool showVcsDiff(IPatchSource* vcsDiff) { KDevelop::IPatchReview* patchReview = ICore::self()->pluginController()->extensionForPlugin("org.kdevelop.IPatchReview"); //Only give one VCS diff at a time to the patch review plugin delete currentShownDiff; currentShownDiff = vcsDiff; if( patchReview ) { patchReview->startReview(currentShownDiff); return true; } else { qWarning() << "Patch review plugin not found"; return false; } } VcsDiff VCSStandardDiffUpdater::update() const { QScopedPointer diffJob(m_vcs->diff(m_url, KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Base), KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Working))); const bool success = diffJob ? diffJob->exec() : false; if (!success) { KMessageBox::error(0, i18n("Could not create a patch for the current version.")); return {}; } return diffJob->fetchResults().value(); } VCSStandardDiffUpdater::VCSStandardDiffUpdater(IBasicVersionControl* vcs, QUrl url) : m_vcs(vcs), m_url(url) { } VCSStandardDiffUpdater::~VCSStandardDiffUpdater() { } VCSDiffUpdater::~VCSDiffUpdater() { } diff --git a/vcs/widgets/vcsdiffpatchsources.h b/vcs/widgets/vcsdiffpatchsources.h index fb7cbfd8b6..6ca06f891b 100644 --- a/vcs/widgets/vcsdiffpatchsources.h +++ b/vcs/widgets/vcsdiffpatchsources.h @@ -1,137 +1,133 @@ /* Copyright 2009 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * This is an internal header */ #ifndef KDEVPLATFORM_VCSDIFFPATCHSOURCES_H #define KDEVPLATFORM_VCSDIFFPATCHSOURCES_H -#include -#include -#include - #include #include "vcs/vcsstatusinfo.h" #include "vcs/vcsjob.h" #include class KTextEdit; class KComboBox; namespace KDevelop { class VcsCommitDialog; class IBasicVersionControl; class VcsDiff; } class QWidget; class VCSDiffUpdater { public: virtual ~VCSDiffUpdater(); virtual KDevelop::VcsDiff update() const = 0; virtual KDevelop::IBasicVersionControl* vcs() const = 0; virtual QUrl url() const = 0; }; class KDEVPLATFORMVCS_EXPORT VCSStandardDiffUpdater : public VCSDiffUpdater { public: VCSStandardDiffUpdater(KDevelop::IBasicVersionControl* vcs, QUrl url); virtual ~VCSStandardDiffUpdater(); virtual KDevelop::VcsDiff update() const override; virtual KDevelop::IBasicVersionControl* vcs() const override { return m_vcs; } virtual QUrl url() const override { return m_url; } private: KDevelop::IBasicVersionControl* m_vcs; QUrl m_url; }; class KDEVPLATFORMVCS_EXPORT VCSDiffPatchSource : public KDevelop::IPatchSource { public: /// The ownership of the updater is taken VCSDiffPatchSource(VCSDiffUpdater* updater); VCSDiffPatchSource(const KDevelop::VcsDiff& diff); virtual ~VCSDiffPatchSource(); virtual QUrl baseDir() const override ; virtual QUrl file() const override ; virtual QString name() const override ; virtual uint depth() const override ; virtual void update() override ; virtual bool isAlreadyApplied() const override { return true; } QMap additionalSelectableFiles() const override ; QUrl m_base, m_file; QString m_name; VCSDiffUpdater* m_updater; QList m_infos; QMap m_selectable; private: void updateFromDiff(KDevelop::VcsDiff diff); uint m_depth = 0; }; class KDEVPLATFORMVCS_EXPORT VCSCommitDiffPatchSource : public VCSDiffPatchSource { Q_OBJECT public: /// The ownership of the updater is taken VCSCommitDiffPatchSource(VCSDiffUpdater* updater); ~VCSCommitDiffPatchSource() ; QStringList oldMessages() const; virtual bool canSelectFiles() const override ; virtual QWidget* customWidget() const override ; virtual QString finishReviewCustomText() const override ; virtual bool canCancel() const override; virtual void cancelReview() override; virtual bool finishReview(QList< QUrl > selection) override ; QList infos() const { return m_infos; } Q_SIGNALS: void reviewFinished(QString message, QList selection); void reviewCancelled(QString message); public: QPointer m_commitMessageWidget; QPointer m_commitMessageEdit; KDevelop::IBasicVersionControl* m_vcs; KComboBox* m_oldMessages; public slots: void addMessageToHistory(const QString& message); void oldMessageChanged(QString); void jobFinished(KJob*); }; ///Sends the diff to the patch-review plugin. ///Returns whether the diff was shown successfully. bool KDEVPLATFORMVCS_EXPORT showVcsDiff(KDevelop::IPatchSource* vcsDiff); #endif // KDEVPLATFORM_VCSDIFFPATCHSOURCES_H diff --git a/vcs/widgets/vcsdiffwidget.cpp b/vcs/widgets/vcsdiffwidget.cpp index 79ecbf8d43..9c0481d2c9 100644 --- a/vcs/widgets/vcsdiffwidget.cpp +++ b/vcs/widgets/vcsdiffwidget.cpp @@ -1,111 +1,108 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcsdiffwidget.h" -#include - #include -#include #include #include #include "../vcsjob.h" #include "../vcsrevision.h" #include "../vcsdiff.h" #include "../debug.h" #include "ui_vcsdiffwidget.h" #include "vcsdiffpatchsources.h" namespace KDevelop { class VcsDiffWidgetPrivate { public: Ui::VcsDiffWidget* m_ui; VcsJob* m_job; VcsDiffWidget* q; VcsDiffWidgetPrivate(VcsDiffWidget* _q) : q(_q) { } void diffReady( KDevelop::VcsJob* job ) { if( job != m_job ) return; KDevelop::VcsDiff diff = m_job->fetchResults().value(); // Try using the patch-review plugin if possible VCSDiffPatchSource* patch = new VCSDiffPatchSource(diff); if(showVcsDiff(patch)) { q->deleteLater(); return; }else{ delete patch; } qCDebug(VCS) << "diff:" << diff.leftTexts().count(); foreach( const KDevelop::VcsLocation &l, diff.leftTexts().keys() ) { qCDebug(VCS) << "diff:" << l.localUrl() << l.repositoryServer(); } qCDebug(VCS) << "diff:" << diff.diff(); qCDebug(VCS) << "diff:" << diff.type(); qCDebug(VCS) << "diff:" << diff.contentType(); m_ui->diffDisplay->setPlainText( diff.diff() ); m_ui->diffDisplay->setReadOnly( true ); } }; VcsDiffWidget::VcsDiffWidget( KDevelop::VcsJob* job, QWidget* parent ) : QWidget( parent ), d(new VcsDiffWidgetPrivate(this)) { d->m_job = job; d->m_ui = new Ui::VcsDiffWidget(); d->m_ui->setupUi( this ); connect( d->m_job, &VcsJob::resultsReady, this, [&] (VcsJob* job) { d->diffReady(job); } ); ICore::self()->runController()->registerJob( d->m_job ); } VcsDiffWidget::~VcsDiffWidget() { delete d->m_ui; delete d; } void VcsDiffWidget::setRevisions( const KDevelop::VcsRevision& first, const KDevelop::VcsRevision& second ) { d->m_ui->revLabel->setText( i18n("Difference between revision %1 and %2:", first.prettyValue(), second.prettyValue() ) ); } } #include "moc_vcsdiffwidget.cpp" diff --git a/vcs/widgets/vcseventwidget.cpp b/vcs/widgets/vcseventwidget.cpp index ee7bb075a6..06c82ad3f1 100644 --- a/vcs/widgets/vcseventwidget.cpp +++ b/vcs/widgets/vcseventwidget.cpp @@ -1,233 +1,230 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2007 Dukju Ahn * * Copyright 2007 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "vcseventwidget.h" #include +#include +#include +#include #include #include -#include -#include +#include +#include #include -#include -#include #include +#include #include -#include -#include -#include -#include #include "ui_vcseventwidget.h" #include "vcsdiffwidget.h" -#include "../vcsjob.h" #include "../interfaces/ibasicversioncontrol.h" -#include "../vcsrevision.h" +#include "../models/vcseventmodel.h" +#include "../models/vcsitemeventmodel.h" +#include "../debug.h" #include "../vcsevent.h" +#include "../vcsjob.h" #include "../vcslocation.h" -#include "../debug.h" - -#include "../models/vcsitemeventmodel.h" -#include "../models/vcseventmodel.h" +#include "../vcsrevision.h" namespace KDevelop { class VcsEventWidgetPrivate { public: VcsEventWidgetPrivate( VcsEventWidget* w ) : q( w ) { m_copyAction = new QAction(QIcon::fromTheme("edit-copy"), i18n("Copy revision number"), q); m_copyAction->setShortcut(Qt::ControlModifier+Qt::Key_C); QObject::connect(m_copyAction, &QAction::triggered, q, [&] { copyRevision(); }); } Ui::VcsEventWidget* m_ui; VcsItemEventModel* m_detailModel; VcsEventModel *m_logModel; QUrl m_url; QModelIndex m_contextIndex; VcsEventWidget* q; QAction* m_copyAction; IBasicVersionControl* m_iface; void eventViewCustomContextMenuRequested( const QPoint &point ); void eventViewClicked( const QModelIndex &index ); void jobReceivedResults( KDevelop::VcsJob* job ); void copyRevision(); void diffToPrevious(); void diffRevisions(); void currentRowChanged(const QModelIndex& start, const QModelIndex& end); }; void VcsEventWidgetPrivate::eventViewCustomContextMenuRequested( const QPoint &point ) { m_contextIndex = m_ui->eventView->indexAt( point ); if( !m_contextIndex.isValid() ){ qCDebug(VCS) << "contextMenu is not in TreeView"; return; } QMenu menu( m_ui->eventView ); menu.addAction(m_copyAction); menu.addAction(i18n("Diff to previous revision"), q, SLOT(diffToPrevious())); QAction* action = menu.addAction(i18n("Diff between revisions"), q, SLOT(diffRevisions())); action->setEnabled(m_ui->eventView->selectionModel()->selectedRows().size()>=2); menu.exec( m_ui->eventView->viewport()->mapToGlobal(point) ); } void VcsEventWidgetPrivate::currentRowChanged(const QModelIndex& start, const QModelIndex& end) { Q_UNUSED(end); if(start.isValid()) eventViewClicked(start); } void VcsEventWidgetPrivate::eventViewClicked( const QModelIndex &index ) { KDevelop::VcsEvent ev = m_logModel->eventForIndex( index ); m_detailModel->removeRows(0, m_detailModel->rowCount()); if( ev.revision().revisionType() != KDevelop::VcsRevision::Invalid ) { m_ui->itemEventView->setEnabled(true); m_ui->message->setEnabled(true); m_ui->message->setPlainText( ev.message() ); m_detailModel->addItemEvents( ev.items() ); }else { m_ui->itemEventView->setEnabled(false); m_ui->message->setEnabled(false); m_ui->message->clear(); } QHeaderView* header = m_ui->itemEventView->header(); header->setSectionResizeMode(QHeaderView::ResizeToContents); header->setStretchLastSection(true); } void VcsEventWidgetPrivate::copyRevision() { qApp->clipboard()->setText(m_contextIndex.sibling(m_contextIndex.row(), 0).data().toString()); } void VcsEventWidgetPrivate::diffToPrevious() { KDevelop::VcsEvent ev = m_logModel->eventForIndex( m_contextIndex ); KDevelop::VcsRevision prev = KDevelop::VcsRevision::createSpecialRevision(KDevelop::VcsRevision::Previous); KDevelop::VcsJob* job = m_iface->diff( m_url, prev, ev.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( prev, ev.revision() ); QDialog* dlg = new QDialog( q ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); dlg->setWindowTitle( i18n("Difference To Previous") ); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto mainWidget = new QWidget; QVBoxLayout *mainLayout = new QVBoxLayout; dlg->setLayout(mainLayout); mainLayout->addWidget(mainWidget); QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(widget); mainLayout->addWidget(buttonBox); dlg->show(); } void VcsEventWidgetPrivate::diffRevisions() { QModelIndexList l = m_ui->eventView->selectionModel()->selectedRows(); KDevelop::VcsEvent ev1 = m_logModel->eventForIndex( l.first() ); KDevelop::VcsEvent ev2 = m_logModel->eventForIndex( l.last() ); KDevelop::VcsJob* job = m_iface->diff( m_url, ev1.revision(), ev2.revision() ); VcsDiffWidget* widget = new VcsDiffWidget( job ); widget->setRevisions( ev1.revision(), ev2.revision() ); auto dlg = new QDialog( q ); dlg->setWindowTitle( i18n("Difference between Revisions") ); widget->connect(widget, &VcsDiffWidget::destroyed, dlg, &QDialog::deleteLater); auto mainLayout = new QVBoxLayout(dlg); auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok); auto okButton = buttonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); dlg->connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept); dlg->connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); mainLayout->addWidget(buttonBox); mainLayout->addWidget(widget); dlg->show(); } VcsEventWidget::VcsEventWidget( const QUrl& url, const VcsRevision& rev, KDevelop::IBasicVersionControl* iface, QWidget* parent ) : QWidget(parent), d(new VcsEventWidgetPrivate(this) ) { d->m_iface = iface; d->m_url = url; d->m_ui = new Ui::VcsEventWidget(); d->m_ui->setupUi(this); d->m_logModel= new VcsEventModel(iface, rev, url, this); d->m_ui->eventView->setModel( d->m_logModel ); d->m_ui->eventView->sortByColumn(0, Qt::DescendingOrder); d->m_ui->eventView->setContextMenuPolicy( Qt::CustomContextMenu ); QHeaderView* header = d->m_ui->eventView->header(); header->setSectionResizeMode( 0, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 1, QHeaderView::Stretch ); header->setSectionResizeMode( 2, QHeaderView::ResizeToContents ); header->setSectionResizeMode( 3, QHeaderView::ResizeToContents ); d->m_detailModel = new VcsItemEventModel(this); d->m_ui->itemEventView->setModel( d->m_detailModel ); connect( d->m_ui->eventView, &QTreeView::clicked, this, [&] (const QModelIndex& index) { d->eventViewClicked(index); } ); connect( d->m_ui->eventView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, [&] (const QModelIndex& start, const QModelIndex& end) { d->currentRowChanged(start, end); }); connect( d->m_ui->eventView, &QTreeView::customContextMenuRequested, this, [&] (const QPoint& point) { d->eventViewCustomContextMenuRequested(point); } ); } VcsEventWidget::~VcsEventWidget() { delete d->m_ui; delete d; } } #include "moc_vcseventwidget.cpp"