File indexing completed on 2024-05-12 05:14:52
0001 /* 0002 * messagewindow.cpp - displays an alarm message in a window 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2001-2024 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "messagewindow.h" 0010 #include "messagedisplayhelper.h" 0011 #include "config-kalarm.h" 0012 0013 #include "deferdlg.h" 0014 #include "editdlg.h" 0015 #include "functions.h" 0016 #include "kalarmapp.h" 0017 #include "mainwindow.h" 0018 #include "preferences.h" 0019 #include "resourcescalendar.h" 0020 #include "lib/config.h" 0021 #include "lib/desktop.h" 0022 #include "lib/file.h" 0023 #include "lib/messagebox.h" 0024 #include "lib/pushbutton.h" 0025 #include "akonadiplugin/akonadiplugin.h" 0026 #include "kalarm_debug.h" 0027 0028 #include <KAboutData> 0029 #include <KStandardGuiItem> 0030 #include <KLocalizedString> 0031 #include <KConfigGroup> 0032 #include <KTextEdit> 0033 #include <KSqueezedTextLabel> 0034 #include <KWindowSystem> 0035 #if ENABLE_X11 0036 #include <KX11Extras> 0037 #include <KWindowInfo> 0038 #include <netwm.h> 0039 #include <QGuiApplication> 0040 #endif 0041 #include <QTextBrowser> 0042 #include <QScrollBar> 0043 #include <QCheckBox> 0044 #include <QLabel> 0045 #include <QPalette> 0046 #include <QTimer> 0047 #include <QFrame> 0048 #include <QGridLayout> 0049 #include <QVBoxLayout> 0050 #include <QHBoxLayout> 0051 #include <QResizeEvent> 0052 #include <QCloseEvent> 0053 #include <QScreen> 0054 #include <QStyle> 0055 0056 #include "kmailinterface.h" 0057 0058 using namespace KAlarmCal; 0059 0060 namespace 0061 { 0062 0063 enum FullScreenType { NoFullScreen = 0, FullScreen = 1, FullScreenActive = 2 }; 0064 FullScreenType haveFullScreenWindow(int screen); 0065 FullScreenType findFullScreenWindows(const QList<QRect>& screenRects, QList<FullScreenType>& screenTypes); 0066 0067 const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail"); 0068 const QLatin1String KMAIL_DBUS_PATH("/KMail"); 0069 0070 // The delay for enabling message window buttons if a zero delay is 0071 // configured, i.e. the windows are placed far from the cursor. 0072 const int proximityButtonDelay = 1000; // (milliseconds) 0073 const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity 0074 0075 // Basic flags for the window 0076 const Qt::WindowFlags WFLAGS = Qt::WindowStaysOnTopHint; 0077 const Qt::WindowFlags WFLAGS2 = Qt::WindowContextHelpButtonHint; 0078 const Qt::WidgetAttribute WidgetFlags = Qt::WA_DeleteOnClose; 0079 0080 } // namespace 0081 0082 0083 // A text label widget which can be scrolled and copied with the mouse. 0084 class MessageText : public KTextEdit 0085 { 0086 public: 0087 MessageText(QWidget* parent = nullptr) 0088 : KTextEdit(parent) 0089 { 0090 setReadOnly(true); 0091 setFrameStyle(NoFrame); 0092 setLineWrapMode(NoWrap); 0093 } 0094 int scrollBarHeight() const { return horizontalScrollBar()->height(); } 0095 int scrollBarWidth() const { return verticalScrollBar()->width(); } 0096 void setBackgroundColour(const QColor& c) 0097 { 0098 QPalette pal = viewport()->palette(); 0099 pal.setColor(viewport()->backgroundRole(), c); 0100 viewport()->setPalette(pal); 0101 } 0102 QSize sizeHint() const override 0103 { 0104 const QSizeF docsize = document()->size(); 0105 return {static_cast<int>(docsize.width() + 0.99) + verticalScrollBar()->width(), 0106 static_cast<int>(docsize.height() + 0.99) + horizontalScrollBar()->height()}; 0107 } 0108 }; 0109 0110 QList<MessageWindow*> MessageWindow::mWindowList; 0111 0112 /****************************************************************************** 0113 * Construct the message window for the specified alarm. 0114 * Other alarms in the supplied event may have been updated by the caller, so 0115 * the whole event needs to be stored for updating the calendar file when it is 0116 * displayed. 0117 */ 0118 MessageWindow::MessageWindow(const KAEvent& event, const KAAlarm& alarm, int flags) 0119 : MainWindowBase(nullptr, static_cast<Qt::WindowFlags>(WFLAGS | WFLAGS2 | ((flags & AlwaysHide) || getWorkAreaAndModal() ? Qt::WindowType(0) : Qt::X11BypassWindowManagerHint))) 0120 , MessageDisplay(event, alarm, flags) 0121 , mRestoreHeight(0) 0122 { 0123 qCDebug(KALARM_LOG) << "MessageWindow():" << mEventId(); 0124 setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags)); 0125 setWindowModality(Qt::WindowModal); 0126 setObjectName(QLatin1StringView("MessageWindow")); // used by LikeBack 0127 if (!(flags & (NoInitView | AlwaysHide))) 0128 MessageWindow::setUpDisplay(); // avoid calling virtual method from constructor 0129 0130 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageWindow::textsChanged); 0131 connect(mHelper, &MessageDisplayHelper::commandExited, this, &MessageWindow::commandCompleted); 0132 0133 // Set to save settings automatically, but don't save window size. 0134 // File alarm window size is saved elsewhere. 0135 setAutoSaveSettings(QStringLiteral("MessageWindow"), false); 0136 mWindowList.append(this); 0137 if (mAlwaysHidden()) 0138 { 0139 hide(); 0140 displayComplete(); // play audio, etc. 0141 } 0142 } 0143 0144 /****************************************************************************** 0145 * Construct the message window for a specified error message. 0146 * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note 0147 * that the option is specific to 'event'. 0148 */ 0149 MessageWindow::MessageWindow(const KAEvent& event, const DateTime& alarmDateTime, 0150 const QStringList& errmsgs, const QString& dontShowAgain) 0151 : MainWindowBase(nullptr, WFLAGS | WFLAGS2) 0152 , MessageDisplay(event, alarmDateTime, errmsgs, dontShowAgain) 0153 , mRestoreHeight(0) 0154 { 0155 qCDebug(KALARM_LOG) << "MessageWindow(errmsg)"; 0156 setAttribute(static_cast<Qt::WidgetAttribute>(WidgetFlags)); 0157 setWindowModality(Qt::WindowModal); 0158 setObjectName(QLatin1StringView("ErrorWin")); // used by LikeBack 0159 getWorkAreaAndModal(); 0160 MessageWindow::setUpDisplay(); // avoid calling virtual method from constructor 0161 0162 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageWindow::textsChanged); 0163 0164 mWindowList.append(this); 0165 } 0166 0167 /****************************************************************************** 0168 * Construct the message window for restoration by session management. 0169 * The window is initialised by readProperties(). 0170 */ 0171 MessageWindow::MessageWindow() 0172 : MainWindowBase(nullptr, WFLAGS) 0173 , MessageDisplay() 0174 { 0175 qCDebug(KALARM_LOG) << "MessageWindow(): restore"; 0176 setAttribute(WidgetFlags); 0177 setWindowModality(Qt::WindowModal); 0178 setObjectName(QLatin1StringView("RestoredMsgWin")); // used by LikeBack 0179 getWorkAreaAndModal(); 0180 0181 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageWindow::textsChanged); 0182 connect(mHelper, &MessageDisplayHelper::commandExited, this, &MessageWindow::commandCompleted); 0183 0184 mWindowList.append(this); 0185 } 0186 0187 /****************************************************************************** 0188 * Destructor. Perform any post-alarm actions before tidying up. 0189 */ 0190 MessageWindow::~MessageWindow() 0191 { 0192 qCDebug(KALARM_LOG) << "~MessageWindow" << (void*)this << mEventId(); 0193 mWindowList.removeAll(this); 0194 } 0195 0196 /****************************************************************************** 0197 * Construct the message window. 0198 */ 0199 void MessageWindow::setUpDisplay() 0200 { 0201 mHelper->initTexts(); 0202 MessageDisplayHelper::DisplayTexts texts = mHelper->texts(); 0203 0204 const bool reminder = (!mErrorWindow() && (mAlarmType() & KAAlarm::Type::Reminder)); 0205 const int leading = fontMetrics().leading(); 0206 setCaption(texts.title); 0207 QWidget* topWidget = new QWidget(this); 0208 setCentralWidget(topWidget); 0209 auto topLayout = new QVBoxLayout(topWidget); 0210 const int dcmLeft = style()->pixelMetric(QStyle::PM_LayoutLeftMargin); 0211 const int dcmTop = style()->pixelMetric(QStyle::PM_LayoutTopMargin); 0212 const int dcmRight = style()->pixelMetric(QStyle::PM_LayoutRightMargin); 0213 const int dcmBottom = style()->pixelMetric(QStyle::PM_LayoutBottomMargin); 0214 0215 QPalette labelPalette = palette(); 0216 labelPalette.setColor(backgroundRole(), labelPalette.color(QPalette::Window)); 0217 0218 // Show the alarm date/time, together with a reminder text where appropriate. 0219 // Alarm date/time: display time zone if not local time zone. 0220 mTimeLabel = new QLabel(topWidget); 0221 mTimeLabel->setText(texts.timeFull); 0222 mTimeLabel->setFrameStyle(QFrame::StyledPanel); 0223 mTimeLabel->setPalette(labelPalette); 0224 mTimeLabel->setAutoFillBackground(true); 0225 mTimeLabel->setAlignment(Qt::AlignHCenter); 0226 topLayout->addWidget(mTimeLabel, 0, Qt::AlignHCenter); 0227 mTimeLabel->setWhatsThis(i18nc("@info:whatsthis", "The scheduled date/time for the message (as opposed to the actual time of display).")); 0228 if (texts.timeFull.isEmpty()) 0229 mTimeLabel->hide(); 0230 0231 if (!mErrorWindow()) 0232 { 0233 // It's a normal alarm message window 0234 switch (mAction()) 0235 { 0236 case KAEvent::SubAction::File: 0237 { 0238 // Display the file name 0239 auto label = new KSqueezedTextLabel(texts.fileName, topWidget); 0240 label->setFrameStyle(QFrame::StyledPanel); 0241 label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); 0242 label->setPalette(labelPalette); 0243 label->setAutoFillBackground(true); 0244 label->setWhatsThis(i18nc("@info:whatsthis", "The file whose contents are displayed below")); 0245 topLayout->addWidget(label, 0, Qt::AlignHCenter); 0246 0247 if (mErrorMsgs().isEmpty()) 0248 { 0249 // Display contents of file 0250 auto view = new QTextBrowser(topWidget); 0251 view->setFrameStyle(QFrame::NoFrame); 0252 view->setWordWrapMode(QTextOption::NoWrap); 0253 QPalette pal = view->viewport()->palette(); 0254 pal.setColor(view->viewport()->backgroundRole(), mBgColour()); 0255 view->viewport()->setPalette(pal); 0256 view->setTextColor(mFgColour()); 0257 view->setCurrentFont(mFont()); 0258 0259 switch (texts.fileType) 0260 { 0261 case File::Type::Image: 0262 case File::Type::TextFormatted: 0263 view->setHtml(texts.message); 0264 break; 0265 default: 0266 view->setPlainText(texts.message); 0267 break; 0268 } 0269 view->setMinimumSize(view->sizeHint()); 0270 topLayout->addWidget(view); 0271 0272 // Set the default size to 20 lines square. 0273 // Note that after the first file has been displayed, this size 0274 // is overridden by the user-set default stored in the config file. 0275 // So there is no need to calculate an accurate size. 0276 int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth(); 0277 view->resize(QSize(h, h).expandedTo(view->sizeHint())); 0278 view->setWhatsThis(i18nc("@info:whatsthis", "The contents of the file to be displayed")); 0279 } 0280 break; 0281 } 0282 case KAEvent::SubAction::Message: 0283 { 0284 // Message label 0285 // Using MessageText instead of QLabel allows scrolling and mouse copying 0286 auto text = new MessageText(topWidget); 0287 text->setAutoFillBackground(true); 0288 text->setBackgroundColour(mBgColour()); 0289 text->setTextColor(mFgColour()); 0290 text->setCurrentFont(mFont()); 0291 text->insertPlainText(texts.message); 0292 const int lineSpacing = text->fontMetrics().lineSpacing(); 0293 const QSize s = text->sizeHint(); 0294 const int h = s.height(); 0295 text->setMaximumHeight(h + text->scrollBarHeight()); 0296 text->setMinimumHeight(qMin(h, lineSpacing*4)); 0297 text->setMaximumWidth(s.width() + text->scrollBarWidth()); 0298 text->setWhatsThis(i18nc("@info:whatsthis", "The alarm message")); 0299 const int vspace = lineSpacing/2; 0300 const int hspace = lineSpacing - (dcmLeft + dcmRight)/2; 0301 topLayout->addSpacing(vspace); 0302 topLayout->addStretch(); 0303 // Don't include any horizontal margins if message is 2/3 screen width 0304 if (text->sizeHint().width() >= Desktop::workArea(mScreenNumber).width()*2/3) 0305 topLayout->addWidget(text, 1, Qt::AlignHCenter); 0306 else 0307 { 0308 auto layout = new QHBoxLayout(); 0309 layout->addSpacing(hspace); 0310 layout->addWidget(text, 1, Qt::AlignHCenter); 0311 layout->addSpacing(hspace); 0312 topLayout->addLayout(layout); 0313 } 0314 if (!reminder) 0315 topLayout->addStretch(); 0316 break; 0317 } 0318 case KAEvent::SubAction::Command: 0319 { 0320 mCommandText = new MessageText(topWidget); 0321 mCommandText->setBackgroundColour(mBgColour()); 0322 mCommandText->setTextColor(mFgColour()); 0323 mCommandText->setCurrentFont(mFont()); 0324 topLayout->addWidget(mCommandText); 0325 mCommandText->setWhatsThis(i18nc("@info:whatsthis", "The output of the alarm's command")); 0326 mCommandText->setPlainText(texts.message); 0327 break; 0328 } 0329 case KAEvent::SubAction::Email: 0330 default: 0331 break; 0332 } 0333 0334 if (!texts.remainingTime.isEmpty()) 0335 { 0336 // Advance reminder: show remaining time until the actual alarm 0337 mRemainingText = new QLabel(topWidget); 0338 mRemainingText->setFrameStyle(QFrame::Box | QFrame::Raised); 0339 mRemainingText->setContentsMargins(leading, leading, leading, leading); 0340 mRemainingText->setPalette(labelPalette); 0341 mRemainingText->setAutoFillBackground(true); 0342 mRemainingText->setText(texts.remainingTime); 0343 topLayout->addWidget(mRemainingText, 0, Qt::AlignHCenter); 0344 topLayout->addSpacing(style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing)); 0345 topLayout->addStretch(); 0346 } 0347 } 0348 else 0349 { 0350 // It's an error message 0351 switch (mAction()) 0352 { 0353 case KAEvent::SubAction::Email: 0354 { 0355 // Display the email addresses and subject. 0356 QFrame* frame = new QFrame(topWidget); 0357 frame->setFrameStyle(QFrame::Box | QFrame::Raised); 0358 frame->setWhatsThis(i18nc("@info:whatsthis", "The email to send")); 0359 topLayout->addWidget(frame, 0, Qt::AlignHCenter); 0360 auto grid = new QGridLayout(frame); 0361 0362 QLabel* label = new QLabel(texts.errorEmail[0], frame); 0363 grid->addWidget(label, 0, 0, Qt::AlignLeft); 0364 label = new QLabel(texts.errorEmail[1], frame); 0365 grid->addWidget(label, 0, 1, Qt::AlignLeft); 0366 0367 label = new QLabel(texts.errorEmail[2], frame); 0368 grid->addWidget(label, 1, 0, Qt::AlignLeft); 0369 label = new QLabel(texts.errorEmail[3], frame); 0370 grid->addWidget(label, 1, 1, Qt::AlignLeft); 0371 break; 0372 } 0373 case KAEvent::SubAction::Command: 0374 case KAEvent::SubAction::File: 0375 case KAEvent::SubAction::Message: 0376 default: 0377 // Just display the error message strings 0378 break; 0379 } 0380 } 0381 0382 if (mErrorMsgs().isEmpty()) 0383 { 0384 topWidget->setAutoFillBackground(true); 0385 QPalette palette = topWidget->palette(); 0386 palette.setColor(topWidget->backgroundRole(), mBgColour()); 0387 topWidget->setPalette(palette); 0388 } 0389 else 0390 { 0391 auto layout = new QHBoxLayout(); 0392 layout->setContentsMargins(2 * dcmLeft, 2 * dcmTop, 2 * dcmRight, 2 * dcmBottom); 0393 layout->addStretch(); 0394 topLayout->addLayout(layout); 0395 QLabel* label = new QLabel(topWidget); 0396 label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize))); 0397 layout->addWidget(label, 0, Qt::AlignRight); 0398 auto vlayout = new QVBoxLayout(); 0399 layout->addLayout(vlayout); 0400 for (const QString& msg : mErrorMsgs()) 0401 { 0402 label = new QLabel(msg, topWidget); 0403 vlayout->addWidget(label, 0, Qt::AlignLeft); 0404 } 0405 layout->addStretch(); 0406 if (!mDontShowAgain().isEmpty()) 0407 { 0408 mDontShowAgainCheck = new QCheckBox(i18nc("@option:check", "Do not display this error message again for this alarm"), topWidget); 0409 topLayout->addWidget(mDontShowAgainCheck, 0, Qt::AlignLeft); 0410 } 0411 } 0412 0413 auto grid = new QGridLayout(); 0414 grid->setColumnStretch(0, 1); // keep the buttons right-adjusted in the window 0415 topLayout->addLayout(grid); 0416 int gridIndex = 1; 0417 0418 // Close button 0419 mOkButton = new PushButton(KStandardGuiItem::close(), topWidget); 0420 // Prevent accidental acknowledgement of the message if the user is typing when the window appears 0421 mOkButton->clearFocus(); 0422 mOkButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection 0423 connect(mOkButton, &QAbstractButton::clicked, this, &MessageWindow::slotOk); 0424 grid->addWidget(mOkButton, 0, gridIndex++, Qt::AlignHCenter); 0425 mOkButton->setWhatsThis(i18nc("@info:whatsthis", "Acknowledge the alarm")); 0426 0427 if (mShowEdit()) 0428 { 0429 // Edit button 0430 mEditButton = new PushButton(i18nc("@action:button", "Edit..."), topWidget); 0431 mEditButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection 0432 connect(mEditButton, &QAbstractButton::clicked, this, &MessageWindow::slotEdit); 0433 grid->addWidget(mEditButton, 0, gridIndex++, Qt::AlignHCenter); 0434 mEditButton->setToolTip(i18nc("@info:tooltip", "Edit the alarm")); 0435 mEditButton->setWhatsThis(i18nc("@info:whatsthis", "Edit the alarm.")); 0436 } 0437 0438 // Defer button 0439 mDeferButton = new PushButton(i18nc("@action:button", "Defer..."), topWidget); 0440 mDeferButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection 0441 connect(mDeferButton, &QAbstractButton::clicked, this, &MessageWindow::slotDefer); 0442 grid->addWidget(mDeferButton, 0, gridIndex++, Qt::AlignHCenter); 0443 mDeferButton->setToolTip(i18nc("@info:tooltip", "Defer the alarm until later")); 0444 mDeferButton->setWhatsThis(xi18nc("@info:whatsthis", "<para>Defer the alarm until later.</para>" 0445 "<para>You will be prompted to specify when the alarm should be redisplayed.</para>")); 0446 0447 if (mNoDefer()) 0448 mDeferButton->hide(); 0449 else 0450 mHelper->setDeferralLimit(mEvent()); // ensure that button is disabled when alarm can't be deferred any more 0451 0452 if (!mAudioFile().isEmpty() && (mVolume() || mFadeVolume() > 0)) 0453 { 0454 // Silence button to stop sound repetition 0455 mSilenceButton = new PushButton(topWidget); 0456 mSilenceButton->setIcon(QIcon::fromTheme(QStringLiteral("media-playback-stop"))); 0457 grid->addWidget(mSilenceButton, 0, gridIndex++, Qt::AlignHCenter); 0458 mSilenceButton->setToolTip(i18nc("@info:tooltip", "Stop sound")); 0459 mSilenceButton->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound")); 0460 // To avoid getting in a mess, disable the button until sound playing has been set up 0461 mSilenceButton->setEnabled(false); 0462 0463 mHelper->setSilenceButton(mSilenceButton); 0464 } 0465 0466 if (mEmailId() >= 0 && Preferences::useAkonadi()) 0467 { 0468 // KMail button 0469 mKMailButton = new PushButton(topWidget); 0470 mKMailButton->setIcon(QIcon::fromTheme(QStringLiteral("internet-mail"))); 0471 connect(mKMailButton, &QAbstractButton::clicked, this, &MessageWindow::slotShowKMailMessage); 0472 grid->addWidget(mKMailButton, 0, gridIndex++, Qt::AlignHCenter); 0473 mKMailButton->setToolTip(xi18nc("@info:tooltip Locate this email in KMail", "Locate in <application>KMail</application>")); 0474 mKMailButton->setWhatsThis(xi18nc("@info:whatsthis", "Locate and highlight this email in <application>KMail</application>")); 0475 } 0476 0477 // KAlarm button 0478 mKAlarmButton = new PushButton(topWidget); 0479 mKAlarmButton->setIcon(QIcon::fromTheme(KAboutData::applicationData().componentName())); 0480 connect(mKAlarmButton, &QAbstractButton::clicked, this, &MessageWindow::displayMainWindow); 0481 grid->addWidget(mKAlarmButton, 0, gridIndex++, Qt::AlignHCenter); 0482 mKAlarmButton->setToolTip(xi18nc("@info:tooltip", "Activate <application>KAlarm</application>")); 0483 mKAlarmButton->setWhatsThis(xi18nc("@info:whatsthis", "Activate <application>KAlarm</application>")); 0484 0485 int butsize = mKAlarmButton->sizeHint().height(); 0486 if (mSilenceButton) 0487 butsize = qMax(butsize, mSilenceButton->sizeHint().height()); 0488 if (mKMailButton) 0489 butsize = qMax(butsize, mKMailButton->sizeHint().height()); 0490 mKAlarmButton->setFixedSize(butsize, butsize); 0491 if (mSilenceButton) 0492 mSilenceButton->setFixedSize(butsize, butsize); 0493 if (mKMailButton) 0494 mKMailButton->setFixedSize(butsize, butsize); 0495 0496 // Disable all buttons initially, to prevent accidental clicking on if they happen to be 0497 // under the mouse just as the window appears. 0498 mOkButton->setEnabled(false); 0499 if (mDeferButton->isVisible()) 0500 mDeferButton->setEnabled(false); 0501 if (mEditButton) 0502 mEditButton->setEnabled(false); 0503 if (mKMailButton) 0504 mKMailButton->setEnabled(false); 0505 mKAlarmButton->setEnabled(false); 0506 0507 topLayout->activate(); 0508 setMinimumSize(QSize(grid->sizeHint().width() + dcmLeft + dcmRight, 0509 sizeHint().height())); 0510 #if ENABLE_X11 0511 if (KWindowSystem::isPlatformX11()) 0512 { 0513 const bool modal = !(windowFlags() & Qt::X11BypassWindowManagerHint); 0514 NET::States wstate = NET::Sticky | NET::KeepAbove; 0515 if (modal) 0516 wstate |= NET::Modal; 0517 WId winid = winId(); 0518 KX11Extras::setState(winid, wstate); 0519 KX11Extras::setOnAllDesktops(winid, true); // show on all virtual desktops 0520 } 0521 #endif 0522 0523 mInitialised = true; // the window's widgets have been created 0524 } 0525 0526 /****************************************************************************** 0527 * Return the number of message windows, optionally excluding always-hidden ones. 0528 */ 0529 int MessageWindow::windowCount(bool excludeAlwaysHidden) 0530 { 0531 int count = mWindowList.count(); 0532 if (excludeAlwaysHidden) 0533 { 0534 for (MessageWindow* win : std::as_const(mWindowList)) 0535 { 0536 if (win->mAlwaysHidden()) 0537 --count; 0538 } 0539 } 0540 return count; 0541 } 0542 0543 /****************************************************************************** 0544 * Returns the widget to act as parent for error messages, etc. 0545 */ 0546 QWidget* MessageWindow::displayParent() 0547 { 0548 return this; 0549 } 0550 0551 void MessageWindow::closeDisplay() 0552 { 0553 close(); 0554 } 0555 0556 void MessageWindow::showDisplay() 0557 { 0558 if (!mAlwaysHidden()) 0559 display(); 0560 } 0561 0562 void MessageWindow::raiseDisplay() 0563 { 0564 raise(); 0565 } 0566 0567 /****************************************************************************** 0568 * Raise the alarm window, re-output any required audio notification, and 0569 * reschedule the alarm in the calendar file. 0570 */ 0571 void MessageWindow::repeat(const KAAlarm& alarm) 0572 { 0573 if (!mInitialised) 0574 return; 0575 if (mDeferData) 0576 { 0577 // Cancel any deferral dialog so that the user notices something's going on, 0578 // and also because the deferral time limit will have changed. 0579 delete mDeferData; 0580 mDeferData = nullptr; 0581 } 0582 if (mEventId().isEmpty()) 0583 return; 0584 KAEvent event = ResourcesCalendar::event(mEventId()); 0585 if (event.isValid()) 0586 { 0587 mAlarmType() = alarm.type(); // store new alarm type for use if it is later deferred 0588 if (mAlwaysHidden()) 0589 playAudio(); 0590 else 0591 { 0592 if (!mDeferData || Preferences::modalMessages()) 0593 { 0594 raise(); 0595 playAudio(); 0596 } 0597 if (mDeferButton->isVisible()) 0598 { 0599 mDeferButton->setEnabled(true); 0600 mHelper->setDeferralLimit(event); // ensure that button is disabled when alarm can't be deferred any more 0601 } 0602 } 0603 if (mHelper->alarmShowing(event)) 0604 ResourcesCalendar::updateEvent(event); 0605 } 0606 } 0607 0608 bool MessageWindow::hasDefer() const 0609 { 0610 return mDeferButton && mDeferButton->isVisible(); 0611 } 0612 0613 /****************************************************************************** 0614 * Show the Defer button when it was previously hidden. 0615 */ 0616 void MessageWindow::showDefer() 0617 { 0618 if (mDeferButton) 0619 { 0620 mNoDefer() = false; 0621 mDeferButton->show(); 0622 mHelper->setDeferralLimit(mEvent()); // ensure that button is disabled when alarm can't be deferred any more 0623 resize(sizeHint()); 0624 } 0625 } 0626 0627 /****************************************************************************** 0628 * Convert a reminder window into a normal alarm window. 0629 */ 0630 void MessageWindow::cancelReminder(const KAEvent& event, const KAAlarm& alarm) 0631 { 0632 if (mHelper->cancelReminder(event, alarm)) 0633 { 0634 const MessageDisplayHelper::DisplayTexts& texts = mHelper->texts(); 0635 setCaption(texts.title); 0636 mTimeLabel->setText(texts.timeFull); 0637 if (mRemainingText) 0638 mRemainingText->hide(); 0639 setMinimumHeight(0); 0640 centralWidget()->layout()->activate(); 0641 setMinimumHeight(sizeHint().height()); 0642 resize(sizeHint()); 0643 } 0644 } 0645 0646 /****************************************************************************** 0647 * Update and show the alarm's trigger time. 0648 * This is assumed to have previously been hidden. 0649 */ 0650 void MessageWindow::showDateTime(const KAEvent& event, const KAAlarm& alarm) 0651 { 0652 if (mTimeLabel && mHelper->updateDateTime(event, alarm)) 0653 { 0654 mTimeLabel->setText(mHelper->texts().timeFull); 0655 mTimeLabel->show(); 0656 } 0657 } 0658 0659 /****************************************************************************** 0660 * Called when the texts to display have changed. 0661 */ 0662 void MessageWindow::textsChanged(MessageDisplayHelper::DisplayTexts::TextIds ids, const QString& change) 0663 { 0664 const MessageDisplayHelper::DisplayTexts& texts = mHelper->texts(); 0665 0666 if (ids & MessageDisplayHelper::DisplayTexts::Title) 0667 setCaption(texts.title); 0668 0669 if (ids & MessageDisplayHelper::DisplayTexts::TimeFull) 0670 mTimeLabel->setText(texts.timeFull); 0671 0672 if (ids & MessageDisplayHelper::DisplayTexts::RemainingTime) 0673 { 0674 if (mRemainingText) 0675 { 0676 if (texts.remainingTime.isEmpty()) 0677 mRemainingText->hide(); 0678 else 0679 mRemainingText->setText(texts.remainingTime); 0680 } 0681 } 0682 0683 if (ids & MessageDisplayHelper::DisplayTexts::MessageAppend) 0684 { 0685 // More output is available from the command which is providing the text 0686 // for this window. Add the output and resize the window to show it. 0687 mCommandText->insertPlainText(change); 0688 resize(sizeHint()); 0689 } 0690 } 0691 0692 /****************************************************************************** 0693 * Called when the command providing the alarm message text has exited. 0694 * 'success' is true if the command did not fail completely. 0695 */ 0696 void MessageWindow::commandCompleted(bool success) 0697 { 0698 if (!success) 0699 { 0700 // The command failed completely. KAlarmApp will output an error 0701 // message, so delete the empty window. 0702 close(); 0703 } 0704 } 0705 0706 /****************************************************************************** 0707 * Save settings to the session managed config file, for restoration 0708 * when the program is restored. 0709 */ 0710 void MessageWindow::saveProperties(KConfigGroup& config) 0711 { 0712 if (mShown && mHelper->saveProperties(config)) 0713 config.writeEntry("Height", height()); 0714 } 0715 0716 /****************************************************************************** 0717 * Read settings from the session managed config file. 0718 * This function is automatically called whenever the app is being restored. 0719 * Read in whatever was saved in saveProperties(). 0720 */ 0721 void MessageWindow::readProperties(const KConfigGroup& config) 0722 { 0723 mRestoreHeight = config.readEntry("Height", 0); 0724 0725 if (mHelper->readProperties(config)) 0726 { 0727 // The retrieved alarm was shown by this class, and we need to initialise 0728 // its display. 0729 setUpDisplay(); 0730 } 0731 } 0732 0733 /****************************************************************************** 0734 * Spread alarm windows over the screen so that they are all visible, or pile 0735 * them on top of each other again. 0736 * Reply = true if windows are now scattered, false if piled up. 0737 */ 0738 bool MessageWindow::spread(bool scatter) 0739 { 0740 if (KWindowSystem::isPlatformWayland()) 0741 return false; // Wayland doesn't allow positioning of windows 0742 0743 if (windowCount(true) <= 1) // ignore always-hidden windows 0744 return false; 0745 0746 const QRect desk = Desktop::workArea(); // get the usable area of the desktop 0747 if (scatter == isSpread(desk.topLeft())) 0748 return scatter; 0749 0750 if (scatter) 0751 { 0752 // Usually there won't be many windows, so a crude 0753 // scattering algorithm should suffice. 0754 int x = desk.left(); 0755 int y = desk.top(); 0756 int ynext = y; 0757 for (int errmsgs = 0; errmsgs < 2; ++errmsgs) 0758 { 0759 // Display alarm messages first, then error messages, since most 0760 // error messages tend to be the same height. 0761 for (MessageWindow* w : std::as_const(mWindowList)) 0762 { 0763 if ((!errmsgs && w->mErrorWindow()) 0764 || (errmsgs && !w->mErrorWindow())) 0765 continue; 0766 const QSize sz = w->frameGeometry().size(); 0767 if (x + sz.width() > desk.right()) 0768 { 0769 x = desk.left(); 0770 y = ynext; 0771 } 0772 int ytmp = y; 0773 if (y + sz.height() > desk.bottom()) 0774 { 0775 ytmp = desk.bottom() - sz.height(); 0776 if (ytmp < desk.top()) 0777 ytmp = desk.top(); 0778 } 0779 w->move(x, ytmp); 0780 x += sz.width(); 0781 if (ytmp + sz.height() > ynext) 0782 ynext = ytmp + sz.height(); 0783 } 0784 } 0785 } 0786 else 0787 { 0788 // Move all windows to the top left corner 0789 for (MessageWindow* w : std::as_const(mWindowList)) 0790 w->move(desk.topLeft()); 0791 } 0792 return scatter; 0793 } 0794 0795 /****************************************************************************** 0796 * Check whether message windows are all piled up, or are spread out. 0797 * Reply = true if windows are currently spread, false if piled up. 0798 */ 0799 bool MessageWindow::isSpread(const QPoint& topLeft) 0800 { 0801 for (MessageWindow* w : std::as_const(mWindowList)) 0802 { 0803 if (w->pos() != topLeft) 0804 return true; 0805 } 0806 return false; 0807 } 0808 0809 /****************************************************************************** 0810 * Display the window, if it should not already be auto-closed. 0811 * If windows are being positioned away from the mouse cursor, it is initially 0812 * positioned at the top left to slightly reduce the number of times the 0813 * windows need to be moved in showEvent(). 0814 */ 0815 void MessageWindow::display() 0816 { 0817 connect(mHelper, &MessageDisplayHelper::autoCloseNow, this, &QWidget::close); 0818 if (mHelper->activateAutoClose()) 0819 { 0820 if (Preferences::messageButtonDelay() == 0) 0821 move(0, 0); 0822 MainWindowBase::show(); 0823 // Ensure that the screen wakes from sleep, in case the window manager 0824 // doesn't do this when the window is displayed. 0825 mHelper->wakeScreen(); 0826 } 0827 } 0828 0829 /****************************************************************************** 0830 * Returns the window's recommended size exclusive of its frame. 0831 */ 0832 QSize MessageWindow::sizeHint() const 0833 { 0834 QSize desired; 0835 switch (mAction()) 0836 { 0837 case KAEvent::SubAction::Message: 0838 desired = MainWindowBase::sizeHint(); 0839 break; 0840 case KAEvent::SubAction::Command: 0841 if (mShown) 0842 { 0843 // For command output, expand the window to accommodate the text 0844 const QSize texthint = mCommandText->sizeHint(); 0845 int w = texthint.width() + style()->pixelMetric(QStyle::PM_LayoutLeftMargin) 0846 + style()->pixelMetric(QStyle::PM_LayoutRightMargin); 0847 if (w < width()) 0848 w = width(); 0849 const int ypadding = height() - mCommandText->height(); 0850 desired = QSize(w, texthint.height() + ypadding); 0851 break; 0852 } 0853 // fall through to default 0854 [[fallthrough]]; 0855 default: 0856 return MainWindowBase::sizeHint(); 0857 } 0858 0859 // Limit the size to fit inside the working area of the desktop 0860 const QSize desktop = Desktop::workArea(mScreenNumber).size(); 0861 const QSize frameThickness = frameGeometry().size() - geometry().size(); // title bar & window frame 0862 return desired.boundedTo(desktop - frameThickness); 0863 } 0864 0865 /****************************************************************************** 0866 * Called when the window is shown. 0867 * The first time, output any required audio notification, and reschedule or 0868 * delete the event from the calendar file. 0869 */ 0870 void MessageWindow::showEvent(QShowEvent* se) 0871 { 0872 MainWindowBase::showEvent(se); 0873 if (mShown || !mInitialised) 0874 return; 0875 if (mErrorWindow() || mAlarmType() == KAAlarm::Type::Invalid) 0876 { 0877 // Don't bother repositioning error messages, 0878 // and invalid alarms should be deleted anyway. 0879 enableButtons(); 0880 } 0881 else 0882 { 0883 /* Set the window size. 0884 * Note that the frame thickness is not yet known when this 0885 * method is called, so for large windows the size needs to be 0886 * set again later. 0887 */ 0888 bool execComplete = true; 0889 QSize s = sizeHint(); // fit the window round the message 0890 if (mAction() == KAEvent::SubAction::File && mErrorMsgs().isEmpty()) 0891 Config::readWindowSize("FileMessage", s); 0892 resize(s); 0893 0894 const QRect desk = Desktop::workArea(mScreenNumber); 0895 const QRect frame = frameGeometry(); 0896 0897 mButtonDelay = Preferences::messageButtonDelay() * 1000; 0898 if (mButtonDelay) 0899 { 0900 // Position the window in the middle of the screen, and 0901 // delay enabling the buttons. 0902 mPositioning = true; 0903 move((desk.width() - frame.width())/2, (desk.height() - frame.height())/2); 0904 execComplete = false; 0905 } 0906 else 0907 { 0908 /* Try to ensure that the window can't accidentally be acknowledged 0909 * by the user clicking the mouse just as it appears. 0910 * To achieve this, move the window so that the OK button is as far away 0911 * from the cursor as possible. If the buttons are still too close to the 0912 * cursor, disable the buttons for a short time. 0913 * N.B. This can't be done in show(), since the geometry of the window 0914 * is not known until it is displayed. Unfortunately by moving the 0915 * window in showEvent(), a flicker is unavoidable. 0916 * See the Qt documentation on window geometry for more details. 0917 */ 0918 // PROBLEM: The frame size is not known yet! 0919 const QPoint cursor = QCursor::pos(); 0920 const QRect rect = geometry(); 0921 // Find the offsets from the outside of the frame to the edges of the OK button 0922 const QRect button(mOkButton->mapToParent(QPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight())); 0923 const int buttonLeft = button.left() + rect.left() - frame.left(); 0924 const int buttonRight = width() - button.right() + frame.right() - rect.right(); 0925 const int buttonTop = button.top() + rect.top() - frame.top(); 0926 const int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom(); 0927 0928 const int centrex = (desk.width() + buttonLeft - buttonRight) / 2; 0929 const int centrey = (desk.height() + buttonTop - buttonBottom) / 2; 0930 const int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left(); 0931 const int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top(); 0932 0933 // Find the enclosing rectangle for the new button positions 0934 // and check if the cursor is too near 0935 QRect buttons = mOkButton->geometry().united(mKAlarmButton->geometry()); 0936 buttons.translate(rect.left() + x - frame.left(), rect.top() + y - frame.top()); 0937 const int minDistance = proximityMultiple * mOkButton->height(); 0938 if ((abs(cursor.x() - buttons.left()) < minDistance 0939 || abs(cursor.x() - buttons.right()) < minDistance) 0940 && (abs(cursor.y() - buttons.top()) < minDistance 0941 || abs(cursor.y() - buttons.bottom()) < minDistance)) 0942 mButtonDelay = proximityButtonDelay; // too near - disable buttons initially 0943 0944 if (x != frame.left() || y != frame.top()) 0945 { 0946 mPositioning = true; 0947 move(x, y); 0948 execComplete = false; 0949 } 0950 } 0951 if (execComplete) 0952 displayComplete(); // play audio, etc. 0953 } 0954 0955 // Set the window size etc. once the frame size is known 0956 QTimer::singleShot(0, this, &MessageWindow::frameDrawn); //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) 0957 0958 mShown = true; 0959 } 0960 0961 /****************************************************************************** 0962 * Called when the window has been moved. 0963 */ 0964 void MessageWindow::moveEvent(QMoveEvent* e) 0965 { 0966 MainWindowBase::moveEvent(e); 0967 if (!KWindowSystem::isPlatformWayland()) // Wayland doesn't allow positioning of windows 0968 theApp()->setSpreadWindowsState(isSpread(Desktop::workArea(mScreenNumber).topLeft())); 0969 if (mPositioning) 0970 { 0971 // The window has just been initially positioned 0972 mPositioning = false; 0973 displayComplete(); // play audio, etc. 0974 } 0975 } 0976 0977 /****************************************************************************** 0978 * Called after (hopefully) the window frame size is known. 0979 * Reset the initial window size if it exceeds the working area of the desktop. 0980 * Set the 'spread windows' menu item status. 0981 */ 0982 void MessageWindow::frameDrawn() 0983 { 0984 if (!mErrorWindow() && mAction() == KAEvent::SubAction::Message) 0985 { 0986 const QSize s = sizeHint(); 0987 if (width() > s.width() || height() > s.height()) 0988 resize(s); 0989 } 0990 if (!KWindowSystem::isPlatformWayland()) // Wayland doesn't allow positioning of windows 0991 theApp()->setSpreadWindowsState(isSpread(Desktop::workArea(mScreenNumber).topLeft())); 0992 } 0993 0994 /****************************************************************************** 0995 * Called when the window has been displayed properly (in its correct position), 0996 * to play sounds and reschedule the event. 0997 */ 0998 void MessageWindow::displayComplete() 0999 { 1000 mHelper->displayComplete(true); 1001 1002 if (!mAlwaysHidden()) 1003 { 1004 // Enable the window's buttons either now or after the configured delay 1005 if (mButtonDelay > 0) 1006 QTimer::singleShot(mButtonDelay, this, &MessageWindow::enableButtons); //NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks) 1007 else 1008 enableButtons(); 1009 } 1010 } 1011 1012 /****************************************************************************** 1013 * Enable the window's buttons. 1014 */ 1015 void MessageWindow::enableButtons() 1016 { 1017 mOkButton->setEnabled(true); 1018 mKAlarmButton->setEnabled(true); 1019 if (mDeferButton->isVisible() && !mDisableDeferral()) 1020 mDeferButton->setEnabled(true); 1021 if (mEditButton) 1022 mEditButton->setEnabled(true); 1023 if (mKMailButton) 1024 mKMailButton->setEnabled(true); 1025 } 1026 1027 /****************************************************************************** 1028 * Called when the window's size has changed (before it is painted). 1029 */ 1030 void MessageWindow::resizeEvent(QResizeEvent* re) 1031 { 1032 if (mRestoreHeight) 1033 { 1034 // Restore the window height on session restoration 1035 if (mRestoreHeight != re->size().height()) 1036 { 1037 QSize size = re->size(); 1038 size.setHeight(mRestoreHeight); 1039 resize(size); 1040 } 1041 else if (isVisible()) 1042 mRestoreHeight = 0; 1043 } 1044 else 1045 { 1046 if (mShown && mAction() == KAEvent::SubAction::File && mErrorMsgs().isEmpty()) 1047 Config::writeWindowSize("FileMessage", re->size()); 1048 MainWindowBase::resizeEvent(re); 1049 } 1050 } 1051 1052 /****************************************************************************** 1053 * Called when a close event is received. 1054 * Only quits the application if there is no system tray icon displayed. 1055 */ 1056 void MessageWindow::closeEvent(QCloseEvent* ce) 1057 { 1058 if (!mHelper->closeEvent()) 1059 { 1060 ce->ignore(); 1061 return; 1062 } 1063 MainWindowBase::closeEvent(ce); 1064 } 1065 1066 /****************************************************************************** 1067 * Called by MessageDisplayHelper to confirm that the alarm message should be 1068 * acknowledged (closed). 1069 */ 1070 bool MessageWindow::confirmAcknowledgement() 1071 { 1072 if (!mNoCloseConfirm()) 1073 { 1074 // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No. 1075 if (KAMessageBox::warningYesNo(this, i18nc("@info", "Do you really want to acknowledge this alarm?"), 1076 i18nc("@action:button", "Acknowledge Alarm"), KGuiItem(i18nc("@action:button", "Acknowledge")), KStandardGuiItem::cancel()) 1077 != KMessageBox::ButtonCode::PrimaryAction) 1078 { 1079 return false; 1080 } 1081 } 1082 return true; 1083 } 1084 1085 /****************************************************************************** 1086 * Called when the OK button is clicked. 1087 */ 1088 void MessageWindow::slotOk() 1089 { 1090 if (mDontShowAgainCheck && mDontShowAgainCheck->isChecked()) 1091 KAlarm::setDontShowErrors(mEventId(), mDontShowAgain()); 1092 close(); 1093 } 1094 1095 /****************************************************************************** 1096 * Called when the KMail button is clicked. 1097 * Tells KMail to display the email message displayed in this message window. 1098 */ 1099 void MessageWindow::slotShowKMailMessage() 1100 { 1101 AkonadiPlugin* akonadiPlugin = Preferences::akonadiPlugin(); 1102 if (!akonadiPlugin) 1103 return; 1104 qCDebug(KALARM_LOG) << "MessageWindow::slotShowKMailMessage"; 1105 if (mEmailId() < 0) 1106 return; 1107 org::kde::kmail::kmail kmail(KMAIL_DBUS_SERVICE, KMAIL_DBUS_PATH, QDBusConnection::sessionBus()); 1108 // Display the message contents 1109 QDBusReply<bool> reply = kmail.showMail(mEmailId()); 1110 bool failed1 = true; 1111 bool failed2 = true; 1112 if (!reply.isValid()) 1113 qCCritical(KALARM_LOG) << "kmail 'showMail' D-Bus call failed:" << reply.error().message(); 1114 else if (reply.value()) 1115 failed1 = false; 1116 1117 // Select the mail folder containing the message 1118 qint64 colId = akonadiPlugin->getCollectionId(mEmailId()); 1119 if (colId < 0) 1120 qCWarning(KALARM_LOG) << "MessageWindow::slotShowKMailMessage: No parent found for item" << mEmailId(); 1121 else 1122 { 1123 reply = kmail.selectFolder(QString::number(colId)); 1124 if (!reply.isValid()) 1125 qCCritical(KALARM_LOG) << "kmail 'selectFolder' D-Bus call failed:" << reply.error().message(); 1126 else if (reply.value()) 1127 failed2 = false; 1128 } 1129 1130 if (failed1 || failed2) 1131 KAMessageBox::error(this, xi18nc("@info", "Unable to locate this email in <application>KMail</application>")); 1132 } 1133 1134 /****************************************************************************** 1135 * Called when the Edit... button is clicked. 1136 * Displays the alarm edit dialog. 1137 * 1138 * NOTE: The alarm edit dialog is made a child of the main window, not this 1139 * window, so that if this window closes before the dialog (e.g. on 1140 * auto-close), KAlarm doesn't crash. The dialog is set non-modal so that 1141 * the main window is unaffected, but modal mode is simulated so that 1142 * this window is inactive while the dialog is open. 1143 */ 1144 void MessageWindow::slotEdit() 1145 { 1146 qCDebug(KALARM_LOG) << "MessageWindow::slotEdit"; 1147 EditAlarmDlg* dlg = mHelper->createEdit(); 1148 if (!dlg) 1149 return; 1150 KWindowSystem::setMainWindow(dlg->windowHandle(), winId()); 1151 #if ENABLE_X11 1152 if (KWindowSystem::isPlatformX11()) 1153 KX11Extras::setOnAllDesktops(dlg->winId(), false); // don't show on all virtual desktops 1154 #endif 1155 setButtonsReadOnly(true); 1156 #if ENABLE_X11 1157 if (KWindowSystem::isPlatformX11()) 1158 connect(KX11Extras::self(), &KX11Extras::activeWindowChanged, this, &MessageWindow::activeWindowChanged); 1159 #endif 1160 mHelper->executeEdit(); 1161 } 1162 1163 /****************************************************************************** 1164 * Called when Cancel is clicked in the alarm edit dialog invoked by the Edit 1165 * button, or when the dialog is deleted. 1166 */ 1167 void MessageWindow::editDlgCancelled() 1168 { 1169 setButtonsReadOnly(false); 1170 } 1171 1172 /****************************************************************************** 1173 * Called when the active window has changed. If this window has become the 1174 * active window and there is an alarm edit dialog, simulate a modal dialog by 1175 * making the alarm edit dialog the active window instead. 1176 */ 1177 void MessageWindow::activeWindowChanged(WId win) 1178 { 1179 #if ENABLE_X11 1180 // Note that the alarm edit dialog is not a QWindow, so we can't call 1181 // QWindow::requestActivate(). 1182 if (mEditDlg() && win == winId()) 1183 KX11Extras::activateWindow(mEditDlg()->winId()); 1184 #endif 1185 } 1186 1187 /****************************************************************************** 1188 * Called when the Defer... button is clicked. 1189 * Displays the defer message dialog. 1190 */ 1191 void MessageWindow::slotDefer() 1192 { 1193 mDeferData = createDeferDlg(this, false); 1194 if (windowFlags() & Qt::X11BypassWindowManagerHint) 1195 mDeferData->dlg->setWindowFlags(mDeferData->dlg->windowFlags() | Qt::X11BypassWindowManagerHint); 1196 if (!Preferences::modalMessages()) 1197 lower(); 1198 executeDeferDlg(mDeferData); 1199 mDeferData = nullptr; // it was deleted by executeDeferDlg() 1200 } 1201 1202 /****************************************************************************** 1203 * Set or clear the read-only state of the dialog buttons. 1204 */ 1205 void MessageWindow::setButtonsReadOnly(bool ro) 1206 { 1207 mOkButton->setReadOnly(ro, true); 1208 mDeferButton->setReadOnly(ro, true); 1209 if (mEditButton) 1210 mEditButton->setReadOnly(ro, true); 1211 if (mSilenceButton) 1212 mSilenceButton->setReadOnly(ro, true); 1213 if (mKMailButton) 1214 mKMailButton->setReadOnly(ro, true); 1215 mKAlarmButton->setReadOnly(ro, true); 1216 } 1217 1218 bool MessageWindow::isDeferButtonEnabled() const 1219 { 1220 return mDeferButton->isEnabled() && mDeferButton->isVisible(); 1221 } 1222 1223 void MessageWindow::enableDeferButton(bool enable) 1224 { 1225 mDeferButton->setEnabled(enable); 1226 } 1227 1228 void MessageWindow::enableEditButton(bool enable) 1229 { 1230 if (mEditButton) 1231 mEditButton->setEnabled(enable); 1232 } 1233 1234 /****************************************************************************** 1235 * Called when the KAlarm icon button in the message window is clicked. 1236 * Displays the main window, with the appropriate alarm selected. 1237 */ 1238 void MessageWindow::displayMainWindow() 1239 { 1240 MessageDisplay::displayMainWindow(); 1241 } 1242 1243 /****************************************************************************** 1244 * Check whether the message window should be modal, i.e. with title bar etc. 1245 * Normally this follows the Preferences setting, but if there is a full screen 1246 * window displayed, on X11 the message window has to bypass the window manager 1247 * in order to display on top of it (which has the side effect that it will have 1248 * no window decoration). 1249 * 1250 * Also find the usable area of the desktop (excluding panel etc.), on the 1251 * appropriate screen if there are multiple screens. 1252 */ 1253 bool MessageWindow::getWorkAreaAndModal() 1254 { 1255 mScreenNumber = -1; 1256 const bool modal = Preferences::modalMessages(); 1257 const QList<QScreen*> screens = QGuiApplication::screens(); 1258 const int numScreens = screens.count(); 1259 if (numScreens > 1) 1260 { 1261 // There are multiple screens. 1262 // Check for any full screen windows, even if they are not the active 1263 // window, and try not to show the alarm message their screens. 1264 mScreenNumber = screens.indexOf(MainWindow::mainMainWindow()->screen()); // default = KAlarm's screen 1265 if (QGuiApplication::primaryScreen()->virtualSiblings().size() > 1) 1266 { 1267 // The screens form a single virtual desktop. 1268 // Xinerama, for example, uses this scheme. 1269 QList<FullScreenType> screenTypes(numScreens); 1270 QList<QRect> screenRects(numScreens); 1271 for (int s = 0; s < numScreens; ++s) 1272 screenRects[s] = screens[s]->geometry(); 1273 const FullScreenType full = findFullScreenWindows(screenRects, screenTypes); 1274 if (full == NoFullScreen || screenTypes[mScreenNumber] == NoFullScreen) 1275 return modal; 1276 for (int s = 0; s < numScreens; ++s) 1277 { 1278 if (screenTypes[s] == NoFullScreen) 1279 1280 { 1281 // There is no full screen window on this screen 1282 mScreenNumber = s; 1283 return modal; 1284 } 1285 } 1286 // All screens contain a full screen window: use one without 1287 // an active full screen window. 1288 for (int s = 0; s < numScreens; ++s) 1289 { 1290 if (screenTypes[s] == FullScreen) 1291 { 1292 mScreenNumber = s; 1293 return modal; 1294 } 1295 } 1296 } 1297 else 1298 { 1299 // The screens are completely separate from each other. 1300 int inactiveScreen = -1; 1301 FullScreenType full = haveFullScreenWindow(mScreenNumber); 1302 //qCDebug(KALARM_LOG)<<"full="<<full<<", screen="<<mScreenNumber; 1303 if (full == NoFullScreen) 1304 return modal; // KAlarm's screen doesn't contain a full screen window 1305 if (full == FullScreen) 1306 inactiveScreen = mScreenNumber; 1307 for (int s = 0; s < numScreens; ++s) 1308 { 1309 if (s != mScreenNumber) 1310 { 1311 full = haveFullScreenWindow(s); 1312 if (full == NoFullScreen) 1313 { 1314 // There is no full screen window on this screen 1315 mScreenNumber = s; 1316 return modal; 1317 } 1318 if (full == FullScreen && inactiveScreen < 0) 1319 inactiveScreen = s; 1320 } 1321 } 1322 if (inactiveScreen >= 0) 1323 { 1324 // All screens contain a full screen window: use one without 1325 // an active full screen window. 1326 mScreenNumber = inactiveScreen; 1327 return modal; 1328 } 1329 } 1330 return false; // can't logically get here, since there can only be one active window... 1331 } 1332 if (modal) 1333 { 1334 #if ENABLE_X11 1335 if (KWindowSystem::isPlatformX11()) 1336 { 1337 const WId activeId = KX11Extras::activeWindow(); 1338 const KWindowInfo wi = KWindowInfo(activeId, NET::WMState); 1339 if (wi.valid() && wi.hasState(NET::FullScreen)) 1340 return false; // the active window is full screen. 1341 } 1342 #endif 1343 } 1344 return modal; 1345 } 1346 1347 namespace 1348 { 1349 1350 /****************************************************************************** 1351 * In a multi-screen setup (not a single virtual desktop), find whether the 1352 * specified screen has a full screen window on it. 1353 */ 1354 FullScreenType haveFullScreenWindow(int screen) 1355 { 1356 FullScreenType type = NoFullScreen; 1357 //TODO: implement on Wayland 1358 #if ENABLE_X11 1359 if (KWindowSystem::isPlatformX11()) 1360 { 1361 using namespace QNativeInterface; 1362 auto* x11App = qGuiApp->nativeInterface<QX11Application>(); 1363 if (!x11App) 1364 return type; 1365 xcb_connection_t* connection = x11App->connection(); 1366 const NETRootInfo rootInfo(connection, NET::ClientList | NET::ActiveWindow, NET::Properties2(), screen); 1367 const xcb_window_t rootWindow = rootInfo.rootWindow(); 1368 const xcb_window_t activeWindow = rootInfo.activeWindow(); 1369 const xcb_window_t* windows = rootInfo.clientList(); 1370 const int windowCount = rootInfo.clientListCount(); 1371 for (int w = 0; w < windowCount; ++w) 1372 { 1373 NETWinInfo winInfo(connection, windows[w], rootWindow, NET::WMState|NET::WMGeometry, NET::Properties2()); 1374 if (winInfo.state() & NET::FullScreen) 1375 { 1376 //qCDebug(KALARM_LOG)<<"Found FULL SCREEN: " << windows[w]; 1377 type = FullScreen; 1378 if (windows[w] == activeWindow) 1379 return FullScreenActive; 1380 } 1381 } 1382 } 1383 #endif // ENABLE_X11 1384 return type; 1385 } 1386 1387 /****************************************************************************** 1388 * In a multi-screen setup (single virtual desktop, e.g. Xinerama), find which 1389 * screens have full screen windows on them. 1390 */ 1391 FullScreenType findFullScreenWindows(const QList<QRect>& screenRects, QList<FullScreenType>& screenTypes) 1392 { 1393 FullScreenType result = NoFullScreen; 1394 screenTypes.fill(NoFullScreen); 1395 //TODO: implement on Wayland 1396 #if ENABLE_X11 1397 if (KWindowSystem::isPlatformX11()) 1398 { 1399 using namespace QNativeInterface; 1400 auto* x11App = qGuiApp->nativeInterface<QX11Application>(); 1401 if (!x11App) 1402 return result; 1403 xcb_connection_t* connection = x11App->connection(); 1404 const NETRootInfo rootInfo(connection, NET::ClientList | NET::ActiveWindow, NET::Properties2()); 1405 const xcb_window_t rootWindow = rootInfo.rootWindow(); 1406 const xcb_window_t activeWindow = rootInfo.activeWindow(); 1407 const xcb_window_t* windows = rootInfo.clientList(); 1408 const int windowCount = rootInfo.clientListCount(); 1409 //qCDebug(KALARM_LOG)<<"Virtual desktops: Window count="<<windowCount<<", active="<<activeWindow<<", geom="<<QApplication::desktop()->screenGeometry(0); 1410 NETRect netgeom; 1411 NETRect netframe; 1412 for (int w = 0; w < windowCount; ++w) 1413 { 1414 NETWinInfo winInfo(connection, windows[w], rootWindow, NET::WMState | NET::WMGeometry, NET::Properties2()); 1415 if (winInfo.state() & NET::FullScreen) 1416 { 1417 // Found a full screen window - find which screen it's on 1418 const bool active = (windows[w] == activeWindow); 1419 winInfo.kdeGeometry(netframe, netgeom); 1420 const QRect winRect(netgeom.pos.x, netgeom.pos.y, netgeom.size.width, netgeom.size.height); 1421 //qCDebug(KALARM_LOG)<<"Found FULL SCREEN: "<<windows[w]<<", geom="<<winRect; 1422 for (int s = 0, count = screenRects.count(); s < count; ++s) 1423 { 1424 if (screenRects[s].contains(winRect)) 1425 { 1426 //qCDebug(KALARM_LOG)<<"FULL SCREEN on screen"<<s<<", active="<<active; 1427 if (active) 1428 screenTypes[s] = result = FullScreenActive; 1429 else 1430 { 1431 if (screenTypes[s] == NoFullScreen) 1432 screenTypes[s] = FullScreen; 1433 if (result == NoFullScreen) 1434 result = FullScreen; 1435 } 1436 break; 1437 } 1438 } 1439 } 1440 } 1441 } 1442 #endif // ENABLE_X11 1443 return result; 1444 } 1445 1446 } // namespace 1447 1448 #include "moc_messagewindow.cpp" 1449 1450 // vim: et sw=4: