diff --git a/plugins/documentswitcher/CMakeLists.txt b/plugins/documentswitcher/CMakeLists.txt index 2e94ddaf4d..eb8ba8a7b5 100644 --- a/plugins/documentswitcher/CMakeLists.txt +++ b/plugins/documentswitcher/CMakeLists.txt @@ -1,14 +1,14 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevdocumentswitcher\") project(documentswitcher) ########### next target ############### set(kdevdocumentswitcher_PART_SRCS documentswitcherplugin.cpp documentswitchertreeview.cpp ) qt5_add_resources(kdevdocumentswitcher_PART_SRCS kdevdocumentswitcher.qrc) kdevplatform_add_plugin(kdevdocumentswitcher JSON kdevdocumentswitcher.json SOURCES ${kdevdocumentswitcher_PART_SRCS}) -target_link_libraries(kdevdocumentswitcher KDev::Interfaces KDev::Sublime KDev::Interfaces ) +target_link_libraries(kdevdocumentswitcher KDev::Interfaces KDev::Sublime KDev::Interfaces KDev::Util KDev::Project ) diff --git a/plugins/documentswitcher/documentswitcherplugin.cpp b/plugins/documentswitcher/documentswitcherplugin.cpp index 492aa5d251..88da1c1d1b 100644 --- a/plugins/documentswitcher/documentswitcherplugin.cpp +++ b/plugins/documentswitcher/documentswitcherplugin.cpp @@ -1,388 +1,396 @@ /*************************************************************************** * 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 "documentswitchertreeview.h" #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(QStringLiteral("kdevdocumentswitcher"), parent), view(0) { setXMLFile(QStringLiteral("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 ( QStringLiteral( "last_used_views_forward" ) ); forwardAction->setText( i18n( "Last Used Views" ) ); forwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "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 ( QStringLiteral( "last_used_views_backward" ) ); backwardAction->setText( i18n( "Last Used Views (Reverse)" ) ); backwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "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->setUniformRowHeights( true ); view->setTextElideMode( Qt::ElideMiddle ); view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); view->addAction( forwardAction ); view->addAction( backwardAction ); + view->setHeaderHidden( true ); + view->setIndentation( 10 ); 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 that is ok. Otherwise one can easily tweak the // max size to be only 1/2th of the central widget size const int rowHeight = view->sizeHintForRow(0); const int frameWidth = view->frameWidth(); const QSize viewSize( std::min( view->sizeHintForColumn(0) + 2 * frameWidth + view->verticalScrollBar()->width(), viewMaxSize.width() ), std::min( std::max( rowHeight * view->model()->rowCount() + 2 * frameWidth, rowHeight * 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(); + auto projectController = KDevelop::ICore::self()->projectController(); 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()); + IProject* project = nullptr; if( doc ) { - QString path = ICore::self()->projectController()->prettyFilePath(doc->url(), + QString path = 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(QLatin1Char(':')); // 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 + 1)); itemText = QStringLiteral("%1 (%2:%3)").arg(itemText, fileInProjectInfo.first, fileInProjectInfo.second); } else { itemText = itemText + " (" + path + ')'; } + project = projectController->findProjectForUrl(doc->url()); } - model->appendRow( new QStandardItem( slDoc->icon(), itemText ) ); + auto item = new QStandardItem( slDoc->icon(), itemText ); + item->setData(QVariant::fromValue(project), DocumentSwitcherTreeView::ProjectRole); + model->appendRow( item ); } } 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) ) { QHash > 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() << "its area is:" << mainwindow->area() << mainwindow->area()->title() << "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/documentswitcher/documentswitcherplugin.h b/plugins/documentswitcher/documentswitcherplugin.h index fce6ff3a7c..534333dbcd 100644 --- a/plugins/documentswitcher/documentswitcherplugin.h +++ b/plugins/documentswitcher/documentswitcherplugin.h @@ -1,81 +1,81 @@ /*************************************************************************** * Copyright 2009 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERPLUGIN_H #include #include #include Q_DECLARE_LOGGING_CATEGORY(PLUGIN_DOCUMENTSWITCHER) class QStandardItemModel; namespace Sublime { class View; class MainWindow; class AreaIndex; class Area; class MainWindow; } -class QListView; +class DocumentSwitcherTreeView; class QModelIndex; class QStringListModel; class QEvent; class QAction; class DocumentSwitcherPlugin: public KDevelop::IPlugin { Q_OBJECT public: explicit DocumentSwitcherPlugin( QObject *parent, const QVariantList &args = QVariantList() ); ~DocumentSwitcherPlugin() override; void unload() override; public slots: void itemActivated( const QModelIndex& ); void switchToClicked(const QModelIndex& ); private slots: void addView( Sublime::View* ); void changeView( Sublime::View* ); void addMainWindow( Sublime::MainWindow* ); void changeArea( Sublime::Area* ); void removeView( Sublime::View* ); void removeMainWindow(QObject*); void walkForward(); void walkBackward(); protected: bool eventFilter( QObject*, QEvent* ) override; private: void setViewGeometry(Sublime::MainWindow* window); void storeAreaViewList( Sublime::MainWindow* mainwindow, Sublime::Area* area ); void enableActions(); void fillModel( Sublime::MainWindow* window ); void walk(const int from, const int to); // Need to use QObject here as we only have a QObject* in // the removeMainWindow method and cannot cast it to the mainwindow anymore QMap > > documentLists; - QListView* view; + DocumentSwitcherTreeView* view; QStandardItemModel* model; QAction* forwardAction; QAction* backwardAction; }; #endif diff --git a/plugins/documentswitcher/documentswitchertreeview.cpp b/plugins/documentswitcher/documentswitchertreeview.cpp index 321563489f..afb5e6bbb4 100644 --- a/plugins/documentswitcher/documentswitchertreeview.cpp +++ b/plugins/documentswitcher/documentswitchertreeview.cpp @@ -1,47 +1,70 @@ /*************************************************************************** * Copyright 2009 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 "documentswitchertreeview.h" #include +#include +#include + +#include +#include +#include +#include + #include "documentswitcherplugin.h" +using namespace KDevelop; + DocumentSwitcherTreeView::DocumentSwitcherTreeView(DocumentSwitcherPlugin* plugin_ ) - : QListView(nullptr) + : QTreeView(nullptr) , plugin(plugin_) { setWindowFlags(Qt::Popup | Qt::FramelessWindowHint); } void DocumentSwitcherTreeView::keyPressEvent(QKeyEvent* event) { - QListView::keyPressEvent(event); + QTreeView::keyPressEvent(event); } void DocumentSwitcherTreeView::keyReleaseEvent(QKeyEvent* event) { if (event->key() == Qt::Key_Control) { plugin->itemActivated(selectionModel()->currentIndex()); event->accept(); hide(); } else { - QListView::keyReleaseEvent(event); + QTreeView::keyReleaseEvent(event); } } +void DocumentSwitcherTreeView::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const +{ + if (WidgetColorizer::colorizeByProject()) { + if (const auto project = index.data(ProjectRole).value()) { + const auto projectPath = project->path(); + const QColor color = WidgetColorizer::colorForId(qHash(projectPath), palette(), true); + WidgetColorizer::drawBranches(this, painter, rect, index, color); + } + } + // don't call the base implementation, as we only want to paint the colorization above + // this means that for people who have the feature disabled, we get some padding on the left, + // but that is OK imo +} diff --git a/plugins/documentswitcher/documentswitchertreeview.h b/plugins/documentswitcher/documentswitchertreeview.h index 3d4aa609b9..e601c066d7 100644 --- a/plugins/documentswitcher/documentswitchertreeview.h +++ b/plugins/documentswitcher/documentswitchertreeview.h @@ -1,42 +1,49 @@ /*************************************************************************** * Copyright 2009 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. * ***************************************************************************/ #ifndef KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H #define KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H -#include +#include class DocumentSwitcherPlugin; -class DocumentSwitcherTreeView : public QListView +class DocumentSwitcherTreeView : public QTreeView { Q_OBJECT public: + enum Roles { + ProjectRole = Qt::UserRole + 1 + }; explicit DocumentSwitcherTreeView(DocumentSwitcherPlugin* plugin); + using QTreeView::sizeHintForColumn; + protected: void keyPressEvent(QKeyEvent* event) override; void keyReleaseEvent(QKeyEvent* event) override; + void drawBranches(QPainter* painter, const QRect& rect, + const QModelIndex& index) const override; private: DocumentSwitcherPlugin* plugin; }; #endif // KDEVPLATFORM_PLUGIN_DOCUMENTSWITCHERTREEVIEW_H