core defaults to safer umask
[quassel.git] / src / qtui / mainwin.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-09 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 #ifdef HAVE_KDE
23 #  include <KAction>
24 #  include <KActionCollection>
25 #  include <KHelpMenu>
26 #  include <KMenuBar>
27 #  include <KShortcutsDialog>
28 #  include <KStatusBar>
29 #  include <KToolBar>
30 #endif
31
32 #ifdef Q_WS_X11
33 #  include <QX11Info>
34 #endif
35
36 #include "aboutdlg.h"
37 #include "awaylogfilter.h"
38 #include "awaylogview.h"
39 #include "action.h"
40 #include "actioncollection.h"
41 #include "bufferhotlistfilter.h"
42 #include "buffermodel.h"
43 #include "bufferview.h"
44 #include "bufferviewoverlay.h"
45 #include "bufferviewoverlayfilter.h"
46 #include "bufferwidget.h"
47 #include "channellistdlg.h"
48 #include "chatlinemodel.h"
49 #include "chatmonitorfilter.h"
50 #include "chatmonitorview.h"
51 #include "chatview.h"
52 #include "client.h"
53 #include "clientsyncer.h"
54 #include "clientbacklogmanager.h"
55 #include "clientbufferviewconfig.h"
56 #include "clientbufferviewmanager.h"
57 #include "clientignorelistmanager.h"
58 #include "coreinfodlg.h"
59 #include "coreconnectdlg.h"
60 #include "contextmenuactionprovider.h"
61 #include "debugbufferviewoverlay.h"
62 #include "debuglogwidget.h"
63 #include "debugmessagemodelfilter.h"
64 #include "flatproxymodel.h"
65 #include "iconloader.h"
66 #include "inputwidget.h"
67 #include "irclistmodel.h"
68 #include "ircconnectionwizard.h"
69 #include "jumpkeyhandler.h"
70 #include "msgprocessorstatuswidget.h"
71 #include "nicklistwidget.h"
72 #include "qtuiapplication.h"
73 #include "qtuimessageprocessor.h"
74 #include "qtuisettings.h"
75 #include "qtuistyle.h"
76 #include "settingsdlg.h"
77 #include "settingspagedlg.h"
78 #include "systemtray.h"
79 #include "toolbaractionprovider.h"
80 #include "topicwidget.h"
81 #include "verticaldock.h"
82
83 #ifndef HAVE_KDE
84 #  ifdef HAVE_DBUS
85 #    include "desktopnotificationbackend.h"
86 #  endif
87 #  ifdef HAVE_PHONON
88 #    include "phononnotificationbackend.h"
89 #  endif
90 #  include "systraynotificationbackend.h"
91 #  include "taskbarnotificationbackend.h"
92 #else /* HAVE_KDE */
93 #  include "knotificationbackend.h"
94 #endif /* HAVE_KDE */
95
96 #ifdef HAVE_INDICATEQT
97   #include "indicatornotificationbackend.h"
98 #endif
99
100 #include "settingspages/aliasessettingspage.h"
101 #include "settingspages/appearancesettingspage.h"
102 #include "settingspages/backlogsettingspage.h"
103 #include "settingspages/bufferviewsettingspage.h"
104 #include "settingspages/chatmonitorsettingspage.h"
105 #include "settingspages/chatviewsettingspage.h"
106 #include "settingspages/connectionsettingspage.h"
107 #include "settingspages/generalsettingspage.h"
108 #include "settingspages/highlightsettingspage.h"
109 #include "settingspages/identitiessettingspage.h"
110 #include "settingspages/ignorelistsettingspage.h"
111 #include "settingspages/inputwidgetsettingspage.h"
112 #include "settingspages/itemviewsettingspage.h"
113 #include "settingspages/networkssettingspage.h"
114 #include "settingspages/notificationssettingspage.h"
115 #include "settingspages/topicwidgetsettingspage.h"
116
117 MainWin::MainWin(QWidget *parent)
118 #ifdef HAVE_KDE
119   : KMainWindow(parent),
120   _kHelpMenu(new KHelpMenu(this, KGlobal::mainComponent().aboutData())),
121 #else
122   : QMainWindow(parent),
123 #endif
124     coreLagLabel(new QLabel()),
125     sslLabel(new QLabel()),
126     msgProcessorStatusWidget(new MsgProcessorStatusWidget()),
127     _titleSetter(this),
128     _awayLog(0)
129 {
130 #ifdef Q_WS_WIN
131   dwTickCount = 0;
132 #endif
133
134   QtUiSettings uiSettings;
135   QString style = uiSettings.value("Style", QString()).toString();
136   if(!style.isEmpty()) {
137     QApplication::setStyle(style);
138   }
139
140   QApplication::setQuitOnLastWindowClosed(false);
141
142   setWindowTitle("Quassel IRC");
143   setWindowIconText("Quassel IRC");
144   updateIcon();
145
146   installEventFilter(new JumpKeyHandler(this));
147 }
148
149 void MainWin::init() {
150   connect(Client::instance(), SIGNAL(networkCreated(NetworkId)), SLOT(clientNetworkCreated(NetworkId)));
151   connect(Client::instance(), SIGNAL(networkRemoved(NetworkId)), SLOT(clientNetworkRemoved(NetworkId)));
152   connect(Client::messageModel(), SIGNAL(rowsInserted(const QModelIndex &, int, int)),
153            SLOT(messagesInserted(const QModelIndex &, int, int)));
154   connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showChannelList(NetworkId)), SLOT(showChannelList(NetworkId)));
155   connect(GraphicalUi::contextMenuActionProvider(), SIGNAL(showIgnoreList(QString)), SLOT(showIgnoreList(QString)));
156
157   // Setup Dock Areas
158   setDockNestingEnabled(true);
159   setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
160   setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
161   setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
162   setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
163
164   // Order is sometimes important
165   setupActions();
166   setupBufferWidget();
167   setupMenus();
168   setupTopicWidget();
169   setupChatMonitor();
170   setupNickWidget();
171   setupInputWidget();
172   setupStatusBar();
173   setupToolBars();
174   setupSystray();
175   setupTitleSetter();
176   setupHotList();
177
178 #ifndef HAVE_KDE
179   QtUi::registerNotificationBackend(new TaskbarNotificationBackend(this));
180   QtUi::registerNotificationBackend(new SystrayNotificationBackend(this));
181 #  ifdef HAVE_PHONON
182   QtUi::registerNotificationBackend(new PhononNotificationBackend(this));
183 #  endif
184 #  ifdef HAVE_DBUS
185   QtUi::registerNotificationBackend(new DesktopNotificationBackend(this));
186 #  endif
187
188 #else /* HAVE_KDE */
189   QtUi::registerNotificationBackend(new KNotificationBackend(this));
190 #endif /* HAVE_KDE */
191
192 #ifdef HAVE_INDICATEQT
193   QtUi::registerNotificationBackend(new IndicatorNotificationBackend(this));
194 #endif
195
196   connect(bufferWidget(), SIGNAL(currentChanged(BufferId)), SLOT(currentBufferChanged(BufferId)));
197
198   setDisconnectedState();  // Disable menus and stuff
199
200 #ifdef HAVE_KDE
201   setAutoSaveSettings();
202 #endif
203
204   // restore mainwin state
205   QtUiSettings s;
206   restoreStateFromSettings(s);
207
208   // restore locked state of docks
209   QtUi::actionCollection("General")->action("LockLayout")->setChecked(s.value("LockLayout", false).toBool());
210
211   if(Quassel::runMode() != Quassel::Monolithic) {
212     showCoreConnectionDlg(true); // autoconnect if appropriate
213   } else {
214     startInternalCore();
215   }
216 }
217
218 MainWin::~MainWin() {
219
220 }
221
222 void MainWin::quit() {
223   QtUiSettings s;
224   saveStateToSettings(s);
225   saveLayout();
226   QApplication::quit();
227 }
228
229 void MainWin::saveStateToSettings(UiSettings &s) {
230   s.setValue("MainWinSize", _normalSize);
231   s.setValue("MainWinPos", _normalPos);
232   s.setValue("MainWinState", saveState());
233   s.setValue("MainWinGeometry", saveGeometry());
234   s.setValue("MainWinMinimized", isMinimized());
235   s.setValue("MainWinMaximized", isMaximized());
236   s.setValue("MainWinHidden", !isVisible());
237
238 #ifdef HAVE_KDE
239   saveAutoSaveSettings();
240 #endif
241 }
242
243 void MainWin::restoreStateFromSettings(UiSettings &s) {
244   _normalSize = s.value("MainWinSize", size()).toSize();
245   _normalPos = s.value("MainWinPos", pos()).toPoint();
246   bool maximized = s.value("MainWinMaximized", false).toBool();
247
248 #ifndef HAVE_KDE
249   restoreGeometry(s.value("MainWinGeometry").toByteArray());
250
251   if(maximized) {
252     // restoreGeometry() fails if the windows was maximized, so we resize and position explicitly
253     resize(_normalSize);
254     move(_normalPos);
255   }
256
257   restoreState(s.value("MainWinState").toByteArray());
258
259 #else
260   move(_normalPos);
261 #endif
262
263   if(s.value("MainWinHidden").toBool())
264     hideToTray();
265   else if(s.value("MainWinMinimized").toBool())
266     showMinimized();
267   else if(maximized)
268     showMaximized();
269   else
270     show();
271 }
272
273 void MainWin::updateIcon() {
274 #ifdef Q_WS_MAC
275   const int size = 128;
276 #else
277   const int size = 48;
278 #endif
279
280   QPixmap icon;
281   if(Client::isConnected())
282     icon = DesktopIcon("quassel", size);
283   else
284     icon = DesktopIcon("quassel_inactive", size);
285   setWindowIcon(icon);
286   qApp->setWindowIcon(icon);
287 }
288
289 void MainWin::setupActions() {
290   ActionCollection *coll = QtUi::actionCollection("General");
291   // File
292   coll->addAction("ConnectCore", new Action(SmallIcon("network-connect"), tr("&Connect to Core..."), coll,
293                                              this, SLOT(showCoreConnectionDlg())));
294   coll->addAction("DisconnectCore", new Action(SmallIcon("network-disconnect"), tr("&Disconnect from Core"), coll,
295                                                 Client::instance(), SLOT(disconnectFromCore())));
296   coll->addAction("CoreInfo", new Action(SmallIcon("help-about"), tr("Core &Info..."), coll,
297                                           this, SLOT(showCoreInfoDlg())));
298   coll->addAction("ConfigureNetworks", new Action(SmallIcon("configure"), tr("Configure &Networks..."), coll,
299                                               this, SLOT(on_actionConfigureNetworks_triggered())));
300   coll->addAction("Quit", new Action(SmallIcon("application-exit"), tr("&Quit"), coll,
301                                       this, SLOT(quit()), tr("Ctrl+Q")));
302
303   // View
304   coll->addAction("ConfigureBufferViews", new Action(tr("&Configure Chat Lists..."), coll,
305                                              this, SLOT(on_actionConfigureViews_triggered())));
306
307   QAction *lockAct = coll->addAction("LockLayout", new Action(tr("&Lock Layout"), coll));
308   lockAct->setCheckable(true);
309   connect(lockAct, SIGNAL(toggled(bool)), SLOT(on_actionLockLayout_toggled(bool)));
310
311   coll->addAction("ToggleSearchBar", new Action(SmallIcon("edit-find"), tr("Show &Search Bar"), coll,
312                                                 0, 0, QKeySequence::Find))->setCheckable(true);
313   coll->addAction("ShowAwayLog", new Action(tr("Show Away Log"), coll,
314                                             this, SLOT(showAwayLog())));
315   coll->addAction("ToggleMenuBar", new Action(SmallIcon("show-menu"), tr("Show &Menubar"), coll,
316                                                 0, 0, tr("Ctrl+M")))->setCheckable(true);
317
318   coll->addAction("ToggleStatusBar", new Action(tr("Show Status &Bar"), coll,
319                                                 0, 0))->setCheckable(true);
320
321   // Settings
322   coll->addAction("ConfigureQuassel", new Action(SmallIcon("configure"), tr("&Configure Quassel..."), coll,
323                                                   this, SLOT(showSettingsDlg()), tr("F7")));
324
325   // Help
326   coll->addAction("AboutQuassel", new Action(SmallIcon("quassel"), tr("&About Quassel"), coll,
327                                               this, SLOT(showAboutDlg())));
328   coll->addAction("AboutQt", new Action(QIcon(":/pics/qt-logo.png"), tr("About &Qt"), coll,
329                                          qApp, SLOT(aboutQt())));
330   coll->addAction("DebugNetworkModel", new Action(SmallIcon("tools-report-bug"), tr("Debug &NetworkModel"), coll,
331                                        this, SLOT(on_actionDebugNetworkModel_triggered())));
332   coll->addAction("DebugBufferViewOverlay", new Action(SmallIcon("tools-report-bug"), tr("Debug &BufferViewOverlay"), coll,
333                                        this, SLOT(on_actionDebugBufferViewOverlay_triggered())));
334   coll->addAction("DebugMessageModel", new Action(SmallIcon("tools-report-bug"), tr("Debug &MessageModel"), coll,
335                                        this, SLOT(on_actionDebugMessageModel_triggered())));
336   coll->addAction("DebugHotList", new Action(SmallIcon("tools-report-bug"), tr("Debug &HotList"), coll,
337                                        this, SLOT(on_actionDebugHotList_triggered())));
338   coll->addAction("DebugLog", new Action(SmallIcon("tools-report-bug"), tr("Debug &Log"), coll,
339                                        this, SLOT(on_actionDebugLog_triggered())));
340   coll->addAction("ReloadStyle", new Action(SmallIcon("view-refresh"), tr("Reload Stylesheet"), coll,
341                                        QtUi::style(), SLOT(reload()), QKeySequence::Refresh));
342
343   // Navigation
344   coll->addAction("JumpHotBuffer", new Action(tr("Jump to hot chat"), coll,
345                                               this, SLOT(on_jumpHotBuffer_triggered()), QKeySequence(Qt::META + Qt::Key_A)));
346 }
347
348 void MainWin::setupMenus() {
349   ActionCollection *coll = QtUi::actionCollection("General");
350
351   _fileMenu = menuBar()->addMenu(tr("&File"));
352
353   static const QStringList coreActions = QStringList()
354     << "ConnectCore" << "DisconnectCore" << "CoreInfo";
355
356   QAction *coreAction;
357   foreach(QString actionName, coreActions) {
358     coreAction = coll->action(actionName);
359     _fileMenu->addAction(coreAction);
360     flagRemoteCoreOnly(coreAction);
361   }
362   flagRemoteCoreOnly(_fileMenu->addSeparator());
363
364   _networksMenu = _fileMenu->addMenu(tr("&Networks"));
365   _networksMenu->addAction(coll->action("ConfigureNetworks"));
366   _networksMenu->addSeparator();
367   _fileMenu->addSeparator();
368   _fileMenu->addAction(coll->action("Quit"));
369
370   _viewMenu = menuBar()->addMenu(tr("&View"));
371   _bufferViewsMenu = _viewMenu->addMenu(tr("&Chat Lists"));
372   _bufferViewsMenu->addAction(coll->action("ConfigureBufferViews"));
373   _toolbarMenu = _viewMenu->addMenu(tr("&Toolbars"));
374   _viewMenu->addSeparator();
375
376   _viewMenu->addAction(coll->action("ToggleMenuBar"));
377   _viewMenu->addAction(coll->action("ToggleStatusBar"));
378   _viewMenu->addAction(coll->action("ToggleSearchBar"));
379
380   coreAction = coll->action("ShowAwayLog");
381   flagRemoteCoreOnly(coreAction);
382   _viewMenu->addAction(coreAction);
383
384   _viewMenu->addSeparator();
385   _viewMenu->addAction(coll->action("LockLayout"));
386
387   _settingsMenu = menuBar()->addMenu(tr("&Settings"));
388 #ifdef HAVE_KDE
389   _settingsMenu->addAction(KStandardAction::configureNotifications(this, SLOT(showNotificationsDlg()), this));
390   _settingsMenu->addAction(KStandardAction::keyBindings(this, SLOT(showShortcutsDlg()), this));
391 #endif
392   _settingsMenu->addAction(coll->action("ConfigureQuassel"));
393
394   _helpMenu = menuBar()->addMenu(tr("&Help"));
395   _helpMenu->addAction(coll->action("AboutQuassel"));
396 #ifndef HAVE_KDE
397   _helpMenu->addAction(coll->action("AboutQt"));
398 #else
399   _helpMenu->addAction(KStandardAction::aboutKDE(_kHelpMenu, SLOT(aboutKDE()), this));
400 #endif
401   _helpMenu->addSeparator();
402   _helpDebugMenu = _helpMenu->addMenu(SmallIcon("tools-report-bug"), tr("Debug"));
403   _helpDebugMenu->addAction(coll->action("DebugNetworkModel"));
404   _helpDebugMenu->addAction(coll->action("DebugBufferViewOverlay"));
405   _helpDebugMenu->addAction(coll->action("DebugMessageModel"));
406   _helpDebugMenu->addAction(coll->action("DebugHotList"));
407   _helpDebugMenu->addAction(coll->action("DebugLog"));
408   _helpDebugMenu->addSeparator();
409   _helpDebugMenu->addAction(coll->action("ReloadStyle"));
410
411   // Toggle visibility
412   QAction *showMenuBar = QtUi::actionCollection("General")->action("ToggleMenuBar");
413
414   QtUiSettings uiSettings;
415   bool enabled = uiSettings.value("ShowMenuBar", QVariant(true)).toBool();
416   showMenuBar->setChecked(enabled);
417   enabled ? menuBar()->show() : menuBar()->hide();
418
419   connect(showMenuBar, SIGNAL(toggled(bool)), menuBar(), SLOT(setVisible(bool)));
420   connect(showMenuBar, SIGNAL(toggled(bool)), this, SLOT(saveMenuBarStatus(bool)));
421 }
422
423 void MainWin::setupBufferWidget() {
424   _bufferWidget = new BufferWidget(this);
425   _bufferWidget->setModel(Client::bufferModel());
426   _bufferWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
427   setCentralWidget(_bufferWidget);
428 }
429
430 void MainWin::addBufferView(int bufferViewConfigId) {
431   addBufferView(Client::bufferViewManager()->clientBufferViewConfig(bufferViewConfigId));
432 }
433
434 void MainWin::addBufferView(ClientBufferViewConfig *config) {
435   if(!config)
436     return;
437
438   config->setLocked(QtUiSettings().value("LockLayout", false).toBool());
439   BufferViewDock *dock = new BufferViewDock(config, this);
440
441   //create the view and initialize it's filter
442   BufferView *view = new BufferView(dock);
443   view->setFilteredModel(Client::bufferModel(), config);
444   view->installEventFilter(_inputWidget); // for key presses
445   view->show();
446
447   Client::bufferModel()->synchronizeView(view);
448
449   dock->setWidget(view);
450   dock->show();
451
452   addDockWidget(Qt::LeftDockWidgetArea, dock);
453   _bufferViewsMenu->addAction(dock->toggleViewAction());
454
455   connect(dock->toggleViewAction(), SIGNAL(toggled(bool)), this, SLOT(bufferViewToggled(bool)));
456   _bufferViews.append(dock);
457 }
458
459 void MainWin::removeBufferView(int bufferViewConfigId) {
460   QVariant actionData;
461   BufferViewDock *dock;
462   foreach(QAction *action, _bufferViewsMenu->actions()) {
463     actionData = action->data();
464     if(!actionData.isValid())
465       continue;
466
467     dock = qobject_cast<BufferViewDock *>(action->parent());
468     if(dock && actionData.toInt() == bufferViewConfigId) {
469       removeAction(action);
470       dock->deleteLater();
471     }
472   }
473 }
474
475 void MainWin::bufferViewToggled(bool enabled) {
476   QAction *action = qobject_cast<QAction *>(sender());
477   Q_ASSERT(action);
478   BufferViewDock *dock = qobject_cast<BufferViewDock *>(action->parent());
479   Q_ASSERT(dock);
480   if(enabled) {
481     Client::bufferViewOverlay()->addView(dock->bufferViewId());
482     BufferViewConfig *config = dock->config();
483     if(config && config->isInitialized()) {
484       BufferIdList buffers;
485       if(config->networkId().isValid()) {
486         foreach(BufferId bufferId, config->bufferList()) {
487           if(Client::networkModel()->networkId(bufferId) == config->networkId())
488             buffers << bufferId;
489         }
490         foreach(BufferId bufferId, config->temporarilyRemovedBuffers().toList()) {
491           if(Client::networkModel()->networkId(bufferId) == config->networkId())
492             buffers << bufferId;
493         }
494       } else {
495         buffers = BufferIdList::fromSet(config->bufferList().toSet() + config->temporarilyRemovedBuffers());
496       }
497       Client::backlogManager()->checkForBacklog(buffers);
498     }
499   } else {
500     Client::bufferViewOverlay()->removeView(dock->bufferViewId());
501   }
502 }
503
504 BufferView *MainWin::allBuffersView() const {
505   // "All Buffers" is always the first dock created
506   if(_bufferViews.count() > 0)
507     return _bufferViews[0]->bufferView();
508   return 0;
509 }
510
511 void MainWin::showNotificationsDlg() {
512   SettingsPageDlg dlg(new NotificationsSettingsPage(this), this);
513   dlg.exec();
514 }
515
516 void MainWin::on_actionConfigureNetworks_triggered() {
517   SettingsPageDlg dlg(new NetworksSettingsPage(this), this);
518   dlg.exec();
519 }
520
521 void MainWin::on_actionConfigureViews_triggered() {
522   SettingsPageDlg dlg(new BufferViewSettingsPage(this), this);
523   dlg.exec();
524 }
525
526 void MainWin::on_actionLockLayout_toggled(bool lock) {
527   QList<VerticalDock *> docks = findChildren<VerticalDock *>();
528   foreach(VerticalDock *dock, docks) {
529     dock->showTitle(!lock);
530   }
531   if(Client::bufferViewManager()) {
532     foreach(ClientBufferViewConfig *config, Client::bufferViewManager()->clientBufferViewConfigs()) {
533       config->setLocked(lock);
534     }
535   }
536   QtUiSettings().setValue("LockLayout", lock);
537 }
538
539 void MainWin::setupNickWidget() {
540   // create nick dock
541   NickListDock *nickDock = new NickListDock(tr("Nicks"), this);
542   nickDock->setObjectName("NickDock");
543   nickDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
544
545   _nickListWidget = new NickListWidget(nickDock);
546   nickDock->setWidget(_nickListWidget);
547
548   addDockWidget(Qt::RightDockWidgetArea, nickDock);
549   _viewMenu->addAction(nickDock->toggleViewAction());
550   nickDock->toggleViewAction()->setText(tr("Show Nick List"));
551
552   // See NickListDock::NickListDock();
553   // connect(nickDock->toggleViewAction(), SIGNAL(triggered(bool)), nickListWidget, SLOT(showWidget(bool)));
554
555   // attach the NickListWidget to the BufferModel and the default selection
556   _nickListWidget->setModel(Client::bufferModel());
557   _nickListWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
558 }
559
560 void MainWin::setupChatMonitor() {
561   VerticalDock *dock = new VerticalDock(tr("Chat Monitor"), this);
562   dock->setObjectName("ChatMonitorDock");
563
564   ChatMonitorFilter *filter = new ChatMonitorFilter(Client::messageModel(), this);
565   ChatMonitorView *chatView = new ChatMonitorView(filter, this);
566   chatView->show();
567   dock->setWidget(chatView);
568   dock->hide();
569
570   addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
571   _viewMenu->addAction(dock->toggleViewAction());
572   dock->toggleViewAction()->setText(tr("Show Chat Monitor"));
573 }
574
575 void MainWin::setupInputWidget() {
576   VerticalDock *dock = new VerticalDock(tr("Inputline"), this);
577   dock->setObjectName("InputDock");
578
579   _inputWidget = new InputWidget(dock);
580   dock->setWidget(_inputWidget);
581
582   addDockWidget(Qt::BottomDockWidgetArea, dock);
583
584   _viewMenu->addAction(dock->toggleViewAction());
585   dock->toggleViewAction()->setText(tr("Show Input Line"));
586
587   _inputWidget->setModel(Client::bufferModel());
588   _inputWidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
589
590   _bufferWidget->setFocusProxy(_inputWidget);
591
592   _inputWidget->inputLine()->installEventFilter(_bufferWidget);
593 }
594
595 void MainWin::setupTopicWidget() {
596   VerticalDock *dock = new VerticalDock(tr("Topic"), this);
597   dock->setObjectName("TopicDock");
598   TopicWidget *topicwidget = new TopicWidget(dock);
599
600   dock->setWidget(topicwidget);
601
602   topicwidget->setModel(Client::bufferModel());
603   topicwidget->setSelectionModel(Client::bufferModel()->standardSelectionModel());
604
605   addDockWidget(Qt::TopDockWidgetArea, dock, Qt::Vertical);
606
607   _viewMenu->addAction(dock->toggleViewAction());
608   dock->toggleViewAction()->setText(tr("Show Topic Line"));
609 }
610
611 void MainWin::setupTitleSetter() {
612   _titleSetter.setModel(Client::bufferModel());
613   _titleSetter.setSelectionModel(Client::bufferModel()->standardSelectionModel());
614 }
615
616 void MainWin::setupStatusBar() {
617   // MessageProcessor progress
618   statusBar()->addPermanentWidget(msgProcessorStatusWidget);
619
620   // Core Lag:
621   updateLagIndicator();
622   statusBar()->addPermanentWidget(coreLagLabel);
623   coreLagLabel->hide();
624   connect(Client::signalProxy(), SIGNAL(lagUpdated(int)), this, SLOT(updateLagIndicator(int)));
625
626   // SSL indicator
627   sslLabel->setPixmap(QPixmap());
628   statusBar()->addPermanentWidget(sslLabel);
629   sslLabel->hide();
630
631   QAction *showStatusbar = QtUi::actionCollection("General")->action("ToggleStatusBar");
632
633   QtUiSettings uiSettings;
634
635   bool enabled = uiSettings.value("ShowStatusBar", QVariant(true)).toBool();
636   showStatusbar->setChecked(enabled);
637   enabled ? statusBar()->show() : statusBar()->hide();
638
639   connect(showStatusbar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool)));
640   connect(showStatusbar, SIGNAL(toggled(bool)), this, SLOT(saveStatusBarStatus(bool)));
641 }
642
643 void MainWin::setupHotList() {
644   FlatProxyModel *flatProxy = new FlatProxyModel(this);
645   flatProxy->setSourceModel(Client::bufferModel());
646   _bufferHotList = new BufferHotListFilter(flatProxy);
647 }
648
649 void MainWin::saveMenuBarStatus(bool enabled) {
650   QtUiSettings uiSettings;
651   uiSettings.setValue("ShowMenuBar", enabled);
652 }
653
654 void MainWin::saveStatusBarStatus(bool enabled) {
655   QtUiSettings uiSettings;
656   uiSettings.setValue("ShowStatusBar", enabled);
657 }
658
659 void MainWin::setupSystray() {
660   _systemTray = new SystemTray(this);
661 }
662
663 void MainWin::setupToolBars() {
664   connect(_bufferWidget, SIGNAL(currentChanged(QModelIndex)),
665           QtUi::toolBarActionProvider(), SLOT(currentBufferChanged(QModelIndex)));
666   connect(_nickListWidget, SIGNAL(nickSelectionChanged(QModelIndexList)),
667           QtUi::toolBarActionProvider(), SLOT(nickSelectionChanged(QModelIndexList)));
668
669 #ifdef Q_WS_MAC
670   setUnifiedTitleAndToolBarOnMac(true);
671 #endif
672
673 #ifdef HAVE_KDE
674   _mainToolBar = new KToolBar("MainToolBar", this, Qt::TopToolBarArea, false, true, true);
675 #else
676   _mainToolBar = new QToolBar(this);
677   _mainToolBar->setObjectName("MainToolBar");
678 #endif
679   _mainToolBar->setWindowTitle(tr("Main Toolbar"));
680   addToolBar(_mainToolBar);
681
682   QtUi::toolBarActionProvider()->addActions(_mainToolBar, ToolBarActionProvider::MainToolBar);
683   _toolbarMenu->addAction(_mainToolBar->toggleViewAction());
684 }
685
686 void MainWin::connectedToCore() {
687   Q_CHECK_PTR(Client::bufferViewManager());
688   connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigAdded(int)), this, SLOT(addBufferView(int)));
689   connect(Client::bufferViewManager(), SIGNAL(bufferViewConfigDeleted(int)), this, SLOT(removeBufferView(int)));
690   connect(Client::bufferViewManager(), SIGNAL(initDone()), this, SLOT(loadLayout()));
691
692   setConnectedState();
693 }
694
695 void MainWin::setConnectedState() {
696   ActionCollection *coll = QtUi::actionCollection("General");
697
698   coll->action("ConnectCore")->setEnabled(false);
699   coll->action("DisconnectCore")->setEnabled(true);
700   coll->action("CoreInfo")->setEnabled(true);
701
702   foreach(QAction *action, _fileMenu->actions()) {
703     if(isRemoteCoreOnly(action))
704       action->setVisible(!Client::internalCore());
705   }
706
707   disconnect(Client::backlogManager(), SIGNAL(updateProgress(int, int)), msgProcessorStatusWidget, SLOT(setProgress(int, int)));
708   disconnect(Client::backlogManager(), SIGNAL(messagesRequested(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
709   disconnect(Client::backlogManager(), SIGNAL(messagesProcessed(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
710   if(!Client::internalCore()) {
711     connect(Client::backlogManager(), SIGNAL(updateProgress(int, int)), msgProcessorStatusWidget, SLOT(setProgress(int, int)));
712     connect(Client::backlogManager(), SIGNAL(messagesRequested(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
713     connect(Client::backlogManager(), SIGNAL(messagesProcessed(const QString &)), this, SLOT(showStatusBarMessage(const QString &)));
714   }
715
716   // _viewMenu->setEnabled(true);
717   if(!Client::internalCore())
718     statusBar()->showMessage(tr("Connected to core."));
719   else
720     statusBar()->clearMessage();
721
722   if(Client::signalProxy()->isSecure()) {
723     sslLabel->setPixmap(SmallIcon("security-high"));
724   } else {
725     sslLabel->setPixmap(SmallIcon("security-low"));
726   }
727
728   sslLabel->setVisible(!Client::internalCore());
729   coreLagLabel->setVisible(!Client::internalCore());
730   updateIcon();
731   systemTray()->setState(SystemTray::Active);
732
733   if(Client::networkIds().isEmpty()) {
734     IrcConnectionWizard *wizard = new IrcConnectionWizard(this, Qt::Sheet);
735     wizard->show();
736   }
737 }
738
739 void MainWin::loadLayout() {
740   QtUiSettings s;
741   int accountId = Client::currentCoreAccount().toInt();
742   restoreState(s.value(QString("MainWinState-%1").arg(accountId)).toByteArray(), accountId);
743 }
744
745 void MainWin::saveLayout() {
746   QtUiSettings s;
747   int accountId = Client::currentCoreAccount().toInt();
748   if(accountId > 0) s.setValue(QString("MainWinState-%1").arg(accountId) , saveState(accountId));
749 }
750
751 void MainWin::updateLagIndicator(int lag) {
752   QString text = tr("Core Lag: %1");
753   if(lag == -1)
754     text = text.arg('-');
755   else
756     text = text.arg("%1 msec").arg(lag);
757   coreLagLabel->setText(text);
758 }
759
760 void MainWin::disconnectedFromCore() {
761   // save core specific layout and remove bufferviews;
762   saveLayout();
763   QVariant actionData;
764   BufferViewDock *dock;
765   foreach(QAction *action, _bufferViewsMenu->actions()) {
766     actionData = action->data();
767     if(!actionData.isValid())
768       continue;
769
770     dock = qobject_cast<BufferViewDock *>(action->parent());
771     if(dock && actionData.toInt() != -1) {
772       removeAction(action);
773       dock->deleteLater();
774     }
775   }
776   QtUiSettings s;
777   restoreState(s.value("MainWinState").toByteArray());
778   setDisconnectedState();
779 }
780
781 void MainWin::setDisconnectedState() {
782   ActionCollection *coll = QtUi::actionCollection("General");
783   //ui.menuCore->setEnabled(false);
784   coll->action("ConnectCore")->setEnabled(true);
785   coll->action("DisconnectCore")->setEnabled(false);
786   coll->action("CoreInfo")->setEnabled(false);
787   //_viewMenu->setEnabled(false);
788   statusBar()->showMessage(tr("Not connected to core."));
789   sslLabel->setPixmap(QPixmap());
790   sslLabel->hide();
791   updateLagIndicator();
792   coreLagLabel->hide();
793   if(msgProcessorStatusWidget)
794     msgProcessorStatusWidget->setProgress(0, 0);
795   updateIcon();
796   systemTray()->setState(SystemTray::Inactive);
797 }
798
799 void MainWin::startInternalCore() {
800   ClientSyncer *syncer = new ClientSyncer();
801   Client::registerClientSyncer(syncer);
802   connect(syncer, SIGNAL(syncFinished()), syncer, SLOT(deleteLater()), Qt::QueuedConnection);
803   syncer->useInternalCore();
804 }
805
806 void MainWin::showCoreConnectionDlg(bool autoConnect) {
807   CoreConnectDlg(autoConnect, this).exec();
808 }
809
810 void MainWin::showChannelList(NetworkId netId) {
811   ChannelListDlg *channelListDlg = new ChannelListDlg();
812
813   if(!netId.isValid()) {
814     QAction *action = qobject_cast<QAction *>(sender());
815     if(action)
816       netId = action->data().value<NetworkId>();
817   }
818
819   channelListDlg->setAttribute(Qt::WA_DeleteOnClose);
820   channelListDlg->setNetwork(netId);
821   channelListDlg->show();
822 }
823
824 void MainWin::showIgnoreList(QString newRule) {
825   SettingsPageDlg dlg(new IgnoreListSettingsPage(this), this);
826   // prepare config dialog for new rule
827   if(!newRule.isEmpty())
828     qobject_cast<IgnoreListSettingsPage *>(dlg.currentPage())->editIgnoreRule(newRule);
829   dlg.exec();
830 }
831
832 void MainWin::showCoreInfoDlg() {
833   CoreInfoDlg(this).exec();
834 }
835
836 void MainWin::showAwayLog() {
837   if(_awayLog)
838     return;
839   AwayLogFilter *filter = new AwayLogFilter(Client::messageModel());
840   _awayLog = new AwayLogView(filter, 0);
841   filter->setParent(_awayLog);
842   connect(_awayLog, SIGNAL(destroyed()), this, SLOT(awayLogDestroyed()));
843   _awayLog->setAttribute(Qt::WA_DeleteOnClose);
844   _awayLog->show();
845 }
846
847 void MainWin::awayLogDestroyed() {
848   _awayLog = 0;
849 }
850
851 void MainWin::showSettingsDlg() {
852   SettingsDlg *dlg = new SettingsDlg();
853
854   //Category: Interface
855   dlg->registerSettingsPage(new AppearanceSettingsPage(dlg));
856   dlg->registerSettingsPage(new ChatViewSettingsPage(dlg));
857   dlg->registerSettingsPage(new ItemViewSettingsPage(dlg));
858   dlg->registerSettingsPage(new InputWidgetSettingsPage(dlg));
859   dlg->registerSettingsPage(new TopicWidgetSettingsPage(dlg));
860   dlg->registerSettingsPage(new HighlightSettingsPage(dlg));
861   dlg->registerSettingsPage(new NotificationsSettingsPage(dlg));
862   dlg->registerSettingsPage(new BacklogSettingsPage(dlg));
863   dlg->registerSettingsPage(new BufferViewSettingsPage(dlg));
864   dlg->registerSettingsPage(new ChatMonitorSettingsPage(dlg));
865
866   //Category: Misc
867   dlg->registerSettingsPage(new GeneralSettingsPage(dlg));
868   dlg->registerSettingsPage(new ConnectionSettingsPage(dlg));
869   dlg->registerSettingsPage(new IdentitiesSettingsPage(dlg));
870   dlg->registerSettingsPage(new NetworksSettingsPage(dlg));
871   dlg->registerSettingsPage(new AliasesSettingsPage(dlg));
872   dlg->registerSettingsPage(new IgnoreListSettingsPage(dlg));
873
874   dlg->show();
875 }
876
877 void MainWin::showAboutDlg() {
878   AboutDlg(this).exec();
879 }
880
881 #ifdef HAVE_KDE
882 void MainWin::showShortcutsDlg() {
883   KShortcutsDialog::configure(QtUi::actionCollection("General"), KShortcutsEditor::LetterShortcutsDisallowed);
884 }
885 #endif
886
887 /********************************************************************************************************/
888
889 bool MainWin::event(QEvent *event) {
890   if(event->type() == QEvent::WindowActivate) {
891     BufferId buffer = Client::bufferModel()->currentBuffer();
892     if(buffer.isValid())
893       QtUi::closeNotifications(buffer);
894   }
895   return QMainWindow::event(event);
896 }
897
898 void MainWin::moveEvent(QMoveEvent *event) {
899   if(!(windowState() & Qt::WindowMaximized))
900     _normalPos = event->pos();
901
902   QMainWindow::moveEvent(event);
903 }
904
905 void MainWin::resizeEvent(QResizeEvent *event) {
906   if(!(windowState() & Qt::WindowMaximized))
907     _normalSize = event->size();
908
909   QMainWindow::resizeEvent(event);
910 }
911
912 void MainWin::closeEvent(QCloseEvent *event) {
913   QtUiSettings s;
914   QtUiApplication* app = qobject_cast<QtUiApplication*> qApp;
915   Q_ASSERT(app);
916   if(!app->isAboutToQuit() && s.value("UseSystemTrayIcon").toBool() && s.value("MinimizeOnClose").toBool()) {
917     hideToTray();
918     event->ignore();
919   } else {
920     event->accept();
921     quit();
922   }
923 }
924
925 void MainWin::changeEvent(QEvent *event) {
926 #ifdef Q_WS_WIN
927   if(event->type() == QEvent::ActivationChange)
928     dwTickCount = GetTickCount();  // needed for toggleMinimizedToTray()
929 #endif
930
931   QMainWindow::changeEvent(event);
932 }
933
934 void MainWin::hideToTray() {
935   if(!systemTray()->isSystemTrayAvailable()) {
936     qWarning() << Q_FUNC_INFO << "was called with no SystemTray available!";
937     return;
938   }
939   hide();
940   systemTray()->setIconVisible();
941 }
942
943 void MainWin::toggleMinimizedToTray() {
944 #ifdef Q_WS_WIN
945   // the problem is that we lose focus when the systray icon is activated
946   // and we don't know the former active window
947   // therefore we watch for activation event and use our stopwatch :)
948   // courtesy: KSystemTrayIcon
949   if(GetTickCount() - dwTickCount >= 300)
950     // we weren't active in the last 300ms -> activate
951     forceActivated();
952   else
953     hideToTray();
954
955 #else
956
957   if(!isVisible() || isMinimized())
958     // restore
959     forceActivated();
960   else
961     hideToTray();
962
963 #endif
964 }
965
966 void MainWin::forceActivated() {
967 #ifdef Q_WS_X11
968   // Bypass focus stealing prevention
969   QX11Info::setAppUserTime(QX11Info::appTime());
970 #endif
971
972   if(windowState() & Qt::WindowMinimized) {
973     // restore
974     setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
975   }
976
977   // this does not actually work on all platforms... and causes more evil than good
978   // move(frameGeometry().topLeft()); // avoid placement policies
979   show();
980   raise();
981   activateWindow();
982 }
983
984 void MainWin::messagesInserted(const QModelIndex &parent, int start, int end) {
985   Q_UNUSED(parent);
986
987   bool hasFocus = QApplication::activeWindow() != 0;
988
989   for(int i = start; i <= end; i++) {
990     QModelIndex idx = Client::messageModel()->index(i, ChatLineModel::ContentsColumn);
991     if(!idx.isValid()) {
992       qDebug() << "MainWin::messagesInserted(): Invalid model index!";
993       continue;
994     }
995     Message::Flags flags = (Message::Flags)idx.data(ChatLineModel::FlagsRole).toInt();
996     if(flags.testFlag(Message::Backlog) || flags.testFlag(Message::Self))
997       continue;
998
999     BufferId bufId = idx.data(ChatLineModel::BufferIdRole).value<BufferId>();
1000     BufferInfo::Type bufType = Client::networkModel()->bufferType(bufId);
1001
1002     if(hasFocus && bufId == Client::bufferModel()->currentBuffer())
1003       continue;
1004
1005     if((flags & Message::Highlight || bufType == BufferInfo::QueryBuffer)
1006       && !(Client::ignoreListManager() && Client::ignoreListManager()->match(idx.data(MessageModel::MessageRole).value<Message>(),
1007                                                                              Client::networkModel()->networkName(bufId))))
1008     {
1009       QModelIndex senderIdx = Client::messageModel()->index(i, ChatLineModel::SenderColumn);
1010       QString sender = senderIdx.data(ChatLineModel::EditRole).toString();
1011       QString contents = idx.data(ChatLineModel::DisplayRole).toString();
1012       AbstractNotificationBackend::NotificationType type;
1013
1014       if(bufType == BufferInfo::QueryBuffer && !hasFocus)
1015         type = AbstractNotificationBackend::PrivMsg;
1016       else if(bufType == BufferInfo::QueryBuffer && hasFocus)
1017         type = AbstractNotificationBackend::PrivMsgFocused;
1018       else if(flags & Message::Highlight && !hasFocus)
1019         type = AbstractNotificationBackend::Highlight;
1020       else
1021         type = AbstractNotificationBackend::HighlightFocused;
1022
1023       QtUi::invokeNotification(bufId, type, sender, contents);
1024     }
1025   }
1026 }
1027
1028 void MainWin::currentBufferChanged(BufferId buffer) {
1029   if(buffer.isValid())
1030     QtUi::closeNotifications(buffer);
1031 }
1032
1033 void MainWin::clientNetworkCreated(NetworkId id) {
1034   const Network *net = Client::network(id);
1035   QAction *act = new QAction(net->networkName(), this);
1036   act->setObjectName(QString("NetworkAction-%1").arg(id.toInt()));
1037   act->setData(QVariant::fromValue<NetworkId>(id));
1038   connect(net, SIGNAL(updatedRemotely()), this, SLOT(clientNetworkUpdated()));
1039   connect(act, SIGNAL(triggered()), this, SLOT(connectOrDisconnectFromNet()));
1040
1041   QAction *beforeAction = 0;
1042   foreach(QAction *action, _networksMenu->actions()) {
1043     if(!action->data().isValid())  // ignore stock actions
1044       continue;
1045     if(net->networkName().localeAwareCompare(action->text()) < 0) {
1046       beforeAction = action;
1047       break;
1048     }
1049   }
1050   _networksMenu->insertAction(beforeAction, act);
1051 }
1052
1053 void MainWin::clientNetworkUpdated() {
1054   const Network *net = qobject_cast<const Network *>(sender());
1055   if(!net)
1056     return;
1057
1058   QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(net->networkId().toInt()));
1059   if(!action)
1060     return;
1061
1062   action->setText(net->networkName());
1063
1064   switch(net->connectionState()) {
1065   case Network::Initialized:
1066     action->setIcon(SmallIcon("network-connect"));
1067     break;
1068   case Network::Disconnected:
1069     action->setIcon(SmallIcon("network-disconnect"));
1070     break;
1071   default:
1072     action->setIcon(SmallIcon("network-wired"));
1073   }
1074 }
1075
1076 void MainWin::clientNetworkRemoved(NetworkId id) {
1077   QAction *action = findChild<QAction *>(QString("NetworkAction-%1").arg(id.toInt()));
1078   if(!action)
1079     return;
1080
1081   action->deleteLater();
1082 }
1083
1084 void MainWin::connectOrDisconnectFromNet() {
1085   QAction *act = qobject_cast<QAction *>(sender());
1086   if(!act) return;
1087   const Network *net = Client::network(act->data().value<NetworkId>());
1088   if(!net) return;
1089   if(net->connectionState() == Network::Disconnected) net->requestConnect();
1090   else net->requestDisconnect();
1091 }
1092
1093 void MainWin::on_jumpHotBuffer_triggered() {
1094   if(!_bufferHotList->rowCount())
1095     return;
1096
1097   QModelIndex topIndex = _bufferHotList->index(0, 0);
1098   BufferId bufferId = _bufferHotList->data(topIndex, NetworkModel::BufferIdRole).value<BufferId>();
1099   Client::bufferModel()->switchToBuffer(bufferId);
1100 }
1101
1102 void MainWin::on_actionDebugNetworkModel_triggered() {
1103   QTreeView *view = new QTreeView;
1104   view->setAttribute(Qt::WA_DeleteOnClose);
1105   view->setWindowTitle("Debug NetworkModel View");
1106   view->setModel(Client::networkModel());
1107   view->setColumnWidth(0, 250);
1108   view->setColumnWidth(1, 250);
1109   view->setColumnWidth(2, 80);
1110   view->resize(610, 300);
1111   view->show();
1112 }
1113
1114 void MainWin::on_actionDebugHotList_triggered() {
1115   QTreeView *view = new QTreeView;
1116   view->setAttribute(Qt::WA_DeleteOnClose);
1117   view->setModel(_bufferHotList);
1118   view->show();
1119 }
1120
1121 void MainWin::on_actionDebugBufferViewOverlay_triggered() {
1122   DebugBufferViewOverlay *overlay = new DebugBufferViewOverlay(0);
1123   overlay->setAttribute(Qt::WA_DeleteOnClose);
1124   overlay->show();
1125 }
1126
1127 void MainWin::on_actionDebugMessageModel_triggered() {
1128   QTableView *view = new QTableView(0);
1129   DebugMessageModelFilter *filter = new DebugMessageModelFilter(view);
1130   filter->setSourceModel(Client::messageModel());
1131   view->setModel(filter);
1132   view->setAttribute(Qt::WA_DeleteOnClose, true);
1133   view->verticalHeader()->hide();
1134   view->horizontalHeader()->setStretchLastSection(true);
1135   view->show();
1136 }
1137
1138 void MainWin::on_actionDebugLog_triggered() {
1139   DebugLogWidget *logWidget = new DebugLogWidget(0);
1140   logWidget->show();
1141 }
1142
1143 void MainWin::showStatusBarMessage(const QString &message) {
1144   statusBar()->showMessage(message, 10000);
1145 }
1146