modernize: Use override instead of virtual
[quassel.git] / src / qtui / mainwin.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
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.                                           *
9  *                                                                         *
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.                          *
14  *                                                                         *
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  ***************************************************************************/
20
21 #include "mainwin.h"
22
23 #include <QIcon>
24 #include <QMenuBar>
25 #include <QMessageBox>
26 #include <QStatusBar>
27 #include <QTableView>
28 #include <QToolBar>
29 #include <QInputDialog>
30
31 #ifdef HAVE_KF5
32 #  include <KConfigWidgets/KStandardAction>
33 #  include <KXmlGui/KHelpMenu>
34 #  include <KXmlGui/KShortcutsDialog>
35 #  include <KXmlGui/KToolBar>
36 #  include <KWidgetsAddons/KToggleFullScreenAction>
37 #endif
38
39 #ifdef Q_WS_X11
40 #  include <QX11Info>
41 #endif
42
43 #include "aboutdlg.h"
44 #include "awaylogfilter.h"
45 #include "awaylogview.h"
46 #include "action.h"
47 #include "actioncollection.h"
48 #include "bufferhotlistfilter.h"
49 #include "buffermodel.h"
50 #include "bufferview.h"
51 #include "bufferviewoverlay.h"
52 #include "bufferviewoverlayfilter.h"
53 #include "bufferwidget.h"
54 #include "channellistdlg.h"
55 #include "chatlinemodel.h"
56 #include "chatmonitorfilter.h"
57 #include "chatmonitorview.h"
58 #include "chatview.h"
59 #include "client.h"
60 #include "clientbacklogmanager.h"
61 #include "clientbufferviewconfig.h"
62 #include "clientbufferviewmanager.h"
63 #include "clientignorelistmanager.h"
64 #include "clienttransfer.h"
65 #include "clienttransfermanager.h"
66 #include "coreconfigwizard.h"
67 #include "coreconnectdlg.h"
68 #include "coreconnection.h"
69 #include "coreconnectionstatuswidget.h"
70 #include "coreinfodlg.h"
71 #include "contextmenuactionprovider.h"
72 #include "debugbufferviewoverlay.h"
73 #include "debuglogdlg.h"
74 #include "debugmessagemodelfilter.h"
75 #include "flatproxymodel.h"
76 #include "icon.h"
77 #include "inputwidget.h"
78 #include "irclistmodel.h"
79 #include "ircconnectionwizard.h"
80 #include "legacysystemtray.h"
81 #include "msgprocessorstatuswidget.h"
82 #include "nicklistwidget.h"
83 #include "passwordchangedlg.h"
84 #include "qtuiapplication.h"
85 #include "qtuimessageprocessor.h"
86 #include "qtuisettings.h"
87 #include "qtuistyle.h"
88 #include "receivefiledlg.h"
89 #include "resourcetreedlg.h"
90 #include "settingsdlg.h"
91 #include "settingspagedlg.h"
92 #include "statusnotifieritem.h"
93 #include "toolbaractionprovider.h"
94 #include "topicwidget.h"
95 #include "transfermodel.h"
96 #include "verticaldock.h"
97
98 #ifndef HAVE_KDE
99 #  ifdef HAVE_QTMULTIMEDIA
100 #    include "qtmultimedianotificationbackend.h"
101 #  endif
102 #  include "systraynotificationbackend.h"
103 #  include "taskbarnotificationbackend.h"
104 #else /* HAVE_KDE */
105 #  include "knotificationbackend.h"
106 #endif /* HAVE_KDE */
107 #include "systrayanimationnotificationbackend.h"
108
109
110 #ifdef HAVE_LIBSNORE
111 #  include "snorenotificationbackend.h"
112 #endif
113
114 #ifdef HAVE_SSL
115 #  include "sslinfodlg.h"
116 #endif
117
118 #ifdef HAVE_NOTIFICATION_CENTER
119   #include "osxnotificationbackend.h"
120 #endif
121
122 #ifdef HAVE_DBUS
123   #include "dockmanagernotificationbackend.h"
124 #endif
125
126 #include "settingspages/aliasessettingspage.h"
127 #include "settingspages/appearancesettingspage.h"
128 #include "settingspages/backlogsettingspage.h"
129 #include "settingspages/bufferviewsettingspage.h"
130 #include "settingspages/chatmonitorsettingspage.h"
131 #include "settingspages/chatviewsettingspage.h"
132 #include "settingspages/chatviewcolorsettingspage.h"
133 #include "settingspages/connectionsettingspage.h"
134 #include "settingspages/coreaccountsettingspage.h"
135 #include "settingspages/coreconnectionsettingspage.h"
136 #include <settingspages/corehighlightsettingspage.h>
137 #include "settingspages/dccsettingspage.h"
138 #include "settingspages/highlightsettingspage.h"
139 #include "settingspages/identitiessettingspage.h"
140 #include "settingspages/ignorelistsettingspage.h"
141 #include "settingspages/inputwidgetsettingspage.h"
142 #include "settingspages/itemviewsettingspage.h"
143 #include "settingspages/networkssettingspage.h"
144 #include "settingspages/notificationssettingspage.h"
145 #include "settingspages/topicwidgetsettingspage.h"
146
147 #ifndef HAVE_KDE
148 #  include "settingspages/shortcutssettingspage.h"
149 #endif
150
151 #ifdef HAVE_SONNET
152 #  include "settingspages/sonnetsettingspage.h"
153 #endif
154
155
156 MainWin::MainWin(QWidget *parent)
157 #ifdef HAVE_KDE
158     : KMainWindow(parent), _kHelpMenu(new KHelpMenu(this)),
159 #else
160     : QMainWindow(parent),
161 #endif
162     _msgProcessorStatusWidget(new MsgProcessorStatusWidget(this)),
163     _coreConnectionStatusWidget(new CoreConnectionStatusWidget(Client::coreConnection(), this)),
164     _titleSetter(this),
165     _awayLog(nullptr),
166     _layoutLoaded(false),
167     _activeBufferViewIndex(-1),
168     _aboutToQuit(false)
169 {
170     setAttribute(Qt::WA_DeleteOnClose, false); // we delete the mainwin manually
171
172     QtUiSettings uiSettings;
173     QString style = uiSettings.value("Style", QString()).toString();
174     if (!style.isEmpty()) {
175         QApplication::setStyle(style);
176     }
177
178     QApplication::setQuitOnLastWindowClosed(false);
179
180     setWindowTitle("Quassel IRC");
181     setWindowIconText("Quassel IRC");
182     // Set the default icon for all windows
183     QApplication::setWindowIcon(icon::get("quassel"));
184     updateIcon();
185 }
186
187
188 void MainWin::init()
189 {
190     connect(Client::instance(), SIGNAL(networkCreated(NetworkId)), SLOT(clientNetworkCreated(NetworkId)));
191     connect(Client::instance(), SIGNAL(networkRemoved(NetworkId)), SLOT(clientNetworkRemoved(NetworkId)));
192     connect(Client::messageModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
193         SLOT(messagesInserted(const QModelIndex &, int, int)));
194     connect(GraphicalUi::contextMenuActionProvider(),
195             SIGNAL(showChannelList(NetworkId,QString,bool)),
196             SLOT(showChannelList(NetworkId,QString,bool)));
197     connect(Client::instance(),
198             SIGNAL(showChannelList(NetworkId,QString,bool)),
199             SLOT(showChannelList(NetworkId,QString,bool)));
200     connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showNetworkConfig(NetworkId)), SLOT(showNetworkConfig(NetworkId)));
201     connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
202     connect(Client::instance(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
203     connect(Client::instance(), SIGNAL(dbUpgradeInProgress(bool)), SLOT(showMigrationWarning(bool)));
204     connect(Client::instance(), SIGNAL(exitRequested(QString)), SLOT(onExitRequested(QString)));
205
206     connect(Client::coreConnection(), SIGNAL(startCoreSetup(QVariantList, QVariantList)), SLOT(showCoreConfigWizard(QVariantList, QVariantList)));
207     connect(Client::coreConnection(), SIGNAL(connectionErrorPopup(QString)), SLOT(handleCoreConnectionError(QString)));
208     connect(Client::coreConnection(), SIGNAL(userAuthenticationRequired(CoreAccount *, bool *, QString)), SLOT(userAuthenticationRequired(CoreAccount *, bool *, QString)));
209     connect(Client::coreConnection(), SIGNAL(handleNoSslInClient(bool *)), SLOT(handleNoSslInClient(bool *)));
210     connect(Client::coreConnection(), SIGNAL(handleNoSslInCore(bool *)), SLOT(handleNoSslInCore(bool *)));
211 #ifdef HAVE_SSL
212     connect(Client::coreConnection(), SIGNAL(handleSslErrors(const QSslSocket *, bool *, bool *)), SLOT(handleSslErrors(const QSslSocket *, bool *, bool *)));
213 #endif
214
215     // Setup Dock Areas
216     setDockNestingEnabled(true);
217     setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
218     setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
219     setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
220     setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
221
222     // Order is sometimes important
223     setupActions();
224     setupBufferWidget();
225     setupMenus();
226     // setupTransferWidget(); not ready yet
227     setupChatMonitor();
228     setupTopicWidget();
229     setupInputWidget();
230     setupNickWidget();
231     setupViewMenuTail();
232     setupStatusBar();
233     setupToolBars();
234     setupSystray();
235     setupTitleSetter();
236     setupHotList();
237
238     _bufferWidget->setFocusProxy(_inputWidget);
239     _chatMonitorView->setFocusProxy(_inputWidget);
240
241 #ifndef HAVE_KDE
242 #  ifdef HAVE_QTMULTIMEDIA
243     QtUi::registerNotificationBackend(new QtMultimediaNotificationBackend(this));
244 #  endif
245     QtUi::registerNotificationBackend(new TaskbarNotificationBackend(this));
246 #else /* HAVE_KDE */
247     QtUi::registerNotificationBackend(new KNotificationBackend(this));
248 #endif /* HAVE_KDE */
249
250
251 #ifndef QT_NO_SYSTEMTRAYICON
252     QtUi::registerNotificationBackend(new SystrayAnimationNotificationBackend(this));
253 #endif
254 #ifdef HAVE_LIBSNORE
255     QtUi::registerNotificationBackend(new SnoreNotificationBackend(this));
256 #elif !defined(QT_NO_SYSTEMTRAYICON) && !defined(HAVE_KDE)
257     QtUi::registerNotificationBackend(new SystrayNotificationBackend(this));
258 #endif
259
260 #ifdef HAVE_NOTIFICATION_CENTER
261     QtUi::registerNotificationBackend(new OSXNotificationBackend(this));
262 #endif
263
264 #ifdef HAVE_DBUS
265     QtUi::registerNotificationBackend(new DockManagerNotificationBackend(this));
266 #endif
267
268     // we assume that at this point, all configurable actions are defined!
269     QtUi::loadShortcuts();
270
271     connect(bufferWidget(), SIGNAL(currentChanged(BufferId)), SLOT(currentBufferChanged(BufferId)));
272
273     setDisconnectedState(); // Disable menus and stuff
274
275 #ifdef HAVE_KDE
276     setAutoSaveSettings();
277 #endif
278
279     // restore mainwin state
280     QtUiSettings s;
281     restoreStateFromSettings(s);
282
283     // restore locked state of docks
284     QtUi::actionCollection("General")->action("LockLayout")->setChecked(s.value("LockLayout", false).toBool());
285
286     Quassel::registerQuitHandler([this]() {
287         QtUiSettings s;
288         saveStateToSettings(s);
289         saveLayout();
290         // Close all open dialogs and the MainWin, so we can safely kill the Client instance afterwards
291         // Note: This does not quit the application, as quitOnLastWindowClosed is set to false.
292         //       We rely on another quit handler to be registered that actually quits the application.
293         qApp->closeAllWindows();
294     });
295
296     QTimer::singleShot(0, this, SLOT(doAutoConnect()));
297 }
298
299
300 void MainWin::saveStateToSettings(UiSettings &s)
301 {
302     s.setValue("MainWinSize", _normalSize);
303     s.setValue("MainWinPos", _normalPos);
304     s.setValue("MainWinState", saveState());
305     s.setValue("MainWinGeometry", saveGeometry());
306     s.setValue("MainWinMinimized", isMinimized());
307     s.setValue("MainWinMaximized", isMaximized());
308     s.setValue("MainWinHidden", !isVisible());
309     BufferId lastBufId = Client::bufferModel()->currentBuffer();
310     if (lastBufId.isValid())
311         s.setValue("LastUsedBufferId", lastBufId.toInt());
312
313 #ifdef HAVE_KDE
314     saveAutoSaveSettings();
315 #endif
316 }
317
318
319 void MainWin::restoreStateFromSettings(UiSettings &s)
320 {
321     _normalSize = s.value("MainWinSize", size()).toSize();
322     _normalPos = s.value("MainWinPos", pos()).toPoint();
323     bool maximized = s.value("MainWinMaximized", false).toBool();
324
325 #ifndef HAVE_KDE
326     restoreGeometry(s.value("MainWinGeometry").toByteArray());
327
328     if (maximized) {
329         // restoreGeometry() fails if the windows was maximized, so we resize and position explicitly
330         resize(_normalSize);
331         move(_normalPos);
332     }
333
334     restoreState(s.value("MainWinState").toByteArray());
335
336 #else
337     move(_normalPos);
338 #endif
339
340     if ((Quassel::isOptionSet("hidewindow")
341             || s.value("MainWinHidden").toBool())
342             && _systemTray->isSystemTrayAvailable())
343         QtUi::hideMainWidget();
344     else if (s.value("MainWinMinimized").toBool())
345         showMinimized();
346     else if (maximized)
347         showMaximized();
348     else
349         show();
350 }
351
352 QMenu *MainWin::createPopupMenu()
353 {
354     QMenu *popupMenu = QMainWindow::createPopupMenu();
355     popupMenu->addSeparator();
356     ActionCollection *coll = QtUi::actionCollection("General");
357     popupMenu->addAction(coll->action("ToggleMenuBar"));
358     return popupMenu;
359 }
360
361
362 void MainWin::updateIcon()
363 {
364     QIcon icon;
365     if (Client::isConnected())
366         icon = icon::get("quassel");
367     else
368         icon = icon::get("inactive-quassel");
369     setWindowIcon(icon);
370 }
371
372
373 void MainWin::setupActions()
374 {
375     ActionCollection *coll = QtUi::actionCollection("General", tr("General"));
376     // File
377     coll->addAction("ConnectCore", new Action(icon::get("connect-quassel"), tr("&Connect to Core..."), coll,
378             this, SLOT(showCoreConnectionDlg())));
379     coll->addAction("DisconnectCore", new Action(icon::get("disconnect-quassel"), tr("&Disconnect from Core"), coll,
380             Client::instance(), SLOT(disconnectFromCore())));
381     coll->addAction("ChangePassword", new Action(icon::get("dialog-password"), tr("Change &Password..."), coll,
382             this, SLOT(showPasswordChangeDlg())));
383     coll->addAction("CoreInfo", new Action(icon::get("help-about"), tr("Core &Info..."), coll,
384             this, SLOT(showCoreInfoDlg())));
385     coll->addAction("ConfigureNetworks", new Action(icon::get("configure"), tr("Configure &Networks..."), coll,
386             this, SLOT(on_actionConfigureNetworks_triggered())));
387     // QKeySequence::Quit was added in Qt 4.6, and could be used instead.  However, that key
388     // sequence is empty by default on Windows, which would remove Ctrl-Q to quit.  It may be best
389     // to just keep it this way.
390     //
391     // See https://doc.qt.io/qt-5/qkeysequence.html
392     coll->addAction("Quit", new Action(icon::get("application-exit"), tr("&Quit"), coll,
393             Quassel::instance(), SLOT(quit()), Qt::CTRL + Qt::Key_Q));
394
395     // View
396     coll->addAction("ConfigureBufferViews", new Action(tr("&Configure Chat Lists..."), coll,
397             this, SLOT(on_actionConfigureViews_triggered())));
398
399     QAction *lockAct = coll->addAction("LockLayout", new Action(tr("&Lock Layout"), coll));
400     lockAct->setCheckable(true);
401     connect(lockAct, SIGNAL(toggled(bool)), SLOT(on_actionLockLayout_toggled(bool)));
402
403     coll->addAction("ToggleSearchBar", new Action(icon::get("edit-find"), tr("Show &Search Bar"), coll,
404             nullptr, nullptr, QKeySequence::Find))->setCheckable(true);
405     coll->addAction("ShowAwayLog", new Action(tr("Show Away Log"), coll,
406             this, SLOT(showAwayLog())));
407     coll->addAction("ToggleMenuBar", new Action(icon::get("show-menu"), tr("Show &Menubar"), coll,
408             nullptr, nullptr))->setCheckable(true);
409
410     coll->addAction("ToggleStatusBar", new Action(tr("Show Status &Bar"), coll,
411             nullptr, nullptr))->setCheckable(true);
412
413 #ifdef HAVE_KDE
414     _fullScreenAction = KStandardAction::fullScreen(this, SLOT(onFullScreenToggled()), this, coll);
415 #else
416     _fullScreenAction = new Action(icon::get("view-fullscreen"), tr("&Full Screen Mode"), coll,
417         this, SLOT(onFullScreenToggled()), QKeySequence(Qt::Key_F11));
418     _fullScreenAction->setCheckable(true);
419     coll->addAction("ToggleFullScreen", _fullScreenAction);
420 #endif
421
422     // Settings
423     QAction *configureShortcutsAct = new Action(icon::get("configure-shortcuts"), tr("Configure &Shortcuts..."), coll,
424         this, SLOT(showShortcutsDlg()));
425     configureShortcutsAct->setMenuRole(QAction::NoRole);
426     coll->addAction("ConfigureShortcuts", configureShortcutsAct);
427
428 #ifdef Q_OS_MAC
429     QAction *configureQuasselAct = new Action(icon::get("configure"), tr("&Configure Quassel..."), coll,
430         this, SLOT(showSettingsDlg()));
431     configureQuasselAct->setMenuRole(QAction::PreferencesRole);
432 #else
433     QAction *configureQuasselAct = new Action(icon::get("configure"), tr("&Configure Quassel..."), coll,
434         this, SLOT(showSettingsDlg()), QKeySequence(Qt::Key_F7));
435 #endif
436     coll->addAction("ConfigureQuassel", configureQuasselAct);
437
438     // Help
439     QAction *aboutQuasselAct = new Action(icon::get("quassel"), tr("&About Quassel"), coll,
440         this, SLOT(showAboutDlg()));
441     aboutQuasselAct->setMenuRole(QAction::AboutRole);
442     coll->addAction("AboutQuassel", aboutQuasselAct);
443
444     QAction *aboutQtAct = new Action(QIcon(":/pics/qt-logo.png"), tr("About &Qt"), coll,
445         qApp, SLOT(aboutQt()));
446     aboutQtAct->setMenuRole(QAction::AboutQtRole);
447     coll->addAction("AboutQt", aboutQtAct);
448     coll->addAction("DebugNetworkModel", new Action(icon::get("tools-report-bug"), tr("Debug &NetworkModel"), coll,
449             this, SLOT(on_actionDebugNetworkModel_triggered())));
450     coll->addAction("DebugBufferViewOverlay", new Action(icon::get("tools-report-bug"), tr("Debug &BufferViewOverlay"), coll,
451             this, SLOT(on_actionDebugBufferViewOverlay_triggered())));
452     coll->addAction("DebugMessageModel", new Action(icon::get("tools-report-bug"), tr("Debug &MessageModel"), coll,
453             this, SLOT(on_actionDebugMessageModel_triggered())));
454     coll->addAction("DebugHotList", new Action(icon::get("tools-report-bug"), tr("Debug &HotList"), coll,
455             this, SLOT(on_actionDebugHotList_triggered())));
456     coll->addAction("DebugLog", new Action(icon::get("tools-report-bug"), tr("Debug &Log"), coll,
457             this, SLOT(on_actionDebugLog_triggered())));
458     coll->addAction("ShowResourceTree", new Action(icon::get("tools-report-bug"), tr("Show &Resource Tree"), coll,
459             this, SLOT(on_actionShowResourceTree_triggered())));
460     coll->addAction("ReloadStyle", new Action(icon::get("view-refresh"), tr("Reload Stylesheet"), coll,
461             QtUi::style(), SLOT(reload()), QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_R)));
462
463     coll->addAction("HideCurrentBuffer", new Action(tr("Hide Current Buffer"), coll,
464             this, SLOT(hideCurrentBuffer()), QKeySequence::Close));
465
466     // Text formatting
467     coll = QtUi::actionCollection("TextFormat", tr("Text formatting"));
468
469     coll->addAction("FormatApplyColor", new Action(
470                         icon::get("format-text-color"), tr("Apply foreground color"), coll,
471                         this, SLOT(on_inputFormatApplyColor_triggered()),
472                         QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_G)));
473
474     coll->addAction("FormatApplyColorFill", new Action(
475                         icon::get("format-fill-color"), tr("Apply background color"), coll,
476                         this, SLOT(on_inputFormatApplyColorFill_triggered()),
477                         QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)));
478
479     coll->addAction("FormatClear", new Action(
480                         icon::get("edit-clear"), tr("Clear formatting"), coll,
481                         this, SLOT(on_inputFormatClear_triggered()),
482                         QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C)));
483
484     coll->addAction("FormatBold", new Action(
485                         icon::get("format-text-bold"), tr("Toggle bold"), coll,
486                         this, SLOT(on_inputFormatBold_triggered()),
487                         QKeySequence::Bold));
488
489     coll->addAction("FormatItalic", new Action(
490                         icon::get("format-text-italic"), tr("Toggle italics"), coll,
491                         this, SLOT(on_inputFormatItalic_triggered()),
492                         QKeySequence::Italic));
493
494     coll->addAction("FormatUnderline", new Action(
495                         icon::get("format-text-underline"), tr("Toggle underline"), coll,
496                         this, SLOT(on_inputFormatUnderline_triggered()), QKeySequence::Underline));
497
498     // Navigation
499     coll = QtUi::actionCollection("Navigation", tr("Navigation"));
500
501     coll->addAction("JumpHotBuffer", new Action(tr("Jump to hot chat"), coll,
502             this, SLOT(on_jumpHotBuffer_triggered()), QKeySequence(Qt::META + Qt::Key_A)));
503
504     coll->addAction("ActivateBufferFilter", new Action(tr("Activate the buffer search"), coll,
505             this, SLOT(on_bufferSearch_triggered()), QKeySequence(Qt::CTRL + Qt::Key_S)));
506
507     // Jump keys
508 #ifdef Q_OS_MAC
509     const int bindModifier = Qt::ControlModifier | Qt::AltModifier;
510     const int jumpModifier = Qt::ControlModifier;
511 #else
512     const int bindModifier = Qt::ControlModifier;
513     const int jumpModifier = Qt::AltModifier;
514 #endif
515
516     coll->addAction("BindJumpKey0", new Action(tr("Set Quick Access #0"), coll, this, SLOT(bindJumpKey()),
517             QKeySequence(bindModifier + Qt::Key_0)))->setProperty("Index", 0);
518     coll->addAction("BindJumpKey1", new Action(tr("Set Quick Access #1"), coll, this, SLOT(bindJumpKey()),
519             QKeySequence(bindModifier + Qt::Key_1)))->setProperty("Index", 1);
520     coll->addAction("BindJumpKey2", new Action(tr("Set Quick Access #2"), coll, this, SLOT(bindJumpKey()),
521             QKeySequence(bindModifier + Qt::Key_2)))->setProperty("Index", 2);
522     coll->addAction("BindJumpKey3", new Action(tr("Set Quick Access #3"), coll, this, SLOT(bindJumpKey()),
523             QKeySequence(bindModifier + Qt::Key_3)))->setProperty("Index", 3);
524     coll->addAction("BindJumpKey4", new Action(tr("Set Quick Access #4"), coll, this, SLOT(bindJumpKey()),
525             QKeySequence(bindModifier + Qt::Key_4)))->setProperty("Index", 4);
526     coll->addAction("BindJumpKey5", new Action(tr("Set Quick Access #5"), coll, this, SLOT(bindJumpKey()),
527             QKeySequence(bindModifier + Qt::Key_5)))->setProperty("Index", 5);
528     coll->addAction("BindJumpKey6", new Action(tr("Set Quick Access #6"), coll, this, SLOT(bindJumpKey()),
529             QKeySequence(bindModifier + Qt::Key_6)))->setProperty("Index", 6);
530     coll->addAction("BindJumpKey7", new Action(tr("Set Quick Access #7"), coll, this, SLOT(bindJumpKey()),
531             QKeySequence(bindModifier + Qt::Key_7)))->setProperty("Index", 7);
532     coll->addAction("BindJumpKey8", new Action(tr("Set Quick Access #8"), coll, this, SLOT(bindJumpKey()),
533             QKeySequence(bindModifier + Qt::Key_8)))->setProperty("Index", 8);
534     coll->addAction("BindJumpKey9", new Action(tr("Set Quick Access #9"), coll, this, SLOT(bindJumpKey()),
535             QKeySequence(bindModifier + Qt::Key_9)))->setProperty("Index", 9);
536
537     coll->addAction("JumpKey0", new Action(tr("Quick Access #0"), coll, this, SLOT(onJumpKey()),
538             QKeySequence(jumpModifier + Qt::Key_0)))->setProperty("Index", 0);
539     coll->addAction("JumpKey1", new Action(tr("Quick Access #1"), coll, this, SLOT(onJumpKey()),
540             QKeySequence(jumpModifier + Qt::Key_1)))->setProperty("Index", 1);
541     coll->addAction("JumpKey2", new Action(tr("Quick Access #2"), coll, this, SLOT(onJumpKey()),
542             QKeySequence(jumpModifier + Qt::Key_2)))->setProperty("Index", 2);
543     coll->addAction("JumpKey3", new Action(tr("Quick Access #3"), coll, this, SLOT(onJumpKey()),
544             QKeySequence(jumpModifier + Qt::Key_3)))->setProperty("Index", 3);
545     coll->addAction("JumpKey4", new Action(tr("Quick Access #4"), coll, this, SLOT(onJumpKey()),
546             QKeySequence(jumpModifier + Qt::Key_4)))->setProperty("Index", 4);
547     coll->addAction("JumpKey5", new Action(tr("Quick Access #5"), coll, this, SLOT(onJumpKey()),
548             QKeySequence(jumpModifier + Qt::Key_5)))->setProperty("Index", 5);
549     coll->addAction("JumpKey6", new Action(tr("Quick Access #6"), coll, this, SLOT(onJumpKey()),
550             QKeySequence(jumpModifier + Qt::Key_6)))->setProperty("Index", 6);
551     coll->addAction("JumpKey7", new Action(tr("Quick Access #7"), coll, this, SLOT(onJumpKey()),
552             QKeySequence(jumpModifier + Qt::Key_7)))->setProperty("Index", 7);
553     coll->addAction("JumpKey8", new Action(tr("Quick Access #8"), coll, this, SLOT(onJumpKey()),
554             QKeySequence(jumpModifier + Qt::Key_8)))->setProperty("Index", 8);
555     coll->addAction("JumpKey9", new Action(tr("Quick Access #9"), coll, this, SLOT(onJumpKey()),
556             QKeySequence(jumpModifier + Qt::Key_9)))->setProperty("Index", 9);
557
558     // Buffer navigation
559     coll->addAction("NextBufferView", new Action(icon::get("go-next-view"), tr("Activate Next Chat List"), coll,
560             this, SLOT(nextBufferView()), QKeySequence(QKeySequence::Forward)));
561     coll->addAction("PreviousBufferView", new Action(icon::get("go-previous-view"), tr("Activate Previous Chat List"), coll,
562             this, SLOT(previousBufferView()), QKeySequence::Back));
563     coll->addAction("NextBuffer", new Action(icon::get("go-down"), tr("Go to Next Chat"), coll,
564             this, SLOT(nextBuffer()), QKeySequence(Qt::ALT + Qt::Key_Down)));
565     coll->addAction("PreviousBuffer", new Action(icon::get("go-up"), tr("Go to Previous Chat"), coll,
566             this, SLOT(previousBuffer()), QKeySequence(Qt::ALT + Qt::Key_Up)));
567 }
568
569
570 void MainWin::setupMenus()
571 {
572     ActionCollection *coll = QtUi::actionCollection("General");
573
574     _fileMenu = menuBar()->addMenu(tr("&File"));
575
576     static const QStringList coreActions = QStringList()
577         << "ConnectCore" << "DisconnectCore" << "ChangePassword" << "CoreInfo";
578
579     QAction *coreAction;
580     foreach(QString actionName, coreActions) {
581         coreAction = coll->action(actionName);
582         _fileMenu->addAction(coreAction);
583         flagRemoteCoreOnly(coreAction);
584     }
585     flagRemoteCoreOnly(_fileMenu->addSeparator());
586
587     _networksMenu = _fileMenu->addMenu(tr("&Networks"));
588     _networksMenu->addAction(coll->action("ConfigureNetworks"));
589     _networksMenu->addSeparator();
590     _fileMenu->addSeparator();
591     _fileMenu->addAction(coll->action("Quit"));
592
593     _viewMenu = menuBar()->addMenu(tr("&View"));
594     _bufferViewsMenu = _viewMenu->addMenu(tr("&Chat Lists"));
595     _bufferViewsMenu->addAction(coll->action("ConfigureBufferViews"));
596     _toolbarMenu = _viewMenu->addMenu(tr("&Toolbars"));
597     _viewMenu->addSeparator();
598
599     _viewMenu->addAction(coll->action("ToggleMenuBar"));
600     _viewMenu->addAction(coll->action("ToggleStatusBar"));
601     _viewMenu->addAction(coll->action("ToggleSearchBar"));
602
603     coreAction = coll->action("ShowAwayLog");
604     flagRemoteCoreOnly(coreAction);
605     _viewMenu->addAction(coreAction);
606
607     _viewMenu->addSeparator();
608     _viewMenu->addAction(coll->action("LockLayout"));
609
610     _settingsMenu = menuBar()->addMenu(tr("&Settings"));
611 #ifdef HAVE_KDE
612     _settingsMenu->addAction(KStandardAction::configureNotifications(this, SLOT(showNotificationsDlg()), this));
613     _settingsMenu->addAction(KStandardAction::keyBindings(this, SLOT(showShortcutsDlg()), this));
614 #else
615     _settingsMenu->addAction(coll->action("ConfigureShortcuts"));
616 #endif
617     _settingsMenu->addAction(coll->action("ConfigureQuassel"));
618
619
620     _helpMenu = menuBar()->addMenu(tr("&Help"));
621
622     _helpMenu->addAction(coll->action("AboutQuassel"));
623 #ifndef HAVE_KDE
624     _helpMenu->addAction(coll->action("AboutQt"));
625 #else
626     _helpMenu->addAction(KStandardAction::aboutKDE(_kHelpMenu, SLOT(aboutKDE()), this));
627 #endif
628     _helpMenu->addSeparator();
629     _helpDebugMenu = _helpMenu->addMenu(icon::get("tools-report-bug"), tr("Debug"));
630     _helpDebugMenu->addAction(coll->action("DebugNetworkModel"));
631     _helpDebugMenu->addAction(coll->action("DebugBufferViewOverlay"));
632     _helpDebugMenu->addAction(coll->action("DebugMessageModel"));
633     _helpDebugMenu->addAction(coll->action("DebugHotList"));
634     _helpDebugMenu->addAction(coll->action("DebugLog"));
635     _helpDebugMenu->addAction(coll->action("ShowResourceTree"));
636     _helpDebugMenu->addSeparator();
637     _helpDebugMenu->addAction(coll->action("ReloadStyle"));
638
639     // Toggle visibility
640     QAction *showMenuBar = QtUi::actionCollection("General")->action("ToggleMenuBar");
641
642     QtUiSettings uiSettings;
643     bool enabled = uiSettings.value("ShowMenuBar", QVariant(true)).toBool();
644     showMenuBar->setChecked(enabled);
645     enabled ? menuBar()->show() : menuBar()->hide();
646
647     connect(showMenuBar, SIGNAL(toggled(bool)), menuBar(), SLOT(setVisible(bool)));
648     connect(showMenuBar, SIGNAL(toggled(bool)), this, SLOT(saveMenuBarStatus(bool)));
649 }
650
651
652 void MainWin::setupBufferWidget()
653 {
654     _bufferWidget = new BufferWidget(this);
655     _bufferWidget->setModel(Client::bufferModel());
656     _bufferWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
657     setCentralWidget(_bufferWidget);
658 }
659
660
661 void MainWin::addBufferView(int bufferViewConfigId)
662 {
663     addBufferView(Client::bufferViewManager()->clientBufferViewConfig(bufferViewConfigId));
664 }
665
666
667 void MainWin::addBufferView(ClientBufferViewConfig *config)
668 {
669     if (!config)
670         return;
671
672     config->setLocked(QtUiSettings().value("LockLayout", false).toBool());
673     BufferViewDock *dock = new BufferViewDock(config, this);
674
675     //create the view and initialize it's filter
676     BufferView *view = new BufferView(dock);
677     view->setFilteredModel(Client::bufferModel(), config);
678     view->installEventFilter(_inputWidget); // for key presses
679
680     Client::bufferModel()->synchronizeView(view);
681
682     dock->setLocked(QtUiSettings().value("LockLayout", false).toBool());
683
684     dock->setWidget(view);
685     dock->setVisible(_layoutLoaded); // don't show before state has been restored
686
687     addDockWidget(Qt::LeftDockWidgetArea, dock);
688     _bufferViewsMenu->addAction(dock->toggleViewAction());
689
690     connect(dock->toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(bufferViewToggled(bool)));
691     connect(dock, SIGNAL(visibilityChanged(bool)), SLOT(bufferViewVisibilityChanged(bool)));
692     _bufferViews.append(dock);
693
694     if (!activeBufferView())
695         nextBufferView();
696 }
697
698
699 void MainWin::removeBufferView(int bufferViewConfigId)
700 {
701     QVariant actionData;
702     BufferViewDock *dock;
703     foreach(QAction *action, _bufferViewsMenu->actions()) {
704         actionData = action->data();
705         if (!actionData.isValid())
706             continue;
707
708         dock = qobject_cast<BufferViewDock *>(action->parent());
709         if (dock && actionData.toInt() == bufferViewConfigId) {
710             removeAction(action);
711             Client::bufferViewOverlay()->removeView(dock->bufferViewId());
712             _bufferViews.removeAll(dock);
713
714             if (dock->isActive()) {
715                 dock->setActive(false);
716                 _activeBufferViewIndex = -1;
717                 nextBufferView();
718             }
719
720             dock->deleteLater();
721         }
722     }
723 }
724
725
726 void MainWin::bufferViewToggled(bool enabled)
727 {
728     if (!enabled && !isMinimized()) {
729         // hiding the mainwindow triggers a toggle of the bufferview (which pretty much sucks big time)
730         // since this isn't our fault and we can't do anything about it, we suppress the resulting calls
731         return;
732     }
733     QAction *action = qobject_cast<QAction *>(sender());
734     Q_ASSERT(action);
735     BufferViewDock *dock = qobject_cast<BufferViewDock *>(action->parent());
736     Q_ASSERT(dock);
737
738     // Make sure we don't toggle backlog fetch for a view we've already removed
739     if (!_bufferViews.contains(dock))
740         return;
741
742     if (enabled)
743         Client::bufferViewOverlay()->addView(dock->bufferViewId());
744     else
745         Client::bufferViewOverlay()->removeView(dock->bufferViewId());
746 }
747
748
749 void MainWin::bufferViewVisibilityChanged(bool visible)
750 {
751     Q_UNUSED(visible);
752     BufferViewDock *dock = qobject_cast<BufferViewDock *>(sender());
753     Q_ASSERT(dock);
754     if ((!dock->isHidden() && !activeBufferView()) || (dock->isHidden() && dock->isActive()))
755         nextBufferView();
756 }
757
758
759 BufferView *MainWin::allBuffersView() const
760 {
761     // "All Buffers" is always the first dock created
762     if (_bufferViews.count() > 0)
763         return _bufferViews[0]->bufferView();
764     return nullptr;
765 }
766
767
768 BufferView *MainWin::activeBufferView() const
769 {
770     if (_activeBufferViewIndex < 0 || _activeBufferViewIndex >= _bufferViews.count())
771         return nullptr;
772     BufferViewDock *dock = _bufferViews.at(_activeBufferViewIndex);
773     return dock->isActive() ? dock->bufferView() : nullptr;
774 }
775
776
777 void MainWin::changeActiveBufferView(int bufferViewId)
778 {
779     if (bufferViewId < 0)
780         return;
781
782     if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
783         _bufferViews[_activeBufferViewIndex]->setActive(false);
784         _activeBufferViewIndex = -1;
785     }
786
787     for (int i = 0; i < _bufferViews.count(); i++) {
788         BufferViewDock *dock = _bufferViews.at(i);
789         if (dock->bufferViewId() == bufferViewId && !dock->isHidden()) {
790             _activeBufferViewIndex = i;
791             dock->setActive(true);
792             return;
793         }
794     }
795
796     nextBufferView(); // fallback
797 }
798
799
800 void MainWin::showPasswordChangeDlg()
801 {
802     if(Client::isCoreFeatureEnabled(Quassel::Feature::PasswordChange)) {
803         PasswordChangeDlg{}.exec();
804     }
805     else {
806         QMessageBox box(QMessageBox::Warning, tr("Feature Not Supported"),
807                         tr("<b>Your Quassel Core does not support this feature</b>"),
808                         QMessageBox::Ok);
809         box.setInformativeText(tr("You need a Quassel Core v0.12.0 or newer in order to be able to remotely change your password."));
810         box.exec();
811     }
812 }
813
814
815 void MainWin::showMigrationWarning(bool show)
816 {
817     if (show && !_migrationWarning) {
818         _migrationWarning = new QMessageBox(QMessageBox::Information,
819                                             tr("Upgrading..."),
820                                             "<b>" + tr("Your database is being upgraded") + "</b>",
821                                             QMessageBox::NoButton, this);
822         _migrationWarning->setInformativeText("<p>"
823                                               + tr("In order to support new features, we need to make changes to your backlog database. This may take a long while.")
824                                               + "</p><p>"
825                                               + tr("Do not exit Quassel until the upgrade is complete!")
826                                               + "</p>");
827         _migrationWarning->setStandardButtons(QMessageBox::NoButton);
828         _migrationWarning->show();
829     }
830     else if (!show && _migrationWarning) {
831         _migrationWarning->close();
832         _migrationWarning->deleteLater();
833         _migrationWarning = nullptr;
834     }
835 }
836
837
838 void MainWin::onExitRequested(const QString &reason)
839 {
840     if (!reason.isEmpty()) {
841         QMessageBox box(QMessageBox::Critical,
842                         tr("Fatal error"),
843                         "<b>" + tr("Quassel encountered a fatal error and is terminated.") + "</b>",
844                         QMessageBox::Ok);
845         box.setInformativeText("<p>" + tr("Reason:<em>") + " " + reason + "</em>");
846         box.exec();
847     }
848 }
849
850
851 void MainWin::changeActiveBufferView(bool backwards)
852 {
853     if (_activeBufferViewIndex >= 0 && _activeBufferViewIndex < _bufferViews.count()) {
854         _bufferViews[_activeBufferViewIndex]->setActive(false);
855     }
856
857     if (!_bufferViews.count())
858         return;
859
860     int c = _bufferViews.count();
861     while (c--) { // yes, this will reactivate the current active one if all others fail
862         if (backwards) {
863             if (--_activeBufferViewIndex < 0)
864                 _activeBufferViewIndex = _bufferViews.count()-1;
865         }
866         else {
867             if (++_activeBufferViewIndex >= _bufferViews.count())
868                 _activeBufferViewIndex = 0;
869         }
870
871         BufferViewDock *dock = _bufferViews.at(_activeBufferViewIndex);
872         if (dock->isHidden())
873             continue;
874
875         dock->setActive(true);
876         return;
877     }
878
879     _activeBufferViewIndex = -1;
880 }
881
882
883 void MainWin::nextBufferView()
884 {
885     changeActiveBufferView(false);
886 }
887
888
889 void MainWin::previousBufferView()
890 {
891     changeActiveBufferView(true);
892 }
893
894
895 void MainWin::nextBuffer()
896 {
897     BufferView *view = activeBufferView();
898     if (view)
899         view->nextBuffer();
900 }
901
902
903 void MainWin::previousBuffer()
904 {
905     BufferView *view = activeBufferView();
906     if (view)
907         view->previousBuffer();
908 }
909
910
911 void MainWin::hideCurrentBuffer()
912 {
913     BufferView *view = activeBufferView();
914     if (view)
915         view->hideCurrentBuffer();
916 }
917
918
919 void MainWin::showNotificationsDlg()
920 {
921     SettingsPageDlg{new NotificationsSettingsPage{}}.exec();
922 }
923
924
925 void MainWin::on_actionConfigureNetworks_triggered()
926 {
927     SettingsPageDlg{new NetworksSettingsPage{}}.exec();
928 }
929
930
931 void MainWin::on_actionConfigureViews_triggered()
932 {
933     SettingsPageDlg{new BufferViewSettingsPage{}}.exec();
934 }
935
936
937 void MainWin::on_actionLockLayout_toggled(bool lock)
938 {
939     QList<VerticalDock *> docks = findChildren<VerticalDock *>();
940     foreach(VerticalDock *dock, docks) {
941         dock->showTitle(!lock);
942     }
943
944     QList<NickListDock *> nickdocks = findChildren<NickListDock *>();
945     foreach(NickListDock *nickdock, nickdocks) {
946         nickdock->setLocked(lock);
947     }
948
949     QList<BufferViewDock *> bufferdocks = findChildren<BufferViewDock *>();
950     foreach(BufferViewDock *bufferdock, bufferdocks) {
951         bufferdock->setLocked(lock);
952     }
953
954     if (Client::bufferViewManager()) {
955         foreach(ClientBufferViewConfig *config, Client::bufferViewManager()->clientBufferViewConfigs()) {
956             config->setLocked(lock);
957         }
958     }
959
960     _mainToolBar->setMovable(!lock);
961     _nickToolBar->setMovable(!lock);
962
963     QtUiSettings().setValue("LockLayout", lock);
964 }
965
966
967 void MainWin::setupNickWidget()
968 {
969     // create nick dock
970     NickListDock *nickDock = new NickListDock(tr("Nicks"), this);
971     nickDock->setObjectName("NickDock");
972     nickDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
973     nickDock->setLocked(QtUiSettings().value("LockLayout", false).toBool());
974
975     _nickListWidget = new NickListWidget(nickDock);
976     nickDock->setWidget(_nickListWidget);
977
978     addDockWidget(Qt::RightDockWidgetArea, nickDock);
979     _viewMenu->addAction(nickDock->toggleViewAction());
980     nickDock->toggleViewAction()->setText(tr("Show Nick List"));
981
982     // See NickListDock::NickListDock();
983     // connect(nickDock->toggleViewAction(), SIGNAL(triggered(bool)), nickListWidget, SLOT(showWidget(bool)));
984
985     // attach the NickListWidget to the BufferModel and the default selection
986     _nickListWidget->setModel(Client::bufferModel());
987     _nickListWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
988
989     _nickListWidget->setVisible(false);
990 }
991
992
993 void MainWin::setupChatMonitor()
994 {
995     VerticalDock *dock = new VerticalDock(tr("Chat Monitor"), this);
996     dock->setObjectName("ChatMonitorDock");
997
998     ChatMonitorFilter *filter = new ChatMonitorFilter(Client::messageModel(), this);
999     _chatMonitorView = new ChatMonitorView(filter, this);
1000     _chatMonitorView->show();
1001     dock->setWidget(_chatMonitorView);
1002     dock->hide();
1003
1004     addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
1005     _viewMenu->addAction(dock->toggleViewAction());
1006     dock->toggleViewAction()->setText(tr("Show Chat Monitor"));
1007 }
1008
1009
1010 void MainWin::setupInputWidget()
1011 {
1012     VerticalDock *dock = new VerticalDock(tr("Inputline"), this);
1013     dock->setObjectName("InputDock");
1014
1015     _inputWidget = new InputWidget(dock);
1016     dock->setWidget(_inputWidget);
1017
1018     addDockWidget(Qt::BottomDockWidgetArea, dock);
1019
1020     _viewMenu->addAction(dock->toggleViewAction());
1021     dock->toggleViewAction()->setText(tr("Show Input Line"));
1022
1023     _inputWidget->setModel(Client::bufferModel());
1024     _inputWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
1025
1026     _inputWidget->inputLine()->installEventFilter(_bufferWidget);
1027
1028     connect(_topicWidget, SIGNAL(switchedPlain()), _bufferWidget, SLOT(setFocus()));
1029 }
1030
1031
1032 void MainWin::setupTopicWidget()
1033 {
1034     VerticalDock *dock = new VerticalDock(tr("Topic"), this);
1035     dock->setObjectName("TopicDock");
1036     _topicWidget = new TopicWidget(dock);
1037
1038     dock->setWidget(_topicWidget);
1039
1040     _topicWidget->setModel(Client::bufferModel());
1041     _topicWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
1042
1043     addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
1044
1045     _viewMenu->addAction(dock->toggleViewAction());
1046     dock->toggleViewAction()->setText(tr("Show Topic Line"));
1047 }
1048
1049
1050 void MainWin::setupTransferWidget()
1051 {
1052     auto dock = new QDockWidget(tr("Transfers"), this);
1053     dock->setObjectName("TransferDock");
1054     dock->setAllowedAreas(Qt::TopDockWidgetArea|Qt::BottomDockWidgetArea);
1055
1056     auto view = new QTableView(dock); // to be replaced by the real thing
1057     view->setModel(Client::transferModel());
1058     dock->setWidget(view);
1059     dock->hide(); // hidden by default
1060     addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
1061
1062     auto action = dock->toggleViewAction();
1063     action->setText(tr("Show File Transfers"));
1064     action->setIcon(icon::get("download"));
1065     action->setShortcut(QKeySequence(Qt::Key_F6));
1066     QtUi::actionCollection("General")->addAction("ShowTransferWidget", action);
1067     _viewMenu->addAction(action);
1068 }
1069
1070
1071 void MainWin::setupViewMenuTail()
1072 {
1073     _viewMenu->addSeparator();
1074     _viewMenu->addAction(_fullScreenAction);
1075 }
1076
1077
1078 void MainWin::setupTitleSetter()
1079 {
1080     _titleSetter.setModel(Client::bufferModel());
1081     _titleSetter.setSelectionModel(Client::bufferModel()->standardSelectionModel());
1082 }
1083
1084
1085 void MainWin::setupStatusBar()
1086 {
1087     // MessageProcessor progress
1088     statusBar()->addPermanentWidget(_msgProcessorStatusWidget);
1089
1090     // Connection state
1091     _coreConnectionStatusWidget->update();
1092     statusBar()->addPermanentWidget(_coreConnectionStatusWidget);
1093
1094     QAction *showStatusbar = QtUi::actionCollection("General")->action("ToggleStatusBar");
1095
1096     QtUiSettings uiSettings;
1097
1098     bool enabled = uiSettings.value("ShowStatusBar", QVariant(true)).toBool();
1099     showStatusbar->setChecked(enabled);
1100     enabled ? statusBar()->show() : statusBar()->hide();
1101
1102     connect(showStatusbar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool)));
1103     connect(showStatusbar, SIGNAL(toggled(bool)), this, SLOT(saveStatusBarStatus(bool)));
1104
1105     connect(Client::coreConnection(), SIGNAL(connectionMsg(QString)), statusBar(), SLOT(showMessage(QString)));
1106 }
1107
1108
1109 void MainWin::setupHotList()
1110 {
1111     FlatProxyModel *flatProxy = new FlatProxyModel(this);
1112     flatProxy->setSourceModel(Client::bufferModel());
1113     _bufferHotList = new BufferHotListFilter(flatProxy);
1114 }
1115
1116
1117 void MainWin::saveMenuBarStatus(bool enabled)
1118 {
1119     QtUiSettings uiSettings;
1120     uiSettings.setValue("ShowMenuBar", enabled);
1121 }
1122
1123
1124 void MainWin::saveStatusBarStatus(bool enabled)
1125 {
1126     QtUiSettings uiSettings;
1127     uiSettings.setValue("ShowStatusBar", enabled);
1128 }
1129
1130
1131 void MainWin::setupSystray()
1132 {
1133 #ifdef HAVE_DBUS
1134     _systemTray = new StatusNotifierItem(this);
1135 #elif !defined QT_NO_SYSTEMTRAYICON
1136     _systemTray = new LegacySystemTray(this);
1137 #else
1138     _systemTray = new SystemTray(this); // dummy
1139 #endif
1140 }
1141
1142
1143 void MainWin::setupToolBars()
1144 {
1145     connect(_bufferWidget, SIGNAL(currentChanged(QModelIndex)),
1146         QtUi::toolBarActionProvider(), SLOT(currentBufferChanged(QModelIndex)));
1147     connect(_nickListWidget, SIGNAL(nickSelectionChanged(QModelIndexList)),
1148         QtUi::toolBarActionProvider(), SLOT(nickSelectionChanged(QModelIndexList)));
1149
1150 #ifdef Q_OS_MAC
1151     setUnifiedTitleAndToolBarOnMac(true);
1152 #endif
1153
1154 #ifdef HAVE_KDE
1155     _mainToolBar = new KToolBar("MainToolBar", this, Qt::TopToolBarArea, false, true, true);
1156 #else
1157     _mainToolBar = new QToolBar(this);
1158     _mainToolBar->setObjectName("MainToolBar");
1159 #endif
1160     _mainToolBar->setWindowTitle(tr("Main Toolbar"));
1161     addToolBar(_mainToolBar);
1162
1163     if (Quassel::runMode() != Quassel::Monolithic) {
1164         ActionCollection *coll = QtUi::actionCollection("General");
1165         _mainToolBar->addAction(coll->action("ConnectCore"));
1166         _mainToolBar->addAction(coll->action("DisconnectCore"));
1167     }
1168
1169     _mainToolBar->setMovable(!QtUiSettings().value("LockLayout", false).toBool());
1170
1171     QtUi::toolBarActionProvider()->addActions(_mainToolBar, ToolBarActionProvider::MainToolBar);
1172     _toolbarMenu->addAction(_mainToolBar->toggleViewAction());
1173
1174 #ifdef HAVE_KDE
1175     _nickToolBar = new KToolBar("NickToolBar", this, Qt::TopToolBarArea, false, true, true);
1176 #else
1177     _nickToolBar = new QToolBar(this);
1178     _nickToolBar->setObjectName("NickToolBar");
1179 #endif
1180     _nickToolBar->setWindowTitle(tr("Nick Toolbar"));
1181     _nickToolBar->setVisible(false); //default: not visible
1182     addToolBar(_nickToolBar);
1183     _nickToolBar->setMovable(!QtUiSettings().value("LockLayout", false).toBool());
1184
1185     QtUi::toolBarActionProvider()->addActions(_nickToolBar, ToolBarActionProvider::NickToolBar);
1186     _toolbarMenu->addAction(_nickToolBar->toggleViewAction());
1187
1188 #ifdef Q_OS_MAC
1189     QtUiSettings uiSettings;
1190
1191     bool visible = uiSettings.value("ShowMainToolBar", QVariant(true)).toBool();
1192     _mainToolBar->setVisible(visible);
1193     connect(_mainToolBar, SIGNAL(visibilityChanged(bool)), this, SLOT(saveMainToolBarStatus(bool)));
1194 #endif
1195 }
1196
1197 void MainWin::saveMainToolBarStatus(bool enabled)
1198 {
1199 #ifdef Q_OS_MAC
1200     QtUiSettings uiSettings;
1201     uiSettings.setValue("ShowMainToolBar", enabled);
1202 #else
1203     Q_UNUSED(enabled);
1204 #endif
1205 }
1206
1207
1208 void MainWin::doAutoConnect()
1209 {
1210     if (!Client::coreConnection()->connectToCore()) {
1211         // No autoconnect selected (or no accounts)
1212         showCoreConnectionDlg();
1213     }
1214 }
1215
1216
1217 void MainWin::connectedToCore()
1218 {
1219     Q_CHECK_PTR(Client::bufferViewManager());
1220     connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigAdded(int)), this, SLOT(addBufferView(int)));
1221     connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigDeleted(int)), this, SLOT(removeBufferView(int)));
1222     connect(Client::bufferViewManager(), SIGNAL(initDone()), this, SLOT(loadLayout()));
1223
1224     if (Client::transferManager()) {
1225         connect(Client::transferManager(), SIGNAL(transferAdded(QUuid)), SLOT(showNewTransferDlg(QUuid)));
1226     }
1227
1228     setConnectedState();
1229 }
1230
1231
1232 void MainWin::setConnectedState()
1233 {
1234     ActionCollection *coll = QtUi::actionCollection("General");
1235
1236     coll->action("ConnectCore")->setEnabled(false);
1237     coll->action("DisconnectCore")->setEnabled(true);
1238     coll->action("ChangePassword")->setEnabled(true);
1239     coll->action("CoreInfo")->setEnabled(true);
1240
1241     foreach(QAction *action, _fileMenu->actions()) {
1242         if (isRemoteCoreOnly(action))
1243             action->setVisible(!Client::internalCore());
1244     }
1245
1246     disconnect(Client::backlogManager(), SIGNAL(updateProgress(int, int)), _msgProcessorStatusWidget, SLOT(setProgress(int, int)));
1247     disconnect(Client::backlogManager(), SIGNAL(messagesRequested(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
1248     disconnect(Client::backlogManager(), SIGNAL(messagesProcessed(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
1249     if (!Client::internalCore()) {
1250         connect(Client::backlogManager(), SIGNAL(updateProgress(int, int)), _msgProcessorStatusWidget, SLOT(setProgress(int, int)));
1251         connect(Client::backlogManager(), SIGNAL(messagesRequested(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
1252         connect(Client::backlogManager(), SIGNAL(messagesProcessed(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
1253     }
1254
1255     // _viewMenu->setEnabled(true);
1256     if (!Client::internalCore())
1257         statusBar()->showMessage(tr("Connected to core."));
1258     else
1259         statusBar()->clearMessage();
1260
1261     _coreConnectionStatusWidget->setVisible(!Client::internalCore());
1262     updateIcon();
1263     systemTray()->setState(SystemTray::Active);
1264
1265     if (Client::networkIds().isEmpty()) {
1266         IrcConnectionWizard *wizard = new IrcConnectionWizard(this, Qt::Sheet);
1267         wizard->show();
1268     }
1269     else {
1270         // Monolithic always preselects last used buffer - Client only if the connection died
1271         if (Client::coreConnection()->wasReconnect() || Quassel::runMode() == Quassel::Monolithic) {
1272             QtUiSettings s;
1273             BufferId lastUsedBufferId(s.value("LastUsedBufferId").toInt());
1274             if (lastUsedBufferId.isValid())
1275                 Client::bufferModel()->switchToBuffer(lastUsedBufferId);
1276         }
1277     }
1278 }
1279
1280
1281 void MainWin::loadLayout()
1282 {
1283     QtUiSettings s;
1284     int accountId = Client::currentCoreAccount().accountId().toInt();
1285     QByteArray state = s.value(QString("MainWinState-%1").arg(accountId)).toByteArray();
1286     if (state.isEmpty()) {
1287         foreach(BufferViewDock *view, _bufferViews)
1288         view->show();
1289         _layoutLoaded = true;
1290         return;
1291     }
1292     _nickListWidget->setVisible(true);
1293     restoreState(state, accountId);
1294     int bufferViewId = s.value(QString("ActiveBufferView-%1").arg(accountId), -1).toInt();
1295     if (bufferViewId >= 0)
1296         changeActiveBufferView(bufferViewId);
1297
1298     _layoutLoaded = true;
1299 }
1300
1301
1302 void MainWin::saveLayout()
1303 {
1304     QtUiSettings s;
1305     int accountId = _bufferViews.count() ? Client::currentCoreAccount().accountId().toInt() : 0; // only save if we still have a layout!
1306     if (accountId > 0) {
1307         s.setValue(QString("MainWinState-%1").arg(accountId), saveState(accountId));
1308         BufferView *view = activeBufferView();
1309         s.setValue(QString("ActiveBufferView-%1").arg(accountId), view ? view->config()->bufferViewId() : -1);
1310     }
1311 }
1312
1313
1314 void MainWin::disconnectedFromCore()
1315 {
1316     // save core specific layout and remove bufferviews;
1317     saveLayout();
1318     _layoutLoaded = false;
1319
1320     QVariant actionData;
1321     BufferViewDock *dock;
1322     foreach(QAction *action, _bufferViewsMenu->actions()) {
1323         actionData = action->data();
1324         if (!actionData.isValid())
1325             continue;
1326
1327         dock = qobject_cast<BufferViewDock *>(action->parent());
1328         if (dock && actionData.toInt() != -1) {
1329             removeAction(action);
1330             _bufferViews.removeAll(dock);
1331             dock->deleteLater();
1332         }
1333     }
1334
1335     // store last active buffer
1336     QtUiSettings s;
1337     BufferId lastBufId = _bufferWidget->currentBuffer();
1338     if (lastBufId.isValid()) {
1339         s.setValue("LastUsedBufferId", lastBufId.toInt());
1340         // clear the current selection
1341         Client::bufferModel()->standardSelectionModel()->clearSelection();
1342     }
1343     restoreState(s.value("MainWinState").toByteArray());
1344     setDisconnectedState();
1345 }
1346
1347
1348 void MainWin::setDisconnectedState()
1349 {
1350     ActionCollection *coll = QtUi::actionCollection("General");
1351     //ui.menuCore->setEnabled(false);
1352     coll->action("ConnectCore")->setEnabled(true);
1353     coll->action("DisconnectCore")->setEnabled(false);
1354     coll->action("CoreInfo")->setEnabled(false);
1355     coll->action("ChangePassword")->setEnabled(false);
1356     //_viewMenu->setEnabled(false);
1357     statusBar()->showMessage(tr("Not connected to core."));
1358     if (_msgProcessorStatusWidget)
1359         _msgProcessorStatusWidget->setProgress(0, 0);
1360     updateIcon();
1361     systemTray()->setState(SystemTray::Passive);
1362     _nickListWidget->setVisible(false);
1363 }
1364
1365
1366 void MainWin::userAuthenticationRequired(CoreAccount *account, bool *valid, const QString &errorMessage)
1367 {
1368     Q_UNUSED(errorMessage)
1369     CoreConnectAuthDlg dlg(account);
1370     *valid = (dlg.exec() == QDialog::Accepted);
1371 }
1372
1373
1374 void MainWin::handleNoSslInClient(bool *accepted)
1375 {
1376     QMessageBox box(QMessageBox::Warning, tr("Unencrypted Connection"), tr("<b>Your client does not support SSL encryption</b>"),
1377         QMessageBox::Ignore|QMessageBox::Cancel);
1378     box.setInformativeText(tr("Sensitive data, like passwords, will be transmitted unencrypted to your Quassel core."));
1379     box.setDefaultButton(QMessageBox::Ignore);
1380     *accepted = box.exec() == QMessageBox::Ignore;
1381 }
1382
1383
1384 void MainWin::handleNoSslInCore(bool *accepted)
1385 {
1386     QMessageBox box(QMessageBox::Warning, tr("Unencrypted Connection"), tr("<b>Your core does not support SSL encryption</b>"),
1387         QMessageBox::Ignore|QMessageBox::Cancel);
1388     box.setInformativeText(tr("Sensitive data, like passwords, will be transmitted unencrypted to your Quassel core."));
1389     box.setDefaultButton(QMessageBox::Ignore);
1390     *accepted = box.exec() == QMessageBox::Ignore;
1391 }
1392
1393
1394 #ifdef HAVE_SSL
1395
1396 void MainWin::handleSslErrors(const QSslSocket *socket, bool *accepted, bool *permanently)
1397 {
1398     QString errorString = "<ul>";
1399     foreach(const QSslError error, socket->sslErrors())
1400     errorString += QString("<li>%1</li>").arg(error.errorString());
1401     errorString += "</ul>";
1402
1403     QMessageBox box(QMessageBox::Warning,
1404         tr("Untrusted Security Certificate"),
1405         tr("<b>The SSL certificate provided by the core at %1 is untrusted for the following reasons:</b>").arg(socket->peerName()),
1406         QMessageBox::Cancel);
1407     box.setInformativeText(errorString);
1408     box.addButton(tr("Continue"), QMessageBox::AcceptRole);
1409     box.setDefaultButton(box.addButton(tr("Show Certificate"), QMessageBox::HelpRole));
1410
1411     QMessageBox::ButtonRole role;
1412     do {
1413         box.exec();
1414         role = box.buttonRole(box.clickedButton());
1415         if (role == QMessageBox::HelpRole) {
1416             SslInfoDlg dlg(socket);
1417             dlg.exec();
1418         }
1419     }
1420     while (role == QMessageBox::HelpRole);
1421
1422     *accepted = role == QMessageBox::AcceptRole;
1423     if (*accepted) {
1424         QMessageBox box2(QMessageBox::Warning,
1425             tr("Untrusted Security Certificate"),
1426             tr("Would you like to accept this certificate forever without being prompted?"),
1427             0);
1428         box2.setDefaultButton(box2.addButton(tr("Current Session Only"), QMessageBox::NoRole));
1429         box2.addButton(tr("Forever"), QMessageBox::YesRole);
1430         box2.exec();
1431         *permanently =  box2.buttonRole(box2.clickedButton()) == QMessageBox::YesRole;
1432     }
1433 }
1434
1435
1436 #endif /* HAVE_SSL */
1437
1438 void MainWin::handleCoreConnectionError(const QString &error)
1439 {
1440     QMessageBox::critical(this, tr("Core Connection Error"), error, QMessageBox::Ok);
1441 }
1442
1443
1444 void MainWin::showCoreConnectionDlg()
1445 {
1446     CoreConnectDlg dlg;
1447     if (dlg.exec() == QDialog::Accepted) {
1448         AccountId accId = dlg.selectedAccount();
1449         if (accId.isValid())
1450             Client::coreConnection()->connectToCore(accId);
1451     }
1452 }
1453
1454
1455 void MainWin::showCoreConfigWizard(const QVariantList &backends, const QVariantList &authenticators)
1456 {
1457     CoreConfigWizard *wizard = new CoreConfigWizard(Client::coreConnection(), backends, authenticators, this);
1458
1459     wizard->show();
1460 }
1461
1462
1463 void MainWin::showChannelList(NetworkId netId, const QString &channelFilters, bool listImmediately)
1464 {
1465     if (!netId.isValid()) {
1466         QAction *action = qobject_cast<QAction *>(sender());
1467         if (action)
1468             netId = action->data().value<NetworkId>();
1469         if (!netId.isValid()) {
1470             // We still haven't found a valid network, probably no network selected, e.g. "/list"
1471             // on the client homescreen when no networks are connected.
1472             QMessageBox box(QMessageBox::Information, tr("No network selected"),
1473                             QString("<b>%1</b>").arg(tr("No network selected")),
1474                             QMessageBox::Ok);
1475             box.setInformativeText(tr("Select a network before trying to view the channel list."));
1476             box.exec();
1477             return;
1478         }
1479     }
1480
1481     ChannelListDlg *channelListDlg = new ChannelListDlg(this);
1482     channelListDlg->setAttribute(Qt::WA_DeleteOnClose);
1483     channelListDlg->setNetwork(netId);
1484     if (!channelFilters.isEmpty()) {
1485         channelListDlg->setChannelFilters(channelFilters);
1486     }
1487     if (listImmediately) {
1488         channelListDlg->requestSearch();
1489     }
1490     channelListDlg->show();
1491 }
1492
1493
1494 void MainWin::showNetworkConfig(NetworkId netId)
1495 {
1496     SettingsPageDlg dlg{new NetworksSettingsPage{}};
1497     if (netId.isValid())
1498         qobject_cast<NetworksSettingsPage *>(dlg.currentPage())->bufferList_Open(netId);
1499     dlg.exec();
1500 }
1501
1502
1503 void MainWin::showIgnoreList(QString newRule)
1504 {
1505     SettingsPageDlg dlg{new IgnoreListSettingsPage{}};
1506     // prepare config dialog for new rule
1507     if (!newRule.isEmpty())
1508         qobject_cast<IgnoreListSettingsPage *>(dlg.currentPage())->editIgnoreRule(newRule);
1509     dlg.exec();
1510 }
1511
1512
1513 void MainWin::showCoreInfoDlg()
1514 {
1515     CoreInfoDlg{}.exec();
1516 }
1517
1518
1519 void MainWin::showAwayLog()
1520 {
1521     if (_awayLog)
1522         return;
1523     AwayLogFilter *filter = new AwayLogFilter(Client::messageModel());
1524     _awayLog = new AwayLogView(filter, nullptr);
1525     filter->setParent(_awayLog);
1526     connect(_awayLog, SIGNAL(destroyed()), this, SLOT(awayLogDestroyed()));
1527     _awayLog->setAttribute(Qt::WA_DeleteOnClose);
1528     _awayLog->show();
1529 }
1530
1531
1532 void MainWin::awayLogDestroyed()
1533 {
1534     _awayLog = nullptr;
1535 }
1536
1537
1538 void MainWin::showSettingsDlg()
1539 {
1540     SettingsDlg *dlg = new SettingsDlg(this);
1541
1542     //Category: Interface
1543     dlg->registerSettingsPage(new AppearanceSettingsPage(dlg));
1544     dlg->registerSettingsPage(new ChatViewSettingsPage(dlg));
1545     dlg->registerSettingsPage(new ChatViewColorSettingsPage(dlg));
1546     dlg->registerSettingsPage(new ChatMonitorSettingsPage(dlg));
1547     dlg->registerSettingsPage(new ItemViewSettingsPage(dlg));
1548     dlg->registerSettingsPage(new BufferViewSettingsPage(dlg));
1549     dlg->registerSettingsPage(new InputWidgetSettingsPage(dlg));
1550     dlg->registerSettingsPage(new TopicWidgetSettingsPage(dlg));
1551 #ifdef HAVE_SONNET
1552     dlg->registerSettingsPage(new SonnetSettingsPage(dlg));
1553 #endif
1554     dlg->registerSettingsPage(new HighlightSettingsPage(dlg));
1555     dlg->registerSettingsPage(new CoreHighlightSettingsPage(dlg));
1556     dlg->registerSettingsPage(new NotificationsSettingsPage(dlg));
1557     dlg->registerSettingsPage(new BacklogSettingsPage(dlg));
1558
1559     //Category: IRC
1560     dlg->registerSettingsPage(new ConnectionSettingsPage(dlg));
1561     dlg->registerSettingsPage(new IdentitiesSettingsPage(dlg));
1562     dlg->registerSettingsPage(new NetworksSettingsPage(dlg));
1563     dlg->registerSettingsPage(new AliasesSettingsPage(dlg));
1564     dlg->registerSettingsPage(new IgnoreListSettingsPage(dlg));
1565     // dlg->registerSettingsPage(new DccSettingsPage(dlg)); not ready yet
1566
1567     // Category: Remote Cores
1568     if (Quassel::runMode() != Quassel::Monolithic) {
1569         dlg->registerSettingsPage(new CoreAccountSettingsPage(dlg));
1570         dlg->registerSettingsPage(new CoreConnectionSettingsPage(dlg));
1571     }
1572
1573     dlg->show();
1574 }
1575
1576
1577 void MainWin::showAboutDlg()
1578 {
1579     AboutDlg{}.exec();
1580 }
1581
1582
1583 void MainWin::showShortcutsDlg()
1584 {
1585 #ifdef HAVE_KDE
1586     KShortcutsDialog dlg(KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed);
1587     foreach(KActionCollection *coll, QtUi::actionCollections())
1588     dlg.addCollection(coll, coll->property("Category").toString());
1589     dlg.configure(true);
1590 #else
1591     SettingsPageDlg{new ShortcutsSettingsPage{QtUi::actionCollections()}}.exec();
1592 #endif
1593 }
1594
1595
1596 void MainWin::showNewTransferDlg(const QUuid &transferId)
1597 {
1598     auto transfer = Client::transferManager()->transfer(transferId);
1599     if (transfer) {
1600         if (transfer->status() == Transfer::Status::New) {
1601             ReceiveFileDlg *dlg = new ReceiveFileDlg(transfer, this);
1602             dlg->show();
1603         }
1604     }
1605     else {
1606         qWarning() << "Unknown transfer ID" << transferId;
1607     }
1608 }
1609
1610
1611 void MainWin::onFullScreenToggled()
1612 {
1613     // Relying on QWidget::isFullScreen is discouraged, see the KToggleFullScreenAction docs
1614     // Also, one should not use showFullScreen() or showNormal(), as those reset all other window flags
1615
1616 #ifdef HAVE_KDE
1617     static_cast<KToggleFullScreenAction*>(_fullScreenAction)->setFullScreen(this, _fullScreenAction->isChecked());
1618 #else
1619     if (_fullScreenAction->isChecked())
1620         setWindowState(windowState() | Qt::WindowFullScreen);
1621     else
1622         setWindowState(windowState() & ~Qt::WindowFullScreen);
1623 #endif
1624 }
1625
1626
1627 /********************************************************************************************************/
1628
1629 bool MainWin::event(QEvent *event)
1630 {
1631     switch(event->type()) {
1632     case QEvent::WindowActivate: {
1633         BufferId bufferId = Client::bufferModel()->currentBuffer();
1634         if (bufferId.isValid())
1635             Client::instance()->markBufferAsRead(bufferId);
1636         break;
1637     }
1638     case QEvent::WindowDeactivate:
1639         if (bufferWidget()->autoMarkerLineOnLostFocus())
1640             bufferWidget()->setMarkerLine();
1641         break;
1642     default:
1643         break;
1644     }
1645     return QMainWindow::event(event);
1646 }
1647
1648
1649 void MainWin::moveEvent(QMoveEvent *event)
1650 {
1651     if (!(windowState() & Qt::WindowMaximized))
1652         _normalPos = event->pos();
1653
1654     QMainWindow::moveEvent(event);
1655 }
1656
1657
1658 void MainWin::resizeEvent(QResizeEvent *event)
1659 {
1660     if (!(windowState() & Qt::WindowMaximized))
1661         _normalSize = event->size();
1662
1663     QMainWindow::resizeEvent(event);
1664 }
1665
1666
1667 void MainWin::closeEvent(QCloseEvent *event)
1668 {
1669     QtUiSettings s;
1670     QtUiApplication *app = qobject_cast<QtUiApplication *> qApp;
1671     Q_ASSERT(app);
1672     // On OSX it can happen that the closeEvent occurs twice. (Especially if packaged with Frameworks)
1673     // This messes up MainWinState/MainWinHidden save/restore.
1674     // It's a bug in Qt: https://bugreports.qt.io/browse/QTBUG-43344
1675     if (!_aboutToQuit && !app->isAboutToQuit() && QtUi::haveSystemTray() && s.value("MinimizeOnClose").toBool()) {
1676         QtUi::hideMainWidget();
1677         event->ignore();
1678     }
1679     else if(!_aboutToQuit) {
1680         _aboutToQuit = true;
1681         event->accept();
1682         Quassel::instance()->quit();
1683     }
1684     else {
1685         event->ignore();
1686     }
1687 }
1688
1689
1690 void MainWin::messagesInserted(const QModelIndex &parent, int start, int end)
1691 {
1692     Q_UNUSED(parent);
1693
1694     bool hasFocus = QApplication::activeWindow() != nullptr;
1695
1696     for (int i = start; i <= end; i++) {
1697         QModelIndex idx = Client::messageModel()->index(i, ChatLineModel::ContentsColumn);
1698         if (!idx.isValid()) {
1699             qDebug() << "MainWin::messagesInserted(): Invalid model index!";
1700             continue;
1701         }
1702         Message::Flags flags = (Message::Flags)idx.data(ChatLineModel::FlagsRole).toInt();
1703         if (flags.testFlag(Message::Backlog) || flags.testFlag(Message::Self))
1704             continue;
1705
1706         BufferId bufId = idx.data(ChatLineModel::BufferIdRole).value<BufferId>();
1707         BufferInfo::Type bufType = Client::networkModel()->bufferType(bufId);
1708
1709         // check if bufferId belongs to the shown chatlists
1710         if (!(Client::bufferViewOverlay()->bufferIds().contains(bufId) ||
1711               Client::bufferViewOverlay()->tempRemovedBufferIds().contains(bufId)))
1712             continue;
1713
1714         // check if it's the buffer currently displayed
1715         if (hasFocus && bufId == Client::bufferModel()->currentBuffer())
1716             continue;
1717
1718         // only show notifications for higlights or queries
1719         if (bufType != BufferInfo::QueryBuffer && !(flags & Message::Highlight))
1720             continue;
1721
1722         // and of course: don't notify for ignored messages
1723         if (Client::ignoreListManager() && Client::ignoreListManager()->match(idx.data(MessageModel::MessageRole).value<Message>(), Client::networkModel()->networkName(bufId)))
1724             continue;
1725
1726         // seems like we have a legit notification candidate!
1727         QModelIndex senderIdx = Client::messageModel()->index(i, ChatLineModel::SenderColumn);
1728         QString sender = senderIdx.data(ChatLineModel::EditRole).toString();
1729         QString contents = idx.data(ChatLineModel::DisplayRole).toString();
1730         AbstractNotificationBackend::NotificationType type;
1731
1732         if (bufType == BufferInfo::QueryBuffer && !hasFocus)
1733             type = AbstractNotificationBackend::PrivMsg;
1734         else if (bufType == BufferInfo::QueryBuffer && hasFocus)
1735             type = AbstractNotificationBackend::PrivMsgFocused;
1736         else if (flags & Message::Highlight && !hasFocus)
1737             type = AbstractNotificationBackend::Highlight;
1738         else
1739             type = AbstractNotificationBackend::HighlightFocused;
1740
1741         QtUi::instance()->invokeNotification(bufId, type, sender, contents);
1742     }
1743 }
1744
1745
1746 void MainWin::currentBufferChanged(BufferId buffer)
1747 {
1748     if (buffer.isValid())
1749         Client::instance()->markBufferAsRead(buffer);
1750 }
1751
1752
1753 void MainWin::clientNetworkCreated(NetworkId id)
1754 {
1755     const Network *net = Client::network(id);
1756     QAction *act = new QAction(net->networkName(), this);
1757     act->setObjectName(QString("NetworkAction-%1").arg(id.toInt()));
1758     act->setData(QVariant::fromValue<NetworkId>(id));
1759     connect(net, SIGNAL(updatedRemotely()), this, SLOT(clientNetworkUpdated()));
1760     connect(act, SIGNAL(triggered()), this, SLOT(connectOrDisconnectFromNet()));
1761
1762     QAction *beforeAction = nullptr;
1763     foreach(QAction *action, _networksMenu->actions()) {
1764         if (!action->data().isValid()) // ignore stock actions
1765             continue;
1766         if (net->networkName().localeAwareCompare(action->text()) < 0) {
1767             beforeAction = action;
1768             break;
1769         }
1770     }
1771     _networksMenu->insertAction(beforeAction, act);
1772 }
1773
1774
1775 void MainWin::clientNetworkUpdated()
1776 {
1777     const Network *net = qobject_cast<const Network *>(sender());
1778     if (!net)
1779         return;
1780
1781     QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(net->networkId().toInt()));
1782     if (!action)
1783         return;
1784
1785     action->setText(net->networkName());
1786
1787     switch (net->connectionState()) {
1788     case Network::Initialized:
1789         action->setIcon(icon::get("network-connect"));
1790         // if we have no currently selected buffer, jump to the first connecting statusbuffer
1791         if (!bufferWidget()->currentBuffer().isValid()) {
1792             QModelIndex idx = Client::networkModel()->networkIndex(net->networkId());
1793             if (idx.isValid()) {
1794                 BufferId statusBufferId = idx.data(NetworkModel::BufferIdRole).value<BufferId>();
1795                 Client::bufferModel()->switchToBuffer(statusBufferId);
1796             }
1797         }
1798         break;
1799     case Network::Disconnected:
1800         action->setIcon(icon::get("network-disconnect"));
1801         break;
1802     default:
1803         action->setIcon(icon::get("network-wired"));
1804     }
1805 }
1806
1807
1808 void MainWin::clientNetworkRemoved(NetworkId id)
1809 {
1810     QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(id.toInt()));
1811     if (!action)
1812         return;
1813
1814     action->deleteLater();
1815 }
1816
1817
1818 void MainWin::connectOrDisconnectFromNet()
1819 {
1820     QAction *act = qobject_cast<QAction *>(sender());
1821     if (!act) return;
1822     const Network *net = Client::network(act->data().value<NetworkId>());
1823     if (!net) return;
1824     if (net->connectionState() == Network::Disconnected) net->requestConnect();
1825     else net->requestDisconnect();
1826 }
1827
1828
1829 void MainWin::on_inputFormatApplyColor_triggered()
1830 {
1831     if (!_inputWidget)
1832         return;
1833
1834     _inputWidget->applyFormatActiveColor();
1835 }
1836
1837
1838 void MainWin::on_inputFormatApplyColorFill_triggered()
1839 {
1840     if (!_inputWidget)
1841         return;
1842
1843     _inputWidget->applyFormatActiveColorFill();
1844 }
1845
1846
1847 void MainWin::on_inputFormatClear_triggered()
1848 {
1849     if (!_inputWidget)
1850         return;
1851
1852     _inputWidget->clearFormat();
1853 }
1854
1855
1856 void MainWin::on_inputFormatBold_triggered()
1857 {
1858     if (!_inputWidget)
1859         return;
1860
1861     _inputWidget->toggleFormatBold();
1862 }
1863
1864
1865 void MainWin::on_inputFormatItalic_triggered()
1866 {
1867     if (!_inputWidget)
1868         return;
1869
1870     _inputWidget->toggleFormatItalic();
1871 }
1872
1873
1874 void MainWin::on_inputFormatUnderline_triggered()
1875 {
1876     if (!_inputWidget)
1877         return;
1878
1879     _inputWidget->toggleFormatUnderline();
1880 }
1881
1882
1883 void MainWin::on_jumpHotBuffer_triggered()
1884 {
1885     if (!_bufferHotList->rowCount())
1886         return;
1887
1888     Client::bufferModel()->switchToBuffer(_bufferHotList->hottestBuffer());
1889 }
1890
1891 void MainWin::on_bufferSearch_triggered()
1892 {
1893     if (_activeBufferViewIndex < 0 || _activeBufferViewIndex >= _bufferViews.count()) {
1894         qWarning() << "Tried to activate filter on invalid bufferview:" << _activeBufferViewIndex;
1895         return;
1896     }
1897
1898     _bufferViews[_activeBufferViewIndex]->activateFilter();
1899 }
1900
1901
1902 void MainWin::onJumpKey()
1903 {
1904     QAction *action = qobject_cast<QAction *>(sender());
1905     if (!action || !Client::bufferModel())
1906         return;
1907     int idx = action->property("Index").toInt();
1908
1909     if (_jumpKeyMap.isEmpty())
1910         _jumpKeyMap = CoreAccountSettings().jumpKeyMap();
1911
1912     if (!_jumpKeyMap.contains(idx))
1913         return;
1914
1915     BufferId buffer = _jumpKeyMap.value(idx);
1916     if (buffer.isValid())
1917         Client::bufferModel()->switchToBuffer(buffer);
1918 }
1919
1920
1921 void MainWin::bindJumpKey()
1922 {
1923     QAction *action = qobject_cast<QAction *>(sender());
1924     if (!action || !Client::bufferModel())
1925         return;
1926     int idx = action->property("Index").toInt();
1927
1928     _jumpKeyMap[idx] = Client::bufferModel()->currentBuffer();
1929     CoreAccountSettings().setJumpKeyMap(_jumpKeyMap);
1930 }
1931
1932
1933 void MainWin::on_actionDebugNetworkModel_triggered()
1934 {
1935     QTreeView *view = new QTreeView;
1936     view->setAttribute(Qt::WA_DeleteOnClose);
1937     view->setWindowTitle("Debug NetworkModel View");
1938     view->setModel(Client::networkModel());
1939     view->setColumnWidth(0, 250);
1940     view->setColumnWidth(1, 250);
1941     view->setColumnWidth(2, 80);
1942     view->resize(610, 300);
1943     view->show();
1944 }
1945
1946
1947 void MainWin::on_actionDebugHotList_triggered()
1948 {
1949     _bufferHotList->invalidate();
1950     _bufferHotList->sort(0, Qt::DescendingOrder);
1951
1952     QTreeView *view = new QTreeView;
1953     view->setAttribute(Qt::WA_DeleteOnClose);
1954     view->setModel(_bufferHotList);
1955     view->show();
1956 }
1957
1958
1959 void MainWin::on_actionDebugBufferViewOverlay_triggered()
1960 {
1961     DebugBufferViewOverlay *overlay = new DebugBufferViewOverlay(nullptr);
1962     overlay->setAttribute(Qt::WA_DeleteOnClose);
1963     overlay->show();
1964 }
1965
1966
1967 void MainWin::on_actionDebugMessageModel_triggered()
1968 {
1969     QTableView *view = new QTableView(nullptr);
1970     DebugMessageModelFilter *filter = new DebugMessageModelFilter(view);
1971     filter->setSourceModel(Client::messageModel());
1972     view->setModel(filter);
1973     view->setAttribute(Qt::WA_DeleteOnClose, true);
1974     view->verticalHeader()->hide();
1975     view->horizontalHeader()->setStretchLastSection(true);
1976     view->show();
1977 }
1978
1979
1980 void MainWin::on_actionDebugLog_triggered()
1981 {
1982     auto dlg = new DebugLogDlg(this);  // will be deleted on close
1983     dlg->show();
1984 }
1985
1986 void MainWin::on_actionShowResourceTree_triggered()
1987 {
1988     auto dlg = new ResourceTreeDlg(this);  // will be deleted on close
1989     dlg->show();
1990 }
1991
1992 void MainWin::showStatusBarMessage(const QString &message)
1993 {
1994     statusBar()->showMessage(message, 10000);
1995 }