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"