File indexing completed on 2024-04-28 04:37:19
0001 /* 0002 SPDX-FileCopyrightText: 2002 Falk Brettschneider <falkbr@kdevelop.org> 0003 SPDX-FileCopyrightText: 2003 John Firebaugh <jfirebaugh@kde.org> 0004 SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org> 0005 SPDX-FileCopyrightText: 2006, 2007 Alexander Dymo <adymo@kdevelop.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "mainwindow.h" 0011 #include "mainwindow_p.h" 0012 0013 #include <QDBusConnection> 0014 #include <QDomElement> 0015 #include <QDragEnterEvent> 0016 #include <QDropEvent> 0017 #include <QMenu> 0018 #include <QMenuBar> 0019 #include <QMimeData> 0020 #include <QUrl> 0021 0022 #include <KActionCollection> 0023 #include <KLocalizedString> 0024 #include <KShortcutsDialog> 0025 #include <KTextEditor/Document> 0026 #include <KTextEditor/View> 0027 #include <KWindowSystem> 0028 #include <KXMLGUIFactory> 0029 0030 #include <sublime/area.h> 0031 #include "shellextension.h" 0032 #include "partcontroller.h" 0033 #include "plugincontroller.h" 0034 #include "projectcontroller.h" 0035 #include "uicontroller.h" 0036 #include "documentcontroller.h" 0037 #include "workingsetcontroller.h" 0038 #include "sessioncontroller.h" 0039 #include "sourceformattercontroller.h" 0040 #include "areadisplay.h" 0041 #include "project.h" 0042 #include "debug.h" 0043 #include "uiconfig.h" 0044 #include "ktexteditorpluginintegration.h" 0045 0046 #include <interfaces/isession.h> 0047 #include <interfaces/iprojectcontroller.h> 0048 #include <sublime/view.h> 0049 #include <sublime/document.h> 0050 #include <sublime/urldocument.h> 0051 #include <sublime/container.h> 0052 #include <util/path.h> 0053 #include <util/widgetcolorizer.h> 0054 0055 using namespace KDevelop; 0056 0057 namespace { 0058 0059 QColor defaultColor(const QPalette& palette) 0060 { 0061 return palette.windowText().color(); 0062 } 0063 0064 QColor colorForDocument(const QUrl& url, const QPalette& palette, const QColor& defaultColor) 0065 { 0066 auto project = Core::self()->projectController()->findProjectForUrl(url); 0067 if (!project) 0068 return defaultColor; 0069 0070 return WidgetColorizer::colorForId(qHash(project->path()), palette); 0071 } 0072 0073 } 0074 0075 void MainWindow::applyMainWindowSettings(const KConfigGroup& config) 0076 { 0077 Q_D(MainWindow); 0078 0079 if(!d->changingActiveView()) 0080 KXmlGuiWindow::applyMainWindowSettings(config); 0081 } 0082 0083 void MainWindow::createGUI(KParts::Part* part) 0084 { 0085 Sublime::MainWindow::setWindowTitleHandling(false); 0086 Sublime::MainWindow::createGUI(part); 0087 } 0088 0089 void MainWindow::initializeCorners() 0090 { 0091 const KConfigGroup cg = KSharedConfig::openConfig()->group( "UiSettings" ); 0092 const int bottomleft = cg.readEntry( "BottomLeftCornerOwner", 0 ); 0093 const int bottomright = cg.readEntry( "BottomRightCornerOwner", 0 ); 0094 qCDebug(SHELL) << "Bottom Left:" << bottomleft; 0095 qCDebug(SHELL) << "Bottom Right:" << bottomright; 0096 0097 // 0 means vertical dock (left, right), 1 means horizontal dock( top, bottom ) 0098 if( bottomleft == 0 ) 0099 setCorner( Qt::BottomLeftCorner, Qt::LeftDockWidgetArea ); 0100 else if( bottomleft == 1 ) 0101 setCorner( Qt::BottomLeftCorner, Qt::BottomDockWidgetArea ); 0102 0103 if( bottomright == 0 ) 0104 setCorner( Qt::BottomRightCorner, Qt::RightDockWidgetArea ); 0105 else if( bottomright == 1 ) 0106 setCorner( Qt::BottomRightCorner, Qt::BottomDockWidgetArea ); 0107 } 0108 0109 MainWindow::MainWindow( Sublime::Controller *parent, Qt::WindowFlags flags ) 0110 : Sublime::MainWindow( parent, flags ) 0111 { 0112 QDBusConnection::sessionBus().registerObject( QStringLiteral("/kdevelop/MainWindow"), 0113 this, QDBusConnection::ExportScriptableSlots ); 0114 0115 setAcceptDrops( true ); 0116 initializeCorners(); 0117 0118 setObjectName( QStringLiteral("MainWindow") ); 0119 d_ptr = new MainWindowPrivate(this); 0120 0121 Q_D(MainWindow); 0122 0123 setStandardToolBarMenuEnabled( true ); 0124 d->setupActions(); 0125 0126 if( !ShellExtension::getInstance()->xmlFile().isEmpty() ) 0127 { 0128 setXMLFile( ShellExtension::getInstance() ->xmlFile() ); 0129 } 0130 0131 menuBar()->setCornerWidget(new AreaDisplay(this), Qt::TopRightCorner); 0132 } 0133 0134 MainWindow::~ MainWindow() 0135 { 0136 if (memberList().count() == 1) { 0137 // We're closing down... 0138 Core::self()->shutdown(); 0139 } 0140 0141 delete d_ptr; 0142 } 0143 0144 KTextEditorIntegration::MainWindow *MainWindow::kateWrapper() const 0145 { 0146 Q_D(const MainWindow); 0147 0148 return d->kateWrapper(); 0149 } 0150 0151 void MainWindow::split(Qt::Orientation orientation) 0152 { 0153 Q_D(MainWindow); 0154 0155 d->split(orientation); 0156 } 0157 0158 void MainWindow::ensureVisible() 0159 { 0160 if (isMinimized()) { 0161 if (isMaximized()) { 0162 showMaximized(); 0163 } else { 0164 showNormal(); 0165 } 0166 } 0167 KWindowSystem::forceActiveWindow(winId()); 0168 } 0169 0170 QAction* MainWindow::createCustomElement(QWidget* parent, int index, const QDomElement& element) 0171 { 0172 QAction* before = nullptr; 0173 if (index > 0 && index < parent->actions().count()) 0174 before = parent->actions().at(index); 0175 0176 //KDevelop needs to ensure that separators defined as <Separator style="visible" /> 0177 //are always shown in the menubar. For those, we create special disabled actions 0178 //instead of calling QMenuBar::addSeparator() because menubar separators are ignored 0179 if (element.tagName().compare(QLatin1String("separator"), Qt::CaseInsensitive) == 0 0180 && element.attribute(QStringLiteral("style")) == QLatin1String("visible")) { 0181 if ( auto* bar = qobject_cast<QMenuBar*>( parent ) ) { 0182 auto* separatorAction = new QAction(QStringLiteral("|"), this); 0183 bar->insertAction( before, separatorAction ); 0184 separatorAction->setDisabled(true); 0185 return separatorAction; 0186 } 0187 } 0188 0189 return KXMLGUIBuilder::createCustomElement(parent, index, element); 0190 } 0191 0192 bool KDevelop::MainWindow::event( QEvent* ev ) 0193 { 0194 if ( ev->type() == QEvent::PaletteChange ) 0195 updateAllTabColors(); 0196 return Sublime::MainWindow::event(ev); 0197 } 0198 0199 void MainWindow::dragEnterEvent( QDragEnterEvent* ev ) 0200 { 0201 const QMimeData* mimeData = ev->mimeData(); 0202 if (mimeData->hasUrls()) { 0203 ev->acceptProposedAction(); 0204 } else if (mimeData->hasText()) { 0205 // also take text which contains a URL 0206 const QUrl url = QUrl::fromUserInput(mimeData->text()); 0207 if (url.isValid()) { 0208 ev->acceptProposedAction(); 0209 } 0210 } 0211 } 0212 0213 void MainWindow::dropEvent( QDropEvent* ev ) 0214 { 0215 Sublime::View* dropToView = viewForPosition(mapToGlobal(ev->pos())); 0216 if(dropToView) 0217 activateView(dropToView); 0218 0219 QList<QUrl> urls; 0220 0221 const QMimeData* mimeData = ev->mimeData(); 0222 if (mimeData->hasUrls()) { 0223 urls = mimeData->urls(); 0224 } else if (mimeData->hasText()) { 0225 const QUrl url = QUrl::fromUserInput(mimeData->text()); 0226 if (url.isValid()) { 0227 urls << url; 0228 } 0229 } 0230 0231 bool eventUsed = false; 0232 if (urls.size() == 1) { 0233 eventUsed = Core::self()->projectControllerInternal()->fetchProjectFromUrl(urls.at(0), ProjectController::NoFetchFlags); 0234 } 0235 0236 if (!eventUsed) { 0237 for(const auto& url : qAsConst(urls)) { 0238 Core::self()->documentController()->openDocument(url); 0239 } 0240 } 0241 0242 ev->acceptProposedAction(); 0243 } 0244 0245 void MainWindow::loadSettings() 0246 { 0247 qCDebug(SHELL) << "Loading Settings"; 0248 initializeCorners(); 0249 0250 updateAllTabColors(); 0251 0252 Sublime::MainWindow::loadSettings(); 0253 } 0254 0255 void MainWindow::configureShortcuts() 0256 { 0257 ///Workaround for a problem with the actions: Always start the shortcut-configuration in the first mainwindow, then propagate the updated 0258 ///settings into the other windows 0259 0260 0261 // We need to bring up the shortcut dialog ourself instead of 0262 // Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->configureShortcuts(); 0263 // so we can connect to the saved() signal to propagate changes in the editor shortcuts 0264 0265 KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsAllowed, this); 0266 0267 const auto firstMainWindowClientsBefore = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients(); 0268 for (KXMLGUIClient* client : firstMainWindowClientsBefore) { 0269 if(client && !client->xmlFile().isEmpty()) 0270 dlg.addCollection( client->actionCollection() ); 0271 } 0272 0273 connect(&dlg, &KShortcutsDialog::saved, this, &MainWindow::shortcutsChanged); 0274 dlg.configure(true); 0275 0276 QMap<QString, QKeySequence> shortcuts; 0277 // querying again just in case something changed behind our back 0278 const auto firstMainWindowClientsAfter = Core::self()->uiControllerInternal()->mainWindows()[0]->guiFactory()->clients(); 0279 for (KXMLGUIClient* client : firstMainWindowClientsAfter) { 0280 const auto actions = client->actionCollection()->actions(); 0281 for (QAction* action : actions) { 0282 if(!action->objectName().isEmpty()) { 0283 shortcuts[action->objectName()] = action->shortcut(); 0284 } 0285 } 0286 } 0287 0288 for(int a = 1; a < Core::self()->uiControllerInternal()->mainWindows().size(); ++a) { 0289 const auto clients = Core::self()->uiControllerInternal()->mainWindows()[a]->guiFactory()->clients(); 0290 for (KXMLGUIClient* client : clients) { 0291 const auto actions = client->actionCollection()->actions(); 0292 for (QAction* action : actions) { 0293 qCDebug(SHELL) << "transferring setting shortcut for" << action->objectName(); 0294 const auto shortcutIt = shortcuts.constFind(action->objectName()); 0295 if (shortcutIt != shortcuts.constEnd()) { 0296 action->setShortcut(*shortcutIt); 0297 } 0298 } 0299 } 0300 } 0301 0302 } 0303 0304 void MainWindow::shortcutsChanged() 0305 { 0306 KTextEditor::View *activeClient = Core::self()->documentController()->activeTextDocumentView(); 0307 if(!activeClient) 0308 return; 0309 0310 const auto documents = Core::self()->documentController()->openDocuments(); 0311 for (IDocument* doc : documents) { 0312 KTextEditor::Document *textDocument = doc->textDocument(); 0313 if (textDocument) { 0314 const auto views = textDocument->views(); 0315 for (KTextEditor::View* client : views) { 0316 if (client != activeClient) { 0317 client->reloadXML(); 0318 } 0319 } 0320 } 0321 } 0322 } 0323 0324 0325 void MainWindow::initialize() 0326 { 0327 Q_D(MainWindow); 0328 0329 KStandardAction::keyBindings(this, SLOT(configureShortcuts()), actionCollection()); 0330 setupGUI(ToolBar | Save); 0331 createGUI(nullptr); 0332 0333 Core::self()->partController()->addManagedTopLevelWidget(this); 0334 qCDebug(SHELL) << "Adding plugin-added connection"; 0335 0336 connect( Core::self()->pluginController(), &IPluginController::pluginLoaded, 0337 d, &MainWindowPrivate::addPlugin); 0338 connect( Core::self()->pluginController(), &IPluginController::pluginUnloaded, 0339 d, &MainWindowPrivate::removePlugin); 0340 connect( Core::self()->partController(), &IPartController::activePartChanged, 0341 d, &MainWindowPrivate::activePartChanged); 0342 connect( this, &MainWindow::activeViewChanged, 0343 d, &MainWindowPrivate::changeActiveView); 0344 connect(Core::self()->sourceFormatterControllerInternal(), &SourceFormatterController::hasFormattersChanged, 0345 d, &MainWindowPrivate::updateSourceFormatterGuiClient); 0346 0347 const auto plugins = Core::self()->pluginController()->loadedPlugins(); 0348 for (IPlugin* plugin : plugins) { 0349 d->addPlugin(plugin); 0350 } 0351 0352 guiFactory()->addClient(Core::self()->sessionController()); 0353 if (Core::self()->sourceFormatterControllerInternal()->hasFormatters()) { 0354 guiFactory()->addClient(Core::self()->sourceFormatterControllerInternal()); 0355 } 0356 0357 // Needed to re-plug the actions from the sessioncontroller as xmlguiclients don't 0358 // seem to remember which actions where plugged in. 0359 Core::self()->sessionController()->updateXmlGuiActionList(); 0360 0361 d->setupGui(); 0362 0363 qRegisterMetaType<QPointer<KTextEditor::Document>>(); 0364 0365 //Queued so we process it with some delay, to make sure the rest of the UI has already adapted 0366 connect(Core::self()->documentController(), &IDocumentController::documentActivated, 0367 // Use a queued connection, because otherwise the view is not yet fully set up 0368 // but wrap the document in a smart pointer to guard against crashes when it 0369 // gets deleted in the meantime 0370 this, [this](IDocument *doc) { 0371 const auto textDocument = QPointer<KTextEditor::Document>(doc->textDocument()); 0372 QMetaObject::invokeMethod(this, "documentActivated", Qt::QueuedConnection, 0373 Q_ARG(QPointer<KTextEditor::Document>, textDocument)); 0374 }); 0375 0376 connect(Core::self()->documentController(), &IDocumentController::documentClosed, this, &MainWindow::updateCaption, Qt::QueuedConnection); 0377 connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); 0378 connect(Core::self()->documentController(), &IDocumentController::documentStateChanged, this, &MainWindow::updateCaption, Qt::QueuedConnection); 0379 connect(Core::self()->sessionController()->activeSession(), &ISession::sessionUpdated, this, &MainWindow::updateCaption); 0380 // if currently viewed document is part of project, trigger update of full path to project prefixed one 0381 connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateCaption, Qt::QueuedConnection); 0382 0383 connect(Core::self()->documentController(), &IDocumentController::documentOpened, this, &MainWindow::updateTabColor); 0384 connect(Core::self()->documentController(), &IDocumentController::documentUrlChanged, this, &MainWindow::updateTabColor); 0385 connect(this, &Sublime::MainWindow::viewAdded, this, &MainWindow::updateAllTabColors); 0386 connect(Core::self()->projectController(), &ProjectController::projectOpened, this, &MainWindow::updateAllTabColors, Qt::QueuedConnection); 0387 0388 updateCaption(); 0389 } 0390 0391 void MainWindow::cleanup() 0392 { 0393 } 0394 0395 void MainWindow::setVisible( bool visible ) 0396 { 0397 KXmlGuiWindow::setVisible( visible ); 0398 emit finishedLoading(); 0399 } 0400 0401 bool MainWindow::queryClose() 0402 { 0403 if (!Core::self()->documentControllerInternal()->saveAllDocumentsForWindow(this, IDocument::Default)) 0404 return false; 0405 0406 return Sublime::MainWindow::queryClose(); 0407 } 0408 0409 void MainWindow::documentActivated(const QPointer<KTextEditor::Document>& textDocument) 0410 { 0411 Q_D(MainWindow); 0412 0413 updateCaption(); 0414 0415 // update active document connection 0416 disconnect(d->activeDocumentReadWriteConnection); 0417 if (textDocument) { 0418 d->activeDocumentReadWriteConnection = connect(textDocument, &KTextEditor::Document::readWriteChanged, 0419 this, &MainWindow::updateCaption); 0420 } 0421 } 0422 0423 void MainWindow::updateCaption() 0424 { 0425 QString title; 0426 QString localFilePath; 0427 bool isDocumentModified = false; 0428 0429 if(area()->activeView()) 0430 { 0431 Sublime::Document* doc = area()->activeView()->document(); 0432 auto* urlDoc = qobject_cast<Sublime::UrlDocument*>(doc); 0433 if(urlDoc) 0434 { 0435 if (urlDoc->url().isLocalFile()) { 0436 localFilePath = urlDoc->url().toLocalFile(); 0437 } 0438 title += Core::self()->projectController()->prettyFileName(urlDoc->url(), KDevelop::IProjectController::FormatPlain); 0439 } 0440 else 0441 title += doc->title(); 0442 0443 auto iDoc = qobject_cast<IDocument*>(doc); 0444 if (iDoc && iDoc->textDocument() && !iDoc->textDocument()->isReadWrite()) { 0445 title += i18n(" (read only)"); 0446 } 0447 0448 title += QLatin1String(" [*]"); // [*] is placeholder for modified state, cmp. QWidget::windowModified 0449 0450 isDocumentModified = iDoc && (iDoc->state() != IDocument::Clean); 0451 } 0452 0453 const auto activeSession = Core::self()->sessionController()->activeSession(); 0454 const QString sessionTitle = activeSession ? activeSession->description() : QString(); 0455 if (!sessionTitle.isEmpty()) { 0456 if (title.isEmpty()) { 0457 title = sessionTitle; 0458 } else { 0459 title = sessionTitle + QLatin1String(" - [ ") + title + QLatin1Char(']'); 0460 } 0461 } 0462 0463 // Workaround for a bug observed on macOS with Qt 5.9.8 (TODO: test with newer Qt, report bug): 0464 // Ensure to call setCaption() (thus implicitly setWindowTitle()) before 0465 // setWindowModified() & setWindowFilePath(). 0466 // Otherwise, if the state will change "modified" from true to false as well change the title string, 0467 // calling setWindowTitle() last results in the "modified" indicator==asterisk becoming part of the 0468 // displayed window title somehow. 0469 // Other platforms so far not known to be affected, any order of calls seems fine. 0470 setCaption(title); 0471 setWindowModified(isDocumentModified); 0472 setWindowFilePath(localFilePath); 0473 } 0474 0475 void MainWindow::updateAllTabColors() 0476 { 0477 auto documentController = Core::self()->documentController(); 0478 if (!documentController) 0479 return; 0480 0481 const auto defaultColor = ::defaultColor(palette()); 0482 if (UiConfig::colorizeByProject()) { 0483 QHash<const Sublime::View*, QColor> viewColors; 0484 const auto containers = this->containers(); 0485 for (auto* container : containers) { 0486 const auto views = container->views(); 0487 viewColors.reserve(views.size()); 0488 viewColors.clear(); 0489 for (auto view : views) { 0490 const auto urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document()); 0491 if (urlDoc) { 0492 viewColors[view] = colorForDocument(urlDoc->url(), palette(), defaultColor); 0493 } 0494 } 0495 container->setTabColors(viewColors); 0496 } 0497 } else { 0498 const auto containers = this->containers(); 0499 for (auto* container : containers) { 0500 container->resetTabColors(defaultColor); 0501 } 0502 } 0503 } 0504 0505 void MainWindow::updateTabColor(IDocument* doc) 0506 { 0507 if (!UiConfig::self()->colorizeByProject()) 0508 return; 0509 0510 const auto color = colorForDocument(doc->url(), palette(), defaultColor(palette())); 0511 const auto containers = this->containers(); 0512 for (auto* container : containers) { 0513 const auto views = container->views(); 0514 for (auto* view : views) { 0515 const auto urlDoc = qobject_cast<Sublime::UrlDocument*>(view->document()); 0516 if (urlDoc && urlDoc->url() == doc->url()) { 0517 container->setTabColor(view, color); 0518 } 0519 } 0520 } 0521 } 0522 0523 void MainWindow::registerStatus(QObject* status) 0524 { 0525 Q_D(MainWindow); 0526 0527 d->registerStatus(status); 0528 } 0529 0530 void MainWindow::initializeStatusBar() 0531 { 0532 Q_D(MainWindow); 0533 0534 d->setupStatusBar(); 0535 } 0536 0537 void MainWindow::showErrorMessage(const QString& message, int timeout) 0538 { 0539 Q_D(MainWindow); 0540 0541 d->showErrorMessage(message, timeout); 0542 } 0543 0544 void MainWindow::tabContextMenuRequested(Sublime::View* view, QMenu* menu) 0545 { 0546 Q_D(MainWindow); 0547 0548 Sublime::MainWindow::tabContextMenuRequested(view, menu); 0549 d->tabContextMenuRequested(view, menu); 0550 } 0551 0552 void MainWindow::tabToolTipRequested(Sublime::View* view, Sublime::Container* container, int tab) 0553 { 0554 Q_D(MainWindow); 0555 0556 d->tabToolTipRequested(view, container, tab); 0557 } 0558 0559 void MainWindow::dockBarContextMenuRequested(Qt::DockWidgetArea area, const QPoint& position) 0560 { 0561 Q_D(MainWindow); 0562 0563 d->dockBarContextMenuRequested(area, position); 0564 } 0565 0566 void MainWindow::newTabRequested() 0567 { 0568 Q_D(MainWindow); 0569 0570 Sublime::MainWindow::newTabRequested(); 0571 0572 d->fileNew(); 0573 } 0574 0575 #include "moc_mainwindow.cpp"