diff --git a/kate/kateviewmanager.cpp b/kate/kateviewmanager.cpp index e69b86f1e..ff15a19f6 100644 --- a/kate/kateviewmanager.cpp +++ b/kate/kateviewmanager.cpp @@ -1,1255 +1,1256 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001 Anders Lund 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. */ //BEGIN Includes #include "kateviewmanager.h" #include "config.h" #include "kateapp.h" #include "katemainwindow.h" #include "kateviewspace.h" #include "kateupdatedisabler.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef KF5Activities_FOUND #include #endif #include #include //END Includes static const qint64 FileSizeAboveToAskUserIfProceedWithOpen = 10 * 1024 * 1024; // 10MB should suffice KateViewManager::KateViewManager(QWidget *parentW, KateMainWindow *parent) : QSplitter(parentW) , m_mainWindow(parent) , m_blockViewCreationAndActivation(false) , m_activeViewRunning(false) , m_minAge(0) , m_guiMergedView(nullptr) { // while init m_init = true; // we don't allow full collapse, see bug 366014 setChildrenCollapsible(false); // important, set them up, as we use them in other methodes setupActions(); KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); connect(this, &KateViewManager::viewChanged, this, &KateViewManager::slotViewChanged); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreatedViewManager, this, &KateViewManager::documentCreated); /** * before document is really deleted: cleanup all views! */ connect(KateApp::self()->documentManager(), &KateDocManager::documentWillBeDeleted , this, &KateViewManager::documentWillBeDeleted); /** * handle document deletion transactions * disable view creation in between * afterwards ensure we have views ;) */ connect(KateApp::self()->documentManager(), &KateDocManager::aboutToDeleteDocuments , this, &KateViewManager::aboutToDeleteDocuments); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted , this, &KateViewManager::documentsDeleted); // register all already existing documents m_blockViewCreationAndActivation = true; const QList &docs = KateApp::self()->documentManager()->documentList(); foreach(KTextEditor::Document * doc, docs) { documentCreated(doc); } m_blockViewCreationAndActivation = false; // init done m_init = false; } KateViewManager::~KateViewManager() { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } } void KateViewManager::setupActions() { /** * view splitting */ m_splitViewVert = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_vert")); m_splitViewVert->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_splitViewVert->setText(i18n("Split Ve&rtical")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewVert, Qt::CTRL + Qt::SHIFT + Qt::Key_L); connect(m_splitViewVert, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceVert); m_splitViewVert->setWhatsThis(i18n("Split the currently active view vertically into two views.")); m_splitViewHoriz = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_horiz")); m_splitViewHoriz->setIcon(QIcon::fromTheme(QStringLiteral("view-split-top-bottom"))); m_splitViewHoriz->setText(i18n("Split &Horizontal")); m_mainWindow->actionCollection()->setDefaultShortcut(m_splitViewHoriz, Qt::CTRL + Qt::SHIFT + Qt::Key_T); connect(m_splitViewHoriz, &QAction::triggered, this, &KateViewManager::slotSplitViewSpaceHoriz); m_splitViewHoriz->setWhatsThis(i18n("Split the currently active view horizontally into two views.")); m_closeView = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_current_space")); m_closeView->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeView->setText(i18n("Cl&ose Current View")); m_mainWindow->actionCollection()->setDefaultShortcut(m_closeView, Qt::CTRL + Qt::SHIFT + Qt::Key_R); connect(m_closeView, &QAction::triggered, this, &KateViewManager::slotCloseCurrentViewSpace, Qt::QueuedConnection); m_closeView->setWhatsThis(i18n("Close the currently active split view")); m_closeOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_close_others")); m_closeOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-close"))); m_closeOtherViews->setText(i18n("Close Inactive Views")); connect(m_closeOtherViews, &QAction::triggered, this, &KateViewManager::slotCloseOtherViews, Qt::QueuedConnection); m_closeOtherViews->setWhatsThis(i18n("Close every view but the active one")); m_hideOtherViews = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_hide_others")); m_hideOtherViews->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen"))); m_hideOtherViews->setText(i18n("Hide Inactive Views")); m_hideOtherViews->setCheckable(true); connect(m_hideOtherViews, &QAction::triggered, this, &KateViewManager::slotHideOtherViews, Qt::QueuedConnection); m_hideOtherViews->setWhatsThis(i18n("Hide every view but the active one")); m_toggleSplitterOrientation = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_toggle")); m_toggleSplitterOrientation->setText(i18n("Toggle Orientation")); connect(m_toggleSplitterOrientation, &QAction::triggered, this, &KateViewManager::toggleSplitterOrientation, Qt::QueuedConnection); m_toggleSplitterOrientation->setWhatsThis(i18n("Toggles the orientation of the current split view")); goNext = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_next_split_view")); goNext->setText(i18n("Next Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goNext, Qt::Key_F8); connect(goNext, &QAction::triggered, this, &KateViewManager::activateNextView); goNext->setWhatsThis(i18n("Make the next split view the active one.")); goPrev = m_mainWindow->actionCollection()->addAction(QStringLiteral("go_prev_split_view")); goPrev->setText(i18n("Previous Split View")); m_mainWindow->actionCollection()->setDefaultShortcut(goPrev, Qt::SHIFT + Qt::Key_F8); connect(goPrev, &QAction::triggered, this, &KateViewManager::activatePrevView); goPrev->setWhatsThis(i18n("Make the previous split view the active one.")); QAction * a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_right")); a->setText(i18n("Move Splitter Right")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterRight); a->setWhatsThis(i18n("Move the splitter of the current view to the right")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_left")); a->setText(i18n("Move Splitter Left")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterLeft); a->setWhatsThis(i18n("Move the splitter of the current view to the left")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_up")); a->setText(i18n("Move Splitter Up")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterUp); a->setWhatsThis(i18n("Move the splitter of the current view up")); a = m_mainWindow->actionCollection()->addAction(QStringLiteral("view_split_move_down")); a->setText(i18n("Move Splitter Down")); connect(a, &QAction::triggered, this, &KateViewManager::moveSplitterDown); a->setWhatsThis(i18n("Move the splitter of the current view down")); } void KateViewManager::updateViewSpaceActions() { m_closeView->setEnabled(m_viewSpaceList.count() > 1); m_closeOtherViews->setEnabled(m_viewSpaceList.count() > 1); m_toggleSplitterOrientation->setEnabled(m_viewSpaceList.count() > 1); goNext->setEnabled(m_viewSpaceList.count() > 1); goPrev->setEnabled(m_viewSpaceList.count() > 1); } void KateViewManager::slotDocumentNew() { createView(); } void KateViewManager::slotDocumentOpen() { // try to start dialog in useful dir: either dir of current doc or last used one KTextEditor::View * const cv = activeView(); QUrl startUrl = cv ? cv->document()->url() : QUrl(); if (startUrl.isValid()) { m_lastOpenDialogUrl = startUrl; } else { startUrl = m_lastOpenDialogUrl; } const QList urls = QFileDialog::getOpenFileUrls(m_mainWindow, i18n("Open File"), startUrl); /** * emit size warning, for local files */ QString fileListWithTooLargeFiles; Q_FOREACH(const QUrl & url, urls) { if (!url.isLocalFile()) { continue; } const auto size = QFile(url.toLocalFile()).size(); if (size > FileSizeAboveToAskUserIfProceedWithOpen) { fileListWithTooLargeFiles += QStringLiteral("
  • %1 (%2MB)
  • ").arg(url.fileName()).arg(size / 1024 / 1024); } } if (!fileListWithTooLargeFiles.isEmpty()) { const QString text = i18n("

    You are attempting to open one or more large files:

      %1

    Do you want to proceed?

    Beware that kate may stop responding for some time when opening large files.

    " , fileListWithTooLargeFiles); const auto ret = KMessageBox::warningYesNo(this, text, i18n("Opening Large File"), KStandardGuiItem::cont(), KStandardGuiItem::stop()); if (ret == KMessageBox::No) { return; } } // activate view of last opened document KateDocumentInfo docInfo; docInfo.openedByUser = true; if (KTextEditor::Document *lastID = openUrls(urls, QString(), false, docInfo)) { activateView(lastID); } } void KateViewManager::slotDocumentClose(KTextEditor::Document *document) { bool shutdownKate = m_mainWindow->modCloseAfterLast() && KateApp::self()->documentManager()->documentList().size() == 1; // close document if (KateApp::self()->documentManager()->closeDocument(document) && shutdownKate) { KateApp::self()->shutdownKate(m_mainWindow); } } void KateViewManager::slotDocumentClose() { // no active view, do nothing if (!activeView()) { return; } slotDocumentClose(activeView()->document()); } KTextEditor::Document *KateViewManager::openUrl(const QUrl &url, const QString &encoding, bool activate, bool isTempFile, const KateDocumentInfo &docInfo) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding, isTempFile, docInfo); if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } if (activate) { activateView(doc); } return doc; } KTextEditor::Document *KateViewManager::openUrls(const QList &urls, const QString &encoding, bool isTempFile, const KateDocumentInfo &docInfo) { QList docs = KateApp::self()->documentManager()->openUrls(urls, encoding, isTempFile, docInfo); foreach(const KTextEditor::Document * doc, docs) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } return docs.isEmpty() ? nullptr : docs.last(); } KTextEditor::View *KateViewManager::openUrlWithView(const QUrl &url, const QString &encoding) { KTextEditor::Document *doc = KateApp::self()->documentManager()->openUrl(url, encoding); if (!doc) { return nullptr; } if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } activateView(doc); return activeView(); } void KateViewManager::openUrl(const QUrl &url) { openUrl(url, QString()); } KateMainWindow *KateViewManager::mainWindow() { return m_mainWindow; } void KateViewManager::documentCreated(KTextEditor::Document *doc) { // forward to currently active view space activeViewSpace()->registerDocument(doc); // to update open recent files on saving connect(doc, &KTextEditor::Document::documentSavedOrUploaded, this, &KateViewManager::documentSavedOrUploaded); if (m_blockViewCreationAndActivation) { return; } if (!activeView()) { activateView(doc); } /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(activeView()->document(), vs); } } } void KateViewManager::aboutToDeleteDocuments(const QList &) { /** * block view creation until the transaction is done * this shall not stack! */ Q_ASSERT (!m_blockViewCreationAndActivation); m_blockViewCreationAndActivation = true; /** * disable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(false); } void KateViewManager::documentsDeleted(const QList &) { /** * again allow view creation */ m_blockViewCreationAndActivation = false; /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } /** * enable updates hard (we can't use KateUpdateDisabler here, we have delayed signal */ mainWindow()->setUpdatesEnabled(true); } void KateViewManager::documentSavedOrUploaded(KTextEditor::Document *doc, bool) { if (!doc->url().isEmpty()) { m_mainWindow->fileOpenRecent()->addUrl(doc->url()); } } KTextEditor::View *KateViewManager::createView(KTextEditor::Document *doc, KateViewSpace *vs) { if (m_blockViewCreationAndActivation) { return nullptr; } // create doc if (!doc) { doc = KateApp::self()->documentManager()->createDoc(); } /** * create view, registers its XML gui itself * pass the view the correct main window */ KTextEditor::View *view = (vs ? vs : activeViewSpace())->createView(doc); /** * remember this view, active == false, min age set * create activity resource */ m_views[view].active = false; m_views[view].lruAge = m_minAge--; #ifdef KF5Activities_FOUND m_views[view].activityResource = new KActivities::ResourceInstance(view->window()->winId(), view); m_views[view].activityResource->setUri(doc->url()); #endif // disable settings dialog action delete view->actionCollection()->action(QStringLiteral("set_confdlg")); delete view->actionCollection()->action(QStringLiteral("editor_options")); connect(view, SIGNAL(dropEventPass(QDropEvent*)), mainWindow(), SLOT(slotDropEvent(QDropEvent*))); connect(view, &KTextEditor::View::focusIn, this, &KateViewManager::activateSpace); viewCreated(view); if (!vs) { activateView(view); } return view; } bool KateViewManager::deleteView(KTextEditor::View *view) { if (!view) { return true; } KateViewSpace *viewspace = static_cast(view->parentWidget()->parentWidget()); viewspace->removeView(view); /** * deregister if needed */ if (m_guiMergedView == view) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } // remove view from mapping and memory !! m_views.remove(view); delete view; return true; } KateViewSpace *KateViewManager::activeViewSpace() { for (QList::const_iterator it = m_viewSpaceList.constBegin(); it != m_viewSpaceList.constEnd(); ++it) { if ((*it)->isActiveSpace()) { return *it; } } // none active, so use the first we grab if (!m_viewSpaceList.isEmpty()) { m_viewSpaceList.first()->setActive(true); return m_viewSpaceList.first(); } Q_ASSERT(false); return nullptr; } KTextEditor::View *KateViewManager::activeView() { if (m_activeViewRunning) { return nullptr; } m_activeViewRunning = true; QHashIterator it(m_views); while (it.hasNext()) { it.next(); if (it.value().active) { m_activeViewRunning = false; return it.key(); } } // if we get to here, no view isActive() // first, try to get one from activeViewSpace() KateViewSpace *vs = activeViewSpace(); if (vs && vs->currentView()) { activateView(vs->currentView()); m_activeViewRunning = false; return vs->currentView(); } - // last attempt: just pick first - if (!m_views.isEmpty()) { - KTextEditor::View *v = m_views.begin().key(); + // last attempt: pick MRU view + auto views = sortedViews(); + if (!views.isEmpty()) { + KTextEditor::View *v = views.front(); activateView(v); m_activeViewRunning = false; return v; } m_activeViewRunning = false; // no views exists! return nullptr; } void KateViewManager::setActiveSpace(KateViewSpace *vs) { if (activeViewSpace()) { activeViewSpace()->setActive(false); } vs->setActive(true); } void KateViewManager::setActiveView(KTextEditor::View *view) { if (activeView()) { m_views[activeView()].active = false; } if (view) { m_views[view].active = true; } } void KateViewManager::activateSpace(KTextEditor::View *v) { if (!v) { return; } KateViewSpace *vs = static_cast(v->parentWidget()->parentWidget()); if (!vs->isActiveSpace()) { setActiveSpace(vs); activateView(v); } } void KateViewManager::reactivateActiveView() { KTextEditor::View *view = activeView(); if (view) { m_views[view].active = false; activateView(view); } } void KateViewManager::activateView(KTextEditor::View *view) { if (!view) { return; } Q_ASSERT (m_views.contains(view)); if (!m_views[view].active) { // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); if (!activeViewSpace()->showView(view)) { // since it wasn't found, give'em a new one createView(view->document()); return; } setActiveView(view); bool toolbarVisible = mainWindow()->toolBar()->isVisible(); if (toolbarVisible) { mainWindow()->toolBar()->hide(); // hide to avoid toolbar flickering } if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } if (!m_blockViewCreationAndActivation) { mainWindow()->guiFactory()->addClient(view); m_guiMergedView = view; } if (toolbarVisible) { mainWindow()->toolBar()->show(); } // remember age of this view m_views[view].lruAge = m_minAge--; emit viewChanged(view); #ifdef KF5Activities_FOUND // inform activity manager m_views[view].activityResource->setUri(view->document()->url()); m_views[view].activityResource->notifyFocusedIn(); #endif } } KTextEditor::View *KateViewManager::activateView(KTextEditor::Document *d) { // no doc with this id found if (!d) { return activeView(); } // activate existing view if possible if (activeViewSpace()->showView(d)) { activateView(activeViewSpace()->currentView()); return activeView(); } // create new view otherwise createView(d); return activeView(); } void KateViewManager::slotViewChanged() { if (activeView() && !activeView()->hasFocus()) { activeView()->setFocus(); } } void KateViewManager::activateNextView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) + 1; if (i >= m_viewSpaceList.count()) { i = 0; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::activatePrevView() { int i = m_viewSpaceList.indexOf(activeViewSpace()) - 1; if (i < 0) { i = m_viewSpaceList.count() - 1; } setActiveSpace(m_viewSpaceList.at(i)); activateView(m_viewSpaceList.at(i)->currentView()); } void KateViewManager::documentWillBeDeleted(KTextEditor::Document *doc) { /** * collect all views of that document that belong to this manager */ QList closeList; Q_FOREACH (KTextEditor::View *v, doc->views()) { if (m_views.contains(v)) { closeList.append(v); } } while (!closeList.isEmpty()) { deleteView(closeList.takeFirst()); } } void KateViewManager::closeView(KTextEditor::View *view) { /** * kill view we want to kill */ deleteView(view); /** * try to have active view around! */ if (!activeView() && !KateApp::self()->documentManager()->documentList().isEmpty()) { createView(KateApp::self()->documentManager()->documentList().last()); } /** * if we have one now, show them in all viewspaces that got empty! */ if (KTextEditor::View *const newActiveView = activeView()) { /** * check if we have any empty viewspaces and give them a view */ Q_FOREACH(KateViewSpace * vs, m_viewSpaceList) { if (!vs->currentView()) { createView(newActiveView->document(), vs); } } emit viewChanged(newActiveView); } } void KateViewManager::splitViewSpace(KateViewSpace *vs, // = 0 Qt::Orientation o) // = Qt::Horizontal { // emergency: fallback to activeViewSpace, and if still invalid, abort if (!vs) { vs = activeViewSpace(); } if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // index where to insert new splitter/viewspace const int index = currentSplitter->indexOf(vs); // create new viewspace KateViewSpace *vsNew = new KateViewSpace(this); // only 1 children -> we are the root container. So simply set the orientation // and add the new view space, then correct the sizes to 50%:50% if (currentSplitter->count() == 1) { if (currentSplitter->orientation() != o) { currentSplitter->setOrientation(o); } QList sizes = currentSplitter->sizes(); sizes << (sizes.first() - currentSplitter->handleWidth()) / 2; sizes[0] = sizes[1]; currentSplitter->insertWidget(index, vsNew); currentSplitter->setSizes(sizes); } else { // create a new QSplitter and replace vs with the splitter. vs and newVS are // the new children of the new QSplitter QSplitter *newContainer = new QSplitter(o); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); QList currentSizes = currentSplitter->sizes(); newContainer->addWidget(vs); newContainer->addWidget(vsNew); currentSplitter->insertWidget(index, newContainer); newContainer->show(); // fix sizes of children of old and new splitter currentSplitter->setSizes(currentSizes); QList newSizes = newContainer->sizes(); newSizes[0] = (newSizes[0] + newSizes[1] - newContainer->handleWidth()) / 2; newSizes[1] = newSizes[0]; newContainer->setSizes(newSizes); } m_viewSpaceList.append(vsNew); activeViewSpace()->setActive(false); vsNew->setActive(true); vsNew->show(); createView((KTextEditor::Document *)activeView()->document()); updateViewSpaceActions(); } void KateViewManager::closeViewSpace(KTextEditor::View *view) { KateViewSpace *space; if (view) { space = static_cast(view->parentWidget()->parentWidget()); } else { space = activeViewSpace(); } removeViewSpace(space); } void KateViewManager::toggleSplitterOrientation() { KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } // get current splitter, and abort if null QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter || (currentSplitter->count() == 1)) { return; } // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // toggle orientation if (currentSplitter->orientation() == Qt::Horizontal) { currentSplitter->setOrientation(Qt::Vertical); } else { currentSplitter->setOrientation(Qt::Horizontal); } } bool KateViewManager::viewsInSameViewSpace(KTextEditor::View *view1, KTextEditor::View *view2) { if (!view1 || !view2) { return false; } if (m_viewSpaceList.size() == 1) { return true; } KateViewSpace *vs1 = static_cast(view1->parentWidget()->parentWidget()); KateViewSpace *vs2 = static_cast(view2->parentWidget()->parentWidget()); return vs1 && (vs1 == vs2); } void KateViewManager::removeViewSpace(KateViewSpace *viewspace) { // abort if viewspace is 0 if (!viewspace) { return; } // abort if this is the last viewspace if (m_viewSpaceList.count() < 2) { return; } // get current splitter QSplitter *currentSplitter = qobject_cast(viewspace->parentWidget()); // no splitter found, bah if (!currentSplitter) { return; } // // 1. get LRU document list from current viewspace // 2. delete current view space // 3. add LRU documents from deleted viewspace to new active viewspace // // backup LRU list const QVector lruDocumntsList = viewspace->lruDocumentList(); // avoid flicker KateUpdateDisabler disableUpdates (mainWindow()); // delete views of the viewspace while (viewspace->currentView()) { deleteView(viewspace->currentView()); } // cu viewspace m_viewSpaceList.removeAt(m_viewSpaceList.indexOf(viewspace)); delete viewspace; // at this point, the splitter has exactly 1 child Q_ASSERT(currentSplitter->count() == 1); // if we are not the root splitter, move the child one level up and delete // the splitter then. if (currentSplitter != this) { // get parent splitter QSplitter *parentSplitter = qobject_cast(currentSplitter->parentWidget()); // only do magic if found ;) if (parentSplitter) { int index = parentSplitter->indexOf(currentSplitter); // save current splitter size, as the removal of currentSplitter looses the info QList parentSizes = parentSplitter->sizes(); parentSplitter->insertWidget(index, currentSplitter->widget(0)); delete currentSplitter; // now restore the sizes again parentSplitter->setSizes(parentSizes); } } else if (QSplitter *splitter = qobject_cast(currentSplitter->widget(0))) { // we are the root splitter and have only one child, which is also a splitter // -> eliminate the redundant splitter and move both children into the root splitter QList sizes = splitter->sizes(); // adapt splitter orientation to the splitter we are about to delete currentSplitter->setOrientation(splitter->orientation()); currentSplitter->addWidget(splitter->widget(0)); currentSplitter->addWidget(splitter->widget(0)); delete splitter; currentSplitter->setSizes(sizes); } // merge docuemnts of closed view space activeViewSpace()->mergeLruList(lruDocumntsList); // find the view that is now active. KTextEditor::View *v = activeViewSpace()->currentView(); if (v) { activateView(v); } updateViewSpaceActions(); emit viewChanged(v); } void KateViewManager::slotCloseOtherViews() { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); foreach(KateViewSpace * v, m_viewSpaceList) { if (active != v) { removeViewSpace(v); } } } void KateViewManager::slotHideOtherViews(bool hideOthers) { // avoid flicker KateUpdateDisabler disableUpdates(mainWindow()); const KateViewSpace *active = activeViewSpace(); foreach(KateViewSpace * v, m_viewSpaceList) { if (active != v) { v->setVisible(!hideOthers); } } // disable the split actions, if we are in single-view-mode m_splitViewVert->setDisabled(hideOthers); m_splitViewHoriz->setDisabled(hideOthers); m_closeView->setDisabled(hideOthers); m_closeOtherViews->setDisabled(hideOthers); m_toggleSplitterOrientation->setDisabled(hideOthers); } /** * session config functions */ void KateViewManager::saveViewConfiguration(KConfigGroup &config) { // set Active ViewSpace to 0, just in case there is none active (would be // strange) and config somehow has previous value set config.writeEntry("Active ViewSpace", 0); m_splitterIndex = 0; saveSplitterConfig(this, config.config(), config.name()); } void KateViewManager::restoreViewConfiguration(const KConfigGroup &config) { /** * remove the single client that is registered at the factory, if any */ if (m_guiMergedView) { mainWindow()->guiFactory()->removeClient(m_guiMergedView); m_guiMergedView = nullptr; } /** * delete viewspaces, they will delete the views */ qDeleteAll(m_viewSpaceList); m_viewSpaceList.clear(); /** * delete mapping of now deleted views */ m_views.clear(); /** * kill all previous existing sub-splitters, just to be sure * e.g. important if one restores a config in an existing window with some splitters */ while (count() > 0) { delete widget(0); } // reset lru history, too! m_minAge = 0; // start recursion for the root splitter (Splitter 0) restoreSplitter(config.config(), config.name() + QStringLiteral("-Splitter 0"), this, config.name()); // finally, make the correct view from the last session active int lastViewSpace = config.readEntry("Active ViewSpace", 0); if (lastViewSpace > m_viewSpaceList.size()) { lastViewSpace = 0; } if (lastViewSpace >= 0 && lastViewSpace < m_viewSpaceList.size()) { setActiveSpace(m_viewSpaceList.at(lastViewSpace)); // activate correct view (wish #195435, #188764) activateView(m_viewSpaceList.at(lastViewSpace)->currentView()); // give view the focus to avoid focus stealing by toolviews / plugins m_viewSpaceList.at(lastViewSpace)->currentView()->setFocus(); } // emergency if (m_viewSpaceList.empty()) { // kill bad children while (count()) { delete widget(0); } KateViewSpace *vs = new KateViewSpace(this, nullptr); addWidget(vs); vs->setActive(true); m_viewSpaceList.append(vs); /** * activate at least one document! */ activateView(KateApp::self()->documentManager()->documentList().last()); if (!vs->currentView()) { createView(activeView()->document(), vs); } } updateViewSpaceActions(); } QString KateViewManager::saveSplitterConfig(QSplitter *s, KConfigBase *configBase, const QString &viewConfGrp) { /** * avoid to export invisible view spaces * else they will stick around for ever in sessions * bug 358266 - code initially done during load * bug 381433 - moved code to save */ /** * create new splitter name, might be not used */ const auto grp = QString(viewConfGrp + QStringLiteral("-Splitter %1")).arg(m_splitterIndex); ++m_splitterIndex; // a QSplitter has two children, either QSplitters and/or KateViewSpaces // special case: root splitter might have only one child (just for info) QStringList childList; const auto sizes = s->sizes(); for (int it = 0; it < s->count(); ++it) { // skip empty sized invisible ones, if not last one, we need one thing at least if ((sizes[it] == 0) && ((it + 1 < s->count()) || !childList.empty())) continue; // For KateViewSpaces, ask them to save the file list. auto obj = s->widget(it); if (auto kvs = qobject_cast(obj)) { childList.append(QString(viewConfGrp + QStringLiteral("-ViewSpace %1")).arg(m_viewSpaceList.indexOf(kvs))); kvs->saveConfig(configBase, m_viewSpaceList.indexOf(kvs), viewConfGrp); // save active viewspace if (kvs->isActiveSpace()) { KConfigGroup viewConfGroup(configBase, viewConfGrp); viewConfGroup.writeEntry("Active ViewSpace", m_viewSpaceList.indexOf(kvs)); } } // for QSplitters, recurse else if (auto splitter = qobject_cast(obj)) { childList.append(saveSplitterConfig(splitter, configBase, viewConfGrp)); } } // if only one thing, skip splitter config export, if not top splitter if ((s != this) && (childList.size() == 1)) return childList.at(0); // Save sizes, orient, children for this splitter KConfigGroup config(configBase, grp); config.writeEntry("Sizes", sizes); config.writeEntry("Orientation", int(s->orientation())); config.writeEntry("Children", childList); return grp; } void KateViewManager::restoreSplitter(const KConfigBase *configBase, const QString &group, QSplitter *parent, const QString &viewConfGrp) { KConfigGroup config(configBase, group); parent->setOrientation((Qt::Orientation)config.readEntry("Orientation", int(Qt::Horizontal))); const QStringList children = config.readEntry("Children", QStringList()); for (const auto& str : children) { // for a viewspace, create it and open all documents therein. if (str.startsWith(viewConfGrp + QStringLiteral("-ViewSpace"))) { KateViewSpace *vs = new KateViewSpace(this, nullptr); m_viewSpaceList.append(vs); // make active so that the view created in restoreConfig has this // new view space as parent. setActiveSpace(vs); parent->addWidget(vs); vs->restoreConfig(this, configBase, str); vs->show(); } else { // for a splitter, recurse auto newContainer = new QSplitter(parent); // we don't allow full collapse, see bug 366014 newContainer->setChildrenCollapsible(false); restoreSplitter(configBase, str, newContainer, viewConfGrp); } } // set sizes parent->setSizes(config.readEntry("Sizes", QList())); parent->show(); } void KateViewManager::moveSplitter(Qt::Key key, int repeats) { if (repeats < 1) { return; } KateViewSpace *vs = activeViewSpace(); if (!vs) { return; } QSplitter *currentSplitter = qobject_cast(vs->parentWidget()); if (!currentSplitter) { return; } if (currentSplitter->count() == 1) { return; } int move = 4 * repeats; // try to use font height in pixel to move splitter { KTextEditor::Attribute::Ptr attrib(vs->currentView()->defaultStyleAttribute(KTextEditor::dsNormal)); QFontMetrics fm(attrib->font()); move = fm.height() * repeats; } QWidget *currentWidget = (QWidget *)vs; bool foundSplitter = false; // find correct splitter to be moved while (currentSplitter && currentSplitter->count() != 1) { if (currentSplitter->orientation() == Qt::Horizontal && (key == Qt::Key_Right || key == Qt::Key_Left)) { foundSplitter = true; } if (currentSplitter->orientation() == Qt::Vertical && (key == Qt::Key_Up || key == Qt::Key_Down)) { foundSplitter = true; } // if the views within the current splitter can be resized, resize them if (foundSplitter) { QList currentSizes = currentSplitter->sizes(); int index = currentSplitter->indexOf(currentWidget); if ((index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) || (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down))) { currentSizes[index] -= move; } if ((index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) || (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up))) { currentSizes[index] += move; } if (index == 0 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index + 1] -= move; } if (index == 0 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index + 1] += move; } if (index == 1 && (key == Qt::Key_Right || key == Qt::Key_Down)) { currentSizes[index - 1] += move; } if (index == 1 && (key == Qt::Key_Left || key == Qt::Key_Up)) { currentSizes[index - 1] -= move; } currentSplitter->setSizes(currentSizes); break; } currentWidget = (QWidget *)currentSplitter; // the parent of the current splitter will become the current splitter currentSplitter = qobject_cast(currentSplitter->parentWidget()); } } diff --git a/kate/kateviewspace.cpp b/kate/kateviewspace.cpp index 94d9e11a2..6238b2f82 100644 --- a/kate/kateviewspace.cpp +++ b/kate/kateviewspace.cpp @@ -1,743 +1,752 @@ /* This file is part of the KDE project Copyright (C) 2001 Christoph Cullmann Copyright (C) 2001 Joseph Wenninger Copyright (C) 2001, 2005 Anders Lund 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 "kateviewspace.h" #include "katemainwindow.h" #include "kateviewmanager.h" #include "katedocmanager.h" #include "kateapp.h" #include "katefileactions.h" #include "katesessionmanager.h" #include "katedebug.h" #include "katetabbar.h" #include "kactioncollection.h" #include "kateupdatedisabler.h" #include #include #include #include #include #include #include #include #include #include #include #include //BEGIN KateViewSpace KateViewSpace::KateViewSpace(KateViewManager *viewManager, QWidget *parent, const char *name) : QWidget(parent) , m_viewManager(viewManager) , m_isActiveSpace(false) { setObjectName(QString::fromLatin1(name)); QVBoxLayout *layout = new QVBoxLayout(this); layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); //BEGIN tab bar QHBoxLayout *hLayout = new QHBoxLayout(); hLayout->setSpacing(0); hLayout->setContentsMargins(0, 0, 0, 0); // add tab bar m_tabBar = new KateTabBar(this); connect(m_tabBar, &KateTabBar::currentChanged, this, &KateViewSpace::changeView); connect(m_tabBar, &KateTabBar::moreTabsRequested, this, &KateViewSpace::addTabs); connect(m_tabBar, &KateTabBar::lessTabsRequested, this, &KateViewSpace::removeTabs); connect(m_tabBar, &KateTabBar::closeTabRequested, this, &KateViewSpace::closeTabRequest, Qt::QueuedConnection); connect(m_tabBar, &KateTabBar::contextMenuRequest, this, &KateViewSpace::showContextMenu, Qt::QueuedConnection); connect(m_tabBar, &KateTabBar::newTabRequested, this, &KateViewSpace::createNewDocument); connect(m_tabBar, SIGNAL(activateViewSpaceRequested()), this, SLOT(makeActive())); hLayout->addWidget(m_tabBar); // add quick open m_quickOpen = new QToolButton(this); m_quickOpen->setAutoRaise(true); KAcceleratorManager::setNoAccel(m_quickOpen); m_quickOpen->installEventFilter(this); // on click, active this view space hLayout->addWidget(m_quickOpen); // forward tab bar quick open action to globa quick open action QAction * bridge = new QAction(QIcon::fromTheme(QStringLiteral("tab-duplicate")), i18nc("indicator for more documents", "+%1", 100), this); m_quickOpen->setDefaultAction(bridge); QAction * quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open")); Q_ASSERT(quickOpen); bridge->setToolTip(quickOpen->toolTip()); bridge->setWhatsThis(i18n("Click here to switch to the Quick Open view.")); connect(bridge, &QAction::triggered, quickOpen, &QAction::trigger); // add vertical split view space m_split = new QToolButton(this); m_split->setAutoRaise(true); m_split->setPopupMode(QToolButton::InstantPopup); m_split->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_vert"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_split_horiz"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_current_space"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_close_others"))); m_split->addAction(m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_hide_others"))); m_split->setWhatsThis(i18n("Control view space splitting")); m_split->installEventFilter(this); // on click, active this view space hLayout->addWidget(m_split); layout->addLayout(hLayout); //END tab bar stack = new QStackedWidget(this); stack->setFocus(); stack->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Expanding)); layout->addWidget(stack); m_group.clear(); // connect signal to hide/show statusbar connect(m_viewManager->mainWindow(), &KateMainWindow::statusBarToggled, this, &KateViewSpace::statusBarToggled); connect(m_viewManager->mainWindow(), &KateMainWindow::tabBarToggled, this, &KateViewSpace::tabBarToggled); // init the bars... statusBarToggled(); tabBarToggled(); // make sure we show correct number of hidden documents updateQuickOpen(); connect(KateApp::self()->documentManager(), &KateDocManager::documentCreated, this, &KateViewSpace::updateQuickOpen); connect(KateApp::self()->documentManager(), &KateDocManager::documentsDeleted, this, &KateViewSpace::updateQuickOpen); } bool KateViewSpace::eventFilter(QObject *obj, QEvent *event) { QToolButton *button = qobject_cast(obj); // quick open button: show tool tip with shortcut if (button == m_quickOpen && event->type() == QEvent::ToolTip) { QHelpEvent *e = static_cast(event); QAction *quickOpen = m_viewManager->mainWindow()->actionCollection()->action(QStringLiteral("view_quick_open")); Q_ASSERT(quickOpen); QToolTip::showText(e->globalPos(), button->toolTip() + QStringLiteral(" (%1)").arg(quickOpen->shortcut().toString()), button); return true; } // quick open button: What's This if (button == m_quickOpen && event->type() == QEvent::WhatsThis) { QHelpEvent *e = static_cast(event); const int hiddenDocs = hiddenDocuments(); QString helpText = (hiddenDocs == 0) ? i18n("Click here to switch to the Quick Open view.") : i18np("Currently, there is one more document open. To see all open documents, switch to the Quick Open view by clicking here.", "Currently, there are %1 more documents open. To see all open documents, switch to the Quick Open view by clicking here.", hiddenDocs); QWhatsThis::showText(e->globalPos(), helpText, m_quickOpen); return true; } // on mouse press on view space bar tool buttons: activate this space if (button && ! isActiveSpace() && event->type() == QEvent::MouseButtonPress) { m_viewManager->setActiveSpace(this); if (currentView()) { m_viewManager->activateView(currentView()->document()); } } return false; } void KateViewSpace::statusBarToggled() { KateUpdateDisabler updatesDisabled (m_viewManager->mainWindow()); Q_FOREACH(KTextEditor::Document * doc, m_lruDocList) { if (m_docToView.contains(doc)) { m_docToView[doc]->setStatusBarEnabled(m_viewManager->mainWindow()->showStatusBar()); } } } QVector KateViewSpace::lruDocumentList() const { return m_lruDocList; } void KateViewSpace::mergeLruList(const QVector & lruList) { // merge lruList documents that are not in m_lruDocList QVectorIterator it(lruList); it.toBack(); while (it.hasPrevious()) { KTextEditor::Document *doc = it.previous(); if (! m_lruDocList.contains(doc)) { registerDocument(doc, false); } } } void KateViewSpace::tabBarToggled() { KateUpdateDisabler updatesDisabled (m_viewManager->mainWindow()); m_tabBar->setVisible(m_viewManager->mainWindow()->showTabBar()); m_split->setVisible(m_viewManager->mainWindow()->showTabBar()); m_quickOpen->setVisible(m_viewManager->mainWindow()->showTabBar()); } KTextEditor::View *KateViewSpace::createView(KTextEditor::Document *doc) { // should only be called if a view does not yet exist Q_ASSERT(! m_docToView.contains(doc)); /** * Create a fresh view */ KTextEditor::View *v = doc->createView(stack, m_viewManager->mainWindow()->wrapper()); // set status bar to right state v->setStatusBarEnabled(m_viewManager->mainWindow()->showStatusBar()); // restore the config of this view if possible if (!m_group.isEmpty()) { QString fn = v->document()->url().toString(); if (! fn.isEmpty()) { QString vgroup = QStringLiteral("%1 %2").arg(m_group, fn); KateSession::Ptr as = KateApp::self()->sessionManager()->activeSession(); if (as->config() && as->config()->hasGroup(vgroup)) { KConfigGroup cg(as->config(), vgroup); v->readSessionConfig(cg); } } } // register document, it is shown below through showView() then if (! m_lruDocList.contains(doc)) { registerDocument(doc); Q_ASSERT(m_lruDocList.contains(doc)); } // insert View into stack stack->addWidget(v); m_docToView[doc] = v; showView(v); return v; } void KateViewSpace::removeView(KTextEditor::View *v) { // remove view mappings Q_ASSERT(m_docToView.contains(v->document())); m_docToView.remove(v->document()); // ...and now: remove from view space stack->removeWidget(v); + + // switch to most recently used rather than letting stack choose one + // (last element could well be v->document() being removed here) + for (auto it = m_lruDocList.rbegin(); it != m_lruDocList.rend(); ++it) { + if (m_docToView.contains(*it)) { + showView(*it); + break; + } + } } bool KateViewSpace::showView(KTextEditor::Document *document) { const int index = m_lruDocList.lastIndexOf(document); if (index < 0) { return false; } if (! m_docToView.contains(document)) { return false; } KTextEditor::View *kv = m_docToView[document]; // move view to end of list m_lruDocList.removeAt(index); m_lruDocList.append(document); stack->setCurrentWidget(kv); kv->show(); // in case a tab does not exist, add one if (! m_docToTabId.contains(document)) { // if space is available, add button if (m_tabBar->count() < m_tabBar->maxTabCount()) { // just insert insertTab(0, document); } else { // remove "oldest" button and replace with new one Q_ASSERT(m_lruDocList.size() > m_tabBar->count()); // we need to subtract by 1 more, as we just added ourself to the end of the lru list! KTextEditor::Document * docToHide = m_lruDocList[m_lruDocList.size() - m_tabBar->maxTabCount() - 1]; Q_ASSERT(m_docToTabId.contains(docToHide)); removeTab(docToHide, false); // add new one always at the beginning insertTab(0, document); } } // follow current view Q_ASSERT(m_docToTabId.contains(document)); m_tabBar->setCurrentTab(m_docToTabId.value(document, -1)); return true; } void KateViewSpace::changeView(int id) { KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); // make sure we open the view in this view space if (! isActiveSpace()) { m_viewManager->setActiveSpace(this); } // tell the view manager to show the view m_viewManager->activateView(doc); } KTextEditor::View *KateViewSpace::currentView() { // might be 0 if the stack contains no view return (KTextEditor::View *)stack->currentWidget(); } bool KateViewSpace::isActiveSpace() { return m_isActiveSpace; } void KateViewSpace::setActive(bool active) { m_isActiveSpace = active; m_tabBar->setActive(active); } void KateViewSpace::makeActive(bool focusCurrentView) { if (! isActiveSpace()) { m_viewManager->setActiveSpace(this); if (focusCurrentView && currentView()) { m_viewManager->activateView(currentView()->document()); } } Q_ASSERT(isActiveSpace()); } void KateViewSpace::insertTab(int index, KTextEditor::Document * doc) { // doc should be in the lru list Q_ASSERT(m_lruDocList.contains(doc)); // doc should not have a id Q_ASSERT(! m_docToTabId.contains(doc)); const int id = m_tabBar->insertTab(index, doc->documentName()); m_tabBar->setTabToolTip(id, doc->url().toDisplayString()); m_docToTabId[doc] = id; updateDocumentState(doc); connect(doc, &KTextEditor::Document::documentNameChanged, this, &KateViewSpace::updateDocumentName); connect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateViewSpace::updateDocumentUrl); connect(doc, &KTextEditor::Document::modifiedChanged, this, &KateViewSpace::updateDocumentState); } int KateViewSpace::removeTab(KTextEditor::Document * doc, bool documentDestroyed) { // // WARNING: removeTab() is also called from documentDestroyed(). // Therefore, is may be that doc is half destroyed already. // Therefore, do not access any KTextEditor::Document functions here! // Only access QObject functions! // Q_ASSERT(m_docToTabId.contains(doc)); const int id = m_docToTabId.value(doc, -1); const int removeIndex = m_tabBar->removeTab(id); m_docToTabId.remove(doc); if (!documentDestroyed) { disconnect(doc, &KTextEditor::Document::documentNameChanged, this, &KateViewSpace::updateDocumentName); disconnect(doc, &KTextEditor::Document::documentUrlChanged, this, &KateViewSpace::updateDocumentUrl); disconnect(doc, &KTextEditor::Document::modifiedChanged, this, &KateViewSpace::updateDocumentState); } return removeIndex; } void KateViewSpace::removeTabs(int count) { const int start = count; /// remove @p count tabs from the tab bar, as they do not all fit while (count > 0) { const int tabCount = m_tabBar->count(); KTextEditor::Document * removeDoc = m_lruDocList[m_lruDocList.size() - tabCount]; removeTab(removeDoc, false); Q_ASSERT(! m_docToTabId.contains(removeDoc)); --count; } // make sure quick open shows the correct number of hidden documents if (start != count) { updateQuickOpen(); } } void KateViewSpace::addTabs(int count) { const int start = count; /// @p count tabs still fit into the tab bar: add as man as possible while (count > 0) { const int tabCount = m_tabBar->count(); if (m_lruDocList.size() <= tabCount) { break; } insertTab(tabCount, m_lruDocList[m_lruDocList.size() - tabCount - 1]); --count; } // make sure quick open shows the correct number of hidden documents if (start != count) { updateQuickOpen(); } } void KateViewSpace::registerDocument(KTextEditor::Document *doc, bool append) { // at this point, the doc should be completely unknown Q_ASSERT(! m_lruDocList.contains(doc)); Q_ASSERT(! m_docToView.contains(doc)); Q_ASSERT(! m_docToTabId.contains(doc)); if (append) { m_lruDocList.append(doc); } else { // prepending == merge doc of closed viewspace m_lruDocList.prepend(doc); } connect(doc, &QObject::destroyed, this, &KateViewSpace::documentDestroyed); // if space is available, add button if (m_tabBar->count() < m_tabBar->maxTabCount()) { insertTab(0, doc); updateQuickOpen(); } else if (append) { // remove "oldest" button and replace with new one Q_ASSERT(m_lruDocList.size() > m_tabBar->count()); KTextEditor::Document * docToHide = m_lruDocList[m_lruDocList.size() - m_tabBar->maxTabCount() - 1]; Q_ASSERT(m_docToTabId.contains(docToHide)); removeTab(docToHide, false); // add new one at removed position insertTab(0, doc); } } void KateViewSpace::documentDestroyed(QObject *doc) { // WARNING: this pointer is half destroyed KTextEditor::Document *invalidDoc = static_cast(doc); Q_ASSERT(m_lruDocList.contains(invalidDoc)); m_lruDocList.remove(m_lruDocList.indexOf(invalidDoc)); // disconnect entirely disconnect(doc, nullptr, this, nullptr); // case: there was no view created yet, but still a button was added if (m_docToTabId.contains(invalidDoc)) { removeTab(invalidDoc, true); // maybe show another tab button in its stead if (m_lruDocList.size() >= m_tabBar->maxTabCount() && m_tabBar->count() < m_tabBar->maxTabCount() ) { KTextEditor::Document * docToShow = m_lruDocList[m_lruDocList.size() - m_tabBar->count() - 1]; Q_ASSERT(! m_docToTabId.contains(docToShow)); // add tab that now fits into the bar insertTab(m_tabBar->count(), docToShow); } } // at this point, the doc should be completely unknown Q_ASSERT(! m_lruDocList.contains(invalidDoc)); Q_ASSERT(! m_docToView.contains(invalidDoc)); Q_ASSERT(! m_docToTabId.contains(invalidDoc)); } void KateViewSpace::updateDocumentName(KTextEditor::Document *doc) { const int buttonId = m_docToTabId[doc]; Q_ASSERT(buttonId >= 0); m_tabBar->setTabText(buttonId, doc->documentName()); m_tabBar->setTabToolTip(buttonId, doc->url().toDisplayString()); } void KateViewSpace::updateDocumentUrl(KTextEditor::Document *doc) { const int buttonId = m_docToTabId[doc]; Q_ASSERT(buttonId >= 0); m_tabBar->setTabUrl(buttonId, doc->url()); } void KateViewSpace::updateDocumentState(KTextEditor::Document *doc) { QIcon icon; if (doc->isModified()) { icon = QIcon::fromTheme(QStringLiteral("document-save")); } Q_ASSERT(m_docToTabId.contains(doc)); const int buttonId = m_docToTabId[doc]; m_tabBar->setTabIcon(buttonId, icon); } void KateViewSpace::closeTabRequest(int id) { KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); m_viewManager->slotDocumentClose(doc); } void KateViewSpace::createNewDocument() { // make sure we open the view in this view space if (! isActiveSpace()) { m_viewManager->setActiveSpace(this); } // create document KTextEditor::Document *doc = KateApp::self()->documentManager()->createDoc(); // tell the view manager to show the document m_viewManager->activateView(doc); } void KateViewSpace::updateQuickOpen() { const int hiddenDocs = hiddenDocuments(); if (hiddenDocs == 0) { m_quickOpen->setToolButtonStyle(Qt::ToolButtonIconOnly); m_quickOpen->defaultAction()->setText(QString()); } else { m_quickOpen->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); m_quickOpen->defaultAction()->setText(i18nc("indicator for more documents", "+%1", hiddenDocs)); } } void KateViewSpace::focusPrevTab() { const int id = m_tabBar->prevTab(); if (id >= 0) { changeView(id); } } void KateViewSpace::focusNextTab() { const int id = m_tabBar->nextTab(); if (id >= 0) { changeView(id); } } int KateViewSpace::hiddenDocuments() const { const int hiddenDocs = KateApp::self()->documents().count() - m_tabBar->count(); Q_ASSERT(hiddenDocs >= 0); return hiddenDocs; } void KateViewSpace::showContextMenu(int id, const QPoint & globalPos) { // right now, show no context menu on empty tab bar space if (id < 0) { return; } KTextEditor::Document *doc = m_docToTabId.key(id); Q_ASSERT(doc); auto addActionFromCollection = [this](QMenu* menu, const char* action_name) { QAction* action = m_viewManager->mainWindow()->action(action_name); return menu->addAction(action->icon(), action->text()); }; QMenu menu(this); QAction *aCloseTab = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close")), i18n("&Close Document")); QAction *aCloseOthers = menu.addAction(QIcon::fromTheme(QStringLiteral("tab-close-other")), i18n("Close Other &Documents")); menu.addSeparator(); QAction *aCopyPath = addActionFromCollection(&menu, "file_copy_filepath"); QAction *aOpenFolder = addActionFromCollection(&menu, "file_open_containing_folder"); QAction *aFileProperties = addActionFromCollection(&menu, "file_properties"); menu.addSeparator(); QAction *aRenameFile = addActionFromCollection(&menu, "file_rename"); QAction *aDeleteFile = addActionFromCollection(&menu, "file_delete"); menu.addSeparator(); QMenu *mCompareWithActive = new QMenu(i18n("Compare with active document"), &menu); mCompareWithActive->setIcon(QIcon::fromTheme(QStringLiteral("kompare"))); menu.addMenu(mCompareWithActive); if (KateApp::self()->documentManager()->documentList().count() < 2) { aCloseOthers->setEnabled(false); } if (doc->url().isEmpty()) { aCopyPath->setEnabled(false); aOpenFolder->setEnabled(false); aRenameFile->setEnabled(false); aDeleteFile->setEnabled(false); aFileProperties->setEnabled(false); mCompareWithActive->setEnabled(false); } auto activeDocument = KTextEditor::Editor::instance()->application()->activeMainWindow()->activeView()->document(); // used for mCompareWithActive which is used with another tab which is not active // both documents must have urls and must not be the same to have the compare feature enabled if (activeDocument->url().isEmpty() || activeDocument == doc) { mCompareWithActive->setEnabled(false); } if (mCompareWithActive->isEnabled()) { for (auto&& diffTool : KateFileActions::supportedDiffTools()) { QAction *compareAction = mCompareWithActive->addAction(diffTool); compareAction->setData(diffTool); } } QAction *choice = menu.exec(globalPos); if (!choice) { return; } if (choice == aCloseTab) { closeTabRequest(id); } else if (choice == aCloseOthers) { KateApp::self()->documentManager()->closeOtherDocuments(doc); } else if (choice == aCopyPath) { KateFileActions::copyFilePathToClipboard(doc); } else if (choice == aOpenFolder) { KateFileActions::openContainingFolder(doc); } else if (choice == aFileProperties) { KateFileActions::openFilePropertiesDialog(doc); } else if (choice == aRenameFile) { KateFileActions::renameDocumentFile(this, doc); } else if (choice == aDeleteFile) { KateFileActions::deleteDocumentFile(this, doc); } else if (choice->parent() == mCompareWithActive) { QString actionData = choice->data().toString(); // name of the executable of the diff program if (!KateFileActions::compareWithExternalProgram(activeDocument, doc, actionData)) { QMessageBox::information(this, i18n("Could not start program"), i18n("The selected program could not be started. Maybe it is not installed."), QMessageBox::StandardButton::Ok); } } } void KateViewSpace::saveConfig(KConfigBase *config, int myIndex , const QString &viewConfGrp) { // qCDebug(LOG_KATE)<<"KateViewSpace::saveConfig("< views; QStringList lruList; Q_FOREACH(KTextEditor::Document* doc, m_lruDocList) { lruList << doc->url().toString(); if (m_docToView.contains(doc)) { views.append(m_docToView[doc]); } } KConfigGroup group(config, groupname); group.writeEntry("Documents", lruList); group.writeEntry("Count", views.count()); if (currentView()) { group.writeEntry("Active View", currentView()->document()->url().toString()); } // Save file list, including cursor position in this instance. int idx = 0; for (QVector::iterator it = views.begin(); it != views.end(); ++it) { if (!(*it)->document()->url().isEmpty()) { group.writeEntry(QStringLiteral("View %1").arg(idx), (*it)->document()->url().toString()); // view config, group: "ViewSpace url" QString vgroup = QStringLiteral("%1 %2").arg(groupname, (*it)->document()->url().toString()); KConfigGroup viewGroup(config, vgroup); (*it)->writeSessionConfig(viewGroup); } ++idx; } } void KateViewSpace::restoreConfig(KateViewManager *viewMan, const KConfigBase *config, const QString &groupname) { KConfigGroup group(config, groupname); // restore Document lru list so that all tabs from the last session reappear const QStringList lruList = group.readEntry("Documents", QStringList()); for (int i = 0; i < lruList.size(); ++i) { // ignore non-existing documents and documents we already added to the LRU list // no wild m_lruDocList modifications to keep list + tabs in sync even for restore auto doc = KateApp::self()->documentManager()->findDocument(QUrl(lruList[i])); if (doc && !m_lruDocList.contains(doc)) { registerDocument(doc); Q_ASSERT(m_lruDocList.contains(doc)); } } // restore active view properties const QString fn = group.readEntry("Active View"); if (!fn.isEmpty()) { KTextEditor::Document *doc = KateApp::self()->documentManager()->findDocument(QUrl(fn)); if (doc) { // view config, group: "ViewSpace url" QString vgroup = QStringLiteral("%1 %2").arg(groupname, fn); KConfigGroup configGroup(config, vgroup); auto view = viewMan->createView(doc, this); if (view) { view->readSessionConfig(configGroup); } } } // avoid empty view space if (m_docToView.isEmpty()) { viewMan->createView (KateApp::self()->documentManager()->documentList().first(), this); } m_group = groupname; // used for restroing view configs later } //END KateViewSpace