1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
24 #include <QInputDialog>
26 #include <QMessageBox>
32 # include <kconfigwidgets_version.h>
34 # include <KConfigWidgets/KStandardAction>
35 # include <KWidgetsAddons/KToggleFullScreenAction>
36 # include <KXmlGui/KHelpMenu>
37 # include <KXmlGui/KShortcutsDialog>
38 # include <KXmlGui/KToolBar>
47 #include "actioncollection.h"
48 #include "awaylogfilter.h"
49 #include "awaylogview.h"
50 #include "bufferhotlistfilter.h"
51 #include "buffermodel.h"
52 #include "bufferview.h"
53 #include "bufferviewoverlay.h"
54 #include "bufferviewoverlayfilter.h"
55 #include "bufferwidget.h"
56 #include "channellistdlg.h"
57 #include "chatlinemodel.h"
58 #include "chatmonitorfilter.h"
59 #include "chatmonitorview.h"
62 #include "clientbacklogmanager.h"
63 #include "clientbufferviewconfig.h"
64 #include "clientbufferviewmanager.h"
65 #include "clientignorelistmanager.h"
66 #include "clienttransfer.h"
67 #include "clienttransfermanager.h"
68 #include "contextmenuactionprovider.h"
69 #include "coreconfigwizard.h"
70 #include "coreconnectdlg.h"
71 #include "coreconnection.h"
72 #include "coreconnectionstatuswidget.h"
73 #include "coreinfodlg.h"
74 #include "debugbufferviewoverlay.h"
75 #include "debuglogdlg.h"
76 #include "debugmessagemodelfilter.h"
77 #include "flatproxymodel.h"
79 #include "inputwidget.h"
80 #include "ircconnectionwizard.h"
81 #include "irclistmodel.h"
82 #include "legacysystemtray.h"
83 #include "msgprocessorstatuswidget.h"
84 #include "nicklistwidget.h"
85 #include "passwordchangedlg.h"
86 #include "qtuiapplication.h"
87 #include "qtuimessageprocessor.h"
88 #include "qtuisettings.h"
89 #include "qtuistyle.h"
90 #include "receivefiledlg.h"
91 #include "resourcetreedlg.h"
92 #include "settingsdlg.h"
93 #include "settingspagedlg.h"
94 #include "statusnotifieritem.h"
95 #include "toolbaractionprovider.h"
96 #include "topicwidget.h"
97 #include "transfermodel.h"
98 #include "verticaldock.h"
101 # ifdef HAVE_QTMULTIMEDIA
102 # include "qtmultimedianotificationbackend.h"
104 # include "systraynotificationbackend.h"
105 # include "taskbarnotificationbackend.h"
107 # include "knotificationbackend.h"
108 #endif /* HAVE_KDE */
109 #include "systrayanimationnotificationbackend.h"
112 # include "snorenotificationbackend.h"
116 # include "sslinfodlg.h"
119 #ifdef HAVE_NOTIFICATION_CENTER
120 # include "osxnotificationbackend.h"
124 # include "dockmanagernotificationbackend.h"
127 #include <settingspages/corehighlightsettingspage.h>
129 #include "settingspages/aliasessettingspage.h"
130 #include "settingspages/appearancesettingspage.h"
131 #include "settingspages/backlogsettingspage.h"
132 #include "settingspages/bufferviewsettingspage.h"
133 #include "settingspages/chatmonitorsettingspage.h"
134 #include "settingspages/chatviewcolorsettingspage.h"
135 #include "settingspages/chatviewsettingspage.h"
136 #include "settingspages/connectionsettingspage.h"
137 #include "settingspages/coreaccountsettingspage.h"
138 #include "settingspages/coreconnectionsettingspage.h"
139 #include "settingspages/dccsettingspage.h"
140 #include "settingspages/highlightsettingspage.h"
141 #include "settingspages/identitiessettingspage.h"
142 #include "settingspages/ignorelistsettingspage.h"
143 #include "settingspages/inputwidgetsettingspage.h"
144 #include "settingspages/itemviewsettingspage.h"
145 #include "settingspages/networkssettingspage.h"
146 #include "settingspages/notificationssettingspage.h"
147 #include "settingspages/topicwidgetsettingspage.h"
150 # include "settingspages/shortcutssettingspage.h"
154 # include "settingspages/sonnetsettingspage.h"
157 MainWin::MainWin(QWidget* parent)
159 : KMainWindow(parent)
160 , _kHelpMenu(new KHelpMenu(this))
163 : QMainWindow(parent)
166 _msgProcessorStatusWidget(new MsgProcessorStatusWidget(this))
167 , _coreConnectionStatusWidget(new CoreConnectionStatusWidget(Client::coreConnection(), this))
170 setAttribute(Qt::WA_DeleteOnClose, false); // we delete the mainwin manually
172 QtUiSettings uiSettings;
173 QString style = uiSettings.value("Style", QString()).toString();
174 if (!style.isEmpty()) {
175 QApplication::setStyle(style);
178 QApplication::setQuitOnLastWindowClosed(false);
180 setWindowTitle("Quassel IRC");
181 setWindowIconText("Quassel IRC");
182 // Set the default icon for all windows
183 QApplication::setWindowIcon(icon::get("quassel"));
189 connect(Client::instance(), &Client::networkCreated, this, &MainWin::clientNetworkCreated);
190 connect(Client::instance(), &Client::networkRemoved, this, &MainWin::clientNetworkRemoved);
191 connect(Client::messageModel(), &QAbstractItemModel::rowsInserted, this, &MainWin::messagesInserted);
192 connect(GraphicalUi::contextMenuActionProvider(), &NetworkModelController::showChannelList, this, &MainWin::showChannelList);
193 connect(Client::instance(), &Client::showChannelList, this, &MainWin::showChannelList);
194 connect(GraphicalUi::contextMenuActionProvider(), &NetworkModelController::showNetworkConfig, this, &MainWin::showNetworkConfig);
195 connect(GraphicalUi::contextMenuActionProvider(), &NetworkModelController::showIgnoreList, this, &MainWin::showIgnoreList);
196 connect(Client::instance(), &Client::showIgnoreList, this, &MainWin::showIgnoreList);
197 connect(Client::instance(), &Client::dbUpgradeInProgress, this, &MainWin::showMigrationWarning);
198 connect(Client::instance(), &Client::exitRequested, this, &MainWin::onExitRequested);
200 connect(Client::coreConnection(), &CoreConnection::startCoreSetup, this, &MainWin::showCoreConfigWizard);
201 connect(Client::coreConnection(), &CoreConnection::connectionErrorPopup, this, &MainWin::handleCoreConnectionError);
202 connect(Client::coreConnection(), &CoreConnection::userAuthenticationRequired, this, &MainWin::userAuthenticationRequired);
203 connect(Client::coreConnection(), &CoreConnection::handleNoSslInClient, this, &MainWin::handleNoSslInClient);
204 connect(Client::coreConnection(), &CoreConnection::handleNoSslInCore, this, &MainWin::handleNoSslInCore);
206 connect(Client::coreConnection(), &CoreConnection::handleSslErrors, this, &MainWin::handleSslErrors);
210 setDockNestingEnabled(true);
211 setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
212 setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
213 setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
214 setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
216 // Order is sometimes important
220 // setupTransferWidget(); not ready yet
232 _bufferWidget->setFocusProxy(_inputWidget);
233 _chatMonitorView->setFocusProxy(_inputWidget);
236 # ifdef HAVE_QTMULTIMEDIA
237 QtUi::registerNotificationBackend(new QtMultimediaNotificationBackend(this));
239 QtUi::registerNotificationBackend(new TaskbarNotificationBackend(this));
241 QtUi::registerNotificationBackend(new KNotificationBackend(this));
242 #endif /* HAVE_KDE */
244 #ifndef QT_NO_SYSTEMTRAYICON
245 QtUi::registerNotificationBackend(new SystrayAnimationNotificationBackend(this));
248 QtUi::registerNotificationBackend(new SnoreNotificationBackend(this));
249 #elif !defined(QT_NO_SYSTEMTRAYICON) && !defined(HAVE_KDE)
250 QtUi::registerNotificationBackend(new SystrayNotificationBackend(this));
253 #ifdef HAVE_NOTIFICATION_CENTER
254 QtUi::registerNotificationBackend(new OSXNotificationBackend(this));
258 QtUi::registerNotificationBackend(new DockManagerNotificationBackend(this));
261 // we assume that at this point, all configurable actions are defined!
262 QtUi::loadShortcuts();
264 connect(bufferWidget(), selectOverload<BufferId>(&AbstractBufferContainer::currentChanged), this, &MainWin::currentBufferChanged);
266 setDisconnectedState(); // Disable menus and stuff
269 setAutoSaveSettings();
272 // restore mainwin state
274 restoreStateFromSettings(s);
276 // restore locked state of docks
277 QtUi::actionCollection("General")->action("LockLayout")->setChecked(s.value("LockLayout", false).toBool());
279 Quassel::registerQuitHandler([this]() {
281 saveStateToSettings(s);
283 // Close all open dialogs and the MainWin, so we can safely kill the Client instance afterwards
284 // Note: This does not quit the application, as quitOnLastWindowClosed is set to false.
285 // We rely on another quit handler to be registered that actually quits the application.
286 qApp->closeAllWindows();
289 QTimer::singleShot(0, this, &MainWin::doAutoConnect);
292 void MainWin::saveStateToSettings(UiSettings& s)
294 s.setValue("MainWinSize", _normalSize);
295 s.setValue("MainWinPos", _normalPos);
296 s.setValue("MainWinState", saveState());
297 s.setValue("MainWinGeometry", saveGeometry());
298 s.setValue("MainWinMinimized", isMinimized());
299 s.setValue("MainWinMaximized", isMaximized());
300 s.setValue("MainWinHidden", !isVisible());
301 BufferId lastBufId = Client::bufferModel()->currentBuffer();
302 if (lastBufId.isValid())
303 s.setValue("LastUsedBufferId", lastBufId.toInt());
306 saveAutoSaveSettings();
310 void MainWin::restoreStateFromSettings(UiSettings& s)
312 _normalSize = s.value("MainWinSize", size()).toSize();
313 _normalPos = s.value("MainWinPos", pos()).toPoint();
314 bool maximized = s.value("MainWinMaximized", false).toBool();
317 restoreGeometry(s.value("MainWinGeometry").toByteArray());
320 // restoreGeometry() fails if the windows was maximized, so we resize and position explicitly
325 restoreState(s.value("MainWinState").toByteArray());
331 if ((Quassel::isOptionSet("hidewindow") || s.value("MainWinHidden").toBool()) && _systemTray->isSystemTrayAvailable())
332 QtUi::hideMainWidget();
333 else if (s.value("MainWinMinimized").toBool())
341 QMenu* MainWin::createPopupMenu()
343 QMenu* popupMenu = QMainWindow::createPopupMenu();
344 popupMenu->addSeparator();
345 ActionCollection* coll = QtUi::actionCollection("General");
346 popupMenu->addAction(coll->action("ToggleMenuBar"));
350 void MainWin::updateIcon()
353 if (Client::isConnected())
354 icon = icon::get("quassel");
356 icon = icon::get("inactive-quassel");
360 void MainWin::setupActions()
362 QAction* action{nullptr};
363 ActionCollection* coll = QtUi::actionCollection("General", tr("General"));
367 {{"ConnectCore", new Action(icon::get("connect-quassel"), tr("&Connect to Core..."), coll, this, &MainWin::showCoreConnectionDlg)},
369 new Action(icon::get("disconnect-quassel"), tr("&Disconnect from Core"), coll, Client::instance(), &Client::disconnectFromCore)},
370 {"ChangePassword", new Action(icon::get("dialog-password"), tr("Change &Password..."), coll, this, &MainWin::showPasswordChangeDlg)},
371 {"CoreInfo", new Action(icon::get("help-about"), tr("Core &Info..."), coll, this, &MainWin::showCoreInfoDlg)},
372 {"ConfigureNetworks",
373 new Action(icon::get("configure"), tr("Configure &Networks..."), coll, this, &MainWin::onConfigureNetworksTriggered)},
374 {"Quit", new Action(icon::get("application-exit"), tr("&Quit"), coll, Quassel::instance(), &Quassel::quit, Qt::CTRL + Qt::Key_Q)}});
377 coll->addAction("ConfigureBufferViews", new Action(tr("&Configure Chat Lists..."), coll, this, &MainWin::onConfigureViewsTriggered));
379 coll->addAction("ToggleSearchBar", new Action(icon::get("edit-find"), tr("Show &Search Bar"), coll, QKeySequence::Find))->setCheckable(true);
380 coll->addAction("ShowAwayLog", new Action(tr("Show Away Log"), coll, this, &MainWin::showAwayLog));
381 coll->addAction("ToggleMenuBar", new Action(icon::get("show-menu"), tr("Show &Menubar"), coll))->setCheckable(true);
382 coll->addAction("ToggleStatusBar", new Action(tr("Show Status &Bar"), coll))->setCheckable(true);
384 action = coll->addAction("LockLayout", new Action(tr("&Lock Layout"), coll));
385 action->setCheckable(true);
386 connect(action, &QAction::toggled, this, &MainWin::onLockLayoutToggled);
389 # if KCONFIGWIDGETS_VERSION < QT_VERSION_CHECK(5, 23, 0)
390 _fullScreenAction = KStandardAction::fullScreen(this, SLOT(onFullScreenToggled()), this, coll);
392 _fullScreenAction = KStandardAction::fullScreen(this, &MainWin::onFullScreenToggled, this, coll);
395 _fullScreenAction = new Action(icon::get("view-fullscreen"),
396 tr("&Full Screen Mode"),
399 &MainWin::onFullScreenToggled,
400 QKeySequence::FullScreen);
401 _fullScreenAction->setCheckable(true);
402 coll->addAction("ToggleFullScreen", _fullScreenAction);
406 coll->addAction("ConfigureShortcuts",
407 new Action(icon::get("configure-shortcuts"), tr("Configure &Shortcuts..."), coll, this, &MainWin::showShortcutsDlg))
408 ->setMenuRole(QAction::NoRole);
409 coll->addAction("ConfigureQuassel",
410 new Action(icon::get("configure"), tr("&Configure Quassel..."), coll, this, &MainWin::showSettingsDlg, QKeySequence(Qt::Key_F7)))
411 ->setMenuRole(QAction::PreferencesRole);
414 coll->addAction("AboutQuassel", new Action(icon::get("quassel"), tr("&About Quassel"), coll, this, &MainWin::showAboutDlg))
415 ->setMenuRole(QAction::AboutRole);
416 coll->addAction("AboutQt", new Action(QIcon(":/pics/qt-logo.png"), tr("About &Qt"), coll, qApp, &QApplication::aboutQt))
417 ->setMenuRole(QAction::AboutQtRole);
419 {{"DebugNetworkModel",
420 new Action(icon::get("tools-report-bug"), tr("Debug &NetworkModel"), coll, this, &MainWin::onDebugNetworkModelTriggered)},
421 {"DebugBufferViewOverlay",
422 new Action(icon::get("tools-report-bug"), tr("Debug &BufferViewOverlay"), coll, this, &MainWin::onDebugBufferViewOverlayTriggered)},
423 {"DebugMessageModel",
424 new Action(icon::get("tools-report-bug"), tr("Debug &MessageModel"), coll, this, &MainWin::onDebugMessageModelTriggered)},
425 {"DebugHotList", new Action(icon::get("tools-report-bug"), tr("Debug &HotList"), coll, this, &MainWin::onDebugHotListTriggered)},
426 {"DebugLog", new Action(icon::get("tools-report-bug"), tr("Debug &Log"), coll, this, &MainWin::onDebugLogTriggered)},
428 new Action(icon::get("tools-report-bug"), tr("Show &Resource Tree"), coll, this, &MainWin::onShowResourceTreeTriggered)},
430 new Action(icon::get("view-refresh"),
431 tr("Reload Stylesheet"),
435 QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_R))}});
438 coll->addAction("HideCurrentBuffer", new Action(tr("Hide Current Buffer"), coll, this, &MainWin::hideCurrentBuffer, QKeySequence::Close));
441 coll = QtUi::actionCollection("TextFormat", tr("Text formatting"));
444 {{"FormatApplyColor",
445 new Action(icon::get("format-text-color"),
446 tr("Apply foreground color"),
449 &MainWin::onFormatApplyColorTriggered,
450 QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G))},
451 {"FormatApplyColorFill",
452 new Action(icon::get("format-fill-color"),
453 tr("Apply background color"),
456 &MainWin::onFormatApplyColorFillTriggered,
457 QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B))},
459 new Action(icon::get("edit-clear"),
460 tr("Clear formatting"),
463 &MainWin::onFormatClearTriggered,
464 QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C))},
466 new Action(icon::get("format-text-bold"), tr("Toggle bold"), coll, this, &MainWin::onFormatBoldTriggered, QKeySequence::Bold)},
468 new Action(icon::get("format-text-italic"), tr("Toggle italics"), coll, this, &MainWin::onFormatItalicTriggered, QKeySequence::Italic)},
470 new Action(icon::get("format-text-underline"),
471 tr("Toggle underline"),
474 &MainWin::onFormatUnderlineTriggered,
475 QKeySequence::Underline)}});
478 coll = QtUi::actionCollection("Navigation", tr("Navigation"));
482 new Action(tr("Jump to hot chat"), coll, this, &MainWin::onJumpHotBufferTriggered, QKeySequence(Qt::META + Qt::Key_A))},
483 {"ActivateBufferFilter",
484 new Action(tr("Activate the buffer search"), coll, this, &MainWin::onBufferSearchTriggered, QKeySequence(Qt::CTRL + Qt::Key_S))}});
488 const int bindModifier = Qt::ControlModifier | Qt::AltModifier;
489 const int jumpModifier = Qt::ControlModifier;
491 const int bindModifier = Qt::ControlModifier;
492 const int jumpModifier = Qt::AltModifier;
495 coll->addAction("BindJumpKey0",
496 new Action(tr("Set Quick Access #0"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_0)))
497 ->setProperty("Index", 0);
498 coll->addAction("BindJumpKey1",
499 new Action(tr("Set Quick Access #1"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_1)))
500 ->setProperty("Index", 1);
501 coll->addAction("BindJumpKey2",
502 new Action(tr("Set Quick Access #2"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_2)))
503 ->setProperty("Index", 2);
504 coll->addAction("BindJumpKey3",
505 new Action(tr("Set Quick Access #3"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_3)))
506 ->setProperty("Index", 3);
507 coll->addAction("BindJumpKey4",
508 new Action(tr("Set Quick Access #4"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_4)))
509 ->setProperty("Index", 4);
510 coll->addAction("BindJumpKey5",
511 new Action(tr("Set Quick Access #5"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_5)))
512 ->setProperty("Index", 5);
513 coll->addAction("BindJumpKey6",
514 new Action(tr("Set Quick Access #6"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_6)))
515 ->setProperty("Index", 6);
516 coll->addAction("BindJumpKey7",
517 new Action(tr("Set Quick Access #7"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_7)))
518 ->setProperty("Index", 7);
519 coll->addAction("BindJumpKey8",
520 new Action(tr("Set Quick Access #8"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_8)))
521 ->setProperty("Index", 8);
522 coll->addAction("BindJumpKey9",
523 new Action(tr("Set Quick Access #9"), coll, this, &MainWin::bindJumpKey, QKeySequence(bindModifier + Qt::Key_9)))
524 ->setProperty("Index", 9);
526 coll->addAction("JumpKey0", new Action(tr("Quick Access #0"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_0)))
527 ->setProperty("Index", 0);
528 coll->addAction("JumpKey1", new Action(tr("Quick Access #1"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_1)))
529 ->setProperty("Index", 1);
530 coll->addAction("JumpKey2", new Action(tr("Quick Access #2"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_2)))
531 ->setProperty("Index", 2);
532 coll->addAction("JumpKey3", new Action(tr("Quick Access #3"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_3)))
533 ->setProperty("Index", 3);
534 coll->addAction("JumpKey4", new Action(tr("Quick Access #4"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_4)))
535 ->setProperty("Index", 4);
536 coll->addAction("JumpKey5", new Action(tr("Quick Access #5"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_5)))
537 ->setProperty("Index", 5);
538 coll->addAction("JumpKey6", new Action(tr("Quick Access #6"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_6)))
539 ->setProperty("Index", 6);
540 coll->addAction("JumpKey7", new Action(tr("Quick Access #7"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_7)))
541 ->setProperty("Index", 7);
542 coll->addAction("JumpKey8", new Action(tr("Quick Access #8"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_8)))
543 ->setProperty("Index", 8);
544 coll->addAction("JumpKey9", new Action(tr("Quick Access #9"), coll, this, &MainWin::onJumpKey, QKeySequence(jumpModifier + Qt::Key_9)))
545 ->setProperty("Index", 9);
548 coll->addAction("NextBufferView",
549 new Action(icon::get("go-next-view"),
550 tr("Activate Next Chat List"),
553 &MainWin::nextBufferView,
554 QKeySequence(QKeySequence::Forward)));
555 coll->addAction("PreviousBufferView",
556 new Action(icon::get("go-previous-view"),
557 tr("Activate Previous Chat List"),
560 &MainWin::previousBufferView,
561 QKeySequence::Back));
562 coll->addAction("NextBuffer",
563 new Action(icon::get("go-down"),
564 tr("Go to Next Chat"),
567 &MainWin::nextBuffer,
568 QKeySequence(Qt::ALT + Qt::Key_Down)));
569 coll->addAction("PreviousBuffer",
570 new Action(icon::get("go-up"),
571 tr("Go to Previous Chat"),
574 &MainWin::previousBuffer,
575 QKeySequence(Qt::ALT + Qt::Key_Up)));
578 void MainWin::setupMenus()
580 ActionCollection* coll = QtUi::actionCollection("General");
582 _fileMenu = menuBar()->addMenu(tr("&File"));
584 static const QStringList coreActions = QStringList() << "ConnectCore"
590 foreach (QString actionName, coreActions) {
591 coreAction = coll->action(actionName);
592 _fileMenu->addAction(coreAction);
593 flagRemoteCoreOnly(coreAction);
595 flagRemoteCoreOnly(_fileMenu->addSeparator());
597 _networksMenu = _fileMenu->addMenu(tr("&Networks"));
598 _networksMenu->addAction(coll->action("ConfigureNetworks"));
599 _networksMenu->addSeparator();
600 _fileMenu->addSeparator();
601 _fileMenu->addAction(coll->action("Quit"));
603 _viewMenu = menuBar()->addMenu(tr("&View"));
604 _bufferViewsMenu = _viewMenu->addMenu(tr("&Chat Lists"));
605 _bufferViewsMenu->addAction(coll->action("ConfigureBufferViews"));
606 _toolbarMenu = _viewMenu->addMenu(tr("&Toolbars"));
607 _viewMenu->addSeparator();
609 _viewMenu->addAction(coll->action("ToggleMenuBar"));
610 _viewMenu->addAction(coll->action("ToggleStatusBar"));
611 _viewMenu->addAction(coll->action("ToggleSearchBar"));
613 coreAction = coll->action("ShowAwayLog");
614 flagRemoteCoreOnly(coreAction);
615 _viewMenu->addAction(coreAction);
617 _viewMenu->addSeparator();
618 _viewMenu->addAction(coll->action("LockLayout"));
620 _settingsMenu = menuBar()->addMenu(tr("&Settings"));
622 # if KCONFIGWIDGETS_VERSION < QT_VERSION_CHECK(5, 23, 0)
623 _settingsMenu->addAction(KStandardAction::configureNotifications(this, SLOT(showNotificationsDlg()), this));
624 _settingsMenu->addAction(KStandardAction::keyBindings(this, SLOT(showShortcutsDlg()), this));
626 _settingsMenu->addAction(KStandardAction::configureNotifications(this, &MainWin::showNotificationsDlg, this));
627 _settingsMenu->addAction(KStandardAction::keyBindings(this, &MainWin::showShortcutsDlg, this));
630 _settingsMenu->addAction(coll->action("ConfigureShortcuts"));
632 _settingsMenu->addAction(coll->action("ConfigureQuassel"));
634 _helpMenu = menuBar()->addMenu(tr("&Help"));
636 _helpMenu->addAction(coll->action("AboutQuassel"));
638 _helpMenu->addAction(coll->action("AboutQt"));
640 # if KCONFIGWIDGETS_VERSION < QT_VERSION_CHECK(5, 23, 0)
641 _helpMenu->addAction(KStandardAction::aboutKDE(_kHelpMenu, SLOT(aboutKDE()), this));
643 _helpMenu->addAction(KStandardAction::aboutKDE(_kHelpMenu, &KHelpMenu::aboutKDE, this));
646 _helpMenu->addSeparator();
647 _helpDebugMenu = _helpMenu->addMenu(icon::get("tools-report-bug"), tr("Debug"));
648 _helpDebugMenu->addAction(coll->action("DebugNetworkModel"));
649 _helpDebugMenu->addAction(coll->action("DebugBufferViewOverlay"));
650 _helpDebugMenu->addAction(coll->action("DebugMessageModel"));
651 _helpDebugMenu->addAction(coll->action("DebugHotList"));
652 _helpDebugMenu->addAction(coll->action("DebugLog"));
653 _helpDebugMenu->addAction(coll->action("ShowResourceTree"));
654 _helpDebugMenu->addSeparator();
655 _helpDebugMenu->addAction(coll->action("ReloadStyle"));
658 QAction* showMenuBar = QtUi::actionCollection("General")->action("ToggleMenuBar");
660 QtUiSettings uiSettings;
661 bool enabled = uiSettings.value("ShowMenuBar", QVariant(true)).toBool();
662 showMenuBar->setChecked(enabled);
663 enabled ? menuBar()->show() : menuBar()->hide();
665 connect(showMenuBar, &QAction::toggled, menuBar(), &QMenuBar::setVisible);
666 connect(showMenuBar, &QAction::toggled, this, &MainWin::saveMenuBarStatus);
669 void MainWin::setupBufferWidget()
671 _bufferWidget = new BufferWidget(this);
672 _bufferWidget->setModel(Client::bufferModel());
673 _bufferWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
674 setCentralWidget(_bufferWidget);
677 void MainWin::addBufferView(int bufferViewConfigId)
679 addBufferView(Client::bufferViewManager()->clientBufferViewConfig(bufferViewConfigId));
682 void MainWin::addBufferView(ClientBufferViewConfig* config)
687 config->setLocked(QtUiSettings().value("LockLayout", false).toBool());
688 auto* dock = new BufferViewDock(config, this);
690 // create the view and initialize it's filter
691 auto* view = new BufferView(dock);
692 view->setFilteredModel(Client::bufferModel(), config);
693 view->installEventFilter(_inputWidget); // for key presses
695 Client::bufferModel()->synchronizeView(view);
697 dock->setLocked(QtUiSettings().value("LockLayout", false).toBool());
699 dock->setWidget(view);
700 dock->setVisible(_layoutLoaded); // don't show before state has been restored
702 addDockWidget(Qt::LeftDockWidgetArea, dock);
703 _bufferViewsMenu->addAction(dock->toggleViewAction());
705 connect(dock->toggleViewAction(), &QAction::toggled, this, &MainWin::bufferViewToggled);
706 connect(dock, &QDockWidget::visibilityChanged, this, &MainWin::bufferViewVisibilityChanged);
707 _bufferViews.append(dock);
709 if (!activeBufferView())
713 void MainWin::removeBufferView(int bufferViewConfigId)
716 BufferViewDock* dock;
717 foreach (QAction* action, _bufferViewsMenu->actions()) {
718 actionData = action->data();
719 if (!actionData.isValid())
722 dock = qobject_cast<BufferViewDock*>(action->parent());
723 if (dock && actionData.toInt() == bufferViewConfigId) {
724 removeAction(action);
725 Client::bufferViewOverlay()->removeView(dock->bufferViewId());
726 _bufferViews.removeAll(dock);
728 if (dock->isActive()) {
729 dock->setActive(false);
730 _activeBufferViewIndex = -1;
739 void MainWin::bufferViewToggled(bool enabled)
741 if (!enabled && !isMinimized()) {
742 // hiding the mainwindow triggers a toggle of the bufferview (which pretty much sucks big time)
743 // since this isn't our fault and we can't do anything about it, we suppress the resulting calls
746 auto* action = qobject_cast<QAction*>(sender());
748 auto* dock = qobject_cast<BufferViewDock*>(action->parent());
751 // Make sure we don't toggle backlog fetch for a view we've already removed
752 if (!_bufferViews.contains(dock))
756 Client::bufferViewOverlay()->addView(dock->bufferViewId());
758 Client::bufferViewOverlay()->removeView(dock->bufferViewId());
761 void MainWin::bufferViewVisibilityChanged(bool visible)
764 auto* dock = qobject_cast<BufferViewDock*>(sender());
766 if ((!dock->isHidden() && !activeBufferView()) || (dock->isHidden() && dock->isActive()))
770 BufferView* MainWin::allBuffersView() const
772 // "All Buffers" is always the first dock created
773 if (_bufferViews.count() > 0)
774 return _bufferViews[0]->bufferView();
778 BufferView* MainWin::activeBufferView() const
780 if (_activeBufferViewIndex < 0 || _activeBufferViewIndex >= _bufferViews.count())
782 BufferViewDock* dock = _bufferViews.at(_activeBufferViewIndex);
783 return dock->isActive() ? dock->bufferView() : nullptr;
786 void MainWin::changeActiveBufferView(int bufferViewId)
788 if (bufferViewId < 0)
791 if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
792 _bufferViews[_activeBufferViewIndex]->setActive(false);
793 _activeBufferViewIndex = -1;
796 for (int i = 0; i < _bufferViews.count(); i++) {
797 BufferViewDock* dock = _bufferViews.at(i);
798 if (dock->bufferViewId() == bufferViewId && !dock->isHidden()) {
799 _activeBufferViewIndex = i;
800 dock->setActive(true);
805 nextBufferView(); // fallback
808 void MainWin::showPasswordChangeDlg()
810 if (Client::isCoreFeatureEnabled(Quassel::Feature::PasswordChange)) {
811 PasswordChangeDlg{}.exec();
814 QMessageBox box(QMessageBox::Warning,
815 tr("Feature Not Supported"),
816 tr("<b>Your Quassel Core does not support this feature</b>"),
818 box.setInformativeText(tr("You need a Quassel Core v0.12.0 or newer in order to be able to remotely change your password."));
823 void MainWin::showMigrationWarning(bool show)
825 if (show && !_migrationWarning) {
826 _migrationWarning = new QMessageBox(QMessageBox::Information,
828 "<b>" + tr("Your database is being upgraded") + "</b>",
829 QMessageBox::NoButton,
831 _migrationWarning->setInformativeText(
832 "<p>" + tr("In order to support new features, we need to make changes to your backlog database. This may take a long while.")
833 + "</p><p>" + tr("Do not exit Quassel until the upgrade is complete!") + "</p>");
834 _migrationWarning->setStandardButtons(QMessageBox::NoButton);
835 _migrationWarning->show();
837 else if (!show && _migrationWarning) {
838 _migrationWarning->close();
839 _migrationWarning->deleteLater();
840 _migrationWarning = nullptr;
844 void MainWin::onExitRequested(const QString& reason)
846 if (!reason.isEmpty()) {
847 QMessageBox box(QMessageBox::Critical,
849 "<b>" + tr("Quassel encountered a fatal error and is terminated.") + "</b>",
851 box.setInformativeText("<p>" + tr("Reason:<em>") + " " + reason + "</em>");
856 void MainWin::changeActiveBufferView(bool backwards)
858 if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
859 _bufferViews[_activeBufferViewIndex]->setActive(false);
862 if (!_bufferViews.count())
865 int c = _bufferViews.count();
866 while (c--) { // yes, this will reactivate the current active one if all others fail
868 if (--_activeBufferViewIndex < 0)
869 _activeBufferViewIndex = _bufferViews.count() - 1;
872 if (++_activeBufferViewIndex >= _bufferViews.count())
873 _activeBufferViewIndex = 0;
876 BufferViewDock* dock = _bufferViews.at(_activeBufferViewIndex);
877 if (dock->isHidden())
880 dock->setActive(true);
884 _activeBufferViewIndex = -1;
887 void MainWin::nextBufferView()
889 changeActiveBufferView(false);
892 void MainWin::previousBufferView()
894 changeActiveBufferView(true);
897 void MainWin::nextBuffer()
899 BufferView* view = activeBufferView();
904 void MainWin::previousBuffer()
906 BufferView* view = activeBufferView();
908 view->previousBuffer();
911 void MainWin::hideCurrentBuffer()
913 BufferView* view = activeBufferView();
915 view->hideCurrentBuffer();
918 void MainWin::showNotificationsDlg()
920 SettingsPageDlg{new NotificationsSettingsPage{}}.exec();
923 void MainWin::onConfigureNetworksTriggered()
925 SettingsPageDlg{new NetworksSettingsPage{}}.exec();
928 void MainWin::onConfigureViewsTriggered()
930 SettingsPageDlg{new BufferViewSettingsPage{}}.exec();
933 void MainWin::onLockLayoutToggled(bool lock)
935 QList<VerticalDock*> docks = findChildren<VerticalDock*>();
936 foreach (VerticalDock* dock, docks) {
937 dock->showTitle(!lock);
940 QList<NickListDock*> nickdocks = findChildren<NickListDock*>();
941 foreach (NickListDock* nickdock, nickdocks) {
942 nickdock->setLocked(lock);
945 QList<BufferViewDock*> bufferdocks = findChildren<BufferViewDock*>();
946 foreach (BufferViewDock* bufferdock, bufferdocks) {
947 bufferdock->setLocked(lock);
950 if (Client::bufferViewManager()) {
951 foreach (ClientBufferViewConfig* config, Client::bufferViewManager()->clientBufferViewConfigs()) {
952 config->setLocked(lock);
956 _mainToolBar->setMovable(!lock);
957 _nickToolBar->setMovable(!lock);
959 QtUiSettings().setValue("LockLayout", lock);
962 void MainWin::setupNickWidget()
965 NickListDock* nickDock = new NickListDock(tr("Nicks"), this);
966 nickDock->setObjectName("NickDock");
967 nickDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
968 nickDock->setLocked(QtUiSettings().value("LockLayout", false).toBool());
970 _nickListWidget = new NickListWidget(nickDock);
971 nickDock->setWidget(_nickListWidget);
973 addDockWidget(Qt::RightDockWidgetArea, nickDock);
974 _viewMenu->addAction(nickDock->toggleViewAction());
975 nickDock->toggleViewAction()->setText(tr("Show Nick List"));
977 // See NickListDock::NickListDock();
978 // connect(nickDock->toggleViewAction(), &NickListDock::triggered, nickListWidget, &QWidget::showWidget);
980 // attach the NickListWidget to the BufferModel and the default selection
981 _nickListWidget->setModel(Client::bufferModel());
982 _nickListWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
984 _nickListWidget->setVisible(false);
987 void MainWin::setupChatMonitor()
989 VerticalDock* dock = new VerticalDock(tr("Chat Monitor"), this);
990 dock->setObjectName("ChatMonitorDock");
992 auto* filter = new ChatMonitorFilter(Client::messageModel(), this);
993 _chatMonitorView = new ChatMonitorView(filter, this);
994 _chatMonitorView->show();
995 dock->setWidget(_chatMonitorView);
998 addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
999 _viewMenu->addAction(dock->toggleViewAction());
1000 dock->toggleViewAction()->setText(tr("Show Chat Monitor"));
1003 void MainWin::setupInputWidget()
1005 VerticalDock* dock = new VerticalDock(tr("Inputline"), this);
1006 dock->setObjectName("InputDock");
1008 _inputWidget = new InputWidget(dock);
1009 dock->setWidget(_inputWidget);
1011 addDockWidget(Qt::BottomDockWidgetArea, dock);
1013 _viewMenu->addAction(dock->toggleViewAction());
1014 dock->toggleViewAction()->setText(tr("Show Input Line"));
1016 _inputWidget->setModel(Client::bufferModel());
1017 _inputWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
1019 _inputWidget->inputLine()->installEventFilter(_bufferWidget);
1022 void MainWin::setupTopicWidget()
1024 VerticalDock* dock = new VerticalDock(tr("Topic"), this);
1025 dock->setObjectName("TopicDock");
1026 _topicWidget = new TopicWidget(dock);
1028 dock->setWidget(_topicWidget);
1030 _topicWidget->setModel(Client::bufferModel());
1031 _topicWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
1033 connect(_topicWidget, &TopicWidget::switchedPlain, _bufferWidget, selectOverload<>(&QWidget::setFocus));
1035 addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
1037 _viewMenu->addAction(dock->toggleViewAction());
1038 dock->toggleViewAction()->setText(tr("Show Topic Line"));
1041 void MainWin::setupTransferWidget()
1043 auto dock = new QDockWidget(tr("Transfers"), this);
1044 dock->setObjectName("TransferDock");
1045 dock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
1047 auto view = new QTableView(dock); // to be replaced by the real thing
1048 view->setModel(Client::transferModel());
1049 dock->setWidget(view);
1050 dock->hide(); // hidden by default
1051 addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
1053 auto action = dock->toggleViewAction();
1054 action->setText(tr("Show File Transfers"));
1055 action->setIcon(icon::get("download"));
1056 action->setShortcut(QKeySequence(Qt::Key_F6));
1057 QtUi::actionCollection("General")->addAction("ShowTransferWidget", action);
1058 _viewMenu->addAction(action);
1061 void MainWin::setupViewMenuTail()
1063 _viewMenu->addSeparator();
1064 _viewMenu->addAction(_fullScreenAction);
1067 void MainWin::setupTitleSetter()
1069 _titleSetter.setModel(Client::bufferModel());
1070 _titleSetter.setSelectionModel(Client::bufferModel()->standardSelectionModel());
1073 void MainWin::setupStatusBar()
1075 // MessageProcessor progress
1076 statusBar()->addPermanentWidget(_msgProcessorStatusWidget);
1079 _coreConnectionStatusWidget->update();
1080 statusBar()->addPermanentWidget(_coreConnectionStatusWidget);
1082 QAction* showStatusbar = QtUi::actionCollection("General")->action("ToggleStatusBar");
1084 QtUiSettings uiSettings;
1086 bool enabled = uiSettings.value("ShowStatusBar", QVariant(true)).toBool();
1087 showStatusbar->setChecked(enabled);
1088 enabled ? statusBar()->show() : statusBar()->hide();
1090 connect(showStatusbar, &QAction::toggled, statusBar(), &QWidget::setVisible);
1091 connect(showStatusbar, &QAction::toggled, this, &MainWin::saveStatusBarStatus);
1093 connect(Client::coreConnection(), &CoreConnection::connectionMsg, statusBar(), [statusBar = statusBar()](auto&& message) {
1094 statusBar->showMessage(message);
1098 void MainWin::setupHotList()
1100 auto* flatProxy = new FlatProxyModel(this);
1101 flatProxy->setSourceModel(Client::bufferModel());
1102 _bufferHotList = new BufferHotListFilter(flatProxy);
1105 void MainWin::saveMenuBarStatus(bool enabled)
1107 QtUiSettings uiSettings;
1108 uiSettings.setValue("ShowMenuBar", enabled);
1111 void MainWin::saveStatusBarStatus(bool enabled)
1113 QtUiSettings uiSettings;
1114 uiSettings.setValue("ShowStatusBar", enabled);
1117 void MainWin::setupSystray()
1120 _systemTray = new StatusNotifierItem(this);
1121 #elif !defined QT_NO_SYSTEMTRAYICON
1122 _systemTray = new LegacySystemTray(this);
1124 _systemTray = new SystemTray(this); // dummy
1128 void MainWin::setupToolBars()
1130 connect(_bufferWidget,
1131 selectOverload<const QModelIndex&>(&AbstractBufferContainer::currentChanged),
1132 QtUi::toolBarActionProvider(),
1133 &ToolBarActionProvider::onCurrentBufferChanged);
1134 connect(_nickListWidget,
1135 &NickListWidget::nickSelectionChanged,
1136 QtUi::toolBarActionProvider(),
1137 &ToolBarActionProvider::onNickSelectionChanged);
1140 setUnifiedTitleAndToolBarOnMac(true);
1144 _mainToolBar = new KToolBar("MainToolBar", this, Qt::TopToolBarArea, false, true, true);
1146 _mainToolBar = new QToolBar(this);
1147 _mainToolBar->setObjectName("MainToolBar");
1149 _mainToolBar->setWindowTitle(tr("Main Toolbar"));
1150 addToolBar(_mainToolBar);
1152 if (Quassel::runMode() != Quassel::Monolithic) {
1153 ActionCollection* coll = QtUi::actionCollection("General");
1154 _mainToolBar->addAction(coll->action("ConnectCore"));
1155 _mainToolBar->addAction(coll->action("DisconnectCore"));
1158 _mainToolBar->setMovable(!QtUiSettings().value("LockLayout", false).toBool());
1160 QtUi::toolBarActionProvider()->addActions(_mainToolBar, ToolBarActionProvider::MainToolBar);
1161 _toolbarMenu->addAction(_mainToolBar->toggleViewAction());
1164 _nickToolBar = new KToolBar("NickToolBar", this, Qt::TopToolBarArea, false, true, true);
1166 _nickToolBar = new QToolBar(this);
1167 _nickToolBar->setObjectName("NickToolBar");
1169 _nickToolBar->setWindowTitle(tr("Nick Toolbar"));
1170 _nickToolBar->setVisible(false); // default: not visible
1171 addToolBar(_nickToolBar);
1172 _nickToolBar->setMovable(!QtUiSettings().value("LockLayout", false).toBool());
1174 QtUi::toolBarActionProvider()->addActions(_nickToolBar, ToolBarActionProvider::NickToolBar);
1175 _toolbarMenu->addAction(_nickToolBar->toggleViewAction());
1178 QtUiSettings uiSettings;
1180 bool visible = uiSettings.value("ShowMainToolBar", QVariant(true)).toBool();
1181 _mainToolBar->setVisible(visible);
1182 connect(_mainToolBar, &QToolBar::visibilityChanged, this, &MainWin::saveMainToolBarStatus);
1186 void MainWin::saveMainToolBarStatus(bool enabled)
1189 QtUiSettings uiSettings;
1190 uiSettings.setValue("ShowMainToolBar", enabled);
1196 void MainWin::doAutoConnect()
1198 if (!Client::coreConnection()->connectToCore()) {
1199 // No autoconnect selected (or no accounts)
1200 showCoreConnectionDlg();
1204 void MainWin::connectedToCore()
1206 Q_CHECK_PTR(Client::bufferViewManager());
1207 connect(Client::bufferViewManager(), &BufferViewManager::bufferViewConfigAdded, this, selectOverload<int>(&MainWin::addBufferView));
1208 connect(Client::bufferViewManager(), &BufferViewManager::bufferViewConfigDeleted, this, &MainWin::removeBufferView);
1209 connect(Client::bufferViewManager(), &SyncableObject::initDone, this, &MainWin::loadLayout);
1211 if (Client::transferManager()) {
1212 connect(Client::transferManager(), &TransferManager::transferAdded, this, &MainWin::showNewTransferDlg);
1215 setConnectedState();
1218 void MainWin::setConnectedState()
1220 ActionCollection* coll = QtUi::actionCollection("General");
1222 coll->action("ConnectCore")->setEnabled(false);
1223 coll->action("DisconnectCore")->setEnabled(true);
1224 coll->action("ChangePassword")->setEnabled(true);
1225 coll->action("CoreInfo")->setEnabled(true);
1227 foreach (QAction* action, _fileMenu->actions()) {
1228 if (isRemoteCoreOnly(action))
1229 action->setVisible(!Client::internalCore());
1232 disconnect(Client::backlogManager(),
1233 &ClientBacklogManager::updateProgress,
1234 _msgProcessorStatusWidget,
1235 &MsgProcessorStatusWidget::setProgress);
1236 disconnect(Client::backlogManager(), &ClientBacklogManager::messagesRequested, this, &MainWin::showStatusBarMessage);
1237 disconnect(Client::backlogManager(), &ClientBacklogManager::messagesProcessed, this, &MainWin::showStatusBarMessage);
1238 if (!Client::internalCore()) {
1239 connect(Client::backlogManager(),
1240 &ClientBacklogManager::updateProgress,
1241 _msgProcessorStatusWidget,
1242 &MsgProcessorStatusWidget::setProgress);
1243 connect(Client::backlogManager(), &ClientBacklogManager::messagesRequested, this, &MainWin::showStatusBarMessage);
1244 connect(Client::backlogManager(), &ClientBacklogManager::messagesProcessed, this, &MainWin::showStatusBarMessage);
1247 // _viewMenu->setEnabled(true);
1248 if (!Client::internalCore())
1249 statusBar()->showMessage(tr("Connected to core."));
1251 statusBar()->clearMessage();
1253 _coreConnectionStatusWidget->setVisible(!Client::internalCore());
1255 systemTray()->setState(SystemTray::Active);
1257 if (Client::networkIds().isEmpty()) {
1258 IrcConnectionWizard* wizard = new IrcConnectionWizard(this, Qt::Sheet);
1262 // Monolithic always preselects last used buffer - Client only if the connection died
1263 if (Client::coreConnection()->wasReconnect() || Quassel::runMode() == Quassel::Monolithic) {
1265 BufferId lastUsedBufferId(s.value("LastUsedBufferId").toInt());
1266 if (lastUsedBufferId.isValid())
1267 Client::bufferModel()->switchToBuffer(lastUsedBufferId);
1272 void MainWin::loadLayout()
1275 int accountId = Client::currentCoreAccount().accountId().toInt();
1276 QByteArray state = s.value(QString("MainWinState-%1").arg(accountId)).toByteArray();
1277 if (state.isEmpty()) {
1278 foreach (BufferViewDock* view, _bufferViews)
1280 _layoutLoaded = true;
1283 _nickListWidget->setVisible(true);
1284 restoreState(state, accountId);
1285 int bufferViewId = s.value(QString("ActiveBufferView-%1").arg(accountId), -1).toInt();
1286 if (bufferViewId >= 0)
1287 changeActiveBufferView(bufferViewId);
1289 _layoutLoaded = true;
1292 void MainWin::saveLayout()
1295 int accountId = _bufferViews.count() ? Client::currentCoreAccount().accountId().toInt() : 0; // only save if we still have a layout!
1296 if (accountId > 0) {
1297 s.setValue(QString("MainWinState-%1").arg(accountId), saveState(accountId));
1298 BufferView* view = activeBufferView();
1299 s.setValue(QString("ActiveBufferView-%1").arg(accountId), view ? view->config()->bufferViewId() : -1);
1303 void MainWin::disconnectedFromCore()
1305 // save core specific layout and remove bufferviews;
1307 _layoutLoaded = false;
1309 QVariant actionData;
1310 BufferViewDock* dock;
1311 foreach (QAction* action, _bufferViewsMenu->actions()) {
1312 actionData = action->data();
1313 if (!actionData.isValid())
1316 dock = qobject_cast<BufferViewDock*>(action->parent());
1317 if (dock && actionData.toInt() != -1) {
1318 removeAction(action);
1319 _bufferViews.removeAll(dock);
1320 dock->deleteLater();
1324 // store last active buffer
1326 BufferId lastBufId = _bufferWidget->currentBuffer();
1327 if (lastBufId.isValid()) {
1328 s.setValue("LastUsedBufferId", lastBufId.toInt());
1329 // clear the current selection
1330 Client::bufferModel()->standardSelectionModel()->clearSelection();
1332 restoreState(s.value("MainWinState").toByteArray());
1333 setDisconnectedState();
1336 void MainWin::setDisconnectedState()
1338 ActionCollection* coll = QtUi::actionCollection("General");
1339 // ui.menuCore->setEnabled(false);
1340 coll->action("ConnectCore")->setEnabled(true);
1341 coll->action("DisconnectCore")->setEnabled(false);
1342 coll->action("CoreInfo")->setEnabled(false);
1343 coll->action("ChangePassword")->setEnabled(false);
1344 //_viewMenu->setEnabled(false);
1345 statusBar()->showMessage(tr("Not connected to core."));
1346 if (_msgProcessorStatusWidget)
1347 _msgProcessorStatusWidget->setProgress(0, 0);
1349 systemTray()->setState(SystemTray::Passive);
1350 _nickListWidget->setVisible(false);
1353 void MainWin::userAuthenticationRequired(CoreAccount* account, bool* valid, const QString& errorMessage)
1355 Q_UNUSED(errorMessage)
1356 CoreConnectAuthDlg dlg(account);
1357 *valid = (dlg.exec() == QDialog::Accepted);
1360 void MainWin::handleNoSslInClient(bool* accepted)
1362 QMessageBox box(QMessageBox::Warning,
1363 tr("Unencrypted Connection"),
1364 tr("<b>Your client does not support SSL encryption</b>"),
1365 QMessageBox::Ignore | QMessageBox::Cancel);
1366 box.setInformativeText(tr("Sensitive data, like passwords, will be transmitted unencrypted to your Quassel core."));
1367 box.setDefaultButton(QMessageBox::Ignore);
1368 *accepted = (box.exec() == QMessageBox::Ignore);
1371 void MainWin::handleNoSslInCore(bool* accepted)
1373 QMessageBox box(QMessageBox::Warning,
1374 tr("Unencrypted Connection"),
1375 tr("<b>Your core does not support SSL encryption</b>"),
1376 QMessageBox::Ignore | QMessageBox::Cancel);
1377 box.setInformativeText(tr("Sensitive data, like passwords, will be transmitted unencrypted to your Quassel core."));
1378 box.setDefaultButton(QMessageBox::Ignore);
1379 *accepted = (box.exec() == QMessageBox::Ignore);
1384 void MainWin::handleSslErrors(const QSslSocket* socket, bool* accepted, bool* permanently)
1386 QString errorString = "<ul>";
1387 foreach (const QSslError error, socket->sslErrors())
1388 errorString += QString("<li>%1</li>").arg(error.errorString());
1389 errorString += "</ul>";
1391 QMessageBox box(QMessageBox::Warning,
1392 tr("Untrusted Security Certificate"),
1393 tr("<b>The SSL certificate provided by the core at %1 is untrusted for the following reasons:</b>").arg(socket->peerName()),
1394 QMessageBox::Cancel);
1395 box.setInformativeText(errorString);
1396 box.addButton(tr("Continue"), QMessageBox::AcceptRole);
1397 box.setDefaultButton(box.addButton(tr("Show Certificate"), QMessageBox::HelpRole));
1399 QMessageBox::ButtonRole role;
1402 role = box.buttonRole(box.clickedButton());
1403 if (role == QMessageBox::HelpRole) {
1404 SslInfoDlg dlg(socket);
1407 } while (role == QMessageBox::HelpRole);
1409 *accepted = role == QMessageBox::AcceptRole;
1411 QMessageBox box2(QMessageBox::Warning,
1412 tr("Untrusted Security Certificate"),
1413 tr("Would you like to accept this certificate forever without being prompted?"),
1414 QMessageBox::NoButton);
1415 box2.setDefaultButton(box2.addButton(tr("Current Session Only"), QMessageBox::NoRole));
1416 box2.addButton(tr("Forever"), QMessageBox::YesRole);
1418 *permanently = (box2.buttonRole(box2.clickedButton()) == QMessageBox::YesRole);
1422 #endif /* HAVE_SSL */
1424 void MainWin::handleCoreConnectionError(const QString& error)
1426 QMessageBox::critical(this, tr("Core Connection Error"), error, QMessageBox::Ok);
1429 void MainWin::showCoreConnectionDlg()
1432 if (dlg.exec() == QDialog::Accepted) {
1433 AccountId accId = dlg.selectedAccount();
1434 if (accId.isValid())
1435 Client::coreConnection()->connectToCore(accId);
1439 void MainWin::showCoreConfigWizard(const QVariantList& backends, const QVariantList& authenticators)
1441 auto* wizard = new CoreConfigWizard(Client::coreConnection(), backends, authenticators, this);
1446 void MainWin::showChannelList(NetworkId netId, const QString& channelFilters, bool listImmediately)
1448 if (!netId.isValid()) {
1449 auto* action = qobject_cast<QAction*>(sender());
1451 netId = action->data().value<NetworkId>();
1452 if (!netId.isValid()) {
1453 // We still haven't found a valid network, probably no network selected, e.g. "/list"
1454 // on the client homescreen when no networks are connected.
1455 QMessageBox box(QMessageBox::Information,
1456 tr("No network selected"),
1457 QString("<b>%1</b>").arg(tr("No network selected")),
1459 box.setInformativeText(tr("Select a network before trying to view the channel list."));
1465 auto* channelListDlg = new ChannelListDlg(this);
1466 channelListDlg->setAttribute(Qt::WA_DeleteOnClose);
1467 channelListDlg->setNetwork(netId);
1468 if (!channelFilters.isEmpty()) {
1469 channelListDlg->setChannelFilters(channelFilters);
1471 if (listImmediately) {
1472 channelListDlg->requestSearch();
1474 channelListDlg->show();
1477 void MainWin::showNetworkConfig(NetworkId netId)
1479 SettingsPageDlg dlg{new NetworksSettingsPage{}};
1480 if (netId.isValid())
1481 qobject_cast<NetworksSettingsPage*>(dlg.currentPage())->bufferList_Open(netId);
1485 void MainWin::showIgnoreList(QString newRule)
1487 SettingsPageDlg dlg{new IgnoreListSettingsPage{}};
1488 // prepare config dialog for new rule
1489 if (!newRule.isEmpty())
1490 qobject_cast<IgnoreListSettingsPage*>(dlg.currentPage())->editIgnoreRule(newRule);
1494 void MainWin::showCoreInfoDlg()
1496 CoreInfoDlg{}.exec();
1499 void MainWin::showAwayLog()
1503 auto* filter = new AwayLogFilter(Client::messageModel());
1504 _awayLog = new AwayLogView(filter, nullptr);
1505 filter->setParent(_awayLog);
1506 connect(_awayLog, &QObject::destroyed, this, &MainWin::awayLogDestroyed);
1507 _awayLog->setAttribute(Qt::WA_DeleteOnClose);
1511 void MainWin::awayLogDestroyed()
1516 void MainWin::showSettingsDlg()
1518 auto dlg = new SettingsDlg();
1520 // Category: Interface
1521 dlg->registerSettingsPage(new AppearanceSettingsPage(dlg));
1522 dlg->registerSettingsPage(new ChatViewSettingsPage(dlg));
1523 dlg->registerSettingsPage(new ChatViewColorSettingsPage(dlg));
1524 dlg->registerSettingsPage(new ChatMonitorSettingsPage(dlg));
1525 dlg->registerSettingsPage(new ItemViewSettingsPage(dlg));
1526 dlg->registerSettingsPage(new BufferViewSettingsPage(dlg));
1527 dlg->registerSettingsPage(new InputWidgetSettingsPage(dlg));
1528 dlg->registerSettingsPage(new TopicWidgetSettingsPage(dlg));
1530 dlg->registerSettingsPage(new SonnetSettingsPage(dlg));
1532 dlg->registerSettingsPage(new HighlightSettingsPage(dlg));
1533 dlg->registerSettingsPage(new CoreHighlightSettingsPage(dlg));
1534 dlg->registerSettingsPage(new NotificationsSettingsPage(dlg));
1535 dlg->registerSettingsPage(new BacklogSettingsPage(dlg));
1538 dlg->registerSettingsPage(new ConnectionSettingsPage(dlg));
1539 dlg->registerSettingsPage(new IdentitiesSettingsPage(dlg));
1540 dlg->registerSettingsPage(new NetworksSettingsPage(dlg));
1541 dlg->registerSettingsPage(new AliasesSettingsPage(dlg));
1542 dlg->registerSettingsPage(new IgnoreListSettingsPage(dlg));
1543 // dlg->registerSettingsPage(new DccSettingsPage(dlg)); not ready yet
1545 // Category: Remote Cores
1546 if (Quassel::runMode() != Quassel::Monolithic) {
1547 dlg->registerSettingsPage(new CoreAccountSettingsPage(dlg));
1548 dlg->registerSettingsPage(new CoreConnectionSettingsPage(dlg));
1554 void MainWin::showAboutDlg()
1559 void MainWin::showShortcutsDlg()
1562 KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed);
1563 foreach (KActionCollection* coll, QtUi::actionCollections()) {
1564 dlg.addCollection(coll, coll->property("Category").toString());
1566 dlg.configure(true);
1568 SettingsPageDlg{new ShortcutsSettingsPage{QtUi::actionCollections()}}.exec();
1572 void MainWin::showNewTransferDlg(const QUuid& transferId)
1574 auto transfer = Client::transferManager()->transfer(transferId);
1576 if (transfer->status() == Transfer::Status::New) {
1577 auto* dlg = new ReceiveFileDlg(transfer, this);
1582 qWarning() << "Unknown transfer ID" << transferId;
1586 void MainWin::onFullScreenToggled()
1588 // Relying on QWidget::isFullScreen is discouraged, see the KToggleFullScreenAction docs
1589 // Also, one should not use showFullScreen() or showNormal(), as those reset all other window flags
1592 static_cast<KToggleFullScreenAction*>(_fullScreenAction)->setFullScreen(this, _fullScreenAction->isChecked());
1594 if (_fullScreenAction->isChecked())
1595 setWindowState(windowState() | Qt::WindowFullScreen);
1597 setWindowState(windowState() & ~Qt::WindowFullScreen);
1601 /********************************************************************************************************/
1603 bool MainWin::event(QEvent* event)
1605 switch (event->type()) {
1606 case QEvent::WindowActivate: {
1607 BufferId bufferId = Client::bufferModel()->currentBuffer();
1608 if (bufferId.isValid())
1609 Client::instance()->markBufferAsRead(bufferId);
1612 case QEvent::WindowDeactivate:
1613 if (bufferWidget()->autoMarkerLineOnLostFocus())
1614 bufferWidget()->setMarkerLine();
1619 return QMainWindow::event(event);
1622 void MainWin::moveEvent(QMoveEvent* event)
1624 if (!(windowState() & Qt::WindowMaximized))
1625 _normalPos = event->pos();
1627 QMainWindow::moveEvent(event);
1630 void MainWin::resizeEvent(QResizeEvent* event)
1632 if (!(windowState() & Qt::WindowMaximized))
1633 _normalSize = event->size();
1635 QMainWindow::resizeEvent(event);
1638 void MainWin::closeEvent(QCloseEvent* event)
1641 auto* app = qobject_cast<QtUiApplication*> qApp;
1643 // On OSX it can happen that the closeEvent occurs twice. (Especially if packaged with Frameworks)
1644 // This messes up MainWinState/MainWinHidden save/restore.
1645 // It's a bug in Qt: https://bugreports.qt.io/browse/QTBUG-43344
1646 if (!_aboutToQuit && !app->isAboutToQuit() && QtUi::haveSystemTray() && s.value("MinimizeOnClose").toBool()) {
1647 QtUi::hideMainWidget();
1650 else if (!_aboutToQuit) {
1651 _aboutToQuit = true;
1653 Quassel::instance()->quit();
1660 void MainWin::messagesInserted(const QModelIndex& parent, int start, int end)
1664 bool hasFocus = QApplication::activeWindow() != nullptr;
1666 for (int i = start; i <= end; i++) {
1667 QModelIndex idx = Client::messageModel()->index(i, ChatLineModel::ContentsColumn);
1668 if (!idx.isValid()) {
1669 qDebug() << "MainWin::messagesInserted(): Invalid model index!";
1672 Message::Flags flags = (Message::Flags)idx.data(ChatLineModel::FlagsRole).toInt();
1673 if (flags.testFlag(Message::Backlog) || flags.testFlag(Message::Self))
1676 BufferId bufId = idx.data(ChatLineModel::BufferIdRole).value<BufferId>();
1677 BufferInfo::Type bufType = Client::networkModel()->bufferType(bufId);
1679 // check if bufferId belongs to the shown chatlists
1680 if (!(Client::bufferViewOverlay()->bufferIds().contains(bufId) || Client::bufferViewOverlay()->tempRemovedBufferIds().contains(bufId)))
1683 // check if it's the buffer currently displayed
1684 if (hasFocus && bufId == Client::bufferModel()->currentBuffer())
1687 // only show notifications for higlights or queries
1688 if (bufType != BufferInfo::QueryBuffer && !(flags & Message::Highlight))
1691 // and of course: don't notify for ignored messages
1692 if (Client::ignoreListManager()
1693 && Client::ignoreListManager()->match(idx.data(MessageModel::MessageRole).value<Message>(),
1694 Client::networkModel()->networkName(bufId)))
1697 // seems like we have a legit notification candidate!
1698 QModelIndex senderIdx = Client::messageModel()->index(i, ChatLineModel::SenderColumn);
1699 QString sender = senderIdx.data(ChatLineModel::EditRole).toString();
1700 QString contents = idx.data(ChatLineModel::DisplayRole).toString();
1701 AbstractNotificationBackend::NotificationType type;
1703 if (bufType == BufferInfo::QueryBuffer && !hasFocus)
1704 type = AbstractNotificationBackend::PrivMsg;
1705 else if (bufType == BufferInfo::QueryBuffer && hasFocus)
1706 type = AbstractNotificationBackend::PrivMsgFocused;
1707 else if (flags & Message::Highlight && !hasFocus)
1708 type = AbstractNotificationBackend::Highlight;
1710 type = AbstractNotificationBackend::HighlightFocused;
1712 QtUi::instance()->invokeNotification(bufId, type, sender, contents);
1716 void MainWin::currentBufferChanged(BufferId buffer)
1718 if (buffer.isValid())
1719 Client::instance()->markBufferAsRead(buffer);
1722 void MainWin::clientNetworkCreated(NetworkId id)
1724 const Network* net = Client::network(id);
1725 auto* act = new QAction(net->networkName(), this);
1726 act->setObjectName(QString("NetworkAction-%1").arg(id.toInt()));
1727 act->setData(QVariant::fromValue<NetworkId>(id));
1728 connect(net, &SyncableObject::updatedRemotely, this, &MainWin::clientNetworkUpdated);
1729 connect(act, &QAction::triggered, this, &MainWin::connectOrDisconnectFromNet);
1731 QAction* beforeAction = nullptr;
1732 foreach (QAction* action, _networksMenu->actions()) {
1733 if (!action->data().isValid()) // ignore stock actions
1735 if (net->networkName().localeAwareCompare(action->text()) < 0) {
1736 beforeAction = action;
1740 _networksMenu->insertAction(beforeAction, act);
1743 void MainWin::clientNetworkUpdated()
1745 const auto* net = qobject_cast<const Network*>(sender());
1749 auto* action = findChild<QAction*>(QString("NetworkAction-%1").arg(net->networkId().toInt()));
1753 action->setText(net->networkName());
1755 switch (net->connectionState()) {
1756 case Network::Initialized:
1757 action->setIcon(icon::get("network-connect"));
1758 // if we have no currently selected buffer, jump to the first connecting statusbuffer
1759 if (!bufferWidget()->currentBuffer().isValid()) {
1760 QModelIndex idx = Client::networkModel()->networkIndex(net->networkId());
1761 if (idx.isValid()) {
1762 BufferId statusBufferId = idx.data(NetworkModel::BufferIdRole).value<BufferId>();
1763 Client::bufferModel()->switchToBuffer(statusBufferId);
1767 case Network::Disconnected:
1768 action->setIcon(icon::get("network-disconnect"));
1771 action->setIcon(icon::get("network-wired"));
1775 void MainWin::clientNetworkRemoved(NetworkId id)
1777 auto* action = findChild<QAction*>(QString("NetworkAction-%1").arg(id.toInt()));
1781 action->deleteLater();
1784 void MainWin::connectOrDisconnectFromNet()
1786 auto* act = qobject_cast<QAction*>(sender());
1789 const Network* net = Client::network(act->data().value<NetworkId>());
1792 if (net->connectionState() == Network::Disconnected)
1793 net->requestConnect();
1795 net->requestDisconnect();
1798 void MainWin::onFormatApplyColorTriggered()
1803 _inputWidget->applyFormatActiveColor();
1806 void MainWin::onFormatApplyColorFillTriggered()
1811 _inputWidget->applyFormatActiveColorFill();
1814 void MainWin::onFormatClearTriggered()
1819 _inputWidget->clearFormat();
1822 void MainWin::onFormatBoldTriggered()
1827 _inputWidget->toggleFormatBold();
1830 void MainWin::onFormatItalicTriggered()
1835 _inputWidget->toggleFormatItalic();
1838 void MainWin::onFormatUnderlineTriggered()
1843 _inputWidget->toggleFormatUnderline();
1846 void MainWin::onJumpHotBufferTriggered()
1848 if (!_bufferHotList->rowCount())
1851 Client::bufferModel()->switchToBuffer(_bufferHotList->hottestBuffer());
1854 void MainWin::onBufferSearchTriggered()
1856 if (_activeBufferViewIndex < 0 || _activeBufferViewIndex >= _bufferViews.count()) {
1857 qWarning() << "Tried to activate filter on invalid bufferview:" << _activeBufferViewIndex;
1861 _bufferViews[_activeBufferViewIndex]->activateFilter();
1864 void MainWin::onJumpKey()
1866 auto* action = qobject_cast<QAction*>(sender());
1867 if (!action || !Client::bufferModel())
1869 int idx = action->property("Index").toInt();
1871 if (_jumpKeyMap.isEmpty())
1872 _jumpKeyMap = CoreAccountSettings().jumpKeyMap();
1874 if (!_jumpKeyMap.contains(idx))
1877 BufferId buffer = _jumpKeyMap.value(idx);
1878 if (buffer.isValid())
1879 Client::bufferModel()->switchToBuffer(buffer);
1882 void MainWin::bindJumpKey()
1884 auto* action = qobject_cast<QAction*>(sender());
1885 if (!action || !Client::bufferModel())
1887 int idx = action->property("Index").toInt();
1889 _jumpKeyMap[idx] = Client::bufferModel()->currentBuffer();
1890 CoreAccountSettings().setJumpKeyMap(_jumpKeyMap);
1893 void MainWin::onDebugNetworkModelTriggered()
1895 auto* view = new QTreeView;
1896 view->setAttribute(Qt::WA_DeleteOnClose);
1897 view->setWindowTitle("Debug NetworkModel View");
1898 view->setModel(Client::networkModel());
1899 view->setColumnWidth(0, 250);
1900 view->setColumnWidth(1, 250);
1901 view->setColumnWidth(2, 80);
1902 view->resize(610, 300);
1906 void MainWin::onDebugHotListTriggered()
1908 _bufferHotList->invalidate();
1909 _bufferHotList->sort(0, Qt::DescendingOrder);
1911 auto* view = new QTreeView;
1912 view->setAttribute(Qt::WA_DeleteOnClose);
1913 view->setModel(_bufferHotList);
1917 void MainWin::onDebugBufferViewOverlayTriggered()
1919 auto* overlay = new DebugBufferViewOverlay(nullptr);
1920 overlay->setAttribute(Qt::WA_DeleteOnClose);
1924 void MainWin::onDebugMessageModelTriggered()
1926 auto* view = new QTableView(nullptr);
1927 auto* filter = new DebugMessageModelFilter(view);
1928 filter->setSourceModel(Client::messageModel());
1929 view->setModel(filter);
1930 view->setAttribute(Qt::WA_DeleteOnClose, true);
1931 view->verticalHeader()->hide();
1932 view->horizontalHeader()->setStretchLastSection(true);
1936 void MainWin::onDebugLogTriggered()
1938 auto dlg = new DebugLogDlg(this); // will be deleted on close
1942 void MainWin::onShowResourceTreeTriggered()
1944 auto dlg = new ResourceTreeDlg(this); // will be deleted on close
1948 void MainWin::showStatusBarMessage(const QString& message)
1950 statusBar()->showMessage(message, 10000);