diff --git a/Changelog b/Changelog index 54cfa33c..468dd5f5 100644 --- a/Changelog +++ b/Changelog @@ -1,1188 +1,1189 @@ KAlarm Change Log -=== Version 2.14.2 (KDE Applications 20.04.2) --- 21 May 2020 === +=== Version 2.14.2 (KDE Applications 20.04.2) --- 22 May 2020 === + Make multiple KAlarm invocations work (Qt >5.12, Frameworks >5.60) [KDE Bug 417108] + Fix failure to set no-autostart for non-KDE desktops, if a writable autostart file exists. +* Fix failure to execute command alarms in a terminal window. === Version 2.14.1 (KDE Applications 20.04.1) --- 11 May 2020 === + Correctly interpret resource IDs in command line and DBus calls. + Fix sizing and reconfiguration of columns in alarm and template lists. === Version 2.14.0 (KDE Applications 20.04) --- 27 March 2020 === + Warn user if archiving but no default archived alarms calendar is set. + Fix some error messages not being displayed. + Refactor to use generic resource classes (part 2). === Version 2.13.3 (KDE Applications 19.12.3) --- 20 February 2020 === + Fix failure of command line options requiring calendar access [KDE Bug 417108] === Version 2.13.2 (KDE Applications 19.12.2) --- 9 January 2020 === + Add Show/Hide Menubar menu option; change New Email Alarm shortcut to Ctrl-L. === Version 2.13.1 (KDE Applications 19.12.1) --- 30 December 2019 === + Make defer dialogue accessible when a full screen window is active [KDE Bug 414383] + 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 (part 1). === 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/editdlgtypes.cpp b/src/editdlgtypes.cpp index e927c96e..7f8c7393 100644 --- a/src/editdlgtypes.cpp +++ b/src/editdlgtypes.cpp @@ -1,1872 +1,1875 @@ /* * editdlgtypes.cpp - dialogs to create or edit alarm or alarm template types * 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 "editdlgtypes.h" #include "editdlg_p.h" #include "emailidcombo.h" #include "fontcolourbutton.h" #include "kalarmapp.h" #include "kamail.h" #include "latecancel.h" #include "mainwindow.h" #include "messagewin.h" #include "pickfileradio.h" #include "preferences.h" #include "reminder.h" #include "soundpicker.h" #include "sounddlg.h" #include "specialactions.h" #include "templatepickdlg.h" #include "lib/autoqpointer.h" #include "lib/buttongroup.h" #include "lib/checkbox.h" #include "lib/colourbutton.h" #include "lib/file.h" #include "lib/lineedit.h" #include "lib/messagebox.h" #include "lib/radiobutton.h" #include "lib/shellprocess.h" #include "lib/timespinbox.h" #include "kalarm_debug.h" #include #include #include #include using namespace KCalendarCore; #include #include #include #include #include #include #include #include #include #include #include #include using namespace KAlarmCal; enum { tTEXT, tFILE, tCOMMAND }; // order of mTypeCombo items /*============================================================================= = Class PickLogFileRadio =============================================================================*/ class PickLogFileRadio : public PickFileRadio { public: PickLogFileRadio(QPushButton* b, LineEdit* e, const QString& text, ButtonGroup* group, QWidget* parent) : PickFileRadio(b, e, text, group, parent) { } bool pickFile(QString& file) override // called when browse button is pressed to select a log file { return File::browseFile(file, i18nc("@title:window", "Choose Log File"), mDefaultDir, fileEdit()->text(), QString(), false, parentWidget()); } private: QString mDefaultDir; // default directory for log file browse button }; /*============================================================================= = Class EditDisplayAlarmDlg = Dialog to edit display alarms. =============================================================================*/ QString EditDisplayAlarmDlg::i18n_chk_ConfirmAck() { return i18nc("@option:check", "Confirm acknowledgment"); } /****************************************************************************** * Constructor. * Parameters: * Template = true to edit/create an alarm template * = false to edit/create an alarm. * event != to initialise the dialog to show the specified event's data. */ EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource) : EditAlarmDlg(Template, KAEvent::MESSAGE, parent, getResource) { qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: New"; init(nullptr); } EditDisplayAlarmDlg::EditDisplayAlarmDlg(bool Template, const KAEvent* event, bool newAlarm, QWidget* parent, GetResourceType getResource, bool readOnly) : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly) { qCDebug(KALARM_LOG) << "EditDisplayAlarmDlg: Event.id()"; init(event); } /****************************************************************************** * Return the window caption. */ QString EditDisplayAlarmDlg::type_caption() const { return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Display Alarm Template") : i18nc("@title:window", "Edit Display Alarm Template")) : (isNewAlarm() ? i18nc("@title:window", "New Display Alarm") : i18nc("@title:window", "Edit Display Alarm")); } /****************************************************************************** * Set up the dialog controls common to display alarms. */ void EditDisplayAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout) { // Display type combo box QWidget* box = new QWidget(parent); // to group widgets for QWhatsThis text QHBoxLayout* boxHLayout = new QHBoxLayout(box); boxHLayout->setContentsMargins(0, 0, 0, 0); QLabel* label = new QLabel(i18nc("@label:listbox", "Display type:"), box); boxHLayout->addWidget(label); label->setFixedSize(label->sizeHint()); mTypeCombo = new ComboBox(box); boxHLayout->addWidget(mTypeCombo); QString textItem = i18nc("@item:inlistbox", "Text message"); QString fileItem = i18nc("@item:inlistbox", "File contents"); QString commandItem = i18nc("@item:inlistbox", "Command output"); mTypeCombo->addItem(textItem); // index = tTEXT mTypeCombo->addItem(fileItem); // index = tFILE mTypeCombo->addItem(commandItem); // index = tCOMMAND mTypeCombo->setFixedSize(mTypeCombo->sizeHint()); mTypeCombo->setCurrentIndex(-1); // ensure slotAlarmTypeChanged() is called when index is set if (!ShellProcess::authorised()) { // User not authorised to issue shell commands - disable Command Output option QStandardItemModel* model = qobject_cast(mTypeCombo->model()); if (model) { QModelIndex index = model->index(2, mTypeCombo->modelColumn(), mTypeCombo->rootModelIndex()); QStandardItem* item = model->itemFromIndex(index); if (item) item->setEnabled(false); } } connect(mTypeCombo, static_cast(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::slotAlarmTypeChanged); connect(mTypeCombo, static_cast(&ComboBox::currentIndexChanged), this, &EditDisplayAlarmDlg::contentsChanged); label->setBuddy(mTypeCombo); box->setWhatsThis(xi18nc("@info:whatsthis", "Select what the alarm should display:" "%1: the alarm will display the text message you type in." "%2: the alarm will display the contents of a text or image file." "%3: the alarm will display the output from a command.", textItem, fileItem, commandItem)); boxHLayout->setStretchFactor(new QWidget(box), 1); // left adjust the control frameLayout->addWidget(box); // Text message edit box mTextMessageEdit = new TextEdit(parent); mTextMessageEdit->setLineWrapMode(KTextEdit::NoWrap); mTextMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the text of the alarm message. It may be multi-line.")); connect(mTextMessageEdit, &TextEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged); frameLayout->addWidget(mTextMessageEdit); // File name edit box mFileBox = new QWidget(parent); frameLayout->addWidget(mFileBox); QHBoxLayout* fileBoxHLayout = new QHBoxLayout(mFileBox); fileBoxHLayout->setContentsMargins(0, 0, 0, 0); fileBoxHLayout->setSpacing(0); mFileMessageEdit = new LineEdit(LineEdit::Url, mFileBox); fileBoxHLayout->addWidget(mFileMessageEdit); mFileMessageEdit->setAcceptDrops(true); mFileMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or URL of a text or image file to display.")); connect(mFileMessageEdit, &LineEdit::textChanged, this, &EditDisplayAlarmDlg::contentsChanged); // File browse button mFileBrowseButton = new QPushButton(mFileBox); fileBoxHLayout->addWidget(mFileBrowseButton); mFileBrowseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); int size = mFileBrowseButton->sizeHint().height(); mFileBrowseButton->setFixedSize(size, size); mFileBrowseButton->setToolTip(i18nc("@info:tooltip", "Choose a file")); mFileBrowseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a text or image file to display.")); connect(mFileBrowseButton, &QPushButton::clicked, this, &EditDisplayAlarmDlg::slotPickFile); // Command type checkbox and edit box mCmdEdit = new CommandEdit(parent); connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditDisplayAlarmDlg::slotCmdScriptToggled); connect(mCmdEdit, &CommandEdit::changed, this, &EditDisplayAlarmDlg::contentsChanged); frameLayout->addWidget(mCmdEdit); // Sound checkbox and file selector QHBoxLayout* hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0, 0, 0, 0); frameLayout->addLayout(hlayout); mSoundPicker = new SoundPicker(parent); mSoundPicker->setFixedSize(mSoundPicker->sizeHint()); connect(mSoundPicker, &SoundPicker::changed, this, &EditDisplayAlarmDlg::contentsChanged); hlayout->addWidget(mSoundPicker); hlayout->addSpacing(2 * style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); hlayout->addStretch(); // Font and colour choice button and sample text mFontColourButton = new FontColourButton(parent); mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height() * 3/2); hlayout->addWidget(mFontColourButton); connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::setColours); connect(mFontColourButton, &FontColourButton::selected, this, &EditDisplayAlarmDlg::contentsChanged); if (ShellProcess::authorised()) // don't display if shell commands not allowed (e.g. kiosk mode) { // Special actions button mSpecialActionsButton = new SpecialActionsButton(false, parent); mSpecialActionsButton->setFixedSize(mSpecialActionsButton->sizeHint()); connect(mSpecialActionsButton, &SpecialActionsButton::selected, this, &EditDisplayAlarmDlg::contentsChanged); frameLayout->addWidget(mSpecialActionsButton, 0, Qt::AlignRight); } // Top-adjust the controls mFilePadding = new QWidget(parent); hlayout = new QHBoxLayout(mFilePadding); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); frameLayout->addWidget(mFilePadding); frameLayout->setStretchFactor(mFilePadding, 1); } /****************************************************************************** * Create a reminder control. */ Reminder* EditDisplayAlarmDlg::createReminder(QWidget* parent) { return new Reminder(i18nc("@info:whatsthis", "Check to additionally display a reminder in advance of or after the main alarm time(s)."), xi18nc("@info:whatsthis", "Enter how long in advance of or after the main alarm to display a reminder alarm.%1", TimeSpinBox::shiftWhatsThis()), i18nc("@info:whatsthis", "Select whether the reminder should be triggered before or after the main alarm"), true, true, parent); } /****************************************************************************** * Create an "acknowledgement confirmation required" checkbox. */ CheckBox* EditDisplayAlarmDlg::createConfirmAckCheckbox(QWidget* parent) { CheckBox* confirmAck = new CheckBox(i18n_chk_ConfirmAck(), parent); confirmAck->setWhatsThis(i18nc("@info:whatsthis", "Check to be prompted for confirmation when you acknowledge the alarm.")); return confirmAck; } /****************************************************************************** * Initialise the dialog controls from the specified event. */ void EditDisplayAlarmDlg::type_initValues(const KAEvent* event) { mAkonadiItemId = -1; lateCancel()->showAutoClose(true); if (event) { if (mAlarmType == KAEvent::MESSAGE && event->akonadiItemId() && AlarmText::checkIfEmail(event->cleanText())) mAkonadiItemId = event->akonadiItemId(); lateCancel()->setAutoClose(event->autoClose()); if (event->useDefaultFont()) mFontColourButton->setDefaultFont(); else mFontColourButton->setFont(event->font()); mFontColourButton->setBgColour(event->bgColour()); mFontColourButton->setFgColour(event->fgColour()); setColours(event->fgColour(), event->bgColour()); mConfirmAck->setChecked(event->confirmAck()); bool recurs = event->recurs(); int reminderMins = event->reminderMinutes(); if (reminderMins > 0 && !event->reminderActive()) reminderMins = 0; // don't show advance reminder which has already passed if (!reminderMins) { if (event->reminderDeferral() && !recurs) { reminderMins = event->deferDateTime().minsTo(event->mainDateTime()); mReminderDeferral = true; } else if (event->reminderMinutes() && recurs) { reminderMins = event->reminderMinutes(); mReminderArchived = true; } } reminder()->setMinutes(reminderMins, dateOnly()); reminder()->setOnceOnly(event->reminderOnceOnly()); reminder()->enableOnceOnly(recurs); if (mSpecialActionsButton) mSpecialActionsButton->setActions(event->preAction(), event->postAction(), event->extraActionOptions()); Preferences::SoundType soundType = event->speak() ? Preferences::Sound_Speak : event->beep() ? Preferences::Sound_Beep : !event->audioFile().isEmpty() ? Preferences::Sound_File : Preferences::Sound_None; mSoundPicker->set(soundType, event->audioFile(), event->soundVolume(), event->fadeVolume(), event->fadeSeconds(), event->repeatSoundPause()); } else { // Set the values to their defaults if (!ShellProcess::authorised()) { // Don't allow shell commands in kiosk mode if (mSpecialActionsButton) mSpecialActionsButton->setEnabled(false); } lateCancel()->setAutoClose(Preferences::defaultAutoClose()); mTypeCombo->setCurrentIndex(0); mFontColourButton->setDefaultFont(); mFontColourButton->setBgColour(Preferences::defaultBgColour()); mFontColourButton->setFgColour(Preferences::defaultFgColour()); setColours(Preferences::defaultFgColour(), Preferences::defaultBgColour()); mConfirmAck->setChecked(Preferences::defaultConfirmAck()); reminder()->setMinutes(0, false); reminder()->enableOnceOnly(isTimedRecurrence()); // must be called after mRecurrenceEdit is set up if (mSpecialActionsButton) { KAEvent::ExtraActionOptions opts({}); if (Preferences::defaultExecPreActionOnDeferral()) opts |= KAEvent::ExecPreActOnDeferral; if (Preferences::defaultCancelOnPreActionError()) opts |= KAEvent::CancelOnPreActError; if (Preferences::defaultDontShowPreActionError()) opts |= KAEvent::DontShowPreActError; mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts); } mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(), Preferences::defaultSoundVolume(), -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1)); } } /****************************************************************************** * Called when the More/Less Options button is clicked. * Show/hide the optional options. */ void EditDisplayAlarmDlg::type_showOptions(bool more) { if (mSpecialActionsButton) { if (more) mSpecialActionsButton->show(); else mSpecialActionsButton->hide(); } } /****************************************************************************** * Called when the font/color button has been clicked. * Set the colors in the message text entry control. */ void EditDisplayAlarmDlg::setColours(const QColor& fgColour, const QColor& bgColour) { QPalette pal = mTextMessageEdit->palette(); pal.setColor(mTextMessageEdit->backgroundRole(), bgColour); pal.setColor(QPalette::Text, fgColour); mTextMessageEdit->setPalette(pal); pal = mTextMessageEdit->viewport()->palette(); pal.setColor(mTextMessageEdit->viewport()->backgroundRole(), bgColour); pal.setColor(QPalette::Text, fgColour); mTextMessageEdit->viewport()->setPalette(pal); // Change the color of existing text QTextCursor cursor = mTextMessageEdit->textCursor(); mTextMessageEdit->selectAll(); mTextMessageEdit->setTextColor(fgColour); mTextMessageEdit->setTextCursor(cursor); } /****************************************************************************** * Set the dialog's action and the action's text. */ void EditDisplayAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText) { QString text = alarmText.displayText(); switch (action) { case KAEvent::MESSAGE: mTypeCombo->setCurrentIndex(tTEXT); mTextMessageEdit->setPlainText(text); mAkonadiItemId = alarmText.isEmail() ? alarmText.akonadiItemId() : -1; break; case KAEvent::FILE: mTypeCombo->setCurrentIndex(tFILE); mFileMessageEdit->setText(text); break; case KAEvent::COMMAND: mTypeCombo->setCurrentIndex(tCOMMAND); mCmdEdit->setText(alarmText); break; default: Q_ASSERT(0); break; } } /****************************************************************************** * Initialise various values in the New Alarm dialogue. */ void EditDisplayAlarmDlg::setBgColour(const QColor& colour) { mFontColourButton->setBgColour(colour); setColours(mFontColourButton->fgColour(), colour); } void EditDisplayAlarmDlg::setFgColour(const QColor& colour) { mFontColourButton->setFgColour(colour); setColours(colour, mFontColourButton->bgColour()); } void EditDisplayAlarmDlg::setConfirmAck(bool confirm) { mConfirmAck->setChecked(confirm); } void EditDisplayAlarmDlg::setAutoClose(bool close) { lateCancel()->setAutoClose(close); } void EditDisplayAlarmDlg::setAudio(Preferences::SoundType type, const QString& file, float volume, int repeatPause) { mSoundPicker->set(type, file, volume, -1, 0, repeatPause); } void EditDisplayAlarmDlg::setReminder(int minutes, bool onceOnly) { reminder()->setMinutes(minutes, dateOnly()); reminder()->setOnceOnly(onceOnly); reminder()->enableOnceOnly(isTimedRecurrence()); } /****************************************************************************** * Set the read-only status of all non-template controls. */ void EditDisplayAlarmDlg::setReadOnly(bool readOnly) { mTypeCombo->setReadOnly(readOnly); mTextMessageEdit->setReadOnly(readOnly); mFileMessageEdit->setReadOnly(readOnly); mCmdEdit->setReadOnly(readOnly); mFontColourButton->setReadOnly(readOnly); mSoundPicker->setReadOnly(readOnly); mConfirmAck->setReadOnly(readOnly); reminder()->setReadOnly(readOnly); if (mSpecialActionsButton) mSpecialActionsButton->setReadOnly(readOnly); if (readOnly) mFileBrowseButton->hide(); else mFileBrowseButton->show(); EditAlarmDlg::setReadOnly(readOnly); } /****************************************************************************** * Save the state of all controls. */ void EditDisplayAlarmDlg::saveState(const KAEvent* event) { EditAlarmDlg::saveState(event); mSavedType = mTypeCombo->currentIndex(); mSavedCmdScript = mCmdEdit->isScript(); mSavedSoundType = mSoundPicker->sound(); mSavedSoundFile = mSoundPicker->file(); mSavedSoundVolume = mSoundPicker->volume(mSavedSoundFadeVolume, mSavedSoundFadeSeconds); mSavedRepeatPause = mSoundPicker->repeatPause(); mSavedConfirmAck = mConfirmAck->isChecked(); mSavedFont = mFontColourButton->font(); mSavedFgColour = mFontColourButton->fgColour(); mSavedBgColour = mFontColourButton->bgColour(); mSavedReminder = reminder()->minutes(); mSavedOnceOnly = reminder()->isOnceOnly(); mSavedAutoClose = lateCancel()->isAutoClose(); if (mSpecialActionsButton) { mSavedPreAction = mSpecialActionsButton->preAction(); mSavedPostAction = mSpecialActionsButton->postAction(); mSavedPreActionOptions = mSpecialActionsButton->options(); } } /****************************************************************************** * Check whether any of the controls has changed state since the dialog was * first displayed. * Reply = true if any controls have changed, or if it's a new event. * = false if no controls have changed. */ bool EditDisplayAlarmDlg::type_stateChanged() const { if (mSavedType != mTypeCombo->currentIndex() || mSavedCmdScript != mCmdEdit->isScript() || mSavedSoundType != mSoundPicker->sound() || mSavedConfirmAck != mConfirmAck->isChecked() || mSavedFont != mFontColourButton->font() || mSavedFgColour != mFontColourButton->fgColour() || mSavedBgColour != mFontColourButton->bgColour() || mSavedReminder != reminder()->minutes() || mSavedOnceOnly != reminder()->isOnceOnly() || mSavedAutoClose != lateCancel()->isAutoClose()) return true; if (mSpecialActionsButton) { if (mSavedPreAction != mSpecialActionsButton->preAction() || mSavedPostAction != mSpecialActionsButton->postAction() || mSavedPreActionOptions != mSpecialActionsButton->options()) return true; } if (mSavedSoundType == Preferences::Sound_File) { if (mSavedSoundFile != mSoundPicker->file()) return true; if (!mSavedSoundFile.isEmpty()) { float fadeVolume; int fadeSecs; if (mSavedRepeatPause != mSoundPicker->repeatPause() || mSavedSoundVolume != mSoundPicker->volume(fadeVolume, fadeSecs) || mSavedSoundFadeVolume != fadeVolume || mSavedSoundFadeSeconds != fadeSecs) return true; } } return false; } /****************************************************************************** * Extract the data in the dialog specific to the alarm type and set up a * KAEvent from it. */ void EditDisplayAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& text, int lateCancel, bool trial) { KAEvent::SubAction type; switch (mTypeCombo->currentIndex()) { case tFILE: type = KAEvent::FILE; break; case tCOMMAND: type = KAEvent::COMMAND; break; default: case tTEXT: type = KAEvent::MESSAGE; break; } event = KAEvent(dt, text, mFontColourButton->bgColour(), mFontColourButton->fgColour(), mFontColourButton->font(), type, lateCancel, getAlarmFlags()); if (type == KAEvent::MESSAGE) { if (AlarmText::checkIfEmail(text)) event.setAkonadiItemId(mAkonadiItemId); } float fadeVolume; int fadeSecs; float volume = mSoundPicker->volume(fadeVolume, fadeSecs); int repeatPause = mSoundPicker->repeatPause(); event.setAudioFile(mSoundPicker->file().toDisplayString(), volume, fadeVolume, fadeSecs, repeatPause); if (!trial && reminder()->isEnabled()) event.setReminder(reminder()->minutes(), reminder()->isOnceOnly()); if (mSpecialActionsButton && mSpecialActionsButton->isEnabled()) event.setActions(mSpecialActionsButton->preAction(), mSpecialActionsButton->postAction(), mSpecialActionsButton->options()); } /****************************************************************************** * Get the currently specified alarm flag bits. */ KAEvent::Flags EditDisplayAlarmDlg::getAlarmFlags() const { bool cmd = (mTypeCombo->currentIndex() == tCOMMAND); KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags(); if (mSoundPicker->sound() == Preferences::Sound_Beep) flags |= KAEvent::BEEP; if (mSoundPicker->sound() == Preferences::Sound_Speak) flags |= KAEvent::SPEAK; if (mSoundPicker->repeatPause() >= 0) flags |= KAEvent::REPEAT_SOUND; if (mConfirmAck->isChecked()) flags |= KAEvent::CONFIRM_ACK; if (lateCancel()->isAutoClose()) flags |= KAEvent::AUTO_CLOSE; if (mFontColourButton->defaultFont()) flags |= KAEvent::DEFAULT_FONT; if (cmd) flags |= KAEvent::DISPLAY_COMMAND; if (cmd && mCmdEdit->isScript()) flags |= KAEvent::SCRIPT; return flags; } /****************************************************************************** * Called when one of the alarm display type combo box is changed, to display * the appropriate set of controls for that action type. */ void EditDisplayAlarmDlg::slotAlarmTypeChanged(int index) { QWidget* focus = nullptr; switch (index) { case tTEXT: // text message mFileBox->hide(); mFilePadding->hide(); mCmdEdit->hide(); mTextMessageEdit->show(); mSoundPicker->showSpeak(true); mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the alarm message now")); focus = mTextMessageEdit; break; case tFILE: // file contents mTextMessageEdit->hide(); mFileBox->show(); mFilePadding->show(); mCmdEdit->hide(); mSoundPicker->showSpeak(false); mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the file now")); mFileMessageEdit->setNoSelect(); focus = mFileMessageEdit; break; case tCOMMAND: // command output mTextMessageEdit->hide(); mFileBox->hide(); slotCmdScriptToggled(mCmdEdit->isScript()); // show/hide mFilePadding mCmdEdit->show(); mSoundPicker->showSpeak(true); mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Display the command output now")); focus = mCmdEdit; break; } if (focus) focus->setFocus(); } /****************************************************************************** * Called when the file browse button is pressed to select a file to display. */ void EditDisplayAlarmDlg::slotPickFile() { static QString defaultDir; // default directory for file browse button QString file; if (File::browseFile(file, i18nc("@title:window", "Choose Text or Image File to Display"), defaultDir, mFileMessageEdit->text(), QString(), true, this)) { if (!file.isEmpty()) { mFileMessageEdit->setText(File::pathOrUrl(file)); contentsChanged(); } } } /****************************************************************************** * Called when one of the command type radio buttons is clicked, * to display the appropriate edit field. */ void EditDisplayAlarmDlg::slotCmdScriptToggled(bool on) { if (on) mFilePadding->hide(); else mFilePadding->show(); } /****************************************************************************** * Clean up the alarm text, and if it's a file, check whether it's valid. */ bool EditDisplayAlarmDlg::checkText(QString& result, bool showErrorMessage) const { switch (mTypeCombo->currentIndex()) { case tTEXT: result = mTextMessageEdit->toPlainText(); break; case tFILE: { QString fileName = mFileMessageEdit->text().trimmed(); QUrl url; File::FileErr err = File::checkFileExists(fileName, url, MainWindow::mainMainWindow()); if (err == File::FileErr::None) { KFileItem fi(url); switch (File::fileType(fi.currentMimeType())) { case File::TextFormatted: case File::TextPlain: case File::TextApplication: case File::Image: break; default: err = File::FileErr::NotTextImage; break; } } if (err != File::FileErr::None && showErrorMessage) { mFileMessageEdit->setFocus(); if (!File::showFileErrMessage(fileName, err, File::FileErr::BlankDisplay, const_cast(this))) return false; } result = fileName; break; } case tCOMMAND: result = mCmdEdit->text(const_cast(this), showErrorMessage); if (result.isEmpty()) return false; break; } return true; } /*============================================================================= = Class EditCommandAlarmDlg = Dialog to edit command alarms. =============================================================================*/ QString EditCommandAlarmDlg::i18n_chk_EnterScript() { return i18nc("@option:check", "Enter a script"); } QString EditCommandAlarmDlg::i18n_radio_ExecInTermWindow() { return i18nc("@option:radio", "Execute in terminal window"); } QString EditCommandAlarmDlg::i18n_chk_ExecInTermWindow() { return i18nc("@option:check", "Execute in terminal window"); } /****************************************************************************** * Constructor. * Parameters: * Template = true to edit/create an alarm template * = false to edit/create an alarm. * event != to initialise the dialog to show the specified event's data. */ EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource) : EditAlarmDlg(Template, KAEvent::COMMAND, parent, getResource) { qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: New"; init(nullptr); } EditCommandAlarmDlg::EditCommandAlarmDlg(bool Template, const KAEvent* event, bool newAlarm, QWidget* parent, GetResourceType getResource, bool readOnly) : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly) { qCDebug(KALARM_LOG) << "EditCommandAlarmDlg: Event.id()"; init(event); } /****************************************************************************** * Return the window caption. */ QString EditCommandAlarmDlg::type_caption() const { return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Command Alarm Template") : i18nc("@title:window", "Edit Command Alarm Template")) : (isNewAlarm() ? i18nc("@title:window", "New Command Alarm") : i18nc("@title:window", "Edit Command Alarm")); } /****************************************************************************** * Set up the command alarm dialog controls. */ void EditCommandAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout) { mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Execute the specified command now")); mCmdEdit = new CommandEdit(parent); connect(mCmdEdit, &CommandEdit::scriptToggled, this, &EditCommandAlarmDlg::slotCmdScriptToggled); connect(mCmdEdit, &CommandEdit::changed, this, &EditCommandAlarmDlg::contentsChanged); frameLayout->addWidget(mCmdEdit); // What to do with command output mCmdOutputBox = new QGroupBox(i18nc("@title:group", "Command Output"), parent); frameLayout->addWidget(mCmdOutputBox); QVBoxLayout* vlayout = new QVBoxLayout(mCmdOutputBox); int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mCmdOutputGroup = new ButtonGroup(mCmdOutputBox); connect(mCmdOutputGroup, &ButtonGroup::buttonSet, this, &EditCommandAlarmDlg::contentsChanged); // Execute in terminal window mCmdExecInTerm = new RadioButton(i18n_radio_ExecInTermWindow(), mCmdOutputBox); mCmdExecInTerm->setFixedSize(mCmdExecInTerm->sizeHint()); mCmdExecInTerm->setWhatsThis(i18nc("@info:whatsthis", "Check to execute the command in a terminal window")); mCmdOutputGroup->addButton(mCmdExecInTerm, Preferences::Log_Terminal); vlayout->addWidget(mCmdExecInTerm, 0, Qt::AlignLeft); // Log file name edit box QWidget* box = new QWidget(mCmdOutputBox); QHBoxLayout* boxHLayout = new QHBoxLayout(box); boxHLayout->setContentsMargins(0, 0, 0, 0); boxHLayout->setSpacing(0); (new QWidget(box))->setFixedWidth(mCmdExecInTerm->style()->pixelMetric(QStyle::PM_ExclusiveIndicatorWidth)); // indent the edit box mCmdLogFileEdit = new LineEdit(LineEdit::Url, box); boxHLayout->addWidget(mCmdLogFileEdit); mCmdLogFileEdit->setAcceptDrops(true); mCmdLogFileEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the name or path of the log file.")); connect(mCmdLogFileEdit, &LineEdit::textChanged, this, &EditCommandAlarmDlg::contentsChanged); // Log file browse button. // The file browser dialog is activated by the PickLogFileRadio class. QPushButton* browseButton = new QPushButton(box); boxHLayout->addWidget(browseButton); browseButton->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); int size = browseButton->sizeHint().height(); browseButton->setFixedSize(size, size); browseButton->setToolTip(i18nc("@info:tooltip", "Choose a file")); browseButton->setWhatsThis(i18nc("@info:whatsthis", "Select a log file.")); // Log output to file mCmdLogToFile = new PickLogFileRadio(browseButton, mCmdLogFileEdit, i18nc("@option:radio", "Log to file"), mCmdOutputGroup, mCmdOutputBox); mCmdLogToFile->setFixedSize(mCmdLogToFile->sizeHint()); mCmdLogToFile->setWhatsThis(i18nc("@info:whatsthis", "Check to log the command output to a local file. The output will be appended to any existing contents of the file.")); connect(mCmdLogToFile, &PickLogFileRadio::fileChanged, this, &EditCommandAlarmDlg::contentsChanged); mCmdOutputGroup->addButton(mCmdLogToFile, Preferences::Log_File); vlayout->addWidget(mCmdLogToFile, 0, Qt::AlignLeft); vlayout->addWidget(box); // Discard output mCmdDiscardOutput = new RadioButton(i18nc("@option:radio", "Discard"), mCmdOutputBox); mCmdDiscardOutput->setFixedSize(mCmdDiscardOutput->sizeHint()); mCmdDiscardOutput->setWhatsThis(i18nc("@info:whatsthis", "Check to discard command output.")); mCmdOutputGroup->addButton(mCmdDiscardOutput, Preferences::Log_Discard); vlayout->addWidget(mCmdDiscardOutput, 0, Qt::AlignLeft); // Top-adjust the controls mCmdPadding = new QWidget(parent); QHBoxLayout* hlayout = new QHBoxLayout(mCmdPadding); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); frameLayout->addWidget(mCmdPadding); frameLayout->setStretchFactor(mCmdPadding, 1); } /****************************************************************************** * Initialise the dialog controls from the specified event. */ void EditCommandAlarmDlg::type_initValues(const KAEvent* event) { if (event) { // Set the values to those for the specified event RadioButton* logType = event->commandXterm() ? mCmdExecInTerm : !event->logFile().isEmpty() ? mCmdLogToFile : mCmdDiscardOutput; if (logType == mCmdLogToFile) mCmdLogFileEdit->setText(event->logFile()); // set file name before setting radio button logType->setChecked(true); } else { // Set the values to their defaults mCmdEdit->setScript(Preferences::defaultCmdScript()); mCmdLogFileEdit->setText(Preferences::defaultCmdLogFile()); // set file name before setting radio button mCmdOutputGroup->setButton(Preferences::defaultCmdLogType()); } slotCmdScriptToggled(mCmdEdit->isScript()); } /****************************************************************************** * Called when the More/Less Options button is clicked. * Show/hide the optional options. */ void EditCommandAlarmDlg::type_showOptions(bool more) { if (more) mCmdOutputBox->show(); else mCmdOutputBox->hide(); } /****************************************************************************** * Set the dialog's action and the action's text. */ void EditCommandAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText) { Q_UNUSED(action); Q_ASSERT(action == KAEvent::COMMAND); mCmdEdit->setText(alarmText); } /****************************************************************************** * Set the read-only status of all non-template controls. */ void EditCommandAlarmDlg::setReadOnly(bool readOnly) { if (!isTemplate() && !ShellProcess::authorised()) readOnly = true; // don't allow editing of existing command alarms in kiosk mode mCmdEdit->setReadOnly(readOnly); mCmdExecInTerm->setReadOnly(readOnly); mCmdLogToFile->setReadOnly(readOnly); mCmdDiscardOutput->setReadOnly(readOnly); EditAlarmDlg::setReadOnly(readOnly); } /****************************************************************************** * Save the state of all controls. */ void EditCommandAlarmDlg::saveState(const KAEvent* event) { EditAlarmDlg::saveState(event); mSavedCmdScript = mCmdEdit->isScript(); mSavedCmdOutputRadio = mCmdOutputGroup->checkedButton(); mSavedCmdLogFile = mCmdLogFileEdit->text(); } /****************************************************************************** * Check whether any of the controls has changed state since the dialog was * first displayed. * Reply = true if any controls have changed, or if it's a new event. * = false if no controls have changed. */ bool EditCommandAlarmDlg::type_stateChanged() const { if (mSavedCmdScript != mCmdEdit->isScript() || mSavedCmdOutputRadio != mCmdOutputGroup->checkedButton()) return true; if (mCmdOutputGroup->checkedButton() == mCmdLogToFile) { if (mSavedCmdLogFile != mCmdLogFileEdit->text()) return true; } return false; } /****************************************************************************** * Extract the data in the dialog specific to the alarm type and set up a * KAEvent from it. */ void EditCommandAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& text, int lateCancel, bool trial) { Q_UNUSED(trial); event = KAEvent(dt, text, QColor(), QColor(), QFont(), KAEvent::COMMAND, lateCancel, getAlarmFlags()); if (mCmdOutputGroup->checkedButton() == mCmdLogToFile) event.setLogFile(mCmdLogFileEdit->text()); } /****************************************************************************** * Get the currently specified alarm flag bits. */ KAEvent::Flags EditCommandAlarmDlg::getAlarmFlags() const { KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags(); if (mCmdEdit->isScript()) flags |= KAEvent::SCRIPT; if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm) flags |= KAEvent::EXEC_IN_XTERM; return flags; } /****************************************************************************** * Validate and convert command alarm data. */ bool EditCommandAlarmDlg::type_validate(bool trial) { Q_UNUSED(trial); if (mCmdOutputGroup->checkedButton() == mCmdLogToFile) { // Validate the log file name QString file = mCmdLogFileEdit->text(); QFileInfo info(file); QDir::setCurrent(QDir::homePath()); bool err = file.isEmpty() || info.isDir(); if (!err) { if (info.exists()) { err = !info.isWritable(); } else { QFileInfo dirinfo(info.absolutePath()); // get absolute directory path err = (!dirinfo.isDir() || !dirinfo.isWritable()); } } if (err) { showMainPage(); mCmdLogFileEdit->setFocus(); KAMessageBox::sorry(this, i18nc("@info", "Log file must be the name or path of a local file, with write permission.")); return false; } // Convert the log file to an absolute path mCmdLogFileEdit->setText(info.absoluteFilePath()); } else if (mCmdOutputGroup->checkedButton() == mCmdExecInTerm) { - if (KAMessageBox::warningContinueCancel(this, xi18nc("@info", "No terminal is selected for command alarms." - "Please set it in the KAlarm Configuration dialog.")) - != KMessageBox::Continue) - return false; + if (Preferences::cmdXTermCommand().isEmpty()) + { + if (KAMessageBox::warningContinueCancel(this, xi18nc("@info", "No terminal is selected for command alarms." + "Please set it in the KAlarm Configuration dialog.")) + != KMessageBox::Continue) + return false; + } } return true; } /****************************************************************************** * Called when the Try action has been executed. * Tell the user the result of the Try action. */ void EditCommandAlarmDlg::type_executedTry(const QString& text, void* result) { ShellProcess* proc = (ShellProcess*)result; if (proc && proc != (void*)-1 && mCmdOutputGroup->checkedButton() != mCmdExecInTerm) { theApp()->commandMessage(proc, this); KAMessageBox::information(this, xi18nc("@info", "Command executed: %1", text)); theApp()->commandMessage(proc, nullptr); } } /****************************************************************************** * Called when one of the command type radio buttons is clicked, * to display the appropriate edit field. */ void EditCommandAlarmDlg::slotCmdScriptToggled(bool on) { if (on) mCmdPadding->hide(); else mCmdPadding->show(); } /****************************************************************************** * Clean up the alarm text. */ bool EditCommandAlarmDlg::checkText(QString& result, bool showErrorMessage) const { result = mCmdEdit->text(const_cast(this), showErrorMessage); if (result.isEmpty()) return false; return true; } /*============================================================================= = Class EditEmailAlarmDlg = Dialog to edit email alarms. =============================================================================*/ QString EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf() { return i18nc("@option:check", "Copy email to self"); } /****************************************************************************** * Constructor. * Parameters: * Template = true to edit/create an alarm template * = false to edit/create an alarm. * event != to initialise the dialog to show the specified event's data. */ EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource) : EditAlarmDlg(Template, KAEvent::EMAIL, parent, getResource) { qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: New"; init(nullptr); } EditEmailAlarmDlg::EditEmailAlarmDlg(bool Template, const KAEvent* event, bool newAlarm, QWidget* parent, GetResourceType getResource, bool readOnly) : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly) { qCDebug(KALARM_LOG) << "EditEmailAlarmDlg: Event.id()"; init(event); } /****************************************************************************** * Return the window caption. */ QString EditEmailAlarmDlg::type_caption() const { return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Email Alarm Template") : i18nc("@title:window", "Edit Email Alarm Template")) : (isNewAlarm() ? i18nc("@title:window", "New Email Alarm") : i18nc("@title:window", "Edit Email Alarm")); } /****************************************************************************** * Set up the email alarm dialog controls. */ void EditEmailAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout) { mTryButton->setWhatsThis(i18nc("@info:whatsthis", "Send the email to the specified addressees now")); QGridLayout* grid = new QGridLayout(); grid->setContentsMargins(0, 0, 0, 0); grid->setColumnStretch(1, 1); frameLayout->addLayout(grid); mEmailFromList = nullptr; if (Preferences::emailFrom() == Preferences::MAIL_FROM_KMAIL) { // Email sender identity QLabel* label = new QLabel(i18nc("@label:listbox 'From' email address", "From:"), parent); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 0); mEmailFromList = new EmailIdCombo(Identities::identityManager(), parent); mEmailFromList->setMinimumSize(mEmailFromList->sizeHint()); label->setBuddy(mEmailFromList); mEmailFromList->setWhatsThis(i18nc("@info:whatsthis", "Your email identity, used to identify you as the sender when sending email alarms.")); connect(mEmailFromList, &EmailIdCombo::identityChanged, this, &EditEmailAlarmDlg::contentsChanged); grid->addWidget(mEmailFromList, 0, 1, 1, 2); } // Email recipients QLabel* label = new QLabel(i18nc("@label:textbox Email addressee", "To:"), parent); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 1, 0); mEmailToEdit = new LineEdit(LineEdit::Emails, parent); mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint()); mEmailToEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the addresses of the email recipients. Separate multiple addresses by " "commas or semicolons.")); connect(mEmailToEdit, &LineEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged); grid->addWidget(mEmailToEdit, 1, 1); mEmailAddressButton = new QPushButton(parent); mEmailAddressButton->setIcon(QIcon::fromTheme(QStringLiteral("help-contents"))); int size = mEmailAddressButton->sizeHint().height(); mEmailAddressButton->setFixedSize(size, size); connect(mEmailAddressButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::openAddressBook); mEmailAddressButton->setToolTip(i18nc("@info:tooltip", "Open address book")); mEmailAddressButton->setWhatsThis(i18nc("@info:whatsthis", "Select email addresses from your address book.")); grid->addWidget(mEmailAddressButton, 1, 2); // Email subject label = new QLabel(i18nc("@label:textbox Email subject", "Subject:"), parent); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 2, 0); mEmailSubjectEdit = new LineEdit(parent); mEmailSubjectEdit->setMinimumSize(mEmailSubjectEdit->sizeHint()); label->setBuddy(mEmailSubjectEdit); mEmailSubjectEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the email subject.")); connect(mEmailSubjectEdit, &LineEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged); grid->addWidget(mEmailSubjectEdit, 2, 1, 1, 2); // Email body mEmailMessageEdit = new TextEdit(parent); mEmailMessageEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the email message.")); connect(mEmailMessageEdit, &TextEdit::textChanged, this, &EditEmailAlarmDlg::contentsChanged); frameLayout->addWidget(mEmailMessageEdit); // Email attachments grid = new QGridLayout(); grid->setContentsMargins(0, 0, 0, 0); frameLayout->addLayout(grid); label = new QLabel(i18nc("@label:listbox", "Attachments:"), parent); label->setFixedSize(label->sizeHint()); grid->addWidget(label, 0, 0); mEmailAttachList = new QComboBox(parent); mEmailAttachList->setEditable(true); mEmailAttachList->setMinimumSize(mEmailAttachList->sizeHint()); if (mEmailAttachList->lineEdit()) mEmailAttachList->lineEdit()->setReadOnly(true); label->setBuddy(mEmailAttachList); mEmailAttachList->setWhatsThis(i18nc("@info:whatsthis", "Files to send as attachments to the email.")); grid->addWidget(mEmailAttachList, 0, 1); grid->setColumnStretch(1, 1); mEmailAddAttachButton = new QPushButton(i18nc("@action:button", "Add..."), parent); connect(mEmailAddAttachButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::slotAddAttachment); mEmailAddAttachButton->setWhatsThis(i18nc("@info:whatsthis", "Add an attachment to the email.")); grid->addWidget(mEmailAddAttachButton, 0, 2); mEmailRemoveButton = new QPushButton(i18nc("@action:button", "Remove"), parent); connect(mEmailRemoveButton, &QPushButton::clicked, this, &EditEmailAlarmDlg::slotRemoveAttachment); mEmailRemoveButton->setWhatsThis(i18nc("@info:whatsthis", "Remove the highlighted attachment from the email.")); grid->addWidget(mEmailRemoveButton, 1, 2); // BCC email to sender mEmailBcc = new CheckBox(i18n_chk_CopyEmailToSelf(), parent); mEmailBcc->setFixedSize(mEmailBcc->sizeHint()); mEmailBcc->setWhatsThis(i18nc("@info:whatsthis", "If checked, the email will be blind copied to you.")); connect(mEmailBcc, &CheckBox::toggled, this, &EditEmailAlarmDlg::contentsChanged); grid->addWidget(mEmailBcc, 1, 0, 1, 2, Qt::AlignLeft); } /****************************************************************************** * Initialise the dialog controls from the specified event. */ void EditEmailAlarmDlg::type_initValues(const KAEvent* event) { if (event) { // Set the values to those for the specified event mEmailAttachList->addItems(event->emailAttachments()); mEmailToEdit->setText(event->emailAddresses(QStringLiteral(", "))); mEmailSubjectEdit->setText(event->emailSubject()); mEmailBcc->setChecked(event->emailBcc()); if (mEmailFromList) mEmailFromList->setCurrentIdentity(event->emailFromId()); } else { // Set the values to their defaults mEmailBcc->setChecked(Preferences::defaultEmailBcc()); } attachmentEnable(); } /****************************************************************************** * Enable/disable controls depending on whether any attachments are entered. */ void EditEmailAlarmDlg::attachmentEnable() { bool enable = mEmailAttachList->count(); mEmailAttachList->setEnabled(enable); if (mEmailRemoveButton) mEmailRemoveButton->setEnabled(enable); } /****************************************************************************** * Set the dialog's action and the action's text. */ void EditEmailAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText) { Q_UNUSED(action); Q_ASSERT(action == KAEvent::EMAIL); if (alarmText.isEmail()) { mEmailToEdit->setText(alarmText.to()); mEmailSubjectEdit->setText(alarmText.subject()); mEmailMessageEdit->setPlainText(alarmText.body()); } else mEmailMessageEdit->setPlainText(alarmText.displayText()); } /****************************************************************************** * Initialise various values in the New Alarm dialogue. */ void EditEmailAlarmDlg::setEmailFields(uint fromID, const KCalendarCore::Person::List& addresses, const QString& subject, const QStringList& attachments) { if (fromID && mEmailFromList) mEmailFromList->setCurrentIdentity(fromID); if (!addresses.isEmpty()) mEmailToEdit->setText(KAEvent::joinEmailAddresses(addresses, QStringLiteral(", "))); if (!subject.isEmpty()) mEmailSubjectEdit->setText(subject); if (!attachments.isEmpty()) { mEmailAttachList->addItems(attachments); attachmentEnable(); } } void EditEmailAlarmDlg::setBcc(bool bcc) { mEmailBcc->setChecked(bcc); } /****************************************************************************** * Set the read-only status of all non-template controls. */ void EditEmailAlarmDlg::setReadOnly(bool readOnly) { mEmailToEdit->setReadOnly(readOnly); mEmailSubjectEdit->setReadOnly(readOnly); mEmailMessageEdit->setReadOnly(readOnly); mEmailBcc->setReadOnly(readOnly); if (mEmailFromList) mEmailFromList->setReadOnly(readOnly); if (readOnly) { mEmailAddressButton->hide(); mEmailAddAttachButton->hide(); mEmailRemoveButton->hide(); } else { mEmailAddressButton->show(); mEmailAddAttachButton->show(); mEmailRemoveButton->show(); } EditAlarmDlg::setReadOnly(readOnly); } /****************************************************************************** * Save the state of all controls. */ void EditEmailAlarmDlg::saveState(const KAEvent* event) { EditAlarmDlg::saveState(event); if (mEmailFromList) mSavedEmailFrom = mEmailFromList->currentIdentityName(); mSavedEmailTo = mEmailToEdit->text(); mSavedEmailSubject = mEmailSubjectEdit->text(); mSavedEmailAttach.clear(); for (int i = 0, end = mEmailAttachList->count(); i < end; ++i) mSavedEmailAttach += mEmailAttachList->itemText(i); mSavedEmailBcc = mEmailBcc->isChecked(); } /****************************************************************************** * Check whether any of the controls has changed state since the dialog was * first displayed. * Reply = true if any controls have changed, or if it's a new event. * = false if no controls have changed. */ bool EditEmailAlarmDlg::type_stateChanged() const { QStringList emailAttach; for (int i = 0, end = mEmailAttachList->count(); i < end; ++i) emailAttach += mEmailAttachList->itemText(i); if ((mEmailFromList && mSavedEmailFrom != mEmailFromList->currentIdentityName()) || mSavedEmailTo != mEmailToEdit->text() || mSavedEmailSubject != mEmailSubjectEdit->text() || mSavedEmailAttach != emailAttach || mSavedEmailBcc != mEmailBcc->isChecked()) return true; return false; } /****************************************************************************** * Extract the data in the dialog specific to the alarm type and set up a * KAEvent from it. */ void EditEmailAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& text, int lateCancel, bool trial) { Q_UNUSED(trial); event = KAEvent(dt, text, QColor(), QColor(), QFont(), KAEvent::EMAIL, lateCancel, getAlarmFlags()); uint from = mEmailFromList ? mEmailFromList->currentIdentity() : 0; event.setEmail(from, mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments); } /****************************************************************************** * Get the currently specified alarm flag bits. */ KAEvent::Flags EditEmailAlarmDlg::getAlarmFlags() const { KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags(); if (mEmailBcc->isChecked()) flags |= KAEvent::EMAIL_BCC; return flags; } /****************************************************************************** * Convert the email addresses to a list, and validate them. Convert the email * attachments to a list. */ bool EditEmailAlarmDlg::type_validate(bool trial) { QString addrs = mEmailToEdit->text(); if (addrs.isEmpty()) mEmailAddresses.clear(); else { QString bad = KAMail::convertAddresses(addrs, mEmailAddresses); if (!bad.isEmpty()) { mEmailToEdit->setFocus(); KAMessageBox::error(this, xi18nc("@info", "Invalid email address: %1", bad)); return false; } } if (mEmailAddresses.isEmpty()) { mEmailToEdit->setFocus(); KAMessageBox::error(this, i18nc("@info", "No email address specified")); return false; } mEmailAttachments.clear(); for (int i = 0, end = mEmailAttachList->count(); i < end; ++i) { QString att = mEmailAttachList->itemText(i); switch (KAMail::checkAttachment(att)) { case 1: mEmailAttachments.append(att); break; case 0: break; // empty case -1: mEmailAttachList->setFocus(); KAMessageBox::error(this, xi18nc("@info", "Invalid email attachment: %1", att)); return false; } } if (trial && KAMessageBox::warningContinueCancel(this, i18nc("@info", "Do you really want to send the email now to the specified recipient(s)?"), i18nc("@action:button", "Confirm Email"), KGuiItem(i18nc("@action:button", "Send"))) != KMessageBox::Continue) return false; return true; } /****************************************************************************** * Called when the Try action is about to be executed. */ void EditEmailAlarmDlg::type_aboutToTry() { // Disconnect any previous connections, to prevent multiple messages being output disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess); connect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess); } /****************************************************************************** * Tell the user the result of the Try action. */ void EditEmailAlarmDlg::slotTrySuccess() { disconnect(theApp(), &KAlarmApp::execAlarmSuccess, this, &EditEmailAlarmDlg::slotTrySuccess); QString msg; QString to = KAEvent::joinEmailAddresses(mEmailAddresses, QStringLiteral("")); to.replace(QLatin1Char('<'), QStringLiteral("<")); to.replace(QLatin1Char('>'), QStringLiteral(">")); if (mEmailBcc->isChecked()) msg = QLatin1String("") + xi18nc("@info", "Email sent to:%1Bcc: %2", to, Preferences::emailBccAddress()) + QLatin1String(""); else msg = QLatin1String("") + xi18nc("@info", "Email sent to:%1", to) + QLatin1String(""); KAMessageBox::information(this, msg); } /****************************************************************************** * Get a selection from the Address Book. */ void EditEmailAlarmDlg::openAddressBook() { // Use AutoQPointer to guard against crash on application exit while // the dialogue is still open. It prevents double deletion (both on // deletion of MainWindow, and on return from this function). AutoQPointer dlg = new Akonadi::EmailAddressSelectionDialog(this); if (dlg->exec() != QDialog::Accepted) return; Akonadi::EmailAddressSelection::List selections = dlg->selectedAddresses(); if (selections.isEmpty()) return; Person person(selections.first().name(), selections.first().email()); QString addrs = mEmailToEdit->text().trimmed(); if (!addrs.isEmpty()) addrs += QLatin1String(", "); addrs += person.fullName(); mEmailToEdit->setText(addrs); } /****************************************************************************** * Select a file to attach to the email. */ void EditEmailAlarmDlg::slotAddAttachment() { QString file; if (File::browseFile(file, i18nc("@title:window", "Choose File to Attach"), mAttachDefaultDir, QString(), QString(), true, this)) { if (!file.isEmpty()) { mEmailAttachList->addItem(file); mEmailAttachList->setCurrentIndex(mEmailAttachList->count() - 1); // select the new item mEmailRemoveButton->setEnabled(true); mEmailAttachList->setEnabled(true); contentsChanged(); } } } /****************************************************************************** * Remove the currently selected attachment from the email. */ void EditEmailAlarmDlg::slotRemoveAttachment() { int item = mEmailAttachList->currentIndex(); mEmailAttachList->removeItem(item); int count = mEmailAttachList->count(); if (item >= count) mEmailAttachList->setCurrentIndex(count - 1); if (!count) { mEmailRemoveButton->setEnabled(false); mEmailAttachList->setEnabled(false); } contentsChanged(); } /****************************************************************************** * Clean up the alarm text. */ bool EditEmailAlarmDlg::checkText(QString& result, bool showErrorMessage) const { Q_UNUSED(showErrorMessage); result = mEmailMessageEdit->toPlainText(); return true; } /*============================================================================= = Class EditAudioAlarmDlg = Dialog to edit audio alarms with no display window. =============================================================================*/ /****************************************************************************** * Constructor. * Parameters: * Template = true to edit/create an alarm template * = false to edit/create an alarm. * event != to initialise the dialog to show the specified event's data. */ EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, QWidget* parent, GetResourceType getResource) : EditAlarmDlg(Template, KAEvent::AUDIO, parent, getResource) { qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: New"; init(nullptr); } EditAudioAlarmDlg::EditAudioAlarmDlg(bool Template, const KAEvent* event, bool newAlarm, QWidget* parent, GetResourceType getResource, bool readOnly) : EditAlarmDlg(Template, event, newAlarm, parent, getResource, readOnly) { qCDebug(KALARM_LOG) << "EditAudioAlarmDlg: Event.id()"; init(event); mTryButton->setEnabled(!MessageWin::isAudioPlaying()); connect(theApp(), &KAlarmApp::audioPlaying, this, &EditAudioAlarmDlg::slotAudioPlaying); } /****************************************************************************** * Return the window caption. */ QString EditAudioAlarmDlg::type_caption() const { return isTemplate() ? (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm Template") : i18nc("@title:window", "Edit Audio Alarm Template")) : (isNewAlarm() ? i18nc("@title:window", "New Audio Alarm") : i18nc("@title:window", "Edit Audio Alarm")); } /****************************************************************************** * Set up the dialog controls common to display alarms. */ void EditAudioAlarmDlg::type_init(QWidget* parent, QVBoxLayout* frameLayout) { // File name edit box mSoundConfig = new SoundWidget(false, true, parent); if (isTemplate()) mSoundConfig->setAllowEmptyFile(); connect(mSoundConfig, &SoundWidget::changed, this, &EditAudioAlarmDlg::contentsChanged); frameLayout->addWidget(mSoundConfig); // Top-adjust the controls mPadding = new QWidget(parent); QHBoxLayout* hlayout = new QHBoxLayout(mPadding); hlayout->setContentsMargins(0, 0, 0, 0); hlayout->setSpacing(0); frameLayout->addWidget(mPadding); frameLayout->setStretchFactor(mPadding, 1); } /****************************************************************************** * Initialise the dialog controls from the specified event. */ void EditAudioAlarmDlg::type_initValues(const KAEvent* event) { if (event) { mSoundConfig->set(event->audioFile(), event->soundVolume(), event->fadeVolume(), event->fadeSeconds(), (event->flags() & KAEvent::REPEAT_SOUND) ? event->repeatSoundPause() : -1); } else { // Set the values to their defaults mSoundConfig->set(Preferences::defaultSoundFile(), Preferences::defaultSoundVolume(), -1, 0, (Preferences::defaultSoundRepeat() ? 0 : -1)); } } /****************************************************************************** * Initialise various values in the New Alarm dialogue. */ void EditAudioAlarmDlg::setAudio(const QString& file, float volume) { mSoundConfig->set(file, volume); } /****************************************************************************** * Set the dialog's action and the action's text. */ void EditAudioAlarmDlg::setAction(KAEvent::SubAction action, const AlarmText& alarmText) { Q_UNUSED(action); Q_ASSERT(action == KAEvent::AUDIO); mSoundConfig->set(alarmText.displayText(), Preferences::defaultSoundVolume()); } /****************************************************************************** * Set the read-only status of all non-template controls. */ void EditAudioAlarmDlg::setReadOnly(bool readOnly) { mSoundConfig->setReadOnly(readOnly); EditAlarmDlg::setReadOnly(readOnly); } /****************************************************************************** * Save the state of all controls. */ void EditAudioAlarmDlg::saveState(const KAEvent* event) { EditAlarmDlg::saveState(event); mSavedFile = mSoundConfig->fileName(); mSoundConfig->getVolume(mSavedVolume, mSavedFadeVolume, mSavedFadeSeconds); mSavedRepeatPause = mSoundConfig->repeatPause(); } /****************************************************************************** * Check whether any of the controls has changed state since the dialog was * first displayed. * Reply = true if any controls have changed, or if it's a new event. * = false if no controls have changed. */ bool EditAudioAlarmDlg::type_stateChanged() const { if (mSavedFile != mSoundConfig->fileName()) return true; if (!mSavedFile.isEmpty() || isTemplate()) { float volume, fadeVolume; int fadeSecs; mSoundConfig->getVolume(volume, fadeVolume, fadeSecs); if (mSavedRepeatPause != mSoundConfig->repeatPause() || mSavedVolume != volume || mSavedFadeVolume != fadeVolume || mSavedFadeSeconds != fadeSecs) return true; } return false; } /****************************************************************************** * Extract the data in the dialog specific to the alarm type and set up a * KAEvent from it. */ void EditAudioAlarmDlg::type_setEvent(KAEvent& event, const KADateTime& dt, const QString& text, int lateCancel, bool trial) { Q_UNUSED(text); Q_UNUSED(trial); event = KAEvent(dt, QString(), QColor(), QColor(), QFont(), KAEvent::AUDIO, lateCancel, getAlarmFlags()); float volume, fadeVolume; int fadeSecs; mSoundConfig->getVolume(volume, fadeVolume, fadeSecs); int repeatPause = mSoundConfig->repeatPause(); QUrl url; mSoundConfig->file(url, false); event.setAudioFile(url.toString(), volume, fadeVolume, fadeSecs, repeatPause, isTemplate()); } /****************************************************************************** * Get the currently specified alarm flag bits. */ KAEvent::Flags EditAudioAlarmDlg::getAlarmFlags() const { KAEvent::Flags flags = EditAlarmDlg::getAlarmFlags(); if (mSoundConfig->repeatPause() >= 0) flags |= KAEvent::REPEAT_SOUND; return flags; } /****************************************************************************** * Check whether the file name is valid. */ bool EditAudioAlarmDlg::checkText(QString& result, bool showErrorMessage) const { QUrl url; if (!mSoundConfig->file(url, showErrorMessage)) { result.clear(); return false; } result = url.isLocalFile() ? url.toLocalFile() : url.toString(); return true; } /****************************************************************************** * Called when the Try button is clicked. * If the audio file is currently playing (as a result of previously clicking * the Try button), cancel playback. Otherwise, play the audio file. */ void EditAudioAlarmDlg::slotTry() { if (!MessageWin::isAudioPlaying()) EditAlarmDlg::slotTry(); // play the audio file else if (mMessageWin) { mMessageWin->stopAudio(); mMessageWin = nullptr; } } /****************************************************************************** * Called when the Try action has been executed. */ void EditAudioAlarmDlg::type_executedTry(const QString&, void* result) { mMessageWin = (MessageWin*)result; // note which MessageWin controls the audio playback if (mMessageWin) { slotAudioPlaying(true); connect(mMessageWin, &MessageWin::destroyed, this, &EditAudioAlarmDlg::audioWinDestroyed); } } /****************************************************************************** * Called when audio playing starts or stops. * Enable/disable/toggle the Try button. */ void EditAudioAlarmDlg::slotAudioPlaying(bool playing) { if (!playing) { // Nothing is playing, so enable the Try button mTryButton->setEnabled(true); mTryButton->setCheckable(false); mTryButton->setChecked(false); mMessageWin = nullptr; } else if (mMessageWin) { // The test sound file is playing, so enable the Try button and depress it mTryButton->setEnabled(true); mTryButton->setCheckable(true); mTryButton->setChecked(true); } else { // An alarm is playing, so disable the Try button mTryButton->setEnabled(false); mTryButton->setCheckable(false); mTryButton->setChecked(false); } } /*============================================================================= = Class CommandEdit = A widget to allow entry of a command or a command script. =============================================================================*/ CommandEdit::CommandEdit(QWidget* parent) : QWidget(parent) { QVBoxLayout* vlayout = new QVBoxLayout(this); vlayout->setContentsMargins(0, 0, 0, 0); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mTypeScript = new CheckBox(EditCommandAlarmDlg::i18n_chk_EnterScript(), this); mTypeScript->setFixedSize(mTypeScript->sizeHint()); mTypeScript->setWhatsThis(i18nc("@info:whatsthis", "Check to enter the contents of a script instead of a shell command line")); connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::slotCmdScriptToggled); connect(mTypeScript, &CheckBox::toggled, this, &CommandEdit::changed); vlayout->addWidget(mTypeScript, 0, Qt::AlignLeft); mCommandEdit = new LineEdit(LineEdit::Url, this); mCommandEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter a shell command to execute.")); connect(mCommandEdit, &LineEdit::textChanged, this, &CommandEdit::changed); vlayout->addWidget(mCommandEdit); mScriptEdit = new TextEdit(this); mScriptEdit->setWhatsThis(i18nc("@info:whatsthis", "Enter the contents of a script to execute")); connect(mScriptEdit, &TextEdit::textChanged, this, &CommandEdit::changed); vlayout->addWidget(mScriptEdit); slotCmdScriptToggled(mTypeScript->isChecked()); } /****************************************************************************** * Initialise the widget controls from the specified event. */ void CommandEdit::setScript(bool script) { mTypeScript->setChecked(script); } bool CommandEdit::isScript() const { return mTypeScript->isChecked(); } /****************************************************************************** * Set the widget's text. */ void CommandEdit::setText(const AlarmText& alarmText) { const QString text = alarmText.displayText(); const bool script = alarmText.isScript(); mTypeScript->setChecked(script); if (script) mScriptEdit->setPlainText(text); else mCommandEdit->setText(File::pathOrUrl(text)); } /****************************************************************************** * Return the widget's text. */ QString CommandEdit::text() const { QString result; if (mTypeScript->isChecked()) result = mScriptEdit->toPlainText(); else result = mCommandEdit->text(); return result.trimmed(); } /****************************************************************************** * Return the alarm text. * If 'showErrorMessage' is true and the text is empty, an error message is * displayed. */ QString CommandEdit::text(EditAlarmDlg* dlg, bool showErrorMessage) const { QString result = text(); if (showErrorMessage && result.isEmpty()) KAMessageBox::sorry(dlg, i18nc("@info", "Please enter a command or script to execute")); return result; } /****************************************************************************** * Set the read-only status of all controls. */ void CommandEdit::setReadOnly(bool readOnly) { mTypeScript->setReadOnly(readOnly); mCommandEdit->setReadOnly(readOnly); mScriptEdit->setReadOnly(readOnly); } /****************************************************************************** * Called when one of the command type radio buttons is clicked, * to display the appropriate edit field. */ void CommandEdit::slotCmdScriptToggled(bool on) { if (on) { mCommandEdit->hide(); mScriptEdit->show(); mScriptEdit->setFocus(); } else { mScriptEdit->hide(); mCommandEdit->show(); mCommandEdit->setFocus(); } Q_EMIT scriptToggled(on); } /****************************************************************************** * Returns the minimum size of the widget. */ QSize CommandEdit::minimumSizeHint() const { QSize t(mTypeScript->minimumSizeHint()); QSize s(mCommandEdit->minimumSizeHint().expandedTo(mScriptEdit->minimumSizeHint())); s.setHeight(s.height() + style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) + t.height()); if (s.width() < t.width()) s.setWidth(t.width()); return s; } /*============================================================================= = Class TextEdit = A text edit field with a minimum height of 3 text lines. =============================================================================*/ TextEdit::TextEdit(QWidget* parent) : KTextEdit(parent) { QSize tsize = sizeHint(); tsize.setHeight(fontMetrics().lineSpacing()*13/4 + 2*frameWidth()); setMinimumSize(tsize); } void TextEdit::dragEnterEvent(QDragEnterEvent* e) { if (KCalUtils::ICalDrag::canDecode(e->mimeData())) e->ignore(); // don't accept "text/calendar" objects KTextEdit::dragEnterEvent(e); } // vim: et sw=4: diff --git a/src/prefdlg.cpp b/src/prefdlg.cpp index ac1533b6..406d651c 100644 --- a/src/prefdlg.cpp +++ b/src/prefdlg.cpp @@ -1,2002 +1,2003 @@ /* * prefdlg.cpp - program preferences dialog * 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 "prefdlg.h" #include "prefdlg_p.h" #include "alarmcalendar.h" #include "alarmtimewidget.h" #include "editdlg.h" #include "editdlgtypes.h" #include "fontcolour.h" #include "functions.h" #include "kalarmapp.h" #include "kamail.h" #include "latecancel.h" #include "mainwindow.h" #include "preferences.h" #include "recurrenceedit.h" #include "sounddlg.h" #include "soundpicker.h" #include "specialactions.h" #include "traywindow.h" #include "resources/resources.h" #include "lib/buttongroup.h" #include "lib/colourbutton.h" #include "lib/config.h" #include "lib/desktop.h" #include "lib/label.h" #include "lib/locale.h" #include "lib/messagebox.h" #include "lib/radiobutton.h" #include "lib/stackedwidgets.h" #include "lib/timeedit.h" #include "lib/timespinbox.h" #include "lib/timezonecombo.h" #include "config-kalarm.h" #include "kalarm_debug.h" #include #include using namespace KHolidays; #include #include #include #include #include #include #include #if KDEPIM_HAVE_X11 #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KCalendarCore; using namespace KAlarmCal; static const char PREF_DIALOG_NAME[] = "PrefDialog"; // Command strings for executing commands in different types of terminal windows. // %t = window title parameter // %c = command to execute in terminal // %w = command to execute in terminal, with 'sleep 86400' appended // %C = temporary command file to execute in terminal // %W = temporary command file to execute in terminal, with 'sleep 86400' appended static QString xtermCommands[] = { QStringLiteral("xterm -sb -hold -title %t -e %c"), QStringLiteral("konsole --noclose -p tabtitle=%t -e ${SHELL:-sh} -c %c"), QStringLiteral("gnome-terminal -t %t -e %W"), QStringLiteral("eterm --pause -T %t -e %C"), // some systems use eterm... QStringLiteral("Eterm --pause -T %t -e %C"), // while some use Eterm QStringLiteral("rxvt -title %t -e ${SHELL:-sh} -c %w"), + QStringLiteral("xfce4-terminal -T %t -H -e %c"), QString() // end of list indicator - don't change! }; /*============================================================================= = Class KAlarmPrefDlg =============================================================================*/ KAlarmPrefDlg* KAlarmPrefDlg::mInstance = nullptr; void KAlarmPrefDlg::display() { if (!mInstance) { mInstance = new KAlarmPrefDlg; QSize s; if (Config::readWindowSize(PREF_DIALOG_NAME, s)) mInstance->resize(s); mInstance->show(); } else { #if KDEPIM_HAVE_X11 KWindowInfo info = KWindowInfo(mInstance->winId(), NET::WMGeometry | NET::WMDesktop); KWindowSystem::setCurrentDesktop(info.desktop()); #endif mInstance->setWindowState(mInstance->windowState() & ~Qt::WindowMinimized); // un-minimize it if necessary mInstance->raise(); mInstance->activateWindow(); } } KAlarmPrefDlg::KAlarmPrefDlg() : KPageDialog() { setAttribute(Qt::WA_DeleteOnClose); setObjectName(QStringLiteral("PrefDlg")); // used by LikeBack setWindowTitle(i18nc("@title:window", "Configure")); setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel | QDialogButtonBox::Help | QDialogButtonBox::RestoreDefaults | QDialogButtonBox::Apply); button(QDialogButtonBox::Ok)->setDefault(true); setFaceType(List); mTabScrollGroup = new StackedScrollGroup(this, this); mMiscPage = new MiscPrefTab(mTabScrollGroup); mMiscPageItem = new KPageWidgetItem(mMiscPage, i18nc("@title:tab General preferences", "General")); mMiscPageItem->setHeader(i18nc("@title General preferences", "General")); mMiscPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-other"))); addPage(mMiscPageItem); mTimePage = new TimePrefTab(mTabScrollGroup); mTimePageItem = new KPageWidgetItem(mTimePage, i18nc("@title:tab", "Time & Date")); mTimePageItem->setHeader(i18nc("@title", "Time and Date")); mTimePageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-time"))); addPage(mTimePageItem); mStorePage = new StorePrefTab(mTabScrollGroup); mStorePageItem = new KPageWidgetItem(mStorePage, i18nc("@title:tab", "Storage")); mStorePageItem->setHeader(i18nc("@title", "Alarm Storage")); mStorePageItem->setIcon(QIcon::fromTheme(QStringLiteral("system-file-manager"))); addPage(mStorePageItem); mEmailPage = new EmailPrefTab(mTabScrollGroup); mEmailPageItem = new KPageWidgetItem(mEmailPage, i18nc("@title:tab Email preferences", "Email")); mEmailPageItem->setHeader(i18nc("@title", "Email Alarm Settings")); mEmailPageItem->setIcon(QIcon::fromTheme(QStringLiteral("internet-mail"))); addPage(mEmailPageItem); mViewPage = new ViewPrefTab(mTabScrollGroup); mViewPageItem = new KPageWidgetItem(mViewPage, i18nc("@title:tab", "View")); mViewPageItem->setHeader(i18nc("@title", "View Settings")); mViewPageItem->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-theme"))); addPage(mViewPageItem); mEditPage = new EditPrefTab(mTabScrollGroup); mEditPageItem = new KPageWidgetItem(mEditPage, i18nc("@title:tab", "Edit")); mEditPageItem->setHeader(i18nc("@title", "Default Alarm Edit Settings")); mEditPageItem->setIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); addPage(mEditPageItem); connect(button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotOk); connect(button(QDialogButtonBox::Cancel), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotCancel); connect(button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotApply); connect(button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotDefault); connect(button(QDialogButtonBox::Help), &QAbstractButton::clicked, this, &KAlarmPrefDlg::slotHelp); restore(false); adjustSize(); } KAlarmPrefDlg::~KAlarmPrefDlg() { mInstance = nullptr; } void KAlarmPrefDlg::slotHelp() { KHelpClient::invokeHelp(QStringLiteral("preferences")); } // Apply the preferences that are currently selected void KAlarmPrefDlg::slotApply() { qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotApply"; QString errmsg = mEmailPage->validate(); if (!errmsg.isEmpty()) { setCurrentPage(mEmailPageItem); if (KAMessageBox::warningYesNo(this, errmsg) != KMessageBox::Yes) { mValid = false; return; } } errmsg = mEditPage->validate(); if (!errmsg.isEmpty()) { setCurrentPage(mEditPageItem); KAMessageBox::sorry(this, errmsg); mValid = false; return; } mValid = true; mEmailPage->apply(false); mViewPage->apply(false); mEditPage->apply(false); mStorePage->apply(false); mTimePage->apply(false); mMiscPage->apply(false); Preferences::self()->save(); } // Apply the preferences that are currently selected void KAlarmPrefDlg::slotOk() { qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotOk"; mValid = true; slotApply(); if (mValid) QDialog::accept(); } // Discard the current preferences and close the dialog void KAlarmPrefDlg::slotCancel() { qCDebug(KALARM_LOG) << "KAlarmPrefDlg::slotCancel"; restore(false); KPageDialog::reject(); } // Reset all controls to the application defaults void KAlarmPrefDlg::slotDefault() { switch (KAMessageBox::questionYesNoCancel(this, i18nc("@info", "Reset all tabs to their default values, or only reset the current tab?"), QString(), KGuiItem(i18nc("@action:button Reset ALL tabs", "&All")), KGuiItem(i18nc("@action:button Reset the CURRENT tab", "C&urrent")))) { case KMessageBox::Yes: restore(true); // restore all tabs break; case KMessageBox::No: Preferences::self()->useDefaults(true); static_cast(currentPage()->widget())->restore(true, false); Preferences::self()->useDefaults(false); break; default: break; } } // Discard the current preferences and use the present ones void KAlarmPrefDlg::restore(bool defaults) { qCDebug(KALARM_LOG) << "KAlarmPrefDlg::restore:" << (defaults ? "defaults" : ""); if (defaults) Preferences::self()->useDefaults(true); mEmailPage->restore(defaults, true); mViewPage->restore(defaults, true); mEditPage->restore(defaults, true); mStorePage->restore(defaults, true); mTimePage->restore(defaults, true); mMiscPage->restore(defaults, true); if (defaults) Preferences::self()->useDefaults(false); } /****************************************************************************** * Return the minimum size for the dialog. * If the minimum size would be too high to fit the desktop, the tab contents * are made scrollable. */ QSize KAlarmPrefDlg::minimumSizeHint() const { if (!mTabScrollGroup->sized()) { QSize s = mTabScrollGroup->adjustSize(); if (s.isValid()) { if (mTabScrollGroup->heightReduction()) { s = QSize(s.width(), s.height() - mTabScrollGroup->heightReduction()); const_cast(this)->resize(s); } return s; } } return KPageDialog::minimumSizeHint(); } void KAlarmPrefDlg::showEvent(QShowEvent* e) { KPageDialog::showEvent(e); if (!mShown) { mTabScrollGroup->adjustSize(true); mShown = true; } } /****************************************************************************** * Called when the dialog's size has changed. * Records the new size in the config file. */ void KAlarmPrefDlg::resizeEvent(QResizeEvent* re) { if (isVisible()) Config::writeWindowSize(PREF_DIALOG_NAME, re->size()); KPageDialog::resizeEvent(re); } /*============================================================================= = Class PrefsTabBase =============================================================================*/ int PrefsTabBase::mIndentWidth = 0; PrefsTabBase::PrefsTabBase(StackedScrollGroup* scrollGroup) : StackedScrollWidget(scrollGroup) { QFrame* topWidget = new QFrame(this); setWidget(topWidget); mTopLayout = new QVBoxLayout(topWidget); mTopLayout->setContentsMargins(0, 0, 0, 0); mTopLayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); if (!mIndentWidth) { QRadioButton radio(this); QStyleOptionButton opt; opt.initFrom(&radio); mIndentWidth = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &opt).width(); } } void PrefsTabBase::apply(bool syncToDisc) { if (syncToDisc) Preferences::self()->save(); } void PrefsTabBase::addAlignedLabel(QLabel* label) { mLabels += label; } void PrefsTabBase::showEvent(QShowEvent*) { if (!mLabelsAligned) { int wid = 0; int i; const int end = mLabels.count(); QList xpos; for (i = 0; i < end; ++i) { const int x = mLabels[i]->mapTo(this, QPoint(0, 0)).x(); xpos += x; const int w = x + mLabels[i]->sizeHint().width(); if (w > wid) wid = w; } for (i = 0; i < end; ++i) { mLabels[i]->setFixedWidth(wid - xpos[i]); mLabels[i]->setAlignment(Qt::AlignRight | Qt::AlignVCenter); } mLabelsAligned = true; } } /*============================================================================= = Class MiscPrefTab =============================================================================*/ MiscPrefTab::MiscPrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { QGroupBox* group = new QGroupBox(i18nc("@title:group", "Run Mode")); topLayout()->addWidget(group); QVBoxLayout* vlayout = new QVBoxLayout(group); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // Start at login mAutoStart = new QCheckBox(i18nc("@option:check", "Start at login"), group); connect(mAutoStart, &QAbstractButton::clicked, this, &MiscPrefTab::slotAutostartClicked); mAutoStart->setWhatsThis(xi18nc("@info:whatsthis", "Automatically start KAlarm whenever you start KDE." "This option should always be checked unless you intend to discontinue use of KAlarm.")); vlayout->addWidget(mAutoStart, 0, Qt::AlignLeft); mQuitWarn = new QCheckBox(i18nc("@option:check", "Warn before quitting"), group); mQuitWarn->setWhatsThis(xi18nc("@info:whatsthis", "Check to display a warning prompt before quitting KAlarm.")); vlayout->addWidget(mQuitWarn, 0, Qt::AlignLeft); // Confirm alarm deletion? QWidget* widget = new QWidget; // this is for consistent left alignment topLayout()->addWidget(widget); QHBoxLayout* hbox = new QHBoxLayout(widget); hbox->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mConfirmAlarmDeletion = new QCheckBox(i18nc("@option:check", "Confirm alarm deletions")); mConfirmAlarmDeletion->setMinimumSize(mConfirmAlarmDeletion->sizeHint()); mConfirmAlarmDeletion->setWhatsThis(i18nc("@info:whatsthis", "Check to be prompted for confirmation each time you delete an alarm.")); hbox->addWidget(mConfirmAlarmDeletion); hbox->addStretch(); // left adjust the controls // Default alarm deferral time widget = new QWidget; // this is to control the QWhatsThis text display area topLayout()->addWidget(widget); hbox = new QHBoxLayout(widget); hbox->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QLabel* label = new QLabel(i18nc("@label:spinbox", "Default defer time interval:")); hbox->addWidget(label); mDefaultDeferTime = new TimeSpinBox(1, 5999); mDefaultDeferTime->setMinimumSize(mDefaultDeferTime->sizeHint()); hbox->addWidget(mDefaultDeferTime); widget->setWhatsThis(i18nc("@info:whatsthis", "Enter the default time interval (hours & minutes) to defer alarms, used by the Defer Alarm dialog.")); label->setBuddy(mDefaultDeferTime); hbox->addStretch(); // left adjust the controls // Terminal window to use for command alarms group = new QGroupBox(i18nc("@title:group", "Terminal for Command Alarms")); group->setWhatsThis(i18nc("@info:whatsthis", "Choose which application to use when a command alarm is executed in a terminal window")); topLayout()->addWidget(group); QGridLayout* grid = new QGridLayout(group); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); int row = 0; mXtermType = new ButtonGroup(group); int index = 0; mXtermFirst = -1; for (mXtermCount = 0; !xtermCommands[mXtermCount].isNull(); ++mXtermCount) { QString cmd = xtermCommands[mXtermCount]; const QStringList args = KShell::splitArgs(cmd); if (args.isEmpty() || QStandardPaths::findExecutable(args[0]).isEmpty()) continue; QRadioButton* radio = new QRadioButton(args[0], group); radio->setMinimumSize(radio->sizeHint()); mXtermType->addButton(radio, mXtermCount); if (mXtermFirst < 0) mXtermFirst = mXtermCount; // note the id of the first button cmd.replace(QStringLiteral("%t"), KAboutData::applicationData().displayName()); cmd.replace(QStringLiteral("%c"), QStringLiteral("")); cmd.replace(QStringLiteral("%w"), QStringLiteral("")); cmd.replace(QStringLiteral("%C"), QStringLiteral("[command]")); cmd.replace(QStringLiteral("%W"), QStringLiteral("[command; sleep]")); radio->setWhatsThis( xi18nc("@info:whatsthis", "Check to execute command alarms in a terminal window by %1", cmd)); grid->addWidget(radio, (row = index/3), index % 3, Qt::AlignLeft); ++index; } // QHBox used here doesn't allow the QLineEdit to expand!? QHBoxLayout* hlayout = new QHBoxLayout(); hlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->addLayout(hlayout, row + 1, 0, 1, 3, Qt::AlignLeft); QRadioButton* radio = new QRadioButton(i18nc("@option:radio Other terminal window command", "Other:"), group); hlayout->addWidget(radio); connect(radio, &QAbstractButton::toggled, this, &MiscPrefTab::slotOtherTerminalToggled); mXtermType->addButton(radio, mXtermCount); if (mXtermFirst < 0) mXtermFirst = mXtermCount; // note the id of the first button mXtermCommand = new QLineEdit(group); mXtermCommand->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); hlayout->addWidget(mXtermCommand); const QString wt = xi18nc("@info:whatsthis", "Enter the full command line needed to execute a command in your chosen terminal window. " "By default the alarm's command string will be appended to what you enter here. " "See the KAlarm Handbook for details of special codes to tailor the command line."); radio->setWhatsThis(wt); mXtermCommand->setWhatsThis(wt); topLayout()->addStretch(); // top adjust the widgets } void MiscPrefTab::restore(bool defaults, bool) { mAutoStart->setChecked(defaults ? true : Preferences::autoStart()); mQuitWarn->setChecked(Preferences::quitWarn()); mConfirmAlarmDeletion->setChecked(Preferences::confirmAlarmDeletion()); mDefaultDeferTime->setValue(Preferences::defaultDeferTime()); QString xtermCmd = Preferences::cmdXTermCommand(); int id = mXtermFirst; if (!xtermCmd.isEmpty()) { for ( ; id < mXtermCount; ++id) { if (mXtermType->find(id) && xtermCmd == xtermCommands[id]) break; } } mXtermType->setButton(id); mXtermCommand->setEnabled(id == mXtermCount); mXtermCommand->setText(id == mXtermCount ? xtermCmd : QString()); } void MiscPrefTab::apply(bool syncToDisc) { // First validate anything entered in Other X-terminal command int xtermID = mXtermType->selectedId(); if (xtermID >= mXtermCount) { QString cmd = mXtermCommand->text(); if (cmd.isEmpty()) xtermID = -1; // 'Other' is only acceptable if it's non-blank else { const QStringList args = KShell::splitArgs(cmd); cmd = args.isEmpty() ? QString() : args[0]; if (QStandardPaths::findExecutable(cmd).isEmpty()) { mXtermCommand->setFocus(); if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(), xi18nc("@info", "Command to invoke terminal window not found: %1", cmd)) != KMessageBox::Continue) return; } } } if (xtermID < 0) { xtermID = mXtermFirst; mXtermType->setButton(mXtermFirst); } if (mQuitWarn->isEnabled()) { const bool b = mQuitWarn->isChecked(); if (b != Preferences::quitWarn()) Preferences::setQuitWarn(b); } bool b = mAutoStart->isChecked(); if (b != Preferences::autoStart()) { Preferences::setAutoStart(b); Preferences::setAskAutoStart(true); // cancel any start-at-login prompt suppression if (b) Preferences::setNoAutoStart(false); Preferences::setAutoStartChangedByUser(true); // prevent prompting the user on quit, about start-at-login } b = mConfirmAlarmDeletion->isChecked(); if (b != Preferences::confirmAlarmDeletion()) Preferences::setConfirmAlarmDeletion(b); int i = mDefaultDeferTime->value(); if (i != Preferences::defaultDeferTime()) Preferences::setDefaultDeferTime(i); QString text = (xtermID < mXtermCount) ? xtermCommands[xtermID] : mXtermCommand->text(); if (text != Preferences::cmdXTermCommand()) Preferences::setCmdXTermCommand(text); PrefsTabBase::apply(syncToDisc); } void MiscPrefTab::slotAutostartClicked() { if (!mAutoStart->isChecked() && KAMessageBox::warningYesNo(topLayout()->parentWidget(), xi18nc("@info", "You should not uncheck this option unless you intend to discontinue use of KAlarm"), QString(), KStandardGuiItem::cont(), KStandardGuiItem::cancel() ) != KMessageBox::Yes) mAutoStart->setChecked(true); } void MiscPrefTab::slotOtherTerminalToggled(bool on) { mXtermCommand->setEnabled(on); } /*============================================================================= = Class TimePrefTab =============================================================================*/ TimePrefTab::TimePrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { // Default time zone QHBoxLayout* itemBox = new QHBoxLayout(); itemBox->setContentsMargins(0, 0, 0, 0); qobject_cast(topLayout())->addLayout(itemBox); QWidget* widget = new QWidget; // this is to control the QWhatsThis text display area itemBox->addWidget(widget); QHBoxLayout* box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QLabel* label = new QLabel(i18nc("@label:listbox", "Time zone:")); box->addWidget(label); addAlignedLabel(label); mTimeZone = new TimeZoneCombo(widget); mTimeZone->setMaxVisibleItems(15); box->addWidget(mTimeZone); widget->setWhatsThis(xi18nc("@info:whatsthis", "Select the time zone which KAlarm should use " "as its default for displaying and entering dates and times.")); label->setBuddy(mTimeZone); itemBox->addStretch(); // Holiday region itemBox = new QHBoxLayout(); itemBox->setContentsMargins(0, 0, 0, 0); qobject_cast(topLayout())->addLayout(itemBox); widget = new QWidget; // this is to control the QWhatsThis text display area itemBox->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:listbox", "Holiday region:")); addAlignedLabel(label); box->addWidget(label); mHolidays = new QComboBox(); mHolidays->setSizeAdjustPolicy(QComboBox::AdjustToContentsOnFirstShow); box->addWidget(mHolidays); itemBox->addStretch(); label->setBuddy(mHolidays); widget->setWhatsThis(i18nc("@info:whatsthis", "Select which holiday region to use")); const QStringList regions = HolidayRegion::regionCodes(); QMap regionsMap; for (const QString& regionCode : regions) { const QString name = HolidayRegion::name(regionCode); const QString languageName = QLocale::languageToString(QLocale(HolidayRegion::languageCode(regionCode)).language()); const QString label = languageName.isEmpty() ? name : i18nc("Holiday region, region language", "%1 (%2)", name, languageName); regionsMap.insert(label, regionCode); } mHolidays->addItem(i18nc("No holiday region", "None"), QString()); for (QMapIterator it(regionsMap); it.hasNext(); ) { it.next(); mHolidays->addItem(it.key(), it.value()); } // Start-of-day time itemBox = new QHBoxLayout(); itemBox->setContentsMargins(0, 0, 0, 0); qobject_cast(topLayout())->addLayout(itemBox); widget = new QWidget; // this is to control the QWhatsThis text display area itemBox->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:spinbox", "Start of day for date-only alarms:")); addAlignedLabel(label); box->addWidget(label); mStartOfDay = new TimeEdit(); box->addWidget(mStartOfDay); label->setBuddy(mStartOfDay); widget->setWhatsThis(xi18nc("@info:whatsthis", "The earliest time of day at which a date-only alarm will be triggered." "%1", TimeSpinBox::shiftWhatsThis())); itemBox->addStretch(); // Working hours QGroupBox* group = new QGroupBox(i18nc("@title:group", "Working Hours")); topLayout()->addWidget(group); QBoxLayout* layout = new QVBoxLayout(group); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); layout->setContentsMargins(dcm, dcm, dcm, dcm); layout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QWidget* daybox = new QWidget(group); // this is to control the QWhatsThis text display area layout->addWidget(daybox); QGridLayout* wgrid = new QGridLayout(daybox); wgrid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); const QLocale locale; for (int i = 0; i < 7; ++i) { int day = Locale::localeDayInWeek_to_weekDay(i); mWorkDays[i] = new QCheckBox(locale.dayName(day), daybox); wgrid->addWidget(mWorkDays[i], i/3, i%3, Qt::AlignLeft); } daybox->setWhatsThis(i18nc("@info:whatsthis", "Check the days in the week which are work days")); itemBox = new QHBoxLayout(); itemBox->setContentsMargins(0, 0, 0, 0); layout->addLayout(itemBox); widget = new QWidget; // this is to control the QWhatsThis text display area itemBox->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:spinbox", "Daily start time:")); addAlignedLabel(label); box->addWidget(label); mWorkStart = new TimeEdit(); box->addWidget(mWorkStart); label->setBuddy(mWorkStart); widget->setWhatsThis(xi18nc("@info:whatsthis", "Enter the start time of the working day." "%1", TimeSpinBox::shiftWhatsThis())); itemBox->addStretch(); itemBox = new QHBoxLayout(); itemBox->setContentsMargins(0, 0, 0, 0); layout->addLayout(itemBox); widget = new QWidget; // this is to control the QWhatsThis text display area itemBox->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:spinbox", "Daily end time:")); addAlignedLabel(label); box->addWidget(label); mWorkEnd = new TimeEdit(); box->addWidget(mWorkEnd); label->setBuddy(mWorkEnd); widget->setWhatsThis(xi18nc("@info:whatsthis", "Enter the end time of the working day." "%1", TimeSpinBox::shiftWhatsThis())); itemBox->addStretch(); // KOrganizer event duration group = new QGroupBox(i18nc("@title:group", "KOrganizer")); topLayout()->addWidget(group); layout = new QVBoxLayout(group); layout->setContentsMargins(dcm, dcm, dcm, dcm); layout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); widget = new QWidget; // this is to control the QWhatsThis text display area layout->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:spinbox", "KOrganizer event duration:")); addAlignedLabel(label); box->addWidget(label); mKOrgEventDuration = new TimeSpinBox(0, 5999); mKOrgEventDuration->setMinimumSize(mKOrgEventDuration->sizeHint()); box->addWidget(mKOrgEventDuration); widget->setWhatsThis(xi18nc("@info:whatsthis", "Enter the event duration in hours and minutes, for alarms which are copied to KOrganizer." "%1", TimeSpinBox::shiftWhatsThis())); label->setBuddy(mKOrgEventDuration); box->addStretch(); // left adjust the controls topLayout()->addStretch(); // top adjust the widgets } void TimePrefTab::restore(bool, bool) { KADateTime::Spec timeSpec = Preferences::timeSpec(); mTimeZone->setTimeZone(timeSpec.type() == KADateTime::TimeZone ? timeSpec.timeZone() : QTimeZone()); const int i = Preferences::holidays().isValid() ? mHolidays->findData(Preferences::holidays().regionCode()) : 0; mHolidays->setCurrentIndex(i); mStartOfDay->setValue(Preferences::startOfDay()); mWorkStart->setValue(Preferences::workDayStart()); mWorkEnd->setValue(Preferences::workDayEnd()); QBitArray days = Preferences::workDays(); for (int i = 0; i < 7; ++i) { const bool x = days.testBit(Locale::localeDayInWeek_to_weekDay(i) - 1); mWorkDays[i]->setChecked(x); } mKOrgEventDuration->setValue(Preferences::kOrgEventDuration()); } void TimePrefTab::apply(bool syncToDisc) { Preferences::setTimeSpec(mTimeZone->timeZone()); const QString hol = mHolidays->itemData(mHolidays->currentIndex()).toString(); if (hol != Preferences::holidays().regionCode()) Preferences::setHolidayRegion(hol); int t = mStartOfDay->value(); const QTime sodt(t/60, t%60, 0); if (sodt != Preferences::startOfDay()) Preferences::setStartOfDay(sodt); t = mWorkStart->value(); Preferences::setWorkDayStart(QTime(t/60, t%60, 0)); t = mWorkEnd->value(); Preferences::setWorkDayEnd(QTime(t/60, t%60, 0)); QBitArray workDays(7); for (int i = 0; i < 7; ++i) if (mWorkDays[i]->isChecked()) workDays.setBit(Locale::localeDayInWeek_to_weekDay(i) - 1, 1); Preferences::setWorkDays(workDays); Preferences::setKOrgEventDuration(mKOrgEventDuration->value()); t = mKOrgEventDuration->value(); if (t != Preferences::kOrgEventDuration()) Preferences::setKOrgEventDuration(t); PrefsTabBase::apply(syncToDisc); } /*============================================================================= = Class StorePrefTab =============================================================================*/ StorePrefTab::StorePrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { // Which resource to save to QGroupBox* group = new QGroupBox(i18nc("@title:group", "New Alarms && Templates")); topLayout()->addWidget(group); QButtonGroup* bgroup = new QButtonGroup(group); QBoxLayout* layout = new QVBoxLayout(group); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); layout->setContentsMargins(dcm, dcm, dcm, dcm); layout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mDefaultResource = new QRadioButton(i18nc("@option:radio", "Store in default calendar"), group); bgroup->addButton(mDefaultResource); mDefaultResource->setWhatsThis(i18nc("@info:whatsthis", "Add all new alarms and alarm templates to the default calendars, without prompting.")); layout->addWidget(mDefaultResource, 0, Qt::AlignLeft); mAskResource = new QRadioButton(i18nc("@option:radio", "Prompt for which calendar to store in"), group); bgroup->addButton(mAskResource); mAskResource->setWhatsThis(xi18nc("@info:whatsthis", "When saving a new alarm or alarm template, prompt for which calendar to store it in, if there is more than one active calendar." "Note that archived alarms are always stored in the default archived alarm calendar.")); layout->addWidget(mAskResource, 0, Qt::AlignLeft); // Archived alarms group = new QGroupBox(i18nc("@title:group", "Archived Alarms")); topLayout()->addWidget(group); QGridLayout* grid = new QGridLayout(group); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setColumnStretch(1, 1); grid->setColumnMinimumWidth(0, indentWidth()); mKeepArchived = new QCheckBox(i18nc("@option:check", "Keep alarms after expiry"), group); connect(mKeepArchived, &QAbstractButton::toggled, this, &StorePrefTab::slotArchivedToggled); mKeepArchived->setWhatsThis( i18nc("@info:whatsthis", "Check to archive alarms after expiry or deletion (except deleted alarms which were never triggered).")); grid->addWidget(mKeepArchived, 0, 0, 1, 2, Qt::AlignLeft); QWidget* widget = new QWidget; QHBoxLayout* box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mPurgeArchived = new QCheckBox(i18nc("@option:check", "Discard archived alarms after:")); mPurgeArchived->setMinimumSize(mPurgeArchived->sizeHint()); box->addWidget(mPurgeArchived); connect(mPurgeArchived, &QAbstractButton::toggled, this, &StorePrefTab::slotArchivedToggled); mPurgeAfter = new SpinBox(); mPurgeAfter->setMinimum(1); mPurgeAfter->setSingleShiftStep(10); mPurgeAfter->setMinimumSize(mPurgeAfter->sizeHint()); box->addWidget(mPurgeAfter); mPurgeAfterLabel = new QLabel(i18nc("@label Time unit for user-entered number", "days")); mPurgeAfterLabel->setMinimumSize(mPurgeAfterLabel->sizeHint()); mPurgeAfterLabel->setBuddy(mPurgeAfter); box->addWidget(mPurgeAfterLabel); widget->setWhatsThis(i18nc("@info:whatsthis", "Uncheck to store archived alarms indefinitely. Check to enter how long archived alarms should be stored.")); grid->addWidget(widget, 1, 1, Qt::AlignLeft); mClearArchived = new QPushButton(i18nc("@action:button", "Clear Archived Alarms"), group); connect(mClearArchived, &QAbstractButton::clicked, this, &StorePrefTab::slotClearArchived); mClearArchived->setWhatsThis((Resources::enabledResources(CalEvent::ARCHIVED, false).count() <= 1) ? i18nc("@info:whatsthis", "Delete all existing archived alarms.") : i18nc("@info:whatsthis", "Delete all existing archived alarms (from the default archived alarm calendar only).")); grid->addWidget(mClearArchived, 2, 1, Qt::AlignLeft); topLayout()->addStretch(); // top adjust the widgets } void StorePrefTab::restore(bool defaults, bool) { mCheckKeepChanges = defaults; if (Preferences::askResource()) mAskResource->setChecked(true); else mDefaultResource->setChecked(true); const int keepDays = Preferences::archivedKeepDays(); if (!defaults) mOldKeepArchived = keepDays; setArchivedControls(keepDays); mCheckKeepChanges = true; } void StorePrefTab::apply(bool syncToDisc) { const bool b = mAskResource->isChecked(); if (b != Preferences::askResource()) Preferences::setAskResource(mAskResource->isChecked()); const int days = !mKeepArchived->isChecked() ? 0 : mPurgeArchived->isChecked() ? mPurgeAfter->value() : -1; if (days != Preferences::archivedKeepDays()) Preferences::setArchivedKeepDays(days); PrefsTabBase::apply(syncToDisc); } void StorePrefTab::setArchivedControls(int purgeDays) { mKeepArchived->setChecked(purgeDays); mPurgeArchived->setChecked(purgeDays > 0); mPurgeAfter->setValue(purgeDays > 0 ? purgeDays : 0); slotArchivedToggled(true); } void StorePrefTab::slotArchivedToggled(bool) { const bool keep = mKeepArchived->isChecked(); if (keep && !mOldKeepArchived && mCheckKeepChanges && !Resources::getStandard(CalEvent::ARCHIVED).isValid()) { KAMessageBox::sorry(topLayout()->parentWidget(), xi18nc("@info", "A default calendar is required in order to archive alarms, but none is currently enabled." "If you wish to keep expired alarms, please first use the calendars view to select a default " "archived alarms calendar.")); mKeepArchived->setChecked(false); return; } mOldKeepArchived = keep; mPurgeArchived->setEnabled(keep); mPurgeAfter->setEnabled(keep && mPurgeArchived->isChecked()); mPurgeAfterLabel->setEnabled(keep); mClearArchived->setEnabled(keep); } void StorePrefTab::slotClearArchived() { const bool single = Resources::enabledResources(CalEvent::ARCHIVED, false).count() <= 1; if (KAMessageBox::warningContinueCancel(topLayout()->parentWidget(), single ? i18nc("@info", "Do you really want to delete all archived alarms?") : i18nc("@info", "Do you really want to delete all alarms in the default archived alarm calendar?")) != KMessageBox::Continue) return; theApp()->purgeAll(); } /*============================================================================= = Class EmailPrefTab =============================================================================*/ EmailPrefTab::EmailPrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { QWidget* widget = new QWidget; topLayout()->addWidget(widget); QHBoxLayout* box = new QHBoxLayout(widget); box->setSpacing(2 * style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QLabel* label = new QLabel(i18nc("@label", "Email client:")); box->addWidget(label); mEmailClient = new ButtonGroup(widget); const QString kmailOption = i18nc("@option:radio", "KMail"); const QString sendmailOption = i18nc("@option:radio", "Sendmail"); mKMailButton = new RadioButton(kmailOption); mKMailButton->setMinimumSize(mKMailButton->sizeHint()); box->addWidget(mKMailButton); mEmailClient->addButton(mKMailButton, Preferences::kmail); mSendmailButton = new RadioButton(sendmailOption); mSendmailButton->setMinimumSize(mSendmailButton->sizeHint()); box->addWidget(mSendmailButton); mEmailClient->addButton(mSendmailButton, Preferences::sendmail); connect(mEmailClient, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotEmailClientChanged); widget->setWhatsThis(xi18nc("@info:whatsthis", "Choose how to send email when an email alarm is triggered." "%1: The email is sent automatically via KMail. KMail is started first if necessary." "%2: The email is sent automatically. This option will only work if " "your system is configured to use sendmail or a sendmail compatible mail transport agent.", kmailOption, sendmailOption)); widget = new QWidget; // this is to allow left adjustment topLayout()->addWidget(widget); box = new QHBoxLayout(widget); mEmailCopyToKMail = new QCheckBox(xi18nc("@option:check", "Copy sent emails into KMail's %1 folder", KAMail::i18n_sent_mail())); mEmailCopyToKMail->setWhatsThis(xi18nc("@info:whatsthis", "After sending an email, store a copy in KMail's %1 folder", KAMail::i18n_sent_mail())); box->addWidget(mEmailCopyToKMail); box->setStretchFactor(new QWidget(widget), 1); // left adjust the controls widget = new QWidget; // this is to allow left adjustment topLayout()->addWidget(widget); box = new QHBoxLayout(widget); mEmailQueuedNotify = new QCheckBox(i18nc("@option:check", "Notify when remote emails are queued")); mEmailQueuedNotify->setWhatsThis( i18nc("@info:whatsthis", "Display a notification message whenever an email alarm has queued an email for sending to a remote system. " "This could be useful if, for example, you have a dial-up connection, so that you can then ensure that the email is actually transmitted.")); box->addWidget(mEmailQueuedNotify); box->setStretchFactor(new QWidget(widget), 1); // left adjust the controls // Your Email Address group box QGroupBox* group = new QGroupBox(i18nc("@title:group", "Your Email Address")); topLayout()->addWidget(group); QGridLayout* grid = new QGridLayout(group); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setColumnStretch(2, 1); // 'From' email address controls ... label = new Label(i18nc("@label 'From' email address", "From:"), group); grid->addWidget(label, 1, 0); mFromAddressGroup = new ButtonGroup(group); connect(mFromAddressGroup, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotFromAddrChanged); // Line edit to enter a 'From' email address mFromAddrButton = new RadioButton(group); mFromAddressGroup->addButton(mFromAddrButton, Preferences::MAIL_FROM_ADDR); label->setBuddy(mFromAddrButton); grid->addWidget(mFromAddrButton, 1, 1); mEmailAddress = new QLineEdit(group); connect(mEmailAddress, &QLineEdit::textChanged, this, &EmailPrefTab::slotAddressChanged); QString whatsThis = i18nc("@info:whatsthis", "Your email address, used to identify you as the sender when sending email alarms."); mFromAddrButton->setWhatsThis(whatsThis); mEmailAddress->setWhatsThis(whatsThis); mFromAddrButton->setFocusWidget(mEmailAddress); grid->addWidget(mEmailAddress, 1, 2); // 'From' email address to be taken from System Settings mFromCCentreButton = new RadioButton(xi18nc("@option:radio", "Use default address from KMail or System Settings"), group); mFromAddressGroup->addButton(mFromCCentreButton, Preferences::MAIL_FROM_SYS_SETTINGS); mFromCCentreButton->setWhatsThis( xi18nc("@info:whatsthis", "Check to use the default email address set in KMail or KDE System Settings, to identify you as the sender when sending email alarms.")); grid->addWidget(mFromCCentreButton, 2, 1, 1, 2, Qt::AlignLeft); // 'From' email address to be picked from KMail's identities when the email alarm is configured mFromKMailButton = new RadioButton(xi18nc("@option:radio", "Use KMail identities"), group); mFromAddressGroup->addButton(mFromKMailButton, Preferences::MAIL_FROM_KMAIL); mFromKMailButton->setWhatsThis( xi18nc("@info:whatsthis", "Check to use KMail's email identities to identify you as the sender when sending email alarms. " "For existing email alarms, KMail's default identity will be used. " "For new email alarms, you will be able to pick which of KMail's identities to use.")); grid->addWidget(mFromKMailButton, 3, 1, 1, 2, Qt::AlignLeft); // 'Bcc' email address controls ... grid->setRowMinimumHeight(4, style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new Label(i18nc("@label 'Bcc' email address", "Bcc:"), group); grid->addWidget(label, 5, 0); mBccAddressGroup = new ButtonGroup(group); connect(mBccAddressGroup, &ButtonGroup::buttonSet, this, &EmailPrefTab::slotBccAddrChanged); // Line edit to enter a 'Bcc' email address mBccAddrButton = new RadioButton(group); mBccAddressGroup->addButton(mBccAddrButton, Preferences::MAIL_FROM_ADDR); label->setBuddy(mBccAddrButton); grid->addWidget(mBccAddrButton, 5, 1); mEmailBccAddress = new QLineEdit(group); whatsThis = xi18nc("@info:whatsthis", "Your email address, used for blind copying email alarms to yourself. " "If you want blind copies to be sent to your account on the computer which KAlarm runs on, you can simply enter your user login name."); mBccAddrButton->setWhatsThis(whatsThis); mEmailBccAddress->setWhatsThis(whatsThis); mBccAddrButton->setFocusWidget(mEmailBccAddress); grid->addWidget(mEmailBccAddress, 5, 2); // 'Bcc' email address to be taken from System Settings mBccCCentreButton = new RadioButton(xi18nc("@option:radio", "Use default address from KMail or System Settings"), group); mBccAddressGroup->addButton(mBccCCentreButton, Preferences::MAIL_FROM_SYS_SETTINGS); mBccCCentreButton->setWhatsThis( xi18nc("@info:whatsthis", "Check to use the default email address set in KMail or KDE System Settings, for blind copying email alarms to yourself.")); grid->addWidget(mBccCCentreButton, 6, 1, 1, 2, Qt::AlignLeft); topLayout()->addStretch(); // top adjust the widgets } void EmailPrefTab::restore(bool defaults, bool) { mEmailClient->setButton(Preferences::emailClient()); mEmailCopyToKMail->setChecked(Preferences::emailCopyToKMail()); setEmailAddress(Preferences::emailFrom(), Preferences::emailAddress()); setEmailBccAddress((Preferences::emailBccFrom() == Preferences::MAIL_FROM_SYS_SETTINGS), Preferences::emailBccAddress()); mEmailQueuedNotify->setChecked(Preferences::emailQueuedNotify()); if (!defaults) mAddressChanged = mBccAddressChanged = false; } void EmailPrefTab::apply(bool syncToDisc) { const int client = mEmailClient->selectedId(); if (client >= 0 && static_cast(client) != Preferences::emailClient()) Preferences::setEmailClient(static_cast(client)); bool b = mEmailCopyToKMail->isChecked(); if (b != Preferences::emailCopyToKMail()) Preferences::setEmailCopyToKMail(b); int from = mFromAddressGroup->selectedId(); QString text = mEmailAddress->text().trimmed(); if ((from >= 0 && static_cast(from) != Preferences::emailFrom()) || text != Preferences::emailAddress()) Preferences::setEmailAddress(static_cast(from), text); b = (mBccAddressGroup->checkedButton() == mBccCCentreButton); Preferences::MailFrom bfrom = b ? Preferences::MAIL_FROM_SYS_SETTINGS : Preferences::MAIL_FROM_ADDR;; text = mEmailBccAddress->text().trimmed(); if (bfrom != Preferences::emailBccFrom() || text != Preferences::emailBccAddress()) Preferences::setEmailBccAddress(b, text); b = mEmailQueuedNotify->isChecked(); if (b != Preferences::emailQueuedNotify()) Preferences::setEmailQueuedNotify(mEmailQueuedNotify->isChecked()); PrefsTabBase::apply(syncToDisc); } void EmailPrefTab::setEmailAddress(Preferences::MailFrom from, const QString& address) { mFromAddressGroup->setButton(from); mEmailAddress->setText(from == Preferences::MAIL_FROM_ADDR ? address.trimmed() : QString()); } void EmailPrefTab::setEmailBccAddress(bool useSystemSettings, const QString& address) { mBccAddressGroup->setButton(useSystemSettings ? Preferences::MAIL_FROM_SYS_SETTINGS : Preferences::MAIL_FROM_ADDR); mEmailBccAddress->setText(useSystemSettings ? QString() : address.trimmed()); } void EmailPrefTab::slotEmailClientChanged(QAbstractButton* button) { mEmailCopyToKMail->setEnabled(button == mSendmailButton); } void EmailPrefTab::slotFromAddrChanged(QAbstractButton* button) { mEmailAddress->setEnabled(button == mFromAddrButton); mAddressChanged = true; } void EmailPrefTab::slotBccAddrChanged(QAbstractButton* button) { mEmailBccAddress->setEnabled(button == mBccAddrButton); mBccAddressChanged = true; } QString EmailPrefTab::validate() { if (mAddressChanged) { mAddressChanged = false; QString errmsg = validateAddr(mFromAddressGroup, mEmailAddress, KAMail::i18n_NeedFromEmailAddress()); if (!errmsg.isEmpty()) return errmsg; } if (mBccAddressChanged) { mBccAddressChanged = false; return validateAddr(mBccAddressGroup, mEmailBccAddress, i18nc("@info", "No valid 'Bcc' email address is specified.")); } return QString(); } QString EmailPrefTab::validateAddr(ButtonGroup* group, QLineEdit* addr, const QString& msg) { QString errmsg = xi18nc("@info", "%1Are you sure you want to save your changes?", msg); switch (group->selectedId()) { case Preferences::MAIL_FROM_SYS_SETTINGS: if (!KAMail::controlCentreAddress().isEmpty()) return QString(); errmsg = xi18nc("@info", "No default email address is currently set in KMail or KDE System Settings. %1", errmsg); break; case Preferences::MAIL_FROM_KMAIL: if (Identities::identitiesExist()) return QString(); errmsg = xi18nc("@info", "No KMail identities currently exist. %1", errmsg); break; case Preferences::MAIL_FROM_ADDR: if (!addr->text().trimmed().isEmpty()) return QString(); break; } return errmsg; } /*============================================================================= = Class EditPrefTab =============================================================================*/ EditPrefTab::EditPrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { KLocalizedString defsetting = kxi18nc("@info:whatsthis", "The default setting for %1 in the alarm edit dialog."); mTabs = new QTabWidget(); topLayout()->addWidget(mTabs); StackedGroupT* tabgroup = new StackedGroupT(mTabs); StackedWidgetT* topGeneral = new StackedWidgetT(tabgroup); QVBoxLayout* tgLayout = new QVBoxLayout(topGeneral); mTabGeneral = mTabs->addTab(topGeneral, i18nc("@title:tab", "General")); StackedWidgetT* topTypes = new StackedWidgetT(tabgroup); QVBoxLayout* ttLayout = new QVBoxLayout(topTypes); mTabTypes = mTabs->addTab(topTypes, i18nc("@title:tab", "Alarm Types")); StackedWidgetT* topFontColour = new StackedWidgetT(tabgroup); QVBoxLayout* tfLayout = new QVBoxLayout(topFontColour); mTabFontColour = mTabs->addTab(topFontColour, i18nc("@title:tab", "Font && Color")); // MISCELLANEOUS // Show in KOrganizer mCopyToKOrganizer = new QCheckBox(EditAlarmDlg::i18n_chk_ShowInKOrganizer()); mCopyToKOrganizer->setMinimumSize(mCopyToKOrganizer->sizeHint()); mCopyToKOrganizer->setWhatsThis(defsetting.subs(EditAlarmDlg::i18n_chk_ShowInKOrganizer()).toString()); tgLayout->addWidget(mCopyToKOrganizer); // Late cancellation QWidget* widget = new QWidget; tgLayout->addWidget(widget); QHBoxLayout* box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(0); mLateCancel = new QCheckBox(LateCancelSelector::i18n_chk_CancelIfLate()); mLateCancel->setMinimumSize(mLateCancel->sizeHint()); mLateCancel->setWhatsThis(defsetting.subs(LateCancelSelector::i18n_chk_CancelIfLate()).toString()); box->addWidget(mLateCancel); // Recurrence widget = new QWidget; // this is to control the QWhatsThis text display area tgLayout->addWidget(widget); box = new QHBoxLayout(widget); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); box->setContentsMargins(0, 0, 0, 0); QLabel* label = new QLabel(i18nc("@label:listbox", "Recurrence:")); box->addWidget(label); mRecurPeriod = new ComboBox(); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_NoRecur()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_AtLogin()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_HourlyMinutely()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Daily()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Weekly()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Monthly()); mRecurPeriod->addItem(RecurrenceEdit::i18n_combo_Yearly()); box->addWidget(mRecurPeriod); box->addStretch(); label->setBuddy(mRecurPeriod); widget->setWhatsThis(i18nc("@info:whatsthis", "The default setting for the recurrence rule in the alarm edit dialog.")); // How to handle February 29th in yearly recurrences QWidget* febBox = new QWidget; // this is to control the QWhatsThis text display area tgLayout->addWidget(febBox); QVBoxLayout* vbox = new QVBoxLayout(febBox); vbox->setContentsMargins(0, 0, 0, 0); vbox->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label", "In non-leap years, repeat yearly February 29th alarms on:")); label->setAlignment(Qt::AlignLeft); label->setWordWrap(true); vbox->addWidget(label); box = new QHBoxLayout(); vbox->addLayout(box); vbox->setContentsMargins(0, 0, 0, 0); box->setSpacing(2 * style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mFeb29 = new ButtonGroup(febBox); widget = new QWidget(); widget->setFixedWidth(3 * style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); box->addWidget(widget); QRadioButton* radio = new QRadioButton(i18nc("@option:radio", "February 2&8th")); radio->setMinimumSize(radio->sizeHint()); box->addWidget(radio); mFeb29->addButton(radio, Preferences::Feb29_Feb28); radio = new QRadioButton(i18nc("@option:radio", "March &1st")); radio->setMinimumSize(radio->sizeHint()); box->addWidget(radio); mFeb29->addButton(radio, Preferences::Feb29_Mar1); radio = new QRadioButton(i18nc("@option:radio", "Do not repeat")); radio->setMinimumSize(radio->sizeHint()); box->addWidget(radio); mFeb29->addButton(radio, Preferences::Feb29_None); febBox->setWhatsThis(xi18nc("@info:whatsthis", "For yearly recurrences, choose what date, if any, alarms due on February 29th should occur in non-leap years." "The next scheduled occurrence of existing alarms is not re-evaluated when you change this setting.")); tgLayout->addStretch(); // top adjust the widgets // DISPLAY ALARMS QGroupBox* group = new QGroupBox(i18nc("@title:group", "Display Alarms")); ttLayout->addWidget(group); QVBoxLayout* vlayout = new QVBoxLayout(group); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mConfirmAck = new QCheckBox(EditDisplayAlarmDlg::i18n_chk_ConfirmAck()); mConfirmAck->setMinimumSize(mConfirmAck->sizeHint()); mConfirmAck->setWhatsThis(defsetting.subs(EditDisplayAlarmDlg::i18n_chk_ConfirmAck()).toString()); vlayout->addWidget(mConfirmAck, 0, Qt::AlignLeft); mAutoClose = new QCheckBox(LateCancelSelector::i18n_chk_AutoCloseWinLC()); mAutoClose->setMinimumSize(mAutoClose->sizeHint()); mAutoClose->setWhatsThis(defsetting.subs(LateCancelSelector::i18n_chk_AutoCloseWin()).toString()); vlayout->addWidget(mAutoClose, 0, Qt::AlignLeft); widget = new QWidget; vlayout->addWidget(widget); box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); label = new QLabel(i18nc("@label:listbox", "Reminder units:")); box->addWidget(label); mReminderUnits = new QComboBox(); mReminderUnits->addItem(i18nc("@item:inlistbox", "Minutes"), TimePeriod::Minutes); mReminderUnits->addItem(i18nc("@item:inlistbox", "Hours/Minutes"), TimePeriod::HoursMinutes); box->addWidget(mReminderUnits); label->setBuddy(mReminderUnits); widget->setWhatsThis(i18nc("@info:whatsthis", "The default units for the reminder in the alarm edit dialog, for alarms due soon.")); box->addStretch(); // left adjust the control mSpecialActionsButton = new SpecialActionsButton(true); box->addWidget(mSpecialActionsButton); // SOUND QGroupBox* bbox = new QGroupBox(i18nc("@title:group Audio options group", "Sound")); ttLayout->addWidget(bbox); vlayout = new QVBoxLayout(bbox); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); QHBoxLayout* hlayout = new QHBoxLayout; hlayout->setContentsMargins(0, 0, 0, 0); vlayout->addLayout(hlayout); mSound = new QComboBox(); mSound->addItem(SoundPicker::i18n_combo_None()); // index 0 mSound->addItem(SoundPicker::i18n_combo_Beep()); // index 1 mSound->addItem(SoundPicker::i18n_combo_File()); // index 2 if (KPIMTextEdit::TextToSpeech::self()->isReady()) mSound->addItem(SoundPicker::i18n_combo_Speak()); // index 3 mSound->setMinimumSize(mSound->sizeHint()); mSound->setWhatsThis(defsetting.subs(SoundPicker::i18n_label_Sound()).toString()); hlayout->addWidget(mSound); hlayout->addStretch(); mSoundRepeat = new QCheckBox(i18nc("@option:check", "Repeat sound file")); mSoundRepeat->setMinimumSize(mSoundRepeat->sizeHint()); mSoundRepeat->setWhatsThis( xi18nc("@info:whatsthis sound file 'Repeat' checkbox", "The default setting for sound file %1 in the alarm edit dialog.", SoundWidget::i18n_chk_Repeat())); hlayout->addWidget(mSoundRepeat); widget = new QWidget; // this is to control the QWhatsThis text display area box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mSoundFileLabel = new QLabel(i18nc("@label:textbox", "Sound file:")); box->addWidget(mSoundFileLabel); mSoundFile = new QLineEdit(); box->addWidget(mSoundFile); mSoundFileLabel->setBuddy(mSoundFile); mSoundFileBrowse = new QPushButton(); mSoundFileBrowse->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); connect(mSoundFileBrowse, &QAbstractButton::clicked, this, &EditPrefTab::slotBrowseSoundFile); mSoundFileBrowse->setToolTip(i18nc("@info:tooltip", "Choose a sound file")); box->addWidget(mSoundFileBrowse); widget->setWhatsThis(i18nc("@info:whatsthis", "Enter the default sound file to use in the alarm edit dialog.")); vlayout->addWidget(widget); // COMMAND ALARMS group = new QGroupBox(i18nc("@title:group", "Command Alarms")); ttLayout->addWidget(group); vlayout = new QVBoxLayout(group); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); hlayout = new QHBoxLayout(); hlayout->setContentsMargins(0, 0, 0, 0); vlayout->addLayout(hlayout); mCmdScript = new QCheckBox(EditCommandAlarmDlg::i18n_chk_EnterScript(), group); mCmdScript->setMinimumSize(mCmdScript->sizeHint()); mCmdScript->setWhatsThis(defsetting.subs(EditCommandAlarmDlg::i18n_chk_EnterScript()).toString()); hlayout->addWidget(mCmdScript); hlayout->addStretch(); mCmdXterm = new QCheckBox(EditCommandAlarmDlg::i18n_chk_ExecInTermWindow(), group); mCmdXterm->setMinimumSize(mCmdXterm->sizeHint()); mCmdXterm->setWhatsThis(defsetting.subs(EditCommandAlarmDlg::i18n_radio_ExecInTermWindow()).toString()); hlayout->addWidget(mCmdXterm); // EMAIL ALARMS group = new QGroupBox(i18nc("@title:group", "Email Alarms")); ttLayout->addWidget(group); vlayout = new QVBoxLayout(group); vlayout->setContentsMargins(dcm, dcm, dcm, dcm); vlayout->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); // BCC email to sender mEmailBcc = new QCheckBox(EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf(), group); mEmailBcc->setMinimumSize(mEmailBcc->sizeHint()); mEmailBcc->setWhatsThis(defsetting.subs(EditEmailAlarmDlg::i18n_chk_CopyEmailToSelf()).toString()); vlayout->addWidget(mEmailBcc, 0, Qt::AlignLeft); ttLayout->addStretch(); // FONT / COLOUR TAB mFontChooser = new FontColourChooser(topFontColour, QStringList(), i18nc("@title:group", "Message Font && Color"), true); tfLayout->addWidget(mFontChooser); } void EditPrefTab::restore(bool, bool allTabs) { int index; if (allTabs || mTabs->currentIndex() == mTabGeneral) { mCopyToKOrganizer->setChecked(Preferences::defaultCopyToKOrganizer()); mLateCancel->setChecked(Preferences::defaultLateCancel()); switch (Preferences::defaultRecurPeriod()) { case Preferences::Recur_Yearly: index = 6; break; case Preferences::Recur_Monthly: index = 5; break; case Preferences::Recur_Weekly: index = 4; break; case Preferences::Recur_Daily: index = 3; break; case Preferences::Recur_SubDaily: index = 2; break; case Preferences::Recur_Login: index = 1; break; case Preferences::Recur_None: default: index = 0; break; } mRecurPeriod->setCurrentIndex(index); mFeb29->setButton(Preferences::defaultFeb29Type()); } if (allTabs || mTabs->currentIndex() == mTabTypes) { mConfirmAck->setChecked(Preferences::defaultConfirmAck()); mAutoClose->setChecked(Preferences::defaultAutoClose()); switch (Preferences::defaultReminderUnits()) { case TimePeriod::Weeks: index = 3; break; case TimePeriod::Days: index = 2; break; default: case TimePeriod::HoursMinutes: index = 1; break; case TimePeriod::Minutes: index = 0; break; } mReminderUnits->setCurrentIndex(index); KAEvent::ExtraActionOptions opts{}; if (Preferences::defaultExecPreActionOnDeferral()) opts |= KAEvent::ExecPreActOnDeferral; if (Preferences::defaultCancelOnPreActionError()) opts |= KAEvent::CancelOnPreActError; if (Preferences::defaultDontShowPreActionError()) opts |= KAEvent::DontShowPreActError; mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction(), opts); mSound->setCurrentIndex(soundIndex(Preferences::defaultSoundType())); mSoundFile->setText(Preferences::defaultSoundFile()); mSoundRepeat->setChecked(Preferences::defaultSoundRepeat()); mCmdScript->setChecked(Preferences::defaultCmdScript()); mCmdXterm->setChecked(Preferences::defaultCmdLogType() == Preferences::Log_Terminal); mEmailBcc->setChecked(Preferences::defaultEmailBcc()); } if (allTabs || mTabs->currentIndex() == mTabFontColour) { mFontChooser->setFgColour(Preferences::defaultFgColour()); mFontChooser->setBgColour(Preferences::defaultBgColour()); mFontChooser->setFont(Preferences::messageFont()); } } void EditPrefTab::apply(bool syncToDisc) { bool b = mAutoClose->isChecked(); if (b != Preferences::defaultAutoClose()) Preferences::setDefaultAutoClose(b); b = mConfirmAck->isChecked(); if (b != Preferences::defaultConfirmAck()) Preferences::setDefaultConfirmAck(b); TimePeriod::Units units; switch (mReminderUnits->currentIndex()) { case 3: units = TimePeriod::Weeks; break; case 2: units = TimePeriod::Days; break; default: case 1: units = TimePeriod::HoursMinutes; break; case 0: units = TimePeriod::Minutes; break; } if (units != Preferences::defaultReminderUnits()) Preferences::setDefaultReminderUnits(units); QString text = mSpecialActionsButton->preAction(); if (text != Preferences::defaultPreAction()) Preferences::setDefaultPreAction(text); text = mSpecialActionsButton->postAction(); if (text != Preferences::defaultPostAction()) Preferences::setDefaultPostAction(text); KAEvent::ExtraActionOptions opts = mSpecialActionsButton->options(); b = opts & KAEvent::ExecPreActOnDeferral; if (b != Preferences::defaultExecPreActionOnDeferral()) Preferences::setDefaultExecPreActionOnDeferral(b); b = opts & KAEvent::CancelOnPreActError; if (b != Preferences::defaultCancelOnPreActionError()) Preferences::setDefaultCancelOnPreActionError(b); b = opts & KAEvent::DontShowPreActError; if (b != Preferences::defaultDontShowPreActionError()) Preferences::setDefaultDontShowPreActionError(b); Preferences::SoundType snd; switch (mSound->currentIndex()) { case 3: snd = Preferences::Sound_Speak; break; case 2: snd = Preferences::Sound_File; break; case 1: snd = Preferences::Sound_Beep; break; case 0: default: snd = Preferences::Sound_None; break; } if (snd != Preferences::defaultSoundType()) Preferences::setDefaultSoundType(snd); text = mSoundFile->text(); if (text != Preferences::defaultSoundFile()) Preferences::setDefaultSoundFile(text); b = mSoundRepeat->isChecked(); if (b != Preferences::defaultSoundRepeat()) Preferences::setDefaultSoundRepeat(b); b = mCmdScript->isChecked(); if (b != Preferences::defaultCmdScript()) Preferences::setDefaultCmdScript(b); Preferences::CmdLogType log = mCmdXterm->isChecked() ? Preferences::Log_Terminal : Preferences::Log_Discard; if (log != Preferences::defaultCmdLogType()) Preferences::setDefaultCmdLogType(log); b = mEmailBcc->isChecked(); if (b != Preferences::defaultEmailBcc()) Preferences::setDefaultEmailBcc(b); b = mCopyToKOrganizer->isChecked(); if (b != Preferences::defaultCopyToKOrganizer()) Preferences::setDefaultCopyToKOrganizer(b); const int i = mLateCancel->isChecked() ? 1 : 0; if (i != Preferences::defaultLateCancel()) Preferences::setDefaultLateCancel(i); Preferences::RecurType period; switch (mRecurPeriod->currentIndex()) { case 6: period = Preferences::Recur_Yearly; break; case 5: period = Preferences::Recur_Monthly; break; case 4: period = Preferences::Recur_Weekly; break; case 3: period = Preferences::Recur_Daily; break; case 2: period = Preferences::Recur_SubDaily; break; case 1: period = Preferences::Recur_Login; break; case 0: default: period = Preferences::Recur_None; break; } if (period != Preferences::defaultRecurPeriod()) Preferences::setDefaultRecurPeriod(period); const int feb29 = mFeb29->selectedId(); if (feb29 >= 0 && static_cast(feb29) != Preferences::defaultFeb29Type()) Preferences::setDefaultFeb29Type(static_cast(feb29)); QColor colour = mFontChooser->fgColour(); if (colour != Preferences::defaultFgColour()) Preferences::setDefaultFgColour(colour); colour = mFontChooser->bgColour(); if (colour != Preferences::defaultBgColour()) Preferences::setDefaultBgColour(colour); const QFont font = mFontChooser->font(); if (font != Preferences::messageFont()) Preferences::setMessageFont(font); PrefsTabBase::apply(syncToDisc); } void EditPrefTab::slotBrowseSoundFile() { QString defaultDir; QString file; if (SoundPicker::browseFile(file, defaultDir, mSoundFile->text())) { if (!file.isEmpty()) mSoundFile->setText(file); } } int EditPrefTab::soundIndex(Preferences::SoundType type) { switch (type) { case Preferences::Sound_Speak: return 3; case Preferences::Sound_File: return 2; case Preferences::Sound_Beep: return 1; case Preferences::Sound_None: default: return 0; } } QString EditPrefTab::validate() { if (mSound->currentIndex() == soundIndex(Preferences::Sound_File) && mSoundFile->text().isEmpty()) { mSoundFile->setFocus(); return xi18nc("@info", "You must enter a sound file when %1 is selected as the default sound type", SoundPicker::i18n_combo_File());; } return QString(); } /*============================================================================= = Class ViewPrefTab =============================================================================*/ ViewPrefTab::ViewPrefTab(StackedScrollGroup* scrollGroup) : PrefsTabBase(scrollGroup) { mTabs = new QTabWidget(); topLayout()->addWidget(mTabs); QWidget* widget = new QWidget; QVBoxLayout* topGeneral = new QVBoxLayout(widget); const int dcm = style()->pixelMetric(QStyle::PM_DefaultChildMargin); const int m = dcm / 2; topGeneral->setContentsMargins(m, m, m, m); topGeneral->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mTabGeneral = mTabs->addTab(widget, i18nc("@title:tab", "General")); widget = new QWidget; QVBoxLayout* topWindows = new QVBoxLayout(widget); topWindows->setContentsMargins(m, m, m, m); topWindows->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mTabWindows = mTabs->addTab(widget, i18nc("@title:tab", "Alarm Windows")); // Run-in-system-tray check box or group. static const QString showInSysTrayText = i18nc("@option:check", "Show in system tray"); static const QString showInSysTrayWhatsThis = xi18nc("@info:whatsthis", "Check to show KAlarm's icon in the system tray." " Showing it in the system tray provides easy access and a status indication."); if (Preferences::noAutoHideSystemTrayDesktops().contains(Desktop::currentIdentityName())) { // Run-in-system-tray check box. // This desktop type doesn't provide GUI controls to view hidden system tray // icons, so don't show options to hide the system tray icon. widget = new QWidget; // this is to allow left adjustment topGeneral->addWidget(widget); QHBoxLayout* box = new QHBoxLayout(widget); mShowInSystemTrayCheck = new QCheckBox(showInSysTrayText); mShowInSystemTrayCheck->setWhatsThis(showInSysTrayWhatsThis); box->addWidget(mShowInSystemTrayCheck); box->setStretchFactor(new QWidget(widget), 1); // left adjust the controls } else { // Run-in-system-tray group box mShowInSystemTrayGroup = new QGroupBox(showInSysTrayText); mShowInSystemTrayGroup->setCheckable(true); mShowInSystemTrayGroup->setWhatsThis(showInSysTrayWhatsThis); topGeneral->addWidget(mShowInSystemTrayGroup); QGridLayout* grid = new QGridLayout(mShowInSystemTrayGroup); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setColumnStretch(1, 1); grid->setColumnMinimumWidth(0, indentWidth()); mAutoHideSystemTray = new ButtonGroup(mShowInSystemTrayGroup); connect(mAutoHideSystemTray, &ButtonGroup::buttonSet, this, &ViewPrefTab::slotAutoHideSysTrayChanged); QRadioButton* radio = new QRadioButton(i18nc("@option:radio Always show KAlarm icon", "Always show"), mShowInSystemTrayGroup); mAutoHideSystemTray->addButton(radio, 0); radio->setWhatsThis( xi18nc("@info:whatsthis", "Check to show KAlarm's icon in the system tray " "regardless of whether alarms are due.")); grid->addWidget(radio, 0, 0, 1, 2, Qt::AlignLeft); radio = new QRadioButton(i18nc("@option:radio", "Automatically hide if no active alarms"), mShowInSystemTrayGroup); mAutoHideSystemTray->addButton(radio, 1); radio->setWhatsThis( xi18nc("@info:whatsthis", "Check to automatically hide KAlarm's icon in " "the system tray if there are no active alarms. When hidden, the icon can " "always be made visible by use of the system tray option to show hidden icons.")); grid->addWidget(radio, 1, 0, 1, 2, Qt::AlignLeft); QString text = xi18nc("@info:whatsthis", "Check to automatically hide KAlarm's icon in the " "system tray if no alarms are due within the specified time period. When hidden, " "the icon can always be made visible by use of the system tray option to show hidden icons."); radio = new QRadioButton(i18nc("@option:radio", "Automatically hide if no alarm due within time period:"), mShowInSystemTrayGroup); radio->setWhatsThis(text); mAutoHideSystemTray->addButton(radio, 2); grid->addWidget(radio, 2, 0, 1, 2, Qt::AlignLeft); mAutoHideSystemTrayPeriod = new TimePeriod(true, mShowInSystemTrayGroup); mAutoHideSystemTrayPeriod->setWhatsThis(text); mAutoHideSystemTrayPeriod->setMaximumWidth(mAutoHideSystemTrayPeriod->sizeHint().width()); grid->addWidget(mAutoHideSystemTrayPeriod, 3, 1, 1, 1, Qt::AlignLeft); mShowInSystemTrayGroup->setMaximumHeight(mShowInSystemTrayGroup->sizeHint().height()); } // System tray tooltip group box QGroupBox* group = new QGroupBox(i18nc("@title:group", "System Tray Tooltip")); topGeneral->addWidget(group); QGridLayout* grid = new QGridLayout(group); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setColumnStretch(2, 1); grid->setColumnMinimumWidth(0, indentWidth()); grid->setColumnMinimumWidth(1, indentWidth()); mTooltipShowAlarms = new QCheckBox(i18nc("@option:check", "Show next &24 hours' alarms"), group); mTooltipShowAlarms->setMinimumSize(mTooltipShowAlarms->sizeHint()); connect(mTooltipShowAlarms, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipAlarmsToggled); mTooltipShowAlarms->setWhatsThis( i18nc("@info:whatsthis", "Specify whether to include in the system tray tooltip, a summary of alarms due in the next 24 hours.")); grid->addWidget(mTooltipShowAlarms, 0, 0, 1, 3, Qt::AlignLeft); widget = new QWidget; QHBoxLayout* box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mTooltipMaxAlarms = new QCheckBox(i18nc("@option:check", "Maximum number of alarms to show:")); mTooltipMaxAlarms->setMinimumSize(mTooltipMaxAlarms->sizeHint()); box->addWidget(mTooltipMaxAlarms); connect(mTooltipMaxAlarms, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipMaxToggled); mTooltipMaxAlarmCount = new SpinBox(1, 99); mTooltipMaxAlarmCount->setSingleShiftStep(5); mTooltipMaxAlarmCount->setMinimumSize(mTooltipMaxAlarmCount->sizeHint()); box->addWidget(mTooltipMaxAlarmCount); widget->setWhatsThis( i18nc("@info:whatsthis", "Uncheck to display all of the next 24 hours' alarms in the system tray tooltip. " "Check to enter an upper limit on the number to be displayed.")); grid->addWidget(widget, 1, 1, 1, 2, Qt::AlignLeft); mTooltipShowTime = new QCheckBox(i18nc("@option:check", "Show alarm time"), group); mTooltipShowTime->setMinimumSize(mTooltipShowTime->sizeHint()); connect(mTooltipShowTime, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipTimeToggled); mTooltipShowTime->setWhatsThis(i18nc("@info:whatsthis", "Specify whether to show in the system tray tooltip, the time at which each alarm is due.")); grid->addWidget(mTooltipShowTime, 2, 1, 1, 2, Qt::AlignLeft); mTooltipShowTimeTo = new QCheckBox(i18nc("@option:check", "Show time until alarm"), group); mTooltipShowTimeTo->setMinimumSize(mTooltipShowTimeTo->sizeHint()); connect(mTooltipShowTimeTo, &QAbstractButton::toggled, this, &ViewPrefTab::slotTooltipTimeToToggled); mTooltipShowTimeTo->setWhatsThis(i18nc("@info:whatsthis", "Specify whether to show in the system tray tooltip, how long until each alarm is due.")); grid->addWidget(mTooltipShowTimeTo, 3, 1, 1, 2, Qt::AlignLeft); widget = new QWidget; // this is to control the QWhatsThis text display area box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mTooltipTimeToPrefixLabel = new QLabel(i18nc("@label:textbox", "Prefix:")); box->addWidget(mTooltipTimeToPrefixLabel); mTooltipTimeToPrefix = new QLineEdit(); box->addWidget(mTooltipTimeToPrefix); mTooltipTimeToPrefixLabel->setBuddy(mTooltipTimeToPrefix); widget->setWhatsThis(i18nc("@info:whatsthis", "Enter the text to be displayed in front of the time until the alarm, in the system tray tooltip.")); grid->addWidget(widget, 4, 2, Qt::AlignLeft); group->setMaximumHeight(group->sizeHint().height()); group = new QGroupBox(i18nc("@title:group", "Alarm List")); topGeneral->addWidget(group); QHBoxLayout* hlayout = new QHBoxLayout(group); hlayout->setContentsMargins(dcm, dcm, dcm, dcm); QVBoxLayout* colourLayout = new QVBoxLayout(); colourLayout->setContentsMargins(0, 0, 0, 0); hlayout->addLayout(colourLayout); widget = new QWidget; // to group widgets for QWhatsThis text box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) / 2); colourLayout->addWidget(widget); QLabel* label1 = new QLabel(i18nc("@label:listbox", "Disabled alarm color:")); box->addWidget(label1); box->setStretchFactor(new QWidget(widget), 0); mDisabledColour = new ColourButton(); box->addWidget(mDisabledColour); label1->setBuddy(mDisabledColour); widget->setWhatsThis(i18nc("@info:whatsthis", "Choose the text color in the alarm list for disabled alarms.")); widget = new QWidget; // to group widgets for QWhatsThis text box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing) / 2); colourLayout->addWidget(widget); QLabel* label2 = new QLabel(i18nc("@label:listbox", "Archived alarm color:")); box->addWidget(label2); box->setStretchFactor(new QWidget(widget), 0); mArchivedColour = new ColourButton(); box->addWidget(mArchivedColour); label2->setBuddy(mArchivedColour); widget->setWhatsThis(i18nc("@info:whatsthis", "Choose the text color in the alarm list for archived alarms.")); hlayout->addStretch(); if (topGeneral) topGeneral->addStretch(); // top adjust the widgets group = new QGroupBox(i18nc("@title:group", "Alarm Message Windows")); topWindows->addWidget(group); grid = new QGridLayout(group); grid->setContentsMargins(dcm, dcm, dcm, dcm); grid->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); grid->setColumnStretch(1, 1); grid->setColumnMinimumWidth(0, indentWidth()); mWindowPosition = new ButtonGroup(group); connect(mWindowPosition, &ButtonGroup::buttonSet, this, &ViewPrefTab::slotWindowPosChanged); const QString whatsthis = xi18nc("@info:whatsthis", "Choose how to reduce the chance of alarm messages being accidentally acknowledged:" "Position alarm message windows as far as possible from the current mouse cursor location, or" "Position alarm message windows in the center of the screen, but disable buttons for a short time after the window is displayed."); QRadioButton* radio = new QRadioButton(i18nc("@option:radio", "Position windows far from mouse cursor"), group); mWindowPosition->addButton(radio, 0); radio->setWhatsThis(whatsthis); grid->addWidget(radio, 0, 0, 1, 2, Qt::AlignLeft); radio = new QRadioButton(i18nc("@option:radio", "Center windows, delay activating window buttons"), group); mWindowPosition->addButton(radio, 1); radio->setWhatsThis(whatsthis); grid->addWidget(radio, 1, 0, 1, 2, Qt::AlignLeft); widget = new QWidget; // this is to control the QWhatsThis text display area box = new QHBoxLayout(widget); box->setContentsMargins(0, 0, 0, 0); box->setSpacing(style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mWindowButtonDelayLabel = new QLabel(i18nc("@label:spinbox", "Button activation delay (seconds):")); box->addWidget(mWindowButtonDelayLabel); mWindowButtonDelay = new QSpinBox(); mWindowButtonDelay->setRange(1, 10); mWindowButtonDelayLabel->setBuddy(mWindowButtonDelay); box->addWidget(mWindowButtonDelay); widget->setWhatsThis(i18nc("@info:whatsthis", "Enter how long its buttons should remain disabled after the alarm message window is shown.")); box->setStretchFactor(new QWidget(widget), 1); // left adjust the controls grid->addWidget(widget, 2, 1, Qt::AlignLeft); grid->setRowMinimumHeight(3, style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing)); mModalMessages = new QCheckBox(i18nc("@option:check", "Message windows have a title bar and take keyboard focus"), group); mModalMessages->setMinimumSize(mModalMessages->sizeHint()); mModalMessages->setWhatsThis(xi18nc("@info:whatsthis", "Specify the characteristics of alarm message windows:" "If checked, the window is a normal window with a title bar, which grabs keyboard input when it is displayed." "If unchecked, the window does not interfere with your typing when " "it is displayed, but it has no title bar and cannot be moved or resized.")); grid->addWidget(mModalMessages, 4, 0, 1, 2, Qt::AlignLeft); if (topWindows) topWindows->addStretch(); // top adjust the widgets } void ViewPrefTab::restore(bool, bool allTabs) { if (allTabs || mTabs->currentIndex() == mTabGeneral) { if (mShowInSystemTrayGroup) mShowInSystemTrayGroup->setChecked(Preferences::showInSystemTray()); else mShowInSystemTrayCheck->setChecked(Preferences::showInSystemTray()); int id; const int mins = Preferences::autoHideSystemTray(); switch (mins) { case -1: id = 1; break; // hide if no active alarms case 0: id = 0; break; // never hide default: { id = 2; int days = 0; int secs = 0; if (mins % 1440) secs = mins * 60; else days = mins / 1440; const TimePeriod::Units units = secs ? TimePeriod::HoursMinutes : (days % 7) ? TimePeriod::Days : TimePeriod::Weeks; const Duration duration((secs ? secs : days), (secs ? Duration::Seconds : Duration::Days)); mAutoHideSystemTrayPeriod->setPeriod(duration, false, units); break; } } if (mAutoHideSystemTray) mAutoHideSystemTray->setButton(id); setTooltip(Preferences::tooltipAlarmCount(), Preferences::showTooltipAlarmTime(), Preferences::showTooltipTimeToAlarm(), Preferences::tooltipTimeToPrefix()); mDisabledColour->setColor(Preferences::disabledColour()); mArchivedColour->setColor(Preferences::archivedColour()); } if (allTabs || mTabs->currentIndex() == mTabWindows) { mWindowPosition->setButton(Preferences::messageButtonDelay() ? 1 : 0); mWindowButtonDelay->setValue(Preferences::messageButtonDelay()); mModalMessages->setChecked(Preferences::modalMessages()); } } void ViewPrefTab::apply(bool syncToDisc) { QColor colour = mDisabledColour->color(); if (colour != Preferences::disabledColour()) Preferences::setDisabledColour(colour); colour = mArchivedColour->color(); if (colour != Preferences::archivedColour()) Preferences::setArchivedColour(colour); int n = mTooltipShowAlarms->isChecked() ? -1 : 0; if (n && mTooltipMaxAlarms->isChecked()) n = mTooltipMaxAlarmCount->value(); if (n != Preferences::tooltipAlarmCount()) Preferences::setTooltipAlarmCount(n); bool b = mTooltipShowTime->isChecked(); if (b != Preferences::showTooltipAlarmTime()) Preferences::setShowTooltipAlarmTime(b); b = mTooltipShowTimeTo->isChecked(); if (b != Preferences::showTooltipTimeToAlarm()) Preferences::setShowTooltipTimeToAlarm(b); QString text = mTooltipTimeToPrefix->text(); if (text != Preferences::tooltipTimeToPrefix()) Preferences::setTooltipTimeToPrefix(text); b = mShowInSystemTrayGroup ? mShowInSystemTrayGroup->isChecked() : mShowInSystemTrayCheck->isChecked(); if (b != Preferences::showInSystemTray()) Preferences::setShowInSystemTray(b); if (b && mAutoHideSystemTray) { switch (mAutoHideSystemTray->selectedId()) { case 0: n = 0; break; // never hide case 1: n = -1; break; // hide if no active alarms case 2: // hide if no alarms due within period n = mAutoHideSystemTrayPeriod->period().asSeconds() / 60; break; } if (n != Preferences::autoHideSystemTray()) Preferences::setAutoHideSystemTray(n); } n = mWindowPosition->selectedId(); if (n) n = mWindowButtonDelay->value(); if (n != Preferences::messageButtonDelay()) Preferences::setMessageButtonDelay(n); b = mModalMessages->isChecked(); if (b != Preferences::modalMessages()) Preferences::setModalMessages(b); PrefsTabBase::apply(syncToDisc); } void ViewPrefTab::setTooltip(int maxAlarms, bool time, bool timeTo, const QString& prefix) { if (!timeTo) time = true; // ensure that at least one time option is ticked // Set the states of the controls without calling signal // handlers, since these could change the checkboxes' states. mTooltipShowAlarms->blockSignals(true); mTooltipShowTime->blockSignals(true); mTooltipShowTimeTo->blockSignals(true); mTooltipShowAlarms->setChecked(maxAlarms); mTooltipMaxAlarms->setChecked(maxAlarms > 0); mTooltipMaxAlarmCount->setValue(maxAlarms > 0 ? maxAlarms : 1); mTooltipShowTime->setChecked(time); mTooltipShowTimeTo->setChecked(timeTo); mTooltipTimeToPrefix->setText(prefix); mTooltipShowAlarms->blockSignals(false); mTooltipShowTime->blockSignals(false); mTooltipShowTimeTo->blockSignals(false); // Enable/disable controls according to their states slotTooltipTimeToToggled(timeTo); slotTooltipAlarmsToggled(maxAlarms); } void ViewPrefTab::slotTooltipAlarmsToggled(bool on) { mTooltipMaxAlarms->setEnabled(on); mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isChecked()); mTooltipShowTime->setEnabled(on); mTooltipShowTimeTo->setEnabled(on); on = on && mTooltipShowTimeTo->isChecked(); mTooltipTimeToPrefix->setEnabled(on); mTooltipTimeToPrefixLabel->setEnabled(on); } void ViewPrefTab::slotTooltipMaxToggled(bool on) { mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isEnabled()); } void ViewPrefTab::slotTooltipTimeToggled(bool on) { if (!on && !mTooltipShowTimeTo->isChecked()) mTooltipShowTimeTo->setChecked(true); } void ViewPrefTab::slotTooltipTimeToToggled(bool on) { if (!on && !mTooltipShowTime->isChecked()) mTooltipShowTime->setChecked(true); on = on && mTooltipShowTimeTo->isEnabled(); mTooltipTimeToPrefix->setEnabled(on); mTooltipTimeToPrefixLabel->setEnabled(on); } void ViewPrefTab::slotAutoHideSysTrayChanged(QAbstractButton* button) { if (mAutoHideSystemTray) mAutoHideSystemTrayPeriod->setEnabled(mAutoHideSystemTray->id(button) == 2); } void ViewPrefTab::slotWindowPosChanged(QAbstractButton* button) { const bool enable = mWindowPosition->id(button); mWindowButtonDelay->setEnabled(enable); mWindowButtonDelayLabel->setEnabled(enable); } #include "moc_prefdlg_p.cpp" #include "moc_prefdlg.cpp" // vim: et sw=4: