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