diff --git a/Changelog b/Changelog index c30240de..bd86517a 100644 --- a/Changelog +++ b/Changelog @@ -1,1168 +1,1169 @@ KAlarm Change Log === Version 2.13.1 (KDE Applications 19.12.1) --- 30 December 2019 === ++ Make defer dialogue accessible when a full screen window is active. + Only show 'Cancel Deferral' in defer dialogue if a deferral is already active. === Version 2.13.0 (KDE Applications 19.12) --- 19 November 2019 === + Fix user not always being prompted to update new resource if in old format. + Terminate application after executing 'kalarm --list'. + Fix alarm type column being too wide in alarm template list. + Fix failure to display image when alarm is configured to display an image file. + Fix failure to set no-autostart for non-KDE desktops, if no autostart directory exists. + Refactor to use generic resource classes. === Version 2.12.8 (KDE Applications 19.08.3) --- 16 October 2019 === + Fix error on redo of an active alarm deletion. + Archive repeat-at-login alarms if previously triggered, when they are deleted. + Fix layout of defer alarm dialogue. + Make user settings changes take effect immediately (fixes regression introduced in 2.10.11). === Version 2.12.7 (KDE Applications 19.08.2) --- 7 October 2019 === + Show correct read-only status of an alarm in its context menu. + Fix errors deleting and reactivating alarms (regression introduced in 2.12.5). + Fix error on undo of an active alarm deletion. + Don't trigger repeat-at-login alarms when they are edited or imported. === Version 2.12.6 (KDE Applications 19.08.1) --- 26 August 2019 === + Fix crash sometimes when a resource is enabled [KDE Bug 410596] + Fix D-Bus alarm creation failing if time zone is omitted from start time [KDE Bug 411296] + Fix command line options which don't work if KAlarm not already running: --edit, --list, --triggerEvent, --cancelEvent. === Version 2.12.5 (KDE Applications 19.08) --- 26 July 2019 === + Enable alarm list columns to be hidden using context menu on list header [KDE Bug 397093] + Fix regression introduced in version 2.12.0: Show time zone abbreviation in message window if alarm time has non-local time zone. + If only one writable archived alarm calendar exists, automatically set it as the default. + Don't allow user to create a new resource using same calendar file as an existing resource. + Remove duplicate resources (i.e. which use the same calendar file) at startup [KDE Bug 403124] + Fix drag and drop of emails from KMail, and KMail button in message window. + Improve drag and drop of events and todos from KOrganizer. === Version 2.12.4 (KDE Applications 19.04.3) --- 4 July 2019 === + Fix calendar resource dialogue not configuring resource correctly [KDE Bug 407882] + Fix calendar resource dialogue creating new resources unusable until restart [KDE Bug 407882] + Enable resource after creating with the calendar resource dialogue [KDE Bug 407882] + Fix colour and alarm type columns being too wide in alarm list. === Version 2.12.3 (KDE Applications 19.04.2) --- 23 May 2019 === * Fix calendar configuration dialogue not appearing. * Fix errors creating calendar resources on first run of KAlarm [KDE Bug 407544] * Display alarm message windows within current screen in multi-head systems. === Version 2.12.2 (KDE Applications 18.08.2) --- 27 September 2018 === * Fix Defer button being disabled for recurring alarms [KDE Bug 398658] === Version 2.12.1 (KDE Applications 18.08.1) --- 18 August 2018 === * Align and right adjust 'Time to' column values in main window [KDE Bug 397130] * Remove seconds values from Time column (erroneously added in 2.12.0). === Version 2.12.0 (KDE Applications 18.08) --- 29 July 2018 === * Use KAlarmCal::KADateTime instead of deprecated KDateTime. * Remove 'clock time' option, in favour of local system time zone. * Fix times being truncated and showing ellipsis in main window [KDE Bug 365257] * Fix evaluation of work days. * Fix reminder-once alarms not being correctly loaded from calendar file. * Fix some regressions introduced in version 2.11.0, including: Make global shortcuts available. Default sound file selection dialogue to the system sound files directory. === Version 2.11.16 (KDE Applications 17.04.1) --- 15 April 2017 === * Fix option text for using default email address from KMail/System Settings [KDE Bug 378722] === Version 2.11.15 (KDE Applications 17.04) --- 15 January 2017 === * Report if terminal for command alarms is not configured. * Don't allow 'auto-hide in system tray' on Unity desktop [KDE Bug 373848] === Version 2.11.14 --- 19 February 2017 === * Fix not showing main window if activated again while already running with --tray [KDE Bug 374520] * Fix --help, --version and option errors not being reported if KAlarm is already running. * Make command options --edit-new-* work [KDE Bug 376209] === Version 2.11.13 (KDE Applications 16.12.2) --- 29 January 2017 === * Fix system tray icon used for "some alarms disabled" * Improved system tray icons (requires Plasma 5.9) [KDE Bug 362631] * Don't show misleading "Failed to update alarm" if command alarm fails [KDE Bug 375615] === Version 2.11.12 (KDE Applications 16.12.1) --- 1 January 2017 === * Fix Export Alarms file save error [KDE Bug 374337] * Fix arrow/page up/down keys not working in date edit control (needs KDE Frameworks 5.30) [KDE Bug 373886] === Version 2.11.11 (KDE Applications 16.12.0) --- 16 November 2016 === * Fix crash on exit [KDE Bug 372223] === Version 2.11.10 (KDE Applications 16.08.3) --- 31 October 2016 === * Fix default calendar files not being created on first run [KDE Bug 362962] * Fix crash when a second instance of KAlarm is started [KDE Bug 371628] * Don't output error messages about temporary files in directory calendar [KDE Bug 370627] === Version 2.11.9 (KDE Applications 16.08.1) --- 18 August 2016 === * Prevent KAlarm autostarting on non-KDE desktops if it has never been run [KDE Bug 366562] === Version 2.11.8 (KDE Applications 16.08.0) --- 13 July 2016 === * Use the default time format in alarm list and system tray status popup [KAlarm Forum: https://forum.kde.org/viewtopic.php?f=229&t=133788] === Version 2.11.7 (KDE Applications 16.04.3) --- 11 June 2016 === * Always use current setting for email sender address when sending emails [KDE Bug 359163] === Version 2.11.6 (KDE Applications 16.04.1) --- 20 April 2016 === * Prevent KAlarm autostarting on non-KDE desktops if start-at-login is disabled [KAlarm Forum: https://forum.kde.org/viewtopic.php?f=229&t=131410] === Version 2.11.5 (KDE Applications 16.04.0) --- 13 April 2016 === * Fix alarm times out by an hour in daylight savings time (needs kcalcore 16.04) [KDE Bug 336738] * Don't show spurious extra calendar after adding new calendar [KDE Bug 361543] * Fix crash when adding new calendar [KDE Bugs 361539, 361717] === Version 2.11.4 (KDE Applications 15.12.3) --- 1 February 2016 === * Fix reminder time edit being covered by 'in advance' combo [KDE Bug 357018] * Fix crash after editing an alarm, if spell check is enabled [KDE Bug 356048] * Fix occasional crash on startup [KDE Bug 358217] * Fix specification on command line of a reminder after the alarm. * Fix deferral time of date-only recurring alarms [KDE Bug 346060] * Fix frequency edit field missing from recurrence editor. === Version 2.11.3 (KDE Applications 15.08.3) --- 4 November 2015 === * Re-enable use of sendmail for email alarms. * Fix conversion error in sub-repetition interval from command line. === Version 2.11.2 (KDE Applications 15.08.2) --- 24 September 2015 === * Enable typing into New Alarm dialogue while alarm is displayed (Unity desktop) [KDE Bug 352889] === Version 2.11.1 (KDE Applications 15.08.1) --- 1 September 2015 === * Fix conversion error in sub-repetition value from command line or D-Bus command. === Version 2.11.0 (KDE Applications 15.08.0) --- 30 July 2015 === * Use KDE Frameworks. * Disable use of sendmail for email alarms, due to removal from Akonadi. === Version 2.10.12 (KDE 4.14.2) --- 30 September 2014 === * Make New Audio Alarm dialogue use sound file repeat preference setting. === Version 2.10.11 (KDE 4.14.0) --- 12 August 2014 === * [Akonadi] Fix alarms not being redisplayed after Akonadi server restarts (requires kdepimlibs 4.14.0) [KDE Bug 336942] === Version 2.10.10 (KDE 4.13.2) --- 10 May 2014 === * [Akonadi] Fix no Defer button in alarm windows restored after login [KDE Bug 334334] * Fix display of duplicate alarm windows after login. === Version 2.10.9 (KDE 4.13.1) --- 4 May 2014 === * [Akonadi] Fix no Defer button in alarm windows restored after crash [KDE Bug 334334] === Version 2.10.8 (KDE 4.12.5) --- 18 April 2014 === * [Akonadi] Fix wrong startup message about no writable active alarm calendar. * [Akonadi] Fix setting Akonadi resource read-only making it unusable (requires kdepim-runtime 4.12.5) [KDE Bug 332889] === Version 2.10.7 (KDE 4.12.4, 4.13.0) --- 21 March 2014 === * [Akonadi] Fix deletion of alarm copies from KOrganiser not working. * Fix crash after session restoration has nothing to restore [KDE Bug 331719] * Prevent data in birthday import dialogue being editable. === Version 2.10.6 (KDE 4.11.1) --- 27 August 2013 === * [Akonadi] Fix error saving template when closing Edit Template dialogue [KDE Bug 323965] === Version 2.10.5 (KDE 4.11.0) --- 3 August 2013 === * Fix memory leak whenever the edit dialogue is closed. * Fix auto-close alarms not displaying when KAlarm defaults to UTC time zone. * Fix display alarm deferral limit when KAlarm defaults to UTC time zone. === Version 2.10.4 (KDE 4.11 beta2) --- 15 June 2013 === * Show startup warning if no writable active alarm calendar is enabled [KDE Bug 316338] === Version 2.10.3 (KDE 4.10.5) --- 15 June 2013 === * Fix sound repetition pause not working in audio alarms [KDE Bug 319261] * Fix Stop Play button being left enabled after closing alarm window. === Version 2.10.2 (KDE 4.10.4) --- 4 May 2013 === * [Akonadi] Fix infinite loop on shutdown if display alarms are active [KDE Bug 317806] === Version 2.10.1 (KDE 4.10.0) --- 10 December 2012 === * [Akonadi] Fix memory leak when an alarm message window is displayed. * [Akonadi] Fix memory leak on alarm edit. === Version 2.10.0 (KDE 4.10 beta1)--- 13 November 2012 === * Add --list command line option to list scheduled alarms to stdout. * Add 'list' D-Bus command to return list of scheduled alarms. * [Akonadi] Wait until calendars are populated before using them at startup. === Version 2.9.3 (KDE 4.9.4) --- 13 November 2012 === * [Akonadi] Fix alarm list not sorting new alarms when calendar is enabled [KDE Bug 306178] === Version 2.9.2 (KDE 4.9.1) --- 22 August 2012 === * Fix Quit not working in system tray icon context menu. * [KResources] Fix KAlarm button not highlighting the alarm in the main window [KDE Bug 266082] === Version 2.9.1 (KDE 4.9.0) --- 7 July 2012 === * Add option to execute a pre-alarm action before deferred alarms. * Provide options to auto-hide system tray icon when no alarms are due. * Store KAlarm version and backend in config file. === Version 2.8.6 (KDE 4.8.5) --- 14 July 2012 === * [Akonadi] Don't display calendars which have no Akonadi resource. * [Akonadi resources] Fix resource if config is missing. * [Akonadi resources] Make resource work if location is set by path OR URL. * Fix crash when closing alarm window for alarm which plays audio file. * Fix "server did not accept the sender address" errors sending emails [KDE Bug 301946] === Version 2.8.5 (KDE 4.8.4) --- 6 June 2012 === * [Akonadi] Warn user and disable KAlarm if Akonadi fails to run [KDE Bug 300083] * [Akonadi] Fix crash when saving new alarm [KDE Bug Bug 300376] === Version 2.8.3 (KDE 4.8.3) --- 22 April 2012 === * Store KAlarm version and backend in config file. * Use the last selected sound file picker directory as the default next time. === Version 2.8.2 (KDE 4.8.2) --- 29 March 2012 === * [Akonadi] Fix error saving changed alarms when closing Edit Alarm dialogue. * [Akonadi] Show old-format calendars in read-only colour in calendar list. * [KResources] Fail cleanly if calendar resources fail to open [KDE Bug 296383] * Prevent multiple email success messages after Try used in Edit Alarm dialogue. === Version 2.8.1 (KDE 4.8.1) --- 19 February 2012 === * [Akonadi] Don't give option to save new alarms in old format calendars. * [Akonadi] Prevent duplicate prompts to update format of new calendar resource. * [Akonadi] Automatically disable duplicated calendar resources [KDE Bug 293193] * [Akonadi] Fix errors when creating default calendar resources [KDE Bug 293208] * [Akonadi] Prevent multiple standard calendars for any alarm type. * [Akonadi] Fix various crashes. * Output cmake error if Akonadi option incompatible with kdepimlibs/kalarmcal. === Version 2.8.0 (KDE 4.8.0) --- 16 January 2012 === * Use Akonadi as the default calendar access method. * Use configurable colours and KDE colour scheme for calendar list. * Allow user to stop playback after clicking Try in audio alarm edit dialogue. === Version 2.7.5 (KDE 4.7.4) --- 23 November 2011 === * Fix crash due to audio thread not being correctly deleted. === Version 2.7.4 (KDE 4.7.1) --- 28 August 2011 === * Fix crash when last recurrence of late-cancel alarm triggers too late. * Fix conversion of pre-version 1.4.14 subsidiary alarms. * Fix new alarm not being scheduled after editing alarm from alarm window. * Don't do search if invalid regular expression is entered in Find dialogue. * Don't prevent interaction with alarm windows when a prompt or warning message window is displayed [using KDE 4.7.1 or later]. * Only reset visible tab in multi-tab settings sections when Defaults is clicked in Configuration dialogue, and Current tab option is selected. * Disable command output option for display alarms in edit alarm dialogue if user not authorised to run shell commands. * Always output "not authorised" error message if unauthorised user tries to run shell commands. === Version 2.7.3 --- 26 July 2011 === * Fix crash when Wake From Suspend dialogue is shown with no alarm selected. === Version 2.7.2 --- 15 July 2011 === * Fix KAlarm not quitting when no visible windows or system tray icon remain. * Cancel wake-from-suspend if alarm is disabled, or if all alarms are disabled. * Various improvements and bug fixes to Wake From Suspend dialogue. * In calendar list show calendar colours by text background, not coloured square. * In alarm list show multi-line tooltip for alarm text when appropriate. === Version 2.7.1 (KDE 4.7.0) --- 6 July 2011 === * Make wake-from-suspend schedule a time-from-now, to make it work correctly on systems whose hardware clock is out of sync with the system clock. * Include Content-Transfer-Encoding header in emails to allow correct display. === Version 2.7.0 --- 9 May 2011 === * Add option to set a reminder AFTER the main alarm. * Add option to wake computer from suspend when a selected alarm is triggered. * Add command line option to disable alarm monitoring. * Replace EMAILID, SPEAK, ERRCANCEL, ERRNOSHOW calendar properties with FLAGS property parameters. === Version 2.6.3 --- 27 April 2011 === * Add option to not notify execution errors for pre-alarm actions. * Set environment variable KALARM_UID to event UID for pre- & post-alarm actions. * Warn user if only UTC time zone is available (if ktimezoned not installed). * Don't reactivate start-at-login without prompting, after user switches it off, except if KAlarm is session restored. * Show error message and set read-only if location is blank for new resource. * Fix crash on some systems when New Alarm dialogue is displayed from system tray icon menu. * Fix KAlarm button in alarm window not always showing main window and not highlighting the alarm in the main window. * Move New Alarm From Template action into New alarm menu to simplify toolbar. === Version 2.4.11 (KDEPIM 4.4.11) --- 16 April 2011 === * Fix bad borders round left hand buttons of time spinboxes in Oxygen style. * Fix initialisation of library global statics. * Ensure sound volume is not out of range when reading from calendar. * Fix New Alarm dialogue from system tray menu restoring other windows. === Version 2.4.10 (KDEPIM 4.4.8) --- 2 December 2010 === * Fix KAlarm showing in system tray at login when configured not to show in tray. * Fix working-time-only alarms not triggering if KAlarm is started up outside working hours, after the last trigger time during working hours was missed. * Don't quit if no window is visible when 'show in system tray' is deselected. * Disable Defer button in new message window when deferral limit has been reached. * Fix reminder time shown when editing a non-recurring alarm's deferred reminder. * Fix conversion of pre-version 1.9.10 non-recurring alarms with simple repetition. * Make disabled system tray icon more distinguishable for colour blind users. === Version 2.4.9 (KDEPIM 4.4.7) --- 19 October 2010 === * Fix crash if alarm triggers while its deletion confirmation prompt is visible. * Fix crash when Try button is clicked while creating new display alarm. * Fix crash on KAlarm exit. * Fix possible crash when enabling individual alarms. * Prevent long file name from expanding the width of file display alarm window. * Allow pre- & post-alarm actions for alarms whose text is generated by a command. * Combine 4 New Alarm icons in toolbar, to fix icon texts not fitting into width. === Version 2.4.8 (KDEPIM 4.4.6) --- 4 September 2010 === * Fix crash when a reminder alarm is being redisplayed. * Fix possible crash: on alarm deletion, always update next alarm to trigger. * Fix Sound File selection dialogue Play button not playing any sound. * Always show current storage location choice in Configuration dialogue. * Fix inability to leave file name blank in audio alarm templates. * Fix changes to volume not enabling OK button when editing an audio alarm template with no audio file specified. === Version 2.4.7 (KDE 4.4.5) --- 3 June 2010 === * Fix inability to defer non-recurring alarms. * Fix crash when selecting calendar type in calendar selector, if text widths and selector width are "exactly wrong". * Fix loss of time zone specification for date only alarms when converting a pre-2.3.2 calendar, if start-of-day time in calendar is not midnight. * Enable alarm edit dialogue Time Zone button in read-only mode. === Version 2.4.6 (KDE 4.4.4) --- 20 May 2010 === * Fix alarm edit dialog not saving changes when invoked from alarm message window's Edit button. * Fix main window close action not working when system tray icon is not shown. === Version 2.4.5 (KDE 4.4.3) --- 7 April 2010 === * Fix audio files playing silently when no volume level has been specified. === Version 2.4.4 (KDE 4.4.2) --- 17 March 2010 === * Fix display alarm whose text is generated by a command and which has an audio file, being converted into an audio-only alarm when reloaded. === Version 2.4.3 (KDE 4.4.1) --- 21 February 2010 === * Disable resource calendars which contain only wrong alarm types. === Version 2.4.2 (KDE 4.4.0) --- 30 January 2010 === * Fix non-ASCII text being corrupted in emails sent by KAlarm. * Show error message if selected email identity has no email address. === Version 2.4.1 (KDE 4.4.0 RC1) --- 8 December 2009 === * Fix date-only recurring alarms triggering repeatedly at high frequency. === Version 2.4.0 --- 24 November 2009 === * New audio alarm option, without displaying alarm window. * Add configuration setting for event duration for alarms copied to KOrganizer. * Provide 'any time' option in Defer Alarm dialogue, for date-only alarms. * Use KDE system settings to determine default working days in the week. * Improve organisation of main menu. * If dual screens, show alarm in other screen if any full screen window exists. * Fix recurring date-only alarm triggering repeatedly and eating up CPU, if the start-of-day time is after midnight and the alarm is due, but current UTC time of day is earlier than the start-of-day time of day in the alarm's time zone. * Update date-only alarm trigger times when user changes the start-of-day time. * Don't write start-of-day time into calendar, to avoid clashes if it is shared. * Don't waste processing time calculating next trigger time for archived alarms. * Disable 'New Alarm from Template' action when no alarm templates exist. * Interpret '~' (i.e. home directory) properly in entered file names. * Fix crash if calendar formats are updated at login, during session restoration. * Fix crash if editing alarm from alarm window Edit button, and window changes from reminder to normal, or window changes from at-login to final at-login trigger time, or window auto-closes. * Prevent infinite loop if NEXTRECUR time in alarm is before alarm start time. * Fix error saving the alarm after editing a repeat-at-login alarm. * Don't set reminder/late-cancel/show-in-KOrganizer when saving repeat-at-login alarms. * Improve error feedback in sound file selection. * Prevent sound file configuration dialogue closing after showing error message. === Version 2.3.0 --- 10 July 2009 === * Alarm edit: warn user if entered start time needs adjustment to fit recurrence. * Command alarm edit: show error message if no command/script has been entered. * Allow use of other command line options with --edit-new-* to initialise edit dialogue options. * Improve detection of conflicting command line options. === Version 2.2.4 --- 23 June 2009 === * Alarm edit: keep existing display file name if file select dialogue cancelled. * Guard against crashes if KAlarm quits while a modal dialogue is open. * Fix crash creating alarm from command line, if KAlarm not already running. * Fix --reminder-once command line option being treated same as --reminder. === Version 2.2.3 --- 14 June 2009 === * Fix crash when more than one alarm with audio is displayed simultaneously. === Version 2.2.2 --- 10 June 2009 === * Fix email alarms sending multiple mails, when sent by KMail. * Fix crash when closing remote calendars. === Version 2.2.1 --- 25 May 2009 === * Include new handbook translation: Ukrainian. === Version 2.2.0 --- 29 April 2009 === * Provide facility to export alarms to a new calendar file. * Provide option to spread alarm and error messages over screen. * Show command execution error indication for alarms in main window alarm list. * Add configuration setting for default deferral time in Defer Alarm dialogue. * Accept drag and drop of Todo entries to create a new alarm. === Version 2.1.8 (KDE 4.2.4) --- 25 May 2009 === * Fix crash on exit from birthday import dialogue. * Fix crash when an alarm is open for edit when its last occurrence triggers, and the edit is then saved. * Fix another possible crash when KAlarm quits. * Don't show time in alarm list for date-only alarms without time zone (e.g. those created by Import Birthdays). === Version 2.1.7 (KDE 4.2.3) --- 29 April 2009 === * Fix recurring alarms being missed when deferred to earlier than next due alarm, when next due alarm is earlier than the next recurrence. * Fix crash at startup if a non-recurring cancel-if-late alarm has been missed. * Fix speech mode not working when alarm messages are displayed. * Fix KAlarm hanging sometimes while trying to play an audio file. * Fix crash when KAlarm quits. * Fix memory leak with undo/redo. === Version 2.1.6 (KDE 4.2.2) --- 18 March 2009 === * Fix memory leaks. * Fix crash when KAlarm quits. === Version 2.1.5 (KDE 4.2.1) --- 7 February 2009 === * Disable inapplicable alarm types in alarm edit dialogue Load Template list. * Prevent multiple identical error messages being displayed for the same alarm. * Fix possible crash on alarm refresh, or removal or disabling of a resource. === Version 2.1.4 (KDE 4.2) --- 18 January 2009 === * Prevent corrupt alarms if deferral reinstates from archived alarm instead of from the displaying calendar. * Ignore events in calendar without usable alarms (which prevents them getting stuck in the alarm list, and fixes high CPU usage). * Show error message when New Template selected but no writable resource exists. * Fix crash when iCalendar item is dragged and dropped onto KAlarm. * Make New Alarm shortcuts work. * Fix alarms not being saved if created by drag-and-drop but not edited further. === Version 2.1.3 (KDE 4.2 RC1) --- 5 January 2009 === * Fix invalid alarm remaining in calendar when pre-alarm action failure message is acknowledged before the alarm is deferred. === Version 2.1.2 --- 27 December 2008 === * New KAlarm icon. * Distinguish disabled from enabled alarm colour when highlighted in alarm list. * Ensure alarm windows show on top of full-screen windows. * Fix crash if KAlarm is activated again while restoring from previous session. * Fix kalarmautostart crash on logout while kalarmautostart is still running. * Fix click on system tray icon not showing main window if 'Show in system tray' configuration setting deselected. === Version 2.1.1 (KDE 4.2 beta2) --- 8 December 2008 === * Allow global shortcuts for New Alarm actions. * Fix failure to update alarms in KOrganizer when Kontact is running but Kontact's calendar component is not loaded. * Fix toolbar configuration being lost after quitting KAlarm. === Version 2.1.0 (KDE 4.2 beta1) --- 13 November 2008 === * Add option to exclude holidays from recurring alarms. * Provide More/Less Options button in edit alarm dialogue. * Improve Configuration dialogue layout, split pages into tabs. * Show separate toolbar buttons for new display, command and email alarms. * Show 'Time Zone' button instead of time zone selection controls when using default time zone. * Set file display alarm font & colour in same way as for text display alarms. * Set default reminder time units according to how long until alarm is due. === Version 2.0.6 (KDE 4.1.3) --- 22 October 2008 === * Fix alarms not triggering correctly after laptop wakes from hibernation. * Fix inability to change or cancel alarm deferral times. * Prevent defer dialogue date being set outside the allowed range. * Set background colour for file display alarm text. * Don't wrap lines in file display alarm message windows. * Fix addition and deletion of alarms to KOrganizer. === Version 2.0.5 --- 27 September 2008 === * Fix very high CPU usage by KAlarm when there are alarms with sub-repetitions, or deferrals, with periods greater than 1 week. Fix requires kdepimlibs 4.1.3. === Version 2.0.4 (KDE 4.1.2)--- 24 September 2008 === * Add work-time-only parameter for D-Bus calls to create new alarms. === Version 2.0.3 --- 7 September 2008 === * Double click accepts selected template in pick list. * Make text in edit alarm dialogue change colour when foreground colour changed. * Replace colour combo boxes by buttons which display standard KDE colour picker. === Version 2.0.2 (KDE 4.1.1) --- 27 August 2008 === * Show alarm text entry fields in the current alarm message colours. * Show background colour selector for file display alarms. * Set KDE sound files directory as default for picking sound files. * Fix width of buttons containing only an icon. * Change Control Center references to System Settings. * Fix formatting of file display alarms for non-HTML text files. * Fix crash when birthday dialogue is opened more than once. * Prevent quitting when main window is closed but system tray icon is visible. === Version 2.0.2 --- 4 August 2008 === * Set KDE sound files directory as default for picking sound files. * Fix width of buttons containing only an icon. * Change Control Center references to System Settings. === Version 2.0.1 (KDE 4.1) --- 17 July 2008 === * Double click in template dialogue list activates template edit dialogue. * Fix KAlarm quitting on closing message window when no main window visible. * Fix KAlarm crashing when quitting. === Version 2.0.0 --- 7 July 2008 === * New facility to use multiple alarm calendar resources. * Add facility to select time zone for alarm times. * Handle summer/winter time changes correctly. * New option to trigger a recurring alarm only during working hours. * Add option for display alarm text to be generated by a command. * Provide "Don't show again for this alarm" option for command error messages. * Alarm edit dialogue layout improvements. * Make alarm edit and preferences dialogues scrollable if too high for screen. * Choose new alarm/template type from menu instead of in alarm edit dialogue. * Add option to show alarm windows in centre of screen, with buttons initially disabled to prevent accidental acknowledgement. * Remove alarm daemon (kalarmd) and do alarm monitoring in KAlarm itself. * Remove --handleEvent command line option. * Use custom properties instead of CATEGORIES in calendar events for KAlarm data. * Don't discard non-KAlarm custom event properties when editing alarms. * Use kconf_update to convert old config file settings. * Change numeric codes in config file to strings for long-term maintainability. * Rename Defaults section options in config file. * Fix detection of yearly February 29th recurrences on Feb 28th or Mar 1st. === Version 1.5.3 --- 16 June 2008 === * In New From Template menu, show list of template names in sorted order. * Fix recurrence count being lost when using alarm templates. * Prevent invalid negative values appearing in 'Time from now' edit field. * Fix time shown in alarm edit dialogue for recurring alarms. * Fix recurrence count shown in alarm edit dialogue once alarm has triggered. * Fix Find not working with a new search text after a failed search. * Display correct error message when a search fails. * Prevent user changing font/colour dialogue when editing read-only alarms. === Version 1.5.2 --- 13 February 2008 === * Prevent repetition duration error message when saving alarm which never recurs. === Version 1.5.1 (KDE 3.5.9) --- 13 February 2008 === * Fix inability to set up sub-repetitions for simple yearly recurrences. === Version 1.5.0 --- 23 January 2008 === * Replace simple repetitions with recurrence sub-repetitions, to save confusion. * Add option to enter reminder times in minutes, in addition to hours/minutes. * Replace alarm edit dialogue background colour selector with font/colour sample. * Store email unique IDs instead of names in email alarms to prevent problems if email IDs are renamed. * Fix error "Sender verify failed (in reply to RCPT TO command)" using sendmail on some systems, by adding envelope sender address to emails. * Fix OpenSolaris build error. === Version 1.4.21 --- 19 December 2007 === * Remember last used main window show/hide options instead of setting them in Preferences dialogue. * Make the Menu key work in the alarm list. * Fix crash when saving preferences, if 'xterm' is not installed in the system. * Prevent multiple identical error messages being displayed for the same alarm. === Version 1.4.20 --- 18 November 2007 === * Fix deferral of non-recurring alarms not working. * Fix loss of reminder details in archive when alarm has had a reminder deferred. * Fix inability to reactivate deleted alarms which still have repetitions to go. * Fix incorrect interpretation of --late-cancel weekly parameter on command line. === Version 1.4.19 --- 11 November 2007 === * Fix KAlarm hanging and freezing the system for a while, especially on startup. * Fix next occurrence time set after editing alarm, when it's a sub-repetition. * Prevent error messages while typing date value, until user finishes entering it. === Version 1.4.18 --- 2 November 2007 === * Fix failure to trigger some recurring date-only alarms (e.g. after suspend-resume). * Fix date-only alarms triggering every minute from midnight to start-of-day time. * Simplify recurrence text shown in alarm edit dialogue Alarm tab when possible. * Prevent error after browsing for command log file, due to file:// prefix. === Version 1.4.17 (KDE 3.5.8) --- 8 October 2007 === * Allow time-from-now values up to 999 hours to be entered. * Fix incorrect email headers resulting in failure to send some emails. === Version 1.4.16a --- 12 September 2007 === * Fix failure to retrieve font and colour settings for display alarms. === Version 1.4.16 --- 10 September 2007 === * Attempt to fix failure to retrieve font and colour settings for display alarms. * Disable reminder etc. controls for at-login recurrence in alarm edit dialogue. === Version 1.4.15 --- 7 September 2007 === * Fix deferrals of recurring alarms not triggering correctly. * Fix failure to archive details of repetitions within a recurrence. * Enable/disable "Show expired alarms" action when preferences change. === Version 1.4.14 --- 5 August 2007 === * Fix handling of exception dates in recurrences. * In sound file dialogue change Play button to a Stop button while playing a file. === Version 1.4.13 --- 18 May 2007 === * Fix time value in templates not being stored. * Expand time spin boxes to make room for all digits. * Make Preferences dialogue non-modal. === Version 1.4.12 (KDE 3.5.7) --- 11 May 2007 === * Display advance reminders for each occurrence of recurring alarms. * Fix Undo of deletion of active alarms. * Disable simple repetition controls if repetitions can't fit between recurrences. * Make the system tray tooltip take account of alarm repetitions. * Show repetition & special action status by button states in alarm edit dialogue. * Fix reminder alarms displaying very big numbers for how long until alarm is due. * Fix KMail omitting attachments from email alarms (if KMail is the email client). === Version 1.4.11 --- 16 April 2007 === * Prevent pre-alarm actions being executed multiple times when alarm is triggered. * Prevent alarm daemon triggering alarms multiple times. * Only execute pre-alarm actions once (not for reminders or deferrals). * Only execute post-alarm actions once when alarm is finally acknowledged (after any deferrals), and not after reminders. * Show file name as a tooltip on sound type combo box when "file" is selected. === Version 1.4.10 --- 3 March 2007 === * Add play button to sound file selection dialogue. * Prevent simple repetitions triggering again when KAlarm is restarted. * Fix recurring alarms being triggered on exception days. * Fix start-of-day time being ignored for date-only alarms. * Disable Defer button in new message window when deferral limit has been reached. * Fix failure to save "Execute in terminal window" option in Preferences dialogue. * Ensure up-to-date menus are displayed if user has a customised toolbar. === Version 1.4.9 (KDE 3.5.6) --- 3 January 2007 === * Minor changes. === Version 1.4.8 --- 28 December 2006 === * Fix Find always using first search text entered even after entering a new one. === Version 1.4.7 --- 14 December 2006 === * Fix crash saving Preferences dialogue (due to command alarm terminal setting). === Version 1.4.6 --- 30 November 2006 === * Fix crash if an alarm triggers while user is deleting it. * Fix "Start alarm monitoring at login" value shown in preferences dialogue. * Fix deselecting "Start alarm monitoring at login" when daemon not running. * Fix editing of 29th February alarm options for non-leap years. * Tidy up preferences dialogue Run mode options. * Tidy up alarm edit/preferences dialogue sound type options into a combo box. * Add context help for sound file fade options. === Version 1.4.5 (KDE 3.5.5) --- 29 September 2006 === * Improve alarm edit dialogue layout (Reminder controls moved to below Time box). === Version 1.4.4 --- 11 July 2006 === * Use an alarm's previous deferral time interval as default for its next deferral. === Version 1.4.3 (KDE 3.5.4) --- 11 July 2006 === * Add facility to import alarms from other calendar files. * Fix Defer dialog time interval maximum to match maximum date/time value. * Fix crash when a deferred expired recurring alarm is edited from message window. * Fix crash when a message is redisplayed after login. * Prevent inapplicable 'Unable to speak' error when alarm redisplayed after login. * Save main window column order changes to use on restart (except message column). === Version 1.3.10 (KDE 3.5.3) --- 22 May 2006 === * Add DCOP calls and command line options to display the edit alarm dialogue. * Add Select All and Deselect actions & shortcuts for import birthdays list. * Make system tray icon appear in non-KDE window managers. * Output error message if deleting copy of alarm from KOrganizer fails. * Fix corruption of alarms displayed at logout and then deferred after login. * Fix reminder time not being saved in alarm templates. * Fix erroneous date adjustment of start of recurrence when saving alarm. * Fix crash when --play command line option is used, if compiled without aRts. * Don't show disabled alarms in system tray tooltip alarm list. === Version 1.3.9 (KDE 3.5.2) --- 7 March 2006 === * Notify daemon by DCOP that alarm has been processed: to prevent alarm loss, and to prevent defunct kalarm processes when run mode is on-demand. * Add Select All and Deselect actions & shortcuts for alarm and template lists. === Version 1.3.8 --- 24 January 2006 === * Fix kalarmd hang when triggering late alarm and KAlarm run mode is on-demand. === Version 1.3.7 --- 22 January 2006 === * Fix column widths when main window is resized, if columns have been reordered. === Version 1.3.6 (KDE 3.5.1) --- 10 January 2006 === * Make autoclose of message windows work. * Fix New From Template not creating alarm if template contents are not changed. * Ensure that day and month names translations are independent of locale calendar. * Display alarm message windows within current screen in multi-head systems. * Reduce size of Preferences dialog to fit in 1024x768 screen. === Version 1.3.5 --- 14 December 2005 === * Fix email attachments being forgotten when saving alarms. * Fix toolbar configuration being lost after quitting KAlarm. === Version 1.3.4 (KDE 3.5) --- 30 October 2005 === * Fix incorrect recurrence frequency in Alarm Edit dialogue's Alarm tab. === Version 1.3.3 --- 22 September 2005 === * Add day-of-week selection to daily recurrence dialog. === Version 1.3.2 (KDE 3.5 beta 1) --- 10 September 2005 === * Add option to show alarms in KOrganizer's active calendar. * Add option for email text alarms to locate the email in KMail. * When email alarm triggers and KMail isn't running, start KMail and send mail automatically instead of opening KMail composer window. * Provide per-alarm option for yearly February 29th recurrences. * Wait longer (20 seconds) before reporting alarm daemon registration failed. * Minimise KMix window if KMix is started by KAlarm when displaying a message. * Fix Plastik style 'enabled' indication for time spinbox left-hand buttons. * Prevent message windows always being full screen after a big message is shown. * Prevent message windows being initially larger than the desktop. * Prevent message windows initially overlapping the KDE panel. * Prevent session restoration displaying main windows which should be hidden. * Fix alarms getting stuck if due during a daylight savings clock change. * Change --volume command line option short form to -V (-v is used by --version). * Fix reported shell errors when output from command alarm is discarded. * Use 'KAlarm' untranslated in calendar product ID, to cater for locale changes. === Version 1.3.1 --- 30 May 2005 === * Add Undo/Redo facility for alarm edit/creation/deletion/reactivation. * Add text search facility. * Add option to speak alarm messages (if speech synthesis is installed). * Add command line option --speak. * Add 'New alarm from template' menu option and toolbar button. * Add 'Time from now' option in alarm templates. * Add fade option for playing sound files. * Add option to log command alarm output to a file. * Add Edit button to alarm message window to allow the alarm to be edited. * Enable drag and drop of alarms to other applications. * Email drag-and-drop from KMail (KDE >= 3.5) now presets alarm edit dialog with full From/To/Cc/Subject headers and body text. === Version 1.2.8 (KDE 3.4.1) --- 9 May 2005 === * Fix failure to enable "Reminder for first recurrence only" checkbox. === Version 1.2.7 --- 20 April 2005 === * Use a sensible default for terminal window command in Preferences dialog. * Validate terminal window command entered in Preferences dialog. * Fix date range no longer being validated in Defer dialog. * Don't ignore Sound setting in Preferences dialog Edit tab. * Reset sound volume (if it was set) as soon as audio file playing is complete. * Don't start KMix when an alarm is displayed if no sound volume is specified. * Add command script and execute-in-terminal options to DCOP interface. === Version 1.2.6 (KDE 3.4) --- 22 February 2005 === * Pop up message windows far from cursor to avoid accidental acknowledgement. * Start KMix if not already running, for setting alarm sound level. * Fix alarms not triggering if IDs are duplicated in different calendar files. * Improve validation when reading configuration file values. === Version 1.2.5 (KDE 3.4 beta2) --- 21 January 2005 === * Prevent multiple "Failed to start Alarm Daemon" error messages at startup. * Fix missing left border for time spinboxes in Plastik style. === Version 1.2.4 (KDE 3.4 beta1) --- 9 January 2005 === * Provide option to enter a script for a command alarm, instead of a command line. * Add option to run command alarms in terminal windows. * Accept drag and drop of KAddressBook entries to alarm edit dialog email fields. * Drag and drop now inserts text where appropriate, rather than replacing it. * Display correct controls after loading a template in alarm edit dialog. === Version 1.2.3 --- 7 December 2004 === * Put alarm type icons in a separate, sortable, column in alarm list. * Align times in alarm list. * Fix crash when the last recurrence of an alarm is reached. * Fix random limit on expired alarm discard time if stepping with spinbox buttons. * Fix dialog layouts for right-to-left languages. * Fix time spin box layout for right-to-left languages. === Version 1.2.2 --- 27 November 2004 === * Make alarm daemon (kalarmd) exclusive to KAlarm. * Move control options for alarm daemon into KAlarm preferences dialog. * Allow user to specify the late-cancellation period for an alarm. * Add option to automatically close window after late-cancellation period. * Add facility to enable and disable individual alarms. * Add simple repetition facility, including repetition within a recurrence. * Add option to pick a KMail identity to use as sender of email alarms. * Add option to copy emails sent via sendmail, to KMail sent-mail folder. * Show scheduled times, not reminder times, in alarm list and system tray tooltip. * Make time edit controls use 12-hour clock when that is the user's default. * Also fill in alarm edit dialog email fields when email is dropped onto KAlarm. * New revised DCOP request interface (old interface still kept for compatibility). * Make detection of email message display alarms independent of language. * Use KMix whenever possible to set hardware sound volume. * Limit range of entered date/time to valid values in deferral dialogue. * Prevent kalarm failing to register with kalarmd except when really necessary. * Fix time-to-alarm column in main window not always updating every minute. === Version 1.1.7 (KDE 3.3.2) --- 27 November 2004 === * Fix KAlarm button on message windows to make it always display main window. * Show scheduled times, not reminder times, in alarm list and system tray tooltip. * Fix time-to-alarm column in main window not always updating every minute. === Version 1.1.6 (KDE 3.3.1) --- 30 September 2004 === * Prevent crash, and output error message, if menu creation fails. * Unsuppress Quit warning message box if default answer is Cancel quit. * Prevent blind copy to self of email alarms via KMail when bcc is deselected. === Version 1.1.5 --- 1 September 2004 === * Show erroneous control in alarm edit dialog when an error message is displayed. * Make alarm edit dialog always appear on current desktop. * Make weekly/monthly/yearly recurrences scheduled from command line correspond correctly to the start date. * Fix start date for monthly/yearly recurrences scheduled from the command line. * Fix DCOP triggerEvent() call to not reschedule alarm if it isn't due yet. === Version 1.1.4 --- 21 August 2004 === * Fix errors when altering or cancelling deferrals of expired recurrences. === Version 1.1.3 (KDE 3.3) --- 28 July 2004 === * Fix dialog sizing the first time KAlarm is run. === Version 1.1.2 (KDE 3.3 beta2) --- 11 July 2004 === * Fix hangup in interactions with alarm daemon introduced in version 1.1.1. * Only tick Alarms Enabled menu items once alarms have actually been enabled. * Fix build for "./configure --without-arts". === Version 1.1.1 (KDE 3.3 beta1) --- 20 June 2004 === * Output error message and disable alarms if can't register with alarm daemon. * Exit if error in alarm calendar name configuration. * Fix bug where sound file is selected even when Cancel is pressed. === Version 1.1.0 --- 1 June 2004 === * Add facility to define alarm templates. * Add facility to specify pre- and post-alarm shell command actions. * Add option to play sound file repeatedly until alarm window is closed. * Add volume control for playing sound file. * Add 'stop sound' button to alarm message window when sound file is played. * Rename command line option --sound to --play, add option --play-repeat. * Add command line option --volume. * Add 'Configure Shortcuts' and 'Configure Toolbars' menu options in main window. * After creating/editing alarm, prompt to re-enable alarms if currently disabled. * Middle mouse button over system tray icon displays new alarm dialog. * Add option to display a reminder once only before the first alarm recurrence. * Display time-to-alarm in reminder message window. * For message texts which are truncated in main window, show full text in tooltip. * Allow time of day to be entered in format HHMM in time spin boxes. * Allow hour to be omitted when colon format time is entered in time spin boxes. * Add "Don't ask again" option to alarm deletion confirmation prompt. * Prevent expired alarm calendar purges clashing with other alarm actions. * Fix initial recurrence date/time for weekly/monthly/yearly recurrences. * Fix yearly recurrences of the last day in the month. * Disable yearly recurrence's month checkboxes depending on selected day of month. * Update which time columns are displayed in alarm list when Preferences change. * Don't store audio/reminder details in email/command alarms. * Don't store email details in message/file/command alarms. * Don't close message windows when quit is selected. * Fix "Warn before quitting" configuration option. * Don't redisplay error message windows on session restoration. * Remove obsolete --displayEvent command line option (replaced by --triggerEvent). * Remove obsolete pre-version 0.7 DCOP calls. === Version 1.0.7 --- 2 May 2004 === * Fix scheduleCommand() and scheduleEmail() DCOP handling. * Make KAlarm build for "./configure --without-arts". * Fix email body text not being saved in email alarms. * Fix loss of --exec command line arguments. * Remove wasted vertical space from message windows. === Version 1.0.6 (KDE 3.2.2) --- 26 March 2004 === * Make the Quit menu item in main window quit the program. * Update time entry field after editing as soon as mouse cursor leaves it. * Cancel deferral if reminder is set before it, to prevent it becoming stuck. * Prevent undeleted recurring alarms being triggered immediately. * Don't allow alarms to be undeleted if they are completely expired. === Version 1.0.5 (KDE 3.2.1) --- 24 February 2004 === * Fix whatsThis text on bottom row of alarm list. === Version 1.0.4 --- 22 February 2004 === * Fix freeze at login when multiple alarms trigger. * Show all audio file types in sound file chooser dialogue. === Version 1.0.3 --- 15 February 2004 === * Prevent email alarms from being sent if no 'From' address is configured. * Omit 'Bcc' when sending email alarms if no 'Bcc' address is configured. * Fix freeze when starting the alarm daemon. * Fix memory leaks displaying dialogs. * Fix scheduleCommand() and scheduleEmail() DCOP handling. * Fix errors saving expired alarm calendar. === Version 1.0.2 (KDE 3.2) --- 29 January 2004 === * Prevent editing alarm and saving without changes from deleting the alarm. === Version 1.0.1 --- 4 January 2004 === * Fix failure to see alarms if KAlarm is reactivated while restoring session. === Version 1.0.0 --- 7 December 2003 === * Allow entered start date for timed recurrence events to be earlier than now. * Prevent attempted entry of recurrence end date earlier than start date or today. * Fix error displaying time of expired repeat-at-login alarms. * Fix memory leak when sending emails with attachments. * Fix error trying to send emails with very small attachments. * Eliminate duplicate reload-calendar calls to alarm daemon. === Version 0.9.6 (KDE 3.2 beta1) --- 7 November 2003 === * Add option to choose foreground colour for alarm messages. * Create new alarm by dragging KMail email onto main window or system tray icon. * Set initial recurrence defaults to correspond to alarm start date. * Add option for how February 29th recurrences are handled in non-leap years. * Monthly/yearly recurrence edit: adhere to user preference for start day of week. * Eliminate multiple confirmation prompts when deleting multiple alarms. * Eliminate duplicate alarms in system tray tooltip. * Fix crash after reporting error opening calendar file. * Fix wrong status in system tray icon if KAlarm starts up with alarms disabled. * Fix wrong number of days in Time-to-alarm column in main window. * Fix omission of deferred alarms from system tray tooltip. === Version 0.9.5 --- 3 September 2003 === * Add option for non-modal alarm message windows. * Add option to display a notification when an email alarm queues an email. * Emails via KMail are sent without opening composer window, if KMail is running. * Provide separate configuration for 'From' and 'Bcc' addresses for email alarms. * Add exceptions to recurrence specification. * Add multiple month selection to yearly recurrence. * Add day of month selection in yearly recurrence. * Add last day of month option in monthly and yearly recurrences. * Add 2nd - 5th last week of month options in monthly and yearly recurrences. * Add filename completion to file and command alarm edit fields. * Display alarms-disabled indication in system tray tooltip. * Enable file alarms to display image files. * Fix file alarms not dislaying some text files, and improve HTML file display. * Fix loss of changes to attachment list after editing email alarms. * Fix wrong recurrence end date being displayed when editing an existing alarm. === Version 0.9.4 --- 3 July 2003 === * Add time-to-alarm display option to main alarm list. * Add option to list next 24 hours' alarms in system tray tooltip. * Create new alarm by dragging text or URL onto main window or system tray icon. * Display reasons for failure to send an email. * Allow editing of the list of message colours. * Edit new alarm by context menu or double click on white space in alarm list. * Add show expired alarms option to preferences dialog. * Display HTML files correctly in file display alarms. === Version 0.9.3 --- 4 March 2003 === * Add preferences option to set default sound file for the Edit Alarm dialog. * Fix display of "Invalid date" message before Edit Alarm dialog displays. === Version 0.9.2 --- 28 February 2003 === * Option to set font for individual alarm messages. * Allow multiple alarm selection in the main window. * KAlarm icon in alarm message window selects the alarm in the main window. * In Edit Alarm dialog, move all recurrence edit controls into Recurrence tab. * Add quit warning message option to preferences dialog. * Add "New Alarm" option to system tray context menu. * Disallow command alarms when KDE is running in kiosk mode. * Revised storage of beep, font, colour and program arguments in calendar file. * Always save alarms in iCalendar format (but vCalendar may still be read). * Add reminder, recurrence and font parameters to DCOP calls. * Fix failure to enable alarms when running in on-demand mode. === Version 0.9.1 --- 16 January 2003 === * Add option to set advance reminders for display alarms. * In run-in-system-tray mode, warn that alarms will be disabled before quitting. * Fix monthly and yearly recurrences on nth Monday etc. of the month. * Fix yearly recurrences on February 29th. * Fix recurrence start times stored in expired calendar file. * Fix extra empty events being stored in expired calendar file. === Version 0.9.0 --- 3 January 2003 === * Add facility to import birthdays from KAddressBook * Add option to send an email instead of displaying an alarm message. * Add option to store and view expired alarms. * Add copy, view and undelete actions (as applicable) for the selected alarm. * In alarm message window, message text can be copied to clipboard using mouse. * Allow message text to be scrolled in alarm message window if too big to fit. * Shift key with left mouse button steps time edit arrows by 5 minutes/6 hours. * Report failure to run command alarm (bash, ksh shells only). * Retain repeat-at-login status on alarm deferral. * Restore alarm messages which were displayed before KAlarm was killed or crashed. * Store alarm data in the calendar file in a more standard way. * Alarm message defer dialog: update recurrence deferral time limit in real time. * Weekly recurrence edit: adhere to user preference for start day of week. * Use standard action icons. === Version 0.8.5 (KDE 3.1.1) --- 21 February 2003 === * Fix monthly and yearly recurrences on nth Monday etc. of the month. * Fix yearly recurrences on February 29th. * Fix failure to enable alarms when running in on-demand mode. === Version 0.8.4 (KDE 3.1) --- 8 January 2003 === * Make KAlarm icon in message window bring main window to current desktop. * Fix detection of KDE desktop. * Fix entry of yearly recurrences on a specified date in the year. === Version 0.8.3 --- 9 November 2002 === * Fix no system tray icon being displayed. * Fix multiple system tray icons being displayed. * Fix alarms being missed after changing "Disable alarms when not running" status. === Version 0.8.2 --- 2 November 2002 === * Fix audio files not playing. === Version 0.8.1 --- 1 November 2002 === * Adhere to KDE single/double click setting when clicking on alarm list. * Fix possible loss of alarms if KAlarm has previously used another calendar file. * Fix coordination between "At time" and "After time" values when they change. * Always remove alarm deferral even when next recurrence triggers instead. * When alarm triggers, replace any existing repeat-at-login alarm message window. * Fix deselection of Sound not working after selecting a sound file. * Fix display of hour spin buttons in time edit spin boxes. * Prevent time edit spin box buttons from selecting the text. * Clean up previous alarm list highlight properly when a new alarm is selected. * Set sensible initial focus when edit alarm dialog pages are displayed. * Fix Quit duplicate entry in system tray context menu. === Version 0.8 (KDE 3.1 beta2) --- 16 September 2002 === * Move recurrence edit to separate tab in alarm dialog (now fits 800x600 display). * Add accelerator keys in dialogs. * Provide date picker for entering dates. === Version 0.7.5 --- 1 September 2002 === * Add preferences options to choose default settings for the Edit Alarm dialog. * Fix right-to-left character sets not being displayed in message edit control. * Make "Help -> Report Bug" use the KDE bug system (bug #43250). * Fix session restoration not occurring. === Version 0.7.4 (KDE 3.1 beta1) --- 5 August 2002 === * Add option to prompt for confirmation on alarm deletion. * Add option to prompt for confirmation on alarm acknowedgement. * Display KAlarm handbook Preferences section when Help clicked in config dialog. * Correctly adjust wrong summer times stored by version 0.5.7 (KDE 3.0.0). === Version 0.7.3 --- 24 July 2002 === * Fix loss of alarm times after saving pre-version 0.7 calendar file. * Fix main alarm list display of hours or hours/minutes repeat interval. * Display KAlarm handbook when Help clicked in configuration dialog. === Version 0.7.2 --- 2 July 2002 === * Fix reading wrong alarm times from pre-version 0.7 calendar file. * Partially fix loss of alarm times after saving pre-version 0.7 calendar file. === Version 0.7.1 --- 29 June 2002 === * Prevent duplicate message windows from being displayed. * Make Close button on message window not the default button to reduce chance of accidental acknowledgement. * Fix non-ASCII message texts being saved as question marks. * Fix memory leak with recurrences. === Version 0.7.0 --- 15 June 2002 === * Add option to play audio file when message is displayed. * Add daily, weekly, monthly, annual recurrences. * Allow deferring only up to next scheduled repetition time. * Don't defer repetitions when an alarm is deferred. * Make regular repetition and repeat-at-login mutually exclusive. * Double click on alarm in main window opens alarm edit dialog. * Change Reset Daemon menu option to Refresh Alarms. * Save and restore window sizes. === Version 0.6.4 --- 8 May 2002 === * Make click on system tray icon always bring KAlarm to top on current desktop. * Fix alarms not being triggered (depending on time zone). === Version 0.6.0 --- 8 March 2002 === * Add option to execute a command instead of displaying an alarm message. * Add Try button to alarm message edit dialog. * Add icons in the alarm list to indicate each alarm's type. * Display error message if a file to be displayed is not a text file. * Reduce chance of lost late-cancel alarms when daemon check interval is reduced. * Rename command line option --displayEvent to --triggerEvent. * Rename DCOP function displayMessage() to triggerEvent(). * Rename DCOP function cancelMessage() to cancelEvent(). === Version 0.5.8 (KDE 3.0.5A) --- 23 November 2002 === * Fix detection of KDE desktop. === Version 0.5.8 (KDE 3.0.5) --- 4 October 2002 === * Fix possible loss of alarms if KAlarm has previously used another calendar file. === Version 0.5.8 (KDE 3.0.4) --- 18 August 2002 === * Make "Help -> Report Bug" use the KDE bug system (bug #43250). * Fix right-to-left character sets not being displayed in message edit control. === Version 0.5.8 (KDE 3.0.3) --- 5 August 2002 === * Adjust wrong summer times stored by version 0.5.7 (KDE 3.0.0). * Display KAlarm handbook when Help clicked in configuration dialog. * Make Close button on message window not the default button to reduce chance of accidental acknowledgement. * Fix session restoration often not occurring at login. === Version 0.5.7 (KDE 3.0.1) --- 9 May 2002 === * Use local time for alarm times instead of using a time zone. * Make click on system tray icon always bring KAlarm to top on current desktop. === Version 0.5.7 (KDE 3.0) --- 17 March 2002 === * Show system tray icon on deferring command line-initiated message (run-in- system-tray mode). * Associate main window with system tray icon when displayed from message window. * Don't start KAlarm at login, until it has been run for the first time. * Add startup notification to kalarm.desktop. * Prevent open main window from cancelling KDE session shutdown. * Fix failure to display messages after daemon is restarted (run-on-demand mode). * Fix possible failure to display command line-initiated message. * Fix crash in some circumstances on changing run mode to run-on-demand. * Fix crash on clicking KAlarm icon in command line-initiated message window. * Fix crash on deferring alarm in command line-initiated message window. * Fix duplication of repeat-at-login alarms at login. * Fix error displaying text file messages. === Version 0.5.4 --- 7 February 2002 === * Fix extra window being displayed in session restoration. === Version 0.5.2 --- 31 January 2002 === * Fix session restore crash if in 'run continuously in system tray' mode. === Version 0.5.1 --- 30 January 2002 === * Change configuration defaults. === Version 0.5 --- 29 January 2002 === * Incorporate system tray icon into KAlarm, add --tray option. * Add 'run continuously in system tray' operating mode. * Don't use alarm daemon GUI application. * Add enable/disable alarms option to main window menu. * Add show/hide system tray icon option to main window menu. * Add toolbar. * Rename alarm dialog Set Alarm button to OK. * Rename message window OK button to Close. * Remove keyboard accelerator for Reset Daemon. * Fix magnified system tray icon. * Include README, etc. files in installation. === Version 0.4 --- 22 December 2001 === * Modify to use split alarm daemon/alarm daemon GUI. * Prevent a command line error exiting all open KAlarm windows. * Ensure the program exits after starting with --stop or --reset options. === Version 0.3.5 --- 5 December 2001 === * Add option to repeat alarms at login. * Add context help button to main window and message window. * Fix occasional crash on displaying non-repeating alarms. * Fix possible failure to display alarms at login. * Fix blank title bar when main window restored at login. * Fix alarms not deleted from main window when displayed at login. * Fix handling of zero-length calendar file. * Improve error messages. * Make documentation files installation dependent on KDE version. === Version 0.3.1 --- 20 November 2001 === * Fix build fault when using ./configure --enable-final === Version 0.3 --- 4 November 2001 === * Add option to display a file's contents instead of specifying a message. * Add dialog option to set an alarm's time as an interval from the current time. * Add defer option to alarm message window. * Provide button in alarm message window to activate KAlarm. * Make dialogs modal only for their parent window. === Version 0.2 --- 20 October 2001 === * Implement repeating alarms. * Add extra pair of arrow buttons to time spinbox to change the hour. * Fix sorting by colour column. * Better What's This? texts for the main window. * Remove -r, -s short options (use --reset, --stop instead). === Version 0.1.1 --- 1 September 2001 === * Fix documentation not being created by build. === Version 0.1 --- 31 August 2001 === * Initial release. diff --git a/src/messagewin.cpp b/src/messagewin.cpp index 2ad505e7..5f650690 100644 --- a/src/messagewin.cpp +++ b/src/messagewin.cpp @@ -1,2421 +1,2424 @@ /* * messagewin.cpp - displays an alarm message * Program: kalarm * Copyright © 2001-2019 David Jarvie * * This program 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 2 of the License, or * (at your option) any later version. * * This program 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 this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "messagewin.h" #include "messagewin_p.h" #include "config-kalarm.h" #include "alarmcalendar.h" #include "autoqpointer.h" #include "deferdlg.h" #include "desktop.h" #include "editdlg.h" #include "functions.h" #include "kalarmapp.h" #include "mainwindow.h" #include "messagebox.h" #include "preferences.h" #include "pushbutton.h" #include "shellprocess.h" #include "synchtimer.h" #include "resources/resources.h" #include "kalarm_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if KDEPIM_HAVE_X11 #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCalendarCore; using namespace KAlarmCal; #if KDEPIM_HAVE_X11 enum FullScreenType { NoFullScreen = 0, FullScreen = 1, FullScreenActive = 2 }; static FullScreenType haveFullScreenWindow(int screen); static FullScreenType findFullScreenWindows(const QVector& screenRects, QVector& screenTypes); #endif #include "kmailinterface.h" static const QLatin1String KMAIL_DBUS_SERVICE("org.kde.kmail"); static const QLatin1String KMAIL_DBUS_PATH("/KMail"); // The delay for enabling message window buttons if a zero delay is // configured, i.e. the windows are placed far from the cursor. static const int proximityButtonDelay = 1000; // (milliseconds) static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity // A text label widget which can be scrolled and copied with the mouse class MessageText : public KTextEdit { public: MessageText(QWidget* parent = nullptr) : KTextEdit(parent), mNewLine(false) { setReadOnly(true); setFrameStyle(NoFrame); setLineWrapMode(NoWrap); } int scrollBarHeight() const { return horizontalScrollBar()->height(); } int scrollBarWidth() const { return verticalScrollBar()->width(); } void setBackgroundColour(const QColor& c) { QPalette pal = viewport()->palette(); pal.setColor(viewport()->backgroundRole(), c); viewport()->setPalette(pal); } QSize sizeHint() const override { const QSizeF docsize = document()->size(); return QSize(static_cast(docsize.width() + 0.99) + verticalScrollBar()->width(), static_cast(docsize.height() + 0.99) + horizontalScrollBar()->height()); } bool newLine() const { return mNewLine; } void setNewLine(bool nl) { mNewLine = nl; } private: bool mNewLine; }; // Basic flags for the window -static const Qt::WindowFlags WFLAGS = Qt::WindowStaysOnTopHint; -static const Qt::WindowFlags WFLAGS2 = Qt::WindowContextHelpButtonHint; +static const Qt::WindowFlags WFLAGS = Qt::WindowStaysOnTopHint; +static const Qt::WindowFlags WFLAGS2 = Qt::WindowContextHelpButtonHint; static const Qt::WidgetAttribute WidgetFlags = Qt::WA_DeleteOnClose; // Error message bit masks -enum { +enum +{ ErrMsg_Speak = 0x01, ErrMsg_AudioFile = 0x02 }; -QList MessageWin::mWindowList; +QList MessageWin::mWindowList; QMap MessageWin::mErrorMessages; bool MessageWin::mRedisplayed = false; // There can only be one audio thread at a time: trying to play multiple // sound files simultaneously would result in a cacophony, and besides // that, Phonon currently crashes... QPointer MessageWin::mAudioThread; MessageWin* AudioThread::mAudioOwner = nullptr; /****************************************************************************** * Construct the message window for the specified alarm. * Other alarms in the supplied event may have been updated by the caller, so * the whole event needs to be stored for updating the calendar file when it is * displayed. */ MessageWin::MessageWin(const KAEvent* event, const KAAlarm& alarm, int flags) : MainWindowBase(nullptr, static_cast(WFLAGS | WFLAGS2 | ((flags & ALWAYS_HIDE) || getWorkAreaAndModal() ? Qt::WindowType(0) : Qt::X11BypassWindowManagerHint))) , mMessage(event->cleanText()) , mFont(event->font()) , mBgColour(event->bgColour()) , mFgColour(event->fgColour()) , mEventId(*event) , mAudioFile(event->audioFile()) , mVolume(event->soundVolume()) , mFadeVolume(event->fadeVolume()) , mFadeSeconds(qMin(event->fadeSeconds(), 86400)) , mDefaultDeferMinutes(event->deferDefaultMinutes()) , mAlarmType(alarm.type()) , mAction(event->actionSubType()) , mAkonadiItemId(event->akonadiItemId()) , mCommandError(event->commandError()) , mRestoreHeight(0) , mAudioRepeatPause(event->repeatSoundPause()) , mConfirmAck(event->confirmAck()) , mNoDefer(true) , mInvalid(false) , mEvent(*event) , mOriginalEvent(*event) , mResource(Resources::resourceForEvent(mEventId.eventId())) , mAlwaysHide(flags & ALWAYS_HIDE) , mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM) , mBeep(event->beep()) , mSpeak(event->speak()) , mRescheduleEvent(!(flags & NO_RESCHEDULE)) { qCDebug(KALARM_LOG) << "MessageWin:" << (void*)this << "event" << mEventId; setAttribute(static_cast(WidgetFlags)); setWindowModality(Qt::WindowModal); setObjectName(QStringLiteral("MessageWin")); // used by LikeBack if (alarm.type() & KAAlarm::REMINDER_ALARM) { if (event->reminderMinutes() < 0) { event->previousOccurrence(alarm.dateTime(false).effectiveKDateTime(), mDateTime, false); if (!mDateTime.isValid() && event->repeatAtLogin()) mDateTime = alarm.dateTime().addSecs(event->reminderMinutes() * 60); } else mDateTime = event->mainDateTime(true); } else mDateTime = alarm.dateTime(true); if (!(flags & (NO_INIT_VIEW | ALWAYS_HIDE))) { const bool readonly = AlarmCalendar::resources()->eventReadOnly(mEventId.eventId()); mShowEdit = !mEventId.isEmpty() && !readonly; mNoDefer = readonly || (flags & NO_DEFER) || alarm.repeatAtLogin(); initView(); } // Set to save settings automatically, but don't save window size. // File alarm window size is saved elsewhere. setAutoSaveSettings(QStringLiteral("MessageWin"), false); mWindowList.append(this); if (event->autoClose()) mCloseTime = alarm.dateTime().effectiveKDateTime().toUtc().qDateTime().addSecs(event->lateCancel() * 60); if (mAlwaysHide) { hide(); displayComplete(); // play audio, etc. } } /****************************************************************************** * Display an error message window. * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note * that the option is specific to 'event'. */ void MessageWin::showError(const KAEvent& event, const DateTime& alarmDateTime, const QStringList& errmsgs, const QString& dontShowAgain) { if (!dontShowAgain.isEmpty() && KAlarm::dontShowErrors(EventId(event), dontShowAgain)) return; // Don't pile up duplicate error messages for the same alarm for (int i = 0, end = mWindowList.count(); i < end; ++i) { const MessageWin* w = mWindowList[i]; if (w->mErrorWindow && w->mEventId == EventId(event) && w->mErrorMsgs == errmsgs && w->mDontShowAgain == dontShowAgain) return; } (new MessageWin(&event, alarmDateTime, errmsgs, dontShowAgain))->show(); } /****************************************************************************** * Construct the message window for a specified error message. * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note * that the option is specific to 'event'. */ MessageWin::MessageWin(const KAEvent* event, const DateTime& alarmDateTime, const QStringList& errmsgs, const QString& dontShowAgain) : MainWindowBase(nullptr, WFLAGS | WFLAGS2) , mMessage(event->cleanText()) , mDateTime(alarmDateTime) , mEventId(*event) , mAlarmType(KAAlarm::MAIN_ALARM) , mAction(event->actionSubType()) , mAkonadiItemId(-1) , mCommandError(KAEvent::CMD_NO_ERROR) , mErrorMsgs(errmsgs) , mDontShowAgain(dontShowAgain) , mRestoreHeight(0) , mConfirmAck(false) , mShowEdit(false) , mNoDefer(true) , mInvalid(false) , mEvent(*event) , mOriginalEvent(*event) , mErrorWindow(true) , mNoPostAction(true) { qCDebug(KALARM_LOG) << "MessageWin: errmsg"; setAttribute(static_cast(WidgetFlags)); setWindowModality(Qt::WindowModal); setObjectName(QStringLiteral("ErrorWin")); // used by LikeBack getWorkAreaAndModal(); initView(); mWindowList.append(this); } /****************************************************************************** * Construct the message window for restoration by session management. * The window is initialised by readProperties(). */ MessageWin::MessageWin() : MainWindowBase(nullptr, WFLAGS) { qCDebug(KALARM_LOG) << "MessageWin:" << (void*)this << "restore"; setAttribute(WidgetFlags); setWindowModality(Qt::WindowModal); setObjectName(QStringLiteral("RestoredMsgWin")); // used by LikeBack getWorkAreaAndModal(); mWindowList.append(this); } /****************************************************************************** * Destructor. Perform any post-alarm actions before tidying up. */ MessageWin::~MessageWin() { qCDebug(KALARM_LOG) << "~MessageWin" << (void*)this << mEventId; if (AudioThread::mAudioOwner == this && !mAudioThread.isNull()) mAudioThread->quit(); mErrorMessages.remove(mEventId); mWindowList.removeAll(this); delete mTempFile; if (!mRecreating) { if (!mNoPostAction && !mEvent.postAction().isEmpty()) theApp()->alarmCompleted(mEvent); if (!instanceCount(true)) theApp()->quitIf(); // no visible windows remain - check whether to quit } } /****************************************************************************** * Construct the message window. */ void MessageWin::initView() { const bool reminder = (!mErrorWindow && (mAlarmType & KAAlarm::REMINDER_ALARM)); const int leading = fontMetrics().leading(); setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18nc("@title:window", "Reminder") : i18nc("@title:window", "Message")); QWidget* topWidget = new QWidget(this); setCentralWidget(topWidget); QVBoxLayout* topLayout = new QVBoxLayout(topWidget); int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); topLayout->setContentsMargins(dcm, dcm, dcm, dcm); topLayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QPalette labelPalette = palette(); labelPalette.setColor(backgroundRole(), labelPalette.color(QPalette::Window)); // Show the alarm date/time, together with a reminder text where appropriate. // Alarm date/time: display time zone if not local time zone. mTimeLabel = new QLabel(topWidget); mTimeLabel->setText(dateTimeToDisplay()); mTimeLabel->setFrameStyle(QFrame::StyledPanel); mTimeLabel->setPalette(labelPalette); mTimeLabel->setAutoFillBackground(true); topLayout->addWidget(mTimeLabel, 0, Qt::AlignHCenter); mTimeLabel->setWhatsThis(i18nc("@info:whatsthis", "The scheduled date/time for the message (as opposed to the actual time of display).")); if (mDateTime.isValid()) { // Reminder if (reminder) { // Create a label "time\nReminder" by inserting the time at the // start of the translated string, allowing for possible HTML tags // enclosing "Reminder". QString s = i18nc("@info", "Reminder"); QRegExp re(QStringLiteral("^(<[^>]+>)*")); re.indexIn(s); s.insert(re.matchedLength(), mTimeLabel->text() + QLatin1String("
")); mTimeLabel->setText(s); mTimeLabel->setAlignment(Qt::AlignHCenter); } } else mTimeLabel->hide(); if (!mErrorWindow) { // It's a normal alarm message window switch (mAction) { case KAEvent::FILE: { // Display the file name KSqueezedTextLabel* label = new KSqueezedTextLabel(mMessage, topWidget); label->setFrameStyle(QFrame::StyledPanel); label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); label->setPalette(labelPalette); label->setAutoFillBackground(true); label->setWhatsThis(i18nc("@info:whatsthis", "The file whose contents are displayed below")); topLayout->addWidget(label, 0, Qt::AlignHCenter); // Display contents of file const QUrl url = QUrl::fromUserInput(mMessage, QString(), QUrl::AssumeLocalFile); auto statJob = KIO::stat(url, KIO::StatJob::SourceSide, 0, KIO::HideProgressInfo); const bool exists = statJob->exec(); const bool isDir = statJob->statResult().isDir(); bool opened = false; if (exists && !isDir) { auto job = KIO::storedGet(url); KJobWidgets::setWindow(job, MainWindow::mainMainWindow()); if (job->exec()) { opened = true; const QByteArray data = job->data(); QMimeDatabase db; QMimeType mime = db.mimeTypeForUrl(url); if (mime.name() == QLatin1String("application/octet-stream")) mime = db.mimeTypeForData(mTempFile); const KAlarm::FileType fileType = KAlarm::fileType(mime); switch (fileType) { case KAlarm::Image: case KAlarm::TextFormatted: delete mTempFile; mTempFile = new QTemporaryFile; mTempFile->open(); mTempFile->write(data); break; default: break; } QTextBrowser* view = new QTextBrowser(topWidget); view->setFrameStyle(QFrame::NoFrame); view->setWordWrapMode(QTextOption::NoWrap); QPalette pal = view->viewport()->palette(); pal.setColor(view->viewport()->backgroundRole(), mBgColour); view->viewport()->setPalette(pal); view->setTextColor(mFgColour); view->setCurrentFont(mFont); switch (fileType) { case KAlarm::Image: view->setHtml(QLatin1String("
fileName() + QLatin1String("\">
")); mTempFile->close(); // keep the file available to be displayed break; case KAlarm::TextFormatted: view->QTextBrowser::setSource(QUrl::fromLocalFile(mTempFile->fileName())); //krazy:exclude=qclasses delete mTempFile; mTempFile = nullptr; break; default: view->setPlainText(QString::fromUtf8(data)); break; } view->setMinimumSize(view->sizeHint()); topLayout->addWidget(view); // Set the default size to 20 lines square. // Note that after the first file has been displayed, this size // is overridden by the user-set default stored in the config file. // So there is no need to calculate an accurate size. int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth(); view->resize(QSize(h, h).expandedTo(view->sizeHint())); view->setWhatsThis(i18nc("@info:whatsthis", "The contents of the file to be displayed")); } } if (!exists || isDir || !opened) { mErrorMsgs += isDir ? i18nc("@info", "File is a folder") : exists ? i18nc("@info", "Failed to open file") : i18nc("@info", "File not found"); } break; } case KAEvent::MESSAGE: { // Message label // Using MessageText instead of QLabel allows scrolling and mouse copying MessageText* text = new MessageText(topWidget); text->setAutoFillBackground(true); text->setBackgroundColour(mBgColour); text->setTextColor(mFgColour); text->setCurrentFont(mFont); text->insertPlainText(mMessage); const int lineSpacing = text->fontMetrics().lineSpacing(); const QSize s = text->sizeHint(); const int h = s.height(); text->setMaximumHeight(h + text->scrollBarHeight()); text->setMinimumHeight(qMin(h, lineSpacing*4)); text->setMaximumWidth(s.width() + text->scrollBarWidth()); text->setWhatsThis(i18nc("@info:whatsthis", "The alarm message")); const int vspace = lineSpacing/2; const int hspace = lineSpacing - style()->pixelMetric(QStyle::PM_DefaultChildMargin); topLayout->addSpacing(vspace); topLayout->addStretch(); // Don't include any horizontal margins if message is 2/3 screen width if (text->sizeHint().width() >= KAlarm::desktopWorkArea(mScreenNumber).width()*2/3) topLayout->addWidget(text, 1, Qt::AlignHCenter); else { QHBoxLayout* layout = new QHBoxLayout(); layout->addSpacing(hspace); layout->addWidget(text, 1, Qt::AlignHCenter); layout->addSpacing(hspace); topLayout->addLayout(layout); } if (!reminder) topLayout->addStretch(); break; } case KAEvent::COMMAND: { mCommandText = new MessageText(topWidget); mCommandText->setBackgroundColour(mBgColour); mCommandText->setTextColor(mFgColour); mCommandText->setCurrentFont(mFont); topLayout->addWidget(mCommandText); mCommandText->setWhatsThis(i18nc("@info:whatsthis", "The output of the alarm's command")); theApp()->execCommandAlarm(mEvent, mEvent.alarm(mAlarmType), this, SLOT(readProcessOutput(ShellProcess*))); break; } case KAEvent::EMAIL: default: break; } if (reminder && mEvent.reminderMinutes() > 0) { // Advance reminder: show remaining time until the actual alarm mRemainingText = new QLabel(topWidget); mRemainingText->setFrameStyle(QFrame::Box | QFrame::Raised); mRemainingText->setContentsMargins(leading, leading, leading, leading); mRemainingText->setPalette(labelPalette); mRemainingText->setAutoFillBackground(true); if (mDateTime.isDateOnly() || KADateTime::currentLocalDate().daysTo(mDateTime.date()) > 0) { setRemainingTextDay(); MidnightTimer::connect(this, SLOT(setRemainingTextDay())); // update every day } else { setRemainingTextMinute(); MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute } topLayout->addWidget(mRemainingText, 0, Qt::AlignHCenter); topLayout->addSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); topLayout->addStretch(); } } else { // It's an error message switch (mAction) { case KAEvent::EMAIL: { // Display the email addresses and subject. QFrame* frame = new QFrame(topWidget); frame->setFrameStyle(QFrame::Box | QFrame::Raised); frame->setWhatsThis(i18nc("@info:whatsthis", "The email to send")); topLayout->addWidget(frame, 0, Qt::AlignHCenter); QGridLayout* grid = new QGridLayout(frame); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QLabel* label = new QLabel(i18nc("@info Email addressee", "To:"), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 0, Qt::AlignLeft); label = new QLabel(mEvent.emailAddresses(QStringLiteral("\n")), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 1, Qt::AlignLeft); label = new QLabel(i18nc("@info Email subject", "Subject:"), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 1, 0, Qt::AlignLeft); label = new QLabel(mEvent.emailSubject(), frame); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 1, 1, Qt::AlignLeft); break; } case KAEvent::COMMAND: case KAEvent::FILE: case KAEvent::MESSAGE: default: // Just display the error message strings break; } } if (!mErrorMsgs.count()) { topWidget->setAutoFillBackground(true); QPalette palette = topWidget->palette(); palette.setColor(topWidget->backgroundRole(), mBgColour); topWidget->setPalette(palette); } else { setCaption(i18nc("@title:window", "Error")); QHBoxLayout* layout = new QHBoxLayout(); int m = 2 * dcm; layout->setContentsMargins(m, m, m, m); layout->addStretch(); topLayout->addLayout(layout); QLabel* label = new QLabel(topWidget); label->setPixmap(QIcon::fromTheme(QStringLiteral("dialog-error")).pixmap(IconSize(KIconLoader::Desktop), IconSize(KIconLoader::Desktop))); label->setFixedSize(label->sizeHint()); layout->addWidget(label, 0, Qt::AlignRight); QVBoxLayout* vlayout = new QVBoxLayout(); layout->addLayout(vlayout); for (QStringList::ConstIterator it = mErrorMsgs.constBegin(); it != mErrorMsgs.constEnd(); ++it) { label = new QLabel(*it, topWidget); label->setFixedSize(label->sizeHint()); vlayout->addWidget(label, 0, Qt::AlignLeft); } layout->addStretch(); if (!mDontShowAgain.isEmpty()) { mDontShowAgainCheck = new QCheckBox(i18nc("@option:check", "Do not display this error message again for this alarm"), topWidget); mDontShowAgainCheck->setFixedSize(mDontShowAgainCheck->sizeHint()); topLayout->addWidget(mDontShowAgainCheck, 0, Qt::AlignLeft); } } QGridLayout* grid = new QGridLayout(); grid->setColumnStretch(0, 1); // keep the buttons right-adjusted in the window topLayout->addLayout(grid); int gridIndex = 1; // Close button mOkButton = new PushButton(KStandardGuiItem::close(), topWidget); // Prevent accidental acknowledgement of the message if the user is typing when the window appears mOkButton->clearFocus(); mOkButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection mOkButton->setFixedSize(mOkButton->sizeHint()); connect(mOkButton, &QAbstractButton::clicked, this, &MessageWin::slotOk); grid->addWidget(mOkButton, 0, gridIndex++, Qt::AlignHCenter); mOkButton->setWhatsThis(i18nc("@info:whatsthis", "Acknowledge the alarm")); if (mShowEdit) { // Edit button mEditButton = new PushButton(i18nc("@action:button", "&Edit..."), topWidget); mEditButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection mEditButton->setFixedSize(mEditButton->sizeHint()); connect(mEditButton, &QAbstractButton::clicked, this, &MessageWin::slotEdit); grid->addWidget(mEditButton, 0, gridIndex++, Qt::AlignHCenter); mEditButton->setWhatsThis(i18nc("@info:whatsthis", "Edit the alarm.")); } // Defer button mDeferButton = new PushButton(i18nc("@action:button", "&Defer..."), topWidget); mDeferButton->setFocusPolicy(Qt::ClickFocus); // don't allow keyboard selection mDeferButton->setFixedSize(mDeferButton->sizeHint()); connect(mDeferButton, &QAbstractButton::clicked, this, &MessageWin::slotDefer); grid->addWidget(mDeferButton, 0, gridIndex++, Qt::AlignHCenter); mDeferButton->setWhatsThis(xi18nc("@info:whatsthis", "Defer the alarm until later." "You will be prompted to specify when the alarm should be redisplayed.")); if (mNoDefer) mDeferButton->hide(); else setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more KIconLoader iconLoader; if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0)) { // Silence button to stop sound repetition const QPixmap pixmap = iconLoader.loadIcon(QStringLiteral("media-playback-stop"), KIconLoader::MainToolbar); mSilenceButton = new PushButton(topWidget); mSilenceButton->setIcon(pixmap); grid->addWidget(mSilenceButton, 0, gridIndex++, Qt::AlignHCenter); mSilenceButton->setToolTip(i18nc("@info:tooltip", "Stop sound")); mSilenceButton->setWhatsThis(i18nc("@info:whatsthis", "Stop playing the sound")); // To avoid getting in a mess, disable the button until sound playing has been set up mSilenceButton->setEnabled(false); } if (mAkonadiItemId >= 0) { // KMail button const QPixmap pixmap = iconLoader.loadIcon(QStringLiteral("internet-mail"), KIconLoader::MainToolbar); mKMailButton = new PushButton(topWidget); mKMailButton->setIcon(pixmap); connect(mKMailButton, &QAbstractButton::clicked, this, &MessageWin::slotShowKMailMessage); grid->addWidget(mKMailButton, 0, gridIndex++, Qt::AlignHCenter); mKMailButton->setToolTip(xi18nc("@info:tooltip Locate this email in KMail", "Locate in KMail")); mKMailButton->setWhatsThis(xi18nc("@info:whatsthis", "Locate and highlight this email in KMail")); } // KAlarm button const QPixmap pixmap = iconLoader.loadIcon(KAboutData::applicationData().componentName(), KIconLoader::MainToolbar); mKAlarmButton = new PushButton(topWidget); mKAlarmButton->setIcon(pixmap); connect(mKAlarmButton, &QAbstractButton::clicked, this, &MessageWin::displayMainWindow); grid->addWidget(mKAlarmButton, 0, gridIndex++, Qt::AlignHCenter); mKAlarmButton->setToolTip(xi18nc("@info:tooltip", "Activate KAlarm")); mKAlarmButton->setWhatsThis(xi18nc("@info:whatsthis", "Activate KAlarm")); int butsize = mKAlarmButton->sizeHint().height(); if (mSilenceButton) butsize = qMax(butsize, mSilenceButton->sizeHint().height()); if (mKMailButton) butsize = qMax(butsize, mKMailButton->sizeHint().height()); mKAlarmButton->setFixedSize(butsize, butsize); if (mSilenceButton) mSilenceButton->setFixedSize(butsize, butsize); if (mKMailButton) mKMailButton->setFixedSize(butsize, butsize); // Disable all buttons initially, to prevent accidental clicking on if they happen to be // under the mouse just as the window appears. mOkButton->setEnabled(false); if (mDeferButton->isVisible()) mDeferButton->setEnabled(false); if (mEditButton) mEditButton->setEnabled(false); if (mKMailButton) mKMailButton->setEnabled(false); mKAlarmButton->setEnabled(false); topLayout->activate(); setMinimumSize(QSize(grid->sizeHint().width() + 2 * style()->pixelMetric(QStyle::PM_DefaultChildMargin), sizeHint().height())); const bool modal = !(windowFlags() & Qt::X11BypassWindowManagerHint); NET::States wstate = NET::Sticky | NET::KeepAbove; if (modal) wstate |= NET::Modal; WId winid = winId(); KWindowSystem::setState(winid, wstate); KWindowSystem::setOnAllDesktops(winid, true); mInitialised = true; // the window's widgets have been created } /****************************************************************************** * Return the number of message windows, optionally excluding always-hidden ones. */ int MessageWin::instanceCount(bool excludeAlwaysHidden) { int count = mWindowList.count(); if (excludeAlwaysHidden) { for (MessageWin* win : qAsConst(mWindowList)) { if (win->mAlwaysHide) --count; } } return count; } bool MessageWin::hasDefer() const { return mDeferButton && mDeferButton->isVisible(); } /****************************************************************************** * Show the Defer button when it was previously hidden. */ void MessageWin::showDefer() { if (mDeferButton) { mNoDefer = false; mDeferButton->show(); setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more resize(sizeHint()); } } /****************************************************************************** * Convert a reminder window into a normal alarm window. */ void MessageWin::cancelReminder(const KAEvent& event, const KAAlarm& alarm) { if (!mInitialised) return; mDateTime = alarm.dateTime(true); mNoPostAction = false; mAlarmType = alarm.type(); if (event.autoClose()) mCloseTime = alarm.dateTime().effectiveKDateTime().toUtc().qDateTime().addSecs(event.lateCancel() * 60); setCaption(i18nc("@title:window", "Message")); mTimeLabel->setText(dateTimeToDisplay()); if (mRemainingText) mRemainingText->hide(); MidnightTimer::disconnect(this, SLOT(setRemainingTextDay())); MinuteTimer::disconnect(this, SLOT(setRemainingTextMinute())); setMinimumHeight(0); centralWidget()->layout()->activate(); setMinimumHeight(sizeHint().height()); resize(sizeHint()); } /****************************************************************************** * Show the alarm's trigger time. * This is assumed to have previously been hidden. */ void MessageWin::showDateTime(const KAEvent& event, const KAAlarm& alarm) { if (!mTimeLabel) return; mDateTime = (alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true); if (mDateTime.isValid()) { mTimeLabel->setText(dateTimeToDisplay()); mTimeLabel->show(); } } /****************************************************************************** * Get the trigger time to display. */ QString MessageWin::dateTimeToDisplay() { QString tm; if (mDateTime.isValid()) { if (mDateTime.isDateOnly()) tm = QLocale().toString(mDateTime.date(), QLocale::ShortFormat); else { bool showZone = false; if (mDateTime.timeType() == KADateTime::UTC || (mDateTime.timeType() == KADateTime::TimeZone && !mDateTime.isLocalZone())) { // Display time zone abbreviation if it's different from the local // zone. Note that the iCalendar time zone might represent the local // time zone in a slightly different way from the system time zone, // so the zone comparison above might not produce the desired result. const QString tz = mDateTime.kDateTime().toString(QStringLiteral("%Z")); KADateTime local = mDateTime.kDateTime(); local.setTimeSpec(KADateTime::Spec::LocalZone()); showZone = (local.toString(QStringLiteral("%Z")) != tz); } const QDateTime dt = mDateTime.qDateTime(); tm = QLocale().toString(dt, QLocale::ShortFormat); if (showZone) tm += QLatin1Char(' ') + mDateTime.timeZone().displayName(dt, QTimeZone::ShortName, QLocale()); } } return tm; } /****************************************************************************** * Set the remaining time text in a reminder window. * Called at the start of every day (at the user-defined start-of-day time). */ void MessageWin::setRemainingTextDay() { QString text; const int days = KADateTime::currentLocalDate().daysTo(mDateTime.date()); if (days <= 0 && !mDateTime.isDateOnly()) { // The alarm is due today, so start refreshing every minute MidnightTimer::disconnect(this, SLOT(setRemainingTextDay())); setRemainingTextMinute(); MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute } else { if (days <= 0) text = i18nc("@info", "Today"); else if (days % 7) text = i18ncp("@info", "Tomorrow", "in %1 days' time", days); else text = i18ncp("@info", "in 1 week's time", "in %1 weeks' time", days/7); } mRemainingText->setText(text); } /****************************************************************************** * Set the remaining time text in a reminder window. * Called on every minute boundary. */ void MessageWin::setRemainingTextMinute() { QString text; const int mins = (KADateTime::currentUtcDateTime().secsTo(mDateTime.effectiveKDateTime()) + 59) / 60; if (mins < 60) text = i18ncp("@info", "in 1 minute's time", "in %1 minutes' time", (mins > 0 ? mins : 0)); else if (mins % 60 == 0) text = i18ncp("@info", "in 1 hour's time", "in %1 hours' time", mins/60); else { QString hourText = i18ncp("@item:intext inserted into 'in ... %1 minute's time' below", "1 hour", "%1 hours", mins/60); text = i18ncp("@info '%2' is the previous message '1 hour'/'%1 hours'", "in %2 1 minute's time", "in %2 %1 minutes' time", mins%60, hourText); } mRemainingText->setText(text); } /****************************************************************************** * Called when output is available from the command which is providing the text * for this window. Add the output and resize the window to show it. */ void MessageWin::readProcessOutput(ShellProcess* proc) { const QByteArray data = proc->readAll(); if (!data.isEmpty()) { // Strip any trailing newline, to avoid showing trailing blank line // in message window. if (mCommandText->newLine()) mCommandText->append(QStringLiteral("\n")); const int nl = data.endsWith('\n') ? 1 : 0; mCommandText->setNewLine(nl); mCommandText->insertPlainText(QString::fromLocal8Bit(data.data(), data.length() - nl)); resize(sizeHint()); } } /****************************************************************************** * Save settings to the session managed config file, for restoration * when the program is restored. */ void MessageWin::saveProperties(KConfigGroup& config) { if (mShown && !mErrorWindow && !mAlwaysHide) { config.writeEntry("EventID", mEventId.eventId()); config.writeEntry("CollectionID", mResource.id()); config.writeEntry("AlarmType", static_cast(mAlarmType)); if (mAlarmType == KAAlarm::INVALID_ALARM) qCCritical(KALARM_LOG) << "MessageWin::saveProperties: Invalid alarm: id=" << mEventId << ", alarm count=" << mEvent.alarmCount(); config.writeEntry("Message", mMessage); config.writeEntry("Type", static_cast(mAction)); config.writeEntry("Font", mFont); config.writeEntry("BgColour", mBgColour); config.writeEntry("FgColour", mFgColour); config.writeEntry("ConfirmAck", mConfirmAck); if (mDateTime.isValid()) { //TODO: Write KADateTime when it becomes possible config.writeEntry("Time", mDateTime.effectiveDateTime()); config.writeEntry("DateOnly", mDateTime.isDateOnly()); QByteArray zone; if (mDateTime.isUtc()) zone = "UTC"; else if (mDateTime.timeType() == KADateTime::TimeZone) { const QTimeZone tz = mDateTime.timeZone(); if (tz.isValid()) zone = tz.id(); } config.writeEntry("TimeZone", zone); } if (mCloseTime.isValid()) config.writeEntry("Expiry", mCloseTime); if (mAudioRepeatPause >= 0 && mSilenceButton && mSilenceButton->isEnabled()) { // Only need to restart sound file playing if it's being repeated config.writePathEntry("AudioFile", mAudioFile); config.writeEntry("Volume", static_cast(mVolume * 100)); config.writeEntry("AudioPause", mAudioRepeatPause); } config.writeEntry("Speak", mSpeak); config.writeEntry("Height", height()); config.writeEntry("DeferMins", mDefaultDeferMinutes); config.writeEntry("NoDefer", mNoDefer); config.writeEntry("NoPostAction", mNoPostAction); config.writeEntry("AkonadiItemId", mAkonadiItemId); config.writeEntry("CmdErr", static_cast(mCommandError)); config.writeEntry("DontShowAgain", mDontShowAgain); } else config.writeEntry("Invalid", true); } /****************************************************************************** * Read settings from the session managed config file. * This function is automatically called whenever the app is being restored. * Read in whatever was saved in saveProperties(). */ void MessageWin::readProperties(const KConfigGroup& config) { mInvalid = config.readEntry("Invalid", false); const QString eventId = config.readEntry("EventID"); const ResourceId collectionId = config.readEntry("CollectionID", ResourceId(-1)); mAlarmType = static_cast(config.readEntry("AlarmType", 0)); if (mAlarmType == KAAlarm::INVALID_ALARM) { mInvalid = true; qCCritical(KALARM_LOG) << "MessageWin::readProperties: Invalid alarm: id=" << eventId; } mMessage = config.readEntry("Message"); mAction = static_cast(config.readEntry("Type", 0)); mFont = config.readEntry("Font", QFont()); mBgColour = config.readEntry("BgColour", QColor(Qt::white)); mFgColour = config.readEntry("FgColour", QColor(Qt::black)); mConfirmAck = config.readEntry("ConfirmAck", false); QDateTime invalidDateTime; QDateTime dt = config.readEntry("Time", invalidDateTime); const QByteArray zoneId = config.readEntry("TimeZone").toLatin1(); KADateTime::Spec timeSpec; if (zoneId.isEmpty()) timeSpec = KADateTime::LocalZone; else if (zoneId == "UTC") timeSpec = KADateTime::UTC; else timeSpec = QTimeZone(zoneId); mDateTime = KADateTime(dt.date(), dt.time(), timeSpec); const bool dateOnly = config.readEntry("DateOnly", false); if (dateOnly) mDateTime.setDateOnly(true); mCloseTime = config.readEntry("Expiry", invalidDateTime); mCloseTime.setTimeSpec(Qt::UTC); mAudioFile = config.readPathEntry("AudioFile", QString()); mVolume = static_cast(config.readEntry("Volume", 0)) / 100; mFadeVolume = -1; mFadeSeconds = 0; if (!mAudioFile.isEmpty()) // audio file URL was only saved if it repeats mAudioRepeatPause = config.readEntry("AudioPause", 0); mBeep = false; // don't beep after restart (similar to not playing non-repeated sound file) mSpeak = config.readEntry("Speak", false); mRestoreHeight = config.readEntry("Height", 0); mDefaultDeferMinutes = config.readEntry("DeferMins", 0); mNoDefer = config.readEntry("NoDefer", false); mNoPostAction = config.readEntry("NoPostAction", true); mAkonadiItemId = config.readEntry("AkonadiItemId", QVariant(QVariant::LongLong)).toLongLong(); mCommandError = KAEvent::CmdErrType(config.readEntry("CmdErr", static_cast(KAEvent::CMD_NO_ERROR))); mDontShowAgain = config.readEntry("DontShowAgain", QString()); mShowEdit = false; // Temporarily initialise mResource and mEventId - they will be set by redisplayAlarm() mResource = Resources::resource(collectionId); mEventId = EventId(collectionId, eventId); qCDebug(KALARM_LOG) << "MessageWin::readProperties:" << eventId; if (mAlarmType != KAAlarm::INVALID_ALARM) { // Recreate the event from the calendar file (if possible) if (eventId.isEmpty()) initView(); else { // Close any other window for this alarm which has already been restored by redisplayAlarms() if (!Resources::allCreated()) { connect(Resources::instance(), &Resources::resourcesCreated, this, &MessageWin::showRestoredAlarm); return; } redisplayAlarm(); } } } /****************************************************************************** * Fetch the restored alarm from the calendar and redisplay it in this window. */ void MessageWin::showRestoredAlarm() { qCDebug(KALARM_LOG) << "MessageWin::showRestoredAlarm:" << mEventId; redisplayAlarm(); show(); } /****************************************************************************** * Fetch the restored alarm from the calendar and redisplay it in this window. */ void MessageWin::redisplayAlarm() { mResource = Resources::resourceForEvent(mEventId.eventId()); mEventId.setCollectionId(mResource.id()); qCDebug(KALARM_LOG) << "MessageWin::redisplayAlarm:" << mEventId; // Delete any already existing window for the same event MessageWin* duplicate = findEvent(mEventId, this); if (duplicate) qCDebug(KALARM_LOG) << "MessageWin::redisplayAlarm: Deleting duplicate window:" << mEventId; delete duplicate; KAEvent* event = AlarmCalendar::resources()->event(mEventId); if (event) { mEvent = *event; mShowEdit = true; } else { // It's not in the active calendar, so try the displaying or archive calendars retrieveEvent(mEvent, mResource, mShowEdit, mNoDefer); mNoDefer = !mNoDefer; } initView(); } /****************************************************************************** * Redisplay alarms which were being shown when the program last exited. * Normally, these alarms will have been displayed by session restoration, but * if the program crashed or was killed, we can redisplay them here so that * they won't be lost. */ void MessageWin::redisplayAlarms() { if (mRedisplayed) return; qCDebug(KALARM_LOG) << "MessageWin::redisplayAlarms"; mRedisplayed = true; AlarmCalendar* cal = AlarmCalendar::displayCalendar(); if (cal && cal->isOpen()) { KAEvent event; Resource resource; const Event::List events = cal->kcalEvents(); for (int i = 0, end = events.count(); i < end; ++i) { bool showDefer, showEdit; reinstateFromDisplaying(events[i], event, resource, showEdit, showDefer); const EventId eventId(event); if (findEvent(eventId)) qCDebug(KALARM_LOG) << "MessageWin::redisplayAlarms: Message window already exists:" << eventId; else { // This event should be displayed, but currently isn't being const KAAlarm alarm = event.convertDisplayingAlarm(); if (alarm.type() == KAAlarm::INVALID_ALARM) { qCCritical(KALARM_LOG) << "MessageWin::redisplayAlarms: Invalid alarm: id=" << eventId; continue; } qCDebug(KALARM_LOG) << "MessageWin::redisplayAlarms:" << eventId; const bool login = alarm.repeatAtLogin(); const int flags = NO_RESCHEDULE | (login ? NO_DEFER : 0) | NO_INIT_VIEW; MessageWin* win = new MessageWin(&event, alarm, flags); win->mResource = resource; const bool rw = resource.isWritable(event.category()); win->mShowEdit = rw ? showEdit : false; win->mNoDefer = (rw && !login) ? !showDefer : true; win->initView(); win->show(); } } } } /****************************************************************************** * Retrieves the event with the current ID from the displaying calendar file, * or if not found there, from the archive calendar. */ bool MessageWin::retrieveEvent(KAEvent& event, Resource& resource, bool& showEdit, bool& showDefer) { const Event::Ptr kcalEvent = AlarmCalendar::displayCalendar()->kcalEvent(CalEvent::uid(mEventId.eventId(), CalEvent::DISPLAYING)); if (!reinstateFromDisplaying(kcalEvent, event, resource, showEdit, showDefer)) { // The event isn't in the displaying calendar. // Try to retrieve it from the archive calendar. KAEvent* ev = nullptr; Resource archiveRes = Resources::getStandard(CalEvent::ARCHIVED); if (archiveRes.isValid()) ev = AlarmCalendar::resources()->event(EventId(archiveRes.id(), CalEvent::uid(mEventId.eventId(), CalEvent::ARCHIVED))); if (!ev) return false; event = *ev; event.setArchive(); // ensure that it gets re-archived if it's saved event.setCategory(CalEvent::ACTIVE); if (mEventId.eventId() != event.id()) qCCritical(KALARM_LOG) << "MessageWin::retrieveEvent: Wrong event ID"; event.setEventId(mEventId.eventId()); resource = Resource(); showEdit = true; showDefer = true; qCDebug(KALARM_LOG) << "MessageWin::retrieveEvent:" << event.id() << ": success"; } return true; } /****************************************************************************** * Retrieves the displayed event from the calendar file, or if not found there, * from the displaying calendar. */ bool MessageWin::reinstateFromDisplaying(const Event::Ptr& kcalEvent, KAEvent& event, Resource& resource, bool& showEdit, bool& showDefer) { if (!kcalEvent) return false; Akonadi::Collection::Id collectionId; event.reinstateFromDisplaying(kcalEvent, collectionId, showEdit, showDefer); event.setCollectionId(collectionId); resource = Resources::resource(collectionId); qCDebug(KALARM_LOG) << "MessageWin::reinstateFromDisplaying:" << EventId(event) << ": success"; return true; } /****************************************************************************** * Called when an alarm is currently being displayed, to store a copy of the * alarm in the displaying calendar, and to reschedule it for its next repetition. * If no repetitions remain, cancel it. */ void MessageWin::alarmShowing(KAEvent& event) { qCDebug(KALARM_LOG) << "MessageWin::alarmShowing:" << event.id() << "," << KAAlarm::debugType(mAlarmType); const KAAlarm alarm = event.alarm(mAlarmType); if (!alarm.isValid()) { qCCritical(KALARM_LOG) << "MessageWin::alarmShowing: Alarm type not found:" << event.id() << ":" << mAlarmType; return; } if (!mAlwaysHide) { // Copy the alarm to the displaying calendar in case of a crash, etc. KAEvent dispEvent; const ResourceId id = Resources::resourceForEvent(event.id()).id(); dispEvent.setDisplaying(event, mAlarmType, id, mDateTime.effectiveKDateTime(), mShowEdit, !mNoDefer); AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); if (cal) { cal->deleteDisplayEvent(dispEvent.id()); // in case it already exists cal->addEvent(dispEvent); cal->save(); } } theApp()->rescheduleAlarm(event, alarm); } /****************************************************************************** * Spread alarm windows over the screen so that they are all visible, or pile * them on top of each other again. * Reply = true if windows are now scattered, false if piled up. */ bool MessageWin::spread(bool scatter) { if (instanceCount(true) <= 1) // ignore always-hidden windows return false; const QRect desk = KAlarm::desktopWorkArea(); // get the usable area of the desktop if (scatter == isSpread(desk.topLeft())) return scatter; if (scatter) { // Usually there won't be many windows, so a crude // scattering algorithm should suffice. int x = desk.left(); int y = desk.top(); int ynext = y; for (int errmsgs = 0; errmsgs < 2; ++errmsgs) { // Display alarm messages first, then error messages, since most // error messages tend to be the same height. for (int i = 0, end = mWindowList.count(); i < end; ++i) { MessageWin* w = mWindowList[i]; if ((!errmsgs && w->mErrorWindow) || (errmsgs && !w->mErrorWindow)) continue; const QSize sz = w->frameGeometry().size(); if (x + sz.width() > desk.right()) { x = desk.left(); y = ynext; } int ytmp = y; if (y + sz.height() > desk.bottom()) { ytmp = desk.bottom() - sz.height(); if (ytmp < desk.top()) ytmp = desk.top(); } w->move(x, ytmp); x += sz.width(); if (ytmp + sz.height() > ynext) ynext = ytmp + sz.height(); } } } else { // Move all windows to the top left corner for (int i = 0, end = mWindowList.count(); i < end; ++i) mWindowList[i]->move(desk.topLeft()); } return scatter; } /****************************************************************************** * Check whether message windows are all piled up, or are spread out. * Reply = true if windows are currently spread, false if piled up. */ bool MessageWin::isSpread(const QPoint& topLeft) { for (int i = 0, end = mWindowList.count(); i < end; ++i) { if (mWindowList[i]->pos() != topLeft) return true; } return false; } /****************************************************************************** * Returns the existing message window (if any) which is displaying the event * with the specified ID. */ MessageWin* MessageWin::findEvent(const EventId& eventId, MessageWin* exclude) { if (!eventId.isEmpty()) { for (int i = 0, end = mWindowList.count(); i < end; ++i) { MessageWin* w = mWindowList[i]; if (w != exclude && w->mEventId == eventId && !w->mErrorWindow) return w; } } return nullptr; } /****************************************************************************** * Beep and play the audio file, as appropriate. */ void MessageWin::playAudio() { if (mBeep) { // Beep using two methods, in case the sound card/speakers are switched off or not working QApplication::beep(); // beep through the internal speaker KNotification::beep(); // beep through the sound card & speakers } if (!mAudioFile.isEmpty()) { if (!mVolume && mFadeVolume <= 0) return; // ensure zero volume doesn't play anything startAudio(); // play the audio file } else if (mSpeak) { // The message is to be spoken. In case of error messges, // call it on a timer to allow the window to display first. QTimer::singleShot(0, this, &MessageWin::slotSpeak); } } /****************************************************************************** * Speak the message. * Called asynchronously to avoid delaying the display of the message. */ void MessageWin::slotSpeak() { KPIMTextEdit::TextToSpeech *tts = KPIMTextEdit::TextToSpeech::self(); if (!tts->isReady()) { KAMessageBox::detailedError(MainWindow::mainMainWindow(), i18nc("@info", "Unable to speak message"), i18nc("@info", "Text-to-speech subsystem is not available")); clearErrorMessage(ErrMsg_Speak); return; } tts->say(mMessage); } /****************************************************************************** * Called when another window's audio thread has been destructed. * Start playing this window's audio file. Because initialising the sound system * and loading the file may take some time, it is called in a separate thread to * allow the window to display first. */ void MessageWin::startAudio() { if (mAudioThread) { // An audio file is already playing for another message // window, so wait until it has finished. connect(mAudioThread.data(), &QObject::destroyed, this, &MessageWin::audioTerminating); } else { qCDebug(KALARM_LOG) << "MessageWin::startAudio:" << QThread::currentThread(); mAudioThread = new AudioThread(this, mAudioFile, mVolume, mFadeVolume, mFadeSeconds, mAudioRepeatPause); connect(mAudioThread.data(), &AudioThread::readyToPlay, this, &MessageWin::playReady); connect(mAudioThread.data(), &QThread::finished, this, &MessageWin::playFinished); if (mSilenceButton) connect(mSilenceButton, &QAbstractButton::clicked, mAudioThread.data(), &QThread::quit); // Notify after creating mAudioThread, so that isAudioPlaying() will // return the correct value. theApp()->notifyAudioPlaying(true); mAudioThread->start(); } } /****************************************************************************** * Return whether audio playback is currently active. */ bool MessageWin::isAudioPlaying() { return mAudioThread; } /****************************************************************************** * Stop audio playback. */ void MessageWin::stopAudio(bool wait) { qCDebug(KALARM_LOG) << "MessageWin::stopAudio"; if (mAudioThread) mAudioThread->stop(wait); } /****************************************************************************** * Called when another window's audio thread is being destructed. * Wait until the destructor has finished. */ void MessageWin::audioTerminating() { QTimer::singleShot(0, this, &MessageWin::startAudio); } /****************************************************************************** * Called when the audio file is ready to start playing. */ void MessageWin::playReady() { if (mSilenceButton) mSilenceButton->setEnabled(true); } /****************************************************************************** * Called when the audio file thread finishes. */ void MessageWin::playFinished() { if (mSilenceButton) mSilenceButton->setEnabled(false); if (mAudioThread) // mAudioThread can actually be null here! { const QString errmsg = mAudioThread->error(); if (!errmsg.isEmpty() && !haveErrorMessage(ErrMsg_AudioFile)) { KAMessageBox::error(this, errmsg); clearErrorMessage(ErrMsg_AudioFile); } } delete mAudioThread.data(); if (mAlwaysHide) close(); } /****************************************************************************** * Constructor for audio thread. */ AudioThread::AudioThread(MessageWin* parent, const QString& audioFile, float volume, float fadeVolume, int fadeSeconds, int repeatPause) : QThread(parent), mFile(audioFile), mVolume(volume), mFadeVolume(fadeVolume), mFadeSeconds(fadeSeconds), mRepeatPause(repeatPause), mAudioObject(nullptr) { if (mAudioOwner) qCCritical(KALARM_LOG) << "MessageWin::AudioThread: mAudioOwner already set"; mAudioOwner = parent; } /****************************************************************************** * Destructor for audio thread. Waits for thread completion and tidies up. * Note that this destructor is executed in the parent thread. */ AudioThread::~AudioThread() { qCDebug(KALARM_LOG) << "~MessageWin::AudioThread"; stop(true); // stop playing and tidy up (timeout 3 seconds) delete mAudioObject; mAudioObject = nullptr; if (mAudioOwner == parent()) mAudioOwner = nullptr; // Notify after deleting mAudioThread, so that isAudioPlaying() will // return the correct value. QTimer::singleShot(0, theApp(), &KAlarmApp::notifyAudioStopped); } /****************************************************************************** * Quits the thread and waits for thread completion and tidies up. */ void AudioThread::stop(bool waiT) { qCDebug(KALARM_LOG) << "MessageWin::AudioThread::stop"; quit(); // stop playing and tidy up wait(3000); // wait for run() to exit (timeout 3 seconds) if (!isFinished()) { // Something has gone wrong - forcibly kill the thread terminate(); if (waiT) wait(); } } /****************************************************************************** * Kick off the thread to play the audio file. */ void AudioThread::run() { mMutex.lock(); if (mAudioObject) { mMutex.unlock(); return; } qCDebug(KALARM_LOG) << "MessageWin::AudioThread::run:" << QThread::currentThread() << mFile; const QString audioFile = mFile; const QUrl url = QUrl::fromUserInput(mFile); mFile = url.isLocalFile() ? url.toLocalFile() : url.toString(); Phonon::MediaSource source(url); if (source.type() == Phonon::MediaSource::Invalid) { mError = xi18nc("@info", "Cannot open audio file: %1", audioFile); mMutex.unlock(); qCCritical(KALARM_LOG) << "MessageWin::AudioThread::run: Open failure:" << audioFile; return; } mAudioObject = new Phonon::MediaObject(); mAudioObject->setCurrentSource(source); mAudioObject->setTransitionTime(100); // workaround to prevent clipping of end of files in Xine backend Phonon::AudioOutput* output = new Phonon::AudioOutput(Phonon::NotificationCategory, mAudioObject); mPath = Phonon::createPath(mAudioObject, output); if (mVolume >= 0 || mFadeVolume >= 0) { const float vol = (mVolume >= 0) ? mVolume : output->volume(); const float maxvol = qMax(vol, mFadeVolume); output->setVolume(maxvol); if (mFadeVolume >= 0 && mFadeSeconds > 0) { Phonon::VolumeFaderEffect* fader = new Phonon::VolumeFaderEffect(mAudioObject); fader->setVolume(mFadeVolume / maxvol); fader->fadeTo(mVolume / maxvol, mFadeSeconds * 1000); mPath.insertEffect(fader); } } connect(mAudioObject, &Phonon::MediaObject::stateChanged, this, &AudioThread::playStateChanged, Qt::DirectConnection); connect(mAudioObject, &Phonon::MediaObject::finished, this, &AudioThread::checkAudioPlay, Qt::DirectConnection); mPlayedOnce = false; mPausing = false; mMutex.unlock(); Q_EMIT readyToPlay(); checkAudioPlay(); // Start an event loop. // The function will exit once exit() or quit() is called. // First, ensure that the thread object is deleted once it has completed. connect(this, &QThread::finished, this, &QObject::deleteLater); exec(); stopPlay(); } /****************************************************************************** * Called when the audio file has loaded and is ready to play, or when play * has completed. * If it is ready to play, start playing it (for the first time or repeated). * If play has not yet completed, wait a bit longer. */ void AudioThread::checkAudioPlay() { mMutex.lock(); if (!mAudioObject) { mMutex.unlock(); return; } if (mPausing) mPausing = false; else { // The file has loaded and is ready to play, or play has completed if (mPlayedOnce) { if (mRepeatPause < 0) { // Play has completed mMutex.unlock(); stopPlay(); return; } if (mRepeatPause > 0) { // Pause before playing the file again mPausing = true; QTimer::singleShot(mRepeatPause * 1000, this, &AudioThread::checkAudioPlay); mMutex.unlock(); return; } } mPlayedOnce = true; } // Start playing the file, either for the first time or again qCDebug(KALARM_LOG) << "MessageWin::AudioThread::checkAudioPlay: start"; mAudioObject->play(); mMutex.unlock(); } /****************************************************************************** * Called when the playback object changes state. * If an error has occurred, quit and return the error to the caller. */ void AudioThread::playStateChanged(Phonon::State newState) { if (newState == Phonon::ErrorState) { QMutexLocker locker(&mMutex); const QString err = mAudioObject->errorString(); if (!err.isEmpty()) { qCCritical(KALARM_LOG) << "MessageWin::AudioThread::playStateChanged: Play failure:" << mFile << ":" << err; mError = xi18nc("@info", "Error playing audio file: %1%2", mFile, err); exit(1); } } } /****************************************************************************** * Called when play completes, the Silence button is clicked, or the window is * closed, to terminate audio access. */ void AudioThread::stopPlay() { mMutex.lock(); if (mAudioObject) { mAudioObject->stop(); const QList effects = mPath.effects(); for (int i = 0; i < effects.count(); ++i) { mPath.removeEffect(effects[i]); delete effects[i]; } delete mAudioObject; mAudioObject = nullptr; } mMutex.unlock(); quit(); // exit the event loop, if it's still running } QString AudioThread::error() const { QMutexLocker locker(&mMutex); return mError; } /****************************************************************************** * Raise the alarm window, re-output any required audio notification, and * reschedule the alarm in the calendar file. */ void MessageWin::repeat(const KAAlarm& alarm) { if (!mInitialised) return; if (mDeferDlg) { // Cancel any deferral dialog so that the user notices something's going on, // and also because the deferral time limit will have changed. delete mDeferDlg; mDeferDlg = nullptr; } KAEvent* event = mEventId.isEmpty() ? nullptr : AlarmCalendar::resources()->event(mEventId); if (event) { mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred if (mAlwaysHide) playAudio(); else { if (!mDeferDlg || Preferences::modalMessages()) { raise(); playAudio(); } if (mDeferButton->isVisible()) { mDeferButton->setEnabled(true); setDeferralLimit(*event); // ensure that button is disabled when alarm can't be deferred any more } } alarmShowing(*event); } } /****************************************************************************** * Display the window. * If windows are being positioned away from the mouse cursor, it is initially * positioned at the top left to slightly reduce the number of times the * windows need to be moved in showEvent(). */ void MessageWin::show() { if (mCloseTime.isValid()) { // Set a timer to auto-close the window int delay = QDateTime::currentDateTimeUtc().secsTo(mCloseTime); if (delay < 0) delay = 0; QTimer::singleShot(delay * 1000, this, &QWidget::close); if (!delay) return; // don't show the window if auto-closing is already due } if (Preferences::messageButtonDelay() == 0) move(0, 0); MainWindowBase::show(); } /****************************************************************************** * Returns the window's recommended size exclusive of its frame. */ QSize MessageWin::sizeHint() const { QSize desired; switch (mAction) { case KAEvent::MESSAGE: desired = MainWindowBase::sizeHint(); break; case KAEvent::COMMAND: if (mShown) { // For command output, expand the window to accommodate the text const QSize texthint = mCommandText->sizeHint(); int w = texthint.width() + 2 * style()->pixelMetric(QStyle::PM_DefaultChildMargin); if (w < width()) w = width(); const int ypadding = height() - mCommandText->height(); desired = QSize(w, texthint.height() + ypadding); break; } // fall through to default Q_FALLTHROUGH(); default: return MainWindowBase::sizeHint(); } // Limit the size to fit inside the working area of the desktop const QSize desktop = KAlarm::desktopWorkArea(mScreenNumber).size(); const QSize frameThickness = frameGeometry().size() - geometry().size(); // title bar & window frame return desired.boundedTo(desktop - frameThickness); } /****************************************************************************** * Called when the window is shown. * The first time, output any required audio notification, and reschedule or * delete the event from the calendar file. */ void MessageWin::showEvent(QShowEvent* se) { MainWindowBase::showEvent(se); if (mShown || !mInitialised) return; if (mErrorWindow || mAlarmType == KAAlarm::INVALID_ALARM) { // Don't bother repositioning error messages, // and invalid alarms should be deleted anyway. enableButtons(); } else { /* Set the window size. * Note that the frame thickness is not yet known when this * method is called, so for large windows the size needs to be * set again later. */ bool execComplete = true; QSize s = sizeHint(); // fit the window round the message if (mAction == KAEvent::FILE && !mErrorMsgs.count()) KAlarm::readConfigWindowSize("FileMessage", s); resize(s); const QRect desk = KAlarm::desktopWorkArea(mScreenNumber); const QRect frame = frameGeometry(); mButtonDelay = Preferences::messageButtonDelay() * 1000; if (mButtonDelay) { // Position the window in the middle of the screen, and // delay enabling the buttons. mPositioning = true; move((desk.width() - frame.width())/2, (desk.height() - frame.height())/2); execComplete = false; } else { /* Try to ensure that the window can't accidentally be acknowledged * by the user clicking the mouse just as it appears. * To achieve this, move the window so that the OK button is as far away * from the cursor as possible. If the buttons are still too close to the * cursor, disable the buttons for a short time. * N.B. This can't be done in show(), since the geometry of the window * is not known until it is displayed. Unfortunately by moving the * window in showEvent(), a flicker is unavoidable. * See the Qt documentation on window geometry for more details. */ // PROBLEM: The frame size is not known yet! const QPoint cursor = QCursor::pos(); const QRect rect = geometry(); // Find the offsets from the outside of the frame to the edges of the OK button const QRect button(mOkButton->mapToParent(QPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight())); const int buttonLeft = button.left() + rect.left() - frame.left(); const int buttonRight = width() - button.right() + frame.right() - rect.right(); const int buttonTop = button.top() + rect.top() - frame.top(); const int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom(); const int centrex = (desk.width() + buttonLeft - buttonRight) / 2; const int centrey = (desk.height() + buttonTop - buttonBottom) / 2; const int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left(); const int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top(); // Find the enclosing rectangle for the new button positions // and check if the cursor is too near QRect buttons = mOkButton->geometry().united(mKAlarmButton->geometry()); buttons.translate(rect.left() + x - frame.left(), rect.top() + y - frame.top()); const int minDistance = proximityMultiple * mOkButton->height(); if ((abs(cursor.x() - buttons.left()) < minDistance || abs(cursor.x() - buttons.right()) < minDistance) && (abs(cursor.y() - buttons.top()) < minDistance || abs(cursor.y() - buttons.bottom()) < minDistance)) mButtonDelay = proximityButtonDelay; // too near - disable buttons initially if (x != frame.left() || y != frame.top()) { mPositioning = true; move(x, y); execComplete = false; } } if (execComplete) displayComplete(); // play audio, etc. } // Set the window size etc. once the frame size is known QTimer::singleShot(0, this, &MessageWin::frameDrawn); mShown = true; } /****************************************************************************** * Called when the window has been moved. */ void MessageWin::moveEvent(QMoveEvent* e) { MainWindowBase::moveEvent(e); theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber).topLeft())); if (mPositioning) { // The window has just been initially positioned mPositioning = false; displayComplete(); // play audio, etc. } } /****************************************************************************** * Called after (hopefully) the window frame size is known. * Reset the initial window size if it exceeds the working area of the desktop. * Set the 'spread windows' menu item status. */ void MessageWin::frameDrawn() { if (!mErrorWindow && mAction == KAEvent::MESSAGE) { const QSize s = sizeHint(); if (width() > s.width() || height() > s.height()) resize(s); } theApp()->setSpreadWindowsState(isSpread(KAlarm::desktopWorkArea(mScreenNumber).topLeft())); } /****************************************************************************** * Called when the window has been displayed properly (in its correct position), * to play sounds and reschedule the event. */ void MessageWin::displayComplete() { delete mTempFile; mTempFile = nullptr; playAudio(); if (mRescheduleEvent) alarmShowing(mEvent); if (!mAlwaysHide) { // Enable the window's buttons either now or after the configured delay if (mButtonDelay > 0) QTimer::singleShot(mButtonDelay, this, &MessageWin::enableButtons); else enableButtons(); } } /****************************************************************************** * Enable the window's buttons. */ void MessageWin::enableButtons() { mOkButton->setEnabled(true); mKAlarmButton->setEnabled(true); if (mDeferButton->isVisible() && !mDisableDeferral) mDeferButton->setEnabled(true); if (mEditButton) mEditButton->setEnabled(true); if (mKMailButton) mKMailButton->setEnabled(true); } /****************************************************************************** * Called when the window's size has changed (before it is painted). */ void MessageWin::resizeEvent(QResizeEvent* re) { if (mRestoreHeight) { // Restore the window height on session restoration if (mRestoreHeight != re->size().height()) { QSize size = re->size(); size.setHeight(mRestoreHeight); resize(size); } else if (isVisible()) mRestoreHeight = 0; } else { if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count()) KAlarm::writeConfigWindowSize("FileMessage", re->size()); MainWindowBase::resizeEvent(re); } } /****************************************************************************** * Called when a close event is received. * Only quits the application if there is no system tray icon displayed. */ void MessageWin::closeEvent(QCloseEvent* ce) { // Don't prompt or delete the alarm from the display calendar if the session is closing if (!mErrorWindow && !qApp->isSavingSession()) { if (mConfirmAck && !mNoCloseConfirm) { // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No. if (KAMessageBox::warningYesNo(this, i18nc("@info", "Do you really want to acknowledge this alarm?"), i18nc("@action:button", "Acknowledge Alarm"), KGuiItem(i18nc("@action:button", "Acknowledge")), KStandardGuiItem::cancel()) != KMessageBox::Yes) { ce->ignore(); return; } } if (!mEventId.isEmpty()) { // Delete from the display calendar KAlarm::deleteDisplayEvent(CalEvent::uid(mEventId.eventId(), CalEvent::DISPLAYING)); } } MainWindowBase::closeEvent(ce); } /****************************************************************************** * Called when the OK button is clicked. */ void MessageWin::slotOk() { if (mDontShowAgainCheck && mDontShowAgainCheck->isChecked()) KAlarm::setDontShowErrors(mEventId, mDontShowAgain); close(); } /****************************************************************************** * Called when the KMail button is clicked. * Tells KMail to display the email message displayed in this message window. */ void MessageWin::slotShowKMailMessage() { qCDebug(KALARM_LOG) << "MessageWin::slotShowKMailMessage"; if (mAkonadiItemId < 0) return; const QString err = KAlarm::runKMail(); if (!err.isNull()) { KAMessageBox::sorry(this, err); return; } org::kde::kmail::kmail kmail(KMAIL_DBUS_SERVICE, KMAIL_DBUS_PATH, QDBusConnection::sessionBus()); // Display the message contents QDBusReply reply = kmail.showMail(mAkonadiItemId); bool failed1 = true; bool failed2 = true; if (!reply.isValid()) qCCritical(KALARM_LOG) << "kmail 'showMail' D-Bus call failed:" << reply.error().message(); else if (reply.value()) failed1 = false; // Select the mail folder containing the message Akonadi::ItemFetchJob* job = new Akonadi::ItemFetchJob(Akonadi::Item(mAkonadiItemId)); job->fetchScope().setAncestorRetrieval(Akonadi::ItemFetchScope::Parent); Akonadi::Item::List items; if (job->exec()) items = job->items(); if (items.isEmpty() || !items.at(0).isValid()) qCWarning(KALARM_LOG) << "MessageWin::slotShowKMailMessage: No parent found for item" << mAkonadiItemId; else { const Akonadi::Item& it = items.at(0); const Akonadi::Collection::Id colId = it.parentCollection().id(); reply = kmail.selectFolder(QString::number(colId)); if (!reply.isValid()) qCCritical(KALARM_LOG) << "kmail 'selectFolder' D-Bus call failed:" << reply.error().message(); else if (reply.value()) failed2 = false; } if (failed1 || failed2) KAMessageBox::sorry(this, xi18nc("@info", "Unable to locate this email in KMail")); } /****************************************************************************** * Called when the Edit... button is clicked. * Displays the alarm edit dialog. * * NOTE: The alarm edit dialog is made a child of the main window, not this * window, so that if this window closes before the dialog (e.g. on * auto-close), KAlarm doesn't crash. The dialog is set non-modal so that * the main window is unaffected, but modal mode is simulated so that * this window is inactive while the dialog is open. */ void MessageWin::slotEdit() { qCDebug(KALARM_LOG) << "MessageWin::slotEdit"; MainWindow* mainWin = MainWindow::mainMainWindow(); mEditDlg = EditAlarmDlg::create(false, &mOriginalEvent, false, mainWin, EditAlarmDlg::RES_IGNORE); #if KWINDOWSYSTEM_VERSION >= QT_VERSION_CHECK(5,62,0) mEditDlg->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(mEditDlg->windowHandle(), winId()); #else KWindowSystem::setMainWindow(mEditDlg, winId()); #endif KWindowSystem::setOnAllDesktops(mEditDlg->winId(), false); setButtonsReadOnly(true); connect(mEditDlg, &QDialog::accepted, this, &MessageWin::editCloseOk); connect(mEditDlg, &QDialog::rejected, this, &MessageWin::editCloseCancel); connect(mEditDlg, &QObject::destroyed, this, &MessageWin::editCloseCancel); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &MessageWin::activeWindowChanged); mainWin->editAlarm(mEditDlg, mOriginalEvent); } /****************************************************************************** * Called when OK is clicked in the alarm edit dialog invoked by the Edit button. * Closes the window. */ void MessageWin::editCloseOk() { mEditDlg = nullptr; mNoCloseConfirm = true; // allow window to close without confirmation prompt close(); } /****************************************************************************** * Called when Cancel is clicked in the alarm edit dialog invoked by the Edit * button, or when the dialog is deleted. */ void MessageWin::editCloseCancel() { mEditDlg = nullptr; setButtonsReadOnly(false); } /****************************************************************************** * Called when the active window has changed. If this window has become the * active window and there is an alarm edit dialog, simulate a modal dialog by * making the alarm edit dialog the active window instead. */ void MessageWin::activeWindowChanged(WId win) { if (mEditDlg && win == winId()) KWindowSystem::activateWindow(mEditDlg->winId()); } /****************************************************************************** * Set or clear the read-only state of the dialog buttons. */ void MessageWin::setButtonsReadOnly(bool ro) { mOkButton->setReadOnly(ro, true); mDeferButton->setReadOnly(ro, true); mEditButton->setReadOnly(ro, true); if (mSilenceButton) mSilenceButton->setReadOnly(ro, true); if (mKMailButton) mKMailButton->setReadOnly(ro, true); mKAlarmButton->setReadOnly(ro, true); } /****************************************************************************** * Set up to disable the defer button when the deferral limit is reached. */ void MessageWin::setDeferralLimit(const KAEvent& event) { mDeferLimit = event.deferralLimit().effectiveKDateTime().toUtc().qDateTime(); MidnightTimer::connect(this, SLOT(checkDeferralLimit())); // check every day mDisableDeferral = false; checkDeferralLimit(); } /****************************************************************************** * Check whether the deferral limit has been reached. * If so, disable the Defer button. * N.B. Ideally, just a single QTimer::singleShot() call would be made to disable * the defer button at the corret time. But for a 32-bit integer, the * milliseconds parameter overflows in about 25 days, so instead a daily * check is done until the day when the deferral limit is reached, followed * by a non-overflowing QTimer::singleShot() call. */ void MessageWin::checkDeferralLimit() { if (!mDeferButton->isEnabled() || !mDeferLimit.isValid()) return; int n = KADateTime::currentLocalDate().daysTo(KADateTime(mDeferLimit, KADateTime::LocalZone).date()); if (n > 0) return; MidnightTimer::disconnect(this, SLOT(checkDeferralLimit())); if (n == 0) { // The deferral limit will be reached today n = QDateTime::currentDateTimeUtc().secsTo(mDeferLimit); if (n > 0) { QTimer::singleShot(n * 1000, this, &MessageWin::checkDeferralLimit); return; } } mDeferButton->setEnabled(false); mDisableDeferral = true; } /****************************************************************************** * Called when the Defer... button is clicked. * Displays the defer message dialog. */ void MessageWin::slotDefer() { mDeferDlg = new DeferAlarmDlg(KADateTime::currentDateTime(Preferences::timeSpec()).addSecs(60), mDateTime.isDateOnly(), false, this); + if (windowFlags() & Qt::X11BypassWindowManagerHint) + mDeferDlg->setWindowFlags(mDeferDlg->windowFlags() | Qt::X11BypassWindowManagerHint); mDeferDlg->setObjectName(QStringLiteral("DeferDlg")); // used by LikeBack mDeferDlg->setDeferMinutes(mDefaultDeferMinutes > 0 ? mDefaultDeferMinutes : Preferences::defaultDeferTime()); mDeferDlg->setLimit(mEvent); if (!Preferences::modalMessages()) lower(); if (mDeferDlg->exec() == QDialog::Accepted) { const DateTime dateTime = mDeferDlg->getDateTime(); const int delayMins = mDeferDlg->deferMinutes(); // Fetch the up-to-date alarm from the calendar. Note that it could have // changed since it was displayed. const KAEvent* event = mEventId.isEmpty() ? nullptr : AlarmCalendar::resources()->event(mEventId); if (event) { // The event still exists in the active calendar qCDebug(KALARM_LOG) << "MessageWin::slotDefer: Deferring event" << mEventId; KAEvent newev(*event); newev.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); newev.setDeferDefaultMinutes(delayMins); KAlarm::updateEvent(newev, mDeferDlg, true); if (newev.deferred()) mNoPostAction = true; } else { // Try to retrieve the event from the displaying or archive calendars Resource resource; KAEvent event; bool showEdit, showDefer; if (!retrieveEvent(event, resource, showEdit, showDefer)) { // The event doesn't exist any more !?!, so recurrence data, // flags, and more, have been lost. KAMessageBox::error(this, xi18nc("@info", "Cannot defer alarm:Alarm not found.")); raise(); delete mDeferDlg; mDeferDlg = nullptr; mDeferButton->setEnabled(false); mEditButton->setEnabled(false); return; } qCDebug(KALARM_LOG) << "MessageWin::slotDefer: Deferring retrieved event" << mEventId; event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true); event.setDeferDefaultMinutes(delayMins); event.setCommandError(mCommandError); // Add the event back into the calendar file, retaining its ID // and not updating KOrganizer. KAlarm::addEvent(event, &resource, mDeferDlg, KAlarm::USE_EVENT_ID); if (event.deferred()) mNoPostAction = true; // Finally delete it from the archived calendar now that it has // been reactivated. event.setCategory(CalEvent::ARCHIVED); KAlarm::deleteEvent(event, false); } if (theApp()->wantShowInSystemTray()) { // Alarms are to be displayed only if the system tray icon is running, // so start it if necessary so that the deferred alarm will be shown. theApp()->displayTrayIcon(true); } mNoCloseConfirm = true; // allow window to close without confirmation prompt close(); } else raise(); delete mDeferDlg; mDeferDlg = nullptr; } /****************************************************************************** * Called when the KAlarm icon button in the message window is clicked. * Displays the main window, with the appropriate alarm selected. */ void MessageWin::displayMainWindow() { KAlarm::displayMainWindowSelected(mEventId.eventId()); } /****************************************************************************** * Check whether the specified error message is already displayed for this * alarm, and note that it will now be displayed. * Reply = true if message is already displayed. */ bool MessageWin::haveErrorMessage(unsigned msg) const { if (!mErrorMessages.contains(mEventId)) mErrorMessages.insert(mEventId, 0); const bool result = (mErrorMessages[mEventId] & msg); mErrorMessages[mEventId] |= msg; return result; } void MessageWin::clearErrorMessage(unsigned msg) const { if (mErrorMessages.contains(mEventId)) { if (mErrorMessages[mEventId] == msg) mErrorMessages.remove(mEventId); else mErrorMessages[mEventId] &= ~msg; } } /****************************************************************************** * Check whether the message window should be modal, i.e. with title bar etc. * Normally this follows the Preferences setting, but if there is a full screen * window displayed, on X11 the message window has to bypass the window manager * in order to display on top of it (which has the side effect that it will have * no window decoration). * * Also find the usable area of the desktop (excluding panel etc.), on the * appropriate screen if there are multiple screens. */ bool MessageWin::getWorkAreaAndModal() { mScreenNumber = -1; const bool modal = Preferences::modalMessages(); #if KDEPIM_HAVE_X11 const QList screens = QGuiApplication::screens(); const int numScreens = screens.count(); if (numScreens > 1) { // There are multiple screens. // Check for any full screen windows, even if they are not the active // window, and try not to show the alarm message their screens. mScreenNumber = QApplication::desktop()->screenNumber(MainWindow::mainMainWindow()); // default = KAlarm's screen if (QGuiApplication::primaryScreen()->virtualSiblings().size() > 1) { // The screens form a single virtual desktop. // Xinerama, for example, uses this scheme. QVector screenTypes(numScreens); QVector screenRects(numScreens); for (int s = 0; s < numScreens; ++s) screenRects[s] = screens[s]->geometry(); const FullScreenType full = findFullScreenWindows(screenRects, screenTypes); if (full == NoFullScreen || screenTypes[mScreenNumber] == NoFullScreen) return modal; for (int s = 0; s < numScreens; ++s) { if (screenTypes[s] == NoFullScreen) { // There is no full screen window on this screen mScreenNumber = s; return modal; } } // All screens contain a full screen window: use one without // an active full screen window. for (int s = 0; s < numScreens; ++s) { if (screenTypes[s] == FullScreen) { mScreenNumber = s; return modal; } } } else { // The screens are completely separate from each other. int inactiveScreen = -1; FullScreenType full = haveFullScreenWindow(mScreenNumber); //qCDebug(KALARM_LOG)<<"full="<& screenRects, QVector& screenTypes) { FullScreenType result = NoFullScreen; screenTypes.fill(NoFullScreen); xcb_connection_t* connection = QX11Info::connection(); const NETRootInfo rootInfo(connection, NET::ClientList | NET::ActiveWindow, NET::Properties2()); const xcb_window_t rootWindow = rootInfo.rootWindow(); const xcb_window_t activeWindow = rootInfo.activeWindow(); const xcb_window_t* windows = rootInfo.clientList(); const int windowCount = rootInfo.clientListCount(); //qCDebug(KALARM_LOG)<<"Virtual desktops: Window count="<