1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
23 #include <QApplication>
27 #include <QStringList>
29 #include "abstractnotificationbackend.h"
30 #include "buffermodel.h"
31 #include "chatlinemodel.h"
32 #include "contextmenuactionprovider.h"
35 #include "qtuimessageprocessor.h"
36 #include "qtuisettings.h"
37 #include "qtuistyle.h"
38 #include "systemtray.h"
39 #include "toolbaractionprovider.h"
43 QList<AbstractNotificationBackend *> QtUi::_notificationBackends;
44 QList<AbstractNotificationBackend::Notification> QtUi::_notifications;
47 QtUi *QtUi::instance()
49 return static_cast<QtUi*>(GraphicalUi::instance());
55 , _systemIconTheme{QIcon::themeName()}
57 QtUiSettings uiSettings;
58 Quassel::loadTranslation(uiSettings.value("Locale", QLocale::system()).value<QLocale>());
60 if (Quassel::isOptionSet("icontheme")) {
61 _systemIconTheme = Quassel::optionValue("icontheme");
62 QIcon::setThemeName(_systemIconTheme);
65 QApplication::setWindowIcon(icon::get("quassel"));
67 setUiStyle(new QtUiStyle(this));
73 unregisterAllNotificationBackends();
79 setContextMenuActionProvider(new ContextMenuActionProvider(this));
80 setToolBarActionProvider(new ToolBarActionProvider(this));
82 _mainWin.reset(new MainWin()); // TODO C++14: std::make_unique
83 setMainWidget(_mainWin.get());
85 connect(_mainWin.get(), SIGNAL(connectToCore(const QVariantMap &)), this, SIGNAL(connectToCore(const QVariantMap &)));
86 connect(_mainWin.get(), SIGNAL(disconnectFromCore()), this, SIGNAL(disconnectFromCore()));
87 connect(Client::instance(), SIGNAL(bufferMarkedAsRead(BufferId)), SLOT(closeNotifications(BufferId)));
91 QtUiSettings uiSettings;
92 uiSettings.initAndNotify("UseSystemTrayIcon", this, SLOT(useSystemTrayChanged(QVariant)), true);
94 GraphicalUi::init(); // needs to be called after the mainWin is initialized
98 MessageModel *QtUi::createMessageModel(QObject *parent)
100 return new ChatLineModel(parent);
104 AbstractMessageProcessor *QtUi::createMessageProcessor(QObject *parent)
106 return new QtUiMessageProcessor(parent);
110 void QtUi::connectedToCore()
112 _mainWin->connectedToCore();
116 void QtUi::disconnectedFromCore()
118 _mainWin->disconnectedFromCore();
119 GraphicalUi::disconnectedFromCore();
123 void QtUi::useSystemTrayChanged(const QVariant &v)
125 _useSystemTray = v.toBool();
126 SystemTray *tray = mainWindow()->systemTray();
127 if (_useSystemTray) {
128 if (tray->isSystemTrayAvailable())
129 tray->setVisible(true);
132 if (tray->isSystemTrayAvailable() && mainWindow()->isVisible())
133 tray->setVisible(false);
138 bool QtUi::haveSystemTray()
140 return mainWindow()->systemTray()->isSystemTrayAvailable() && instance()->_useSystemTray;
144 bool QtUi::isHidingMainWidgetAllowed() const
146 return haveSystemTray();
150 void QtUi::minimizeRestore(bool show)
152 SystemTray *tray = mainWindow()->systemTray();
154 if (tray && !_useSystemTray)
155 tray->setVisible(false);
158 if (tray && _useSystemTray)
159 tray->setVisible(true);
161 GraphicalUi::minimizeRestore(show);
165 void QtUi::registerNotificationBackend(AbstractNotificationBackend *backend)
167 if (!_notificationBackends.contains(backend)) {
168 _notificationBackends.append(backend);
169 instance()->connect(backend, SIGNAL(activated(uint)), SLOT(notificationActivated(uint)));
174 void QtUi::unregisterNotificationBackend(AbstractNotificationBackend *backend)
176 _notificationBackends.removeAll(backend);
180 void QtUi::unregisterAllNotificationBackends()
182 _notificationBackends.clear();
186 const QList<AbstractNotificationBackend *> &QtUi::notificationBackends()
188 return _notificationBackends;
192 uint QtUi::invokeNotification(BufferId bufId, AbstractNotificationBackend::NotificationType type, const QString &sender, const QString &text)
194 static int notificationId = 0;
196 AbstractNotificationBackend::Notification notification(++notificationId, bufId, type, sender, text);
197 _notifications.append(notification);
198 foreach(AbstractNotificationBackend *backend, _notificationBackends)
199 backend->notify(notification);
200 return notificationId;
204 void QtUi::closeNotification(uint notificationId)
206 QList<AbstractNotificationBackend::Notification>::iterator i = _notifications.begin();
207 while (i != _notifications.end()) {
208 if (i->notificationId == notificationId) {
209 foreach(AbstractNotificationBackend *backend, _notificationBackends)
210 backend->close(notificationId);
211 i = _notifications.erase(i);
218 void QtUi::closeNotifications(BufferId bufferId)
220 QList<AbstractNotificationBackend::Notification>::iterator i = _notifications.begin();
221 while (i != _notifications.end()) {
222 if (!bufferId.isValid() || i->bufferId == bufferId) {
223 foreach(AbstractNotificationBackend *backend, _notificationBackends)
224 backend->close(i->notificationId);
225 i = _notifications.erase(i);
232 const QList<AbstractNotificationBackend::Notification> &QtUi::activeNotifications()
234 return _notifications;
238 void QtUi::notificationActivated(uint notificationId)
240 if (notificationId != 0) {
241 QList<AbstractNotificationBackend::Notification>::iterator i = _notifications.begin();
242 while (i != _notifications.end()) {
243 if (i->notificationId == notificationId) {
244 BufferId bufId = i->bufferId;
246 Client::bufferModel()->switchToBuffer(bufId);
252 closeNotification(notificationId);
254 activateMainWidget();
258 void QtUi::bufferMarkedAsRead(BufferId bufferId)
260 if (bufferId.isValid()) {
261 closeNotifications(bufferId);
266 std::vector<std::pair<QString, QString>> QtUi::availableIconThemes() const
268 //: Supported icon theme names
269 static const std::vector<std::pair<QString, QString>> supported {
270 { "breeze", tr("Breeze") },
271 { "breeze-dark", tr("Breeze Dark") },
272 #ifdef WITH_OXYGEN_ICONS
273 { "oxygen", tr("Oxygen") }
277 std::vector<std::pair<QString, QString>> result;
278 for (auto &&themePair : supported) {
279 for (auto &&dir : QIcon::themeSearchPaths()) {
280 if (QFileInfo{dir + "/" + themePair.first + "/index.theme"}.exists()) {
281 result.push_back(themePair);
291 QString QtUi::systemIconTheme() const
293 return _systemIconTheme;
297 void QtUi::setupIconTheme()
299 // Add paths to our own icon sets to the theme search paths
300 QStringList themePaths = QIcon::themeSearchPaths();
301 themePaths.removeAll(":/icons"); // this should come last
302 for (auto &&dataDir : Quassel::dataDirPaths()) {
303 QString iconDir{dataDir + "icons"};
304 if (QFileInfo{iconDir}.isDir()) {
305 themePaths << iconDir;
308 themePaths << ":/icons";
309 QIcon::setThemeSearchPaths(themePaths);
315 void QtUi::refreshIconTheme()
317 // List of available fallback themes
318 QStringList availableThemes;
319 for (auto &&themePair : availableIconThemes()) {
320 availableThemes << themePair.first;
323 if (availableThemes.isEmpty()) {
324 // We could probably introduce a more sophisticated fallback handling, such as putting the "most important" icons into hicolor,
325 // but this just gets complex for no good reason. We really rely on a supported theme to be installed, if not system-wide, then
326 // as part of the Quassel installation (which is enabled by default anyway).
327 qWarning() << tr("No supported icon theme installed, you'll lack icons! Supported are the KDE/Plasma themes Breeze, Breeze Dark and Oxygen.");
332 QString fallbackTheme{s.value("Icons/FallbackTheme").toString()};
334 if (fallbackTheme.isEmpty() || !availableThemes.contains(fallbackTheme)) {
335 if (availableThemes.contains(_systemIconTheme)) {
336 fallbackTheme = _systemIconTheme;
339 fallbackTheme = availableThemes.first();
343 if (_systemIconTheme.isEmpty() || _systemIconTheme == fallbackTheme || s.value("Icons/OverrideSystemTheme", true).toBool()) {
344 // We have a valid fallback theme and want to override the system theme (if it's even defined), so we're basically done
345 QIcon::setThemeName(fallbackTheme);
346 emit iconThemeRefreshed();
350 // At this point, we have a system theme that we don't want to override, but that may not contain all
352 // We create a dummy theme that inherits first from the system theme, then from the supported fallback.
353 // This rather ugly hack allows us to inject the fallback into the inheritance chain, so non-standard
354 // icons missing in the system theme will be filled in by the fallback.
355 // Since we can't get notified when the system theme changes, this means that a restart may be required
356 // to apply a theme change... but you can't have everything, I guess.
357 if (!_dummyThemeDir) {
358 _dummyThemeDir.reset(new QTemporaryDir{});
359 if (!_dummyThemeDir->isValid() || !QDir{_dummyThemeDir->path()}.mkpath("icons/quassel-icon-proxy/apps/32")) {
360 qWarning() << "Could not create temporary directory for proxying the system icon theme, using fallback";
361 QIcon::setThemeName(fallbackTheme);
362 emit iconThemeRefreshed();
365 // Add this to XDG_DATA_DIRS, otherwise KIconLoader complains
366 auto xdgDataDirs = qgetenv("XDG_DATA_DIRS");
367 if (!xdgDataDirs.isEmpty())
369 xdgDataDirs += _dummyThemeDir->path();
370 qputenv("XDG_DATA_DIRS", xdgDataDirs);
372 QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << _dummyThemeDir->path() + "/icons");
375 QFile indexFile{_dummyThemeDir->path() + "/icons/quassel-icon-proxy/index.theme"};
376 if (!indexFile.open(QFile::WriteOnly|QFile::Truncate)) {
377 qWarning() << "Could not create index file for proxying the system icon theme, using fallback";
378 QIcon::setThemeName(fallbackTheme);
379 emit iconThemeRefreshed();
383 // Write a dummy index file that is sufficient to make QIconLoader happy
384 auto indexContents = QString{
386 "Name=quassel-icon-proxy\n"
388 "Directories=apps/32\n"
389 "[apps/32]\nSize=32\nType=Fixed\n"
390 }.arg(_systemIconTheme, fallbackTheme);
391 if (indexFile.write(indexContents.toLatin1()) < 0) {
392 qWarning() << "Could not write index file for proxying the system icon theme, using fallback";
393 QIcon::setThemeName(fallbackTheme);
394 emit iconThemeRefreshed();
398 QIcon::setThemeName("quassel-icon-proxy");