diff --git a/AppMenu.cxx b/AppMenu.cxx index cf1fe29..397cfca 100644 --- a/AppMenu.cxx +++ b/AppMenu.cxx @@ -1,195 +1,196 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include //-------------------------------------------------------------------------------- AppMenu::AppMenu(DesktopPanel *parent) : Launcher(parent, "AppMenu") { button = new QToolButton; // QToolButton is smaller than QPushButton adjustIconSize(); button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); popup = new Menu(button); connect(button, &QToolButton::pressed, this, &AppMenu::showMenu); layout()->addWidget(button); loadConfig(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); connect(parent, &DesktopPanel::rowsChanged, this, &AppMenu::adjustIconSize); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &AppMenu::adjustIconSize); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &AppMenu::fill); - connect(QApplication::desktop(), &QDesktopWidget::resized, this, &AppMenu::fill); + connect(QApplication::primaryScreen(), &QScreen::geometryChanged, this, &AppMenu::fill); + connect(qApp, &QApplication::primaryScreenChanged, this, &AppMenu::fill); } //-------------------------------------------------------------------------------- void AppMenu::adjustIconSize() { const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); if ( MAX_ROWS > 1 ) button->setIconSize(QSize(48, 48)); else { int size = KIconLoader::global()->currentSize(KIconLoader::Panel); button->setIconSize(QSize(size, size)); } } //-------------------------------------------------------------------------------- void AppMenu::fill() { KDesktopFile desktopFile(dirPath + "/.directory"); if ( !desktopFile.readIcon().isEmpty() ) button->setIcon(QIcon::fromTheme(desktopFile.readIcon())); else if ( dirPath == QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) ) button->setIcon(QIcon::fromTheme("user-desktop")); else // fallback button->setIcon(QIcon::fromTheme("folder")); QLayoutItem *child; while ( (child = popup->layout()->takeAt(0)) ) { delete child->widget(); delete child; } QDir dir(dirPath); QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotDot); int row = 0, col = 0, h = 0; - const int maxHeight = QApplication::desktop()->height() - parentWidget()->sizeHint().height(); + const int maxHeight = QApplication::primaryScreen()->size().height() - parentWidget()->sizeHint().height(); for (const QFileInfo &info : entries) { QUrl url(QUrl::fromLocalFile(info.absoluteFilePath())); QString name; QIcon icon; KFileItem item(url); if ( item.isDesktopFile() ) { KDesktopFile desktopFile(info.absoluteFilePath()); if ( desktopFile.noDisplay() ) continue; name = desktopFile.readName(); if ( name.isEmpty() ) name = desktopFile.readGenericName(); QString iconName = desktopFile.readIcon(); icon = QIcon::fromTheme(iconName.isEmpty() ? name : iconName); } else if ( info.isDir() ) { if ( info.fileName() == "." ) { name = info.dir().dirName(); icon = button->icon(); } else icon = QIcon::fromTheme("folder"); } else { QMimeDatabase db; icon = QIcon::fromTheme(db.mimeTypeForFile(info.absoluteFilePath()).iconName()); } if ( name.isEmpty() ) name = info.fileName(); IconButton *entryButton = new IconButton(this, icon, 32, name); connect(entryButton, &IconButton::clicked, [this, url]() { popup->close(); new KRun(url, nullptr); }); h += entryButton->sizeHint().height(); if ( h >= maxHeight ) { h = entryButton->sizeHint().height(); col++; row = 0; } static_cast(popup->layout())->addWidget(entryButton, row++, col); } } //-------------------------------------------------------------------------------- void AppMenu::showMenu() { popup->exec(); button->setDown(false); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- Menu::Menu(QWidget *parent) : QMenu(parent) { QGridLayout *grid = new QGridLayout(this); grid->setContentsMargins(QMargins()); grid->setSpacing(0); } //-------------------------------------------------------------------------------- void Menu::hideEvent(QHideEvent *event) { Q_UNUSED(event); eventLoop.exit(); } //-------------------------------------------------------------------------------- void Menu::exec() { adjustSize(); QPoint p = parentWidget()->mapToGlobal(QPoint(0, 0)); move(p.x(), p.y() - height()); show(); eventLoop.exec(); } //-------------------------------------------------------------------------------- diff --git a/ClockWidget.cxx b/ClockWidget.cxx index 9b3ad96..a1e5c27 100644 --- a/ClockWidget.cxx +++ b/ClockWidget.cxx @@ -1,248 +1,248 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017,2018 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- CalendarPopup::CalendarPopup(QWidget *parent) : QFrame(parent) { setWindowFlags(windowFlags() | Qt::Popup); setFrameShape(QFrame::StyledPanel); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); cal = new QCalendarWidget; vbox->addWidget(cal); QPushButton *today = new QPushButton(QIcon::fromTheme("go-jump-today"), QString()); vbox->addWidget(today); connect(today, &QPushButton::clicked, this, &CalendarPopup::goToday); } //-------------------------------------------------------------------------------- void CalendarPopup::goToday() { cal->showToday(); cal->setSelectedDate(QDate::currentDate()); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- ClockWidget::ClockWidget(DesktopPanel *parent) : QFrame(parent), calendar(nullptr) { ensurePolished(); // make sure we already have the css applied timer = new QTimer(this); // if seconds are shown, update every second, else less often if ( timeFormat.contains('s') ) timer->setInterval(1000); else timer->setInterval(5000); timer->start(); connect(timer, &QTimer::timeout, this, &ClockWidget::tick); connect(parent, &DesktopPanel::rowsChanged, this, &ClockWidget::fill); timeLabel = new QLabel(this); dayLabel = new QLabel(this); dateLabel = new QLabel(this); timeLabel->setObjectName("time"); dayLabel->setObjectName("day"); dateLabel->setObjectName("date"); timeLabel->setTextFormat(Qt::PlainText); dayLabel->setTextFormat(Qt::PlainText); dateLabel->setTextFormat(Qt::PlainText); timeLabel->setAlignment(Qt::AlignCenter); dayLabel->setAlignment(Qt::AlignCenter); dateLabel->setAlignment(Qt::AlignCenter); QFont f = font(); f.setPointSizeF(fontInfo().pointSizeF() * 1.5); f.setBold(true); timeLabel->setFont(f); fill(); timeLabel->setVisible(!timeFormat.isEmpty()); dayLabel->setVisible(!dayFormat.isEmpty()); dateLabel->setVisible(!dateFormat.isEmpty()); // context menu QAction *action = new QAction(this); action->setIcon(QIcon::fromTheme("configure")); action->setText(i18n("Select Timezones...")); addAction(action); connect(action, &QAction::triggered, [this]() { ClockWidgetConfigureDialog dialog(parentWidget(), timeZoneIds); dialog.setWindowTitle(i18n("Select Timezones")); dialog.resize(600, 400); if ( dialog.exec() == QDialog::Accepted ) { timeZoneIds = dialog.getSelectedTimeZoneIds(); KConfig config; KConfigGroup group = config.group("ClockWidget"); QStringList list; for (const QByteArray &id : timeZoneIds) list.append(id); group.writeEntry("timeZoneIds", list); tick(); // update tooltip } } ); action = new QAction(this); action->setIcon(QIcon::fromTheme("preferences-system-time")); action->setText(i18n("Configure Date & Time...")); addAction(action); connect(action, &QAction::triggered, [this]() { auto dialog = new KCMultiDialog(parentWidget()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Date & Time")); dialog->addModule("clock"); dialog->adjustSize(); dialog->show(); } ); setContextMenuPolicy(Qt::ActionsContextMenu); // load config KConfig config; KConfigGroup group = config.group("ClockWidget"); QStringList list; list = group.readEntry("timeZoneIds", QStringList()); for (const QString &id : list) timeZoneIds.append(id.toLatin1()); tick(); timeLabel->setFixedHeight(timeLabel->fontMetrics().tightBoundingRect(timeLabel->text()).height() + 2); dayLabel->setFixedHeight(dayLabel->fontMetrics().tightBoundingRect(dayLabel->text()).height() + 4); dateLabel->setFixedHeight(dateLabel->fontMetrics().tightBoundingRect(dateLabel->text()).height() + 4); } //-------------------------------------------------------------------------------- void ClockWidget::fill() { delete layout(); const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); QBoxLayout *box; if ( MAX_ROWS >= 2 ) { box = new QVBoxLayout(this); box->setSpacing(0); } else { box = new QHBoxLayout(this); } box->setContentsMargins(QMargins()); box->addWidget(timeLabel); box->addWidget(dayLabel); box->addWidget(dateLabel); } //-------------------------------------------------------------------------------- void ClockWidget::tick() { QDateTime dateTimeUtc = QDateTime::currentDateTimeUtc(); QDateTime dateTime = dateTimeUtc.toLocalTime(); timeLabel->setText(dateTime.time().toString(timeFormat)); dayLabel->setText(dateTime.date().toString(dayFormat)); dateLabel->setText(dateTime.date().toString(dateFormat)); if ( !timeZoneIds.isEmpty() ) { QString tip = ""; for (const QByteArray &id : timeZoneIds) { QTimeZone timeZone(id); QDateTime dt = dateTimeUtc; dateTime = dt.toTimeZone(timeZone); tip += QString("") .arg(QLatin1String(id)) .arg(dateTime.time().toString(timeFormat)) .arg(dateTime.date().toString(dayFormat)) .arg(dateTime.date().toString(dateFormat)); } tip += "
%1 %2 %3 %4
"; setToolTip(tip); } } //-------------------------------------------------------------------------------- void ClockWidget::mousePressEvent(QMouseEvent *event) { if ( event->button() == Qt::LeftButton ) { if ( !calendar ) calendar = new CalendarPopup(this); calendar->goToday(); QPoint point = mapToGlobal(pos()); - QRect screen = QApplication::desktop()->availableGeometry(this); + QRect screen = QApplication::primaryScreen()->availableGeometry(); point.setX(std::min(point.x(), screen.x() + screen.width() - calendar->sizeHint().width())); point.setY(point.y() - calendar->sizeHint().height()); calendar->move(point); calendar->show(); } } //-------------------------------------------------------------------------------- diff --git a/ConfigureDesktopDialog.cxx b/ConfigureDesktopDialog.cxx index 7924dfe..fca1603 100644 --- a/ConfigureDesktopDialog.cxx +++ b/ConfigureDesktopDialog.cxx @@ -1,178 +1,178 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include -#include +#include #include #include //-------------------------------------------------------------------------------- ConfigureDesktopDialog::ConfigureDesktopDialog(QWidget *parent, const DesktopWidget::Wallpaper &wp) : QDialog(parent), wallpaper(wp) { ui.setupUi(this); ui.iconView->setIconSize(QSize(200, 200)); connect(ui.iconView, &QListWidget::itemClicked, [this](QListWidgetItem *item) { ui.kurlrequester->setUrl(QUrl::fromLocalFile(item->data(Qt::UserRole).toString())); wallpaper.fileName = ui.kurlrequester->url().toLocalFile(); emit changed(); }); showImages(); QPushButton *newstuff = ui.buttonBox->addButton(i18n("Get New Wallpapers..."), QDialogButtonBox::ActionRole); newstuff->setIcon(QIcon::fromTheme("get-hot-new-stuff")); connect(newstuff, &QPushButton::clicked, [this]() { KNS3::DownloadDialog dialog("wallpaper.knsrc", this); dialog.setTitle(i18n("Download Wallpapers")); dialog.exec(); if ( dialog.changedEntries().count() ) showImages(); }); ui.kcolorcombo->setColor(wallpaper.color); ui.kurlrequester->setUrl(QUrl::fromLocalFile(wallpaper.fileName)); connect(ui.kcolorcombo, &KColorCombo::activated, [this](const QColor &col) { wallpaper.color = col; emit changed(); }); connect(ui.kurlrequester, &KUrlRequester::urlSelected, [this](const QUrl &url) { wallpaper.fileName = url.toLocalFile(); emit changed(); }); // older compiler can't use this //connect(ui.kurlrequester, QOverload::of(&KUrlRequester::returnPressed), // [this](const QString &text) { wallpaper.fileName = text; emit changed(); }); connect(ui.kurlrequester, SIGNAL(returnPressed(QString)), this, SLOT(returnPressed(QString))); if ( wallpaper.mode == "Scaled" ) ui.scaledIgnoreRatioButton->setChecked(true); else if ( wallpaper.mode == "ScaledKeepRatio" ) ui.scaledKeepRatioButton->setChecked(true); else if ( wallpaper.mode == "ScaledKeepRatioExpand" ) ui.scaledKeepRatioClipButton->setChecked(true); else ui.origSizeButton->setChecked(true); buttonGroup.addButton(ui.origSizeButton); buttonGroup.addButton(ui.scaledIgnoreRatioButton); buttonGroup.addButton(ui.scaledKeepRatioButton); buttonGroup.addButton(ui.scaledKeepRatioClipButton); connect(&buttonGroup, SIGNAL(buttonClicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *))); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::returnPressed(const QString &text) { wallpaper.fileName = text; emit changed(); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::buttonClicked(QAbstractButton *button) { if ( button == ui.origSizeButton ) wallpaper.mode = ""; else if ( button == ui.scaledIgnoreRatioButton ) wallpaper.mode = "Scaled"; else if ( button == ui.scaledKeepRatioButton ) wallpaper.mode = "ScaledKeepRatio"; else if ( button == ui.scaledKeepRatioClipButton ) wallpaper.mode = "ScaledKeepRatioExpand"; emit changed(); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::showImages() { // create filter (list of patterns) for image files we can read QMimeDatabase db; QStringList filterList; foreach (const QByteArray &type, QImageReader::supportedMimeTypes()) { QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type))); if ( mime.isValid() ) { foreach (const QString &pattern, mime.globPatterns()) filterList << pattern; } } ui.iconView->clear(); QStringList dirNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "wallpapers", QStandardPaths::LocateDirectory); const QString geometryString = QString("%1x%2") - .arg(QApplication::desktop()->width()) - .arg(QApplication::desktop()->height()); + .arg(QApplication::primaryScreen()->size().width()) + .arg(QApplication::primaryScreen()->size().height()); for (const QString &dirName : dirNames) { // check for file directly in this folder QStringList fileNames = QDir(dirName).entryList(filterList, QDir::Files | QDir::Readable); for (const QString &fileName : fileNames) { QPixmap pixmap(dirName + '/' + fileName); QListWidgetItem *item = new QListWidgetItem(pixmap, fileName, ui.iconView); item->setData(Qt::UserRole, dirName + '/' + fileName); item->setToolTip(QString("%1 (%2x%3)").arg(fileName).arg(pixmap.width()).arg(pixmap.height())); } // check for files in the special subdirs for (const QString &subdir : QDir(dirName).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)) { QDir dir(dirName + '/' + subdir + "/contents/images"); QString chosenFilePath; for (const QString &fileName : dir.entryList(filterList, QDir::Files | QDir::Readable)) { chosenFilePath = dir.absoluteFilePath(fileName); if ( fileName.startsWith(geometryString) ) break; // just take one } if ( !chosenFilePath.isEmpty() ) { QPixmap pixmap(chosenFilePath); QListWidgetItem *item = new QListWidgetItem(pixmap, subdir, ui.iconView); item->setData(Qt::UserRole, chosenFilePath); item->setToolTip(QString("%1 (%2x%3)").arg(subdir).arg(pixmap.width()).arg(pixmap.height())); } } } } //-------------------------------------------------------------------------------- diff --git a/DesktopWidget.cxx b/DesktopWidget.cxx index 4a1d6c0..d5e8d97 100644 --- a/DesktopWidget.cxx +++ b/DesktopWidget.cxx @@ -1,396 +1,399 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017, 2018 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- int DesktopWidget::appletNum = 0; //-------------------------------------------------------------------------------- DesktopWidget::DesktopWidget() : QWidget(nullptr, Qt::WindowDoesNotAcceptFocus) { setAttribute(Qt::WA_AlwaysShowToolTips); setAutoFillBackground(true); - setFixedSize(QApplication::desktop()->size()); + setFixedSize(QApplication::primaryScreen()->virtualSize()); setWindowIcon(QIcon::fromTheme("liquidshell")); KWindowSystem::setType(winId(), NET::Desktop); panel = new DesktopPanel(nullptr); panel->show(); connect(panel, &DesktopPanel::resized, this, &DesktopWidget::placePanel); placePanel(); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &DesktopWidget::desktopChanged); connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, this, &DesktopWidget::loadSettings); setContextMenuPolicy(Qt::ActionsContextMenu); QAction *action = new QAction(QIcon::fromTheme("configure"), i18n("Configure Wallpaper..."), this); connect(action, &QAction::triggered, this, &DesktopWidget::configureWallpaper); addAction(action); action = new QAction(i18n("Add Applet"), this); addAction(action); QMenu *menu = new QMenu; action->setMenu(menu); action = menu->addAction(QIcon::fromTheme("weather-clouds"), i18n("Weather")); connect(action, &QAction::triggered, [this]() { addApplet("Weather"); }); action = menu->addAction(QIcon::fromTheme("drive-harddisk"), i18n("Disk Usage")); connect(action, &QAction::triggered, [this]() { addApplet("DiskUsage"); }); action = menu->addAction(QIcon::fromTheme("image-x-generix"), i18n("Picture Frame")); connect(action, &QAction::triggered, [this]() { addApplet("PictureFrame"); }); action = new QAction(QIcon::fromTheme("preferences-desktop-display"), i18n("Configure Display..."), this); connect(action, &QAction::triggered, this, &DesktopWidget::configureDisplay); addAction(action); action = new QAction(QIcon::fromTheme("system-run"), i18n("Run Command..."), this); connect(action, &QAction::triggered, []() { QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.kde.krunner", "/App", "org.kde.krunner.App", "display")); }); addAction(action); action = new QAction(i18n("Help"), this); KHelpMenu *helpMenu = new KHelpMenu(this); helpMenu->action(KHelpMenu::menuHelpContents)->setVisible(false); // no handbook helpMenu->action(KHelpMenu::menuWhatsThis)->setVisible(false); action->setMenu(helpMenu->menu()); addAction(action); // restore applets KConfig config; KConfigGroup group = config.group("DesktopApplets"); QStringList appletNames = group.readEntry("applets", QStringList()); for (const QString &appletName : appletNames) { DesktopApplet *applet = nullptr; if ( appletName.startsWith("Weather_") ) applet = new WeatherApplet(this, appletName); else if ( appletName.startsWith("DiskUsage_") ) applet = new DiskUsageApplet(this, appletName); else if ( appletName.startsWith("PictureFrame_") ) applet = new PictureFrameApplet(this, appletName); if ( applet ) { int num = appletName.mid(appletName.indexOf('_') + 1).toInt(); if ( num > appletNum ) appletNum = num; applet->loadConfig(); applets.append(applet); connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet); } } loadSettings(); - connect(QApplication::desktop(), &QDesktopWidget::primaryScreenChanged, this, &DesktopWidget::placePanel); + connect(qApp, &QApplication::primaryScreenChanged, this, &DesktopWidget::placePanel); - connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, - [this]() { setFixedSize(QApplication::desktop()->size()); desktopChanged(); }); + connect(qApp, &QApplication::screenAdded, + [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); desktopChanged(); }); - connect(QApplication::desktop(), &QDesktopWidget::resized, - [this]() { setFixedSize(QApplication::desktop()->size()); placePanel(); desktopChanged(); }); + connect(qApp, &QApplication::screenRemoved, + [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); desktopChanged(); }); + + connect(QApplication::primaryScreen(), &QScreen::virtualGeometryChanged, + [this]() { setFixedSize(QApplication::primaryScreen()->virtualSize()); placePanel(); desktopChanged(); }); new OnScreenVolume(this); } //-------------------------------------------------------------------------------- void DesktopWidget::loadSettings() { // simple check for default files matching current desktop size QStringList dirNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "wallpapers", QStandardPaths::LocateDirectory); QStringList defaultFiles; const QString geometryString = QString("%1x%2").arg(width()).arg(height()); for (const QString &dirName : dirNames) { for (const QString &subdir : QDir(dirName).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)) { QDir dir(dirName + '/' + subdir + "/contents/images"); for (const QString &fileName : dir.entryList(QDir::Files | QDir::Readable)) { if ( fileName.startsWith(geometryString) ) defaultFiles.append(dir.absoluteFilePath(fileName)); } } } KConfig config; for (int i = wallpapers.count() + 1; i <= KWindowSystem::numberOfDesktops(); i++) { KConfigGroup group = config.group(QString("Desktop_%1").arg(i)); Wallpaper wallpaper; wallpaper.color = group.readEntry("color", QColor(Qt::black)); wallpaper.mode = group.readEntry("wallpaperMode", QString()); int idx = defaultFiles.count() ? ((i - 1) % defaultFiles.count()) : -1; wallpaper.fileName = group.readEntry("wallpaper", (idx != -1) ? defaultFiles[idx] : QString()); wallpapers.append(wallpaper); } wallpapers.resize(KWindowSystem::numberOfDesktops()); // if we had more before, reduce size wallpapers.squeeze(); desktopChanged(); } //-------------------------------------------------------------------------------- void DesktopWidget::configureWallpaper() { if ( dialog ) // already open, do nothing return; bool showingDesktop = KWindowSystem::showingDesktop(); KWindowSystem::setShowingDesktop(true); int desktopNum = currentDesktop; Wallpaper origWallpaper = wallpapers[desktopNum]; dialog = new ConfigureDesktopDialog(nullptr, wallpapers[desktopNum]); connect(dialog, &ConfigureDesktopDialog::changed, [=]() { wallpapers[desktopNum] = dialog->getWallpaper(); desktopChanged(); }); connect(dialog, &QDialog::finished, [=](int result) { if ( result == QDialog::Rejected ) { wallpapers[desktopNum] = origWallpaper; desktopChanged(); } else // store in config file { const Wallpaper &wallpaper = wallpapers[desktopNum]; KConfig config; KConfigGroup group = config.group(QString("Desktop_%1").arg(desktopNum + 1)); group.writeEntry("color", wallpaper.color); group.writeEntry("wallpaper", wallpaper.fileName); group.writeEntry("wallpaperMode", wallpaper.mode); } KWindowSystem::setShowingDesktop(showingDesktop); // restore dialog->deleteLater(); dialog = nullptr; }); dialog->show(); // not modal } //-------------------------------------------------------------------------------- void DesktopWidget::configureDisplay() { KCMultiDialog *dialog = new KCMultiDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->addModule("kcm_kscreen"); dialog->adjustSize(); dialog->setWindowTitle(i18n("Configure Display")); dialog->show(); // not modal } //-------------------------------------------------------------------------------- void DesktopWidget::placePanel() { int panelHeight = qMin(panel->sizeHint().height(), panel->height()); - QRect r = QApplication::desktop()->screenGeometry(); + QRect r = QApplication::primaryScreen()->geometry(); panel->setGeometry(r.x(), r.y() + r.height() - panelHeight, r.width(), panelHeight); KWindowSystem::setStrut(panel->winId(), 0, 0, 0, panelHeight); } //-------------------------------------------------------------------------------- void DesktopWidget::desktopChanged() { // free memory in previous shown desktop if ( (currentDesktop >= 0) && (currentDesktop < wallpapers.count()) ) wallpapers[currentDesktop].pixmaps.clear(); currentDesktop = KWindowSystem::currentDesktop() - 1; // num to index if ( currentDesktop >= wallpapers.count() ) return; Wallpaper &wallpaper = wallpapers[currentDesktop]; if ( wallpaper.color.isValid() ) { QPalette pal = palette(); pal.setColor(backgroundRole(), wallpaper.color); setPalette(pal); } if ( !wallpaper.fileName.isEmpty() ) { QPixmap pixmap(wallpaper.fileName); if ( !pixmap.isNull() ) { - for (int i = 0; i < QApplication::desktop()->screenCount(); i++) + for (int i = 0; i < QApplication::screens().count(); i++) { - QRect r = QApplication::desktop()->screenGeometry(i); + QRect r = QApplication::screens().at(i)->geometry(); QPixmap scaledPixmap = pixmap; if ( wallpaper.mode == "Scaled" ) scaledPixmap = pixmap.scaled(r.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); else if ( wallpaper.mode == "ScaledKeepRatio" ) scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); else if ( wallpaper.mode == "ScaledKeepRatioExpand" ) scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); wallpaper.pixmaps.append(scaledPixmap); } } } update(); // show applets for new desktop for (DesktopApplet *applet : applets) { if ( !applet->isConfiguring() ) applet->setVisible(applet->isOnDesktop(currentDesktop + 1)); } } //-------------------------------------------------------------------------------- void DesktopWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event) if ( currentDesktop < wallpapers.count() ) { Wallpaper &wallpaper = wallpapers[currentDesktop]; QPainter painter(this); - for (int i = 0; i < QApplication::desktop()->screenCount(); i++) + for (int i = 0; i < QApplication::screens().count(); i++) { if ( i < wallpaper.pixmaps.count() ) { - QRect r = QApplication::desktop()->screenGeometry(i); + QRect r = QApplication::screens().at(i)->geometry(); painter.setClipRect(r); painter.drawPixmap(r.x() + (r.width() - wallpaper.pixmaps[i].width()) / 2, r.y() + (r.height() - wallpaper.pixmaps[i].height()) / 2, wallpaper.pixmaps[i]); } } } } //-------------------------------------------------------------------------------- void DesktopWidget::addApplet(const QString &type) { DesktopApplet *applet = nullptr; if ( type == "Weather" ) { applet = new WeatherApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } else if ( type == "DiskUsage" ) { applet = new DiskUsageApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } else if ( type == "PictureFrame" ) { applet = new PictureFrameApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } if ( applet ) { applets.append(applet); applet->loadConfig(); // defaults + size applet->move(QCursor::pos()); applet->saveConfig(); applet->show(); connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet); } saveAppletsList(); } //-------------------------------------------------------------------------------- void DesktopWidget::removeApplet(DesktopApplet *applet) { applets.removeOne(applet); applet->deleteLater(); saveAppletsList(); } //-------------------------------------------------------------------------------- void DesktopWidget::saveAppletsList() { KConfig config; KConfigGroup group = config.group("DesktopApplets"); QStringList appletNames; for (DesktopApplet *applet : applets) appletNames.append(applet->getId()); group.writeEntry("applets", appletNames); } //-------------------------------------------------------------------------------- diff --git a/DesktopWidget.hxx b/DesktopWidget.hxx index 84cf505..8587dc4 100644 --- a/DesktopWidget.hxx +++ b/DesktopWidget.hxx @@ -1,71 +1,71 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #ifndef DESKTOP_WIDGET_H #define DESKTOP_WIDGET_H #include #include #include class DesktopPanel; class DesktopApplet; class ConfigureDesktopDialog; class DesktopWidget : public QWidget { Q_OBJECT public: DesktopWidget(); struct Wallpaper { QString fileName, mode; QColor color; QVector pixmaps; // per screen }; protected: - virtual void paintEvent(QPaintEvent *event); + void paintEvent(QPaintEvent *event) override; private Q_SLOTS: void loadSettings(); void placePanel(); void desktopChanged(); void configureWallpaper(); void configureDisplay(); void addApplet(const QString &type); void removeApplet(DesktopApplet *applet); private: void saveAppletsList(); private: DesktopPanel *panel = nullptr; ConfigureDesktopDialog *dialog = nullptr; QVector wallpapers; int currentDesktop = -1; QVector applets; static int appletNum; }; #endif diff --git a/NotificationList.cxx b/NotificationList.cxx index fb66cf7..828660b 100644 --- a/NotificationList.cxx +++ b/NotificationList.cxx @@ -1,332 +1,332 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017,2018 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include -#include +#include #include #include #include #include #include #include static const Qt::WindowFlags POPUP_FLAGS = Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint; //-------------------------------------------------------------------------------- NotifyItem::NotifyItem(QWidget *parent, uint theId, const QString &app, const QString &theSummary, const QString &theBody, const QIcon &icon, const QStringList &theActions) : QFrame(parent, POPUP_FLAGS), id(theId), appName(app), summary(theSummary), body(theBody), actions(theActions) { setAttribute(Qt::WA_ShowWithoutActivating); // avoid focus stealing setFrameShape(QFrame::StyledPanel); QMargins margins = contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click setContentsMargins(margins); QVBoxLayout *vbox = new QVBoxLayout; timeLabel = new QLabel; iconLabel = new QLabel; vbox->addWidget(timeLabel, 0, Qt::AlignTop | Qt::AlignHCenter); vbox->addWidget(iconLabel, 0, Qt::AlignTop | Qt::AlignHCenter); QVBoxLayout *centerBox = new QVBoxLayout; QHBoxLayout *hbox = new QHBoxLayout(this); margins = hbox->contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click hbox->setContentsMargins(margins); textLabel = new QLabel; QToolButton *closeButton = new QToolButton; // easier to click with larger size closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); closeButton->setMinimumWidth(40); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme("window-close")); connect(closeButton, &QToolButton::clicked, this, &NotifyItem::deleteLater); textLabel->setWordWrap(true); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); textLabel->setOpenExternalLinks(true); textLabel->setMinimumWidth(300); textLabel->setMaximumWidth(600); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); centerBox->addWidget(textLabel); if ( actions.count() ) { QHBoxLayout *actionBox = new QHBoxLayout; centerBox->addLayout(actionBox); for (int i = 0; i < actions.count(); i++) { if ( ((i % 2) != 0) && !actions[i].isEmpty() ) // id { QPushButton *button = new QPushButton; button->setText(actions[i]); actionBox->addWidget(button); QString key = actions[i - 1]; connect(button, &QPushButton::clicked, this, [this, key]() { QDBusMessage msg = QDBusMessage::createSignal("/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked"); msg << id << key; QDBusConnection::sessionBus().send(msg); }); } } } hbox->addLayout(vbox); hbox->addLayout(centerBox); hbox->addWidget(closeButton); iconLabel->setFixedSize(32, 32); iconLabel->setPixmap(icon.pixmap(iconLabel->size())); QString title = (appName == summary) ? appName : (appName + ": " + summary); textLabel->setText(QString("
%1

