File indexing completed on 2024-04-28 04:38:37

0001 /*
0002     SPDX-FileCopyrightText: 2009, 2013 Andreas Pakulat <apaku@gmx.de>
0003     SPDX-FileCopyrightText: 2013 Jarosław Sierant <jaroslaw.sierant@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "documentswitcherplugin.h"
0009 
0010 #include <QApplication>
0011 #include <QListView>
0012 #include <QStandardItemModel>
0013 #include <QScrollBar>
0014 
0015 #include <KActionCollection>
0016 #include <KLocalizedString>
0017 #include <KPluginFactory>
0018 
0019 #include <interfaces/icore.h>
0020 #include <interfaces/iuicontroller.h>
0021 #include <interfaces/idocument.h>
0022 #include <interfaces/idocumentcontroller.h>
0023 #include <sublime/mainwindow.h>
0024 
0025 #include "documentswitchertreeview.h"
0026 #include "documentswitcheritem.h"
0027 #include "debug.h"
0028 
0029 #include <algorithm>
0030 
0031 K_PLUGIN_FACTORY_WITH_JSON(DocumentSwitcherFactory, "kdevdocumentswitcher.json", registerPlugin<DocumentSwitcherPlugin>();)
0032 
0033 //TODO: Show frame around view's widget while walking through
0034 //TODO: Make the widget transparent
0035 
0036 DocumentSwitcherPlugin::DocumentSwitcherPlugin(QObject *parent, const QVariantList &/*args*/)
0037     :KDevelop::IPlugin(QStringLiteral("kdevdocumentswitcher"), parent), view(nullptr)
0038 {
0039     setXMLFile(QStringLiteral("kdevdocumentswitcher.rc"));
0040     qCDebug(PLUGIN_DOCUMENTSWITCHER) << "Adding active mainwindow from constructor";
0041 
0042     KDevelop::IDocumentController *documentController = KDevelop::ICore::self()->documentController();
0043     for (KDevelop::IDocument *doc : documentController->openDocuments()) {
0044         documentOpened(doc);
0045     }
0046 
0047     // Signals to track last used documents.
0048     connect(documentController, &KDevelop::IDocumentController::documentOpened, this, &DocumentSwitcherPlugin::documentOpened);
0049     connect(documentController, &KDevelop::IDocumentController::documentActivated, this, &DocumentSwitcherPlugin::documentActivated);
0050     connect(documentController, &KDevelop::IDocumentController::documentClosed, this, &DocumentSwitcherPlugin::documentClosed);
0051 
0052 #ifdef Q_OS_MACOS
0053     // Qt/Mac swaps the Ctrl and Meta (Command) keys by default, so that shortcuts defined as Ctrl+X
0054     // become the platform-standard Command+X . Ideally we would map the document switcher shortcut to
0055     // Control+Tab (and thus Qt::META|Qt::Key_Tab) everywhere because Command+Tab and Command+Shift+Tab
0056     // are reserved system shortcuts that bring up the application switcher. The Control+Tab shortcut is 
0057     // inoperable on Mac, so we resort to the Alt (Option) key, unless the AA_MacDontSwapCtrlAndMeta
0058     // attribute is set.
0059     const Qt::Modifier shortcutAccelerator = QCoreApplication::testAttribute(Qt::AA_MacDontSwapCtrlAndMeta) ? Qt::CTRL : Qt::ALT;
0060 #else
0061     const Qt::Modifier shortcutAccelerator = Qt::CTRL;
0062 #endif
0063 
0064     forwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_forward" ) );
0065     forwardAction->setText(i18nc("@action", "Last Used Views"));
0066     forwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-next-view-page") ) );
0067     actionCollection()->setDefaultShortcut( forwardAction, shortcutAccelerator | Qt::Key_Tab );
0068     forwardAction->setWhatsThis(i18nc("@info:whatsthis", "Opens a list to walk through the list of last used views."));
0069     forwardAction->setToolTip( i18nc("@info:tooltip", "Walk through the list of last used views"));
0070     connect( forwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkForward );
0071 
0072     backwardAction = actionCollection()->addAction ( QStringLiteral( "last_used_views_backward" ) );
0073     backwardAction->setText(i18nc("@action", "Last Used Views (Reverse)"));
0074     backwardAction->setIcon( QIcon::fromTheme( QStringLiteral( "go-previous-view-page") ) );
0075     actionCollection()->setDefaultShortcut( backwardAction, shortcutAccelerator | Qt::SHIFT | Qt::Key_Tab );
0076     backwardAction->setWhatsThis(i18nc("@info:whatsthis", "Opens a list to walk through the list of last used views in reverse."));
0077     backwardAction->setToolTip(i18nc("@info:tooltip", "Walk through the list of last used views"));
0078     connect( backwardAction, &QAction::triggered, this, &DocumentSwitcherPlugin::walkBackward );
0079 
0080     view = new DocumentSwitcherTreeView( this );
0081     view->setSelectionBehavior( QAbstractItemView::SelectRows );
0082     view->setSelectionMode( QAbstractItemView::SingleSelection );
0083     view->setUniformRowHeights( true );
0084     view->setTextElideMode( Qt::ElideMiddle );
0085     view->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff );
0086     view->addAction( forwardAction );
0087     view->addAction( backwardAction );
0088     view->setHeaderHidden( true );
0089     view->setIndentation( 10 );
0090     connect( view, &QListView::pressed, this, &DocumentSwitcherPlugin::switchToClicked );
0091     connect( view, &QListView::activated, this, &DocumentSwitcherPlugin::itemActivated );
0092 
0093     model = new QStandardItemModel( view );
0094     view->setModel( model );
0095 }
0096 
0097 void DocumentSwitcherPlugin::setViewGeometry(Sublime::MainWindow* window)
0098 {
0099     const QSize centralSize = window->centralWidget()->size();
0100 
0101     // Maximum size of the view is 3/4th of the central widget (the editor area) so the view does not overlap the
0102     // mainwindow since that looks awkward.
0103     const QSize viewMaxSize( centralSize.width() * 3/4, centralSize.height() * 3/4 );
0104 
0105     // The actual view size should be as big as the columns/rows need it, but smaller than the max-size. This means
0106     // the view will get quite high with many open files but I think that is ok. Otherwise one can easily tweak the
0107     // max size to be only 1/2th of the central widget size
0108     const int rowHeight = view->sizeHintForRow(0);
0109     const int frameWidth = view->frameWidth();
0110     const QSize viewSize( std::min( view->sizeHintForColumn(0) + 2 * frameWidth + view->verticalScrollBar()->width(), viewMaxSize.width() ),
0111                           std::min( std::max( rowHeight * view->model()->rowCount() + 2 * frameWidth, rowHeight * 6 ), viewMaxSize.height() ) );
0112 
0113     // Position should be central over the editor area, so map to global from parent of central widget since
0114     // the view is positioned in global coords
0115     QPoint centralWidgetPos = window->mapToGlobal( window->centralWidget()->pos() );
0116     const int xPos = std::max(0, centralWidgetPos.x() + (centralSize.width()  - viewSize.width()  ) / 2);
0117     const int yPos = std::max(0, centralWidgetPos.y() + (centralSize.height() - viewSize.height() ) / 2);
0118 
0119     view->setFixedSize(viewSize);
0120     view->move(xPos, yPos);
0121 }
0122 
0123 void DocumentSwitcherPlugin::walk(const int from, const int to)
0124 {
0125     auto* window = qobject_cast<Sublime::MainWindow*>( KDevelop::ICore::self()->uiController()->activeMainWindow() );
0126 
0127     QModelIndex idx;
0128     const int step = from < to ? 1 : -1;
0129     if(!view->isVisible())
0130     {
0131         fillModel();
0132         setViewGeometry(window);
0133         idx = model->index(from + step, 0);
0134         if(!idx.isValid()) { idx = model->index(0, 0); }
0135         view->show();
0136     } else {
0137         int newRow = view->selectionModel()->currentIndex().row() + step;
0138         if(newRow == to + step) { newRow = from; }
0139         idx = model->index(newRow, 0);
0140     }
0141     view->selectionModel()->select(idx, QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect);
0142     view->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows);
0143 }
0144 
0145 
0146 void DocumentSwitcherPlugin::walkForward() { walk(0, model->rowCount()-1); }
0147 
0148 void DocumentSwitcherPlugin::walkBackward() { walk(model->rowCount()-1, 0); }
0149 
0150 void DocumentSwitcherPlugin::fillModel()
0151 {
0152     model->clear();
0153 
0154     for (auto *doc : documentLists) {
0155         DocumentSwitcherItem *item = new DocumentSwitcherItem(doc);
0156         model->appendRow(item);
0157     }
0158 }
0159 
0160 DocumentSwitcherPlugin::~DocumentSwitcherPlugin()
0161 {
0162 }
0163 
0164 void DocumentSwitcherPlugin::switchToClicked( const QModelIndex& idx )
0165 {
0166     view->selectionModel()->select(idx, QItemSelectionModel::ClearAndSelect);
0167     itemActivated(idx);
0168 }
0169 
0170 void DocumentSwitcherPlugin::itemActivated( const QModelIndex& idx )
0171 {
0172     Q_UNUSED( idx );
0173     if( view->selectionModel()->selectedRows().isEmpty() )
0174     {
0175         return;
0176     }
0177     const int row = view->selectionModel()->selectedRows().first().row();
0178 
0179     // Retrieve document from index
0180     auto* activatedDocument = documentLists.value(row);
0181     if( activatedDocument ) {
0182         // Close document
0183         if( QApplication::mouseButtons() & Qt::MiddleButton )
0184         {
0185             activatedDocument->close();
0186             fillModel();
0187             if ( model->rowCount() == 0 ) {
0188                 view->hide();
0189             } else {
0190                 view->selectionModel()->select( view->model()->index(0, 0), QItemSelectionModel::ClearAndSelect );
0191             }
0192         }
0193         // Activate document
0194         else
0195         {
0196             KDevelop::IDocumentController *documentController = KDevelop::ICore::self()->documentController();
0197             documentController->activateDocument(activatedDocument);
0198             view->hide();
0199         }
0200     }
0201 }
0202 
0203 void DocumentSwitcherPlugin::documentOpened(KDevelop::IDocument *document)
0204 {
0205     if (!documentLists.contains(document)) {
0206         documentLists.prepend(document);
0207     }
0208 }
0209 
0210 void DocumentSwitcherPlugin::documentActivated(KDevelop::IDocument *document)
0211 {
0212     documentLists.removeOne(document);
0213     documentLists.prepend(document);
0214 }
0215 
0216 void DocumentSwitcherPlugin::documentClosed(KDevelop::IDocument *document)
0217 {
0218     documentLists.removeOne(document);
0219 }
0220 
0221 void DocumentSwitcherPlugin::unload()
0222 {
0223     delete forwardAction;
0224     delete backwardAction;
0225     view->deleteLater();
0226 }
0227 
0228 bool DocumentSwitcherPlugin::eventFilter( QObject* watched, QEvent* ev )
0229 {
0230     auto* mw = qobject_cast<Sublime::MainWindow*>(watched);
0231     if( mw && ev->type() == QEvent::WindowActivate )
0232     {
0233         enableActions();
0234     }
0235     return QObject::eventFilter( watched, ev );
0236 }
0237 
0238 void DocumentSwitcherPlugin::enableActions()
0239 {
0240     forwardAction->setEnabled(true);
0241     backwardAction->setEnabled(true);
0242 }
0243 
0244 #include "documentswitcherplugin.moc"
0245 #include "moc_documentswitcherplugin.cpp"