1 /*  SPDX-License-Identifier: LGPL-2.0-or-later
2 
3     SPDX-FileCopyrightText: 2005 Christoph Cullmann <cullmann@kde.org>
4     SPDX-FileCopyrightText: 2002, 2003 Joseph Wenninger <jowenn@kde.org>
5 
6     GUIClient partly based on ktoolbarhandler.cpp: SPDX-FileCopyrightText: 2002 Simon Hausmann <hausmann@kde.org>
7 
8     SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "katemdi.h"
12 
13 // #include "katedebug.h"
14 
15 #include <KActionCollection>
16 #include <KActionMenu>
17 #include <KConfigGroup>
18 #include <KLocalizedString>
19 #include <KMessageBox>
20 #include <KSharedConfig>
21 #include <KToolBar>
22 #include <KWindowConfig>
23 #include <KXMLGUIFactory>
24 
25 #include <QContextMenuEvent>
26 #include <QDomDocument>
27 #include <QLabel>
28 #include <QMenu>
29 #include <QSizePolicy>
30 #include <QStyle>
31 #include <QTimer>
32 #include <QVBoxLayout>
33 
34 namespace KateMDI
35 {
36 // BEGIN TOGGLETOOLVIEWACTION
37 //
ToggleToolViewAction(const QString & text,ToolView * tv,QObject * parent)38 ToggleToolViewAction::ToggleToolViewAction(const QString &text, ToolView *tv, QObject *parent)
39     : KToggleAction(text, parent)
40     , m_tv(tv)
41 {
42     connect(this, &ToggleToolViewAction::toggled, this, &ToggleToolViewAction::slotToggled);
43     connect(m_tv, &ToolView::toolVisibleChanged, this, &ToggleToolViewAction::toolVisibleChanged);
44 
45     setChecked(m_tv->toolVisible());
46 }
47 
toolVisibleChanged(bool)48 void ToggleToolViewAction::toolVisibleChanged(bool)
49 {
50     if (isChecked() != m_tv->toolVisible()) {
51         setChecked(m_tv->toolVisible());
52     }
53 }
54 
slotToggled(bool t)55 void ToggleToolViewAction::slotToggled(bool t)
56 {
57     if (t) {
58         m_tv->mainWindow()->showToolView(m_tv);
59         m_tv->setFocus();
60     } else {
61         m_tv->mainWindow()->hideToolView(m_tv);
62     }
63 }
64 
65 // END TOGGLETOOLVIEWACTION
66 
67 // BEGIN GUICLIENT
68 
69 static const QString actionListName = QStringLiteral("kate_mdi_view_actions");
70 
GUIClient(MainWindow * mw)71 GUIClient::GUIClient(MainWindow *mw)
72     : QObject(mw)
73     , KXMLGUIClient(mw)
74     , m_mw(mw)
75 {
76     connect(m_mw->guiFactory(), &KXMLGUIFactory::clientAdded, this, &GUIClient::clientAdded);
77     const QString guiDescription = QStringLiteral(
78         ""
79         "<!DOCTYPE gui><gui name=\"kate_mdi_view_actions\">"
80         "<MenuBar>"
81         "    <Menu name=\"view\">"
82         "        <ActionList name=\"%1\" />"
83         "    </Menu>"
84         "</MenuBar>"
85         "</gui>");
86 
87     if (domDocument().documentElement().isNull()) {
88         QString completeDescription = guiDescription.arg(actionListName);
89 
90         setXML(completeDescription, false /*merge*/);
91     }
92 
93     m_toolMenu = new KActionMenu(i18n("Tool &Views"), this);
94     actionCollection()->addAction(QStringLiteral("kate_mdi_toolview_menu"), m_toolMenu);
95     m_showSidebarsAction = new KToggleAction(i18n("Show Side&bars"), this);
96     actionCollection()->addAction(QStringLiteral("kate_mdi_sidebar_visibility"), m_showSidebarsAction);
97     actionCollection()->setDefaultShortcut(m_showSidebarsAction, Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_F);
98 
99     m_showSidebarsAction->setChecked(m_mw->sidebarsVisible());
100     connect(m_showSidebarsAction, &KToggleAction::toggled, m_mw, &MainWindow::setSidebarsVisible);
101 
102     m_toolMenu->addAction(m_showSidebarsAction);
103     QAction *sep_act = new QAction(this);
104     sep_act->setSeparator(true);
105     m_toolMenu->addAction(sep_act);
106 
107     // read shortcuts
108     actionCollection()->setConfigGroup(QStringLiteral("Shortcuts"));
109     actionCollection()->readSettings();
110 
111     actionCollection()->addAssociatedWidget(m_mw);
112     const auto actions = actionCollection()->actions();
113     for (QAction *action : actions) {
114         action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
115     }
116 }
117 
updateSidebarsVisibleAction()118 void GUIClient::updateSidebarsVisibleAction()
119 {
120     m_showSidebarsAction->setChecked(m_mw->sidebarsVisible());
121 }
122 
registerToolView(ToolView * tv)123 void GUIClient::registerToolView(ToolView *tv)
124 {
125     QString aname = QLatin1String("kate_mdi_toolview_") + tv->id;
126 
127     // try to read the action shortcut
128     QList<QKeySequence> shortcuts;
129 
130     KSharedConfigPtr cfg = KSharedConfig::openConfig();
131     QString shortcutString = cfg->group("Shortcuts").readEntry(aname, QString());
132 
133     const auto shortcutStrings = shortcutString.split(QLatin1Char(';'));
134     for (const QString &shortcut : shortcutStrings) {
135         shortcuts << QKeySequence::fromString(shortcut);
136     }
137 
138     KToggleAction *a = new ToggleToolViewAction(i18n("Show %1", tv->text), tv, this);
139     actionCollection()->setDefaultShortcuts(a, shortcuts);
140     actionCollection()->addAction(aname, a);
141 
142     m_toolViewActions.push_back(a);
143     m_toolMenu->addAction(a);
144 
145     m_toolToAction.emplace(tv, a);
146 
147     updateActions();
148 }
149 
unregisterToolView(ToolView * tv)150 void GUIClient::unregisterToolView(ToolView *tv)
151 {
152     QAction *a = m_toolToAction[tv];
153 
154     if (!a) {
155         return;
156     }
157 
158     m_toolViewActions.erase(std::remove(m_toolViewActions.begin(), m_toolViewActions.end(), a), m_toolViewActions.end());
159     delete a;
160 
161     m_toolToAction.erase(tv);
162 
163     updateActions();
164 }
165 
clientAdded(KXMLGUIClient * client)166 void GUIClient::clientAdded(KXMLGUIClient *client)
167 {
168     if (client == this) {
169         updateActions();
170     }
171 }
172 
updateActions()173 void GUIClient::updateActions()
174 {
175     if (!factory()) {
176         return;
177     }
178 
179     unplugActionList(actionListName);
180 
181     QList<QAction *> addList;
182     addList.append(m_toolMenu);
183 
184     plugActionList(actionListName, addList);
185 }
186 
187 // END GUICLIENT
188 
189 // BEGIN TOOLVIEW
190 
ToolView(MainWindow * mainwin,Sidebar * sidebar,QWidget * parent)191 ToolView::ToolView(MainWindow *mainwin, Sidebar *sidebar, QWidget *parent)
192     : QFrame(parent)
193     , m_mainWin(mainwin)
194     , m_sidebar(sidebar)
195     , m_toolbar(nullptr)
196     , m_toolVisible(false)
197     , persistent(false)
198 {
199     // try to fix resize policy
200     QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred);
201     policy.setRetainSizeWhenHidden(true);
202     setSizePolicy(policy);
203 
204     // per default vbox layout
205     QVBoxLayout *layout = new QVBoxLayout(this);
206     layout->setContentsMargins(0, 0, 0, 0);
207     layout->setSpacing(0);
208     setLayout(layout);
209 
210     // toolbar to collect actions
211     m_toolbar = new KToolBar(this);
212     m_toolbar->setVisible(false);
213     m_toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
214 
215     // ensure reasonable icons sizes, like e.g. the quick-open and co. icons
216     // the normal toolbar sizes are TOO large, e.g. for scaled stuff even more!
217     const int iconSize = style()->pixelMetric(QStyle::PM_ButtonIconSize, nullptr, this);
218     m_toolbar->setIconSize(QSize(iconSize, iconSize));
219 }
220 
sizeHint() const221 QSize ToolView::sizeHint() const
222 {
223     return size();
224 }
225 
minimumSizeHint() const226 QSize ToolView::minimumSizeHint() const
227 {
228     return QSize(160, 160);
229 }
230 
~ToolView()231 ToolView::~ToolView()
232 {
233     m_mainWin->toolViewDeleted(this);
234 }
235 
setToolVisible(bool vis)236 void ToolView::setToolVisible(bool vis)
237 {
238     if (m_toolVisible == vis) {
239         return;
240     }
241 
242     m_toolVisible = vis;
243     Q_EMIT toolVisibleChanged(m_toolVisible);
244 }
245 
toolVisible() const246 bool ToolView::toolVisible() const
247 {
248     return m_toolVisible;
249 }
250 
childEvent(QChildEvent * ev)251 void ToolView::childEvent(QChildEvent *ev)
252 {
253     // set the widget to be focus proxy if possible
254     if ((ev->type() == QEvent::ChildAdded) && qobject_cast<QWidget *>(ev->child())) {
255         QWidget *widget = qobject_cast<QWidget *>(ev->child());
256         setFocusProxy(widget);
257         layout()->addWidget(widget);
258     }
259 
260     QFrame::childEvent(ev);
261 }
262 
actionEvent(QActionEvent * event)263 void ToolView::actionEvent(QActionEvent *event)
264 {
265     QFrame::actionEvent(event);
266     if (event->type() == QEvent::ActionAdded) {
267         m_toolbar->addAction(event->action());
268     } else if (event->type() == QEvent::ActionRemoved) {
269         m_toolbar->removeAction(event->action());
270     }
271     m_toolbar->setVisible(!m_toolbar->actions().isEmpty());
272 }
273 
274 // END TOOLVIEW
275 
276 // BEGIN SIDEBAR
277 
Sidebar(KMultiTabBar::KMultiTabBarPosition pos,MainWindow * mainwin,QWidget * parent)278 Sidebar::Sidebar(KMultiTabBar::KMultiTabBarPosition pos, MainWindow *mainwin, QWidget *parent)
279     : KMultiTabBar(pos, parent)
280     , m_mainWin(mainwin)
281     , m_splitter(nullptr)
282     , m_ownSplit(nullptr)
283     , m_lastSize(0)
284 {
285     hide();
286 }
287 
setSplitter(QSplitter * sp)288 void Sidebar::setSplitter(QSplitter *sp)
289 {
290     // if splitter was set before, disconnect handler for collapse
291     if (m_splitter) {
292         disconnect(m_splitter, &QSplitter::splitterMoved, this, &Sidebar::handleCollapse);
293         const int ownSplitIndex = m_splitter->indexOf(m_ownSplit);
294         for (int i : {ownSplitIndex, ownSplitIndex + 1}) {
295             if (i > 0 && i < m_splitter->count()) {
296                 m_splitter->handle(i)->removeEventFilter(this);
297             }
298         }
299     }
300 
301     m_splitter = sp;
302     m_ownSplit = new QSplitter((position() == KMultiTabBar::Top || position() == KMultiTabBar::Bottom) ? Qt::Horizontal : Qt::Vertical, m_splitter);
303     m_ownSplit->setChildrenCollapsible(false);
304     m_ownSplit->hide();
305 
306     // Add resize placeholder (an empty QLabel) so that sidebar will still be resizable after collapse
307     // see Sidebar::handleCollapse
308     m_resizePlaceholder = new QLabel();
309     m_ownSplit->addWidget(m_resizePlaceholder);
310     m_resizePlaceholder->hide();
311     m_resizePlaceholder->setMinimumSize(QSize(160, 160)); // Same minimum size set in ToolView::minimumSizeHint
312 
313     connect(m_splitter, &QSplitter::splitterMoved, this, &Sidebar::handleCollapse);
314 }
315 
updateLastSizeOnResize()316 void Sidebar::updateLastSizeOnResize()
317 {
318     const int splitHandleIndex = qMin(m_splitter->indexOf(m_ownSplit) + 1, m_splitter->count() - 1);
319     // this method requires that a splitter handle for resizing the sidebar exists
320     Q_ASSERT(splitHandleIndex > 0);
321     m_splitter->handle(splitHandleIndex)->installEventFilter(this);
322 }
323 
addWidget(const QIcon & icon,const QString & text,ToolView * widget)324 ToolView *Sidebar::addWidget(const QIcon &icon, const QString &text, ToolView *widget)
325 {
326     static int id = 0;
327 
328     if (widget) {
329         if (widget->sidebar() == this) {
330             return widget;
331         }
332 
333         widget->sidebar()->removeWidget(widget);
334     }
335 
336     int newId = ++id;
337 
338     appendTab(icon, newId, text);
339 
340     if (!widget) {
341         widget = new ToolView(m_mainWin, this, m_ownSplit);
342         widget->hide();
343         widget->icon = icon;
344         widget->text = text;
345     } else {
346         widget->hide();
347         widget->setParent(m_ownSplit);
348         widget->m_sidebar = this;
349     }
350 
351     // save its pos ;)
352     widget->persistent = false;
353 
354     m_idToWidget.emplace(newId, widget);
355     m_widgetToId.emplace(widget, newId);
356     m_toolviews.push_back(widget);
357 
358     // widget => size, for correct size restoration after hide/show
359     // starts with invalid size
360     m_widgetToSize.emplace(widget, QSize());
361 
362     show();
363 
364     connect(tab(newId), SIGNAL(clicked(int)), this, SLOT(tabClicked(int)));
365     tab(newId)->installEventFilter(this);
366     tab(newId)->setToolTip(QString());
367 
368     return widget;
369 }
370 
removeWidget(ToolView * widget)371 bool Sidebar::removeWidget(ToolView *widget)
372 {
373     if (m_widgetToId.find(widget) == m_widgetToId.end()) {
374         return false;
375     }
376 
377     removeTab(m_widgetToId[widget]);
378 
379     m_idToWidget.erase(m_widgetToId[widget]);
380     m_widgetToId.erase(widget);
381     m_widgetToSize.erase(widget);
382     m_toolviews.erase(std::remove(m_toolviews.begin(), m_toolviews.end(), widget), m_toolviews.end());
383 
384     bool anyVis = false;
385     for (const auto &[id, wid] : m_idToWidget) {
386         if (wid->isVisible()) {
387             anyVis = true;
388             break;
389         }
390     }
391 
392     if (m_idToWidget.empty()) {
393         m_ownSplit->hide();
394         hide();
395     } else if (!anyVis) {
396         m_ownSplit->hide();
397     }
398 
399     return true;
400 }
401 
showWidget(ToolView * widget)402 bool Sidebar::showWidget(ToolView *widget)
403 {
404     if (m_widgetToId.find(widget) == m_widgetToId.end()) {
405         return false;
406     }
407 
408     bool unfixSize = false;
409     // hide other non-persistent views
410     for (const auto &[id, wid] : m_idToWidget) {
411         if (wid != widget && !wid->persistent) {
412             hideWidget(wid);
413         }
414         // lock persistents' size while show/hide reshuffle happens
415         // (could also make this behavior per-widget configurable)
416         if (wid->persistent) {
417             const auto size = wid->size();
418             wid->setMaximumSize(size);
419             wid->setMinimumSize(size);
420             unfixSize = true;
421         }
422     }
423 
424     setTab(m_widgetToId[widget], true);
425 
426     /**
427      * resize to right size again and show, else artifacts
428      */
429     if (m_widgetToSize[widget].isValid()) {
430         widget->resize(m_widgetToSize[widget]);
431     }
432 
433     /**
434      * resize to right size again and show, else artifacts
435      * same as for widget, both needed
436      */
437     if (m_preHideSize.isValid()) {
438         widget->resize(m_preHideSize);
439         m_ownSplit->resize(m_preHideSize);
440     }
441     m_ownSplit->show();
442     widget->show();
443 
444     // release persistent size again
445     // (later on, when all event processing has happened)
446     auto func = [this]() {
447         auto wsizes = m_ownSplit->sizes();
448         for (const auto &[id, widget] : m_idToWidget) {
449             Q_UNUSED(id)
450             widget->setMinimumSize(QSize(0, 0));
451             widget->setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
452         }
453         m_ownSplit->setSizes(wsizes);
454     };
455     if (unfixSize) {
456         QTimer::singleShot(0, this, func);
457     }
458 
459     // ensure that the sidebar is expanded
460     expandSidebar(widget);
461 
462     /**
463      * we are visible again!
464      */
465     widget->setToolVisible(true);
466     return true;
467 }
468 
hideWidget(ToolView * widget)469 bool Sidebar::hideWidget(ToolView *widget)
470 {
471     if (m_widgetToId.find(widget) == m_widgetToId.end()) {
472         return false;
473     }
474 
475     updateLastSize();
476 
477     bool anyVis = false;
478     for (const auto &[id, wid] : m_idToWidget) {
479         if (wid == widget) {
480             if (widget->isVisible()) {
481                 m_widgetToSize[widget] = widget->size();
482             }
483         } else if (wid->isVisible()) {
484             anyVis = true;
485             break;
486         }
487     }
488 
489     widget->hide();
490 
491     // lower tab
492     setTab(m_widgetToId[widget], false);
493 
494     if (!anyVis) {
495         if (m_ownSplit->isVisible()) {
496             m_preHideSize = m_ownSplit->size();
497         }
498         m_ownSplit->hide();
499     } else {
500         // some toolviews are still visible, so we must ensure the sidebar is expanded
501         expandSidebar(widget);
502     }
503 
504     widget->setToolVisible(false);
505     return true;
506 }
507 
isCollapsed()508 bool Sidebar::isCollapsed()
509 {
510     if (!m_splitter) {
511         // sidebar still has no splitter set
512         return false;
513     }
514 
515     const int ownSplitIndex = m_splitter->indexOf(m_ownSplit);
516     if (ownSplitIndex == -1) {
517         // m_ownSplit should already be a child of m_splitter if it is set, but we check here just in case
518         return false;
519     }
520 
521     return m_splitter->sizes().at(ownSplitIndex) == 0;
522 }
523 
handleCollapse(int pos,int index)524 void Sidebar::handleCollapse(int pos, int index)
525 {
526     Q_UNUSED(pos);
527     if (!m_splitter) {
528         return;
529     }
530 
531     // Verify that the sidebar really belongs to m_splitter
532     // and that we are handling the correct/matching sidebar
533     // 0 | 1 | 2  <- ownSplitIndex
534     //   1   2    <- index (of splitters, represented by |)
535     // ownSplitIndex should only be equal to index or (index - 1)
536     const int ownSplitIndex = m_splitter->indexOf(m_ownSplit);
537     if (ownSplitIndex == -1 || qAbs(index - ownSplitIndex) > 1) {
538         return;
539     }
540 
541     if (isCollapsed()) {
542         if (m_isPreviouslyCollapsed) {
543             return;
544         }
545 
546         // If the sidebar is collapsed, we need to hide the activated plugin-views since they are,
547         // visually speaking, hidden from the user but technically isVisible() would still return true
548         for (const auto &[id, wid] : m_idToWidget) {
549             if (wid->isVisible()) {
550                 wid->hide();
551             }
552         }
553 
554         // show resize placeholder again, otherwise the sidebar splitter won't be manually resizable/expandable
555         m_resizePlaceholder->show();
556         m_isPreviouslyCollapsed = true;
557     } else if (m_isPreviouslyCollapsed && m_resizePlaceholder->isVisible()) {
558         // If the sidebar is manually expanded again, we need to show the activated plugin-views again
559         for (const auto &[id, wid] : m_idToWidget) {
560             if (isTabRaised(id)) {
561                 wid->show();
562             }
563         }
564 
565         m_resizePlaceholder->hide();
566         m_isPreviouslyCollapsed = false;
567     }
568 }
569 
expandSidebar(ToolView * widget)570 void Sidebar::expandSidebar(ToolView *widget)
571 {
572     if (m_widgetToId.find(widget) == m_widgetToId.end()) {
573         return;
574     }
575 
576     // If the sidebar is collapsed, we need to resize it so that it does not become "stuck" in the collapsed state
577     // see BUG: 439535
578     // NOTE: Even if the sidebar is expanded, this does not ensure that it is visible (might be hidden with hide() or setVisible(false))
579     if (m_isPreviouslyCollapsed && isCollapsed()) {
580         QList<int> wsizes = m_splitter->sizes();
581         const int ownSplitIndex = m_splitter->indexOf(m_ownSplit);
582         if (m_lastSize > 2) { // use last size if available (see updateLastSize())
583             wsizes[ownSplitIndex] = m_lastSize;
584         } else if (m_splitter->orientation() == Qt::Vertical) {
585             wsizes[ownSplitIndex] = qMax(widget->minimumSizeHint().height(), m_widgetToSize[widget].height());
586         } else {
587             wsizes[ownSplitIndex] = qMax(widget->minimumSizeHint().width(), m_widgetToSize[widget].width());
588         }
589 
590         // when the sidebar was collapsed, the activated widgets were hidden, so we need to show them again
591         // see Sidebar::handleCollapse
592         for (const auto &[id, wid] : m_idToWidget) {
593             if (isTabRaised(id)) {
594                 wid->show();
595             }
596         }
597 
598         m_resizePlaceholder->hide();
599         m_isPreviouslyCollapsed = false;
600         m_splitter->setSizes(wsizes);
601     }
602 }
603 
tabClicked(int i)604 void Sidebar::tabClicked(int i)
605 {
606     ToolView *w = m_idToWidget[i];
607 
608     if (!w) {
609         return;
610     }
611 
612     if (isTabRaised(i) || isCollapsed()) {
613         showWidget(w);
614         w->setFocus();
615         // When sidebar is collapsed and the toolview was pressed, we only expand
616         // the toolview/sidebar, so the tab must remain activated
617         setTab(m_widgetToId[w], true);
618     } else {
619         hideWidget(w);
620     }
621 }
622 
eventFilter(QObject * obj,QEvent * ev)623 bool Sidebar::eventFilter(QObject *obj, QEvent *ev)
624 {
625     if (ev->type() == QEvent::ContextMenu) {
626         QContextMenuEvent *e = static_cast<QContextMenuEvent *>(ev);
627         KMultiTabBarTab *bt = dynamic_cast<KMultiTabBarTab *>(obj);
628         if (bt) {
629             // qCDebug(LOG_KATE) << "Request for popup";
630 
631             m_popupButton = bt->id();
632 
633             ToolView *w = m_idToWidget[m_popupButton];
634 
635             if (w) {
636                 QMenu menu(this);
637 
638                 if (!w->plugin.isNull()) {
639                     if (w->plugin.data()->configPages() > 0) {
640                         menu.addAction(i18n("Configure ..."))->setData(20);
641                     }
642                 }
643 
644                 menu.addSection(QIcon::fromTheme(QStringLiteral("view_remove")), i18n("Behavior"));
645 
646                 menu.addAction(w->persistent ? QIcon::fromTheme(QStringLiteral("view-restore")) : QIcon::fromTheme(QStringLiteral("view-fullscreen")),
647                                w->persistent ? i18n("Make Non-Persistent") : i18n("Make Persistent"))
648                     ->setData(10);
649 
650                 menu.addSection(QIcon::fromTheme(QStringLiteral("move")), i18n("Move To"));
651 
652                 if (position() != 0) {
653                     menu.addAction(QIcon::fromTheme(QStringLiteral("go-previous")), i18n("Left Sidebar"))->setData(0);
654                 }
655 
656                 if (position() != 1) {
657                     menu.addAction(QIcon::fromTheme(QStringLiteral("go-next")), i18n("Right Sidebar"))->setData(1);
658                 }
659 
660                 if (position() != 2) {
661                     menu.addAction(QIcon::fromTheme(QStringLiteral("go-up")), i18n("Top Sidebar"))->setData(2);
662                 }
663 
664                 if (position() != 3) {
665                     menu.addAction(QIcon::fromTheme(QStringLiteral("go-down")), i18n("Bottom Sidebar"))->setData(3);
666                 }
667 
668                 connect(&menu, &QMenu::triggered, this, &Sidebar::buttonPopupActivate);
669 
670                 menu.exec(e->globalPos());
671 
672                 return true;
673             }
674         }
675     } else if (ev->type() == QEvent::MouseButtonRelease) {
676         // The sidebar's splitter handle handle is release, so we update the sidebar's size. See Sidebar::updateLastSizeOnResize
677         QMouseEvent *e = static_cast<QMouseEvent *>(ev);
678         if (e->button() == Qt::LeftButton) {
679             updateLastSize();
680         }
681     }
682 
683     return false;
684 }
685 
setVisible(bool visible)686 void Sidebar::setVisible(bool visible)
687 {
688     // visible==true means show-request
689     if (visible && (m_idToWidget.empty() || !m_mainWin->sidebarsVisible())) {
690         return;
691     }
692 
693     KMultiTabBar::setVisible(visible);
694 }
695 
buttonPopupActivate(QAction * a)696 void Sidebar::buttonPopupActivate(QAction *a)
697 {
698     int id = a->data().toInt();
699     ToolView *w = m_idToWidget[m_popupButton];
700 
701     if (!w) {
702         return;
703     }
704 
705     // move ids
706     if (id < 4) {
707         // move + show ;)
708         m_mainWin->moveToolView(w, static_cast<KMultiTabBar::KMultiTabBarPosition>(id));
709         m_mainWin->showToolView(w);
710     }
711 
712     // toggle persistent
713     if (id == 10) {
714         w->persistent = !w->persistent;
715     }
716 
717     // configure actionCollection
718     if (id == 20) {
719         if (!w->plugin.isNull()) {
720             if (w->plugin.data()->configPages() > 0) {
721                 Q_EMIT sigShowPluginConfigPage(w->plugin.data(), 0);
722             }
723         }
724     }
725 }
726 
updateLastSize()727 void Sidebar::updateLastSize()
728 {
729     QList<int> s = m_splitter->sizes();
730 
731     int i = 0;
732     if ((position() == KMultiTabBar::Right || position() == KMultiTabBar::Bottom)) {
733         i = 2;
734     }
735 
736     // little threshold
737     if (s[i] > 2) {
738         m_lastSize = s[i];
739     }
740 }
741 
742 class TmpToolViewSorter
743 {
744 public:
745     ToolView *tv;
746     unsigned int pos;
747 };
748 
restoreSession(KConfigGroup & config)749 void Sidebar::restoreSession(KConfigGroup &config)
750 {
751     // get the last correct placed toolview
752     int firstWrong = 0;
753     const int toolViewsCount = (int)m_toolviews.size();
754     for (; firstWrong < toolViewsCount; ++firstWrong) {
755         ToolView *tv = m_toolviews[firstWrong];
756 
757         int pos = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(tv->id), firstWrong);
758 
759         if (pos != firstWrong) {
760             break;
761         }
762     }
763 
764     // we need to reshuffle, ahhh :(
765     if (firstWrong < toolViewsCount) {
766         // first: collect the items to reshuffle
767         std::vector<TmpToolViewSorter> toSort;
768         for (int i = firstWrong; i < toolViewsCount; ++i) {
769             TmpToolViewSorter s;
770             s.tv = m_toolviews[i];
771             s.pos = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(m_toolviews[i]->id), i);
772             toSort.push_back(s);
773         }
774 
775         // now: sort the stuff we need to reshuffle
776         std::sort(toSort.begin(), toSort.end(), [](const TmpToolViewSorter &l, const TmpToolViewSorter &r) {
777             return l.pos < r.pos;
778         });
779 
780         // then: remove this items from the button bar
781         // do this backwards, to minimize the relayout efforts
782         for (int i = toolViewsCount - 1; i >= firstWrong; --i) {
783             removeTab(m_widgetToId[m_toolviews[i]]);
784         }
785 
786         // insert the reshuffled things in order :)
787         for (int i = 0; i < (int)toSort.size(); ++i) {
788             ToolView *tv = toSort[i].tv;
789 
790             m_toolviews[firstWrong + i] = tv;
791 
792             // readd the button
793             int newId = m_widgetToId[tv];
794             appendTab(tv->icon, newId, tv->text);
795             connect(tab(newId), &KMultiTabBarTab::clicked, this, &Sidebar::tabClicked);
796             tab(newId)->installEventFilter(this);
797             tab(newId)->setToolTip(QString());
798 
799             // reshuffle in splitter: move to last
800             m_ownSplit->addWidget(tv);
801         }
802     }
803 
804     // update last size if needed
805     updateLastSize();
806 
807     // restore the own splitter sizes
808     QList<int> s = config.readEntry(QStringLiteral("Kate-MDI-Sidebar-%1-Splitter").arg(position()), QList<int>());
809     m_ownSplit->setSizes(s);
810 
811     // show only correct toolviews, remember persistent values ;)
812     bool anyVis = false;
813     for (auto tv : m_toolviews) {
814         tv->persistent = config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Persistent").arg(tv->id), false);
815         tv->setToolVisible(config.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Visible").arg(tv->id), false));
816 
817         if (!anyVis) {
818             anyVis = tv->toolVisible();
819         }
820 
821         setTab(m_widgetToId[tv], tv->toolVisible());
822 
823         if (tv->toolVisible()) {
824             tv->show();
825         } else {
826             tv->hide();
827         }
828     }
829 
830     if (anyVis) {
831         m_ownSplit->show();
832         // if there are activated plugin-views but sidebar is collapsed, we must set m_isPreviouslyCollapsed to true so that it can be expanded
833         if (isCollapsed()) {
834             m_isPreviouslyCollapsed = true;
835         }
836     } else {
837         m_ownSplit->hide();
838     }
839 }
840 
saveSession(KConfigGroup & config)841 void Sidebar::saveSession(KConfigGroup &config)
842 {
843     // store the own splitter sizes
844     QList<int> s = m_ownSplit->sizes();
845     config.writeEntry(QStringLiteral("Kate-MDI-Sidebar-%1-Splitter").arg(position()), s);
846 
847     // store the data about all toolviews in this sidebar ;)
848     for (int i = 0; i < (int)m_toolviews.size(); ++i) {
849         ToolView *tv = m_toolviews[i];
850 
851         config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(tv->id), int(tv->sidebar()->position()));
852         config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Sidebar-Position").arg(tv->id), i);
853         config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Visible").arg(tv->id), tv->toolVisible());
854         config.writeEntry(QStringLiteral("Kate-MDI-ToolView-%1-Persistent").arg(tv->id), tv->persistent);
855     }
856 }
857 
858 // END SIDEBAR
859 
860 // BEGIN MAIN WINDOW
861 
MainWindow(QWidget * parentWidget)862 MainWindow::MainWindow(QWidget *parentWidget)
863     : KParts::MainWindow(parentWidget, Qt::Window)
864     , m_guiClient(new GUIClient(this))
865 {
866     // init the internal widgets
867     QFrame *hb = new QFrame(this);
868     QHBoxLayout *hlayout = new QHBoxLayout(hb);
869     hlayout->setContentsMargins(0, 0, 0, 0);
870     hlayout->setSpacing(0);
871 
872     setCentralWidget(hb);
873 
874     m_sidebars[KMultiTabBar::Left] = std::make_unique<Sidebar>(KMultiTabBar::Left, this, hb);
875     hlayout->addWidget(m_sidebars[KMultiTabBar::Left].get());
876 
877     m_hSplitter = new QSplitter(Qt::Horizontal, hb);
878     hlayout->addWidget(m_hSplitter);
879 
880     m_sidebars[KMultiTabBar::Left]->setSplitter(m_hSplitter);
881 
882     QFrame *vb = new QFrame(m_hSplitter);
883     QVBoxLayout *vlayout = new QVBoxLayout(vb);
884     vlayout->setContentsMargins(0, 0, 0, 0);
885     vlayout->setSpacing(0);
886 
887     m_hSplitter->setCollapsible(m_hSplitter->indexOf(vb), false);
888     m_hSplitter->setStretchFactor(m_hSplitter->indexOf(vb), 1);
889 
890     m_sidebars[KMultiTabBar::Top] = std::make_unique<Sidebar>(KMultiTabBar::Top, this, vb);
891     vlayout->addWidget(m_sidebars[KMultiTabBar::Top].get());
892 
893     m_vSplitter = new QSplitter(Qt::Vertical, vb);
894     vlayout->addWidget(m_vSplitter);
895 
896     m_sidebars[KMultiTabBar::Top]->setSplitter(m_vSplitter);
897 
898     m_centralWidget = new QWidget(m_vSplitter);
899     m_centralWidget->setLayout(new QVBoxLayout);
900     m_centralWidget->layout()->setSpacing(0);
901     m_centralWidget->layout()->setContentsMargins(0, 0, 0, 0);
902 
903     m_vSplitter->setCollapsible(m_vSplitter->indexOf(m_centralWidget), false);
904     m_vSplitter->setStretchFactor(m_vSplitter->indexOf(m_centralWidget), 1);
905 
906     m_sidebars[KMultiTabBar::Bottom] = std::make_unique<Sidebar>(KMultiTabBar::Bottom, this, vb);
907     vlayout->addWidget(m_sidebars[KMultiTabBar::Bottom].get());
908     m_sidebars[KMultiTabBar::Bottom]->setSplitter(m_vSplitter);
909 
910     m_sidebars[KMultiTabBar::Right] = std::make_unique<Sidebar>(KMultiTabBar::Right, this, hb);
911     hlayout->addWidget(m_sidebars[KMultiTabBar::Right].get());
912     m_sidebars[KMultiTabBar::Right]->setSplitter(m_hSplitter);
913 
914     for (const auto &sidebar : qAsConst(m_sidebars)) {
915         connect(sidebar.get(), &Sidebar::sigShowPluginConfigPage, this, &MainWindow::sigShowPluginConfigPage);
916         // all sidebar splitter handles are instantiated, so we can now safely call this
917         sidebar->updateLastSizeOnResize();
918     }
919 }
920 
~MainWindow()921 MainWindow::~MainWindow()
922 {
923     // cu toolviews
924     qDeleteAll(m_toolviews);
925 
926     // seems like we really should delete this by hand ;)
927     delete m_centralWidget;
928 }
929 
centralWidget() const930 QWidget *MainWindow::centralWidget() const
931 {
932     return m_centralWidget;
933 }
934 
createToolView(KTextEditor::Plugin * plugin,const QString & identifier,KMultiTabBar::KMultiTabBarPosition pos,const QIcon & icon,const QString & text)935 ToolView *MainWindow::createToolView(KTextEditor::Plugin *plugin,
936                                      const QString &identifier,
937                                      KMultiTabBar::KMultiTabBarPosition pos,
938                                      const QIcon &icon,
939                                      const QString &text)
940 {
941     if (m_idToWidget[identifier]) {
942         return nullptr;
943     }
944 
945     // try the restore config to figure out real pos
946     if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) {
947         KConfigGroup cg(m_restoreConfig, m_restoreGroup);
948         pos = static_cast<KMultiTabBar::KMultiTabBarPosition>(cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(identifier), int(pos)));
949     }
950 
951     ToolView *v = m_sidebars[pos]->addWidget(icon, text, nullptr);
952     v->id = identifier;
953     v->plugin = plugin;
954 
955     m_idToWidget.emplace(identifier, v);
956     m_toolviews.push_back(v);
957 
958     // register for menu stuff
959     m_guiClient->registerToolView(v);
960 
961     return v;
962 }
963 
toolView(const QString & identifier) const964 ToolView *MainWindow::toolView(const QString &identifier) const
965 {
966     auto it = m_idToWidget.find(identifier);
967     if (it != m_idToWidget.end()) {
968         return it->second;
969     }
970     return nullptr;
971 }
972 
toolViewDeleted(ToolView * widget)973 void MainWindow::toolViewDeleted(ToolView *widget)
974 {
975     if (!widget) {
976         return;
977     }
978 
979     if (widget->mainWindow() != this) {
980         return;
981     }
982 
983     // unregister from menu stuff
984     m_guiClient->unregisterToolView(widget);
985 
986     widget->sidebar()->removeWidget(widget);
987 
988     m_idToWidget.erase(widget->id);
989 
990     m_toolviews.erase(std::remove(m_toolviews.begin(), m_toolviews.end(), widget), m_toolviews.end());
991 }
992 
setSidebarsVisible(bool visible)993 void MainWindow::setSidebarsVisible(bool visible)
994 {
995     bool old_visible = m_sidebarsVisible;
996     m_sidebarsVisible = visible;
997 
998     m_sidebars[0]->setVisible(visible);
999     m_sidebars[1]->setVisible(visible);
1000     m_sidebars[2]->setVisible(visible);
1001     m_sidebars[3]->setVisible(visible);
1002 
1003     m_guiClient->updateSidebarsVisibleAction();
1004 
1005     // show information message box, if the users hides the sidebars
1006     if (old_visible && (!m_sidebarsVisible)) {
1007         KMessageBox::information(this,
1008                                  i18n("<qt>You are about to hide the sidebars. With "
1009                                       "hidden sidebars it is not possible to directly "
1010                                       "access the tool views with the mouse anymore, "
1011                                       "so if you need to access the sidebars again "
1012                                       "invoke <b>View &gt; Tool Views &gt; Show Sidebars</b> "
1013                                       "in the menu. It is still possible to show/hide "
1014                                       "the tool views with the assigned shortcuts.</qt>"),
1015                                  QString(),
1016                                  QStringLiteral("Kate hide sidebars notification message"));
1017     }
1018 }
1019 
sidebarsVisible() const1020 bool MainWindow::sidebarsVisible() const
1021 {
1022     return m_sidebarsVisible;
1023 }
1024 
setToolViewStyle(KMultiTabBar::KMultiTabBarStyle style)1025 void MainWindow::setToolViewStyle(KMultiTabBar::KMultiTabBarStyle style)
1026 {
1027     m_sidebars[0]->setStyle(style);
1028     m_sidebars[1]->setStyle(style);
1029     m_sidebars[2]->setStyle(style);
1030     m_sidebars[3]->setStyle(style);
1031 }
1032 
toolViewStyle() const1033 KMultiTabBar::KMultiTabBarStyle MainWindow::toolViewStyle() const
1034 {
1035     // all sidebars have the same style, so just take Top
1036     return m_sidebars[KMultiTabBar::Top]->tabStyle();
1037 }
1038 
moveToolView(ToolView * widget,KMultiTabBar::KMultiTabBarPosition pos)1039 bool MainWindow::moveToolView(ToolView *widget, KMultiTabBar::KMultiTabBarPosition pos)
1040 {
1041     if (!widget || widget->mainWindow() != this) {
1042         return false;
1043     }
1044 
1045     // try the restore config to figure out real pos
1046     if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) {
1047         KConfigGroup cg(m_restoreConfig, m_restoreGroup);
1048         pos = static_cast<KMultiTabBar::KMultiTabBarPosition>(cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(widget->id), int(pos)));
1049     }
1050 
1051     m_sidebars[pos]->addWidget(widget->icon, widget->text, widget);
1052 
1053     return true;
1054 }
1055 
showToolView(ToolView * widget)1056 bool MainWindow::showToolView(ToolView *widget)
1057 {
1058     if (!widget || widget->mainWindow() != this) {
1059         return false;
1060     }
1061 
1062     // skip this if happens during restoring, or we will just see flicker
1063     if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) {
1064         return true;
1065     }
1066 
1067     return widget->sidebar()->showWidget(widget);
1068 }
1069 
hideToolView(ToolView * widget)1070 bool MainWindow::hideToolView(ToolView *widget)
1071 {
1072     if (!widget || widget->mainWindow() != this) {
1073         return false;
1074     }
1075 
1076     // skip this if happens during restoring, or we will just see flicker
1077     if (m_restoreConfig && m_restoreConfig->hasGroup(m_restoreGroup)) {
1078         return true;
1079     }
1080 
1081     const bool ret = widget->sidebar()->hideWidget(widget);
1082     m_centralWidget->setFocus();
1083     return ret;
1084 }
1085 
startRestore(KConfigBase * config,const QString & group)1086 void MainWindow::startRestore(KConfigBase *config, const QString &group)
1087 {
1088     // first save this stuff
1089     m_restoreConfig = config;
1090     m_restoreGroup = group;
1091 
1092     if (!m_restoreConfig || !m_restoreConfig->hasGroup(m_restoreGroup)) {
1093         // if no config around, set already now sane default sizes
1094         // otherwise, set later in ::finishRestore(), since it does not work
1095         // if set already now (see bug #164438)
1096         QList<int> hs = (QList<int>() << 200 << 100 << 200);
1097         QList<int> vs = (QList<int>() << 150 << 100 << 200);
1098 
1099         m_sidebars[0]->setLastSize(hs[0]);
1100         m_sidebars[1]->setLastSize(hs[2]);
1101         m_sidebars[2]->setLastSize(vs[0]);
1102         m_sidebars[3]->setLastSize(vs[2]);
1103 
1104         m_hSplitter->setSizes(hs);
1105         m_vSplitter->setSizes(vs);
1106         return;
1107     }
1108 
1109     // apply size once, to get sizes ready ;)
1110     KConfigGroup cg(m_restoreConfig, m_restoreGroup);
1111     KWindowConfig::restoreWindowSize(windowHandle(), cg);
1112 
1113     setToolViewStyle(static_cast<KMultiTabBar::KMultiTabBarStyle>(cg.readEntry("Kate-MDI-Sidebar-Style", static_cast<int>(toolViewStyle()))));
1114     // after reading m_sidebarsVisible, update the GUI toggle action
1115     m_sidebarsVisible = cg.readEntry("Kate-MDI-Sidebar-Visible", true);
1116     m_guiClient->updateSidebarsVisibleAction();
1117 }
1118 
finishRestore()1119 void MainWindow::finishRestore()
1120 {
1121     if (!m_restoreConfig) {
1122         return;
1123     }
1124 
1125     if (m_restoreConfig->hasGroup(m_restoreGroup)) {
1126         // apply all settings, like toolbar pos and more ;)
1127         KConfigGroup cg(m_restoreConfig, m_restoreGroup);
1128         applyMainWindowSettings(cg);
1129 
1130         // reshuffle toolviews only if needed
1131         for (const auto tv : m_toolviews) {
1132             KMultiTabBar::KMultiTabBarPosition newPos = static_cast<KMultiTabBar::KMultiTabBarPosition>(
1133                 cg.readEntry(QStringLiteral("Kate-MDI-ToolView-%1-Position").arg(tv->id), int(tv->sidebar()->position())));
1134 
1135             if (tv->sidebar()->position() != newPos) {
1136                 moveToolView(tv, newPos);
1137             }
1138         }
1139 
1140         // restore the sidebars
1141         for (auto &sidebar : qAsConst(m_sidebars)) {
1142             sidebar->restoreSession(cg);
1143         }
1144 
1145         // restore splitter sizes
1146         QList<int> hs = (QList<int>() << 200 << 100 << 200);
1147         QList<int> vs = (QList<int>() << 150 << 100 << 200);
1148 
1149         // get main splitter sizes ;)
1150         hs = cg.readEntry("Kate-MDI-H-Splitter", hs);
1151         vs = cg.readEntry("Kate-MDI-V-Splitter", vs);
1152 
1153         m_sidebars[0]->setLastSize(hs[0]);
1154         m_sidebars[1]->setLastSize(hs[2]);
1155         m_sidebars[2]->setLastSize(vs[0]);
1156         m_sidebars[3]->setLastSize(vs[2]);
1157 
1158         m_hSplitter->setSizes(hs);
1159         m_vSplitter->setSizes(vs);
1160     }
1161 
1162     // clear this stuff, we are done ;)
1163     m_restoreConfig = nullptr;
1164     m_restoreGroup.clear();
1165 }
1166 
saveSession(KConfigGroup & config)1167 void MainWindow::saveSession(KConfigGroup &config)
1168 {
1169     saveMainWindowSettings(config);
1170 
1171     // save main splitter sizes ;)
1172     QList<int> hs = m_hSplitter->sizes();
1173     QList<int> vs = m_vSplitter->sizes();
1174 
1175     if (hs[0] <= 2 && !m_sidebars[0]->splitterVisible()) {
1176         hs[0] = m_sidebars[0]->lastSize();
1177     }
1178     if (hs[2] <= 2 && !m_sidebars[1]->splitterVisible()) {
1179         hs[2] = m_sidebars[1]->lastSize();
1180     }
1181     if (vs[0] <= 2 && !m_sidebars[2]->splitterVisible()) {
1182         vs[0] = m_sidebars[2]->lastSize();
1183     }
1184     if (vs[2] <= 2 && !m_sidebars[3]->splitterVisible()) {
1185         vs[2] = m_sidebars[3]->lastSize();
1186     }
1187 
1188     config.writeEntry("Kate-MDI-H-Splitter", hs);
1189     config.writeEntry("Kate-MDI-V-Splitter", vs);
1190 
1191     // save sidebar style
1192     config.writeEntry("Kate-MDI-Sidebar-Style", static_cast<int>(toolViewStyle()));
1193     config.writeEntry("Kate-MDI-Sidebar-Visible", m_sidebarsVisible);
1194 
1195     // save the sidebars
1196     for (auto &sidebar : qAsConst(m_sidebars)) {
1197         sidebar->saveSession(config);
1198     }
1199 }
1200 
1201 // END MAIN WINDOW
1202 
1203 } // namespace KateMDI
1204