%2") .arg(title) .arg(body)); timeLabel->setText(QTime::currentTime().toString(Qt::SystemLocaleShortDate)); } //-------------------------------------------------------------------------------- void NotifyItem::destroySysResources() { destroy(); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- NotificationList::NotificationList(QWidget *parent) : QWidget(parent) { setWindowFlags(windowFlags() | Qt::Tool); setWindowTitle(i18n("Notifications")); scrollArea = new QScrollArea; scrollArea->setWidgetResizable(true); QWidget *listWidget = new QWidget; listVbox = new QVBoxLayout(listWidget); listVbox->setContentsMargins(QMargins()); listVbox->addStretch(); scrollArea->setWidget(listWidget); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); vbox->addWidget(scrollArea); QPushButton *clearButton = new QPushButton; clearButton->setIcon(QIcon::fromTheme("edit-clear-list")); connect(clearButton, &QPushButton::clicked, this, [this]() { for (NotifyItem *item : items) item->deleteLater(); }); vbox->addWidget(clearButton); resize(500, 300); } //-------------------------------------------------------------------------------- NotificationList::~NotificationList() { for (NotifyItem *item : items) item->disconnect(); // make sure destroyed() is no longer triggered } //-------------------------------------------------------------------------------- void NotificationList::addItem(uint id, const QString &appName, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions, const QVariantMap &hints, int timeout) { QPointer item = new NotifyItem(nullptr, id, appName, summary, body, icon, actions); item->resize(500, item->sizeHint().height()); KWindowSystem::setState(item->winId(), NET::SkipTaskbar | NET::SkipPager); items.append(item.data()); placeItems(); int wordCount = body.splitRef(' ', QString::SkipEmptyParts).count(); bool neverExpires = timeout == 0; // according to spec if ( timeout <= 0 ) { timeout = 4000 + 250 * wordCount; if ( actions.count() ) timeout += 15000; // give user more time to think ... } // 0=low, 1=normal, 2=critical if ( hints.contains("urgency") && (hints["urgency"].toInt() == 2) ) { neverExpires = true; timeout = 20000; // just show it longer since its urgent } bool transient = hints.contains("transient") ? hints["transient"].toBool() : false; // if there are actions, show it longer if ( actions.count() || neverExpires ) transient = false; // exceptions ... if ( transient && hints.contains("x-kde-eventId") ) { // I'd like to keep this much longer if ( hints["x-kde-eventId"].toString() == "new-email" ) transient = false; } // I often get the same notification multiple times (e.g. KDE-connect or EWS akonadi resource) // but I don't want to fill up the screen with useless duplicate information. Therefore // whenever the same notification is received while another instance of it is still visible, // only put it in the list-view (it has a different id, therefore still store it), but don't show it as popup for (NotifyItem *it : items) { if ( (it != item) && // not the new item already in items !it->parentWidget() && // temporary item not in the list-view yet (appName == it->appName) && (summary == it->summary) && (body == it->body) && (actions == it->actions) ) { timeout = 0; } } numItems++; emit itemsCountChanged(); connect(item.data(), &NotifyItem::destroyed, this, [this](QObject *obj) { items.removeOne(static_cast(obj)); placeItems(); if ( --numItems == 0 ) { hide(); emit listNowEmpty(); } else emit itemsCountChanged(); } ); QTimer::singleShot(timeout, [=]() { if ( item ) { // sometimes there were some leftover/half-functioning windows. This seems to avoid that item->destroySysResources(); listVbox->insertWidget(listVbox->count() - 1, item); // insert before stretch item->show(); placeItems(); // reorder the remaining ones scrollArea->setWidgetResizable(true); // to update scrollbars, else next line does not work scrollArea->ensureWidgetVisible(item); if ( !neverExpires ) { if ( !appTimeouts.contains(appName) ) appTimeouts.insert(appName, 10); // default: 10 minutes lifetime int expireTimeout; if ( transient ) expireTimeout = 2 * 60 * 1000; // still keep it a short time else expireTimeout = appTimeouts[appName] * 60 * 1000; QTimer::singleShot(expireTimeout, item.data(), &NotifyItem::deleteLater); } } } ); } //-------------------------------------------------------------------------------- void NotificationList::closeItem(uint id) { for (NotifyItem *item : items) { if ( item->id == id ) { item->deleteLater(); break; } } } //-------------------------------------------------------------------------------- void NotificationList::placeItems() { - QRect screen = QApplication::desktop()->availableGeometry(this); + QRect screen = QApplication::primaryScreen()->availableGeometry(); QPoint point = parentWidget()->mapToGlobal(parentWidget()->pos()); int x = point.x(); int y = screen.bottom(); for (NotifyItem *item : items) { if ( !item->parentWidget() ) // temporary item not in the list yet { y -= item->sizeHint().height(); y -= 5; // a small space point.setX(std::min(x, screen.x() + screen.width() - item->sizeHint().width())); point.setY(y); item->move(point); item->show(); } } } //-------------------------------------------------------------------------------- diff --git a/OnScreenVolume.cxx b/OnScreenVolume.cxx index f0f78e4..43ff959 100644 --- a/OnScreenVolume.cxx +++ b/OnScreenVolume.cxx @@ -1,175 +1,175 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include -#include +#include #include #include #include #include //-------------------------------------------------------------------------------- OnScreenVolume::OnScreenVolume(QWidget *parent) : QProgressBar(parent) { setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); setFixedSize(400, 40); hide(); KWindowSystem::setState(winId(), NET::KeepAbove); KWindowSystem::setType(winId(), NET::Dock); KWindowSystem::setOnAllDesktops(winId(), true); hideTimer.setInterval(1000); hideTimer.setSingleShot(true); connect(&hideTimer, &QTimer::timeout, this, &QWidget::hide); QDBusConnection::sessionBus() .connect("org.kde.kmix", "/Mixers", "org.kde.KMix.MixSet", "masterChanged", this, SLOT(getMasterMixer())); if ( !QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kmix") ) { QDBusServiceWatcher *w = new QDBusServiceWatcher("org.kde.kmix", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(w, &QDBusServiceWatcher::serviceRegistered, this, &OnScreenVolume::getMasterMixer); } else getMasterMixer(); } //-------------------------------------------------------------------------------- void OnScreenVolume::getMasterMixer() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kmix", "/Mixers", "org.freedesktop.DBus.Properties", "GetAll"); msg << "org.kde.KMix.MixSet"; QDBusConnection::sessionBus() .callWithCallback(msg, this, SLOT(gotMasterMixer(QDBusMessage)), SLOT(gotMasterMixerError(QDBusError, QDBusMessage))); } //-------------------------------------------------------------------------------- void OnScreenVolume::gotMasterMixerError(QDBusError error, QDBusMessage msg) { Q_UNUSED(error) Q_UNUSED(msg) if ( retryTimer.interval() == 0 ) { // the service could already be registered but no object yet retryTimer.setInterval(1000); retryTimer.setSingleShot(true); connect(&retryTimer, &QTimer::timeout, this, &OnScreenVolume::getMasterMixer); } retryTimer.start(); } //-------------------------------------------------------------------------------- void OnScreenVolume::gotMasterMixer(QDBusMessage msg) { QDBusReply reply = msg; if ( !reply.isValid() ) return; if ( !masterMixer.isEmpty() ) { // disconnect previous master mixer QDBusConnection::sessionBus() .disconnect("org.kde.kmix", "/Mixers/" + masterMixer, "org.kde.KMix.Mixer", "controlChanged", this, SLOT(controlChanged())); } masterMixer = reply.value()["currentMasterMixer"].toString(); masterMixer.replace(':', '_'); masterControl = reply.value()["currentMasterControl"].toString(); masterControl.replace('.', '_'); masterControl.replace('-', '_'); //qDebug() << masterMixer << masterControl; QDBusConnection::sessionBus() .connect("org.kde.kmix", "/Mixers/" + masterMixer, "org.kde.KMix.Mixer", "controlChanged", this, SLOT(controlChanged())); } //-------------------------------------------------------------------------------- void OnScreenVolume::controlChanged() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kmix", "/Mixers/" + masterMixer + '/' + masterControl, "org.freedesktop.DBus.Properties", "Get"); msg << "org.kde.KMix.Control" << "volume"; QDBusConnection::sessionBus().callWithCallback(msg, this, SLOT(volumeChanged(QDBusMessage))); } //-------------------------------------------------------------------------------- void OnScreenVolume::volumeChanged(QDBusMessage reply) { if ( reply.type() == QDBusMessage::ErrorMessage ) return; if ( reply.arguments().count() && (reply.arguments()[0].value().variant().toInt() != value()) ) { setValue(reply.arguments()[0].value().variant().toInt()); if ( !isVisible() ) // just always once before showing { KConfig config("kmixrc"); KConfigGroup group = config.group("Global"); if ( !group.readEntry("showOSD", true) ) return; } - move((QApplication::desktop()->width() - width()) / 2, - QApplication::desktop()->height() * 0.8); + move((QApplication::primaryScreen()->size().width() - width()) / 2, + QApplication::primaryScreen()->size().height() * 0.8); show(); hideTimer.start(); } } //-------------------------------------------------------------------------------- diff --git a/SysTrayItem.cxx b/SysTrayItem.cxx index 41674ec..1b526c1 100644 --- a/SysTrayItem.cxx +++ b/SysTrayItem.cxx @@ -1,90 +1,90 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* - Copyright 2017 Martin Koller, kollix@aon.at + Copyright 2017 - 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include -#include +#include #include #include #include //-------------------------------------------------------------------------------- SysTrayItem::SysTrayItem(QWidget *parent, const QString &icon) : QLabel(parent), iconName(icon) { setFixedSize(QSize(22, 22)); if ( !iconName.isEmpty() ) { setPixmap(QIcon::fromTheme(iconName).pixmap(size())); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, [this]() { setPixmap(QIcon::fromTheme(iconName).pixmap(size())); }); } } //-------------------------------------------------------------------------------- void SysTrayItem::mousePressEvent(QMouseEvent *event) { if ( event->button() != Qt::LeftButton ) return; toggleDetailsList(); } //-------------------------------------------------------------------------------- void SysTrayItem::showDetailsList() { QWidget *detailsList = getDetailsList(); if ( !detailsList ) return; QPoint point = mapToGlobal(pos()); - QRect screen = QApplication::desktop()->availableGeometry(this); - point.setX(std::min(point.x(), screen.x() + screen.width() - detailsList->size().width())); - point.setY(screen.bottom() - detailsList->size().height()); + QRect screen = QApplication::primaryScreen()->availableGeometry(); + point.setX(std::min(point.x(), screen.x() + screen.width() - detailsList->width())); + point.setY(screen.bottom() - detailsList->height()); detailsList->move(point); detailsList->show(); KWindowSystem::raiseWindow(detailsList->winId()); } //-------------------------------------------------------------------------------- void SysTrayItem::toggleDetailsList() { QWidget *detailsList = getDetailsList(); if ( !detailsList ) return; if ( detailsList->isVisible() ) detailsList->close(); else showDetailsList(); } //-------------------------------------------------------------------------------- diff --git a/WeatherApplet.cxx b/WeatherApplet.cxx index f59f100..5d9eacb 100644 --- a/WeatherApplet.cxx +++ b/WeatherApplet.cxx @@ -1,401 +1,401 @@ // SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. liquidshell is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- QString WeatherApplet::apiKey; //-------------------------------------------------------------------------------- WeatherApplet::WeatherApplet(QWidget *parent, const QString &theId) : DesktopApplet(parent, theId) { setAutoFillBackground(true); timer.setInterval(600000); // 10min smallest update interval for free data connect(&timer, &QTimer::timeout, this, &WeatherApplet::fetchData); QVBoxLayout *vbox = new QVBoxLayout(this); cityLabel = new QLabel(this); cityLabel->setObjectName("city"); cityLabel->setWordWrap(true); QFont f = font(); f.setPointSizeF(fontInfo().pointSizeF() * 2); f.setBold(true); cityLabel->setFont(f); vbox->addWidget(cityLabel); QGridLayout *grid = new QGridLayout; vbox->addLayout(grid); grid->addWidget(new QLabel(i18n("Temperature:"), this), 0, 0); grid->addWidget(tempLabel = new QLabel, 0, 1); grid->addWidget(new QLabel(i18n("Pressure:"), this), 1, 0); grid->addWidget(pressureLabel = new QLabel, 1, 1); grid->addWidget(new QLabel(i18n("Humidity:"), this), 2, 0); grid->addWidget(humidityLabel = new QLabel, 2, 1); grid->addWidget(new QLabel(i18n("Wind Speed:"), this), 3, 0); grid->addWidget(windSpeedLabel = new QLabel, 3, 1); grid->addWidget(new QLabel(i18n("Wind Direction:"), this), 4, 0); grid->addWidget(windDirectionLabel = new QLabel, 4, 1); for (int i = 0; i < 4; i++) { shortForecast[i] = new ForecastWidget(this, false); grid->addWidget(shortForecast[i], 0, 2 + i, 5, 1, Qt::AlignCenter); } QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout(hbox); for (int i = 0; i < 5; i++) { forecast[i] = new ForecastWidget(this); hbox->addWidget(forecast[i]); if ( i < 4 ) hbox->addStretch(); } connect(NetworkManager::notifier(), &NetworkManager::Notifier::connectivityChanged, this, [this](NetworkManager::Connectivity connectivity) { if ( connectivity == NetworkManager::Full ) fetchData(); }); } //-------------------------------------------------------------------------------- QSize WeatherApplet::sizeHint() const { return QSize(700, 300); } //-------------------------------------------------------------------------------- void WeatherApplet::loadConfig() { KConfig config; KConfigGroup group = config.group("Weather"); apiKey = group.readEntry("apiKey", QString()); group = config.group(id); cityId = group.readEntry("cityId", QString()); units = group.readEntry("units", QString("metric")); DesktopApplet::loadConfig(); if ( apiKey.isEmpty() || cityId.isEmpty() ) cityLabel->setText(i18n("Not configured")); } //-------------------------------------------------------------------------------- void WeatherApplet::showEvent(QShowEvent *) { // only query every 10 minutes, which is the limit for free data if ( !timer.isActive() ) { timer.start(); fetchData(); } } //-------------------------------------------------------------------------------- void WeatherApplet::fetchData() { if ( !isVisible() || apiKey.isEmpty() || cityId.isEmpty() ) return; QString url = QString("http://api.openweathermap.org/data/2.5/weather?APPID=%1&units=%2&id=%3") .arg(apiKey, units, cityId); KIO::StoredTransferJob *job = KIO::storedGet(QUrl(url), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, &WeatherApplet::gotData); url = QString("http://api.openweathermap.org/data/2.5/forecast?APPID=%1&units=%2&id=%3") .arg(apiKey, units, cityId); job = KIO::storedGet(QUrl(url), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, &WeatherApplet::gotData); } //-------------------------------------------------------------------------------- void WeatherApplet::gotData(KJob *job) { if ( job->error() ) { cityLabel->setText(job->errorString()); return; } QJsonDocument doc = QJsonDocument::fromJson(static_cast(job)->data()); if ( doc.isNull() || !doc.isObject() ) return; QString tempUnit = i18n("°K"); if ( units == "metric" ) tempUnit = i18n("°C"); else if ( units == "imperial" ) tempUnit = i18n("°F"); QJsonObject data = doc.object(); if ( data.contains("city") && data["city"].isObject() ) cityLabel->setText(data["city"].toObject()["name"].toString()); // current if ( data.contains("main") && data["main"].isObject() ) { QJsonObject mainData = data["main"].toObject(); double temp = mainData["temp"].toDouble(); tempLabel->setText(i18n("%1 %2", locale().toString(temp, 'f', 1), tempUnit)); double pressure = mainData["pressure"].toDouble(); pressureLabel->setText(i18n("%1 hPa", locale().toString(pressure, 'f', 1))); double humidity = mainData["humidity"].toDouble(); humidityLabel->setText(i18n("%1 %", locale().toString(humidity, 'f', 1))); } if ( data.contains("wind") && data["wind"].isObject() ) { QJsonObject windData = data["wind"].toObject(); QString speedUnit = "m/s"; if ( units == "imperial" ) speedUnit = "mi/h"; double speed = windData["speed"].toDouble(); windSpeedLabel->setText(i18n("%1 %2", locale().toString(speed, 'f', 0), speedUnit)); double deg = windData["deg"].toDouble(); windDirectionLabel->setText(i18n("%1 °", locale().toString(deg, 'f', 0))); } if ( data.contains("weather") && data["weather"].isArray() ) { QDateTime dt = QDateTime::fromMSecsSinceEpoch(qint64(data["dt"].toInt()) * 1000); shortForecast[0]->day->setText(dt.time().toString(Qt::SystemLocaleShortDate)); setIcon(shortForecast[0]->icon, data["weather"].toArray()[0].toObject()["icon"].toString()); } // forecast if ( data.contains("list") && data["list"].isArray() ) { for (int i = 0; i < 5; i++) forecast[i]->hide(); QJsonArray array = data["list"].toArray(); // 3 hours short forecast for (int i = 0; i < 3; i++) { setIcon(shortForecast[1 + i]->icon, array[i].toObject()["weather"].toArray()[0].toObject()["icon"].toString()); QDateTime dt = QDateTime::fromMSecsSinceEpoch(qint64(array[i].toObject()["dt"].toInt()) * 1000); shortForecast[1 + i]->day->setText(dt.time().toString(Qt::SystemLocaleShortDate)); shortForecast[1 + i]->show(); } QHash minTemp, maxTemp; // key = day for (QJsonValue value : array) { QJsonObject obj = value.toObject(); int day = QDateTime::fromMSecsSinceEpoch(qint64(obj["dt"].toInt()) * 1000).date().dayOfWeek(); double temp = obj["main"].toObject()["temp"].toDouble(); if ( !minTemp.contains(day) ) { minTemp.insert(day, temp); maxTemp.insert(day, temp); } else { if ( temp < minTemp[day] ) minTemp[day] = temp; if ( temp > maxTemp[day] ) maxTemp[day] = temp; } } int idx = 0; for (QJsonValue value : array) { QJsonObject obj = value.toObject(); if ( obj["dt_txt"].toString().contains("12:00") ) { QString icon = obj["weather"].toArray()[0].toObject()["icon"].toString(); setIcon(forecast[idx]->icon, icon); int day = QDateTime::fromMSecsSinceEpoch(qint64(obj["dt"].toInt()) * 1000).date().dayOfWeek(); - forecast[idx]->day->setText(QDate::shortDayName(day)); + forecast[idx]->day->setText(locale().dayName(day, QLocale::ShortFormat)); forecast[idx]->min->setText(i18n("%1 %2", locale().toString(minTemp[day], 'f', 1), tempUnit)); forecast[idx]->max->setText(i18n("%1 %2", locale().toString(maxTemp[day], 'f', 1), tempUnit)); forecast[idx]->show(); idx++; if ( idx == 5 ) break; } } } timer.start(); // after showEvent make sure to wait another full timeout phase } //-------------------------------------------------------------------------------- void WeatherApplet::setIcon(QLabel *label, const QString &icon) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/api.openweathermap.org/"; QDir dir; dir.mkpath(cacheDir); QString filePath = cacheDir + icon + ".png"; if ( QFile::exists(filePath) ) { QPixmap pixmap(filePath); if ( !pixmap.isNull() ) label->setPixmap(pixmap); } else { KIO::StoredTransferJob *job = KIO::storedGet(QUrl("http://api.openweathermap.org/img/w/" + icon), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [label, filePath](KJob *job) { if ( job->error() ) return; QPixmap pixmap; pixmap.loadFromData(static_cast(job)->data()); if ( !pixmap.isNull() ) { label->setPixmap(pixmap); pixmap.save(filePath, "PNG"); } }); } } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- ForecastWidget::ForecastWidget(QWidget *parent, bool showMinMax) : QWidget(parent) { QGridLayout *grid = new QGridLayout(this); if ( showMinMax ) { min = new QLabel(this); max = new QLabel(this); min->setAlignment(Qt::AlignRight); max->setAlignment(Qt::AlignRight); grid->addWidget(max, 0, 1); grid->addWidget(min, 1, 1); } day = new QLabel(this); icon = new QLabel(this); day->setAlignment(Qt::AlignCenter); icon->setFixedSize(64, 64); icon->setScaledContents(true); grid->addWidget(day, 2, 0, 1, 2); grid->addWidget(icon, 0, 0, 2, 1); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); } //-------------------------------------------------------------------------------- void WeatherApplet::configure() { if ( dialog ) { dialog->raise(); dialog->activateWindow(); return; } dialog = new WeatherAppletConfigureDialog(this); dialog->setWindowTitle(i18n("Configure Weather Applet")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); connect(dialog.data(), &QDialog::accepted, this, [this]() { saveConfig(); KConfig config; KConfigGroup group = config.group("Weather"); group.writeEntry("apiKey", apiKey); group = config.group(id); group.writeEntry("cityId", cityId); group.writeEntry("units", units); if ( !apiKey.isEmpty() && !cityId.isEmpty() ) { fetchData(); timer.start(); } else { cityLabel->setText(i18n("Not configured")); } }); } //--------------------------------------------------------------------------------