giving the chatscene more control over the webpreview thus making it less crashy
[quassel.git] / src / qtui / mainwin.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-08 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  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 #include "mainwin.h"
21
22 #include "aboutdlg.h"
23 #include "action.h"
24 #include "actioncollection.h"
25 #include "bufferview.h"
26 #include "bufferviewconfig.h"
27 #include "bufferviewfilter.h"
28 #include "bufferviewmanager.h"
29 #include "channellistdlg.h"
30 #include "chatlinemodel.h"
31 #include "chatmonitorfilter.h"
32 #include "chatmonitorview.h"
33 #include "chatview.h"
34 #include "chatviewsearchbar.h"
35 #include "client.h"
36 #include "clientbacklogmanager.h"
37 #include "coreinfodlg.h"
38 #include "coreconnectdlg.h"
39 #include "iconloader.h"
40 #include "msgprocessorstatuswidget.h"
41 #include "qtuimessageprocessor.h"
42 #include "qtuiapplication.h"
43 #include "networkmodel.h"
44 #include "buffermodel.h"
45 #include "nicklistwidget.h"
46 #include "settingsdlg.h"
47 #include "settingspagedlg.h"
48 #include "signalproxy.h"
49 #include "topicwidget.h"
50 #include "inputwidget.h"
51 #include "irclistmodel.h"
52 #include "verticaldock.h"
53 #include "uisettings.h"
54 #include "qtuisettings.h"
55 #include "jumpkeyhandler.h"
56 #include "sessionsettings.h"
57
58 #include "selectionmodelsynchronizer.h"
59 #include "mappedselectionmodel.h"
60
61 #include "settingspages/aliasessettingspage.h"
62 #include "settingspages/appearancesettingspage.h"
63 #include "settingspages/bufferviewsettingspage.h"
64 #include "settingspages/colorsettingspage.h"
65 #include "settingspages/fontssettingspage.h"
66 #include "settingspages/generalsettingspage.h"
67 #include "settingspages/highlightsettingspage.h"
68 #include "settingspages/identitiessettingspage.h"
69 #include "settingspages/networkssettingspage.h"
70 #include "settingspages/notificationssettingspage.h"
71
72 #include "qtuistyle.h"
73
74 MainWin::MainWin(QWidget *parent)
75   : QMainWindow(parent),
76     coreLagLabel(new QLabel()),
77     sslLabel(new QLabel()),
78     msgProcessorStatusWidget(new MsgProcessorStatusWidget()),
79
80     _titleSetter(this),
81     systray(new QSystemTrayIcon(this)),
82
83     activeTrayIcon(DesktopIcon("quassel_newmessage", IconLoader::SizeEnormous)),
84     onlineTrayIcon(DesktopIcon("quassel", IconLoader::SizeEnormous)),
85     offlineTrayIcon(DesktopIcon("quassel_disconnected", IconLoader::SizeEnormous)),
86     trayIconActive(false),
87
88     timer(new QTimer(this)),
89     _actionCollection(new ActionCollection(this))
90 {
91   UiSettings uiSettings;
92   QString style = uiSettings.value("Style", QString("")).toString();
93   if(style != "") {
94     QApplication::setStyle(style);
95   }
96   ui.setupUi(this);
97   setWindowTitle("Quassel IRC");
98   setWindowIcon(offlineTrayIcon);
99   qApp->setWindowIcon(offlineTrayIcon);
100   systray->setIcon(offlineTrayIcon);
101   setWindowIconText("Quassel IRC");
102
103   QtUi::actionCollection()->addAssociatedWidget(this);
104
105   statusBar()->showMessage(tr("Waiting for core..."));
106
107   installEventFilter(new JumpKeyHandler(this));
108
109 #ifdef HAVE_DBUS
110   desktopNotifications = new org::freedesktop::Notifications(
111                             "org.freedesktop.Notifications",
112                             "/org/freedesktop/Notifications",
113                             QDBusConnection::sessionBus(), this);
114   notificationId = 0;
115   connect(desktopNotifications, SIGNAL(NotificationClosed(uint, uint)), this, SLOT(desktopNotificationClosed(uint, uint)));
116   connect(desktopNotifications, SIGNAL(ActionInvoked(uint, const QString&)), this, SLOT(desktopNotificationInvoked(uint, const QString&)));
117 #endif
118   QtUiApplication* app = qobject_cast<QtUiApplication*> qApp;
119   connect(app, SIGNAL(saveStateToSession(const QString&)), this, SLOT(saveStateToSession(const QString&)));
120   connect(app, SIGNAL(saveStateToSessionSettings(SessionSettings&)), this, SLOT(saveStateToSessionSettings(SessionSettings&)));
121 }
122
123 void MainWin::init() {
124   QtUiSettings s;
125   if(s.value("MainWinSize").isValid())
126     resize(s.value("MainWinSize").toSize());
127   else
128     resize(QSize(800, 500));
129
130   connect(QApplication::instance(), SIGNAL(aboutToQuit()), this, SLOT(saveLayout()));
131
132   connect(Client::instance(), SIGNAL(networkCreated(NetworkId)), this, SLOT(clientNetworkCreated(NetworkId)));
133   connect(Client::instance(), SIGNAL(networkRemoved(NetworkId)), this, SLOT(clientNetworkRemoved(NetworkId)));
134
135   show();
136
137   statusBar()->showMessage(tr("Not connected to core."));
138
139   // DOCK OPTIONS
140   setDockNestingEnabled(true);
141
142   setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
143   setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
144   setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
145   setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
146
147   // setup stuff...
148   setupActions();
149   setupMenus();
150   setupViews();
151   setupNickWidget();
152   setupTopicWidget();
153   setupChatMonitor();
154   setupInputWidget();
155   setupStatusBar();
156   setupSystray();
157
158   // restore mainwin state
159   restoreState(s.value("MainWinState").toByteArray());
160
161   // restore locked state of docks
162   ui.actionLockDockPositions->setChecked(s.value("LockDocks", false).toBool());
163
164   setDisconnectedState();  // Disable menus and stuff
165   showCoreConnectionDlg(true); // autoconnect if appropriate
166
167   // attach the BufferWidget to the BufferModel and the default selection
168   ui.bufferWidget->setModel(Client::bufferModel());
169   ui.bufferWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
170   ui.menuViews->addAction(QtUi::actionCollection()->action("toggleSearchBar"));
171
172   _titleSetter.setModel(Client::bufferModel());
173   _titleSetter.setSelectionModel(Client::bufferModel()->standardSelectionModel());
174 }
175
176 MainWin::~MainWin() {
177   QtUiSettings s;
178   s.setValue("MainWinSize", size());
179   s.setValue("MainWinPos", pos());
180   s.setValue("MainWinState", saveState());
181 }
182
183 void MainWin::setupActions() {
184   // TODO don't get these from *.ui anymore... we shouldn't need one
185   ui.actionQuit->setIcon(SmallIcon("application-exit"));
186   ui.actionSettingsDlg->setIcon(SmallIcon("configure"));
187   ui.actionManageViews->setIcon(SmallIcon("view-tree"));
188   ui.actionManageViews2->setIcon(SmallIcon("view-tree"));
189   ui.actionAboutQt->setIcon(SmallIcon("qt"));
190   ui.actionAboutQuassel->setIcon(SmallIcon("quassel"));
191   ui.actionConnectCore->setIcon(SmallIcon("network-connect"));
192   ui.actionDisconnectCore->setIcon(SmallIcon("network-disconnect"));
193   ui.actionCoreInfo->setIcon(SmallIcon("help-about"));
194 }
195
196 void MainWin::setupMenus() {
197   connect(ui.actionConnectCore, SIGNAL(triggered()), this, SLOT(showCoreConnectionDlg()));
198   connect(ui.actionDisconnectCore, SIGNAL(triggered()), Client::instance(), SLOT(disconnectFromCore()));
199   connect(ui.actionCoreInfo, SIGNAL(triggered()), this, SLOT(showCoreInfoDlg()));
200   connect(ui.actionQuit, SIGNAL(triggered()), QCoreApplication::instance(), SLOT(quit()));
201   connect(ui.actionSettingsDlg, SIGNAL(triggered()), this, SLOT(showSettingsDlg()));
202   connect(ui.actionAboutQuassel, SIGNAL(triggered()), this, SLOT(showAboutDlg()));
203   connect(ui.actionAboutQt, SIGNAL(triggered()), QApplication::instance(), SLOT(aboutQt()));
204 }
205
206 void MainWin::setupViews() {
207   addBufferView();
208 }
209
210 void MainWin::addBufferView(int bufferViewConfigId) {
211   addBufferView(Client::bufferViewManager()->bufferViewConfig(bufferViewConfigId));
212 }
213
214 void MainWin::addBufferView(BufferViewConfig *config) {
215   BufferViewDock *dock;
216   if(config)
217     dock = new BufferViewDock(config, this);
218   else
219     dock = new BufferViewDock(this);
220
221   //create the view and initialize it's filter
222   BufferView *view = new BufferView(dock);
223   view->setFilteredModel(Client::bufferModel(), config);
224   view->show();
225
226   connect(&view->showChannelList, SIGNAL(triggered()), this, SLOT(showChannelList()));
227
228   Client::bufferModel()->synchronizeView(view);
229
230   dock->setWidget(view);
231   dock->show();
232
233   addDockWidget(Qt::LeftDockWidgetArea, dock);
234   ui.menuBufferViews->addAction(dock->toggleViewAction());
235
236   _netViews.append(dock);
237 }
238
239 void MainWin::removeBufferView(int bufferViewConfigId) {
240   QVariant actionData;
241   BufferViewDock *dock;
242   foreach(QAction *action, ui.menuBufferViews->actions()) {
243     actionData = action->data();
244     if(!actionData.isValid())
245       continue;
246
247     dock = qobject_cast<BufferViewDock *>(action->parent());
248     if(dock && actionData.toInt() == bufferViewConfigId) {
249       removeAction(action);
250       dock->deleteLater();
251     }
252   }
253 }
254
255 void MainWin::on_actionEditNetworks_triggered() {
256   SettingsPageDlg dlg(new NetworksSettingsPage(this), this);
257   dlg.exec();
258 }
259
260 void MainWin::on_actionManageViews_triggered() {
261   SettingsPageDlg dlg(new BufferViewSettingsPage(this), this);
262   dlg.exec();
263 }
264
265 void MainWin::on_actionLockDockPositions_toggled(bool lock) {
266   QList<VerticalDock *> docks = findChildren<VerticalDock *>();
267   foreach(VerticalDock *dock, docks) {
268     dock->showTitle(!lock);
269   }
270   QtUiSettings().setValue("LockDocks", lock);
271 }
272
273 void MainWin::setupNickWidget() {
274   // create nick dock
275   NickListDock *nickDock = new NickListDock(tr("Nicks"), this);
276   nickDock->setObjectName("NickDock");
277   nickDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
278
279   nickListWidget = new NickListWidget(nickDock);
280   nickDock->setWidget(nickListWidget);
281
282   addDockWidget(Qt::RightDockWidgetArea, nickDock);
283   ui.menuViews->addAction(nickDock->toggleViewAction());
284   // See NickListDock::NickListDock();
285   // connect(nickDock->toggleViewAction(), SIGNAL(triggered(bool)), nickListWidget, SLOT(showWidget(bool)));
286
287   // attach the NickListWidget to the BufferModel and the default selection
288   nickListWidget->setModel(Client::bufferModel());
289   nickListWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
290 }
291
292 void MainWin::setupChatMonitor() {
293   VerticalDock *dock = new VerticalDock(tr("Chat Monitor"), this);
294   dock->setObjectName("ChatMonitorDock");
295
296   ChatMonitorFilter *filter = new ChatMonitorFilter(Client::messageModel(), this);
297   ChatMonitorView *chatView = new ChatMonitorView(filter, this);
298   chatView->show();
299   dock->setWidget(chatView);
300   dock->show();
301
302   addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
303   ui.menuViews->addAction(dock->toggleViewAction());
304 }
305
306 void MainWin::setupInputWidget() {
307   VerticalDock *dock = new VerticalDock(tr("Inputline"), this);
308   dock->setObjectName("InputDock");
309
310   InputWidget *inputWidget = new InputWidget(dock);
311   dock->setWidget(inputWidget);
312
313   addDockWidget(Qt::BottomDockWidgetArea, dock);
314
315   ui.menuViews->addAction(dock->toggleViewAction());
316
317   inputWidget->setModel(Client::bufferModel());
318   inputWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
319
320   ui.bufferWidget->setFocusProxy(inputWidget);
321 }
322
323 void MainWin::setupTopicWidget() {
324   VerticalDock *dock = new VerticalDock(tr("Topic"), this);
325   dock->setObjectName("TopicDock");
326   TopicWidget *topicwidget = new TopicWidget(dock);
327
328   dock->setWidget(topicwidget);
329
330   topicwidget->setModel(Client::bufferModel());
331   topicwidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
332
333   addDockWidget(Qt::TopDockWidgetArea, dock);
334
335   ui.menuViews->addAction(dock->toggleViewAction());
336 }
337
338 void MainWin::setupStatusBar() {
339   // MessageProcessor progress
340   statusBar()->addPermanentWidget(msgProcessorStatusWidget);
341   connect(Client::messageProcessor(), SIGNAL(progressUpdated(int, int)), msgProcessorStatusWidget, SLOT(setProgress(int, int)));
342
343   // Core Lag:
344   updateLagIndicator(0);
345   statusBar()->addPermanentWidget(coreLagLabel);
346   connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), this, SLOT(updateLagIndicator(int)));
347
348   // SSL indicator
349   connect(Client::instance(), SIGNAL(securedConnection()), this, SLOT(securedConnection()));
350   sslLabel->setPixmap(QPixmap());
351   statusBar()->addPermanentWidget(sslLabel);
352
353   ui.menuViews->addSeparator();
354   QAction *showStatusbar = ui.menuViews->addAction(tr("Statusbar"));
355   showStatusbar->setCheckable(true);
356
357   UiSettings uiSettings;
358
359   bool enabled = uiSettings.value("ShowStatusBar", QVariant(true)).toBool();
360   showStatusbar->setChecked(enabled);
361   enabled ? statusBar()->show() : statusBar()->hide();
362
363   connect(showStatusbar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool)));
364   connect(showStatusbar, SIGNAL(toggled(bool)), this, SLOT(saveStatusBarStatus(bool)));
365 }
366
367 void MainWin::saveStatusBarStatus(bool enabled) {
368   UiSettings uiSettings;
369   uiSettings.setValue("ShowStatusBar", enabled);
370 }
371
372 void MainWin::setupSystray() {
373   connect(timer, SIGNAL(timeout()), this, SLOT(makeTrayIconBlink()));
374   connect(Client::messageModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
375                             this, SLOT(messagesInserted(const QModelIndex &, int, int)));
376
377   systrayMenu = new QMenu(this);
378   systrayMenu->addAction(ui.actionAboutQuassel);
379   systrayMenu->addSeparator();
380   systrayMenu->addAction(ui.actionConnectCore);
381   systrayMenu->addAction(ui.actionDisconnectCore);
382   systrayMenu->addSeparator();
383   systrayMenu->addAction(ui.actionQuit);
384
385   systray->setContextMenu(systrayMenu);
386
387   UiSettings s;
388   if(s.value("UseSystemTrayIcon", QVariant(true)).toBool()) {
389     systray->show();
390   }
391
392 #ifndef Q_WS_MAC
393   connect(systray, SIGNAL(activated( QSystemTrayIcon::ActivationReason )),
394           this, SLOT(systrayActivated( QSystemTrayIcon::ActivationReason )));
395 #endif
396
397 }
398
399 void MainWin::changeEvent(QEvent *event) {
400   if(event->type() == QEvent::WindowStateChange) {
401     if(windowState() & Qt::WindowMinimized) {
402       UiSettings s;
403       if(s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnMinimize").toBool()) {
404         toggleVisibility();
405         event->ignore();
406       }
407     }
408   }
409 }
410
411 void MainWin::connectedToCore() {
412   Q_CHECK_PTR(Client::bufferViewManager());
413   connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigAdded(int)), this, SLOT(addBufferView(int)));
414   connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigDeleted(int)), this, SLOT(removeBufferView(int)));
415   connect(Client::bufferViewManager(), SIGNAL(initDone()), this, SLOT(loadLayout()));
416
417   Client::backlogManager()->requestInitialBacklog();
418   setConnectedState();
419 }
420
421 void MainWin::setConnectedState() {
422   //ui.menuCore->setEnabled(true);
423   ui.actionConnectCore->setEnabled(false);
424   ui.actionDisconnectCore->setEnabled(true);
425   ui.actionCoreInfo->setEnabled(true);
426   ui.menuViews->setEnabled(true);
427   ui.bufferWidget->show();
428   statusBar()->showMessage(tr("Connected to core."));
429   setWindowIcon(onlineTrayIcon);
430   qApp->setWindowIcon(onlineTrayIcon);
431   systray->setIcon(onlineTrayIcon);
432   if(sslLabel->width() == 0)
433     sslLabel->setPixmap(SmallIcon("security-low"));
434 }
435
436 void MainWin::loadLayout() {
437   QtUiSettings s;
438   int accountId = Client::currentCoreAccount().toInt();
439   restoreState(s.value(QString("MainWinState-%1").arg(accountId)).toByteArray(), accountId);
440 }
441
442 void MainWin::saveLayout() {
443   QtUiSettings s;
444   int accountId = Client::currentCoreAccount().toInt();
445   if(accountId > 0) s.setValue(QString("MainWinState-%1").arg(accountId) , saveState(accountId));
446 }
447
448 void MainWin::updateLagIndicator(int lag) {
449   coreLagLabel->setText(QString(tr("Core Lag: %1 msec")).arg(lag));
450 }
451
452
453 void MainWin::securedConnection() {
454   // todo: make status bar entry
455   sslLabel->setPixmap(SmallIcon("security-high"));
456 }
457
458 void MainWin::disconnectedFromCore() {
459   // save core specific layout and remove bufferviews;
460   saveLayout();
461   QVariant actionData;
462   BufferViewDock *dock;
463   foreach(QAction *action, ui.menuBufferViews->actions()) {
464     actionData = action->data();
465     if(!actionData.isValid())
466       continue;
467
468     dock = qobject_cast<BufferViewDock *>(action->parent());
469     if(dock && actionData.toInt() != -1) {
470       removeAction(action);
471       dock->deleteLater();
472     }
473   }
474   QtUiSettings s;
475   restoreState(s.value("MainWinState").toByteArray());
476   setDisconnectedState();
477 }
478
479 void MainWin::setDisconnectedState() {
480   //ui.menuCore->setEnabled(false);
481   ui.actionConnectCore->setEnabled(true);
482   ui.actionDisconnectCore->setEnabled(false);
483   ui.actionCoreInfo->setEnabled(false);
484   ui.menuViews->setEnabled(false);
485   ui.bufferWidget->hide();
486   statusBar()->showMessage(tr("Not connected to core."));
487   setWindowIcon(offlineTrayIcon);
488   qApp->setWindowIcon(offlineTrayIcon);
489   systray->setIcon(offlineTrayIcon);
490   sslLabel->setPixmap(QPixmap());
491 }
492
493 void MainWin::showCoreConnectionDlg(bool autoConnect) {
494   CoreConnectDlg(autoConnect, this).exec();
495 }
496
497 void MainWin::showChannelList(NetworkId netId) {
498   ChannelListDlg *channelListDlg = new ChannelListDlg();
499
500   if(!netId.isValid()) {
501     QAction *action = qobject_cast<QAction *>(sender());
502     if(action)
503       netId = action->data().value<NetworkId>();
504   }
505
506   channelListDlg->setAttribute(Qt::WA_DeleteOnClose);
507   channelListDlg->setNetwork(netId);
508   channelListDlg->show();
509 }
510
511 void MainWin::showCoreInfoDlg() {
512   CoreInfoDlg(this).exec();
513 }
514
515 void MainWin::showSettingsDlg() {
516   SettingsDlg *dlg = new SettingsDlg();
517
518   //Category: Appearance
519   dlg->registerSettingsPage(new ColorSettingsPage(dlg));
520   dlg->registerSettingsPage(new FontsSettingsPage(dlg));
521   dlg->registerSettingsPage(new AppearanceSettingsPage(dlg)); //General
522   //Category: Behaviour
523   dlg->registerSettingsPage(new GeneralSettingsPage(dlg));
524   dlg->registerSettingsPage(new HighlightSettingsPage(dlg));
525   dlg->registerSettingsPage(new AliasesSettingsPage(dlg));
526   dlg->registerSettingsPage(new NotificationsSettingsPage(dlg));
527   //Category: General
528   dlg->registerSettingsPage(new IdentitiesSettingsPage(dlg));
529   dlg->registerSettingsPage(new NetworksSettingsPage(dlg));
530   dlg->registerSettingsPage(new BufferViewSettingsPage(dlg));
531
532   dlg->show();
533 }
534
535 void MainWin::showAboutDlg() {
536   AboutDlg(this).exec();
537 }
538
539 void MainWin::closeEvent(QCloseEvent *event) {
540   UiSettings s;
541   if(s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnClose").toBool()) {
542     toggleVisibility();
543     event->ignore();
544   } else {
545     event->accept();
546   }
547 }
548
549 void MainWin::systrayActivated( QSystemTrayIcon::ActivationReason activationReason) {
550   if(activationReason == QSystemTrayIcon::Trigger) {
551     toggleVisibility();
552   }
553 }
554
555 void MainWin::toggleVisibility() {
556   if(isHidden() /*|| !isActiveWindow()*/) {
557     show();
558     if(isMinimized()) {
559       if(isMaximized())
560         showMaximized();
561       else
562         showNormal();
563     }
564
565     raise();
566     activateWindow();
567     // setFocus(); //Qt::ActiveWindowFocusReason
568
569   } else {
570     if(systray->isSystemTrayAvailable ()) {
571       clearFocus();
572       hide();
573       if(!systray->isVisible()) {
574         systray->show();
575       }
576     } else {
577       lower();
578     }
579   }
580 }
581
582 void MainWin::messagesInserted(const QModelIndex &parent, int start, int end) {
583   Q_UNUSED(parent);
584
585   if(QApplication::activeWindow() != 0)
586     return;
587
588   for(int i = start; i <= end; i++) {
589     QModelIndex idx = Client::messageModel()->index(i, ChatLineModel::ContentsColumn);
590     if(!idx.isValid()) {
591       qDebug() << "MainWin::messagesInserted(): Invalid model index!";
592       continue;
593     }
594     Message::Flags flags = (Message::Flags)idx.data(ChatLineModel::FlagsRole).toInt();
595     if(flags.testFlag(Message::Backlog)) continue;
596     flags |= Message::Backlog;  // we only want to trigger a highlight once!
597     Client::messageModel()->setData(idx, (int)flags, ChatLineModel::FlagsRole);
598
599     BufferId bufId = idx.data(ChatLineModel::BufferIdRole).value<BufferId>();
600     BufferInfo::Type bufType = Client::networkModel()->bufferType(bufId);
601
602     if(flags & Message::Highlight || bufType == BufferInfo::QueryBuffer) {
603       QString title = Client::networkModel()->networkName(bufId) + " - " + Client::networkModel()->bufferName(bufId);
604
605       // FIXME Don't instantiate this for every highlight...
606       UiSettings uiSettings;
607
608       bool displayBubble = uiSettings.value("NotificationBubble", QVariant(true)).toBool();
609       bool displayDesktop = uiSettings.value("NotificationDesktop", QVariant(true)).toBool();
610       if(displayBubble || displayDesktop) {
611         if(uiSettings.value("DisplayPopupMessages", QVariant(true)).toBool()) {
612           QString text = idx.data(ChatLineModel::DisplayRole).toString();
613           if(displayBubble) displayTrayIconMessage(title, text);
614 #   ifdef HAVE_DBUS
615           if(displayDesktop) sendDesktopNotification(title, text);
616 #   endif
617         }
618         if(uiSettings.value("AnimateTrayIcon", QVariant(true)).toBool()) {
619           QApplication::alert(this);
620           setTrayIconActivity(true);
621         }
622       }
623     }
624   }
625 }
626
627 bool MainWin::event(QEvent *event) {
628   if(event->type() == QEvent::WindowActivate)
629     setTrayIconActivity(false);
630   return QMainWindow::event(event);
631 }
632
633 #ifdef HAVE_DBUS
634
635 /*
636 Using the notification-daemon from Freedesktop's Galago project
637 http://www.galago-project.org/specs/notification/0.9/x408.html#command-notify
638 */
639 void MainWin::sendDesktopNotification(const QString &title, const QString &message) {
640   QStringList actions;
641   QMap<QString, QVariant> hints;
642   UiSettings uiSettings;
643
644   hints["x"] = uiSettings.value("NotificationDesktopHintX", QVariant(0)).toInt(); // Standard hint: x location for the popup to show up
645   hints["y"] = uiSettings.value("NotificationDesktopHintY", QVariant(0)).toInt(); // Standard hint: y location for the popup to show up
646
647   actions << "click" << "Click Me!";
648
649   QDBusReply<uint> reply = desktopNotifications->Notify(
650                 "Quassel", // Application name
651                 notificationId, // ID of previous notification to replace
652                 "", // Icon to display
653                 title, // Summary / Header of the message to display
654                 QString("%1: %2:\n%3").arg(QTime::currentTime().toString()).arg(title).arg(message), // Body of the message to display
655                 actions, // Actions from which the user may choose
656                 hints, // Hints to the server displaying the message
657                 uiSettings.value("NotificationDesktopTimeout", QVariant(5000)).toInt() // Timeout in milliseconds
658         );
659
660   if(!reply.isValid()) {
661     /* ERROR */
662     // could also happen if no notification service runs, so... whatever :)
663     //qDebug() << "Error on sending notification..." << reply.error();
664     return;
665   }
666
667   notificationId = reply.value();
668
669   // qDebug() << "ID: " << notificationId << " Time: " << QTime::currentTime().toString();
670 }
671
672
673 void MainWin::desktopNotificationClosed(uint id, uint reason) {
674   Q_UNUSED(id); Q_UNUSED(reason);
675   // qDebug() << "OID: " << notificationId << " ID: " << id << " Reason: " << reason << " Time: " << QTime::currentTime().toString();
676   notificationId = 0;
677 }
678
679
680 void MainWin::desktopNotificationInvoked(uint id, const QString & action) {
681   Q_UNUSED(id); Q_UNUSED(action);
682   // qDebug() << "OID: " << notificationId << " ID: " << id << " Action: " << action << " Time: " << QTime::currentTime().toString();
683 }
684
685 #endif /* HAVE_DBUS */
686
687 void MainWin::displayTrayIconMessage(const QString &title, const QString &message) {
688   systray->showMessage(title, message);
689 }
690
691 void MainWin::setTrayIconActivity(bool active) {
692   if(active) {
693     if(!timer->isActive())
694       timer->start(500);
695   } else {
696     timer->stop();
697     systray->setIcon(onlineTrayIcon);
698   }
699 }
700
701 void MainWin::makeTrayIconBlink() {
702   if(trayIconActive) {
703     systray->setIcon(onlineTrayIcon);
704     trayIconActive = false;
705   } else {
706     systray->setIcon(activeTrayIcon);
707     trayIconActive = true;
708   }
709 }
710
711 void MainWin::clientNetworkCreated(NetworkId id) {
712   const Network *net = Client::network(id);
713   QAction *act = new QAction(net->networkName(), this);
714   act->setObjectName(QString("NetworkAction-%1").arg(id.toInt()));
715   act->setData(QVariant::fromValue<NetworkId>(id));
716   connect(net, SIGNAL(updatedRemotely()), this, SLOT(clientNetworkUpdated()));
717   connect(act, SIGNAL(triggered()), this, SLOT(connectOrDisconnectFromNet()));
718
719   QAction *beforeAction = 0;
720   foreach(QAction *action, ui.menuNetworks->actions()) {
721     if(action->isSeparator()) {
722       beforeAction = action;
723       break;
724     }
725     if(net->networkName().localeAwareCompare(action->text()) < 0) {
726       beforeAction = action;
727       break;
728     }
729   }
730   Q_CHECK_PTR(beforeAction);
731   ui.menuNetworks->insertAction(beforeAction, act);
732 }
733
734 void MainWin::clientNetworkUpdated() {
735   const Network *net = qobject_cast<const Network *>(sender());
736   if(!net)
737     return;
738
739   QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(net->networkId().toInt()));
740   if(!action)
741     return;
742
743   action->setText(net->networkName());
744
745   switch(net->connectionState()) {
746   case Network::Initialized:
747     action->setIcon(SmallIcon("network-connect"));
748     break;
749   case Network::Disconnected:
750     action->setIcon(SmallIcon("network-disconnect"));
751     break;
752   default:
753     action->setIcon(SmallIcon("network-wired"));
754   }
755 }
756
757 void MainWin::clientNetworkRemoved(NetworkId id) {
758   QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(id.toInt()));
759   if(!action)
760     return;
761
762   action->deleteLater();
763 }
764
765 void MainWin::connectOrDisconnectFromNet() {
766   QAction *act = qobject_cast<QAction *>(sender());
767   if(!act) return;
768   const Network *net = Client::network(act->data().value<NetworkId>());
769   if(!net) return;
770   if(net->connectionState() == Network::Disconnected) net->requestConnect();
771   else net->requestDisconnect();
772 }
773
774
775
776 void MainWin::on_actionDebugNetworkModel_triggered(bool) {
777   QTreeView *view = new QTreeView;
778   view->setAttribute(Qt::WA_DeleteOnClose);
779   view->setWindowTitle("Debug NetworkModel View");
780   view->setModel(Client::networkModel());
781   view->setColumnWidth(0, 250);
782   view->setColumnWidth(1, 250);
783   view->setColumnWidth(2, 80);
784   view->resize(610, 300);
785   view->show();
786 }
787
788 void MainWin::saveStateToSession(const QString &sessionId) {
789   return;
790   SessionSettings s(sessionId);
791
792   s.setValue("MainWinSize", size());
793   s.setValue("MainWinPos", pos());
794   s.setValue("MainWinState", saveState());
795 }
796
797 void MainWin::saveStateToSessionSettings(SessionSettings & s)
798 {
799   s.setValue("MainWinSize", size());
800   s.setValue("MainWinPos", pos());
801   s.setValue("MainWinState", saveState());
802 }