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 > Tool Views > 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