From 460c52653ab0dcca6f19a4f492ed2c5e4e963ab0 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdepim@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kalarm/ACKNOWLEDGEMENTS | 26 + kalarm/AUTHORS | 11 + kalarm/BUGS | 17 + kalarm/COPYING | 341 +++ kalarm/Changelog | 657 ++++ kalarm/INSTALL | 40 + kalarm/Makefile.am | 76 + kalarm/README | 60 + kalarm/alarmcalendar.cpp | 908 ++++++ kalarm/alarmcalendar.h | 114 + kalarm/alarmevent.cpp | 3488 ++++++++++++++++++++++ kalarm/alarmevent.h | 500 ++++ kalarm/alarmlistview.cpp | 711 +++++ kalarm/alarmlistview.h | 133 + kalarm/alarmtext.cpp | 288 ++ kalarm/alarmtext.h | 75 + kalarm/alarmtimewidget.cpp | 556 ++++ kalarm/alarmtimewidget.h | 94 + kalarm/birthdaydlg.cpp | 427 +++ kalarm/birthdaydlg.h | 103 + kalarm/calendarcompat.cpp | 150 + kalarm/calendarcompat.h | 40 + kalarm/configure.in.bot | 16 + kalarm/configure.in.in | 2 + kalarm/daemon.cpp | 774 +++++ kalarm/daemon.h | 134 + kalarm/dcophandler.cpp | 768 +++++ kalarm/dcophandler.h | 107 + kalarm/deferdlg.cpp | 177 ++ kalarm/deferdlg.h | 58 + kalarm/editdlg.cpp | 2043 +++++++++++++ kalarm/editdlg.h | 260 ++ kalarm/editdlgprivate.h | 47 + kalarm/emailidcombo.cpp | 62 + kalarm/emailidcombo.h | 44 + kalarm/eventlistviewbase.cpp | 466 +++ kalarm/eventlistviewbase.h | 132 + kalarm/find.cpp | 394 +++ kalarm/find.h | 75 + kalarm/fontcolour.cpp | 265 ++ kalarm/fontcolour.h | 78 + kalarm/fontcolourbutton.cpp | 161 + kalarm/fontcolourbutton.h | 91 + kalarm/functions.cpp | 1099 +++++++ kalarm/functions.h | 130 + kalarm/hi16-app-kalarm.png | Bin 0 -> 1036 bytes kalarm/hi32-app-kalarm.png | Bin 0 -> 2521 bytes kalarm/hi48-app-kalarm.png | Bin 0 -> 4300 bytes kalarm/kalarm.desktop | 75 + kalarm/kalarm.h | 39 + kalarm/kalarm.tray.desktop | 74 + kalarm/kalarmapp.cpp | 2187 ++++++++++++++ kalarm/kalarmapp.h | 189 ++ kalarm/kalarmd/Makefile.am | 28 + kalarm/kalarmd/adapp.cpp | 71 + kalarm/kalarmd/adapp.h | 42 + kalarm/kalarmd/adcalendar.cpp | 253 ++ kalarm/kalarmd/adcalendar.h | 120 + kalarm/kalarmd/adconfigdata.cpp | 146 + kalarm/kalarmd/adconfigdata.h | 41 + kalarm/kalarmd/admain.cpp | 59 + kalarm/kalarmd/alarmdaemon.cpp | 614 ++++ kalarm/kalarmd/alarmdaemon.h | 79 + kalarm/kalarmd/alarmdaemoniface.h | 45 + kalarm/kalarmd/alarmguiiface.h | 71 + kalarm/kalarmd/clientinfo.cpp | 110 + kalarm/kalarmd/clientinfo.h | 72 + kalarm/kalarmd/kalarmd.autostart.desktop | 103 + kalarm/kalarmd/kalarmd.desktop | 55 + kalarm/kalarmd/kalarmd.h | 40 + kalarm/kalarmiface.h | 355 +++ kalarm/kalarmui.rc | 67 + kalarm/kamail.cpp | 1096 +++++++ kalarm/kamail.h | 65 + kalarm/karecurrence.cpp | 876 ++++++ kalarm/karecurrence.h | 89 + kalarm/latecancel.cpp | 161 + kalarm/latecancel.h | 69 + kalarm/lib/Makefile.am | 22 + kalarm/lib/buttongroup.cpp | 70 + kalarm/lib/buttongroup.h | 90 + kalarm/lib/checkbox.cpp | 133 + kalarm/lib/checkbox.h | 88 + kalarm/lib/colourcombo.cpp | 239 ++ kalarm/lib/colourcombo.h | 102 + kalarm/lib/colourlist.cpp | 43 + kalarm/lib/colourlist.h | 110 + kalarm/lib/combobox.cpp | 78 + kalarm/lib/combobox.h | 69 + kalarm/lib/dateedit.cpp | 122 + kalarm/lib/dateedit.h | 90 + kalarm/lib/datetime.cpp | 80 + kalarm/lib/datetime.h | 241 ++ kalarm/lib/label.cpp | 118 + kalarm/lib/label.h | 96 + kalarm/lib/lineedit.cpp | 200 ++ kalarm/lib/lineedit.h | 94 + kalarm/lib/messagebox.cpp | 178 ++ kalarm/lib/messagebox.h | 125 + kalarm/lib/pushbutton.cpp | 102 + kalarm/lib/pushbutton.h | 77 + kalarm/lib/radiobutton.cpp | 134 + kalarm/lib/radiobutton.h | 88 + kalarm/lib/shellprocess.cpp | 208 ++ kalarm/lib/shellprocess.h | 138 + kalarm/lib/slider.cpp | 85 + kalarm/lib/slider.h | 80 + kalarm/lib/spinbox.cpp | 476 +++ kalarm/lib/spinbox.h | 156 + kalarm/lib/spinbox2.cpp | 511 ++++ kalarm/lib/spinbox2.h | 317 ++ kalarm/lib/spinbox2private.h | 92 + kalarm/lib/synchtimer.cpp | 233 ++ kalarm/lib/synchtimer.h | 198 ++ kalarm/lib/timeedit.cpp | 207 ++ kalarm/lib/timeedit.h | 122 + kalarm/lib/timeperiod.cpp | 384 +++ kalarm/lib/timeperiod.h | 146 + kalarm/lib/timespinbox.cpp | 364 +++ kalarm/lib/timespinbox.h | 127 + kalarm/main.cpp | 131 + kalarm/mainwindow.cpp | 1424 +++++++++ kalarm/mainwindow.h | 182 ++ kalarm/mainwindowbase.cpp | 50 + kalarm/mainwindowbase.h | 51 + kalarm/messagewin.cpp | 1727 +++++++++++ kalarm/messagewin.h | 165 + kalarm/pickfileradio.cpp | 182 ++ kalarm/pickfileradio.h | 120 + kalarm/pixmaps/Makefile.am | 3 + kalarm/pixmaps/cr16-action-file.png | Bin 0 -> 1080 bytes kalarm/pixmaps/cr16-action-kalarm.png | Bin 0 -> 865 bytes kalarm/pixmaps/cr16-action-message.png | Bin 0 -> 221 bytes kalarm/pixmaps/cr16-action-new_from_template.png | Bin 0 -> 705 bytes kalarm/pixmaps/cr16-action-playsound.png | Bin 0 -> 750 bytes kalarm/pixmaps/cr22-action-kalarm.png | Bin 0 -> 1462 bytes kalarm/pixmaps/cr22-action-kalarm_disabled.png | Bin 0 -> 1396 bytes kalarm/pixmaps/cr22-action-new_from_template.png | Bin 0 -> 843 bytes kalarm/prefdlg.cpp | 1363 +++++++++ kalarm/prefdlg.h | 270 ++ kalarm/preferences.cpp | 705 +++++ kalarm/preferences.h | 235 ++ kalarm/recurrenceedit.cpp | 1639 ++++++++++ kalarm/recurrenceedit.h | 191 ++ kalarm/recurrenceeditprivate.h | 196 ++ kalarm/reminder.cpp | 159 + kalarm/reminder.h | 60 + kalarm/repetition.cpp | 364 +++ kalarm/repetition.h | 99 + kalarm/sounddlg.cpp | 472 +++ kalarm/sounddlg.h | 97 + kalarm/soundpicker.cpp | 292 ++ kalarm/soundpicker.h | 135 + kalarm/specialactions.cpp | 192 ++ kalarm/specialactions.h | 96 + kalarm/startdaytimer.cpp | 49 + kalarm/startdaytimer.h | 66 + kalarm/templatedlg.cpp | 241 ++ kalarm/templatedlg.h | 62 + kalarm/templatelistview.cpp | 131 + kalarm/templatelistview.h | 88 + kalarm/templatemenuaction.cpp | 84 + kalarm/templatemenuaction.h | 46 + kalarm/templatepickdlg.cpp | 87 + kalarm/templatepickdlg.h | 43 + kalarm/timeselector.cpp | 159 + kalarm/timeselector.h | 61 + kalarm/traywindow.cpp | 361 +++ kalarm/traywindow.h | 71 + kalarm/undo.cpp | 1127 +++++++ kalarm/undo.h | 93 + kalarm/uninstall.desktop | 2 + 172 files changed, 43493 insertions(+) create mode 100644 kalarm/ACKNOWLEDGEMENTS create mode 100644 kalarm/AUTHORS create mode 100644 kalarm/BUGS create mode 100644 kalarm/COPYING create mode 100644 kalarm/Changelog create mode 100644 kalarm/INSTALL create mode 100644 kalarm/Makefile.am create mode 100644 kalarm/README create mode 100644 kalarm/alarmcalendar.cpp create mode 100644 kalarm/alarmcalendar.h create mode 100644 kalarm/alarmevent.cpp create mode 100644 kalarm/alarmevent.h create mode 100644 kalarm/alarmlistview.cpp create mode 100644 kalarm/alarmlistview.h create mode 100644 kalarm/alarmtext.cpp create mode 100644 kalarm/alarmtext.h create mode 100644 kalarm/alarmtimewidget.cpp create mode 100644 kalarm/alarmtimewidget.h create mode 100644 kalarm/birthdaydlg.cpp create mode 100644 kalarm/birthdaydlg.h create mode 100644 kalarm/calendarcompat.cpp create mode 100644 kalarm/calendarcompat.h create mode 100644 kalarm/configure.in.bot create mode 100644 kalarm/configure.in.in create mode 100644 kalarm/daemon.cpp create mode 100644 kalarm/daemon.h create mode 100644 kalarm/dcophandler.cpp create mode 100644 kalarm/dcophandler.h create mode 100644 kalarm/deferdlg.cpp create mode 100644 kalarm/deferdlg.h create mode 100644 kalarm/editdlg.cpp create mode 100644 kalarm/editdlg.h create mode 100644 kalarm/editdlgprivate.h create mode 100644 kalarm/emailidcombo.cpp create mode 100644 kalarm/emailidcombo.h create mode 100644 kalarm/eventlistviewbase.cpp create mode 100644 kalarm/eventlistviewbase.h create mode 100644 kalarm/find.cpp create mode 100644 kalarm/find.h create mode 100644 kalarm/fontcolour.cpp create mode 100644 kalarm/fontcolour.h create mode 100644 kalarm/fontcolourbutton.cpp create mode 100644 kalarm/fontcolourbutton.h create mode 100644 kalarm/functions.cpp create mode 100644 kalarm/functions.h create mode 100644 kalarm/hi16-app-kalarm.png create mode 100644 kalarm/hi32-app-kalarm.png create mode 100644 kalarm/hi48-app-kalarm.png create mode 100644 kalarm/kalarm.desktop create mode 100644 kalarm/kalarm.h create mode 100644 kalarm/kalarm.tray.desktop create mode 100644 kalarm/kalarmapp.cpp create mode 100644 kalarm/kalarmapp.h create mode 100644 kalarm/kalarmd/Makefile.am create mode 100644 kalarm/kalarmd/adapp.cpp create mode 100644 kalarm/kalarmd/adapp.h create mode 100644 kalarm/kalarmd/adcalendar.cpp create mode 100644 kalarm/kalarmd/adcalendar.h create mode 100644 kalarm/kalarmd/adconfigdata.cpp create mode 100644 kalarm/kalarmd/adconfigdata.h create mode 100644 kalarm/kalarmd/admain.cpp create mode 100644 kalarm/kalarmd/alarmdaemon.cpp create mode 100644 kalarm/kalarmd/alarmdaemon.h create mode 100644 kalarm/kalarmd/alarmdaemoniface.h create mode 100644 kalarm/kalarmd/alarmguiiface.h create mode 100644 kalarm/kalarmd/clientinfo.cpp create mode 100644 kalarm/kalarmd/clientinfo.h create mode 100644 kalarm/kalarmd/kalarmd.autostart.desktop create mode 100644 kalarm/kalarmd/kalarmd.desktop create mode 100644 kalarm/kalarmd/kalarmd.h create mode 100644 kalarm/kalarmiface.h create mode 100644 kalarm/kalarmui.rc create mode 100644 kalarm/kamail.cpp create mode 100644 kalarm/kamail.h create mode 100644 kalarm/karecurrence.cpp create mode 100644 kalarm/karecurrence.h create mode 100644 kalarm/latecancel.cpp create mode 100644 kalarm/latecancel.h create mode 100644 kalarm/lib/Makefile.am create mode 100644 kalarm/lib/buttongroup.cpp create mode 100644 kalarm/lib/buttongroup.h create mode 100644 kalarm/lib/checkbox.cpp create mode 100644 kalarm/lib/checkbox.h create mode 100644 kalarm/lib/colourcombo.cpp create mode 100644 kalarm/lib/colourcombo.h create mode 100644 kalarm/lib/colourlist.cpp create mode 100644 kalarm/lib/colourlist.h create mode 100644 kalarm/lib/combobox.cpp create mode 100644 kalarm/lib/combobox.h create mode 100644 kalarm/lib/dateedit.cpp create mode 100644 kalarm/lib/dateedit.h create mode 100644 kalarm/lib/datetime.cpp create mode 100644 kalarm/lib/datetime.h create mode 100644 kalarm/lib/label.cpp create mode 100644 kalarm/lib/label.h create mode 100644 kalarm/lib/lineedit.cpp create mode 100644 kalarm/lib/lineedit.h create mode 100644 kalarm/lib/messagebox.cpp create mode 100644 kalarm/lib/messagebox.h create mode 100644 kalarm/lib/pushbutton.cpp create mode 100644 kalarm/lib/pushbutton.h create mode 100644 kalarm/lib/radiobutton.cpp create mode 100644 kalarm/lib/radiobutton.h create mode 100644 kalarm/lib/shellprocess.cpp create mode 100644 kalarm/lib/shellprocess.h create mode 100644 kalarm/lib/slider.cpp create mode 100644 kalarm/lib/slider.h create mode 100644 kalarm/lib/spinbox.cpp create mode 100644 kalarm/lib/spinbox.h create mode 100644 kalarm/lib/spinbox2.cpp create mode 100644 kalarm/lib/spinbox2.h create mode 100644 kalarm/lib/spinbox2private.h create mode 100644 kalarm/lib/synchtimer.cpp create mode 100644 kalarm/lib/synchtimer.h create mode 100644 kalarm/lib/timeedit.cpp create mode 100644 kalarm/lib/timeedit.h create mode 100644 kalarm/lib/timeperiod.cpp create mode 100644 kalarm/lib/timeperiod.h create mode 100644 kalarm/lib/timespinbox.cpp create mode 100644 kalarm/lib/timespinbox.h create mode 100644 kalarm/main.cpp create mode 100644 kalarm/mainwindow.cpp create mode 100644 kalarm/mainwindow.h create mode 100644 kalarm/mainwindowbase.cpp create mode 100644 kalarm/mainwindowbase.h create mode 100644 kalarm/messagewin.cpp create mode 100644 kalarm/messagewin.h create mode 100644 kalarm/pickfileradio.cpp create mode 100644 kalarm/pickfileradio.h create mode 100644 kalarm/pixmaps/Makefile.am create mode 100644 kalarm/pixmaps/cr16-action-file.png create mode 100644 kalarm/pixmaps/cr16-action-kalarm.png create mode 100644 kalarm/pixmaps/cr16-action-message.png create mode 100644 kalarm/pixmaps/cr16-action-new_from_template.png create mode 100644 kalarm/pixmaps/cr16-action-playsound.png create mode 100644 kalarm/pixmaps/cr22-action-kalarm.png create mode 100644 kalarm/pixmaps/cr22-action-kalarm_disabled.png create mode 100644 kalarm/pixmaps/cr22-action-new_from_template.png create mode 100644 kalarm/prefdlg.cpp create mode 100644 kalarm/prefdlg.h create mode 100644 kalarm/preferences.cpp create mode 100644 kalarm/preferences.h create mode 100644 kalarm/recurrenceedit.cpp create mode 100644 kalarm/recurrenceedit.h create mode 100644 kalarm/recurrenceeditprivate.h create mode 100644 kalarm/reminder.cpp create mode 100644 kalarm/reminder.h create mode 100644 kalarm/repetition.cpp create mode 100644 kalarm/repetition.h create mode 100644 kalarm/sounddlg.cpp create mode 100644 kalarm/sounddlg.h create mode 100644 kalarm/soundpicker.cpp create mode 100644 kalarm/soundpicker.h create mode 100644 kalarm/specialactions.cpp create mode 100644 kalarm/specialactions.h create mode 100644 kalarm/startdaytimer.cpp create mode 100644 kalarm/startdaytimer.h create mode 100644 kalarm/templatedlg.cpp create mode 100644 kalarm/templatedlg.h create mode 100644 kalarm/templatelistview.cpp create mode 100644 kalarm/templatelistview.h create mode 100644 kalarm/templatemenuaction.cpp create mode 100644 kalarm/templatemenuaction.h create mode 100644 kalarm/templatepickdlg.cpp create mode 100644 kalarm/templatepickdlg.h create mode 100644 kalarm/timeselector.cpp create mode 100644 kalarm/timeselector.h create mode 100644 kalarm/traywindow.cpp create mode 100644 kalarm/traywindow.h create mode 100644 kalarm/undo.cpp create mode 100644 kalarm/undo.h create mode 100644 kalarm/uninstall.desktop (limited to 'kalarm') diff --git a/kalarm/ACKNOWLEDGEMENTS b/kalarm/ACKNOWLEDGEMENTS new file mode 100644 index 000000000..0131c5dbf --- /dev/null +++ b/kalarm/ACKNOWLEDGEMENTS @@ -0,0 +1,26 @@ +Thank you to the following people who have contributed significantly to KAlarm +whether by giving advice and opinions, suggesting improvements, or giving their +time and effort to help fix faults: + +Cornelius Schumacher for his advice and opinions on various issues. + +Bill Dimm for helping to track down several faults, and useful suggestions for +extra facilities. + +Martin Köbele for code to send mail automatically via KMail. + +Wolfgang Jeltsch for investigating the corruption of deferred alarms. + +Bill Pfeffer for several suggestions for improvements. + +Serg ("Frr") for useful suggestions for extra facilities. + +Thorsten Schnebeck for helping to track down a tricky time zone problem. + +Ray ("thesun") for suggestions and problem reporting. + +Kyle Maus for helping to track down some recurrence problems. + +m.eik for contributing RPMs. + +Daniel Eckl for helping to fix some calendar problems in a beta issue. diff --git a/kalarm/AUTHORS b/kalarm/AUTHORS new file mode 100644 index 000000000..5f36e497b --- /dev/null +++ b/kalarm/AUTHORS @@ -0,0 +1,11 @@ +KAlarm author: +David Jarvie + +Alarm daemon author: +David Jarvie + +Original KOrganizer alarm daemon author: +Preston Brown + +KOrganizer alarm daemon maintainer: +Cornelius Schumacher \ No newline at end of file diff --git a/kalarm/BUGS b/kalarm/BUGS new file mode 100644 index 000000000..ff0c38397 --- /dev/null +++ b/kalarm/BUGS @@ -0,0 +1,17 @@ +Known bugs in KAlarm +==================== + +The following bugs are known to exist in KAlarm: + +-- Bug 79247: Summer/winter time changes (daylight savings time changes), are + not handled well. KAlarm needs to use time zones in scheduling alarms instead + of a simple local time which it currently uses. This requires improved + timezone handling in kdelibs and libkcal. This is fixed in KAlarm version 1.9. + +The following bugs appear to be KAlarm bugs but are actually due to other software: + +-- System tray icon has an opaque background (KDE bug 122988) + In the system tray, the KAlarm icon sometimes does not have a transparent + background, but instead displays inside an opaque square set to the KDE window + background colour. This is due to a bug in the QtCurve style, and can be fixed + by choosing a different style. diff --git a/kalarm/COPYING b/kalarm/COPYING new file mode 100644 index 000000000..2e3f0efad --- /dev/null +++ b/kalarm/COPYING @@ -0,0 +1,341 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/kalarm/Changelog b/kalarm/Changelog new file mode 100644 index 000000000..6c51c621e --- /dev/null +++ b/kalarm/Changelog @@ -0,0 +1,657 @@ +KAlarm Change Log + +=== Version 1.5.5 --- 21 January 2009 === +- Require a real double click to accept the selected template in pick list. +- Make mouse wheel work, and fix highlighting, for left-hand time spinbox + buttons in Plastik style. +- Ensure alarm windows show on top of full-screen windows. +- Fix failure to update alarms in KOrganizer when Kontact is running but + Kontact's calendar component is not loaded. +- Fix inability to change or cancel alarm deferral times. +- Fix invalid alarm remaining in calendar when pre-alarm action failure message + is acknowledged before the alarm is deferred. +- 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). +- Prevent defer dialogue date being set outside the allowed range. +- Cancel screensaver when an alarm is displayed. + +=== Version 1.5.4 (KDE 3.5.10) --- 18 August 2008 === +- Show background colour selector for file display alarms. +- Display default font correctly in alarm edit dialogue sample text. +- Expand font selection control when dialogue is expanded. +- Fix potential crash when KAlarm quits, if birthday dialogue was opened. + +=== Version 1.5.3 --- 22 May 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 --- 27 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. +- Rearrange yearly recurrence controls to reduce alarm edit dialogue height. +- Store email unique IDs instead of names in email alarms to prevent problems if + email IDs are renamed. + +=== Version 1.4.22 --- 27 January 2008 === +- 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. +- 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/kalarm/INSTALL b/kalarm/INSTALL new file mode 100644 index 000000000..34cab87ab --- /dev/null +++ b/kalarm/INSTALL @@ -0,0 +1,40 @@ +Setting up KAlarm on non-KDE desktops +===================================== + +Although KAlarm is a KDE application and requires KDE to be installed on your +system, you can still use it while running other desktops or window managers. + +In order to have alarms monitored and displayed automatically from one login +session to the next, the alarm daemon (which monitors the alarm list and tells +KAlarm to display each alarm when it becomes due) must be run automatically +when you graphically log in or otherwise start X. If you are running the KDE +desktop, the KAlarm installation process sets this up for you. + +If you want to use KAlarm with Gnome or another non-KDE window manager, you +have two alternatives: + +1) If your desktop environment/window manager performs session restoration, +ensure that the alarm daemon (kalarmd) is included in the session restoration, +and that after login or restarting X the alarm daemon is running with a +'-session' command line option, e.g. + + kalarmd -session 117f000002000100176495700000008340018 + +You can use the 'ps' command to check this. + +2) If you cannot use session restoration to start the alarm daemon correctly, +you must configure the following command to be run whenever you graphically log +in or start X: + + kalarmd --login + +If your desktop environment or window manager has a facility to configure +programs to be run at login, you can use that facility. Otherwise, you need to +add the command to an appropriate script which is run after X is started. + +If you can specify an order to start the applications, start kalarm first, then +kalarmd. + + +If you can send me details on how to set up the daemon for any particular +window manager, I will include these in the next version of KAlarm. diff --git a/kalarm/Makefile.am b/kalarm/Makefile.am new file mode 100644 index 000000000..6d7c5658a --- /dev/null +++ b/kalarm/Makefile.am @@ -0,0 +1,76 @@ +#KDE_OPTIONS = nofinal + +SUBDIRS = kalarmd lib pixmaps + +INCLUDES= -I$(top_srcdir) -I$(top_srcdir)/kalarm \ + -I$(top_srcdir)/kalarm/lib \ + -I$(top_builddir)/libkcal/libical/src \ + -I$(top_srcdir)/libkmime \ + -I$(top_srcdir)/certmanager/lib $(all_includes) + +bin_PROGRAMS = kalarm +# Don't change the order of the kalarm_SOURCES files without first checking +# that it builds with --enable-final !!! +kalarm_SOURCES = birthdaydlg.cpp main.cpp alarmevent.cpp editdlg.cpp \ + fontcolour.cpp soundpicker.cpp sounddlg.cpp alarmcalendar.cpp daemon.cpp undo.cpp \ + kalarmapp.cpp mainwindowbase.cpp mainwindow.cpp messagewin.cpp \ + preferences.cpp prefdlg.cpp kalarmiface.skel \ + traywindow.cpp dcophandler.cpp alarmguiiface.skel \ + recurrenceedit.cpp karecurrence.cpp deferdlg.cpp functions.cpp \ + fontcolourbutton.cpp alarmtimewidget.cpp \ + specialactions.cpp reminder.cpp startdaytimer.cpp \ + eventlistviewbase.cpp alarmlistview.cpp kamail.cpp timeselector.cpp \ + templatelistview.cpp templatepickdlg.cpp templatedlg.cpp \ + templatemenuaction.cpp latecancel.cpp repetition.cpp alarmtext.cpp \ + emailidcombo.cpp find.cpp pickfileradio.cpp calendarcompat.cpp +kalarm_LDFLAGS = $(all_libraries) $(KDE_RPATH) +if include_ARTS +ARTSLIB = -lartskde +endif +kalarm_LDADD = $(top_builddir)/kalarm/lib/libkalarm.la \ + $(top_builddir)/libkcal/libkcal.la \ + $(top_builddir)/libkmime/libkmime.la \ + $(top_builddir)/kalarm/kalarmd/libkalarmd.la \ + $(top_builddir)/libkdepim/libkdepim.la \ + $(top_builddir)/libkpimidentities/libkpimidentities.la \ + -lkabc -lkutils $(LIB_KDEUI) $(ARTSLIB) $(LIBXTST) +noinst_HEADERS = alarmcalendar.h alarmevent.h alarmlistview.h alarmtext.h \ + alarmtimewidget.h birthdaydlg.h daemon.h dcophandler.h deferdlg.h \ + editdlg.h editdlgprivate.h emailidcombo.h eventlistviewbase.h find.h \ + fontcolour.h fontcolourbutton.h functions.h kalarm.h kalarmapp.h \ + kamail.h karecurrence.h latecancel.h mainwindow.h mainwindowbase.h \ + messagewin.h pickfileradio.h prefdlg.h preferences.h recurrenceedit.h \ + recurrenceeditprivate.h reminder.h repetition.h sounddlg.h \ + soundpicker.h specialactions.h startdaytimer.h templatedlg.h \ + templatelistview.h templatemenuaction.h templatepickdlg.h timeselector.h \ + traywindow.h undo.h calendarcompat.h + +METASOURCES = AUTO + +alarmguiiface_DIR = $(top_srcdir)/kalarm/kalarmd + +autostart_DATA = kalarm.tray.desktop +autostartdir = $(datadir)/autostart + +xdg_apps_DATA = kalarm.desktop + +install-data-local: + $(mkinstalldirs) $(DESTDIR)$(kde_appsdir)/Applications + $(INSTALL_DATA) $(srcdir)/uninstall.desktop $(DESTDIR)$(kde_appsdir)/Applications/kalarm.desktop + +KDE_ICON = AUTO + + +rcdir = $(kde_datadir)/kalarm +rc_DATA = kalarmui.rc + +messages: rc.cpp + $(EXTRACTRC) *.rc >> rc.cpp + $(XGETTEXT) *.h *.cpp lib/*.h lib/*.cpp kalarmd/*.cpp -o $(podir)/kalarm.pot + +DOXYGEN_REFERENCES = libkcal +include $(top_srcdir)/admin/Doxyfile.am + +calendarcompat.o alarmcalendar.o: ../libkcal/libical/src/libical/ical.h +daemon.o: kalarmd/alarmdaemoniface_stub.h + diff --git a/kalarm/README b/kalarm/README new file mode 100644 index 000000000..e52cf40b2 --- /dev/null +++ b/kalarm/README @@ -0,0 +1,60 @@ +KAlarm + +KAlarm is a personal alarm message, command and email scheduler. It lets you +set up personal alarm messages which pop up on the screen at the chosen time, +or you can schedule commands to be executed or emails to be sent. + +When setting up or modifying an alarm, the options available let you: + * choose whether the alarm should display a text message, display a text file, + execute a command, or send an email. + * configure the alarm to recur on an hours/minutes, daily, weekly, monthly or + annual basis, or set it to trigger every time you log in. + * specify that a reminder should be displayed in advance of the main alarm + time(s). + * choose a colour and font for displaying the alarm message. + * specify an audible beep or a sound file to play when the message is + displayed. + * choose whether or not the alarm should be cancelled if it can't be triggered + at its scheduled time. An alarm can only be triggered while you are logged + in and running a graphical environment. If you choose not to cancel the + alarm if it can't be triggered at the correct time, it will be triggered + when you eventually log in. + +In addition to its graphical mode, alarms may also be scheduled from the +command line, or via DCOP calls from other programs. + +See the INSTALL file for installation instructions. + + +USE ON NON-KDE DESKTOPS + +For details on the requirements for setting up KAlarm to run on non-KDE +desktops, see the INSTALL file. + + +PROGRAM STATUS + +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 COPYING file for more details. + + +BUGS: + +Bugs can be reported to the KDE bug tracking system (http://bugs.kde.org). +There you can also get information about the status of older bugs. +KAlarm, as most other KDE programs, offers a menu item "Report Bug" in the +Help menu for convenient reporting of bugs or feature requests. + + +COMMENTS AND SUGGESTIONS: + +Please send your comments and suggestions to +David Jarvie . diff --git a/kalarm/alarmcalendar.cpp b/kalarm/alarmcalendar.cpp new file mode 100644 index 000000000..85c4f7bcf --- /dev/null +++ b/kalarm/alarmcalendar.cpp @@ -0,0 +1,908 @@ +/* + * alarmcalendar.cpp - KAlarm calendar file access + * Program: kalarm + * Copyright © 2001-2006,2009 by 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 "kalarm.h" +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +} + +#include +#include +#include + +#include "calendarcompat.h" +#include "daemon.h" +#include "functions.h" +#include "kalarmapp.h" +#include "mainwindow.h" +#include "preferences.h" +#include "startdaytimer.h" +#include "alarmcalendar.moc" + +using namespace KCal; + +QString AlarmCalendar::icalProductId() +{ + return QString::fromLatin1("-//K Desktop Environment//NONSGML " KALARM_NAME " %1//EN").arg(KAlarm::currentCalendarVersionString()); +} + +static const KAEvent::Status eventTypes[AlarmCalendar::NCALS] = { + KAEvent::ACTIVE, KAEvent::EXPIRED, KAEvent::DISPLAYING, KAEvent::TEMPLATE +}; +static const QString calendarNames[AlarmCalendar::NCALS] = { + QString::fromLatin1("calendar.ics"), + QString::fromLatin1("expired.ics"), + QString::fromLatin1("displaying.ics"), + QString::fromLatin1("template.ics") +}; +static KStaticDeleter calendarDeleter[AlarmCalendar::NCALS]; // ensure that the calendar destructors are called + +AlarmCalendar* AlarmCalendar::mCalendars[NCALS] = { 0, 0, 0, 0 }; + + +/****************************************************************************** +* Initialise the alarm calendars, and ensure that their file names are different. +* There are 4 calendars: +* 1) A user-independent one containing the active alarms; +* 2) A historical one containing expired alarms; +* 3) A user-specific one which contains details of alarms which are currently +* being displayed to that user and which have not yet been acknowledged; +* 4) One containing alarm templates. +* Reply = true if success, false if calendar name error. +*/ +bool AlarmCalendar::initialiseCalendars() +{ + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + QString activeKey = QString::fromLatin1("Calendar"); + QString expiredKey = QString::fromLatin1("ExpiredCalendar"); + QString templateKey = QString::fromLatin1("TemplateCalendar"); + QString displayCal, activeCal, expiredCal, templateCal; + calendarDeleter[ACTIVE].setObject(mCalendars[ACTIVE], createCalendar(ACTIVE, config, activeCal, activeKey)); + calendarDeleter[EXPIRED].setObject(mCalendars[EXPIRED], createCalendar(EXPIRED, config, expiredCal, expiredKey)); + calendarDeleter[DISPLAY].setObject(mCalendars[DISPLAY], createCalendar(DISPLAY, config, displayCal)); + calendarDeleter[TEMPLATE].setObject(mCalendars[TEMPLATE], createCalendar(TEMPLATE, config, templateCal, templateKey)); + + QString errorKey1, errorKey2; + if (activeCal == displayCal) + errorKey1 = activeKey; + else if (expiredCal == displayCal) + errorKey1 = expiredKey; + else if (templateCal == displayCal) + errorKey1 = templateKey; + if (!errorKey1.isNull()) + { + kdError(5950) << "AlarmCalendar::initialiseCalendars(): '" << errorKey1 << "' calendar name = display calendar name\n"; + QString file = config->readPathEntry(errorKey1); + KAlarmApp::displayFatalError(i18n("%1: file name not permitted: %2").arg(errorKey1).arg(file)); + return false; + } + if (activeCal == expiredCal) + { + errorKey1 = activeKey; + errorKey2 = expiredKey; + } + else if (activeCal == templateCal) + { + errorKey1 = activeKey; + errorKey2 = templateKey; + } + else if (expiredCal == templateCal) + { + errorKey1 = expiredKey; + errorKey2 = templateKey; + } + if (!errorKey1.isNull()) + { + kdError(5950) << "AlarmCalendar::initialiseCalendars(): calendar names clash: " << errorKey1 << ", " << errorKey2 << endl; + KAlarmApp::displayFatalError(i18n("%1, %2: file names must be different").arg(errorKey1).arg(errorKey2)); + return false; + } + if (!mCalendars[ACTIVE]->valid()) + { + QString path = mCalendars[ACTIVE]->path(); + kdError(5950) << "AlarmCalendar::initialiseCalendars(): invalid name: " << path << endl; + KAlarmApp::displayFatalError(i18n("Invalid calendar file name: %1").arg(path)); + return false; + } + return true; +} + +/****************************************************************************** +* Create an alarm calendar instance. +* If 'configKey' is non-null, the calendar will be converted to ICal format. +*/ +AlarmCalendar* AlarmCalendar::createCalendar(CalID type, KConfig* config, QString& writePath, const QString& configKey) +{ + static QRegExp vcsRegExp(QString::fromLatin1("\\.vcs$")); + static QString ical = QString::fromLatin1(".ics"); + + if (configKey.isNull()) + { + writePath = locateLocal("appdata", calendarNames[type]); + return new AlarmCalendar(writePath, type); + } + else + { + QString readPath = config->readPathEntry(configKey, locateLocal("appdata", calendarNames[type])); + writePath = readPath; + writePath.replace(vcsRegExp, ical); + return new AlarmCalendar(readPath, type, writePath, configKey); + } +} + +/****************************************************************************** +* Terminate access to all calendars. +*/ +void AlarmCalendar::terminateCalendars() +{ + for (int i = 0; i < NCALS; ++i) + { + calendarDeleter[i].destructObject(); + mCalendars[i] = 0; + } +} + +/****************************************************************************** +* Return a calendar, opening it first if not already open. +* Reply = calendar instance +* = 0 if calendar could not be opened. +*/ +AlarmCalendar* AlarmCalendar::calendarOpen(CalID id) +{ + AlarmCalendar* cal = mCalendars[id]; + if (!cal->mPurgeDays) + return 0; // all events are automatically purged from the calendar + if (cal->open()) + return cal; + kdError(5950) << "AlarmCalendar::calendarOpen(" << calendarNames[id] << "): open error\n"; + return 0; +} + +/****************************************************************************** +* Find and return the event with the specified ID. +* The calendar searched is determined by the calendar identifier in the ID. +*/ +const KCal::Event* AlarmCalendar::getEvent(const QString& uniqueID) +{ + if (uniqueID.isEmpty()) + return 0; + CalID calID; + switch (KAEvent::uidStatus(uniqueID)) + { + case KAEvent::ACTIVE: calID = ACTIVE; break; + case KAEvent::TEMPLATE: calID = TEMPLATE; break; + case KAEvent::EXPIRED: calID = EXPIRED; break; + case KAEvent::DISPLAYING: calID = DISPLAY; break; + default: + return 0; + } + AlarmCalendar* cal = calendarOpen(calID); + if (!cal) + return 0; + return cal->event(uniqueID); +} + + +/****************************************************************************** +* Constructor. +* If 'icalPath' is non-null, the file will be always be saved in ICal format. +* If 'configKey' is also non-null, that config file entry will be updated when +* the file is saved in ICal format. +*/ +AlarmCalendar::AlarmCalendar(const QString& path, CalID type, const QString& icalPath, + const QString& configKey) + : mCalendar(0), + mConfigKey(icalPath.isNull() ? QString::null : configKey), + mType(eventTypes[type]), + mPurgeDays(-1), // default to not purging + mOpen(false), + mPurgeDaysQueued(-1), + mUpdateCount(0), + mUpdateSave(false) +{ + mUrl.setPath(path); // N.B. constructor mUrl(path) doesn't work with UNIX paths + mICalUrl.setPath(icalPath.isNull() ? path : icalPath); + mVCal = (icalPath.isNull() || path != icalPath); // is the calendar in ICal or VCal format? +} + +AlarmCalendar::~AlarmCalendar() +{ + close(); +} + +/****************************************************************************** +* Open the calendar file if not already open, and load it into memory. +*/ +bool AlarmCalendar::open() +{ + if (mOpen) + return true; + if (!mUrl.isValid()) + return false; + + kdDebug(5950) << "AlarmCalendar::open(" << mUrl.prettyURL() << ")\n"; + if (!mCalendar) + mCalendar = new CalendarLocal(QString::fromLatin1("UTC")); + mCalendar->setLocalTime(); // write out using local time (i.e. no time zone) + + // Check for file's existence, assuming that it does exist when uncertain, + // to avoid overwriting it. + if (!KIO::NetAccess::exists(mUrl, true, MainWindow::mainMainWindow())) + { + // The calendar file doesn't yet exist, so create it + if (create()) + load(); + } + else + { + // Load the existing calendar file + if (load() == 0) + { + if (create()) // zero-length file - create a new one + load(); + } + } + if (!mOpen) + { + delete mCalendar; + mCalendar = 0; + } + return mOpen; +} + +/****************************************************************************** +* Private method to create a new calendar file. +* It is always created in iCalendar format. +*/ +bool AlarmCalendar::create() +{ + if (mICalUrl.isLocalFile()) + return saveCal(mICalUrl.path()); + else + { + KTempFile tmpFile; + return saveCal(tmpFile.name()); + } +} + +/****************************************************************************** +* Load the calendar file into memory. +* Reply = 1 if success +* = 0 if zero-length file exists. +* = -1 if failure to load calendar file +* = -2 if instance uninitialised. +*/ +int AlarmCalendar::load() +{ + if (!mCalendar) + return -2; + + kdDebug(5950) << "AlarmCalendar::load(): " << mUrl.prettyURL() << endl; + QString tmpFile; + if (!KIO::NetAccess::download(mUrl, tmpFile, MainWindow::mainMainWindow())) + { + kdError(5950) << "AlarmCalendar::load(): Load failure" << endl; + KMessageBox::error(0, i18n("Cannot open calendar:\n%1").arg(mUrl.prettyURL())); + return -1; + } + kdDebug(5950) << "AlarmCalendar::load(): --- Downloaded to " << tmpFile << endl; + mCalendar->setTimeZoneId(QString::null); // default to the local time zone for reading + bool loaded = mCalendar->load(tmpFile); + mCalendar->setLocalTime(); // write using local time (i.e. no time zone) + if (!loaded) + { + // Check if the file is zero length + KIO::NetAccess::removeTempFile(tmpFile); + KIO::UDSEntry uds; + KIO::NetAccess::stat(mUrl, uds, MainWindow::mainMainWindow()); + KFileItem fi(uds, mUrl); + if (!fi.size()) + return 0; // file is zero length + kdError(5950) << "AlarmCalendar::load(): Error loading calendar file '" << tmpFile << "'" << endl; + KMessageBox::error(0, i18n("Error loading calendar:\n%1\n\nPlease fix or delete the file.").arg(mUrl.prettyURL())); + // load() could have partially populated the calendar, so clear it out + mCalendar->close(); + delete mCalendar; + mCalendar = 0; + return -1; + } + if (!mLocalFile.isEmpty()) + KIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file + mLocalFile = tmpFile; + + CalendarCompat::fix(*mCalendar, mLocalFile); // convert events to current KAlarm format for when calendar is saved + mOpen = true; + return 1; +} + +/****************************************************************************** +* Reload the calendar file into memory. +*/ +bool AlarmCalendar::reload() +{ + if (!mCalendar) + return false; + kdDebug(5950) << "AlarmCalendar::reload(): " << mUrl.prettyURL() << endl; + close(); + bool result = open(); + return result; +} + +/****************************************************************************** +* Save the calendar from memory to file. +* If a filename is specified, create a new calendar file. +*/ +bool AlarmCalendar::saveCal(const QString& newFile) +{ + if (!mCalendar || !mOpen && newFile.isNull()) + return false; + + kdDebug(5950) << "AlarmCalendar::saveCal(\"" << newFile << "\", " << mType << ")\n"; + QString saveFilename = newFile.isNull() ? mLocalFile : newFile; + if (mVCal && newFile.isNull() && mUrl.isLocalFile()) + saveFilename = mICalUrl.path(); + if (!mCalendar->save(saveFilename, new ICalFormat)) + { + kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): failed.\n"; + KMessageBox::error(0, i18n("Failed to save calendar to\n'%1'").arg(mICalUrl.prettyURL())); + return false; + } + + if (!mICalUrl.isLocalFile()) + { + if (!KIO::NetAccess::upload(saveFilename, mICalUrl, MainWindow::mainMainWindow())) + { + kdError(5950) << "AlarmCalendar::saveCal(" << saveFilename << "): upload failed.\n"; + KMessageBox::error(0, i18n("Cannot upload calendar to\n'%1'").arg(mICalUrl.prettyURL())); + return false; + } + } + + if (mVCal) + { + // The file was in vCalendar format, but has now been saved in iCalendar format. + // Save the change in the config file. + if (!mConfigKey.isNull()) + { + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + config->writePathEntry(mConfigKey, mICalUrl.path()); + config->sync(); + } + mUrl = mICalUrl; + mVCal = false; + } + + mUpdateSave = false; + emit calendarSaved(this); + return true; +} + +/****************************************************************************** +* Delete any temporary file at program exit. +*/ +void AlarmCalendar::close() +{ + if (!mLocalFile.isEmpty()) + { + KIO::NetAccess::removeTempFile(mLocalFile); // removes it only if it IS a temporary file + mLocalFile = ""; + } + if (mCalendar) + { + mCalendar->close(); + delete mCalendar; + mCalendar = 0; + } + mOpen = false; +} + +/****************************************************************************** +* Import alarms from an external calendar and merge them into KAlarm's calendar. +* The alarms are given new unique event IDs. +* Parameters: parent = parent widget for error message boxes +* Reply = true if all alarms in the calendar were successfully imported +* = false if any alarms failed to be imported. +*/ +bool AlarmCalendar::importAlarms(QWidget* parent) +{ + KURL url = KFileDialog::getOpenURL(QString::fromLatin1(":importalarms"), + QString::fromLatin1("*.vcs *.ics|%1").arg(i18n("Calendar Files")), parent); + if (url.isEmpty()) + { + kdError(5950) << "AlarmCalendar::importAlarms(): Empty URL" << endl; + return false; + } + if (!url.isValid()) + { + kdDebug(5950) << "AlarmCalendar::importAlarms(): Invalid URL" << endl; + return false; + } + kdDebug(5950) << "AlarmCalendar::importAlarms(" << url.prettyURL() << ")" << endl; + + bool success = true; + QString filename; + bool local = url.isLocalFile(); + if (local) + { + filename = url.path(); + if (!KStandardDirs::exists(filename)) + { + kdDebug(5950) << "AlarmCalendar::importAlarms(): File '" << url.prettyURL() << "' not found" << endl; + KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL())); + return false; + } + } + else + { + if (!KIO::NetAccess::download(url, filename, MainWindow::mainMainWindow())) + { + kdError(5950) << "AlarmCalendar::importAlarms(): Download failure" << endl; + KMessageBox::error(parent, i18n("Cannot download calendar:\n%1").arg(url.prettyURL())); + return false; + } + kdDebug(5950) << "--- Downloaded to " << filename << endl; + } + + // Read the calendar and add its alarms to the current calendars + CalendarLocal cal(QString::fromLatin1("UTC")); + cal.setLocalTime(); // write out using local time (i.e. no time zone) + success = cal.load(filename); + if (!success) + { + kdDebug(5950) << "AlarmCalendar::importAlarms(): error loading calendar '" << filename << "'" << endl; + KMessageBox::error(parent, i18n("Could not load calendar '%1'.").arg(url.prettyURL())); + } + else + { + CalendarCompat::fix(cal, filename); + bool saveActive = false; + bool saveExpired = false; + bool saveTemplate = false; + AlarmCalendar* active = activeCalendar(); + AlarmCalendar* expired = expiredCalendar(); + AlarmCalendar* templat = 0; + AlarmCalendar* acal; + Event::List events = cal.rawEvents(); + for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + const Event* event = *it; + if (event->alarms().isEmpty() || !KAEvent(*event).valid()) + continue; // ignore events without alarms, or usable alarms + KAEvent::Status type = KAEvent::uidStatus(event->uid()); + switch (type) + { + case KAEvent::ACTIVE: + acal = active; + saveActive = true; + break; + case KAEvent::EXPIRED: + acal = expired; + saveExpired = true; + break; + case KAEvent::TEMPLATE: + if (!templat) + templat = templateCalendarOpen(); + acal = templat; + saveTemplate = true; + break; + default: + continue; + } + if (!acal) + continue; + + Event* newev = new Event(*event); + + // If there is a display alarm without display text, use the event + // summary text instead. + if (type == KAEvent::ACTIVE && !newev->summary().isEmpty()) + { + const Alarm::List& alarms = newev->alarms(); + for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait) + { + Alarm* alarm = *ait; + if (alarm->type() == Alarm::Display && alarm->text().isEmpty()) + alarm->setText(newev->summary()); + } + newev->setSummary(QString::null); // KAlarm only uses summary for template names + } + // Give the event a new ID and add it to the calendar + newev->setUid(KAEvent::uid(CalFormat::createUniqueId(), type)); + if (!acal->mCalendar->addEvent(newev)) + success = false; + } + + // Save any calendars which have been modified + if (saveActive) + active->saveCal(); + if (saveExpired) + expired->saveCal(); + if (saveTemplate) + templat->saveCal(); + } + if (!local) + KIO::NetAccess::removeTempFile(filename); + return success; +} + +/****************************************************************************** +* Flag the start of a group of calendar update calls. +* The purpose is to avoid multiple calendar saves during a group of operations. +*/ +void AlarmCalendar::startUpdate() +{ + ++mUpdateCount; +} + +/****************************************************************************** +* Flag the end of a group of calendar update calls. +* The calendar is saved if appropriate. +*/ +bool AlarmCalendar::endUpdate() +{ + if (mUpdateCount > 0) + --mUpdateCount; + if (!mUpdateCount) + { + if (mUpdateSave) + return saveCal(); + } + return true; +} + +/****************************************************************************** +* Save the calendar, or flag it for saving if in a group of calendar update calls. +*/ +bool AlarmCalendar::save() +{ + if (mUpdateCount) + { + mUpdateSave = true; + return true; + } + else + return saveCal(); +} + +#if 0 +/****************************************************************************** +* If it is VCal format, convert the calendar URL to ICal and save the new URL +* in the config file. +*/ +void AlarmCalendar::convertToICal() +{ + if (mVCal) + { + if (!mConfigKey.isNull()) + { + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + config->writePathEntry(mConfigKey, mICalUrl.path()); + config->sync(); + } + mUrl = mICalUrl; + mVCal = false; + } +} +#endif + +/****************************************************************************** +* Set the number of days to keep alarms. +* Alarms which are older are purged immediately, and at the start of each day. +*/ +void AlarmCalendar::setPurgeDays(int days) +{ + if (days != mPurgeDays) + { + int oldDays = mPurgeDays; + mPurgeDays = days; + if (mPurgeDays <= 0) + StartOfDayTimer::disconnect(this); + if (oldDays < 0 || days >= 0 && days < oldDays) + { + // Alarms are now being kept for less long, so purge them + if (open()) + slotPurge(); + } + else if (mPurgeDays > 0) + startPurgeTimer(); + } +} + +/****************************************************************************** +* Called at the start of each day by the purge timer. +* Purge all events from the calendar whose end time is longer ago than 'mPurgeDays'. +*/ +void AlarmCalendar::slotPurge() +{ + purge(mPurgeDays); + startPurgeTimer(); +} + +/****************************************************************************** +* Purge all events from the calendar whose end time is longer ago than +* 'daysToKeep'. All events are deleted if 'daysToKeep' is zero. +*/ +void AlarmCalendar::purge(int daysToKeep) +{ + if (mPurgeDaysQueued < 0 || daysToKeep < mPurgeDaysQueued) + mPurgeDaysQueued = daysToKeep; + + // Do the purge once any other current operations are completed + theApp()->processQueue(); +} + +/****************************************************************************** +* This method must only be called from the main KAlarm queue processing loop, +* to prevent asynchronous calendar operations interfering with one another. +* +* Purge all events from the calendar whose end time is longer ago than 'daysToKeep'. +* All events are deleted if 'daysToKeep' is zero. +* The calendar must already be open. +*/ +void AlarmCalendar::purgeIfQueued() +{ + if (mPurgeDaysQueued >= 0) + { + if (open()) + { + kdDebug(5950) << "AlarmCalendar::purgeIfQueued(" << mPurgeDaysQueued << ")\n"; + bool changed = false; + QDate cutoff = QDate::currentDate().addDays(-mPurgeDaysQueued); + Event::List events = mCalendar->rawEvents(); + for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + Event* kcalEvent = *it; + if (!mPurgeDaysQueued || kcalEvent->created().date() < cutoff) + { + mCalendar->deleteEvent(kcalEvent); + changed = true; + } + } + if (changed) + { + saveCal(); + emit purged(); + } + mPurgeDaysQueued = -1; + } + } +} + + +/****************************************************************************** +* Start the purge timer to expire at the start of the next day (using the user- +* defined start-of-day time). +*/ +void AlarmCalendar::startPurgeTimer() +{ + if (mPurgeDays > 0) + StartOfDayTimer::connect(this, SLOT(slotPurge())); +} + +/****************************************************************************** +* Add the specified event to the calendar. +* If it is the active calendar and 'useEventID' is false, a new event ID is +* created. In all other cases, the event ID is taken from 'event'. +* 'event' is updated with the actual event ID. +* Reply = the KCal::Event as written to the calendar. +*/ +Event* AlarmCalendar::addEvent(KAEvent& event, bool useEventID) +{ + if (!mOpen) + return 0; + QString id = event.id(); + Event* kcalEvent = new Event; + if (mType == KAEvent::ACTIVE) + { + if (id.isEmpty()) + useEventID = false; + if (!useEventID) + event.setEventID(kcalEvent->uid()); + } + else + { + if (id.isEmpty()) + id = kcalEvent->uid(); + useEventID = true; + } + if (useEventID) + { + id = KAEvent::uid(id, mType); + event.setEventID(id); + kcalEvent->setUid(id); + } + event.updateKCalEvent(*kcalEvent, false, (mType == KAEvent::EXPIRED), true); + mCalendar->addEvent(kcalEvent); + event.clearUpdated(); + return kcalEvent; +} + +/****************************************************************************** +* Update the specified event in the calendar with its new contents. +* The event retains the same ID. +*/ +void AlarmCalendar::updateEvent(const KAEvent& evnt) +{ + if (mOpen) + { + Event* kcalEvent = event(evnt.id()); + if (kcalEvent) + { + evnt.updateKCalEvent(*kcalEvent); + evnt.clearUpdated(); + if (mType == KAEvent::ACTIVE) + Daemon::savingEvent(evnt.id()); + return; + } + } + if (mType == KAEvent::ACTIVE) + Daemon::eventHandled(evnt.id(), false); +} + +/****************************************************************************** +* Delete the specified event from the calendar, if it exists. +* The calendar is then optionally saved. +*/ +bool AlarmCalendar::deleteEvent(const QString& eventID, bool saveit) +{ + if (mOpen) + { + Event* kcalEvent = event(eventID); + if (kcalEvent) + { + mCalendar->deleteEvent(kcalEvent); + if (mType == KAEvent::ACTIVE) + Daemon::savingEvent(eventID); + if (saveit) + return save(); + return true; + } + } + if (mType == KAEvent::ACTIVE) + Daemon::eventHandled(eventID, false); + return false; +} + +/****************************************************************************** +* Emit a signal to indicate whether the calendar is empty. +*/ +void AlarmCalendar::emitEmptyStatus() +{ + emit emptyStatus(events().isEmpty()); +} + +/****************************************************************************** +* Return the event with the specified ID. +*/ +KCal::Event* AlarmCalendar::event(const QString& uniqueID) +{ + return mCalendar ? mCalendar->event(uniqueID) : 0; +} + +/****************************************************************************** +* Return all events in the calendar which contain usable alarms. +*/ +KCal::Event::List AlarmCalendar::events() +{ + if (!mCalendar) + return KCal::Event::List(); + KCal::Event::List list = mCalendar->rawEvents(); + KCal::Event::List::Iterator it = list.begin(); + while (it != list.end()) + { + KCal::Event* event = *it; + if (event->alarms().isEmpty() || !KAEvent(*event).valid()) + it = list.remove(it); + else + ++it; + } + return list; +} + +/****************************************************************************** +* Return all events which have alarms falling within the specified time range. +*/ +Event::List AlarmCalendar::eventsWithAlarms(const QDateTime& from, const QDateTime& to) +{ + kdDebug(5950) << "AlarmCalendar::eventsWithAlarms(" << from.toString() << " - " << to.toString() << ")\n"; + Event::List evnts; + QDateTime dt; + Event::List allEvents = events(); // ignore events without usable alarms + for (Event::List::ConstIterator it = allEvents.begin(); it != allEvents.end(); ++it) + { + Event* e = *it; + bool recurs = e->doesRecur(); + int endOffset = 0; + bool endOffsetValid = false; + const Alarm::List& alarms = e->alarms(); + for (Alarm::List::ConstIterator ait = alarms.begin(); ait != alarms.end(); ++ait) + { + Alarm* alarm = *ait; + if (alarm->enabled()) + { + if (recurs) + { + if (alarm->hasTime()) + dt = alarm->time(); + else + { + // The alarm time is defined by an offset from the event start or end time. + // Find the offset from the event start time, which is also used as the + // offset from the recurrence time. + int offset = 0; + if (alarm->hasStartOffset()) + offset = alarm->startOffset().asSeconds(); + else if (alarm->hasEndOffset()) + { + if (!endOffsetValid) + { + endOffset = e->hasDuration() ? e->duration() : e->hasEndDate() ? e->dtStart().secsTo(e->dtEnd()) : 0; + endOffsetValid = true; + } + offset = alarm->endOffset().asSeconds() + endOffset; + } + // Adjust the 'from' date/time and find the next recurrence at or after it + QDateTime pre = from.addSecs(-offset - 1); + if (e->doesFloat() && pre.time() < Preferences::startOfDay()) + pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come + dt = e->recurrence()->getNextDateTime(pre); + if (!dt.isValid()) + continue; + dt = dt.addSecs(offset); + } + } + else + dt = alarm->time(); + if (dt >= from && dt <= to) + { + kdDebug(5950) << "AlarmCalendar::events() '" << e->summary() + << "': " << dt.toString() << endl; + evnts.append(e); + break; + } + } + } + } + return evnts; +} diff --git a/kalarm/alarmcalendar.h b/kalarm/alarmcalendar.h new file mode 100644 index 000000000..87abc2d13 --- /dev/null +++ b/kalarm/alarmcalendar.h @@ -0,0 +1,114 @@ +/* + * alarmcalendar.h - KAlarm calendar file access + * Program: kalarm + * Copyright © 2001-2006 by 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. + */ + +#ifndef ALARMCALENDAR_H +#define ALARMCALENDAR_H + +#include +#include +#include +#include "alarmevent.h" + +class KConfig; + + +/** Provides read and write access to calendar files. + * Either vCalendar or iCalendar files may be read, but the calendar is saved + * only in iCalendar format to avoid information loss. + */ +class AlarmCalendar : public QObject +{ + Q_OBJECT + public: + virtual ~AlarmCalendar(); + bool valid() const { return mUrl.isValid(); } + KAEvent::Status type() const { return mType; } + bool open(); + int load(); + bool reload(); + bool save(); + void close(); + void startUpdate(); + bool endUpdate(); + KCal::Event* event(const QString& uniqueID); + KCal::Event::List events(); + KCal::Event::List eventsWithAlarms(const QDateTime& from, const QDateTime& to); + KCal::Event* addEvent(KAEvent&, bool useEventID = false); + void updateEvent(const KAEvent&); + bool deleteEvent(const QString& eventID, bool save = false); + void emitEmptyStatus(); + void purgeAll() { purge(0); } + void setPurgeDays(int days); + void purgeIfQueued(); // must only be called from KAlarmApp::processQueue() + bool isOpen() const { return mOpen; } + QString path() const { return mUrl.prettyURL(); } + QString urlString() const { return mUrl.url(); } + + static QString icalProductId(); + static bool initialiseCalendars(); + static void terminateCalendars(); + static AlarmCalendar* activeCalendar() { return mCalendars[ACTIVE]; } + static AlarmCalendar* expiredCalendar() { return mCalendars[EXPIRED]; } + static AlarmCalendar* displayCalendar() { return mCalendars[DISPLAY]; } + static AlarmCalendar* templateCalendar() { return mCalendars[TEMPLATE]; } + static AlarmCalendar* activeCalendarOpen() { return calendarOpen(ACTIVE); } + static AlarmCalendar* expiredCalendarOpen() { return calendarOpen(EXPIRED); } + static AlarmCalendar* displayCalendarOpen() { return calendarOpen(DISPLAY); } + static AlarmCalendar* templateCalendarOpen() { return calendarOpen(TEMPLATE); } + static bool importAlarms(QWidget*); + static const KCal::Event* getEvent(const QString& uniqueID); + + enum CalID { ACTIVE, EXPIRED, DISPLAY, TEMPLATE, NCALS }; + + signals: + void calendarSaved(AlarmCalendar*); + void purged(); + void emptyStatus(bool empty); + + private slots: + void slotPurge(); + + private: + AlarmCalendar(const QString& file, CalID, const QString& icalFile = QString::null, + const QString& configKey = QString::null); + bool create(); + bool saveCal(const QString& newFile = QString::null); + void purge(int daysToKeep); + void startPurgeTimer(); + static AlarmCalendar* createCalendar(CalID, KConfig*, QString& writePath, const QString& configKey = QString::null); + static AlarmCalendar* calendarOpen(CalID); + + static AlarmCalendar* mCalendars[NCALS]; // the calendars + + KCal::CalendarLocal* mCalendar; + KURL mUrl; // URL of current calendar file + KURL mICalUrl; // URL of iCalendar file + QString mLocalFile; // calendar file, or local copy if it's a remote file + QString mConfigKey; // config file key for this calendar's URL + KAEvent::Status mType; // what type of events the calendar file is for + int mPurgeDays; // how long to keep alarms, 0 = don't keep, -1 = keep indefinitely + bool mOpen; // true if the calendar file is open + int mPurgeDaysQueued; // >= 0 to purge the calendar when called from KAlarmApp::processLoop() + int mUpdateCount; // nesting level of group of calendar update calls + bool mUpdateSave; // save() was called while mUpdateCount > 0 + bool mVCal; // true if calendar file is in VCal format +}; + +#endif // ALARMCALENDAR_H diff --git a/kalarm/alarmevent.cpp b/kalarm/alarmevent.cpp new file mode 100644 index 000000000..28b0a55a4 --- /dev/null +++ b/kalarm/alarmevent.cpp @@ -0,0 +1,3488 @@ +/* + * alarmevent.cpp - represents calendar alarms and events + * Program: kalarm + * Copyright © 2001-2009 by 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 "kalarm.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include "alarmtext.h" +#include "functions.h" +#include "kalarmapp.h" +#include "kamail.h" +#include "preferences.h" +#include "alarmcalendar.h" +#include "alarmevent.h" +using namespace KCal; + + +const QCString APPNAME("KALARM"); + +// KAlarm version which first used the current calendar/event format. +// If this changes, KAEvent::convertKCalEvents() must be changed correspondingly. +// The string version is the KAlarm version string used in the calendar file. +QString KAEvent::calVersionString() { return QString::fromLatin1("1.5.0"); } +int KAEvent::calVersion() { return KAlarm::Version(1,5,0); } + +// Custom calendar properties. +// Note that all custom property names are prefixed with X-KDE-KALARM- in the calendar file. +// - Event properties +static const QCString NEXT_RECUR_PROPERTY("NEXTRECUR"); // X-KDE-KALARM-NEXTRECUR property +static const QCString REPEAT_PROPERTY("REPEAT"); // X-KDE-KALARM-REPEAT property +// - General alarm properties +static const QCString TYPE_PROPERTY("TYPE"); // X-KDE-KALARM-TYPE property +static const QString FILE_TYPE = QString::fromLatin1("FILE"); +static const QString AT_LOGIN_TYPE = QString::fromLatin1("LOGIN"); +static const QString REMINDER_TYPE = QString::fromLatin1("REMINDER"); +static const QString REMINDER_ONCE_TYPE = QString::fromLatin1("REMINDER_ONCE"); +static const QString ARCHIVE_REMINDER_ONCE_TYPE = QString::fromLatin1("ONCE"); +static const QString TIME_DEFERRAL_TYPE = QString::fromLatin1("DEFERRAL"); +static const QString DATE_DEFERRAL_TYPE = QString::fromLatin1("DATE_DEFERRAL"); +static const QString DISPLAYING_TYPE = QString::fromLatin1("DISPLAYING"); // used only in displaying calendar +static const QString PRE_ACTION_TYPE = QString::fromLatin1("PRE"); +static const QString POST_ACTION_TYPE = QString::fromLatin1("POST"); +static const QCString NEXT_REPEAT_PROPERTY("NEXTREPEAT"); // X-KDE-KALARM-NEXTREPEAT property +// - Display alarm properties +static const QCString FONT_COLOUR_PROPERTY("FONTCOLOR"); // X-KDE-KALARM-FONTCOLOR property +// - Email alarm properties +static const QCString EMAIL_ID_PROPERTY("EMAILID"); // X-KDE-KALARM-EMAILID property +// - Audio alarm properties +static const QCString VOLUME_PROPERTY("VOLUME"); // X-KDE-KALARM-VOLUME property +static const QCString SPEAK_PROPERTY("SPEAK"); // X-KDE-KALARM-SPEAK property + +// Event categories +static const QString DATE_ONLY_CATEGORY = QString::fromLatin1("DATE"); +static const QString EMAIL_BCC_CATEGORY = QString::fromLatin1("BCC"); +static const QString CONFIRM_ACK_CATEGORY = QString::fromLatin1("ACKCONF"); +static const QString LATE_CANCEL_CATEGORY = QString::fromLatin1("LATECANCEL;"); +static const QString AUTO_CLOSE_CATEGORY = QString::fromLatin1("LATECLOSE;"); +static const QString TEMPL_AFTER_TIME_CATEGORY = QString::fromLatin1("TMPLAFTTIME;"); +static const QString KMAIL_SERNUM_CATEGORY = QString::fromLatin1("KMAIL:"); +static const QString KORGANIZER_CATEGORY = QString::fromLatin1("KORG"); +static const QString DEFER_CATEGORY = QString::fromLatin1("DEFER;"); +static const QString ARCHIVE_CATEGORY = QString::fromLatin1("SAVE"); +static const QString ARCHIVE_CATEGORIES = QString::fromLatin1("SAVE:"); +static const QString LOG_CATEGORY = QString::fromLatin1("LOG:"); +static const QString xtermURL = QString::fromLatin1("xterm:"); + +// Event status strings +static const QString DISABLED_STATUS = QString::fromLatin1("DISABLED"); + +static const QString EXPIRED_UID = QString::fromLatin1("-exp-"); +static const QString DISPLAYING_UID = QString::fromLatin1("-disp-"); +static const QString TEMPLATE_UID = QString::fromLatin1("-tmpl-"); +static const QString KORGANIZER_UID = QString::fromLatin1("-korg-"); + +struct AlarmData +{ + const Alarm* alarm; + QString cleanText; // text or audio file name + uint emailFromId; + EmailAddressList emailAddresses; + QString emailSubject; + QStringList emailAttachments; + QFont font; + QColor bgColour, fgColour; + float soundVolume; + float fadeVolume; + int fadeSeconds; + int startOffsetSecs; + bool speak; + KAAlarm::SubType type; + KAAlarmEventBase::Type action; + int displayingFlags; + bool defaultFont; + bool reminderOnceOnly; + bool isEmailText; + bool commandScript; + int repeatCount; + int repeatInterval; + int nextRepeat; +}; +typedef QMap AlarmMap; + +static void setProcedureAlarm(Alarm*, const QString& commandLine); + + +/*============================================================================= += Class KAEvent += Corresponds to a KCal::Event instance. +=============================================================================*/ + +inline void KAEvent::set_deferral(DeferType type) +{ + if (type) + { + if (!mDeferral) + ++mAlarmCount; + } + else + { + if (mDeferral) + --mAlarmCount; + } + mDeferral = type; +} + +inline void KAEvent::set_reminder(int minutes) +{ + if (minutes && !mReminderMinutes) + ++mAlarmCount; + else if (!minutes && mReminderMinutes) + --mAlarmCount; + mReminderMinutes = minutes; + mArchiveReminderMinutes = 0; +} + +inline void KAEvent::set_archiveReminder() +{ + if (mReminderMinutes) + --mAlarmCount; + mArchiveReminderMinutes = mReminderMinutes; + mReminderMinutes = 0; +} + + +void KAEvent::copy(const KAEvent& event) +{ + KAAlarmEventBase::copy(event); + mTemplateName = event.mTemplateName; + mAudioFile = event.mAudioFile; + mPreAction = event.mPreAction; + mPostAction = event.mPostAction; + mStartDateTime = event.mStartDateTime; + mSaveDateTime = event.mSaveDateTime; + mAtLoginDateTime = event.mAtLoginDateTime; + mDeferralTime = event.mDeferralTime; + mDisplayingTime = event.mDisplayingTime; + mDisplayingFlags = event.mDisplayingFlags; + mReminderMinutes = event.mReminderMinutes; + mArchiveReminderMinutes = event.mArchiveReminderMinutes; + mDeferDefaultMinutes = event.mDeferDefaultMinutes; + mRevision = event.mRevision; + mAlarmCount = event.mAlarmCount; + mDeferral = event.mDeferral; + mLogFile = event.mLogFile; + mCommandXterm = event.mCommandXterm; + mKMailSerialNumber = event.mKMailSerialNumber; + mCopyToKOrganizer = event.mCopyToKOrganizer; + mReminderOnceOnly = event.mReminderOnceOnly; + mMainExpired = event.mMainExpired; + mArchiveRepeatAtLogin = event.mArchiveRepeatAtLogin; + mArchive = event.mArchive; + mTemplateAfterTime = event.mTemplateAfterTime; + mEnabled = event.mEnabled; + mUpdated = event.mUpdated; + delete mRecurrence; + if (event.mRecurrence) + mRecurrence = new KARecurrence(*event.mRecurrence); + else + mRecurrence = 0; +} + +/****************************************************************************** + * Initialise the KAEvent from a KCal::Event. + */ +void KAEvent::set(const Event& event) +{ + // Extract status from the event + mEventID = event.uid(); + mRevision = event.revision(); + mTemplateName = QString::null; + mLogFile = QString::null; + mTemplateAfterTime = -1; + mBeep = false; + mSpeak = false; + mEmailBcc = false; + mCommandXterm = false; + mCopyToKOrganizer = false; + mConfirmAck = false; + mArchive = false; + mReminderOnceOnly = false; + mAutoClose = false; + mArchiveRepeatAtLogin = false; + mArchiveReminderMinutes = 0; + mDeferDefaultMinutes = 0; + mLateCancel = 0; + mKMailSerialNumber = 0; + mBgColour = QColor(255, 255, 255); // missing/invalid colour - return white background + mFgColour = QColor(0, 0, 0); // and black foreground + mDefaultFont = true; + mEnabled = true; + clearRecur(); + bool ok; + bool dateOnly = false; + const QStringList cats = event.categories(); + for (unsigned int i = 0; i < cats.count(); ++i) + { + if (cats[i] == DATE_ONLY_CATEGORY) + dateOnly = true; + else if (cats[i] == CONFIRM_ACK_CATEGORY) + mConfirmAck = true; + else if (cats[i] == EMAIL_BCC_CATEGORY) + mEmailBcc = true; + else if (cats[i] == ARCHIVE_CATEGORY) + mArchive = true; + else if (cats[i] == KORGANIZER_CATEGORY) + mCopyToKOrganizer = true; + else if (cats[i].startsWith(KMAIL_SERNUM_CATEGORY)) + mKMailSerialNumber = cats[i].mid(KMAIL_SERNUM_CATEGORY.length()).toULong(); + else if (cats[i].startsWith(LOG_CATEGORY)) + { + QString logUrl = cats[i].mid(LOG_CATEGORY.length()); + if (logUrl == xtermURL) + mCommandXterm = true; + else + mLogFile = logUrl; + } + else if (cats[i].startsWith(ARCHIVE_CATEGORIES)) + { + // It's the archive flag plus a reminder time and/or repeat-at-login flag + mArchive = true; + QStringList list = QStringList::split(';', cats[i].mid(ARCHIVE_CATEGORIES.length())); + for (unsigned int j = 0; j < list.count(); ++j) + { + if (list[j] == AT_LOGIN_TYPE) + mArchiveRepeatAtLogin = true; + else if (list[j] == ARCHIVE_REMINDER_ONCE_TYPE) + mReminderOnceOnly = true; + else + { + char ch; + const char* cat = list[j].latin1(); + while ((ch = *cat) != 0 && (ch < '0' || ch > '9')) + ++cat; + if (ch) + { + mArchiveReminderMinutes = ch - '0'; + while ((ch = *++cat) >= '0' && ch <= '9') + mArchiveReminderMinutes = mArchiveReminderMinutes * 10 + ch - '0'; + switch (ch) + { + case 'M': break; + case 'H': mArchiveReminderMinutes *= 60; break; + case 'D': mArchiveReminderMinutes *= 1440; break; + } + } + } + } + } + else if (cats[i].startsWith(DEFER_CATEGORY)) + { + mDeferDefaultMinutes = static_cast(cats[i].mid(DEFER_CATEGORY.length()).toUInt(&ok)); + if (!ok) + mDeferDefaultMinutes = 0; // invalid parameter + } + else if (cats[i].startsWith(TEMPL_AFTER_TIME_CATEGORY)) + { + mTemplateAfterTime = static_cast(cats[i].mid(TEMPL_AFTER_TIME_CATEGORY.length()).toUInt(&ok)); + if (!ok) + mTemplateAfterTime = -1; // invalid parameter + } + else if (cats[i].startsWith(LATE_CANCEL_CATEGORY)) + { + mLateCancel = static_cast(cats[i].mid(LATE_CANCEL_CATEGORY.length()).toUInt(&ok)); + if (!ok || !mLateCancel) + mLateCancel = 1; // invalid parameter defaults to 1 minute + } + else if (cats[i].startsWith(AUTO_CLOSE_CATEGORY)) + { + mLateCancel = static_cast(cats[i].mid(AUTO_CLOSE_CATEGORY.length()).toUInt(&ok)); + if (!ok || !mLateCancel) + mLateCancel = 1; // invalid parameter defaults to 1 minute + mAutoClose = true; + } + } + QString prop = event.customProperty(APPNAME, REPEAT_PROPERTY); + if (!prop.isEmpty()) + { + // This property is used when the main alarm has expired + QStringList list = QStringList::split(':', prop); + if (list.count() >= 2) + { + int interval = static_cast(list[0].toUInt()); + int count = static_cast(list[1].toUInt()); + if (interval && count) + { + mRepeatInterval = interval; + mRepeatCount = count; + } + } + } + mNextMainDateTime = readDateTime(event, dateOnly, mStartDateTime); + mSaveDateTime = event.created(); + if (uidStatus() == TEMPLATE) + mTemplateName = event.summary(); + if (event.statusStr() == DISABLED_STATUS) + mEnabled = false; + + // Extract status from the event's alarms. + // First set up defaults. + mActionType = T_MESSAGE; + mMainExpired = true; + mRepeatAtLogin = false; + mDisplaying = false; + mRepeatSound = false; + mCommandScript = false; + mDeferral = NO_DEFERRAL; + mSoundVolume = -1; + mFadeVolume = -1; + mFadeSeconds = 0; + mReminderMinutes = 0; + mEmailFromIdentity = 0; + mText = ""; + mAudioFile = ""; + mPreAction = ""; + mPostAction = ""; + mEmailSubject = ""; + mEmailAddresses.clear(); + mEmailAttachments.clear(); + + // Extract data from all the event's alarms and index the alarms by sequence number + AlarmMap alarmMap; + readAlarms(event, &alarmMap); + + // Incorporate the alarms' details into the overall event + mAlarmCount = 0; // initialise as invalid + DateTime alTime; + bool set = false; + bool isEmailText = false; + bool setDeferralTime = false; + Duration deferralOffset; + for (AlarmMap::ConstIterator it = alarmMap.begin(); it != alarmMap.end(); ++it) + { + const AlarmData& data = it.data(); + DateTime dateTime = data.alarm->hasStartOffset() ? mNextMainDateTime.addSecs(data.alarm->startOffset().asSeconds()) : data.alarm->time(); + switch (data.type) + { + case KAAlarm::MAIN__ALARM: + mMainExpired = false; + alTime = dateTime; + alTime.setDateOnly(mStartDateTime.isDateOnly()); + if (data.repeatCount && data.repeatInterval) + { + mRepeatInterval = data.repeatInterval; // values may be adjusted in setRecurrence() + mRepeatCount = data.repeatCount; + mNextRepeat = data.nextRepeat; + } + break; + case KAAlarm::AT_LOGIN__ALARM: + mRepeatAtLogin = true; + mAtLoginDateTime = dateTime.rawDateTime(); + alTime = mAtLoginDateTime; + break; + case KAAlarm::REMINDER__ALARM: + mReminderMinutes = -(data.startOffsetSecs / 60); + if (mReminderMinutes) + mArchiveReminderMinutes = 0; + break; + case KAAlarm::DEFERRED_REMINDER_DATE__ALARM: + case KAAlarm::DEFERRED_DATE__ALARM: + mDeferral = (data.type == KAAlarm::DEFERRED_REMINDER_DATE__ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL; + mDeferralTime = dateTime; + mDeferralTime.setDateOnly(true); + if (data.alarm->hasStartOffset()) + deferralOffset = data.alarm->startOffset(); + break; + case KAAlarm::DEFERRED_REMINDER_TIME__ALARM: + case KAAlarm::DEFERRED_TIME__ALARM: + mDeferral = (data.type == KAAlarm::DEFERRED_REMINDER_TIME__ALARM) ? REMINDER_DEFERRAL : NORMAL_DEFERRAL; + mDeferralTime = dateTime; + if (data.alarm->hasStartOffset()) + deferralOffset = data.alarm->startOffset(); + break; + case KAAlarm::DISPLAYING__ALARM: + { + mDisplaying = true; + mDisplayingFlags = data.displayingFlags; + bool dateOnly = (mDisplayingFlags & DEFERRAL) ? !(mDisplayingFlags & TIMED_FLAG) + : mStartDateTime.isDateOnly(); + mDisplayingTime = dateTime; + mDisplayingTime.setDateOnly(dateOnly); + alTime = mDisplayingTime; + break; + } + case KAAlarm::AUDIO__ALARM: + mAudioFile = data.cleanText; + mSpeak = data.speak && mAudioFile.isEmpty(); + mBeep = !mSpeak && mAudioFile.isEmpty(); + mSoundVolume = (!mBeep && !mSpeak) ? data.soundVolume : -1; + mFadeVolume = (mSoundVolume >= 0 && data.fadeSeconds > 0) ? data.fadeVolume : -1; + mFadeSeconds = (mFadeVolume >= 0) ? data.fadeSeconds : 0; + mRepeatSound = (!mBeep && !mSpeak) && (data.repeatCount < 0); + break; + case KAAlarm::PRE_ACTION__ALARM: + mPreAction = data.cleanText; + break; + case KAAlarm::POST_ACTION__ALARM: + mPostAction = data.cleanText; + break; + case KAAlarm::INVALID__ALARM: + default: + break; + } + + if (data.reminderOnceOnly) + mReminderOnceOnly = true; + bool noSetNextTime = false; + switch (data.type) + { + case KAAlarm::DEFERRED_REMINDER_DATE__ALARM: + case KAAlarm::DEFERRED_DATE__ALARM: + case KAAlarm::DEFERRED_REMINDER_TIME__ALARM: + case KAAlarm::DEFERRED_TIME__ALARM: + if (!set) + { + // The recurrence has to be evaluated before we can + // calculate the time of a deferral alarm. + setDeferralTime = true; + noSetNextTime = true; + } + // fall through to AT_LOGIN__ALARM etc. + case KAAlarm::AT_LOGIN__ALARM: + case KAAlarm::REMINDER__ALARM: + case KAAlarm::DISPLAYING__ALARM: + if (!set && !noSetNextTime) + mNextMainDateTime = alTime; + // fall through to MAIN__ALARM + case KAAlarm::MAIN__ALARM: + // Ensure that the basic fields are set up even if there is no main + // alarm in the event (if it has expired and then been deferred) + if (!set) + { + mActionType = data.action; + mText = (mActionType == T_COMMAND) ? data.cleanText.stripWhiteSpace() : data.cleanText; + switch (data.action) + { + case T_MESSAGE: + mFont = data.font; + mDefaultFont = data.defaultFont; + if (data.isEmailText) + isEmailText = true; + // fall through to T_FILE + case T_FILE: + mBgColour = data.bgColour; + mFgColour = data.fgColour; + break; + case T_COMMAND: + mCommandScript = data.commandScript; + break; + case T_EMAIL: + mEmailFromIdentity = data.emailFromId; + mEmailAddresses = data.emailAddresses; + mEmailSubject = data.emailSubject; + mEmailAttachments = data.emailAttachments; + break; + default: + break; + } + set = true; + } + if (data.action == T_FILE && mActionType == T_MESSAGE) + mActionType = T_FILE; + ++mAlarmCount; + break; + case KAAlarm::AUDIO__ALARM: + case KAAlarm::PRE_ACTION__ALARM: + case KAAlarm::POST_ACTION__ALARM: + case KAAlarm::INVALID__ALARM: + default: + break; + } + } + if (!isEmailText) + mKMailSerialNumber = 0; + if (mRepeatAtLogin) + mArchiveRepeatAtLogin = false; + + Recurrence* recur = event.recurrence(); + if (recur && recur->doesRecur()) + { + int nextRepeat = mNextRepeat; // setRecurrence() clears mNextRepeat + setRecurrence(*recur); + if (nextRepeat <= mRepeatCount) + mNextRepeat = nextRepeat; + } + else + checkRepetition(); + + if (mMainExpired && deferralOffset.asSeconds() && checkRecur() != KARecurrence::NO_RECUR) + { + // Adjust the deferral time for an expired recurrence, since the + // offset is relative to the first actual occurrence. + DateTime dt = mRecurrence->getNextDateTime(mStartDateTime.dateTime().addDays(-1)); + dt.setDateOnly(mStartDateTime.isDateOnly()); + if (mDeferralTime.isDateOnly()) + { + mDeferralTime = dt.addSecs(deferralOffset.asSeconds()); + mDeferralTime.setDateOnly(true); + } + else + mDeferralTime = deferralOffset.end(dt.dateTime()); + } + if (mDeferral) + { + if (mNextMainDateTime == mDeferralTime) + mDeferral = CANCEL_DEFERRAL; // it's a cancelled deferral + if (setDeferralTime) + mNextMainDateTime = mDeferralTime; + } + + mUpdated = false; +} + +/****************************************************************************** +* Fetch the start and next date/time for a KCal::Event. +* Reply = next main date/time. +*/ +DateTime KAEvent::readDateTime(const Event& event, bool dateOnly, DateTime& start) +{ + start.set(event.dtStart(), dateOnly); + DateTime next = start; + QString prop = event.customProperty(APPNAME, NEXT_RECUR_PROPERTY); + if (prop.length() >= 8) + { + // The next due recurrence time is specified + QDate d(prop.left(4).toInt(), prop.mid(4,2).toInt(), prop.mid(6,2).toInt()); + if (d.isValid()) + { + if (dateOnly && prop.length() == 8) + next = d; + else if (!dateOnly && prop.length() == 15 && prop[8] == QChar('T')) + { + QTime t(prop.mid(9,2).toInt(), prop.mid(11,2).toInt(), prop.mid(13,2).toInt()); + if (t.isValid()) + next = QDateTime(d, t); + } + } + } + return next; +} + +/****************************************************************************** + * Parse the alarms for a KCal::Event. + * Reply = map of alarm data, indexed by KAAlarm::Type + */ +void KAEvent::readAlarms(const Event& event, void* almap) +{ + AlarmMap* alarmMap = (AlarmMap*)almap; + Alarm::List alarms = event.alarms(); + for (Alarm::List::ConstIterator it = alarms.begin(); it != alarms.end(); ++it) + { + // Parse the next alarm's text + AlarmData data; + readAlarm(**it, data); + if (data.type != KAAlarm::INVALID__ALARM) + alarmMap->insert(data.type, data); + } +} + +/****************************************************************************** + * Parse a KCal::Alarm. + * Reply = alarm ID (sequence number) + */ +void KAEvent::readAlarm(const Alarm& alarm, AlarmData& data) +{ + // Parse the next alarm's text + data.alarm = &alarm; + data.startOffsetSecs = alarm.startOffset().asSeconds(); // can have start offset but no valid date/time (e.g. reminder in template) + data.displayingFlags = 0; + data.isEmailText = false; + data.nextRepeat = 0; + data.repeatInterval = alarm.snoozeTime(); + data.repeatCount = alarm.repeatCount(); + if (data.repeatCount) + { + bool ok; + QString property = alarm.customProperty(APPNAME, NEXT_REPEAT_PROPERTY); + int n = static_cast(property.toUInt(&ok)); + if (ok) + data.nextRepeat = n; + } + switch (alarm.type()) + { + case Alarm::Procedure: + data.action = T_COMMAND; + data.cleanText = alarm.programFile(); + data.commandScript = data.cleanText.isEmpty(); // blank command indicates a script + if (!alarm.programArguments().isEmpty()) + { + if (!data.commandScript) + data.cleanText += ' '; + data.cleanText += alarm.programArguments(); + } + break; + case Alarm::Email: + data.action = T_EMAIL; + data.emailFromId = alarm.customProperty(APPNAME, EMAIL_ID_PROPERTY).toUInt(); + data.emailAddresses = alarm.mailAddresses(); + data.emailSubject = alarm.mailSubject(); + data.emailAttachments = alarm.mailAttachments(); + data.cleanText = alarm.mailText(); + break; + case Alarm::Display: + { + data.action = T_MESSAGE; + data.cleanText = AlarmText::fromCalendarText(alarm.text(), data.isEmailText); + QString property = alarm.customProperty(APPNAME, FONT_COLOUR_PROPERTY); + QStringList list = QStringList::split(QChar(';'), property, true); + data.bgColour = QColor(255, 255, 255); // white + data.fgColour = QColor(0, 0, 0); // black + int n = list.count(); + if (n > 0) + { + if (!list[0].isEmpty()) + { + QColor c(list[0]); + if (c.isValid()) + data.bgColour = c; + } + if (n > 1 && !list[1].isEmpty()) + { + QColor c(list[1]); + if (c.isValid()) + data.fgColour = c; + } + } + data.defaultFont = (n <= 2 || list[2].isEmpty()); + if (!data.defaultFont) + data.font.fromString(list[2]); + break; + } + case Alarm::Audio: + { + data.action = T_AUDIO; + data.cleanText = alarm.audioFile(); + data.type = KAAlarm::AUDIO__ALARM; + data.soundVolume = -1; + data.fadeVolume = -1; + data.fadeSeconds = 0; + data.speak = !alarm.customProperty(APPNAME, SPEAK_PROPERTY).isNull(); + QString property = alarm.customProperty(APPNAME, VOLUME_PROPERTY); + if (!property.isEmpty()) + { + bool ok; + float fadeVolume; + int fadeSecs = 0; + QStringList list = QStringList::split(QChar(';'), property, true); + data.soundVolume = list[0].toFloat(&ok); + if (!ok) + data.soundVolume = -1; + if (data.soundVolume >= 0 && list.count() >= 3) + { + fadeVolume = list[1].toFloat(&ok); + if (ok) + fadeSecs = static_cast(list[2].toUInt(&ok)); + if (ok && fadeVolume >= 0 && fadeSecs > 0) + { + data.fadeVolume = fadeVolume; + data.fadeSeconds = fadeSecs; + } + } + } + return; + } + case Alarm::Invalid: + data.type = KAAlarm::INVALID__ALARM; + return; + } + + bool atLogin = false; + bool reminder = false; + bool deferral = false; + bool dateDeferral = false; + data.reminderOnceOnly = false; + data.type = KAAlarm::MAIN__ALARM; + QString property = alarm.customProperty(APPNAME, TYPE_PROPERTY); + QStringList types = QStringList::split(QChar(','), property); + for (unsigned int i = 0; i < types.count(); ++i) + { + QString type = types[i]; + if (type == AT_LOGIN_TYPE) + atLogin = true; + else if (type == FILE_TYPE && data.action == T_MESSAGE) + data.action = T_FILE; + else if (type == REMINDER_TYPE) + reminder = true; + else if (type == REMINDER_ONCE_TYPE) + reminder = data.reminderOnceOnly = true; + else if (type == TIME_DEFERRAL_TYPE) + deferral = true; + else if (type == DATE_DEFERRAL_TYPE) + dateDeferral = deferral = true; + else if (type == DISPLAYING_TYPE) + data.type = KAAlarm::DISPLAYING__ALARM; + else if (type == PRE_ACTION_TYPE && data.action == T_COMMAND) + data.type = KAAlarm::PRE_ACTION__ALARM; + else if (type == POST_ACTION_TYPE && data.action == T_COMMAND) + data.type = KAAlarm::POST_ACTION__ALARM; + } + + if (reminder) + { + if (data.type == KAAlarm::MAIN__ALARM) + data.type = dateDeferral ? KAAlarm::DEFERRED_REMINDER_DATE__ALARM + : deferral ? KAAlarm::DEFERRED_REMINDER_TIME__ALARM : KAAlarm::REMINDER__ALARM; + else if (data.type == KAAlarm::DISPLAYING__ALARM) + data.displayingFlags = dateDeferral ? REMINDER | DATE_DEFERRAL + : deferral ? REMINDER | TIME_DEFERRAL : REMINDER; + } + else if (deferral) + { + if (data.type == KAAlarm::MAIN__ALARM) + data.type = dateDeferral ? KAAlarm::DEFERRED_DATE__ALARM : KAAlarm::DEFERRED_TIME__ALARM; + else if (data.type == KAAlarm::DISPLAYING__ALARM) + data.displayingFlags = dateDeferral ? DATE_DEFERRAL : TIME_DEFERRAL; + } + if (atLogin) + { + if (data.type == KAAlarm::MAIN__ALARM) + data.type = KAAlarm::AT_LOGIN__ALARM; + else if (data.type == KAAlarm::DISPLAYING__ALARM) + data.displayingFlags = REPEAT_AT_LOGIN; + } +//kdDebug(5950)<<"ReadAlarm(): text="<= 0) + { + mFadeVolume = (fadeSeconds > 0) ? fadeVolume : -1; + mFadeSeconds = (mFadeVolume >= 0) ? fadeSeconds : 0; + } + else + { + mFadeVolume = -1; + mFadeSeconds = 0; + } + mUpdated = true; +} + +void KAEvent::setReminder(int minutes, bool onceOnly) +{ + if (minutes != mReminderMinutes) + { + set_reminder(minutes); + mReminderOnceOnly = onceOnly; + mUpdated = true; + } +} + +/****************************************************************************** + * Return the time of the next scheduled occurrence of the event. + * Reminders and deferred reminders can optionally be ignored. + */ +DateTime KAEvent::displayDateTime() const +{ + DateTime dt = mainDateTime(true); + if (mDeferral > 0 && mDeferral != REMINDER_DEFERRAL) + { + if (mMainExpired) + return mDeferralTime; + return QMIN(mDeferralTime, dt); + } + return dt; +} + +/****************************************************************************** + * Convert a unique ID to indicate that the event is in a specified calendar file. + */ +QString KAEvent::uid(const QString& id, Status status) +{ + QString result = id; + Status oldStatus; + int i, len; + if ((i = result.find(EXPIRED_UID)) > 0) + { + oldStatus = EXPIRED; + len = EXPIRED_UID.length(); + } + else if ((i = result.find(DISPLAYING_UID)) > 0) + { + oldStatus = DISPLAYING; + len = DISPLAYING_UID.length(); + } + else if ((i = result.find(TEMPLATE_UID)) > 0) + { + oldStatus = TEMPLATE; + len = TEMPLATE_UID.length(); + } + else if ((i = result.find(KORGANIZER_UID)) > 0) + { + oldStatus = KORGANIZER; + len = KORGANIZER_UID.length(); + } + else + { + oldStatus = ACTIVE; + i = result.findRev('-'); + len = 1; + } + if (status != oldStatus && i > 0) + { + QString part; + switch (status) + { + case ACTIVE: part = "-"; break; + case EXPIRED: part = EXPIRED_UID; break; + case DISPLAYING: part = DISPLAYING_UID; break; + case TEMPLATE: part = TEMPLATE_UID; break; + case KORGANIZER: part = KORGANIZER_UID; break; + } + result.replace(i, len, part); + } + return result; +} + +/****************************************************************************** + * Get the calendar type for a unique ID. + */ +KAEvent::Status KAEvent::uidStatus(const QString& uid) +{ + if (uid.find(EXPIRED_UID) > 0) + return EXPIRED; + if (uid.find(DISPLAYING_UID) > 0) + return DISPLAYING; + if (uid.find(TEMPLATE_UID) > 0) + return TEMPLATE; + if (uid.find(KORGANIZER_UID) > 0) + return KORGANIZER; + return ACTIVE; +} + +int KAEvent::flags() const +{ + return KAAlarmEventBase::flags() + | (mStartDateTime.isDateOnly() ? ANY_TIME : 0) + | (mDeferral > 0 ? DEFERRAL : 0) + | (mCommandXterm ? EXEC_IN_XTERM : 0) + | (mCopyToKOrganizer ? COPY_KORGANIZER : 0) + | (mEnabled ? 0 : DISABLED); +} + +/****************************************************************************** + * Create a new Event from the KAEvent data. + */ +Event* KAEvent::event() const +{ + KCal::Event* ev = new KCal::Event; + ev->setUid(mEventID); + updateKCalEvent(*ev, false); + return ev; +} + +/****************************************************************************** + * Update an existing KCal::Event with the KAEvent data. + * If 'original' is true, the event start date/time is adjusted to its original + * value instead of its next occurrence, and the expired main alarm is + * reinstated. + */ +bool KAEvent::updateKCalEvent(Event& ev, bool checkUid, bool original, bool cancelCancelledDefer) const +{ + if (checkUid && !mEventID.isEmpty() && mEventID != ev.uid() + || !mAlarmCount && (!original || !mMainExpired)) + return false; + + checkRecur(); // ensure recurrence/repetition data is consistent + bool readOnly = ev.isReadOnly(); + ev.setReadOnly(false); + ev.setTransparency(Event::Transparent); + + // Set up event-specific data + + // Set up custom properties. + ev.removeCustomProperty(APPNAME, NEXT_RECUR_PROPERTY); + ev.removeCustomProperty(APPNAME, REPEAT_PROPERTY); + + QStringList cats; + if (mStartDateTime.isDateOnly()) + cats.append(DATE_ONLY_CATEGORY); + if (mConfirmAck) + cats.append(CONFIRM_ACK_CATEGORY); + if (mEmailBcc) + cats.append(EMAIL_BCC_CATEGORY); + if (mKMailSerialNumber) + cats.append(QString("%1%2").arg(KMAIL_SERNUM_CATEGORY).arg(mKMailSerialNumber)); + if (mCopyToKOrganizer) + cats.append(KORGANIZER_CATEGORY); + if (mCommandXterm) + cats.append(LOG_CATEGORY + xtermURL); + else if (!mLogFile.isEmpty()) + cats.append(LOG_CATEGORY + mLogFile); + if (mLateCancel) + cats.append(QString("%1%2").arg(mAutoClose ? AUTO_CLOSE_CATEGORY : LATE_CANCEL_CATEGORY).arg(mLateCancel)); + if (mDeferDefaultMinutes) + cats.append(QString("%1%2").arg(DEFER_CATEGORY).arg(mDeferDefaultMinutes)); + if (!mTemplateName.isEmpty() && mTemplateAfterTime >= 0) + cats.append(QString("%1%2").arg(TEMPL_AFTER_TIME_CATEGORY).arg(mTemplateAfterTime)); + if (mArchive && !original) + { + QStringList params; + if (mArchiveReminderMinutes) + { + if (mReminderOnceOnly) + params += ARCHIVE_REMINDER_ONCE_TYPE; + char unit = 'M'; + int count = mArchiveReminderMinutes; + if (count % 1440 == 0) + { + unit = 'D'; + count /= 1440; + } + else if (count % 60 == 0) + { + unit = 'H'; + count /= 60; + } + params += QString("%1%2").arg(count).arg(unit); + } + if (mArchiveRepeatAtLogin) + params += AT_LOGIN_TYPE; + if (params.count() > 0) + { + QString cat = ARCHIVE_CATEGORIES; + cat += params.join(QString::fromLatin1(";")); + cats.append(cat); + } + else + cats.append(ARCHIVE_CATEGORY); + } + ev.setCategories(cats); + ev.setCustomStatus(mEnabled ? QString::null : DISABLED_STATUS); + ev.setRevision(mRevision); + ev.clearAlarms(); + + // Always set DTSTART as date/time, since alarm times can only be specified + // in local time (instead of UTC) if they are relative to a DTSTART or DTEND + // which is also specified in local time. Instead of calling setFloats() to + // indicate a date-only event, the category "DATE" is included. + ev.setDtStart(mStartDateTime.dateTime()); + ev.setFloats(false); + ev.setHasEndDate(false); + + DateTime dtMain = original ? mStartDateTime : mNextMainDateTime; + int ancillaryType = 0; // 0 = invalid, 1 = time, 2 = offset + DateTime ancillaryTime; // time for ancillary alarms (audio, pre-action, etc) + int ancillaryOffset = 0; // start offset for ancillary alarms + if (!mMainExpired || original) + { + /* The alarm offset must always be zero for the main alarm. To determine + * which recurrence is due, the property X-KDE-KALARM_NEXTRECUR is used. + * If the alarm offset was non-zero, exception dates and rules would not + * work since they apply to the event time, not the alarm time. + */ + if (!original && checkRecur() != KARecurrence::NO_RECUR) + { + QDateTime dt = mNextMainDateTime.dateTime(); + ev.setCustomProperty(APPNAME, NEXT_RECUR_PROPERTY, + dt.toString(mNextMainDateTime.isDateOnly() ? "yyyyMMdd" : "yyyyMMddThhmmss")); + } + // Add the main alarm + initKCalAlarm(ev, 0, QStringList(), KAAlarm::MAIN_ALARM); + ancillaryOffset = 0; + ancillaryType = dtMain.isValid() ? 2 : 0; + } + else if (mRepeatCount && mRepeatInterval) + { + // Alarm repetition is normally held in the main alarm, but since + // the main alarm has expired, store in a custom property. + QString param = QString("%1:%2").arg(mRepeatInterval).arg(mRepeatCount); + ev.setCustomProperty(APPNAME, REPEAT_PROPERTY, param); + } + + // Add subsidiary alarms + if (mRepeatAtLogin || mArchiveRepeatAtLogin && original) + { + DateTime dtl; + if (mArchiveRepeatAtLogin) + dtl = mStartDateTime.dateTime().addDays(-1); + else if (mAtLoginDateTime.isValid()) + dtl = mAtLoginDateTime; + else if (mStartDateTime.isDateOnly()) + dtl = QDate::currentDate().addDays(-1); + else + dtl = QDateTime::currentDateTime(); + initKCalAlarm(ev, dtl, AT_LOGIN_TYPE); + if (!ancillaryType && dtl.isValid()) + { + ancillaryTime = dtl; + ancillaryType = 1; + } + } + if (mReminderMinutes || mArchiveReminderMinutes && original) + { + int minutes = mReminderMinutes ? mReminderMinutes : mArchiveReminderMinutes; + initKCalAlarm(ev, -minutes * 60, QStringList(mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE)); + if (!ancillaryType) + { + ancillaryOffset = -minutes * 60; + ancillaryType = 2; + } + } + if (mDeferral > 0 || mDeferral == CANCEL_DEFERRAL && !cancelCancelledDefer) + { + DateTime nextDateTime = mNextMainDateTime; + if (mMainExpired) + { + if (checkRecur() == KARecurrence::NO_RECUR) + nextDateTime = mStartDateTime; + else if (!original) + { + // It's a deferral of an expired recurrence. + // Need to ensure that the alarm offset is to an occurrence + // which isn't excluded by an exception - otherwise, it will + // never be triggered. So choose the first recurrence which + // isn't an exception. + nextDateTime = mRecurrence->getNextDateTime(mStartDateTime.dateTime().addDays(-1)); + nextDateTime.setDateOnly(mStartDateTime.isDateOnly()); + } + } + int startOffset; + QStringList list; + if (mDeferralTime.isDateOnly()) + { + startOffset = nextDateTime.secsTo(mDeferralTime.dateTime()); + list += DATE_DEFERRAL_TYPE; + } + else + { + startOffset = nextDateTime.dateTime().secsTo(mDeferralTime.dateTime()); + list += TIME_DEFERRAL_TYPE; + } + if (mDeferral == REMINDER_DEFERRAL) + list += mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE; + initKCalAlarm(ev, startOffset, list); + if (!ancillaryType && mDeferralTime.isValid()) + { + ancillaryOffset = startOffset; + ancillaryType = 2; + } + } + if (!mTemplateName.isEmpty()) + ev.setSummary(mTemplateName); + else if (mDisplaying) + { + QStringList list(DISPLAYING_TYPE); + if (mDisplayingFlags & REPEAT_AT_LOGIN) + list += AT_LOGIN_TYPE; + else if (mDisplayingFlags & DEFERRAL) + { + if (mDisplayingFlags & TIMED_FLAG) + list += TIME_DEFERRAL_TYPE; + else + list += DATE_DEFERRAL_TYPE; + } + if (mDisplayingFlags & REMINDER) + list += mReminderOnceOnly ? REMINDER_ONCE_TYPE : REMINDER_TYPE; + initKCalAlarm(ev, mDisplayingTime, list); + if (!ancillaryType && mDisplayingTime.isValid()) + { + ancillaryTime = mDisplayingTime; + ancillaryType = 1; + } + } + if (mBeep || mSpeak || !mAudioFile.isEmpty()) + { + // A sound is specified + if (ancillaryType == 2) + initKCalAlarm(ev, ancillaryOffset, QStringList(), KAAlarm::AUDIO_ALARM); + else + initKCalAlarm(ev, ancillaryTime, QStringList(), KAAlarm::AUDIO_ALARM); + } + if (!mPreAction.isEmpty()) + { + // A pre-display action is specified + if (ancillaryType == 2) + initKCalAlarm(ev, ancillaryOffset, QStringList(PRE_ACTION_TYPE), KAAlarm::PRE_ACTION_ALARM); + else + initKCalAlarm(ev, ancillaryTime, QStringList(PRE_ACTION_TYPE), KAAlarm::PRE_ACTION_ALARM); + } + if (!mPostAction.isEmpty()) + { + // A post-display action is specified + if (ancillaryType == 2) + initKCalAlarm(ev, ancillaryOffset, QStringList(POST_ACTION_TYPE), KAAlarm::POST_ACTION_ALARM); + else + initKCalAlarm(ev, ancillaryTime, QStringList(POST_ACTION_TYPE), KAAlarm::POST_ACTION_ALARM); + } + + if (mRecurrence) + mRecurrence->writeRecurrence(*ev.recurrence()); + else + ev.clearRecurrence(); + if (mSaveDateTime.isValid()) + ev.setCreated(mSaveDateTime); + ev.setReadOnly(readOnly); + return true; +} + +/****************************************************************************** + * Create a new alarm for a libkcal event, and initialise it according to the + * alarm action. If 'types' is non-null, it is appended to the X-KDE-KALARM-TYPE + * property value list. + */ +Alarm* KAEvent::initKCalAlarm(Event& event, const DateTime& dt, const QStringList& types, KAAlarm::Type type) const +{ + int startOffset = dt.isDateOnly() ? mStartDateTime.secsTo(dt) + : mStartDateTime.dateTime().secsTo(dt.dateTime()); + return initKCalAlarm(event, startOffset, types, type); +} + +Alarm* KAEvent::initKCalAlarm(Event& event, int startOffsetSecs, const QStringList& types, KAAlarm::Type type) const +{ + QStringList alltypes; + Alarm* alarm = event.newAlarm(); + alarm->setEnabled(true); + if (type != KAAlarm::MAIN_ALARM) + { + // RFC2445 specifies that absolute alarm times must be stored as UTC. + // So, in order to store local times, set the alarm time as an offset to DTSTART. + alarm->setStartOffset(startOffsetSecs); + } + + switch (type) + { + case KAAlarm::AUDIO_ALARM: + alarm->setAudioAlarm(mAudioFile); // empty for a beep or for speaking + if (mSpeak) + alarm->setCustomProperty(APPNAME, SPEAK_PROPERTY, QString::fromLatin1("Y")); + if (mRepeatSound) + { + alarm->setRepeatCount(-1); + alarm->setSnoozeTime(0); + } + if (!mAudioFile.isEmpty() && mSoundVolume >= 0) + alarm->setCustomProperty(APPNAME, VOLUME_PROPERTY, + QString::fromLatin1("%1;%2;%3").arg(QString::number(mSoundVolume, 'f', 2)) + .arg(QString::number(mFadeVolume, 'f', 2)) + .arg(mFadeSeconds)); + break; + case KAAlarm::PRE_ACTION_ALARM: + setProcedureAlarm(alarm, mPreAction); + break; + case KAAlarm::POST_ACTION_ALARM: + setProcedureAlarm(alarm, mPostAction); + break; + case KAAlarm::MAIN_ALARM: + alarm->setSnoozeTime(mRepeatInterval); + alarm->setRepeatCount(mRepeatCount); + if (mRepeatCount) + alarm->setCustomProperty(APPNAME, NEXT_REPEAT_PROPERTY, + QString::number(mNextRepeat)); + // fall through to INVALID_ALARM + case KAAlarm::INVALID_ALARM: + switch (mActionType) + { + case T_FILE: + alltypes += FILE_TYPE; + // fall through to T_MESSAGE + case T_MESSAGE: + alarm->setDisplayAlarm(AlarmText::toCalendarText(mText)); + alarm->setCustomProperty(APPNAME, FONT_COLOUR_PROPERTY, + QString::fromLatin1("%1;%2;%3").arg(mBgColour.name()) + .arg(mFgColour.name()) + .arg(mDefaultFont ? QString::null : mFont.toString())); + break; + case T_COMMAND: + if (mCommandScript) + alarm->setProcedureAlarm("", mText); + else + setProcedureAlarm(alarm, mText); + break; + case T_EMAIL: + alarm->setEmailAlarm(mEmailSubject, mText, mEmailAddresses, mEmailAttachments); + if (mEmailFromIdentity) + alarm->setCustomProperty(APPNAME, EMAIL_ID_PROPERTY, QString::number(mEmailFromIdentity)); + break; + case T_AUDIO: + break; + } + break; + case KAAlarm::REMINDER_ALARM: + case KAAlarm::DEFERRED_ALARM: + case KAAlarm::DEFERRED_REMINDER_ALARM: + case KAAlarm::AT_LOGIN_ALARM: + case KAAlarm::DISPLAYING_ALARM: + break; + } + alltypes += types; + if (alltypes.count() > 0) + alarm->setCustomProperty(APPNAME, TYPE_PROPERTY, alltypes.join(",")); + return alarm; +} + +/****************************************************************************** + * Return the alarm of the specified type. + */ +KAAlarm KAEvent::alarm(KAAlarm::Type type) const +{ + checkRecur(); // ensure recurrence/repetition data is consistent + KAAlarm al; // this sets type to INVALID_ALARM + if (mAlarmCount) + { + al.mEventID = mEventID; + al.mActionType = mActionType; + al.mText = mText; + al.mBgColour = mBgColour; + al.mFgColour = mFgColour; + al.mFont = mFont; + al.mDefaultFont = mDefaultFont; + al.mBeep = mBeep; + al.mSpeak = mSpeak; + al.mSoundVolume = mSoundVolume; + al.mFadeVolume = mFadeVolume; + al.mFadeSeconds = mFadeSeconds; + al.mRepeatSound = mRepeatSound; + al.mConfirmAck = mConfirmAck; + al.mRepeatCount = 0; + al.mRepeatInterval = 0; + al.mRepeatAtLogin = false; + al.mDeferred = false; + al.mLateCancel = mLateCancel; + al.mAutoClose = mAutoClose; + al.mEmailBcc = mEmailBcc; + al.mCommandScript = mCommandScript; + if (mActionType == T_EMAIL) + { + al.mEmailFromIdentity = mEmailFromIdentity; + al.mEmailAddresses = mEmailAddresses; + al.mEmailSubject = mEmailSubject; + al.mEmailAttachments = mEmailAttachments; + } + switch (type) + { + case KAAlarm::MAIN_ALARM: + if (!mMainExpired) + { + al.mType = KAAlarm::MAIN__ALARM; + al.mNextMainDateTime = mNextMainDateTime; + al.mRepeatCount = mRepeatCount; + al.mRepeatInterval = mRepeatInterval; + al.mNextRepeat = mNextRepeat; + } + break; + case KAAlarm::REMINDER_ALARM: + if (mReminderMinutes) + { + al.mType = KAAlarm::REMINDER__ALARM; + if (mReminderOnceOnly) + al.mNextMainDateTime = mStartDateTime.addMins(-mReminderMinutes); + else + al.mNextMainDateTime = mNextMainDateTime.addMins(-mReminderMinutes); + } + break; + case KAAlarm::DEFERRED_REMINDER_ALARM: + if (mDeferral != REMINDER_DEFERRAL) + break; + // fall through to DEFERRED_ALARM + case KAAlarm::DEFERRED_ALARM: + if (mDeferral > 0) + { + al.mType = static_cast((mDeferral == REMINDER_DEFERRAL ? KAAlarm::DEFERRED_REMINDER_ALARM : KAAlarm::DEFERRED_ALARM) + | (mDeferralTime.isDateOnly() ? 0 : KAAlarm::TIMED_DEFERRAL_FLAG)); + al.mNextMainDateTime = mDeferralTime; + al.mDeferred = true; + } + break; + case KAAlarm::AT_LOGIN_ALARM: + if (mRepeatAtLogin) + { + al.mType = KAAlarm::AT_LOGIN__ALARM; + al.mNextMainDateTime = mAtLoginDateTime; + al.mRepeatAtLogin = true; + al.mLateCancel = 0; + al.mAutoClose = false; + } + break; + case KAAlarm::DISPLAYING_ALARM: + if (mDisplaying) + { + al.mType = KAAlarm::DISPLAYING__ALARM; + al.mNextMainDateTime = mDisplayingTime; + al.mDisplaying = true; + } + break; + case KAAlarm::AUDIO_ALARM: + case KAAlarm::PRE_ACTION_ALARM: + case KAAlarm::POST_ACTION_ALARM: + case KAAlarm::INVALID_ALARM: + default: + break; + } + } + return al; +} + +/****************************************************************************** + * Return the main alarm for the event. + * If the main alarm does not exist, one of the subsidiary ones is returned if + * possible. + * N.B. a repeat-at-login alarm can only be returned if it has been read from/ + * written to the calendar file. + */ +KAAlarm KAEvent::firstAlarm() const +{ + if (mAlarmCount) + { + if (!mMainExpired) + return alarm(KAAlarm::MAIN_ALARM); + return nextAlarm(KAAlarm::MAIN_ALARM); + } + return KAAlarm(); +} + +/****************************************************************************** + * Return the next alarm for the event, after the specified alarm. + * N.B. a repeat-at-login alarm can only be returned if it has been read from/ + * written to the calendar file. + */ +KAAlarm KAEvent::nextAlarm(KAAlarm::Type prevType) const +{ + switch (prevType) + { + case KAAlarm::MAIN_ALARM: + if (mReminderMinutes) + return alarm(KAAlarm::REMINDER_ALARM); + // fall through to REMINDER_ALARM + case KAAlarm::REMINDER_ALARM: + // There can only be one deferral alarm + if (mDeferral == REMINDER_DEFERRAL) + return alarm(KAAlarm::DEFERRED_REMINDER_ALARM); + if (mDeferral == NORMAL_DEFERRAL) + return alarm(KAAlarm::DEFERRED_ALARM); + // fall through to DEFERRED_ALARM + case KAAlarm::DEFERRED_REMINDER_ALARM: + case KAAlarm::DEFERRED_ALARM: + if (mRepeatAtLogin) + return alarm(KAAlarm::AT_LOGIN_ALARM); + // fall through to AT_LOGIN_ALARM + case KAAlarm::AT_LOGIN_ALARM: + if (mDisplaying) + return alarm(KAAlarm::DISPLAYING_ALARM); + // fall through to DISPLAYING_ALARM + case KAAlarm::DISPLAYING_ALARM: + // fall through to default + case KAAlarm::AUDIO_ALARM: + case KAAlarm::PRE_ACTION_ALARM: + case KAAlarm::POST_ACTION_ALARM: + case KAAlarm::INVALID_ALARM: + default: + break; + } + return KAAlarm(); +} + +/****************************************************************************** + * Remove the alarm of the specified type from the event. + * This must only be called to remove an alarm which has expired, not to + * reconfigure the event. + */ +void KAEvent::removeExpiredAlarm(KAAlarm::Type type) +{ + int count = mAlarmCount; + switch (type) + { + case KAAlarm::MAIN_ALARM: + mAlarmCount = 0; // removing main alarm - also remove subsidiary alarms + break; + case KAAlarm::AT_LOGIN_ALARM: + if (mRepeatAtLogin) + { + // Remove the at-login alarm, but keep a note of it for archiving purposes + mArchiveRepeatAtLogin = true; + mRepeatAtLogin = false; + --mAlarmCount; + } + break; + case KAAlarm::REMINDER_ALARM: + // Remove any reminder alarm, but keep a note of it for archiving purposes + set_archiveReminder(); + break; + case KAAlarm::DEFERRED_REMINDER_ALARM: + case KAAlarm::DEFERRED_ALARM: + set_deferral(NO_DEFERRAL); + break; + case KAAlarm::DISPLAYING_ALARM: + if (mDisplaying) + { + mDisplaying = false; + --mAlarmCount; + } + break; + case KAAlarm::AUDIO_ALARM: + case KAAlarm::PRE_ACTION_ALARM: + case KAAlarm::POST_ACTION_ALARM: + case KAAlarm::INVALID_ALARM: + default: + break; + } + if (mAlarmCount != count) + mUpdated = true; +} + +/****************************************************************************** + * Defer the event to the specified time. + * If the main alarm time has passed, the main alarm is marked as expired. + * If 'adjustRecurrence' is true, ensure that the next scheduled recurrence is + * after the current time. + * Reply = true if a repetition has been deferred. + */ +bool KAEvent::defer(const DateTime& dateTime, bool reminder, bool adjustRecurrence) +{ + bool result = false; + bool setNextRepetition = false; + bool checkRepetition = false; + cancelCancelledDeferral(); + if (checkRecur() == KARecurrence::NO_RECUR) + { + if (mReminderMinutes || mDeferral == REMINDER_DEFERRAL || mArchiveReminderMinutes) + { + if (dateTime < mNextMainDateTime.dateTime()) + { + set_deferral(REMINDER_DEFERRAL); // defer reminder alarm + mDeferralTime = dateTime; + } + else + { + // Deferring past the main alarm time, so adjust any existing deferral + if (mReminderMinutes || mDeferral == REMINDER_DEFERRAL) + set_deferral(NO_DEFERRAL); + } + // Remove any reminder alarm, but keep a note of it for archiving purposes + if (mReminderMinutes) + set_archiveReminder(); + } + if (mDeferral != REMINDER_DEFERRAL) + { + // We're deferring the main alarm, not a reminder + if (mRepeatCount && mRepeatInterval && dateTime < mainEndRepeatTime()) + { + // The alarm is repeated, and we're deferring to a time before the last repetition + set_deferral(NORMAL_DEFERRAL); + mDeferralTime = dateTime; + result = true; + setNextRepetition = true; + } + else + { + // Main alarm has now expired + mNextMainDateTime = mDeferralTime = dateTime; + set_deferral(NORMAL_DEFERRAL); + if (!mMainExpired) + { + // Mark the alarm as expired now + mMainExpired = true; + --mAlarmCount; + if (mRepeatAtLogin) + { + // Remove the repeat-at-login alarm, but keep a note of it for archiving purposes + mArchiveRepeatAtLogin = true; + mRepeatAtLogin = false; + --mAlarmCount; + } + } + } + } + } + else if (reminder) + { + // Deferring a reminder for a recurring alarm + if (dateTime >= mNextMainDateTime.dateTime()) + set_deferral(NO_DEFERRAL); // (error) + else + { + set_deferral(REMINDER_DEFERRAL); + mDeferralTime = dateTime; + checkRepetition = true; + } + } + else + { + mDeferralTime = dateTime; + if (mDeferral <= 0) + set_deferral(NORMAL_DEFERRAL); + if (adjustRecurrence) + { + QDateTime now = QDateTime::currentDateTime(); + if (mainEndRepeatTime() < now) + { + // The last repetition (if any) of the current recurrence has already passed. + // Adjust to the next scheduled recurrence after now. + if (!mMainExpired && setNextOccurrence(now) == NO_OCCURRENCE) + { + mMainExpired = true; + --mAlarmCount; + } + } + else + setNextRepetition = (mRepeatCount && mRepeatInterval); + } + else + checkRepetition = true; + } + if (checkRepetition) + setNextRepetition = (mRepeatCount && mRepeatInterval && mDeferralTime < mainEndRepeatTime()); + if (setNextRepetition) + { + // The alarm is repeated, and we're deferring to a time before the last repetition. + // Set the next scheduled repetition to the one after the deferral. + mNextRepeat = (mNextMainDateTime < mDeferralTime) + ? mNextMainDateTime.secsTo(mDeferralTime) / (mRepeatInterval * 60) + 1 : 0; + } + mUpdated = true; + return result; +} + +/****************************************************************************** + * Cancel any deferral alarm. + */ +void KAEvent::cancelDefer() +{ + if (mDeferral > 0) + { + // Set the deferral time to be the same as the next recurrence/repetition. + // This prevents an immediate retriggering of the alarm. + if (mMainExpired + || nextOccurrence(QDateTime::currentDateTime(), mDeferralTime, RETURN_REPETITION) == NO_OCCURRENCE) + { + // The main alarm has expired, so simply delete the deferral + mDeferralTime = DateTime(); + set_deferral(NO_DEFERRAL); + } + else + set_deferral(CANCEL_DEFERRAL); + mUpdated = true; + } +} + +/****************************************************************************** + * Cancel any cancelled deferral alarm. + */ +void KAEvent::cancelCancelledDeferral() +{ + if (mDeferral == CANCEL_DEFERRAL) + { + mDeferralTime = DateTime(); + set_deferral(NO_DEFERRAL); + } +} + +/****************************************************************************** +* Find the latest time which the alarm can currently be deferred to. +*/ +DateTime KAEvent::deferralLimit(KAEvent::DeferLimitType* limitType) const +{ + DeferLimitType ltype; + DateTime endTime; + bool recurs = (checkRecur() != KARecurrence::NO_RECUR); + if (recurs || mRepeatCount) + { + // It's a repeated alarm. Don't allow it to be deferred past its + // next occurrence or repetition. + DateTime reminderTime; + QDateTime now = QDateTime::currentDateTime(); + OccurType type = nextOccurrence(now, endTime, RETURN_REPETITION); + if (type & OCCURRENCE_REPEAT) + ltype = LIMIT_REPETITION; + else if (type == NO_OCCURRENCE) + ltype = LIMIT_NONE; + else if (mReminderMinutes && (now < (reminderTime = endTime.addMins(-mReminderMinutes)))) + { + endTime = reminderTime; + ltype = LIMIT_REMINDER; + } + else if (type == FIRST_OR_ONLY_OCCURRENCE && !recurs) + ltype = LIMIT_REPETITION; + else + ltype = LIMIT_RECURRENCE; + } + else if ((mReminderMinutes || mDeferral == REMINDER_DEFERRAL || mArchiveReminderMinutes) + && QDateTime::currentDateTime() < mNextMainDateTime.dateTime()) + { + // It's an reminder alarm. Don't allow it to be deferred past its main alarm time. + endTime = mNextMainDateTime; + ltype = LIMIT_REMINDER; + } + else + ltype = LIMIT_NONE; + if (ltype != LIMIT_NONE) + endTime = endTime.addMins(-1); + if (limitType) + *limitType = ltype; + return endTime; +} + +/****************************************************************************** + * Set the event to be a copy of the specified event, making the specified + * alarm the 'displaying' alarm. + * The purpose of setting up a 'displaying' alarm is to be able to reinstate + * the alarm message in case of a crash, or to reinstate it should the user + * choose to defer the alarm. Note that even repeat-at-login alarms need to be + * saved in case their end time expires before the next login. + * Reply = true if successful, false if alarm was not copied. + */ +bool KAEvent::setDisplaying(const KAEvent& event, KAAlarm::Type alarmType, const QDateTime& repeatAtLoginTime) +{ + if (!mDisplaying + && (alarmType == KAAlarm::MAIN_ALARM + || alarmType == KAAlarm::REMINDER_ALARM + || alarmType == KAAlarm::DEFERRED_REMINDER_ALARM + || alarmType == KAAlarm::DEFERRED_ALARM + || alarmType == KAAlarm::AT_LOGIN_ALARM)) + { +//kdDebug(5950)<<"KAEvent::setDisplaying("<frequency(); + switch (mRecurrence->defaultRRuleConst()->recurrenceType()) + { + case RecurrenceRule::rMinutely: + if (frequency < 60) + return i18n("1 Minute", "%n Minutes", frequency); + else if (frequency % 60 == 0) + return i18n("1 Hour", "%n Hours", frequency/60); + else + { + QString mins; + return i18n("Hours and Minutes", "%1H %2M").arg(QString::number(frequency/60)).arg(mins.sprintf("%02d", frequency%60)); + } + case RecurrenceRule::rDaily: + return i18n("1 Day", "%n Days", frequency); + case RecurrenceRule::rWeekly: + return i18n("1 Week", "%n Weeks", frequency); + case RecurrenceRule::rMonthly: + return i18n("1 Month", "%n Months", frequency); + case RecurrenceRule::rYearly: + return i18n("1 Year", "%n Years", frequency); + case RecurrenceRule::rNone: + default: + break; + } + } + return brief ? QString::null : i18n("None"); +} + +/****************************************************************************** + * Return the repetition interval as text suitable for display. + */ +QString KAEvent::repetitionText(bool brief) const +{ + if (mRepeatCount) + { + if (mRepeatInterval % 1440) + { + if (mRepeatInterval < 60) + return i18n("1 Minute", "%n Minutes", mRepeatInterval); + if (mRepeatInterval % 60 == 0) + return i18n("1 Hour", "%n Hours", mRepeatInterval/60); + QString mins; + return i18n("Hours and Minutes", "%1H %2M").arg(QString::number(mRepeatInterval/60)).arg(mins.sprintf("%02d", mRepeatInterval%60)); + } + if (mRepeatInterval % (7*1440)) + return i18n("1 Day", "%n Days", mRepeatInterval/1440); + return i18n("1 Week", "%n Weeks", mRepeatInterval/(7*1440)); + } + return brief ? QString::null : i18n("None"); +} + +/****************************************************************************** + * Adjust the event date/time to the first recurrence of the event, on or after + * start date/time. The event start date may not be a recurrence date, in which + * case a later date will be set. + */ +void KAEvent::setFirstRecurrence() +{ + switch (checkRecur()) + { + case KARecurrence::NO_RECUR: + case KARecurrence::MINUTELY: + return; + case KARecurrence::ANNUAL_DATE: + case KARecurrence::ANNUAL_POS: + if (mRecurrence->yearMonths().isEmpty()) + return; // (presumably it's a template) + break; + case KARecurrence::DAILY: + case KARecurrence::WEEKLY: + case KARecurrence::MONTHLY_POS: + case KARecurrence::MONTHLY_DAY: + break; + } + QDateTime recurStart = mRecurrence->startDateTime(); + if (mRecurrence->recursOn(recurStart.date())) + return; // it already recurs on the start date + + // Set the frequency to 1 to find the first possible occurrence + int frequency = mRecurrence->frequency(); + mRecurrence->setFrequency(1); + DateTime next; + nextRecurrence(mNextMainDateTime.dateTime(), next); + if (!next.isValid()) + mRecurrence->setStartDateTime(recurStart); // reinstate the old value + else + { + mRecurrence->setStartDateTime(next.dateTime()); + mStartDateTime = mNextMainDateTime = next; + mUpdated = true; + } + mRecurrence->setFrequency(frequency); // restore the frequency +} + +/****************************************************************************** +* Initialise the event's recurrence from a KCal::Recurrence. +* The event's start date/time is not changed. +*/ +void KAEvent::setRecurrence(const KARecurrence& recurrence) +{ + mUpdated = true; + delete mRecurrence; + if (recurrence.doesRecur()) + { + mRecurrence = new KARecurrence(recurrence); + mRecurrence->setStartDateTime(mStartDateTime.dateTime()); + mRecurrence->setFloats(mStartDateTime.isDateOnly()); + } + else + mRecurrence = 0; + + // Adjust sub-repetition values to fit the recurrence + setRepetition(mRepeatInterval, mRepeatCount); +} + +/****************************************************************************** +* Initialise the event's sub-repetition. +* The repetition length is adjusted if necessary to fit any recurrence interval. +* Reply = false if a non-daily interval was specified for a date-only recurrence. +*/ +bool KAEvent::setRepetition(int interval, int count) +{ + mUpdated = true; + mRepeatInterval = 0; + mRepeatCount = 0; + mNextRepeat = 0; + if (interval > 0 && count > 0 && !mRepeatAtLogin) + { + Q_ASSERT(checkRecur() != KARecurrence::NO_RECUR); + if (interval % 1440 && mStartDateTime.isDateOnly()) + return false; // interval must be in units of days for date-only alarms + if (checkRecur() != KARecurrence::NO_RECUR) + { + int longestInterval = mRecurrence->longestInterval() - 1; + if (interval * count > longestInterval) + count = longestInterval / interval; + } + mRepeatInterval = interval; + mRepeatCount = count; + } + return true; +} + +/****************************************************************************** + * Set the recurrence to recur at a minutes interval. + * Parameters: + * freq = how many minutes between recurrences. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date/time (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurMinutely(int freq, int count, const QDateTime& end) +{ + return setRecur(RecurrenceRule::rMinutely, freq, count, end); +} + +/****************************************************************************** + * Set the recurrence to recur daily. + * Parameters: + * freq = how many days between recurrences. + * days = which days of the week alarms are allowed to occur on. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurDaily(int freq, const QBitArray& days, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rDaily, freq, count, end)) + return false; + int n = 0; + for (int i = 0; i < 7; ++i) + { + if (days.testBit(i)) + ++n; + } + if (n < 7) + mRecurrence->addWeeklyDays(days); + return true; +} + +/****************************************************************************** + * Set the recurrence to recur weekly, on the specified weekdays. + * Parameters: + * freq = how many weeks between recurrences. + * days = which days of the week alarms should occur on. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurWeekly(int freq, const QBitArray& days, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rWeekly, freq, count, end)) + return false; + mRecurrence->addWeeklyDays(days); + return true; +} + +/****************************************************************************** + * Set the recurrence to recur monthly, on the specified days within the month. + * Parameters: + * freq = how many months between recurrences. + * days = which days of the month alarms should occur on. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurMonthlyByDate(int freq, const QValueList& days, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rMonthly, freq, count, end)) + return false; + for (QValueListConstIterator it = days.begin(); it != days.end(); ++it) + mRecurrence->addMonthlyDate(*it); + return true; +} + +/****************************************************************************** + * Set the recurrence to recur monthly, on the specified weekdays in the + * specified weeks of the month. + * Parameters: + * freq = how many months between recurrences. + * posns = which days of the week/weeks of the month alarms should occur on. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurMonthlyByPos(int freq, const QValueList& posns, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rMonthly, freq, count, end)) + return false; + for (QValueListConstIterator it = posns.begin(); it != posns.end(); ++it) + mRecurrence->addMonthlyPos((*it).weeknum, (*it).days); + return true; +} + +/****************************************************************************** + * Set the recurrence to recur annually, on the specified start date in each + * of the specified months. + * Parameters: + * freq = how many years between recurrences. + * months = which months of the year alarms should occur on. + * day = day of month, or 0 to use start date + * feb29 = when February 29th should recur in non-leap years. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurAnnualByDate(int freq, const QValueList& months, int day, KARecurrence::Feb29Type feb29, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rYearly, freq, count, end, feb29)) + return false; + for (QValueListConstIterator it = months.begin(); it != months.end(); ++it) + mRecurrence->addYearlyMonth(*it); + if (day) + mRecurrence->addMonthlyDate(day); + return true; +} + +/****************************************************************************** + * Set the recurrence to recur annually, on the specified weekdays in the + * specified weeks of the specified months. + * Parameters: + * freq = how many years between recurrences. + * posns = which days of the week/weeks of the month alarms should occur on. + * months = which months of the year alarms should occur on. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecurAnnualByPos(int freq, const QValueList& posns, const QValueList& months, int count, const QDate& end) +{ + if (!setRecur(RecurrenceRule::rYearly, freq, count, end)) + return false; + for (QValueListConstIterator it = months.begin(); it != months.end(); ++it) + mRecurrence->addYearlyMonth(*it); + for (QValueListConstIterator it = posns.begin(); it != posns.end(); ++it) + mRecurrence->addYearlyPos((*it).weeknum, (*it).days); + return true; +} + +/****************************************************************************** + * Initialise the event's recurrence data. + * Parameters: + * freq = how many intervals between recurrences. + * count = number of occurrences, including first and last. + * = -1 to recur indefinitely. + * = 0 to use 'end' instead. + * end = end date/time (invalid to use 'count' instead). + * Reply = false if no recurrence was set up. + */ +bool KAEvent::setRecur(RecurrenceRule::PeriodType recurType, int freq, int count, const QDateTime& end, KARecurrence::Feb29Type feb29) +{ + if (count >= -1 && (count || end.date().isValid())) + { + if (!mRecurrence) + mRecurrence = new KARecurrence; + if (mRecurrence->init(recurType, freq, count, mNextMainDateTime, end, feb29)) + { + mUpdated = true; + return true; + } + } + clearRecur(); + return false; +} + +/****************************************************************************** + * Clear the event's recurrence and alarm repetition data. + */ +void KAEvent::clearRecur() +{ + delete mRecurrence; + mRecurrence = 0; + mRepeatInterval = 0; + mRepeatCount = 0; + mNextRepeat = 0; + mUpdated = true; +} + +/****************************************************************************** +* Validate the event's recurrence data, correcting any inconsistencies (which +* should never occur!). +* Reply = true if a recurrence (as opposed to a login repetition) exists. +*/ +KARecurrence::Type KAEvent::checkRecur() const +{ + if (mRecurrence) + { + KARecurrence::Type type = mRecurrence->type(); + switch (type) + { + case KARecurrence::MINUTELY: // hourly + case KARecurrence::DAILY: // daily + case KARecurrence::WEEKLY: // weekly on multiple days of week + case KARecurrence::MONTHLY_DAY: // monthly on multiple dates in month + case KARecurrence::MONTHLY_POS: // monthly on multiple nth day of week + case KARecurrence::ANNUAL_DATE: // annually on multiple months (day of month = start date) + case KARecurrence::ANNUAL_POS: // annually on multiple nth day of week in multiple months + return type; + default: + if (mRecurrence) + const_cast(this)->clearRecur(); // recurrence shouldn't exist!! + break; + } + } + return KARecurrence::NO_RECUR; +} + + +/****************************************************************************** + * Return the recurrence interval in units of the recurrence period type. + */ +int KAEvent::recurInterval() const +{ + if (mRecurrence) + { + switch (mRecurrence->type()) + { + case KARecurrence::MINUTELY: + case KARecurrence::DAILY: + case KARecurrence::WEEKLY: + case KARecurrence::MONTHLY_DAY: + case KARecurrence::MONTHLY_POS: + case KARecurrence::ANNUAL_DATE: + case KARecurrence::ANNUAL_POS: + return mRecurrence->frequency(); + default: + break; + } + } + return 0; +} + +/****************************************************************************** +* Validate the event's alarm sub-repetition data, correcting any +* inconsistencies (which should never occur!). +*/ +void KAEvent::checkRepetition() const +{ + if (mRepeatCount && !mRepeatInterval) + const_cast(this)->mRepeatCount = 0; + if (!mRepeatCount && mRepeatInterval) + const_cast(this)->mRepeatInterval = 0; +} + +#if 0 +/****************************************************************************** + * Convert a QValueList to QValueList. + */ +QValueList KAEvent::convRecurPos(const QValueList& wdaypos) +{ + QValueList mposns; + for (QValueList::ConstIterator it = wdaypos.begin(); it != wdaypos.end(); ++it) + { + int daybit = (*it).day() - 1; + int weeknum = (*it).pos(); + bool found = false; + for (QValueList::Iterator mit = mposns.begin(); mit != mposns.end(); ++mit) + { + if ((*mit).weeknum == weeknum) + { + (*mit).days.setBit(daybit); + found = true; + break; + } + } + if (!found) + { + MonthPos mpos; + mpos.days.fill(false); + mpos.days.setBit(daybit); + mpos.weeknum = weeknum; + mposns.append(mpos); + } + } + return mposns; +} +#endif + +/****************************************************************************** + * Find the alarm template with the specified name. + * Reply = invalid event if not found. + */ +KAEvent KAEvent::findTemplateName(AlarmCalendar& calendar, const QString& name) +{ + KAEvent event; + Event::List events = calendar.events(); + for (Event::List::ConstIterator evit = events.begin(); evit != events.end(); ++evit) + { + Event* ev = *evit; + if (ev->summary() == name) + { + event.set(*ev); + if (!event.isTemplate()) + return KAEvent(); // this shouldn't ever happen + break; + } + } + return event; +} + +/****************************************************************************** + * Adjust the time at which date-only events will occur for each of the events + * in a list. Events for which both date and time are specified are left + * unchanged. + * Reply = true if any events have been updated. + */ +bool KAEvent::adjustStartOfDay(const Event::List& events) +{ + bool changed = false; + QTime startOfDay = Preferences::startOfDay(); + for (Event::List::ConstIterator evit = events.begin(); evit != events.end(); ++evit) + { + Event* event = *evit; + const QStringList cats = event->categories(); + if (cats.find(DATE_ONLY_CATEGORY) != cats.end()) + { + // It's an untimed event, so fix it + QTime oldTime = event->dtStart().time(); + int adjustment = oldTime.secsTo(startOfDay); + if (adjustment) + { + event->setDtStart(QDateTime(event->dtStart().date(), startOfDay)); + Alarm::List alarms = event->alarms(); + int deferralOffset = 0; + for (Alarm::List::ConstIterator alit = alarms.begin(); alit != alarms.end(); ++alit) + { + // Parse the next alarm's text + Alarm& alarm = **alit; + AlarmData data; + readAlarm(alarm, data); + if (data.type & KAAlarm::TIMED_DEFERRAL_FLAG) + { + // Timed deferral alarm, so adjust the offset + deferralOffset = alarm.startOffset().asSeconds(); + alarm.setStartOffset(deferralOffset - adjustment); + } + else if (data.type == KAAlarm::AUDIO__ALARM + && alarm.startOffset().asSeconds() == deferralOffset) + { + // Audio alarm is set for the same time as the deferral alarm + alarm.setStartOffset(deferralOffset - adjustment); + } + } + changed = true; + } + } + else + { + // It's a timed event. Fix any untimed alarms. + int deferralOffset = 0; + int newDeferralOffset = 0; + DateTime start; + QDateTime nextMainDateTime = readDateTime(*event, false, start).rawDateTime(); + AlarmMap alarmMap; + readAlarms(*event, &alarmMap); + for (AlarmMap::Iterator it = alarmMap.begin(); it != alarmMap.end(); ++it) + { + const AlarmData& data = it.data(); + if (!data.alarm->hasStartOffset()) + continue; + if ((data.type & KAAlarm::DEFERRED_ALARM) + && !(data.type & KAAlarm::TIMED_DEFERRAL_FLAG)) + { + // Date-only deferral alarm, so adjust its time + QDateTime altime = nextMainDateTime.addSecs(data.alarm->startOffset().asSeconds()); + altime.setTime(startOfDay); + deferralOffset = data.alarm->startOffset().asSeconds(); + newDeferralOffset = event->dtStart().secsTo(altime); + const_cast(data.alarm)->setStartOffset(newDeferralOffset); + changed = true; + } + else if (data.type == KAAlarm::AUDIO__ALARM + && data.alarm->startOffset().asSeconds() == deferralOffset) + { + // Audio alarm is set for the same time as the deferral alarm + const_cast(data.alarm)->setStartOffset(newDeferralOffset); + changed = true; + } + } + } + } + return changed; +} + +/****************************************************************************** + * If the calendar was written by a previous version of KAlarm, do any + * necessary format conversions on the events to ensure that when the calendar + * is saved, no information is lost or corrupted. + */ +void KAEvent::convertKCalEvents(KCal::Calendar& calendar, int version, bool adjustSummerTime) +{ + // KAlarm pre-0.9 codes held in the alarm's DESCRIPTION property + static const QChar SEPARATOR = ';'; + static const QChar LATE_CANCEL_CODE = 'C'; + static const QChar AT_LOGIN_CODE = 'L'; // subsidiary alarm at every login + static const QChar DEFERRAL_CODE = 'D'; // extra deferred alarm + static const QString TEXT_PREFIX = QString::fromLatin1("TEXT:"); + static const QString FILE_PREFIX = QString::fromLatin1("FILE:"); + static const QString COMMAND_PREFIX = QString::fromLatin1("CMD:"); + + // KAlarm pre-0.9.2 codes held in the event's CATEGORY property + static const QString BEEP_CATEGORY = QString::fromLatin1("BEEP"); + + // KAlarm pre-1.1.1 LATECANCEL category with no parameter + static const QString LATE_CANCEL_CAT = QString::fromLatin1("LATECANCEL"); + + // KAlarm pre-1.3.0 TMPLDEFTIME category with no parameter + static const QString TEMPL_DEF_TIME_CAT = QString::fromLatin1("TMPLDEFTIME"); + + // KAlarm pre-1.3.1 XTERM category + static const QString EXEC_IN_XTERM_CAT = QString::fromLatin1("XTERM"); + + // KAlarm pre-1.4.22 properties + static const QCString KMAIL_ID_PROPERTY("KMAILID"); // X-KDE-KALARM-KMAILID property + + if (version >= calVersion()) + return; + + kdDebug(5950) << "KAEvent::convertKCalEvents(): adjusting version " << version << endl; + bool pre_0_7 = (version < KAlarm::Version(0,7,0)); + bool pre_0_9 = (version < KAlarm::Version(0,9,0)); + bool pre_0_9_2 = (version < KAlarm::Version(0,9,2)); + bool pre_1_1_1 = (version < KAlarm::Version(1,1,1)); + bool pre_1_2_1 = (version < KAlarm::Version(1,2,1)); + bool pre_1_3_0 = (version < KAlarm::Version(1,3,0)); + bool pre_1_3_1 = (version < KAlarm::Version(1,3,1)); + bool pre_1_4_14 = (version < KAlarm::Version(1,4,14)); + bool pre_1_5_0 = (version < KAlarm::Version(1,5,0)); + Q_ASSERT(calVersion() == KAlarm::Version(1,5,0)); + + QDateTime dt0(QDate(1970,1,1), QTime(0,0,0)); + QTime startOfDay = Preferences::startOfDay(); + + Event::List events = calendar.rawEvents(); + for (Event::List::ConstIterator evit = events.begin(); evit != events.end(); ++evit) + { + Event* event = *evit; + Alarm::List alarms = event->alarms(); + if (alarms.isEmpty()) + continue; // KAlarm isn't interested in events without alarms + QStringList cats = event->categories(); + bool addLateCancel = false; + + if (pre_0_7 && event->doesFloat()) + { + // It's a KAlarm pre-0.7 calendar file. + // Ensure that when the calendar is saved, the alarm time isn't lost. + event->setFloats(false); + } + + if (pre_0_9) + { + /* + * It's a KAlarm pre-0.9 calendar file. + * All alarms were of type DISPLAY. Instead of the X-KDE-KALARM-TYPE + * alarm property, characteristics were stored as a prefix to the + * alarm DESCRIPTION property, as follows: + * SEQNO;[FLAGS];TYPE:TEXT + * where + * SEQNO = sequence number of alarm within the event + * FLAGS = C for late-cancel, L for repeat-at-login, D for deferral + * TYPE = TEXT or FILE or CMD + * TEXT = message text, file name/URL or command + */ + for (Alarm::List::ConstIterator alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + bool atLogin = false; + bool deferral = false; + bool lateCancel = false; + KAAlarmEventBase::Type action = T_MESSAGE; + QString txt = alarm->text(); + int length = txt.length(); + int i = 0; + if (txt[0].isDigit()) + { + while (++i < length && txt[i].isDigit()) ; + if (i < length && txt[i++] == SEPARATOR) + { + while (i < length) + { + QChar ch = txt[i++]; + if (ch == SEPARATOR) + break; + if (ch == LATE_CANCEL_CODE) + lateCancel = true; + else if (ch == AT_LOGIN_CODE) + atLogin = true; + else if (ch == DEFERRAL_CODE) + deferral = true; + } + } + else + i = 0; // invalid prefix + } + if (txt.find(TEXT_PREFIX, i) == i) + i += TEXT_PREFIX.length(); + else if (txt.find(FILE_PREFIX, i) == i) + { + action = T_FILE; + i += FILE_PREFIX.length(); + } + else if (txt.find(COMMAND_PREFIX, i) == i) + { + action = T_COMMAND; + i += COMMAND_PREFIX.length(); + } + else + i = 0; + txt = txt.mid(i); + + QStringList types; + switch (action) + { + case T_FILE: + types += FILE_TYPE; + // fall through to T_MESSAGE + case T_MESSAGE: + alarm->setDisplayAlarm(txt); + break; + case T_COMMAND: + setProcedureAlarm(alarm, txt); + break; + case T_EMAIL: // email alarms were introduced in KAlarm 0.9 + case T_AUDIO: // never occurs in this context + break; + } + if (atLogin) + { + types += AT_LOGIN_TYPE; + lateCancel = false; + } + else if (deferral) + types += TIME_DEFERRAL_TYPE; + if (lateCancel) + addLateCancel = true; + if (types.count() > 0) + alarm->setCustomProperty(APPNAME, TYPE_PROPERTY, types.join(",")); + + if (pre_0_7 && alarm->repeatCount() > 0 && alarm->snoozeTime() > 0) + { + // It's a KAlarm pre-0.7 calendar file. + // Minutely recurrences were stored differently. + Recurrence* recur = event->recurrence(); + if (recur && recur->doesRecur()) + { + recur->setMinutely(alarm->snoozeTime()); + recur->setDuration(alarm->repeatCount() + 1); + alarm->setRepeatCount(0); + alarm->setSnoozeTime(0); + } + } + + if (adjustSummerTime) + { + // The calendar file was written by the KDE 3.0.0 version of KAlarm 0.5.7. + // Summer time was ignored when converting to UTC. + QDateTime dt = alarm->time(); + time_t t = dt0.secsTo(dt); + struct tm* dtm = localtime(&t); + if (dtm->tm_isdst) + { + dt = dt.addSecs(-3600); + alarm->setTime(dt); + } + } + } + } + + if (pre_0_9_2) + { + /* + * It's a KAlarm pre-0.9.2 calendar file. + * For the expired calendar, set the CREATED time to the DTEND value. + * Convert date-only DTSTART to date/time, and add category "DATE". + * Set the DTEND time to the DTSTART time. + * Convert all alarm times to DTSTART offsets. + * For display alarms, convert the first unlabelled category to an + * X-KDE-KALARM-FONTCOLOUR property. + * Convert BEEP category into an audio alarm with no audio file. + */ + if (uidStatus(event->uid()) == EXPIRED) + event->setCreated(event->dtEnd()); + QDateTime start = event->dtStart(); + if (event->doesFloat()) + { + event->setFloats(false); + start.setTime(startOfDay); + cats.append(DATE_ONLY_CATEGORY); + } + event->setHasEndDate(false); + + Alarm::List::ConstIterator alit; + for (alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + QDateTime dt = alarm->time(); + alarm->setStartOffset(start.secsTo(dt)); + } + + if (cats.count() > 0) + { + for (alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + if (alarm->type() == Alarm::Display) + alarm->setCustomProperty(APPNAME, FONT_COLOUR_PROPERTY, + QString::fromLatin1("%1;;").arg(cats[0])); + } + cats.remove(cats.begin()); + } + + for (QStringList::Iterator it = cats.begin(); it != cats.end(); ++it) + { + if (*it == BEEP_CATEGORY) + { + cats.remove(it); + + Alarm* alarm = event->newAlarm(); + alarm->setEnabled(true); + alarm->setAudioAlarm(); + QDateTime dt = event->dtStart(); // default + + // Parse and order the alarms to know which one's date/time to use + AlarmMap alarmMap; + readAlarms(*event, &alarmMap); + AlarmMap::ConstIterator it = alarmMap.begin(); + if (it != alarmMap.end()) + { + dt = it.data().alarm->time(); + break; + } + alarm->setStartOffset(start.secsTo(dt)); + break; + } + } + } + + if (pre_1_1_1) + { + /* + * It's a KAlarm pre-1.1.1 calendar file. + * Convert simple LATECANCEL category to LATECANCEL:n where n = minutes late. + */ + QStringList::Iterator it; + while ((it = cats.find(LATE_CANCEL_CAT)) != cats.end()) + { + cats.remove(it); + addLateCancel = true; + } + } + + if (pre_1_2_1) + { + /* + * It's a KAlarm pre-1.2.1 calendar file. + * Convert email display alarms from translated to untranslated header prefixes. + */ + for (Alarm::List::ConstIterator alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + if (alarm->type() == Alarm::Display) + { + QString oldtext = alarm->text(); + QString newtext = AlarmText::toCalendarText(oldtext); + if (oldtext != newtext) + alarm->setDisplayAlarm(newtext); + } + } + } + + if (pre_1_3_0) + { + /* + * It's a KAlarm pre-1.3.0 calendar file. + * Convert simple TMPLDEFTIME category to TMPLAFTTIME:n where n = minutes after. + */ + QStringList::Iterator it; + while ((it = cats.find(TEMPL_DEF_TIME_CAT)) != cats.end()) + { + cats.remove(it); + cats.append(QString("%1%2").arg(TEMPL_AFTER_TIME_CATEGORY).arg(0)); + } + } + + if (pre_1_3_1) + { + /* + * It's a KAlarm pre-1.3.1 calendar file. + * Convert simple XTERM category to LOG:xterm: + */ + QStringList::Iterator it; + while ((it = cats.find(EXEC_IN_XTERM_CAT)) != cats.end()) + { + cats.remove(it); + cats.append(LOG_CATEGORY + xtermURL); + } + } + + if (addLateCancel) + cats.append(QString("%1%2").arg(LATE_CANCEL_CATEGORY).arg(1)); + + event->setCategories(cats); + + + if (pre_1_4_14 + && event->recurrence() && event->recurrence()->doesRecur()) + { + /* + * It's a KAlarm pre-1.4.14 calendar file. + * For recurring events, convert the main alarm offset to an absolute + * time in the X-KDE-KALARM-NEXTRECUR property, and convert main + * alarm offsets to zero and deferral alarm offsets to be relative to + * the next recurrence. + */ + bool dateOnly = (cats.find(DATE_ONLY_CATEGORY) != cats.end()); + DateTime startDateTime(event->dtStart(), dateOnly); + // Convert the main alarm and get the next main trigger time from it + DateTime nextMainDateTime; + bool mainExpired = true; + Alarm::List::ConstIterator alit; + for (alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + if (!alarm->hasStartOffset()) + continue; + bool mainAlarm = true; + QString property = alarm->customProperty(APPNAME, TYPE_PROPERTY); + QStringList types = QStringList::split(QChar(','), property); + for (unsigned int i = 0; i < types.count(); ++i) + { + QString type = types[i]; + if (type == AT_LOGIN_TYPE + || type == TIME_DEFERRAL_TYPE + || type == DATE_DEFERRAL_TYPE + || type == REMINDER_TYPE + || type == REMINDER_ONCE_TYPE + || type == DISPLAYING_TYPE + || type == PRE_ACTION_TYPE + || type == POST_ACTION_TYPE) + mainAlarm = false; + } + if (mainAlarm) + { + mainExpired = false; + nextMainDateTime = alarm->time(); + nextMainDateTime.setDateOnly(dateOnly); + if (nextMainDateTime != startDateTime) + { + QDateTime dt = nextMainDateTime.dateTime(); + event->setCustomProperty(APPNAME, NEXT_RECUR_PROPERTY, + dt.toString(dateOnly ? "yyyyMMdd" : "yyyyMMddThhmmss")); + } + alarm->setStartOffset(0); + } + } + int adjustment; + if (mainExpired) + { + // It's an expired recurrence. + // Set the alarm offset relative to the first actual occurrence + // (taking account of possible exceptions). + DateTime dt = event->recurrence()->getNextDateTime(startDateTime.dateTime().addDays(-1)); + dt.setDateOnly(dateOnly); + adjustment = startDateTime.secsTo(dt); + } + else + adjustment = startDateTime.secsTo(nextMainDateTime); + if (adjustment) + { + // Convert deferred alarms + for (alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + if (!alarm->hasStartOffset()) + continue; + QString property = alarm->customProperty(APPNAME, TYPE_PROPERTY); + QStringList types = QStringList::split(QChar(','), property); + for (unsigned int i = 0; i < types.count(); ++i) + { + QString type = types[i]; + if (type == TIME_DEFERRAL_TYPE + || type == DATE_DEFERRAL_TYPE) + { + alarm->setStartOffset(alarm->startOffset().asSeconds() - adjustment); + break; + } + } + } + } + } + + if (pre_1_5_0) + { + /* + * It's a KAlarm pre-1.5.0 calendar file. + * Convert email identity names to uoids. + * Convert simple repetitions without a recurrence, to a recurrence. + */ + for (Alarm::List::ConstIterator alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + QString name = alarm->customProperty(APPNAME, KMAIL_ID_PROPERTY); + if (name.isEmpty()) + continue; + uint id = KAMail::identityUoid(name); + if (id) + alarm->setCustomProperty(APPNAME, EMAIL_ID_PROPERTY, QString::number(id)); + alarm->removeCustomProperty(APPNAME, KMAIL_ID_PROPERTY); + } + convertRepetition(event); + } + } +} + +/****************************************************************************** +* If the calendar was written by a pre-1.4.22 version of KAlarm, or another +* program, convert simple repetitions in events without a recurrence, to a +* recurrence. +* Reply = true if any conversions were done. +*/ +void KAEvent::convertRepetitions(KCal::CalendarLocal& calendar) +{ + + Event::List events = calendar.rawEvents(); + for (Event::List::ConstIterator ev = events.begin(); ev != events.end(); ++ev) + convertRepetition(*ev); +} + +/****************************************************************************** +* Convert simple repetitions in an event without a recurrence, to a +* recurrence. Repetitions which are an exact multiple of 24 hours are converted +* to daily recurrences; else they are converted to minutely recurrences. Note +* that daily and minutely recurrences produce different results when they span +* a daylight saving time change. +* Reply = true if any conversions were done. +*/ +bool KAEvent::convertRepetition(KCal::Event* event) +{ + Alarm::List alarms = event->alarms(); + if (alarms.isEmpty()) + return false; + Recurrence* recur = event->recurrence(); // guaranteed to return non-null + if (!recur->doesRecur()) + return false; + bool converted = false; + bool readOnly = event->isReadOnly(); + for (Alarm::List::ConstIterator alit = alarms.begin(); alit != alarms.end(); ++alit) + { + Alarm* alarm = *alit; + if (alarm->repeatCount() > 0 && alarm->snoozeTime() > 0) + { + if (!converted) + { + if (readOnly) + event->setReadOnly(false); + if (alarm->snoozeTime() % (24*3600)) + recur->setMinutely(alarm->snoozeTime()); + else + recur->setDaily(alarm->snoozeTime() / (24*3600)); + recur->setDuration(alarm->repeatCount() + 1); + converted = true; + } + alarm->setRepeatCount(0); + alarm->setSnoozeTime(0); + } + } + if (converted) + { + if (readOnly) + event->setReadOnly(true); + } + return converted; +} + +#ifndef NDEBUG +void KAEvent::dumpDebug() const +{ + kdDebug(5950) << "KAEvent dump:\n"; + KAAlarmEventBase::dumpDebug(); + if (!mTemplateName.isEmpty()) + { + kdDebug(5950) << "-- mTemplateName:" << mTemplateName << ":\n"; + kdDebug(5950) << "-- mTemplateAfterTime:" << mTemplateAfterTime << ":\n"; + } + if (mActionType == T_MESSAGE || mActionType == T_FILE) + { + kdDebug(5950) << "-- mAudioFile:" << mAudioFile << ":\n"; + kdDebug(5950) << "-- mPreAction:" << mPreAction << ":\n"; + kdDebug(5950) << "-- mPostAction:" << mPostAction << ":\n"; + } + else if (mActionType == T_COMMAND) + { + kdDebug(5950) << "-- mCommandXterm:" << (mCommandXterm ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mLogFile:" << mLogFile << ":\n"; + } + kdDebug(5950) << "-- mKMailSerialNumber:" << mKMailSerialNumber << ":\n"; + kdDebug(5950) << "-- mCopyToKOrganizer:" << (mCopyToKOrganizer ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mStartDateTime:" << mStartDateTime.toString() << ":\n"; + kdDebug(5950) << "-- mSaveDateTime:" << mSaveDateTime.toString() << ":\n"; + if (mRepeatAtLogin) + kdDebug(5950) << "-- mAtLoginDateTime:" << mAtLoginDateTime.toString() << ":\n"; + kdDebug(5950) << "-- mArchiveRepeatAtLogin:" << (mArchiveRepeatAtLogin ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mEnabled:" << (mEnabled ? "true" : "false") << ":\n"; + if (mReminderMinutes) + kdDebug(5950) << "-- mReminderMinutes:" << mReminderMinutes << ":\n"; + if (mArchiveReminderMinutes) + kdDebug(5950) << "-- mArchiveReminderMinutes:" << mArchiveReminderMinutes << ":\n"; + if (mReminderMinutes || mArchiveReminderMinutes) + kdDebug(5950) << "-- mReminderOnceOnly:" << mReminderOnceOnly << ":\n"; + else if (mDeferral > 0) + { + kdDebug(5950) << "-- mDeferral:" << (mDeferral == NORMAL_DEFERRAL ? "normal" : "reminder") << ":\n"; + kdDebug(5950) << "-- mDeferralTime:" << mDeferralTime.toString() << ":\n"; + } + else if (mDeferral == CANCEL_DEFERRAL) + kdDebug(5950) << "-- mDeferral:cancel:\n"; + kdDebug(5950) << "-- mDeferDefaultMinutes:" << mDeferDefaultMinutes << ":\n"; + if (mDisplaying) + { + kdDebug(5950) << "-- mDisplayingTime:" << mDisplayingTime.toString() << ":\n"; + kdDebug(5950) << "-- mDisplayingFlags:" << mDisplayingFlags << ":\n"; + } + kdDebug(5950) << "-- mRevision:" << mRevision << ":\n"; + kdDebug(5950) << "-- mRecurrence:" << (mRecurrence ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mAlarmCount:" << mAlarmCount << ":\n"; + kdDebug(5950) << "-- mMainExpired:" << (mMainExpired ? "true" : "false") << ":\n"; + kdDebug(5950) << "KAEvent dump end\n"; +} +#endif + + +/*============================================================================= += Class KAAlarm += Corresponds to a single KCal::Alarm instance. +=============================================================================*/ + +KAAlarm::KAAlarm(const KAAlarm& alarm) + : KAAlarmEventBase(alarm), + mType(alarm.mType), + mRecurs(alarm.mRecurs), + mDeferred(alarm.mDeferred) +{ } + + +int KAAlarm::flags() const +{ + return KAAlarmEventBase::flags() + | (mDeferred ? KAEvent::DEFERRAL : 0); + +} + +#ifndef NDEBUG +void KAAlarm::dumpDebug() const +{ + kdDebug(5950) << "KAAlarm dump:\n"; + KAAlarmEventBase::dumpDebug(); + const char* altype = 0; + switch (mType) + { + case MAIN__ALARM: altype = "MAIN"; break; + case REMINDER__ALARM: altype = "REMINDER"; break; + case DEFERRED_DATE__ALARM: altype = "DEFERRED(DATE)"; break; + case DEFERRED_TIME__ALARM: altype = "DEFERRED(TIME)"; break; + case DEFERRED_REMINDER_DATE__ALARM: altype = "DEFERRED_REMINDER(DATE)"; break; + case DEFERRED_REMINDER_TIME__ALARM: altype = "DEFERRED_REMINDER(TIME)"; break; + case AT_LOGIN__ALARM: altype = "LOGIN"; break; + case DISPLAYING__ALARM: altype = "DISPLAYING"; break; + case AUDIO__ALARM: altype = "AUDIO"; break; + case PRE_ACTION__ALARM: altype = "PRE_ACTION"; break; + case POST_ACTION__ALARM: altype = "POST_ACTION"; break; + default: altype = "INVALID"; break; + } + kdDebug(5950) << "-- mType:" << altype << ":\n"; + kdDebug(5950) << "-- mRecurs:" << (mRecurs ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mDeferred:" << (mDeferred ? "true" : "false") << ":\n"; + kdDebug(5950) << "KAAlarm dump end\n"; +} + +const char* KAAlarm::debugType(Type type) +{ + switch (type) + { + case MAIN_ALARM: return "MAIN"; + case REMINDER_ALARM: return "REMINDER"; + case DEFERRED_ALARM: return "DEFERRED"; + case DEFERRED_REMINDER_ALARM: return "DEFERRED_REMINDER"; + case AT_LOGIN_ALARM: return "LOGIN"; + case DISPLAYING_ALARM: return "DISPLAYING"; + case AUDIO_ALARM: return "AUDIO"; + case PRE_ACTION_ALARM: return "PRE_ACTION"; + case POST_ACTION_ALARM: return "POST_ACTION"; + default: return "INVALID"; + } +} +#endif + + +/*============================================================================= += Class KAAlarmEventBase +=============================================================================*/ + +void KAAlarmEventBase::copy(const KAAlarmEventBase& rhs) +{ + mEventID = rhs.mEventID; + mText = rhs.mText; + mNextMainDateTime = rhs.mNextMainDateTime; + mBgColour = rhs.mBgColour; + mFgColour = rhs.mFgColour; + mFont = rhs.mFont; + mEmailFromIdentity = rhs.mEmailFromIdentity; + mEmailAddresses = rhs.mEmailAddresses; + mEmailSubject = rhs.mEmailSubject; + mEmailAttachments = rhs.mEmailAttachments; + mSoundVolume = rhs.mSoundVolume; + mFadeVolume = rhs.mFadeVolume; + mFadeSeconds = rhs.mFadeSeconds; + mActionType = rhs.mActionType; + mCommandScript = rhs.mCommandScript; + mRepeatCount = rhs.mRepeatCount; + mRepeatInterval = rhs.mRepeatInterval; + mNextRepeat = rhs.mNextRepeat; + mBeep = rhs.mBeep; + mSpeak = rhs.mSpeak; + mRepeatSound = rhs.mRepeatSound; + mRepeatAtLogin = rhs.mRepeatAtLogin; + mDisplaying = rhs.mDisplaying; + mLateCancel = rhs.mLateCancel; + mAutoClose = rhs.mAutoClose; + mEmailBcc = rhs.mEmailBcc; + mConfirmAck = rhs.mConfirmAck; + mDefaultFont = rhs.mDefaultFont; +} + +void KAAlarmEventBase::set(int flags) +{ + mSpeak = flags & KAEvent::SPEAK; + mBeep = (flags & KAEvent::BEEP) && !mSpeak; + mRepeatSound = flags & KAEvent::REPEAT_SOUND; + mRepeatAtLogin = flags & KAEvent::REPEAT_AT_LOGIN; + mAutoClose = (flags & KAEvent::AUTO_CLOSE) && mLateCancel; + mEmailBcc = flags & KAEvent::EMAIL_BCC; + mConfirmAck = flags & KAEvent::CONFIRM_ACK; + mDisplaying = flags & KAEvent::DISPLAYING_; + mDefaultFont = flags & KAEvent::DEFAULT_FONT; + mCommandScript = flags & KAEvent::SCRIPT; +} + +int KAAlarmEventBase::flags() const +{ + return (mBeep && !mSpeak ? KAEvent::BEEP : 0) + | (mSpeak ? KAEvent::SPEAK : 0) + | (mRepeatSound ? KAEvent::REPEAT_SOUND : 0) + | (mRepeatAtLogin ? KAEvent::REPEAT_AT_LOGIN : 0) + | (mAutoClose ? KAEvent::AUTO_CLOSE : 0) + | (mEmailBcc ? KAEvent::EMAIL_BCC : 0) + | (mConfirmAck ? KAEvent::CONFIRM_ACK : 0) + | (mDisplaying ? KAEvent::DISPLAYING_ : 0) + | (mDefaultFont ? KAEvent::DEFAULT_FONT : 0) + | (mCommandScript ? KAEvent::SCRIPT : 0); +} + +const QFont& KAAlarmEventBase::font() const +{ + return mDefaultFont ? Preferences::messageFont() : mFont; +} + +#ifndef NDEBUG +void KAAlarmEventBase::dumpDebug() const +{ + kdDebug(5950) << "-- mEventID:" << mEventID << ":\n"; + kdDebug(5950) << "-- mActionType:" << (mActionType == T_MESSAGE ? "MESSAGE" : mActionType == T_FILE ? "FILE" : mActionType == T_COMMAND ? "COMMAND" : mActionType == T_EMAIL ? "EMAIL" : mActionType == T_AUDIO ? "AUDIO" : "??") << ":\n"; + kdDebug(5950) << "-- mText:" << mText << ":\n"; + if (mActionType == T_COMMAND) + kdDebug(5950) << "-- mCommandScript:" << (mCommandScript ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mNextMainDateTime:" << mNextMainDateTime.toString() << ":\n"; + if (mActionType == T_EMAIL) + { + kdDebug(5950) << "-- mEmail: FromKMail:" << mEmailFromIdentity << ":\n"; + kdDebug(5950) << "-- Addresses:" << mEmailAddresses.join(", ") << ":\n"; + kdDebug(5950) << "-- Subject:" << mEmailSubject << ":\n"; + kdDebug(5950) << "-- Attachments:" << mEmailAttachments.join(", ") << ":\n"; + kdDebug(5950) << "-- Bcc:" << (mEmailBcc ? "true" : "false") << ":\n"; + } + kdDebug(5950) << "-- mBgColour:" << mBgColour.name() << ":\n"; + kdDebug(5950) << "-- mFgColour:" << mFgColour.name() << ":\n"; + kdDebug(5950) << "-- mDefaultFont:" << (mDefaultFont ? "true" : "false") << ":\n"; + if (!mDefaultFont) + kdDebug(5950) << "-- mFont:" << mFont.toString() << ":\n"; + kdDebug(5950) << "-- mBeep:" << (mBeep ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mSpeak:" << (mSpeak ? "true" : "false") << ":\n"; + if (mActionType == T_AUDIO) + { + if (mSoundVolume >= 0) + { + kdDebug(5950) << "-- mSoundVolume:" << mSoundVolume << ":\n"; + if (mFadeVolume >= 0) + { + kdDebug(5950) << "-- mFadeVolume:" << mFadeVolume << ":\n"; + kdDebug(5950) << "-- mFadeSeconds:" << mFadeSeconds << ":\n"; + } + else + kdDebug(5950) << "-- mFadeVolume:-:\n"; + } + else + kdDebug(5950) << "-- mSoundVolume:-:\n"; + kdDebug(5950) << "-- mRepeatSound:" << (mRepeatSound ? "true" : "false") << ":\n"; + } + kdDebug(5950) << "-- mConfirmAck:" << (mConfirmAck ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mRepeatAtLogin:" << (mRepeatAtLogin ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mRepeatCount:" << mRepeatCount << ":\n"; + kdDebug(5950) << "-- mRepeatInterval:" << mRepeatInterval << ":\n"; + kdDebug(5950) << "-- mNextRepeat:" << mNextRepeat << ":\n"; + kdDebug(5950) << "-- mDisplaying:" << (mDisplaying ? "true" : "false") << ":\n"; + kdDebug(5950) << "-- mLateCancel:" << mLateCancel << ":\n"; + kdDebug(5950) << "-- mAutoClose:" << (mAutoClose ? "true" : "false") << ":\n"; +} +#endif + + +/*============================================================================= += Class EmailAddressList +=============================================================================*/ + +/****************************************************************************** + * Sets the list of email addresses, removing any empty addresses. + * Reply = false if empty addresses were found. + */ +EmailAddressList& EmailAddressList::operator=(const QValueList& addresses) +{ + clear(); + for (QValueList::ConstIterator it = addresses.begin(); it != addresses.end(); ++it) + { + if (!(*it).email().isEmpty()) + append(*it); + } + return *this; +} + +/****************************************************************************** + * Return the email address list as a string, each address being delimited by + * the specified separator string. + */ +QString EmailAddressList::join(const QString& separator) const +{ + QString result; + bool first = true; + for (QValueList::ConstIterator it = begin(); it != end(); ++it) + { + if (first) + first = false; + else + result += separator; + + bool quote = false; + QString name = (*it).name(); + if (!name.isEmpty()) + { + // Need to enclose the name in quotes if it has any special characters + int len = name.length(); + for (int i = 0; i < len; ++i) + { + QChar ch = name[i]; + if (!ch.isLetterOrNumber()) + { + quote = true; + result += '\"'; + break; + } + } + result += (*it).name(); + result += (quote ? "\" <" : " <"); + quote = true; // need angle brackets round email address + } + + result += (*it).email(); + if (quote) + result += '>'; + } + return result; +} + + +/*============================================================================= += Static functions +=============================================================================*/ + +/****************************************************************************** + * Set the specified alarm to be a procedure alarm with the given command line. + * The command line is first split into its program file and arguments before + * initialising the alarm. + */ +static void setProcedureAlarm(Alarm* alarm, const QString& commandLine) +{ + QString command = QString::null; + QString arguments = QString::null; + QChar quoteChar; + bool quoted = false; + uint posMax = commandLine.length(); + uint pos; + for (pos = 0; pos < posMax; ++pos) + { + QChar ch = commandLine[pos]; + if (quoted) + { + if (ch == quoteChar) + { + ++pos; // omit the quote character + break; + } + command += ch; + } + else + { + bool done = false; + switch (ch) + { + case ' ': + case ';': + case '|': + case '<': + case '>': + done = !command.isEmpty(); + break; + case '\'': + case '"': + if (command.isEmpty()) + { + // Start of a quoted string. Omit the quote character. + quoted = true; + quoteChar = ch; + break; + } + // fall through to default + default: + command += ch; + break; + } + if (done) + break; + } + } + + // Skip any spaces after the command + for ( ; pos < posMax && commandLine[pos] == ' '; ++pos) ; + arguments = commandLine.mid(pos); + + alarm->setProcedureAlarm(command, arguments); +} diff --git a/kalarm/alarmevent.h b/kalarm/alarmevent.h new file mode 100644 index 000000000..ff8ab3056 --- /dev/null +++ b/kalarm/alarmevent.h @@ -0,0 +1,500 @@ +/* + * alarmevent.h - represents calendar alarms and events + * Program: kalarm + * Copyright © 2001-2008 by 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. + */ + +#ifndef KALARMEVENT_H +#define KALARMEVENT_H + +/** @file alarmevent.h - represents calendar alarms and events */ + +#include +#include + +#include +#include +namespace KCal { + class Calendar; + class CalendarLocal; +} + +#include "datetime.h" +#include "karecurrence.h" + +class AlarmCalendar; +class KARecurrence; +struct AlarmData; + + +typedef KCal::Person EmailAddress; +class EmailAddressList : public QValueList +{ + public: + EmailAddressList() : QValueList() { } + EmailAddressList(const QValueList& list) { operator=(list); } + EmailAddressList& operator=(const QValueList&); + QString join(const QString& separator) const; +}; + + +// Base class containing data common to KAAlarm and KAEvent +class KAAlarmEventBase +{ + public: + ~KAAlarmEventBase() { } + const QString& cleanText() const { return mText; } + QString message() const { return (mActionType == T_MESSAGE || mActionType == T_EMAIL) ? mText : QString::null; } + QString fileName() const { return (mActionType == T_FILE) ? mText : QString::null; } + QString command() const { return (mActionType == T_COMMAND) ? mText : QString::null; } + uint emailFromId() const { return mEmailFromIdentity; } + const EmailAddressList& emailAddresses() const { return mEmailAddresses; } + QString emailAddresses(const QString& sep) const { return mEmailAddresses.join(sep); } + const QString& emailSubject() const { return mEmailSubject; } + const QStringList& emailAttachments() const { return mEmailAttachments; } + QString emailAttachments(const QString& sep) const { return mEmailAttachments.join(sep); } + bool emailBcc() const { return mEmailBcc; } + const QColor& bgColour() const { return mBgColour; } + const QColor& fgColour() const { return mFgColour; } + bool defaultFont() const { return mDefaultFont; } + const QFont& font() const; + int lateCancel() const { return mLateCancel; } + bool autoClose() const { return mAutoClose; } + bool commandScript() const { return mCommandScript; } + bool confirmAck() const { return mConfirmAck; } + bool repeatAtLogin() const { return mRepeatAtLogin; } + int repeatCount() const { return mRepeatCount; } + int repeatInterval() const { return mRepeatInterval; } + bool displaying() const { return mDisplaying; } + bool beep() const { return mBeep; } + bool speak() const { return (mActionType == T_MESSAGE) && mSpeak; } + int flags() const; +#ifdef NDEBUG + void dumpDebug() const { } +#else + void dumpDebug() const; +#endif + + protected: + enum Type { T_MESSAGE, T_FILE, T_COMMAND, T_AUDIO, T_EMAIL }; + + KAAlarmEventBase() : mRepeatCount(0), mLateCancel(0), mAutoClose(false), mBeep(false), mRepeatAtLogin(false), + mDisplaying(false), mEmailBcc(false), mConfirmAck(false) { } + KAAlarmEventBase(const KAAlarmEventBase& rhs) { copy(rhs); } + KAAlarmEventBase& operator=(const KAAlarmEventBase& rhs) { copy(rhs); return *this; } + void copy(const KAAlarmEventBase&); + void set(int flags); + + QString mEventID; // UID: KCal::Event unique ID + QString mText; // message text, file URL, command, email body [or audio file for KAAlarm] + DateTime mNextMainDateTime; // next time to display the alarm, excluding repetitions + QColor mBgColour; // background colour of alarm message + QColor mFgColour; // foreground colour of alarm message, or invalid for default + QFont mFont; // font of alarm message (ignored if mDefaultFont true) + uint mEmailFromIdentity;// KMail identity for email 'From' field, or empty + EmailAddressList mEmailAddresses; // ATTENDEE: addresses to send email to + QString mEmailSubject; // SUMMARY: subject line of email + QStringList mEmailAttachments; // ATTACH: email attachment file names + float mSoundVolume; // volume for sound file, or < 0 for unspecified + float mFadeVolume; // initial volume for sound file, or < 0 for no fade + int mFadeSeconds; // fade time for sound file, or 0 if none + Type mActionType; // alarm action type + int mRepeatCount; // sub-repetition count (excluding the first time) + int mRepeatInterval; // sub-repetition interval (minutes) + int mNextRepeat; // repetition count of next due sub-repetition + int mLateCancel; // how many minutes late will cancel the alarm, or 0 for no cancellation + bool mAutoClose; // whether to close the alarm window after the late-cancel period + bool mCommandScript; // the command text is a script, not a shell command line + bool mBeep; // whether to beep when the alarm is displayed + bool mSpeak; // whether to speak the message when the alarm is displayed + bool mRepeatSound; // whether to repeat the sound file while the alarm is displayed + bool mRepeatAtLogin; // whether to repeat the alarm at every login + bool mDisplaying; // whether the alarm is currently being displayed + bool mEmailBcc; // blind copy the email to the user + bool mConfirmAck; // alarm acknowledgement requires confirmation by user + bool mDefaultFont; // use default message font, not mFont + + friend class AlarmData; +}; + + +// KAAlarm corresponds to a single KCal::Alarm instance. +// A KAEvent may contain multiple KAAlarm's. +class KAAlarm : public KAAlarmEventBase +{ + public: + // Define the basic KAAlaem action types + enum Action + { + MESSAGE = T_MESSAGE, // KCal::Alarm::Display type: display a text message + FILE = T_FILE, // KCal::Alarm::Display type: display a file (URL given by the alarm text) + COMMAND = T_COMMAND, // KCal::Alarm::Procedure type: execute a shell command + EMAIL = T_EMAIL, // KCal::Alarm::Email type: send an email + AUDIO = T_AUDIO // KCal::Alarm::Audio type: play a sound file + }; + // Define the KAAlarm types. + // KAAlarm's of different types may be contained in a KAEvent, + // each defining a different component of the overall alarm. + enum Type + { + INVALID_ALARM = 0, // not an alarm + MAIN_ALARM = 1, // THE real alarm. Must be the first in the enumeration. + // The following values may be used in combination as a bitmask 0x0E + REMINDER_ALARM = 0x02, // reminder in advance of main alarm + DEFERRED_ALARM = 0x04, // deferred alarm + DEFERRED_REMINDER_ALARM = REMINDER_ALARM | DEFERRED_ALARM, // deferred early warning + // The following values must be greater than the preceding ones, to + // ensure that in ordered processing they are processed afterwards. + AT_LOGIN_ALARM = 0x10, // additional repeat-at-login trigger + DISPLAYING_ALARM = 0x20, // copy of the alarm currently being displayed + // The following values are for internal KAEvent use only + AUDIO_ALARM = 0x30, // sound to play when displaying the alarm + PRE_ACTION_ALARM = 0x40, // command to execute before displaying the alarm + POST_ACTION_ALARM = 0x50 // command to execute after the alarm window is closed + }; + enum SubType + { + INVALID__ALARM = INVALID_ALARM, + MAIN__ALARM = MAIN_ALARM, + // The following values may be used in combination as a bitmask 0x0E + REMINDER__ALARM = REMINDER_ALARM, + TIMED_DEFERRAL_FLAG = 0x08, // deferral has a time; if clear, it is date-only + DEFERRED_DATE__ALARM = DEFERRED_ALARM, // deferred alarm - date-only + DEFERRED_TIME__ALARM = DEFERRED_ALARM | TIMED_DEFERRAL_FLAG, + DEFERRED_REMINDER_DATE__ALARM = REMINDER_ALARM | DEFERRED_ALARM, // deferred early warning (date-only) + DEFERRED_REMINDER_TIME__ALARM = REMINDER_ALARM | DEFERRED_ALARM | TIMED_DEFERRAL_FLAG, // deferred early warning (date/time) + // The following values must be greater than the preceding ones, to + // ensure that in ordered processing they are processed afterwards. + AT_LOGIN__ALARM = AT_LOGIN_ALARM, + DISPLAYING__ALARM = DISPLAYING_ALARM, + // The following values are for internal KAEvent use only + AUDIO__ALARM = AUDIO_ALARM, + PRE_ACTION__ALARM = PRE_ACTION_ALARM, + POST_ACTION__ALARM = POST_ACTION_ALARM + }; + + KAAlarm() : mType(INVALID__ALARM), mDeferred(false) { } + KAAlarm(const KAAlarm&); + ~KAAlarm() { } + Action action() const { return (Action)mActionType; } + bool valid() const { return mType != INVALID__ALARM; } + Type type() const { return static_cast(mType & ~TIMED_DEFERRAL_FLAG); } + SubType subType() const { return mType; } + const QString& eventID() const { return mEventID; } + DateTime dateTime(bool withRepeats = false) const + { return (withRepeats && mNextRepeat && mRepeatInterval) + ? mNextMainDateTime.addSecs(mNextRepeat * mRepeatInterval * 60) : mNextMainDateTime; } + QDate date() const { return mNextMainDateTime.date(); } + QTime time() const { return mNextMainDateTime.time(); } + QString audioFile() const { return (mActionType == T_AUDIO) && !mBeep ? mText : QString::null; } + float soundVolume() const { return (mActionType == T_AUDIO) && !mBeep && !mText.isEmpty() ? mSoundVolume : -1; } + float fadeVolume() const { return (mActionType == T_AUDIO) && mSoundVolume >= 0 && mFadeSeconds && !mBeep && !mText.isEmpty() ? mFadeVolume : -1; } + int fadeSeconds() const { return (mActionType == T_AUDIO) && mSoundVolume >= 0 && mFadeVolume >= 0 && !mBeep && !mText.isEmpty() ? mFadeSeconds : 0; } + bool repeatSound() const { return (mActionType == T_AUDIO) && mRepeatSound && !mBeep && !mText.isEmpty(); } + bool reminder() const { return mType == REMINDER__ALARM; } + bool deferred() const { return mDeferred; } + void setTime(const DateTime& dt) { mNextMainDateTime = dt; } + void setTime(const QDateTime& dt) { mNextMainDateTime = dt; } + int flags() const; +#ifdef NDEBUG + void dumpDebug() const { } + static const char* debugType(Type) { return ""; } +#else + void dumpDebug() const; + static const char* debugType(Type); +#endif + + private: + SubType mType; // alarm type + bool mRecurs; // there is a recurrence rule for the alarm + bool mDeferred; // whether the alarm is an extra deferred/deferred-reminder alarm + + friend class KAEvent; +}; + + +/** KAEvent corresponds to a KCal::Event instance */ +class KAEvent : public KAAlarmEventBase +{ + public: + enum // flags for use in DCOP calls, etc. + { +#ifdef OLD_DCOP + // *** DON'T CHANGE THESE VALUES *** + // because they are part of KAlarm's external DCOP interface. + // (But it's alright to add new values.) + LATE_CANCEL = 0x01, // cancel alarm if not triggered within a minute of its scheduled time +#endif + BEEP = 0x02, // sound audible beep when alarm is displayed + REPEAT_AT_LOGIN = 0x04, // repeat alarm at every login + ANY_TIME = 0x08, // only a date is specified for the alarm, not a time + CONFIRM_ACK = 0x10, // closing the alarm message window requires confirmation prompt + EMAIL_BCC = 0x20, // blind copy the email to the user + DEFAULT_FONT = 0x40, // use default alarm message font + REPEAT_SOUND = 0x80, // repeat sound file while alarm is displayed + DISABLED = 0x100, // alarm is currently disabled + AUTO_CLOSE = 0x200, // auto-close alarm window after late-cancel period + SCRIPT = 0x400, // command is a script, not a shell command line + EXEC_IN_XTERM = 0x800, // execute command in terminal window + SPEAK = 0x1000, // speak the message when the alarm is displayed + COPY_KORGANIZER = 0x2000, // KOrganizer should hold a copy of the event +#ifdef OLD_DCOP + // The following are read-only internal values, and may be changed +#else + // The following are read-only internal values +#endif + REMINDER = 0x10000, + DEFERRAL = 0x20000, + TIMED_FLAG = 0x40000, + DATE_DEFERRAL = DEFERRAL, + TIME_DEFERRAL = DEFERRAL | TIMED_FLAG, + DISPLAYING_ = 0x80000, + READ_ONLY_FLAGS = 0xF0000 // mask for all read-only internal values + }; + /** The category of an event, indicated by the middle part of its UID. */ + enum Status + { + ACTIVE, // the event is currently active + EXPIRED, // the event has expired + DISPLAYING, // the event is currently being displayed + TEMPLATE, // the event is an alarm template + KORGANIZER // the event is a copy of a KAlarm event, held by KOrganizer + }; + enum Action + { + MESSAGE = T_MESSAGE, + FILE = T_FILE, + COMMAND = T_COMMAND, + EMAIL = T_EMAIL + }; + enum OccurType // what type of occurrence is due + { + NO_OCCURRENCE = 0, // no occurrence is due + FIRST_OR_ONLY_OCCURRENCE = 0x01, // the first occurrence (takes precedence over LAST_RECURRENCE) + RECURRENCE_DATE = 0x02, // a recurrence with only a date, not a time + RECURRENCE_DATE_TIME = 0x03, // a recurrence with a date and time + LAST_RECURRENCE = 0x04, // the last recurrence + OCCURRENCE_REPEAT = 0x10, // (bitmask for a repetition of an occurrence) + FIRST_OR_ONLY_OCCURRENCE_REPEAT = OCCURRENCE_REPEAT | FIRST_OR_ONLY_OCCURRENCE, // a repetition of the first occurrence + RECURRENCE_DATE_REPEAT = OCCURRENCE_REPEAT | RECURRENCE_DATE, // a repetition of a date-only recurrence + RECURRENCE_DATE_TIME_REPEAT = OCCURRENCE_REPEAT | RECURRENCE_DATE_TIME, // a repetition of a date/time recurrence + LAST_RECURRENCE_REPEAT = OCCURRENCE_REPEAT | LAST_RECURRENCE // a repetition of the last recurrence + }; + enum OccurOption // options for nextOccurrence() + { + IGNORE_REPETITION, // check for recurrences only, ignore repetitions + RETURN_REPETITION, // return repetition if it's the next occurrence + ALLOW_FOR_REPETITION // check for repetition being the next occurrence, but return recurrence + }; + enum DeferLimitType // what type of occurrence currently limits a deferral + { + LIMIT_NONE, + LIMIT_MAIN, + LIMIT_RECURRENCE, + LIMIT_REPETITION, + LIMIT_REMINDER + }; + + KAEvent() : mRevision(0), mRecurrence(0), mAlarmCount(0) { } + KAEvent(const QDateTime& dt, const QString& message, const QColor& bg, const QColor& fg, const QFont& f, Action action, int lateCancel, int flags) + : mRecurrence(0) { set(dt, message, bg, fg, f, action, lateCancel, flags); } + explicit KAEvent(const KCal::Event& e) : mRecurrence(0) { set(e); } + KAEvent(const KAEvent& e) : KAAlarmEventBase(e), mRecurrence(0) { copy(e); } + ~KAEvent() { delete mRecurrence; } + KAEvent& operator=(const KAEvent& e) { if (&e != this) copy(e); return *this; } + void set(const KCal::Event&); + void set(const QDateTime&, const QString& message, const QColor& bg, const QColor& fg, const QFont&, Action, int lateCancel, int flags); + void setEmail(uint from, const EmailAddressList&, const QString& subject, const QStringList& attachments); + void setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds); + void setTemplate(const QString& name, int afterTime = -1) { mTemplateName = name; mTemplateAfterTime = afterTime; mUpdated = true; } + void setActions(const QString& pre, const QString& post) { mPreAction = pre; mPostAction = post; mUpdated = true; } + OccurType setNextOccurrence(const QDateTime& preDateTime); + void setFirstRecurrence(); + void setEventID(const QString& id) { mEventID = id; mUpdated = true; } + void setDate(const QDate& d) { mNextMainDateTime.set(d); mUpdated = true; } + void setTime(const QDateTime& dt) { mNextMainDateTime.set(dt); mUpdated = true; } + void setSaveDateTime(const QDateTime& dt) { mSaveDateTime = dt; mUpdated = true; } + void setLateCancel(int lc) { mLateCancel = lc; mUpdated = true; } + void setAutoClose(bool ac) { mAutoClose = ac; mUpdated = true; } + void setRepeatAtLogin(bool rl) { mRepeatAtLogin = rl; mUpdated = true; } + void setUid(Status s) { mEventID = uid(mEventID, s); mUpdated = true; } + void setKMailSerialNumber(unsigned long n) { mKMailSerialNumber = n; } + void setLogFile(const QString& logfile); + void setReminder(int minutes, bool onceOnly); + bool defer(const DateTime&, bool reminder, bool adjustRecurrence = false); + void cancelDefer(); + void cancelCancelledDeferral(); + void setDeferDefaultMinutes(int minutes) { mDeferDefaultMinutes = minutes; mUpdated = true; } + bool setDisplaying(const KAEvent&, KAAlarm::Type, const QDateTime&); + void reinstateFromDisplaying(const KAEvent& dispEvent); + void setArchive() { mArchive = true; mUpdated = true; } + void setEnabled(bool enable) { mEnabled = enable; mUpdated = true; } + void setUpdated() { mUpdated = true; } + void clearUpdated() const { mUpdated = false; } + void removeExpiredAlarm(KAAlarm::Type); + void incrementRevision() { ++mRevision; mUpdated = true; } + + KCal::Event* event() const; // convert to new Event + bool isTemplate() const { return !mTemplateName.isEmpty(); } + const QString& templateName() const { return mTemplateName; } + bool usingDefaultTime() const { return mTemplateAfterTime == 0; } + int templateAfterTime() const { return mTemplateAfterTime; } + KAAlarm alarm(KAAlarm::Type) const; + KAAlarm firstAlarm() const; + KAAlarm nextAlarm(const KAAlarm& al) const { return nextAlarm(al.type()); } + KAAlarm nextAlarm(KAAlarm::Type) const; + KAAlarm convertDisplayingAlarm() const; + bool updateKCalEvent(KCal::Event&, bool checkUid = true, bool original = false, bool cancelCancelledDefer = false) const; + Action action() const { return (Action)mActionType; } + bool displayAction() const { return mActionType == T_MESSAGE || mActionType == T_FILE; } + const QString& id() const { return mEventID; } + bool valid() const { return mAlarmCount && (mAlarmCount != 1 || !mRepeatAtLogin); } + int alarmCount() const { return mAlarmCount; } + const DateTime& startDateTime() const { return mStartDateTime; } + DateTime mainDateTime(bool withRepeats = false) const + { return (withRepeats && mNextRepeat && mRepeatInterval) + ? mNextMainDateTime.addSecs(mNextRepeat * mRepeatInterval * 60) : mNextMainDateTime; } + QDate mainDate() const { return mNextMainDateTime.date(); } + QTime mainTime() const { return mNextMainDateTime.time(); } + DateTime mainEndRepeatTime() const { return (mRepeatCount > 0 && mRepeatInterval) + ? mNextMainDateTime.addSecs(mRepeatCount * mRepeatInterval * 60) : mNextMainDateTime; } + int reminder() const { return mReminderMinutes; } + bool reminderOnceOnly() const { return mReminderOnceOnly; } + bool reminderDeferral() const { return mDeferral == REMINDER_DEFERRAL; } + int reminderArchived() const { return mArchiveReminderMinutes; } + DateTime deferDateTime() const { return mDeferralTime; } + DateTime deferralLimit(DeferLimitType* = 0) const; + int deferDefaultMinutes() const { return mDeferDefaultMinutes; } + DateTime displayDateTime() const; + const QString& messageFileOrCommand() const { return mText; } + QString logFile() const { return mLogFile; } + bool commandXterm() const { return mCommandXterm; } + unsigned long kmailSerialNumber() const { return mKMailSerialNumber; } + bool copyToKOrganizer() const { return mCopyToKOrganizer; } + const QString& audioFile() const { return mAudioFile; } + float soundVolume() const { return !mAudioFile.isEmpty() ? mSoundVolume : -1; } + float fadeVolume() const { return !mAudioFile.isEmpty() && mSoundVolume >= 0 && mFadeSeconds ? mFadeVolume : -1; } + int fadeSeconds() const { return !mAudioFile.isEmpty() && mSoundVolume >= 0 && mFadeVolume >= 0 ? mFadeSeconds : 0; } + bool repeatSound() const { return mRepeatSound && !mAudioFile.isEmpty(); } + const QString& preAction() const { return mPreAction; } + const QString& postAction() const { return mPostAction; } + bool recurs() const { return checkRecur() != KARecurrence::NO_RECUR; } + KARecurrence::Type recurType() const { return checkRecur(); } + KARecurrence* recurrence() const { return mRecurrence; } + int recurInterval() const; // recurrence period in units of the recurrence period type (minutes, days, etc) + int longestRecurrenceInterval() const { return mRecurrence ? mRecurrence->longestInterval() : 0; } + QString recurrenceText(bool brief = false) const; + QString repetitionText(bool brief = false) const; + bool occursAfter(const QDateTime& preDateTime, bool includeRepetitions) const; + OccurType nextOccurrence(const QDateTime& preDateTime, DateTime& result, OccurOption = IGNORE_REPETITION) const; + OccurType previousOccurrence(const QDateTime& afterDateTime, DateTime& result, bool includeRepetitions = false) const; + int flags() const; + bool deferred() const { return mDeferral > 0; } + bool toBeArchived() const { return mArchive; } + bool enabled() const { return mEnabled; } + bool updated() const { return mUpdated; } + bool mainExpired() const { return mMainExpired; } + bool expired() const { return mDisplaying && mMainExpired || uidStatus(mEventID) == EXPIRED; } + Status uidStatus() const { return uidStatus(mEventID); } + static Status uidStatus(const QString& uid); + static QString uid(const QString& id, Status); + static KAEvent findTemplateName(AlarmCalendar&, const QString& name); + + struct MonthPos + { + MonthPos() : days(7) { } + int weeknum; // week in month, or < 0 to count from end of month + QBitArray days; // days in week + }; + bool setRepetition(int interval, int count); + void setNoRecur() { clearRecur(); } + void setRecurrence(const KARecurrence&); + bool setRecurMinutely(int freq, int count, const QDateTime& end); + bool setRecurDaily(int freq, const QBitArray& days, int count, const QDate& end); + bool setRecurWeekly(int freq, const QBitArray& days, int count, const QDate& end); + bool setRecurMonthlyByDate(int freq, const QValueList& days, int count, const QDate& end); + bool setRecurMonthlyByPos(int freq, const QValueList& pos, int count, const QDate& end); + bool setRecurAnnualByDate(int freq, const QValueList& months, int day, KARecurrence::Feb29Type, int count, const QDate& end); + bool setRecurAnnualByPos(int freq, const QValueList& pos, const QValueList& months, int count, const QDate& end); +// static QValueList convRecurPos(const QValueList&); +#ifdef NDEBUG + void dumpDebug() const { } +#else + void dumpDebug() const; +#endif + static int calVersion(); + static QString calVersionString(); + static bool adjustStartOfDay(const KCal::Event::List&); + static void convertKCalEvents(KCal::Calendar&, int version, bool adjustSummerTime); + static void convertRepetitions(KCal::CalendarLocal&); + + private: + enum DeferType { + CANCEL_DEFERRAL = -1, // there was a deferred alarm, but it has been cancelled + NO_DEFERRAL = 0, // there is no deferred alarm + NORMAL_DEFERRAL, // the main alarm, a recurrence or a repeat is deferred + REMINDER_DEFERRAL // a reminder alarm is deferred + }; + + void copy(const KAEvent&); + bool setRecur(KCal::RecurrenceRule::PeriodType, int freq, int count, const QDateTime& end, KARecurrence::Feb29Type = KARecurrence::FEB29_FEB29); + void clearRecur(); + KARecurrence::Type checkRecur() const; + void checkRepetition() const; + OccurType nextRecurrence(const QDateTime& preDateTime, DateTime& result) const; + OccurType previousRecurrence(const QDateTime& afterDateTime, DateTime& result) const; + static bool convertRepetition(KCal::Event*); + KCal::Alarm* initKCalAlarm(KCal::Event&, const DateTime&, const QStringList& types, KAAlarm::Type = KAAlarm::INVALID_ALARM) const; + KCal::Alarm* initKCalAlarm(KCal::Event&, int startOffsetSecs, const QStringList& types, KAAlarm::Type = KAAlarm::INVALID_ALARM) const; + static DateTime readDateTime(const KCal::Event&, bool dateOnly, DateTime& start); + static void readAlarms(const KCal::Event&, void* alarmMap); + static void readAlarm(const KCal::Alarm&, AlarmData&); + inline void set_deferral(DeferType); + inline void set_reminder(int minutes); + inline void set_archiveReminder(); + + QString mTemplateName; // alarm template's name, or null if normal event + QString mAudioFile; // ATTACH: audio file to play + QString mPreAction; // command to execute before alarm is displayed + QString mPostAction; // command to execute after alarm window is closed + DateTime mStartDateTime; // DTSTART and DTEND: start and end time for event + QDateTime mSaveDateTime; // CREATED: date event was created, or saved in expired calendar + QDateTime mAtLoginDateTime; // repeat-at-login time + DateTime mDeferralTime; // extra time to trigger alarm (if alarm or reminder deferred) + DateTime mDisplayingTime; // date/time shown in the alarm currently being displayed + int mDisplayingFlags; // type of alarm which is currently being displayed + int mReminderMinutes; // how long in advance reminder is to be, or 0 if none + int mArchiveReminderMinutes; // original reminder period if now expired, or 0 if none + int mDeferDefaultMinutes; // default number of minutes for deferral dialogue, or 0 to select time control + int mRevision; // SEQUENCE: revision number of the original alarm, or 0 + KARecurrence* mRecurrence; // RECUR: recurrence specification, or 0 if none + int mAlarmCount; // number of alarms: count of !mMainExpired, mRepeatAtLogin, mDeferral, mReminderMinutes, mDisplaying + DeferType mDeferral; // whether the alarm is an extra deferred/deferred-reminder alarm + unsigned long mKMailSerialNumber;// if email text, message's KMail serial number + int mTemplateAfterTime;// time not specified: use n minutes after default time, or -1 (applies to templates only) + QString mLogFile; // alarm output is to be logged to this URL + bool mCommandXterm; // command alarm is to be executed in a terminal window + bool mCopyToKOrganizer; // KOrganizer should hold a copy of the event + bool mReminderOnceOnly; // the reminder is output only for the first recurrence + bool mMainExpired; // main alarm has expired (in which case a deferral alarm will exist) + bool mArchiveRepeatAtLogin; // if now expired, original event was repeat-at-login + bool mArchive; // event has triggered in the past, so archive it when closed + bool mEnabled; // false if event is disabled + mutable bool mUpdated; // event has been updated but not written to calendar file +}; + +#endif // KALARMEVENT_H diff --git a/kalarm/alarmlistview.cpp b/kalarm/alarmlistview.cpp new file mode 100644 index 000000000..19fcda4b5 --- /dev/null +++ b/kalarm/alarmlistview.cpp @@ -0,0 +1,711 @@ +/* + * alarmlistview.cpp - widget showing list of outstanding alarms + * Program: kalarm + * Copyright © 2001-2007 by 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 "kalarm.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "alarmcalendar.h" +#include "alarmtext.h" +#include "functions.h" +#include "kalarmapp.h" +#include "preferences.h" +#include "alarmlistview.moc" + + +class AlarmListTooltip : public QToolTip +{ + public: + AlarmListTooltip(QWidget* parent) : QToolTip(parent) { } + virtual ~AlarmListTooltip() {} + protected: + virtual void maybeTip(const QPoint&); +}; + + +/*============================================================================= += Class: AlarmListView += Displays the list of outstanding alarms. +=============================================================================*/ +QValueList AlarmListView::mInstanceList; +bool AlarmListView::mDragging = false; + + +AlarmListView::AlarmListView(const QValueList& order, QWidget* parent, const char* name) + : EventListViewBase(parent, name), + mMousePressed(false), + mDrawMessageInColour(false), + mShowExpired(false) +{ + static QString titles[COLUMN_COUNT] = { + i18n("Time"), + i18n("Time To"), + i18n("Repeat"), + QString::null, + QString::null, + i18n("Message, File or Command") + }; + + setSelectionMode(QListView::Extended); + + // Set the column order + int i; + bool ok = false; + if (order.count() >= COLUMN_COUNT) + { + // The column order is specified + bool posns[COLUMN_COUNT]; + for (i = 0; i < COLUMN_COUNT; ++i) + posns[i] = false; + for (i = 0; i < COLUMN_COUNT; ++i) + { + int ord = order[i]; + if (ord < COLUMN_COUNT && ord >= 0) + { + mColumn[i] = ord; + posns[ord] = true; + } + } + ok = true; + for (i = 0; i < COLUMN_COUNT; ++i) + if (!posns[i]) + ok = false; + if (ok && mColumn[MESSAGE_COLUMN] != MESSAGE_COLUMN) + { + // Shift the message column to be last, since otherwise + // column widths get screwed up. + int messageCol = mColumn[MESSAGE_COLUMN]; + for (i = 0; i < COLUMN_COUNT; ++i) + if (mColumn[i] > messageCol) + --mColumn[i]; + mColumn[MESSAGE_COLUMN] = MESSAGE_COLUMN; + } + } + if (!ok) + { + // Either no column order was specified, or it was invalid, + // so use the default order + for (i = 0; i < COLUMN_COUNT; ++i) + mColumn[i] = i; + } + + // Initialise the columns + for (i = 0; i < COLUMN_COUNT; ++i) + { + for (int j = 0; j < COLUMN_COUNT; ++j) + if (mColumn[j] == i) + { + if (j != MESSAGE_COLUMN) + addColumn(titles[j]); + break; + } + } + addLastColumn(titles[MESSAGE_COLUMN]); + + setSorting(mColumn[TIME_COLUMN]); // sort initially by date/time + mTimeColumnHeaderWidth = columnWidth(mColumn[TIME_COLUMN]); + mTimeToColumnHeaderWidth = columnWidth(mColumn[TIME_TO_COLUMN]); + setColumnAlignment(mColumn[REPEAT_COLUMN], Qt::AlignHCenter); + setColumnWidthMode(mColumn[REPEAT_COLUMN], QListView::Maximum); + + // Set the width of the colour column in proportion to height + setColumnWidth(mColumn[COLOUR_COLUMN], itemHeight() * 3/4); + setColumnWidthMode(mColumn[COLOUR_COLUMN], QListView::Manual); + + // Set the width of the alarm type column to exactly accommodate the icons. + // Don't allow the user to resize it (to avoid refresh problems, and bearing + // in mind that resizing doesn't seem very useful anyway). + setColumnWidth(mColumn[TYPE_COLUMN], AlarmListViewItem::typeIconWidth(this)); + setColumnWidthMode(mColumn[TYPE_COLUMN], QListView::Manual); + header()->setResizeEnabled(false, mColumn[TYPE_COLUMN]); + + mInstanceList.append(this); + + mTooltip = new AlarmListTooltip(viewport()); +} + +AlarmListView::~AlarmListView() +{ + delete mTooltip; + mTooltip = 0; + mInstanceList.remove(this); +} + +/****************************************************************************** +* Add all the current alarms to the list. +*/ +void AlarmListView::populate() +{ + KAEvent event; + KCal::Event::List events; + KCal::Event::List::ConstIterator it; + QDateTime now = QDateTime::currentDateTime(); + if (mShowExpired) + { + AlarmCalendar* cal = AlarmCalendar::expiredCalendarOpen(); + if (cal) + { + events = cal->events(); + for (it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + if (kcalEvent->alarms().count() > 0) + { + event.set(*kcalEvent); + addEntry(event, now); + } + } + } + } + events = AlarmCalendar::activeCalendar()->events(); + for (it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + event.set(*kcalEvent); + if (mShowExpired || !event.expired()) + addEntry(event, now); + } +} + +/****************************************************************************** +* Set which time columns are to be displayed. +*/ +void AlarmListView::selectTimeColumns(bool time, bool timeTo) +{ + if (!time && !timeTo) + return; // always show at least one time column + bool changed = false; + int w = columnWidth(mColumn[TIME_COLUMN]); + if (time && !w) + { + // Unhide the time column + int colWidth = mTimeColumnHeaderWidth; + QFontMetrics fm = fontMetrics(); + for (AlarmListViewItem* item = firstChild(); item; item = item->nextSibling()) + { + int w = item->width(fm, this, mColumn[TIME_COLUMN]); + if (w > colWidth) + colWidth = w; + } + setColumnWidth(mColumn[TIME_COLUMN], colWidth); + setColumnWidthMode(mColumn[TIME_COLUMN], QListView::Maximum); + changed = true; + } + else if (!time && w) + { + // Hide the time column + setColumnWidthMode(mColumn[TIME_COLUMN], QListView::Manual); + setColumnWidth(mColumn[TIME_COLUMN], 0); + changed = true; + } + w = columnWidth(mColumn[TIME_TO_COLUMN]); + if (timeTo && !w) + { + // Unhide the time-to-alarm column + setColumnWidthMode(mColumn[TIME_TO_COLUMN], QListView::Maximum); + updateTimeToAlarms(true); + if (columnWidth(mColumn[TIME_TO_COLUMN]) < mTimeToColumnHeaderWidth) + setColumnWidth(mColumn[TIME_TO_COLUMN], mTimeToColumnHeaderWidth); + changed = true; + } + else if (!timeTo && w) + { + // Hide the time-to-alarm column + setColumnWidthMode(mColumn[TIME_TO_COLUMN], QListView::Manual); + setColumnWidth(mColumn[TIME_TO_COLUMN], 0); + changed = true; + } + if (changed) + { + resizeLastColumn(); + triggerUpdate(); // ensure scroll bar appears if needed + } +} + +/****************************************************************************** +* Update all the values in the time-to-alarm column. +*/ +void AlarmListView::updateTimeToAlarms(bool forceDisplay) +{ + if (forceDisplay || columnWidth(mColumn[TIME_TO_COLUMN])) + { + QDateTime now = QDateTime::currentDateTime(); + for (AlarmListViewItem* item = firstChild(); item; item = item->nextSibling()) + item->updateTimeToAlarm(now, forceDisplay); + } +} + +/****************************************************************************** +* Add an event to every list instance. +* The selection highlight is moved to the new event in the specified instance only. +*/ +void AlarmListView::addEvent(const KAEvent& event, EventListViewBase* view) +{ + QDateTime now = QDateTime::currentDateTime(); + for (InstanceListConstIterator it = mInstanceList.begin(); it != mInstanceList.end(); ++it) + static_cast(*it)->addEntry(event, now, true, (*it == view)); +} + +/****************************************************************************** +* Add a new item to the list. +*/ +AlarmListViewItem* AlarmListView::addEntry(const KAEvent& event, const QDateTime& now, bool setSize, bool reselect) +{ + if (!mShowExpired && event.expired()) + return 0; + AlarmListViewItem* item = new AlarmListViewItem(this, event, now); + return static_cast(EventListViewBase::addEntry(item, setSize, reselect)); +} + +/****************************************************************************** +* Create a new list item for addEntry(). +*/ +EventListViewItemBase* AlarmListView::createItem(const KAEvent& event) +{ + return new AlarmListViewItem(this, event, QDateTime::currentDateTime()); +} + +/****************************************************************************** +* Check whether an item's alarm has expired. +*/ +bool AlarmListView::expired(AlarmListViewItem* item) const +{ + return item->event().expired(); +} + +/****************************************************************************** +* Return the column order. +*/ +QValueList AlarmListView::columnOrder() const +{ + QHeader* hdr = header(); + int order[COLUMN_COUNT]; + order[TIME_COLUMN] = hdr->mapToIndex(mColumn[TIME_COLUMN]); + order[TIME_TO_COLUMN] = hdr->mapToIndex(mColumn[TIME_TO_COLUMN]); + order[REPEAT_COLUMN] = hdr->mapToIndex(mColumn[REPEAT_COLUMN]); + order[COLOUR_COLUMN] = hdr->mapToIndex(mColumn[COLOUR_COLUMN]); + order[TYPE_COLUMN] = hdr->mapToIndex(mColumn[TYPE_COLUMN]); + order[MESSAGE_COLUMN] = hdr->mapToIndex(mColumn[MESSAGE_COLUMN]); + QValueList orderList; + for (int i = 0; i < COLUMN_COUNT; ++i) + orderList += order[i]; + return orderList; +} + +/****************************************************************************** +* Returns the QWhatsThis text for a specified column. +*/ +QString AlarmListView::whatsThisText(int column) const +{ + if (column == mColumn[TIME_COLUMN]) + return i18n("Next scheduled date and time of the alarm"); + if (column == mColumn[TIME_TO_COLUMN]) + return i18n("How long until the next scheduled trigger of the alarm"); + if (column == mColumn[REPEAT_COLUMN]) + return i18n("How often the alarm recurs"); + if (column == mColumn[COLOUR_COLUMN]) + return i18n("Background color of alarm message"); + if (column == mColumn[TYPE_COLUMN]) + return i18n("Alarm type (message, file, command or email)"); + if (column == mColumn[MESSAGE_COLUMN]) + return i18n("Alarm message text, URL of text file to display, command to execute, or email subject line"); + return i18n("List of scheduled alarms"); +} + +/****************************************************************************** +* Called when the mouse button is pressed. +* Records the position of the mouse when the left button is pressed, for use +* in drag operations. +*/ +void AlarmListView::contentsMousePressEvent(QMouseEvent* e) +{ + QListView::contentsMousePressEvent(e); + if (e->button() == Qt::LeftButton) + { + QPoint p(contentsToViewport(e->pos())); + if (itemAt(p)) + { + mMousePressPos = e->pos(); + mMousePressed = true; + } + mDragging = false; + } +} + +/****************************************************************************** +* Called when the mouse is moved. +* Creates a drag object when the mouse drags one or more selected items. +*/ +void AlarmListView::contentsMouseMoveEvent(QMouseEvent* e) +{ + QListView::contentsMouseMoveEvent(e); + if (mMousePressed + && (mMousePressPos - e->pos()).manhattanLength() > QApplication::startDragDistance()) + { + // Create a calendar object containing all the currently selected alarms + kdDebug(5950) << "AlarmListView::contentsMouseMoveEvent(): drag started" << endl; + mMousePressed = false; + KCal::CalendarLocal cal(QString::fromLatin1("UTC")); + cal.setLocalTime(); // write out using local time (i.e. no time zone) + QValueList items = selectedItems(); + if (!items.count()) + return; + for (QValueList::Iterator it = items.begin(); it != items.end(); ++it) + { + const KAEvent& event = (*it)->event(); + KCal::Event* kcalEvent = new KCal::Event; + event.updateKCalEvent(*kcalEvent, false, true); + kcalEvent->setUid(event.id()); + cal.addEvent(kcalEvent); + } + + // Create the drag object for the destination program to receive + mDragging = true; + KCal::ICalDrag* dobj = new KCal::ICalDrag(&cal, this); + dobj->dragCopy(); // the drag operation will copy the alarms + } +} + +/****************************************************************************** +* Called when the mouse button is released. +* Notes that the mouse left button is no longer pressed, for use in drag +* operations. +*/ +void AlarmListView::contentsMouseReleaseEvent(QMouseEvent *e) +{ + QListView::contentsMouseReleaseEvent(e); + mMousePressed = false; + mDragging = false; +} + + +/*============================================================================= += Class: AlarmListViewItem += Contains the details of one alarm for display in the AlarmListView. +=============================================================================*/ +int AlarmListViewItem::mTimeHourPos = -2; +int AlarmListViewItem::mDigitWidth = -1; + +AlarmListViewItem::AlarmListViewItem(AlarmListView* parent, const KAEvent& event, const QDateTime& now) + : EventListViewItemBase(parent, event), + mMessageTruncated(false), + mTimeToAlarmShown(false) +{ + setLastColumnText(); // set the message column text + + DateTime dateTime = event.expired() ? event.startDateTime() : event.displayDateTime(); + if (parent->column(AlarmListView::TIME_COLUMN) >= 0) + setText(parent->column(AlarmListView::TIME_COLUMN), alarmTimeText(dateTime)); + if (parent->column(AlarmListView::TIME_TO_COLUMN) >= 0) + { + QString tta = timeToAlarmText(now); + setText(parent->column(AlarmListView::TIME_TO_COLUMN), tta); + mTimeToAlarmShown = !tta.isNull(); + } + QTime t = dateTime.time(); + mDateTimeOrder.sprintf("%04d%03d%02d%02d", dateTime.date().year(), dateTime.date().dayOfYear(), + t.hour(), t.minute()); + + int repeatOrder = 0; + int repeatInterval = 0; + QString repeatText = event.recurrenceText(true); // text displayed in Repeat column + if (repeatText.isEmpty()) + repeatText = event.repetitionText(true); + if (event.repeatAtLogin()) + repeatOrder = 1; + else + { + repeatInterval = event.recurInterval(); + switch (event.recurType()) + { + case KARecurrence::MINUTELY: + repeatOrder = 2; + break; + case KARecurrence::DAILY: + repeatOrder = 3; + break; + case KARecurrence::WEEKLY: + repeatOrder = 4; + break; + case KARecurrence::MONTHLY_DAY: + case KARecurrence::MONTHLY_POS: + repeatOrder = 5; + break; + case KARecurrence::ANNUAL_DATE: + case KARecurrence::ANNUAL_POS: + repeatOrder = 6; + break; + case KARecurrence::NO_RECUR: + default: + break; + } + } + setText(parent->column(AlarmListView::REPEAT_COLUMN), repeatText); + mRepeatOrder.sprintf("%c%08d", '0' + repeatOrder, repeatInterval); + + bool showColour = (event.action() == KAEvent::MESSAGE || event.action() == KAEvent::FILE); + mColourOrder.sprintf("%06u", (showColour ? event.bgColour().rgb() : 0)); + + mTypeOrder.sprintf("%02d", event.action()); +} + +/****************************************************************************** +* Return the single line alarm summary text. +*/ +QString AlarmListViewItem::alarmText(const KAEvent& event) const +{ + bool truncated; + QString text = AlarmText::summary(event, 1, &truncated); + mMessageTruncated = truncated; + return text; +} + +/****************************************************************************** +* Return the alarm time text in the form "date time". +*/ +QString AlarmListViewItem::alarmTimeText(const DateTime& dateTime) const +{ + KLocale* locale = KGlobal::locale(); + QString dateTimeText = locale->formatDate(dateTime.date(), true); + if (!dateTime.isDateOnly()) + { + dateTimeText += ' '; + QString time = locale->formatTime(dateTime.time()); + if (mTimeHourPos == -2) + { + // Initialise the position of the hour within the time string, if leading + // zeroes are omitted, so that displayed times can be aligned with each other. + mTimeHourPos = -1; // default = alignment isn't possible/sensible + if (!QApplication::reverseLayout()) // don't try to align right-to-left languages + { + QString fmt = locale->timeFormat(); + int i = fmt.find(QRegExp("%[kl]")); // check if leading zeroes are omitted + if (i >= 0 && i == fmt.find('%')) // and whether the hour is first + mTimeHourPos = i; // yes, so need to align + } + } + if (mTimeHourPos >= 0 && (int)time.length() > mTimeHourPos + 1 + && time[mTimeHourPos].isDigit() && !time[mTimeHourPos + 1].isDigit()) + dateTimeText += '~'; // improve alignment of times with no leading zeroes + dateTimeText += time; + } + return dateTimeText + ' '; +} + +/****************************************************************************** +* Return the time-to-alarm text. +*/ +QString AlarmListViewItem::timeToAlarmText(const QDateTime& now) const +{ + if (event().expired()) + return QString::null; + DateTime dateTime = event().displayDateTime(); + if (dateTime.isDateOnly()) + { + int days = now.date().daysTo(dateTime.date()); + return i18n("n days", " %1d ").arg(days); + } + int mins = (now.secsTo(dateTime.dateTime()) + 59) / 60; + if (mins < 0) + return QString::null; + char minutes[3] = "00"; + minutes[0] = (mins%60) / 10 + '0'; + minutes[1] = (mins%60) % 10 + '0'; + if (mins < 24*60) + return i18n("hours:minutes", " %1:%2 ").arg(mins/60).arg(minutes); + int days = mins / (24*60); + mins = mins % (24*60); + return i18n("days hours:minutes", " %1d %2:%3 ").arg(days).arg(mins/60).arg(minutes); +} + +/****************************************************************************** +* Update the displayed time-to-alarm value. +* The updated value is only displayed if it is different from the existing value, +* or if 'forceDisplay' is true. +*/ +void AlarmListViewItem::updateTimeToAlarm(const QDateTime& now, bool forceDisplay) +{ + if (event().expired()) + { + if (forceDisplay || mTimeToAlarmShown) + { + setText(alarmListView()->column(AlarmListView::TIME_TO_COLUMN), QString::null); + mTimeToAlarmShown = false; + } + } + else + { + QString tta = timeToAlarmText(now); + if (forceDisplay || tta != text(alarmListView()->column(AlarmListView::TIME_TO_COLUMN))) + setText(alarmListView()->column(AlarmListView::TIME_TO_COLUMN), tta); + mTimeToAlarmShown = !tta.isNull(); + } +} + +/****************************************************************************** +* Paint one value in one of the columns in the list view. +*/ +void AlarmListViewItem::paintCell(QPainter* painter, const QColorGroup& cg, int column, int width, int /*align*/) +{ + const AlarmListView* listView = alarmListView(); + int margin = listView->itemMargin(); + QRect box(margin, margin, width - margin*2, height() - margin*2); // area within which to draw + bool selected = isSelected(); + QColor bgColour = selected ? cg.highlight() : cg.base(); + QColor fgColour = selected ? cg.highlightedText() + : !event().enabled() ? Preferences::disabledColour() + : event().expired() ? Preferences::expiredColour() : cg.text(); + painter->setPen(fgColour); + painter->fillRect(0, 0, width, height(), bgColour); + + if (column == listView->column(AlarmListView::TIME_COLUMN)) + { + int i = -1; + QString str = text(column); + if (mTimeHourPos >= 0) + { + // Need to pad out spacing to align times without leading zeroes + i = str.find(" ~"); + if (i >= 0) + { + if (mDigitWidth < 0) + mDigitWidth = painter->fontMetrics().width("0"); + QString date = str.left(i + 1); + int w = painter->fontMetrics().width(date) + mDigitWidth; + painter->drawText(box, AlignVCenter, date); + box.setLeft(box.left() + w); + painter->drawText(box, AlignVCenter, str.mid(i + 2)); + } + } + if (i < 0) + painter->drawText(box, AlignVCenter, str); + } + else if (column == listView->column(AlarmListView::TIME_TO_COLUMN)) + painter->drawText(box, AlignVCenter | AlignRight, text(column)); + else if (column == listView->column(AlarmListView::REPEAT_COLUMN)) + painter->drawText(box, AlignVCenter | AlignHCenter, text(column)); + else if (column == listView->column(AlarmListView::COLOUR_COLUMN)) + { + // Paint the cell the colour of the alarm message + if (event().action() == KAEvent::MESSAGE || event().action() == KAEvent::FILE) + painter->fillRect(box, event().bgColour()); + } + else if (column == listView->column(AlarmListView::TYPE_COLUMN)) + { + // Display the alarm type icon, horizontally and vertically centred in the cell + QPixmap* pixmap = eventIcon(); + QRect pixmapRect = pixmap->rect(); + int diff = box.height() - pixmap->height(); + if (diff < 0) + { + pixmapRect.setTop(-diff / 2); + pixmapRect.setHeight(box.height()); + } + QPoint iconTopLeft(box.left() + (box.width() - pixmapRect.width()) / 2, + box.top() + (diff > 0 ? diff / 2 : 0)); + painter->drawPixmap(iconTopLeft, *pixmap, pixmapRect); + } + else if (column == listView->column(AlarmListView::MESSAGE_COLUMN)) + { + if (!selected && listView->drawMessageInColour()) + { + painter->fillRect(box, event().bgColour()); + painter->setBackgroundColor(event().bgColour()); +// painter->setPen(event().fgColour()); + } + QString txt = text(column); + painter->drawText(box, AlignVCenter, txt); + mMessageColWidth = listView->fontMetrics().boundingRect(txt).width(); + } +} + +/****************************************************************************** +* Return the width needed for the icons in the alarm type column. +*/ +int AlarmListViewItem::typeIconWidth(AlarmListView* v) +{ + return iconWidth() + 2 * v->style().pixelMetric(QStyle::PM_DefaultFrameWidth); +} + +/****************************************************************************** +* Return the column sort order for one item in the list. +*/ +QString AlarmListViewItem::key(int column, bool) const +{ + AlarmListView* listView = alarmListView(); + if (column == listView->column(AlarmListView::TIME_COLUMN) + || column == listView->column(AlarmListView::TIME_TO_COLUMN)) + return mDateTimeOrder; + if (column == listView->column(AlarmListView::REPEAT_COLUMN)) + return mRepeatOrder; + if (column == listView->column(AlarmListView::COLOUR_COLUMN)) + return mColourOrder; + if (column == listView->column(AlarmListView::TYPE_COLUMN)) + return mTypeOrder; + return text(column).lower(); +} + + +/*============================================================================= += Class: AlarmListTooltip += Displays the full alarm text in a tooltip when necessary. +=============================================================================*/ + +/****************************************************************************** +* Displays the full alarm text in a tooltip, if not all the text is displayed. +*/ +void AlarmListTooltip::maybeTip(const QPoint& pt) +{ + AlarmListView* listView = (AlarmListView*)parentWidget()->parentWidget(); + int column = listView->column(AlarmListView::MESSAGE_COLUMN); + int xOffset = listView->contentsX(); + if (listView->header()->sectionAt(pt.x() + xOffset) == column) + { + AlarmListViewItem* item = (AlarmListViewItem*)listView->itemAt(pt); + if (item) + { + int columnX = listView->header()->sectionPos(column) - xOffset; + int columnWidth = listView->columnWidth(column); + int widthNeeded = item->messageColWidthNeeded(); + if (!item->messageTruncated() && columnWidth >= widthNeeded) + { + if (columnX + widthNeeded <= listView->viewport()->width()) + return; + } + QRect rect = listView->itemRect(item); + rect.setLeft(columnX); + rect.setWidth(columnWidth); + kdDebug(5950) << "AlarmListTooltip::maybeTip(): display\n"; + tip(rect, AlarmText::summary(item->event(), 10)); // display up to 10 lines of text + } + } +} + diff --git a/kalarm/alarmlistview.h b/kalarm/alarmlistview.h new file mode 100644 index 000000000..63d5bf0b0 --- /dev/null +++ b/kalarm/alarmlistview.h @@ -0,0 +1,133 @@ +/* + * alarmlistview.h - widget showing list of outstanding alarms + * Program: kalarm + * Copyright © 2001-2006 by 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. + */ + +#ifndef ALARMLISTVIEW_H +#define ALARMLISTVIEW_H + +#include "kalarm.h" +#include "eventlistviewbase.h" + +class AlarmListView; +class AlarmListTooltip; + + +class AlarmListViewItem : public EventListViewItemBase +{ + public: + AlarmListViewItem(AlarmListView* parent, const KAEvent&, const QDateTime& now); + QTime showTimeToAlarm(bool show); + void updateTimeToAlarm(const QDateTime& now, bool forceDisplay = false); + virtual void paintCell(QPainter*, const QColorGroup&, int column, int width, int align); + AlarmListView* alarmListView() const { return (AlarmListView*)listView(); } + bool messageTruncated() const { return mMessageTruncated; } + int messageColWidthNeeded() const { return mMessageColWidth; } + static int typeIconWidth(AlarmListView*); + // Overridden base class methods + AlarmListViewItem* nextSibling() const { return (AlarmListViewItem*)QListViewItem::nextSibling(); } + virtual QString key(int column, bool ascending) const; + protected: + virtual QString lastColumnText() const { return alarmText(event()); } + private: + QString alarmText(const KAEvent&) const; + QString alarmTimeText(const DateTime&) const; + QString timeToAlarmText(const QDateTime& now) const; + + static int mTimeHourPos; // position of hour within time string, or -1 if leading zeroes included + static int mDigitWidth; // display width of a digit + QString mDateTimeOrder; // controls ordering of date/time column + QString mRepeatOrder; // controls ordering of repeat column + QString mColourOrder; // controls ordering of colour column + QString mTypeOrder; // controls ordering of alarm type column + mutable int mMessageColWidth; // width needed to display complete message text + mutable bool mMessageTruncated; // multi-line message text has been truncated for the display + bool mTimeToAlarmShown; // relative alarm time is displayed +}; + + +class AlarmListView : public EventListViewBase +{ + Q_OBJECT // needed by QObject::isA() calls + public: + enum ColumnIndex { // default column order + TIME_COLUMN, TIME_TO_COLUMN, REPEAT_COLUMN, COLOUR_COLUMN, TYPE_COLUMN, MESSAGE_COLUMN, + COLUMN_COUNT + }; + + AlarmListView(const QValueList& order, QWidget* parent = 0, const char* name = 0); + ~AlarmListView(); + void showExpired(bool show) { mShowExpired = show; } + bool showingExpired() const { return mShowExpired; } + bool showingTimeTo() const { return columnWidth(mColumn[TIME_TO_COLUMN]); } + void selectTimeColumns(bool time, bool timeTo); + void updateTimeToAlarms(bool forceDisplay = false); + bool expired(AlarmListViewItem*) const; + bool drawMessageInColour() const { return mDrawMessageInColour; } + void setDrawMessageInColour(bool inColour) { mDrawMessageInColour = inColour; } + QValueList columnOrder() const; + int column(ColumnIndex i) const { return mColumn[i]; } + static bool dragging() { return mDragging; } + // Overridden base class methods + static void addEvent(const KAEvent&, EventListViewBase*); + static void modifyEvent(const KAEvent& e, EventListViewBase* selectionView) + { EventListViewBase::modifyEvent(e.id(), e, mInstanceList, selectionView); } + static void modifyEvent(const QString& oldEventID, const KAEvent& newEvent, EventListViewBase* selectionView) + { EventListViewBase::modifyEvent(oldEventID, newEvent, mInstanceList, selectionView); } + static void deleteEvent(const QString& eventID) + { EventListViewBase::deleteEvent(eventID, mInstanceList); } + static void undeleteEvent(const QString& oldEventID, const KAEvent& event, EventListViewBase* selectionView) + { EventListViewBase::modifyEvent(oldEventID, event, mInstanceList, selectionView); } + AlarmListViewItem* getEntry(const QString& eventID) { return (AlarmListViewItem*)EventListViewBase::getEntry(eventID); } + AlarmListViewItem* currentItem() const { return (AlarmListViewItem*)EventListViewBase::currentItem(); } + AlarmListViewItem* firstChild() const { return (AlarmListViewItem*)EventListViewBase::firstChild(); } + AlarmListViewItem* selectedItem() const { return (AlarmListViewItem*)EventListViewBase::selectedItem(); } + virtual void setSelected(QListViewItem* item, bool selected) { EventListViewBase::setSelected(item, selected); } + virtual void setSelected(AlarmListViewItem* item, bool selected) { EventListViewBase::setSelected(item, selected); } + virtual InstanceList instances() { return mInstanceList; } + + protected: + virtual void populate(); + EventListViewItemBase* createItem(const KAEvent&); + virtual QString whatsThisText(int column) const; + virtual bool shouldShowEvent(const KAEvent& e) const { return mShowExpired || !e.expired(); } + AlarmListViewItem* addEntry(const KAEvent& e, bool setSize = false) + { return addEntry(e, QDateTime::currentDateTime(), setSize); } + AlarmListViewItem* updateEntry(AlarmListViewItem* item, const KAEvent& newEvent, bool setSize = false) + { return (AlarmListViewItem*)EventListViewBase::updateEntry(item, newEvent, setSize); } + virtual void contentsMousePressEvent(QMouseEvent*); + virtual void contentsMouseMoveEvent(QMouseEvent*); + virtual void contentsMouseReleaseEvent(QMouseEvent*); + + private: + AlarmListViewItem* addEntry(const KAEvent&, const QDateTime& now, bool setSize = false, bool reselect = false); + + static InstanceList mInstanceList; // all current instances of this class + static bool mDragging; + int mColumn[COLUMN_COUNT]; // initial position of each column + int mTimeColumnHeaderWidth; + int mTimeToColumnHeaderWidth; + AlarmListTooltip* mTooltip; // tooltip for showing full text of alarm messages + QPoint mMousePressPos; // where the mouse left button was last pressed + bool mMousePressed; // true while the mouse left button is pressed + bool mDrawMessageInColour; + bool mShowExpired; // true to show expired alarms +}; + +#endif // ALARMLISTVIEW_H + diff --git a/kalarm/alarmtext.cpp b/kalarm/alarmtext.cpp new file mode 100644 index 000000000..2162f62a4 --- /dev/null +++ b/kalarm/alarmtext.cpp @@ -0,0 +1,288 @@ +/* + * alarmtext.cpp - text/email alarm text conversion + * Program: kalarm + * Copyright (C) 2004, 2005 by 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 "kalarm.h" +#include +#include + +#include "alarmevent.h" +#include "editdlg.h" +#include "alarmtext.h" + + +QString AlarmText::mFromPrefix; +QString AlarmText::mToPrefix; +QString AlarmText::mCcPrefix; +QString AlarmText::mDatePrefix; +QString AlarmText::mSubjectPrefix; +QString AlarmText::mFromPrefixEn = QString::fromLatin1("From:"); +QString AlarmText::mToPrefixEn = QString::fromLatin1("To:"); +QString AlarmText::mCcPrefixEn = QString::fromLatin1("Cc:"); +QString AlarmText::mDatePrefixEn = QString::fromLatin1("Date:"); +QString AlarmText::mSubjectPrefixEn = QString::fromLatin1("Subject:"); + + +void AlarmText::setText(const QString& text) +{ + mBody = text; + mIsScript = text.startsWith(QString::fromLatin1("#!")); + mIsEmail = false; + mTo = mFrom = mCc = mTime = mSubject = QString::null; + mKMailSerialNum = 0; +} + +void AlarmText::setEmail(const QString& to, const QString& from, const QString& cc, const QString& time, + const QString& subject, const QString& body, unsigned long kmailSerialNumber) +{ + mIsScript = false; + mIsEmail = true; + mTo = to; + mFrom = from; + mCc = cc; + mTime = time; + mSubject = subject; + mBody = body; + mKMailSerialNum = kmailSerialNumber; +} + +/****************************************************************************** +* Return the text for a text message alarm, in display format. +*/ +QString AlarmText::displayText() const +{ + if (mIsEmail) + { + // Format the email into a text alarm + setUpTranslations(); + QString text; + text = mFromPrefix + '\t' + mFrom + '\n'; + text += mToPrefix + '\t' + mTo + '\n'; + if (!mCc.isEmpty()) + text += mCcPrefix + '\t' + mCc + '\n'; + if (!mTime.isEmpty()) + text += mDatePrefix + '\t' + mTime + '\n'; + text += mSubjectPrefix + '\t' + mSubject; + if (!mBody.isEmpty()) + { + text += "\n\n"; + text += mBody; + } + return text; + } + return mBody; +} + +/****************************************************************************** +* Return whether there is any text. +*/ +bool AlarmText::isEmpty() const +{ + if (!mBody.isEmpty()) + return false; + if (!mIsEmail) + return true; + return mFrom.isEmpty() && mTo.isEmpty() && mCc.isEmpty() && mTime.isEmpty() && mSubject.isEmpty(); +} + +/****************************************************************************** +* Check whether a text is an email. +*/ +bool AlarmText::checkIfEmail(const QString& text) +{ + QStringList lines = QStringList::split('\n', text); + return emailHeaderCount(lines); +} + +/****************************************************************************** +* Check whether a text is an email. +* Reply = number of email header lines, or 0 if not an email. +*/ +int AlarmText::emailHeaderCount(const QStringList& lines) +{ + setUpTranslations(); + int maxn = lines.count(); + if (maxn >= 4 + && lines[0].startsWith(mFromPrefix) + && lines[1].startsWith(mToPrefix)) + { + int n = 2; + if (lines[2].startsWith(mCcPrefix)) + ++n; + if (maxn > n + 1 + && lines[n].startsWith(mDatePrefix) + && lines[n+1].startsWith(mSubjectPrefix)) + return n+2; + } + return 0; +} + +/****************************************************************************** +* Check whether a text is an email, and if so return its headers or optionally +* only its subject line. +* Reply = headers/subject line, or QString::null if not the text of an email. +*/ +QString AlarmText::emailHeaders(const QString& text, bool subjectOnly) +{ + QStringList lines = QStringList::split('\n', text); + int n = emailHeaderCount(lines); + if (!n) + return QString::null; + if (subjectOnly) + return lines[n-1].mid(mSubjectPrefix.length()).stripWhiteSpace(); + QString h = lines[0]; + for (int i = 1; i < n; ++i) + { + h += '\n'; + h += lines[i]; + } + return h; +} + +/****************************************************************************** +* Translate an alarm calendar text to a display text. +* Translation is needed for email texts, since the alarm calendar stores +* untranslated email prefixes. +* 'email' is set to indicate whether it is an email text. +*/ +QString AlarmText::fromCalendarText(const QString& text, bool& email) +{ + QStringList lines = QStringList::split('\n', text); + int maxn = lines.count(); + if (maxn >= 4 + && lines[0].startsWith(mFromPrefixEn) + && lines[1].startsWith(mToPrefixEn)) + { + int n = 2; + if (lines[2].startsWith(mCcPrefixEn)) + ++n; + if (maxn > n + 1 + && lines[n].startsWith(mDatePrefixEn) + && lines[n+1].startsWith(mSubjectPrefixEn)) + { + setUpTranslations(); + QString dispText; + dispText = mFromPrefix + lines[0].mid(mFromPrefixEn.length()) + '\n'; + dispText += mToPrefix + lines[1].mid(mToPrefixEn.length()) + '\n'; + if (n == 3) + dispText += mCcPrefix + lines[2].mid(mCcPrefixEn.length()) + '\n'; + dispText += mDatePrefix + lines[n].mid(mDatePrefixEn.length()) + '\n'; + dispText += mSubjectPrefix + lines[n+1].mid(mSubjectPrefixEn.length()); + int i = text.find(mSubjectPrefixEn); + i = text.find('\n', i); + if (i > 0) + dispText += text.mid(i); + email = true; + return dispText; + } + } + email = false; + return text; +} + +/****************************************************************************** +* Return the text for a text message alarm, in alarm calendar format. +* (The prefix strings are untranslated in the calendar.) +*/ +QString AlarmText::toCalendarText(const QString& text) +{ + setUpTranslations(); + QStringList lines = QStringList::split('\n', text); + int maxn = lines.count(); + if (maxn >= 4 + && lines[0].startsWith(mFromPrefix) + && lines[1].startsWith(mToPrefix)) + { + int n = 2; + if (lines[2].startsWith(mCcPrefix)) + ++n; + if (maxn > n + 1 + && lines[n].startsWith(mDatePrefix) + && lines[n+1].startsWith(mSubjectPrefix)) + { + // Format the email into a text alarm + QString calText; + calText = mFromPrefixEn + lines[0].mid(mFromPrefix.length()) + '\n'; + calText += mToPrefixEn + lines[1].mid(mToPrefix.length()) + '\n'; + if (n == 3) + calText += mCcPrefixEn + lines[2].mid(mCcPrefix.length()) + '\n'; + calText += mDatePrefixEn + lines[n].mid(mDatePrefix.length()) + '\n'; + calText += mSubjectPrefixEn + lines[n+1].mid(mSubjectPrefix.length()); + int i = text.find(mSubjectPrefix); + i = text.find('\n', i); + if (i > 0) + calText += text.mid(i); + return calText; + } + } + return text; +} + +/****************************************************************************** +* Set up messages used by executeDropEvent() and emailHeaders(). +*/ +void AlarmText::setUpTranslations() +{ + if (mFromPrefix.isNull()) + { + mFromPrefix = EditAlarmDlg::i18n_EmailFrom(); + mToPrefix = EditAlarmDlg::i18n_EmailTo(); + mCcPrefix = i18n("Copy-to in email headers", "Cc:"); + mDatePrefix = i18n("Date:"); + mSubjectPrefix = EditAlarmDlg::i18n_EmailSubject(); + } +} + +/****************************************************************************** +* Return the alarm summary text for either single line or tooltip display. +* The maximum number of line returned is determined by 'maxLines'. +* If 'truncated' is non-null, it will be set true if the text returned has been +* truncated, other than to strip a trailing newline. +*/ +QString AlarmText::summary(const KAEvent& event, int maxLines, bool* truncated) +{ + QString text = (event.action() == KAEvent::EMAIL) ? event.emailSubject() : event.cleanText(); + if (event.action() == KAEvent::MESSAGE) + { + // If the message is the text of an email, return its headers or just subject line + QString subject = emailHeaders(text, (maxLines <= 1)); + if (!subject.isNull()) + { + if (truncated) + *truncated = true; + return subject; + } + } + if (truncated) + *truncated = false; + if (text.contains('\n') < maxLines) + return text; + int newline = -1; + for (int i = 0; i < maxLines; ++i) + { + newline = text.find('\n', newline + 1); + if (newline < 0) + return text; // not truncated after all !?! + } + if (newline == static_cast(text.length()) - 1) + return text.left(newline); // text ends in newline + if (truncated) + *truncated = true; + return text.left(newline + (maxLines <= 1 ? 0 : 1)) + QString::fromLatin1("..."); +} diff --git a/kalarm/alarmtext.h b/kalarm/alarmtext.h new file mode 100644 index 000000000..8d643b3cb --- /dev/null +++ b/kalarm/alarmtext.h @@ -0,0 +1,75 @@ +/* + * alarmtext.h - text/email alarm text conversion + * Program: kalarm + * Copyright (C) 2004, 2005 by 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. + */ + +#ifndef ALARMTEXT_H +#define ALARMTEXT_H + +#include +class QStringList; +class KAEvent; + + +class AlarmText +{ + public: + AlarmText(const QString& text = QString::null) { setText(text); } + void setText(const QString&); + void setScript(const QString& text) { setText(text); mIsScript = true; } + void setEmail(const QString& to, const QString& from, const QString& cc, const QString& time, + const QString& subject, const QString& body, unsigned long kmailSerialNumber = 0); + QString displayText() const; + QString calendarText() const; + QString to() const { return mTo; } + QString from() const { return mFrom; } + QString cc() const { return mCc; } + QString time() const { return mTime; } + QString subject() const { return mSubject; } + QString body() const { return mIsEmail ? mBody : QString::null; } + bool isEmpty() const; + bool isEmail() const { return mIsEmail; } + bool isScript() const { return mIsScript; } + unsigned long kmailSerialNumber() const { return mKMailSerialNum; } + static QString summary(const KAEvent&, int maxLines = 1, bool* truncated = 0); + static bool checkIfEmail(const QString&); + static QString emailHeaders(const QString&, bool subjectOnly); + static QString fromCalendarText(const QString&, bool& email); + static QString toCalendarText(const QString&); + + private: + static void setUpTranslations(); + static int emailHeaderCount(const QStringList&); + + static QString mFromPrefix; // translated header prefixes + static QString mToPrefix; + static QString mCcPrefix; + static QString mDatePrefix; + static QString mSubjectPrefix; + static QString mFromPrefixEn; // untranslated header prefixes + static QString mToPrefixEn; + static QString mCcPrefixEn; + static QString mDatePrefixEn; + static QString mSubjectPrefixEn; + QString mBody, mFrom, mTo, mCc, mTime, mSubject; + unsigned long mKMailSerialNum; // if email, message's KMail serial number, else 0 + bool mIsEmail; + bool mIsScript; +}; + +#endif // ALARMTEXT_H diff --git a/kalarm/alarmtimewidget.cpp b/kalarm/alarmtimewidget.cpp new file mode 100644 index 000000000..1e7e777b0 --- /dev/null +++ b/kalarm/alarmtimewidget.cpp @@ -0,0 +1,556 @@ +/* + * alarmtimewidget.cpp - alarm date/time entry widget + * Program: kalarm + * Copyright © 2001-2007 by 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 "kalarm.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "checkbox.h" +#include "dateedit.h" +#include "datetime.h" +#include "radiobutton.h" +#include "synchtimer.h" +#include "timeedit.h" +#include "timespinbox.h" +#include "alarmtimewidget.moc" + +static const QTime time_23_59(23, 59); + + +const int AlarmTimeWidget::maxDelayTime = 999*60 + 59; // < 1000 hours + +QString AlarmTimeWidget::i18n_w_TimeFromNow() { return i18n("Time from no&w:"); } +QString AlarmTimeWidget::i18n_TimeAfterPeriod() +{ + return i18n("Enter the length of time (in hours and minutes) after " + "the current time to schedule the alarm."); +} + + +/****************************************************************************** +* Construct a widget with a group box and title. +*/ +AlarmTimeWidget::AlarmTimeWidget(const QString& groupBoxTitle, int mode, QWidget* parent, const char* name) + : ButtonGroup(groupBoxTitle, parent, name), + mMinDateTimeIsNow(false), + mPastMax(false), + mMinMaxTimeSet(false) +{ + init(mode); +} + +/****************************************************************************** +* Construct a widget without a group box or title. +*/ +AlarmTimeWidget::AlarmTimeWidget(int mode, QWidget* parent, const char* name) + : ButtonGroup(parent, name), + mMinDateTimeIsNow(false), + mPastMax(false), + mMinMaxTimeSet(false) +{ + setFrameStyle(QFrame::NoFrame); + init(mode); +} + +void AlarmTimeWidget::init(int mode) +{ + static const QString recurText = i18n("For a simple repetition, enter the date/time of the first occurrence.\n" + "If a recurrence is configured, the start date/time will be adjusted " + "to the first recurrence on or after the entered date/time."); + + connect(this, SIGNAL(buttonSet(int)), SLOT(slotButtonSet(int))); + QBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint()); + if (!title().isEmpty()) + { + topLayout->setMargin(KDialog::marginHint()); + topLayout->addSpacing(fontMetrics().lineSpacing()/2); + } + + // At time radio button/label + mAtTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("&Defer to date/time:") : i18n("At &date/time:")), this, "atTimeRadio"); + mAtTimeRadio->setFixedSize(mAtTimeRadio->sizeHint()); + QWhatsThis::add(mAtTimeRadio, + ((mode & DEFER_TIME) ? i18n("Reschedule the alarm to the specified date and time.") + : i18n("Schedule the alarm at the specified date and time."))); + + // Date edit box + mDateEdit = new DateEdit(this); + mDateEdit->setFixedSize(mDateEdit->sizeHint()); + connect(mDateEdit, SIGNAL(dateEntered(const QDate&)), SLOT(dateTimeChanged())); + static const QString enterDateText = i18n("Enter the date to schedule the alarm."); + QWhatsThis::add(mDateEdit, ((mode & DEFER_TIME) ? enterDateText + : QString("%1\n%2").arg(enterDateText).arg(recurText))); + mAtTimeRadio->setFocusWidget(mDateEdit); + + // Time edit box and Any time checkbox + QHBox* timeBox = new QHBox(this); + timeBox->setSpacing(2*KDialog::spacingHint()); + mTimeEdit = new TimeEdit(timeBox); + mTimeEdit->setFixedSize(mTimeEdit->sizeHint()); + connect(mTimeEdit, SIGNAL(valueChanged(int)), SLOT(dateTimeChanged())); + static const QString enterTimeText = i18n("Enter the time to schedule the alarm."); + QWhatsThis::add(mTimeEdit, + ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(enterTimeText).arg(TimeSpinBox::shiftWhatsThis()) + : QString("%1\n%2\n\n%3").arg(enterTimeText).arg(recurText).arg(TimeSpinBox::shiftWhatsThis()))); + + mAnyTime = -1; // current status is uninitialised + if (mode & DEFER_TIME) + { + mAnyTimeAllowed = false; + mAnyTimeCheckBox = 0; + } + else + { + mAnyTimeAllowed = true; + mAnyTimeCheckBox = new CheckBox(i18n("An&y time"), timeBox); + mAnyTimeCheckBox->setFixedSize(mAnyTimeCheckBox->sizeHint()); + connect(mAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool))); + QWhatsThis::add(mAnyTimeCheckBox, i18n("Schedule the alarm for any time during the day")); + } + + // 'Time from now' radio button/label + mAfterTimeRadio = new RadioButton(((mode & DEFER_TIME) ? i18n("Defer for time &interval:") : i18n_w_TimeFromNow()), + this, "afterTimeRadio"); + mAfterTimeRadio->setFixedSize(mAfterTimeRadio->sizeHint()); + QWhatsThis::add(mAfterTimeRadio, + ((mode & DEFER_TIME) ? i18n("Reschedule the alarm for the specified time interval after now.") + : i18n("Schedule the alarm after the specified time interval from now."))); + + // Delay time spin box + mDelayTimeEdit = new TimeSpinBox(1, maxDelayTime, this); + mDelayTimeEdit->setValue(maxDelayTime); + mDelayTimeEdit->setFixedSize(mDelayTimeEdit->sizeHint()); + connect(mDelayTimeEdit, SIGNAL(valueChanged(int)), SLOT(delayTimeChanged(int))); + QWhatsThis::add(mDelayTimeEdit, + ((mode & DEFER_TIME) ? QString("%1\n\n%2").arg(i18n_TimeAfterPeriod()).arg(TimeSpinBox::shiftWhatsThis()) + : QString("%1\n%2\n\n%3").arg(i18n_TimeAfterPeriod()).arg(recurText).arg(TimeSpinBox::shiftWhatsThis()))); + mAfterTimeRadio->setFocusWidget(mDelayTimeEdit); + + // Set up the layout, either narrow or wide + if (mode & NARROW) + { + QGridLayout* grid = new QGridLayout(topLayout, 2, 2, KDialog::spacingHint()); + grid->addWidget(mAtTimeRadio, 0, 0); + grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto); + grid->addWidget(timeBox, 1, 1, Qt::AlignAuto); + grid->setColStretch(2, 1); + topLayout->addStretch(); + QBoxLayout* layout = new QHBoxLayout(topLayout, KDialog::spacingHint()); + layout->addWidget(mAfterTimeRadio); + layout->addWidget(mDelayTimeEdit); + layout->addStretch(); + } + else + { + QGridLayout* grid = new QGridLayout(topLayout, 2, 3, KDialog::spacingHint()); + grid->addWidget(mAtTimeRadio, 0, 0, Qt::AlignAuto); + grid->addWidget(mDateEdit, 0, 1, Qt::AlignAuto); + grid->addWidget(timeBox, 0, 2, Qt::AlignAuto); + grid->setRowStretch(0, 1); + grid->addWidget(mAfterTimeRadio, 1, 0, Qt::AlignAuto); + grid->addWidget(mDelayTimeEdit, 1, 1, Qt::AlignAuto); + grid->setColStretch(3, 1); + topLayout->addStretch(); + } + + // Initialise the radio button statuses + setButton(id(mAtTimeRadio)); + + // Timeout every minute to update alarm time fields. + MinuteTimer::connect(this, SLOT(slotTimer())); +} + +/****************************************************************************** +* Set or clear read-only status for the controls +*/ +void AlarmTimeWidget::setReadOnly(bool ro) +{ + mAtTimeRadio->setReadOnly(ro); + mDateEdit->setReadOnly(ro); + mTimeEdit->setReadOnly(ro); + if (mAnyTimeCheckBox) + mAnyTimeCheckBox->setReadOnly(ro); + mAfterTimeRadio->setReadOnly(ro); + mDelayTimeEdit->setReadOnly(ro); +} + +/****************************************************************************** +* Select the "Time from now" radio button. +*/ +void AlarmTimeWidget::selectTimeFromNow(int minutes) +{ + mAfterTimeRadio->setChecked(true); + slotButtonSet(1); + if (minutes > 0) + mDelayTimeEdit->setValue(minutes); +} + +/****************************************************************************** +* Fetch the entered date/time. +* If 'checkExpired' is true and the entered value <= current time, an error occurs. +* If 'minsFromNow' is non-null, it is set to the number of minutes' delay selected, +* or to zero if a date/time was entered. +* In this case, if 'showErrorMessage' is true, output an error message. +* 'errorWidget' if non-null, is set to point to the widget containing the error. +* Reply = invalid date/time if error. +*/ +DateTime AlarmTimeWidget::getDateTime(int* minsFromNow, bool checkExpired, bool showErrorMessage, QWidget** errorWidget) const +{ + if (minsFromNow) + *minsFromNow = 0; + if (errorWidget) + *errorWidget = 0; + QTime nowt = QTime::currentTime(); + QDateTime now(QDate::currentDate(), QTime(nowt.hour(), nowt.minute())); + if (mAtTimeRadio->isOn()) + { + bool anyTime = mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked(); + if (!mDateEdit->isValid() || !mTimeEdit->isValid()) + { + // The date and/or time is invalid + if (!mDateEdit->isValid()) + { + if (showErrorMessage) + KMessageBox::sorry(const_cast(this), i18n("Invalid date")); + if (errorWidget) + *errorWidget = mDateEdit; + } + else + { + if (showErrorMessage) + KMessageBox::sorry(const_cast(this), i18n("Invalid time")); + if (errorWidget) + *errorWidget = mTimeEdit; + } + return DateTime(); + } + + DateTime result; + if (anyTime) + { + result = mDateEdit->date(); + if (checkExpired && result.date() < now.date()) + { + if (showErrorMessage) + KMessageBox::sorry(const_cast(this), i18n("Alarm date has already expired")); + if (errorWidget) + *errorWidget = mDateEdit; + return DateTime(); + } + } + else + { + result.set(mDateEdit->date(), mTimeEdit->time()); + if (checkExpired && result <= now.addSecs(1)) + { + if (showErrorMessage) + KMessageBox::sorry(const_cast(this), i18n("Alarm time has already expired")); + if (errorWidget) + *errorWidget = mTimeEdit; + return DateTime(); + } + } + return result; + } + else + { + if (!mDelayTimeEdit->isValid()) + { + if (showErrorMessage) + KMessageBox::sorry(const_cast(this), i18n("Invalid time")); + if (errorWidget) + *errorWidget = mDelayTimeEdit; + return DateTime(); + } + int delayMins = mDelayTimeEdit->value(); + if (minsFromNow) + *minsFromNow = delayMins; + return now.addSecs(delayMins * 60); + } +} + +/****************************************************************************** +* Set the date/time. +*/ +void AlarmTimeWidget::setDateTime(const DateTime& dt) +{ + if (dt.date().isValid()) + { + mTimeEdit->setValue(dt.time()); + mDateEdit->setDate(dt.date()); + dateTimeChanged(); // update the delay time edit box + } + else + { + mTimeEdit->setValid(false); + mDateEdit->setInvalid(); + mDelayTimeEdit->setValid(false); + } + if (mAnyTimeCheckBox) + { + bool anyTime = dt.isDateOnly(); + if (anyTime) + mAnyTimeAllowed = true; + mAnyTimeCheckBox->setChecked(anyTime); + setAnyTime(); + } +} + +/****************************************************************************** +* Set the minimum date/time to track the current time. +*/ +void AlarmTimeWidget::setMinDateTimeIsCurrent() +{ + mMinDateTimeIsNow = true; + mMinDateTime = QDateTime(); + QDateTime now = QDateTime::currentDateTime(); + mDateEdit->setMinDate(now.date()); + setMaxMinTimeIf(now); +} + +/****************************************************************************** +* Set the minimum date/time, adjusting the entered date/time if necessary. +* If 'dt' is invalid, any current minimum date/time is cleared. +*/ +void AlarmTimeWidget::setMinDateTime(const QDateTime& dt) +{ + mMinDateTimeIsNow = false; + mMinDateTime = dt; + mDateEdit->setMinDate(dt.date()); + setMaxMinTimeIf(QDateTime::currentDateTime()); +} + +/****************************************************************************** +* Set the maximum date/time, adjusting the entered date/time if necessary. +* If 'dt' is invalid, any current maximum date/time is cleared. +*/ +void AlarmTimeWidget::setMaxDateTime(const DateTime& dt) +{ + mPastMax = false; + if (dt.isValid() && dt.isDateOnly()) + mMaxDateTime = dt.dateTime().addSecs(24*3600 - 60); + else + mMaxDateTime = dt.dateTime(); + mDateEdit->setMaxDate(mMaxDateTime.date()); + QDateTime now = QDateTime::currentDateTime(); + setMaxMinTimeIf(now); + setMaxDelayTime(now); +} + +/****************************************************************************** +* If the minimum and maximum date/times fall on the same date, set the minimum +* and maximum times in the time edit box. +*/ +void AlarmTimeWidget::setMaxMinTimeIf(const QDateTime& now) +{ + int mint = 0; + QTime maxt = time_23_59; + mMinMaxTimeSet = false; + if (mMaxDateTime.isValid()) + { + bool set = true; + QDateTime minDT; + if (mMinDateTimeIsNow) + minDT = now.addSecs(60); + else if (mMinDateTime.isValid()) + minDT = mMinDateTime; + else + set = false; + if (set && mMaxDateTime.date() == minDT.date()) + { + // The minimum and maximum times are on the same date, so + // constrain the time value. + mint = minDT.time().hour()*60 + minDT.time().minute(); + maxt = mMaxDateTime.time(); + mMinMaxTimeSet = true; + } + } + mTimeEdit->setMinValue(mint); + mTimeEdit->setMaxValue(maxt); + mTimeEdit->setWrapping(!mint && maxt == time_23_59); +} + +/****************************************************************************** +* Set the maximum value for the delay time edit box, depending on the maximum +* value for the date/time. +*/ +void AlarmTimeWidget::setMaxDelayTime(const QDateTime& now) +{ + int maxVal = maxDelayTime; + if (mMaxDateTime.isValid()) + { + if (now.date().daysTo(mMaxDateTime.date()) < 100) // avoid possible 32-bit overflow on secsTo() + { + QDateTime dt(now.date(), QTime(now.time().hour(), now.time().minute(), 0)); // round down to nearest minute + maxVal = dt.secsTo(mMaxDateTime) / 60; + if (maxVal > maxDelayTime) + maxVal = maxDelayTime; + } + } + mDelayTimeEdit->setMaxValue(maxVal); +} + +/****************************************************************************** +* Set the status for whether a time is specified, or just a date. +*/ +void AlarmTimeWidget::setAnyTime() +{ + int old = mAnyTime; + mAnyTime = (mAtTimeRadio->isOn() && mAnyTimeAllowed && mAnyTimeCheckBox && mAnyTimeCheckBox->isChecked()) ? 1 : 0; + if (mAnyTime != old) + emit anyTimeToggled(mAnyTime); +} + +/****************************************************************************** +* Enable/disable the "any time" checkbox. +*/ +void AlarmTimeWidget::enableAnyTime(bool enable) +{ + if (mAnyTimeCheckBox) + { + mAnyTimeAllowed = enable; + bool at = mAtTimeRadio->isOn(); + mAnyTimeCheckBox->setEnabled(enable && at); + if (at) + mTimeEdit->setEnabled(!enable || !mAnyTimeCheckBox->isChecked()); + setAnyTime(); + } +} + +/****************************************************************************** +* Called every minute to update the alarm time data entry fields. +* If the maximum date/time has been reached, a 'pastMax()' signal is emitted. +*/ +void AlarmTimeWidget::slotTimer() +{ + QDateTime now; + if (mMinDateTimeIsNow) + { + // Make sure that the minimum date is updated when the day changes + now = QDateTime::currentDateTime(); + mDateEdit->setMinDate(now.date()); + } + if (mMaxDateTime.isValid()) + { + if (!now.isValid()) + now = QDateTime::currentDateTime(); + if (!mPastMax) + { + // Check whether the maximum date/time has now been reached + if (now.date() >= mMaxDateTime.date()) + { + // The current date has reached or has passed the maximum date + if (now.date() > mMaxDateTime.date() + || !mAnyTime && now.time() > mTimeEdit->maxTime()) + { + mPastMax = true; + emit pastMax(); + } + else if (mMinDateTimeIsNow && !mMinMaxTimeSet) + { + // The minimum date/time tracks the clock, so set the minimum + // and maximum times + setMaxMinTimeIf(now); + } + } + } + setMaxDelayTime(now); + } + + if (mAtTimeRadio->isOn()) + dateTimeChanged(); + else + delayTimeChanged(mDelayTimeEdit->value()); +} + + +/****************************************************************************** +* Called when the At or After time radio button states have been set. +* Updates the appropriate edit box. +*/ +void AlarmTimeWidget::slotButtonSet(int) +{ + bool at = mAtTimeRadio->isOn(); + mDateEdit->setEnabled(at); + mTimeEdit->setEnabled(at && (!mAnyTimeAllowed || !mAnyTimeCheckBox || !mAnyTimeCheckBox->isChecked())); + if (mAnyTimeCheckBox) + mAnyTimeCheckBox->setEnabled(at && mAnyTimeAllowed); + // Ensure that the value of the delay edit box is > 0. + QDateTime dt(mDateEdit->date(), mTimeEdit->time()); + int minutes = (QDateTime::currentDateTime().secsTo(dt) + 59) / 60; + if (minutes <= 0) + mDelayTimeEdit->setValid(true); + mDelayTimeEdit->setEnabled(!at); + setAnyTime(); +} + +/****************************************************************************** +* Called after the mAnyTimeCheckBox checkbox has been toggled. +*/ +void AlarmTimeWidget::slotAnyTimeToggled(bool on) +{ + mTimeEdit->setEnabled((!mAnyTimeAllowed || !on) && mAtTimeRadio->isOn()); + setAnyTime(); +} + +/****************************************************************************** +* Called when the date or time edit box values have changed. +* Updates the time delay edit box accordingly. +*/ +void AlarmTimeWidget::dateTimeChanged() +{ + QDateTime dt(mDateEdit->date(), mTimeEdit->time()); + int minutes = (QDateTime::currentDateTime().secsTo(dt) + 59) / 60; + bool blocked = mDelayTimeEdit->signalsBlocked(); + mDelayTimeEdit->blockSignals(true); // prevent infinite recursion between here and delayTimeChanged() + if (minutes <= 0 || minutes > mDelayTimeEdit->maxValue()) + mDelayTimeEdit->setValid(false); + else + mDelayTimeEdit->setValue(minutes); + mDelayTimeEdit->blockSignals(blocked); +} + +/****************************************************************************** +* Called when the delay time edit box value has changed. +* Updates the Date and Time edit boxes accordingly. +*/ +void AlarmTimeWidget::delayTimeChanged(int minutes) +{ + if (mDelayTimeEdit->isValid()) + { + QDateTime dt = QDateTime::currentDateTime().addSecs(minutes * 60); + bool blockedT = mTimeEdit->signalsBlocked(); + bool blockedD = mDateEdit->signalsBlocked(); + mTimeEdit->blockSignals(true); // prevent infinite recursion between here and dateTimeChanged() + mDateEdit->blockSignals(true); + mTimeEdit->setValue(dt.time()); + mDateEdit->setDate(dt.date()); + mTimeEdit->blockSignals(blockedT); + mDateEdit->blockSignals(blockedD); + } +} diff --git a/kalarm/alarmtimewidget.h b/kalarm/alarmtimewidget.h new file mode 100644 index 000000000..0a93e9cf4 --- /dev/null +++ b/kalarm/alarmtimewidget.h @@ -0,0 +1,94 @@ +/* + * alarmtimewidget.h - alarm date/time entry widget + * Program: kalarm + * Copyright © 2001-2006 by 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. + */ + +#ifndef ALARMTIMEWIDGET_H +#define ALARMTIMEWIDGET_H + +#include "buttongroup.h" +#include "datetime.h" + +class RadioButton; +class CheckBox; +class DateEdit; +class TimeEdit; +class TimeSpinBox; + + +class AlarmTimeWidget : public ButtonGroup +{ + Q_OBJECT + public: + enum { // 'mode' values for constructor. May be OR'ed together. + AT_TIME = 0x00, // "At ..." + DEFER_TIME = 0x01, // "Defer to ..." + NARROW = 0x02 // make a narrow widget + }; + AlarmTimeWidget(const QString& groupBoxTitle, int mode, QWidget* parent = 0, const char* name = 0); + AlarmTimeWidget(int mode, QWidget* parent = 0, const char* name = 0); + DateTime getDateTime(int* minsFromNow = 0, bool checkExpired = true, bool showErrorMessage = true, QWidget** errorWidget = 0) const; + void setDateTime(const DateTime&); + void setMinDateTimeIsCurrent(); + void setMinDateTime(const QDateTime& = QDateTime()); + void setMaxDateTime(const DateTime& = DateTime()); + const QDateTime& maxDateTime() const { return mMaxDateTime; } + void setReadOnly(bool); + bool anyTime() const { return mAnyTime; } + void enableAnyTime(bool enable); + void selectTimeFromNow(int minutes = 0); + QSize sizeHint() const { return minimumSizeHint(); } + + static QString i18n_w_TimeFromNow(); // text of 'Time from now:' radio button, with 'w' shortcut + static QString i18n_TimeAfterPeriod(); + static const int maxDelayTime; // maximum time from now + + signals: + void anyTimeToggled(bool anyTime); + void pastMax(); + + protected slots: + void slotTimer(); + void slotButtonSet(int id); + void dateTimeChanged(); + void delayTimeChanged(int); + void slotAnyTimeToggled(bool); + + private: + void init(int mode); + void setAnyTime(); + void setMaxDelayTime(const QDateTime& now); + void setMaxMinTimeIf(const QDateTime& now); + + RadioButton* mAtTimeRadio; + RadioButton* mAfterTimeRadio; + DateEdit* mDateEdit; + TimeEdit* mTimeEdit; + TimeSpinBox* mDelayTimeEdit; + CheckBox* mAnyTimeCheckBox; + QDateTime mMinDateTime; // earliest allowed date/time + QDateTime mMaxDateTime; // latest allowed date/time + int mAnyTime; // 0 = date/time is specified, 1 = only a date, -1 = uninitialised + bool mAnyTimeAllowed; // 'mAnyTimeCheckBox' is enabled + bool mMinDateTimeIsNow; // earliest allowed date/time is the current time + bool mPastMax; // current time is past the maximum date/time + bool mMinMaxTimeSet; // limits have been set for the time edit control + bool mTimerSyncing; // mTimer is not yet synchronised to the minute boundary +}; + +#endif // ALARMTIMEWIDGET_H diff --git a/kalarm/birthdaydlg.cpp b/kalarm/birthdaydlg.cpp new file mode 100644 index 000000000..3c4f28ab0 --- /dev/null +++ b/kalarm/birthdaydlg.cpp @@ -0,0 +1,427 @@ +/* + * birthdaydlg.cpp - dialog to pick birthdays from address book + * Program: kalarm + * Copyright © 2002-2008 by 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 "kalarm.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alarmcalendar.h" +#include "checkbox.h" +#include "colourcombo.h" +#include "editdlg.h" +#include "fontcolourbutton.h" +#include "kalarmapp.h" +#include "latecancel.h" +#include "preferences.h" +#include "reminder.h" +#include "repetition.h" +#include "shellprocess.h" +#include "soundpicker.h" +#include "specialactions.h" +#include "birthdaydlg.moc" + +using namespace KCal; + + +class AddresseeItem : public QListViewItem +{ + public: + enum columns { NAME = 0, BIRTHDAY = 1 }; + AddresseeItem(QListView* parent, const QString& name, const QDate& birthday); + QDate birthday() const { return mBirthday; } + virtual QString key(int column, bool ascending) const; + private: + QDate mBirthday; + QString mBirthdayOrder; +}; + + +const KABC::AddressBook* BirthdayDlg::mAddressBook = 0; + + +BirthdayDlg::BirthdayDlg(QWidget* parent) + : KDialogBase(KDialogBase::Plain, i18n("Import Birthdays From KAddressBook"), Ok|Cancel, Ok, parent, "BirthdayDlg"), + mSpecialActionsButton(0) +{ + QWidget* topWidget = plainPage(); + QBoxLayout* topLayout = new QVBoxLayout(topWidget); + topLayout->setSpacing(spacingHint()); + + // Prefix and suffix to the name in the alarm text + // Get default prefix and suffix texts from config file + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + mPrefixText = config->readEntry(QString::fromLatin1("BirthdayPrefix"), i18n("Birthday: ")); + mSuffixText = config->readEntry(QString::fromLatin1("BirthdaySuffix")); + + QGroupBox* textGroup = new QGroupBox(2, Qt::Horizontal, i18n("Alarm Text"), topWidget); + topLayout->addWidget(textGroup); + QLabel* label = new QLabel(i18n("Pre&fix:"), textGroup); + mPrefix = new BLineEdit(mPrefixText, textGroup); + mPrefix->setMinimumSize(mPrefix->sizeHint()); + label->setBuddy(mPrefix); + connect(mPrefix, SIGNAL(focusLost()), SLOT(slotTextLostFocus())); + QWhatsThis::add(mPrefix, + i18n("Enter text to appear before the person's name in the alarm message, " + "including any necessary trailing spaces.")); + + label = new QLabel(i18n("S&uffix:"), textGroup); + mSuffix = new BLineEdit(mSuffixText, textGroup); + mSuffix->setMinimumSize(mSuffix->sizeHint()); + label->setBuddy(mSuffix); + connect(mSuffix, SIGNAL(focusLost()), SLOT(slotTextLostFocus())); + QWhatsThis::add(mSuffix, + i18n("Enter text to appear after the person's name in the alarm message, " + "including any necessary leading spaces.")); + + QGroupBox* group = new QGroupBox(1, Qt::Horizontal, i18n("Select Birthdays"), topWidget); + topLayout->addWidget(group); + mAddresseeList = new BListView(group); + mAddresseeList->setMultiSelection(true); + mAddresseeList->setSelectionMode(QListView::Extended); + mAddresseeList->setAllColumnsShowFocus(true); + mAddresseeList->setFullWidth(true); + mAddresseeList->addColumn(i18n("Name")); + mAddresseeList->addColumn(i18n("Birthday")); + connect(mAddresseeList, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); + QWhatsThis::add(mAddresseeList, + i18n("Select birthdays to set alarms for.\n" + "This list shows all birthdays in KAddressBook except those for which alarms already exist.\n\n" + "You can select multiple birthdays at one time by dragging the mouse over the list, " + "or by clicking the mouse while pressing Ctrl or Shift.")); + + group = new QGroupBox(i18n("Alarm Configuration"), topWidget); + topLayout->addWidget(group); + QBoxLayout* groupLayout = new QVBoxLayout(group, marginHint(), spacingHint()); + groupLayout->addSpacing(fontMetrics().lineSpacing()/2); + + // Font and colour choice button and sample text + mFontColourButton = new FontColourButton(group); + mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height() * 3/2); + groupLayout->addWidget(mFontColourButton); + + // Sound checkbox and file selector + mSoundPicker = new SoundPicker(group); + mSoundPicker->setFixedSize(mSoundPicker->sizeHint()); + groupLayout->addWidget(mSoundPicker, 0, Qt::AlignAuto); + + // How much to advance warning to give + mReminder = new Reminder(i18n("&Reminder"), + i18n("Check to display a reminder in advance of the birthday."), + i18n("Enter the number of days before each birthday to display a reminder. " + "This is in addition to the alarm which is displayed on the birthday."), + false, false, group); + mReminder->setFixedSize(mReminder->sizeHint()); + mReminder->setMaximum(0, 364); + mReminder->setMinutes(0, true); + groupLayout->addWidget(mReminder, 0, Qt::AlignAuto); + + // Acknowledgement confirmation required - default = no confirmation + QHBoxLayout* layout = new QHBoxLayout(groupLayout, 2*spacingHint()); + mConfirmAck = EditAlarmDlg::createConfirmAckCheckbox(group); + layout->addWidget(mConfirmAck); + layout->addSpacing(2*spacingHint()); + layout->addStretch(); + + if (ShellProcess::authorised()) // don't display if shell commands not allowed (e.g. kiosk mode) + { + // Special actions button + mSpecialActionsButton = new SpecialActionsButton(i18n("Special Actions..."), group); + layout->addWidget(mSpecialActionsButton); + } + + // Late display checkbox - default = allow late display + layout = new QHBoxLayout(groupLayout, 2*spacingHint()); + mLateCancel = new LateCancelSelector(false, group); + layout->addWidget(mLateCancel); + layout->addStretch(); + + // Sub-repetition button + mSubRepetition = new RepetitionButton(i18n("Sub-Repetition"), false, group); + mSubRepetition->set(0, 0, true, 364*24*60); + QWhatsThis::add(mSubRepetition, i18n("Set up an additional alarm repetition")); + layout->addWidget(mSubRepetition); + + // Set the values to their defaults + mFontColourButton->setDefaultFont(); + mFontColourButton->setBgColour(Preferences::defaultBgColour()); + mFontColourButton->setFgColour(Preferences::defaultFgColour()); // set colour before setting alarm type buttons + mLateCancel->setMinutes(Preferences::defaultLateCancel(), true, TimePeriod::DAYS); + mConfirmAck->setChecked(Preferences::defaultConfirmAck()); + mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(), + Preferences::defaultSoundVolume(), -1, 0, Preferences::defaultSoundRepeat()); + if (mSpecialActionsButton) + mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction()); + + // Initialise the birthday selection list and disable the OK button + loadAddressBook(); +} + +/****************************************************************************** +* Load the address book in preparation for displaying the birthday selection list. +*/ +void BirthdayDlg::loadAddressBook() +{ + if (!mAddressBook) + { +#if KDE_IS_VERSION(3,1,90) + mAddressBook = KABC::StdAddressBook::self(true); + if (mAddressBook) + connect(mAddressBook, SIGNAL(addressBookChanged(AddressBook*)), SLOT(updateSelectionList())); +#else + mAddressBook = KABC::StdAddressBook::self(); + if (mAddressBook) + updateSelectionList(); +#endif + } + else + updateSelectionList(); + if (!mAddressBook) + KMessageBox::error(this, i18n("Error reading address book")); +} + +/****************************************************************************** +* Close the address book.This is called at program termination. +*/ +void BirthdayDlg::close() +{ + if (mAddressBook) + { + KABC::StdAddressBook::close(); + mAddressBook = 0; + } +} + +/****************************************************************************** +* Initialise or update the birthday selection list by fetching all birthdays +* from the address book and displaying those which do not already have alarms. +*/ +void BirthdayDlg::updateSelectionList() +{ + // Compile a list of all pending alarm messages which look like birthdays + QStringList messageList; + KAEvent event; + Event::List events = AlarmCalendar::activeCalendar()->events(); + for (Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + Event* kcalEvent = *it; + event.set(*kcalEvent); + if (event.action() == KAEvent::MESSAGE + && event.recurType() == KARecurrence::ANNUAL_DATE + && (mPrefixText.isEmpty() || event.message().startsWith(mPrefixText))) + messageList.append(event.message()); + } + + // Fetch all birthdays from the address book + for (KABC::AddressBook::ConstIterator abit = mAddressBook->begin(); abit != mAddressBook->end(); ++abit) + { + const KABC::Addressee& addressee = *abit; + if (addressee.birthday().isValid()) + { + // Create a list entry for this birthday + QDate birthday = addressee.birthday().date(); + QString name = addressee.nickName(); + if (name.isEmpty()) + name = addressee.realName(); + // Check if the birthday already has an alarm + QString text = mPrefixText + name + mSuffixText; + bool alarmExists = (messageList.find(text) != messageList.end()); + // Check if the birthday is already in the selection list + bool inSelectionList = false; + AddresseeItem* item = 0; + for (QListViewItem* qitem = mAddresseeList->firstChild(); qitem; qitem = qitem->nextSibling()) + { + item = dynamic_cast(qitem); + if (item && item->text(AddresseeItem::NAME) == name && item->birthday() == birthday) + { + inSelectionList = true; + break; + } + } + + if (alarmExists && inSelectionList) + delete item; // alarm exists, so remove from selection list + else if (!alarmExists && !inSelectionList) + new AddresseeItem(mAddresseeList, name, birthday); // add to list + } + } +// mAddresseeList->setUpdatesEnabled(true); + + // Enable/disable OK button according to whether anything is currently selected + bool selection = false; + for (QListViewItem* item = mAddresseeList->firstChild(); item; item = item->nextSibling()) + if (mAddresseeList->isSelected(item)) + { + selection = true; + break; + } + enableButtonOK(selection); +} + +/****************************************************************************** +* Return a list of events for birthdays chosen. +*/ +QValueList BirthdayDlg::events() const +{ + QValueList list; + QDate today = QDate::currentDate(); + QDateTime todayNoon(today, QTime(12, 0, 0)); + int thisYear = today.year(); + int reminder = mReminder->minutes(); + + for (QListViewItem* item = mAddresseeList->firstChild(); item; item = item->nextSibling()) + { + if (mAddresseeList->isSelected(item)) + { + AddresseeItem* aItem = dynamic_cast(item); + if (aItem) + { + QDate date = aItem->birthday(); + date.setYMD(thisYear, date.month(), date.day()); + if (date <= today) + date.setYMD(thisYear + 1, date.month(), date.day()); + KAEvent event(date, + mPrefix->text() + aItem->text(AddresseeItem::NAME) + mSuffix->text(), + mFontColourButton->bgColour(), mFontColourButton->fgColour(), + mFontColourButton->font(), KAEvent::MESSAGE, mLateCancel->minutes(), + mFlags); + float fadeVolume; + int fadeSecs; + float volume = mSoundPicker->volume(fadeVolume, fadeSecs); + event.setAudioFile(mSoundPicker->file(), volume, fadeVolume, fadeSecs); + QValueList months; + months.append(date.month()); + event.setRecurAnnualByDate(1, months, 0, Preferences::defaultFeb29Type(), -1, QDate()); + event.setRepetition(mSubRepetition->interval(), mSubRepetition->count()); + event.setNextOccurrence(todayNoon); + if (reminder) + event.setReminder(reminder, false); + if (mSpecialActionsButton) + event.setActions(mSpecialActionsButton->preAction(), + mSpecialActionsButton->postAction()); + list.append(event); + } + } + } + return list; +} + +/****************************************************************************** +* Called when the OK button is selected to import the selected birthdays. +*/ +void BirthdayDlg::slotOk() +{ + // Save prefix and suffix texts to use as future defaults + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + config->writeEntry(QString::fromLatin1("BirthdayPrefix"), mPrefix->text()); + config->writeEntry(QString::fromLatin1("BirthdaySuffix"), mSuffix->text()); + config->sync(); + + mFlags = (mSoundPicker->sound() == SoundPicker::BEEP ? KAEvent::BEEP : 0) + | (mSoundPicker->repeat() ? KAEvent::REPEAT_SOUND : 0) + | (mConfirmAck->isChecked() ? KAEvent::CONFIRM_ACK : 0) + | (mFontColourButton->defaultFont() ? KAEvent::DEFAULT_FONT : 0) + | KAEvent::ANY_TIME; + KDialogBase::slotOk(); +} + +/****************************************************************************** +* Called when the group of items selected changes. +* Enable/disable the OK button depending on whether anything is selected. +*/ +void BirthdayDlg::slotSelectionChanged() +{ + for (QListViewItem* item = mAddresseeList->firstChild(); item; item = item->nextSibling()) + if (mAddresseeList->isSelected(item)) + { + enableButtonOK(true); + return; + } + enableButtonOK(false); + +} + +/****************************************************************************** +* Called when the prefix or suffix text has lost keyboard focus. +* If the text has changed, re-evaluates the selection list according to the new +* birthday alarm text format. +*/ +void BirthdayDlg::slotTextLostFocus() +{ + QString prefix = mPrefix->text(); + QString suffix = mSuffix->text(); + if (prefix != mPrefixText || suffix != mSuffixText) + { + // Text has changed - re-evaluate the selection list + mPrefixText = prefix; + mSuffixText = suffix; + loadAddressBook(); + } +} + + +/*============================================================================= += Class: AddresseeItem +=============================================================================*/ + +AddresseeItem::AddresseeItem(QListView* parent, const QString& name, const QDate& birthday) + : QListViewItem(parent), + mBirthday(birthday) +{ + setText(NAME, name); + setText(BIRTHDAY, KGlobal::locale()->formatDate(mBirthday, true)); + mBirthdayOrder.sprintf("%04d%03d", mBirthday.year(), mBirthday.dayOfYear()); +} + +QString AddresseeItem::key(int column, bool) const +{ + if (column == BIRTHDAY) + return mBirthdayOrder; + return text(column).lower(); +} + + +/*============================================================================= += Class: BListView +=============================================================================*/ + +BListView::BListView(QWidget* parent, const char* name) + : KListView(parent, name) +{ + KAccel* accel = new KAccel(this); + accel->insert(KStdAccel::SelectAll, this, SLOT(slotSelectAll())); + accel->insert(KStdAccel::Deselect, this, SLOT(slotDeselect())); + accel->readSettings(); +} diff --git a/kalarm/birthdaydlg.h b/kalarm/birthdaydlg.h new file mode 100644 index 000000000..b8dd202c1 --- /dev/null +++ b/kalarm/birthdaydlg.h @@ -0,0 +1,103 @@ +/* + * birthdaydlg.h - dialog to pick birthdays from address book + * Program: kalarm + * Copyright © 2002-2004,2006,2008 by 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. + */ +#ifndef BIRTHDAYDLG_H +#define BIRTHDAYDLG_H + +#include +#include +#include + +#include "alarmevent.h" + +class QCheckBox; +class KListView; +class CheckBox; +class FontColourButton; +class SoundPicker; +class SpecialActionsButton; +class RepetitionButton; +class LateCancelSelector; +class Reminder; +namespace KABC { class AddressBook; } +class BLineEdit; +class BListView; + + +class BirthdayDlg : public KDialogBase +{ + Q_OBJECT + public: + BirthdayDlg(QWidget* parent = 0); + QValueList events() const; + static void close(); + + protected slots: + virtual void slotOk(); + + private slots: + void slotSelectionChanged(); + void slotTextLostFocus(); + void updateSelectionList(); + + private: + void loadAddressBook(); + + static const KABC::AddressBook* mAddressBook; + BListView* mAddresseeList; + BLineEdit* mPrefix; + BLineEdit* mSuffix; + Reminder* mReminder; + SoundPicker* mSoundPicker; + FontColourButton* mFontColourButton; + CheckBox* mConfirmAck; + LateCancelSelector* mLateCancel; + SpecialActionsButton* mSpecialActionsButton; + RepetitionButton* mSubRepetition; + QString mPrefixText; // last entered value of prefix text + QString mSuffixText; // last entered value of suffix text + int mFlags; // event flag bits +}; + + +class BLineEdit : public QLineEdit +{ + Q_OBJECT + public: + BLineEdit(QWidget* parent = 0, const char* name = 0) + : QLineEdit(parent, name) {} + BLineEdit(const QString& text, QWidget* parent = 0, const char* name = 0) + : QLineEdit(text, parent, name) {} + signals: + void focusLost(); + protected: + virtual void focusOutEvent(QFocusEvent*) { emit focusLost(); } +}; + +class BListView : public KListView +{ + Q_OBJECT + public: + BListView(QWidget* parent = 0, const char* name = 0); + public slots: + virtual void slotSelectAll() { selectAll(true); } + virtual void slotDeselect() { selectAll(false); } +}; + +#endif // BIRTHDAYDLG_H diff --git a/kalarm/calendarcompat.cpp b/kalarm/calendarcompat.cpp new file mode 100644 index 000000000..ada75e768 --- /dev/null +++ b/kalarm/calendarcompat.cpp @@ -0,0 +1,150 @@ +/* + * calendarcompat.cpp - compatibility for old calendar file formats + * Program: kalarm + * Copyright © 2001-2006 by 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 "kalarm.h" + +#include +#include +#include + +#include +#include +#include + +#include + +#include "alarmevent.h" +#include "functions.h" +#include "preferences.h" +#include "calendarcompat.h" + +using namespace KCal; + + +/****************************************************************************** +* Find the version of KAlarm which wrote the calendar file, and do any +* necessary conversions to the current format. The calendar is not saved - any +* conversions will only be saved if changes are made later. +*/ +void CalendarCompat::fix(KCal::Calendar& calendar, const QString& localFile) +{ + bool version057_UTC = false; + QString subVersion; + int version = readKAlarmVersion(calendar, subVersion); + if (!version) + { + // The calendar was created either by the current version of KAlarm, + // or another program, so don't do any conversions + return; + } + if (version == KAlarm::Version(0,5,7) && !localFile.isEmpty()) + { + // KAlarm version 0.5.7 - check whether times are stored in UTC, in which + // case it is the KDE 3.0.0 version, which needs adjustment of summer times. + version057_UTC = isUTC(localFile); + kdDebug(5950) << "CalendarCompat::fix(): KAlarm version 0.5.7 (" << (version057_UTC ? "" : "non-") << "UTC)\n"; + } + else + kdDebug(5950) << "CalendarCompat::fix(): KAlarm version " << version << endl; + + // Convert events to current KAlarm format for when calendar is saved + KAEvent::convertKCalEvents(calendar, version, version057_UTC); +} + +/****************************************************************************** +* Return the KAlarm version which wrote the calendar which has been loaded. +* The format is, for example, 000507 for 0.5.7. +* Reply = 0 if the calendar was created by the current version of KAlarm, +* KAlarm pre-0.3.5, or another program. +*/ +int CalendarCompat::readKAlarmVersion(KCal::Calendar& calendar, QString& subVersion) +{ + subVersion = QString::null; + const QString& prodid = calendar.productId(); + + // Find the KAlarm identifier + QString progname = QString::fromLatin1(" KAlarm "); + int i = prodid.find(progname, 0, false); + if (i < 0) + { + // Older versions used KAlarm's translated name in the product ID, which + // could have created problems using a calendar in different locales. + progname = QString(" ") + kapp->aboutData()->programName() + ' '; + i = prodid.find(progname, 0, false); + if (i < 0) + return 0; // calendar wasn't created by KAlarm + } + + // Extract the KAlarm version string + QString ver = prodid.mid(i + progname.length()).stripWhiteSpace(); + i = ver.find('/'); + int j = ver.find(' '); + if (j >= 0 && j < i) + i = j; + if (i <= 0) + return 0; // missing version string + ver = ver.left(i); // ver now contains the KAlarm version string + if (ver == KAlarm::currentCalendarVersionString()) + return 0; // the calendar is in the current KAlarm format + return KAlarm::getVersionNumber(ver, &subVersion); +} + +/****************************************************************************** + * Check whether the calendar file has its times stored as UTC times, + * indicating that it was written by the KDE 3.0.0 version of KAlarm 0.5.7. + * Reply = true if times are stored in UTC + * = false if the calendar is a vCalendar, times are not UTC, or any error occurred. + */ +bool CalendarCompat::isUTC(const QString& localFile) +{ + // Read the calendar file into a QString + QFile file(localFile); + if (!file.open(IO_ReadOnly)) + return false; + QTextStream ts(&file); + ts.setEncoding(QTextStream::Latin1); + QString text = ts.read(); + file.close(); + + // Extract the CREATED property for the first VEVENT from the calendar + QString VCALENDAR = QString::fromLatin1("BEGIN:VCALENDAR"); + QString VEVENT = QString::fromLatin1("BEGIN:VEVENT"); + QString CREATED = QString::fromLatin1("CREATED:"); + QStringList lines = QStringList::split(QChar('\n'), text); + for (QStringList::ConstIterator it = lines.begin(); it != lines.end(); ++it) + { + if ((*it).startsWith(VCALENDAR)) + { + while (++it != lines.end()) + { + if ((*it).startsWith(VEVENT)) + { + while (++it != lines.end()) + { + if ((*it).startsWith(CREATED)) + return (*it).endsWith("Z"); + } + } + } + break; + } + } + return false; +} diff --git a/kalarm/calendarcompat.h b/kalarm/calendarcompat.h new file mode 100644 index 000000000..a8cc3e812 --- /dev/null +++ b/kalarm/calendarcompat.h @@ -0,0 +1,40 @@ +/* + * calendarcompat.h - compatibility for old calendar file formats + * Program: kalarm + * Copyright © 2005,2007 by 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. + */ + +#ifndef CALENDARCOMPAT_H +#define CALENDARCOMPAT_H + +/* @file calendarcompat.h - compatibility for old calendar file formats */ + +#include +namespace KCal { class Calendar; } + + +class CalendarCompat +{ + public: + static void fix(KCal::Calendar&, const QString& localFile = QString::null); + + private: + static int readKAlarmVersion(KCal::Calendar&, QString& subVersion); + static bool isUTC(const QString& localFile); +}; + +#endif // CALENDARCOMPAT_H diff --git a/kalarm/configure.in.bot b/kalarm/configure.in.bot new file mode 100644 index 000000000..9af89064a --- /dev/null +++ b/kalarm/configure.in.bot @@ -0,0 +1,16 @@ +if test -z "$KMIX"; then + echo "" + echo "=================================================================" + echo "kmix (from the kdemultimedia package) is missing." + echo "KAlarm will not be able to set the absolute sound volume when" + echo "playing audio files until you install kmix." + echo "=================================================================" +fi +if test -z "$KTTSD"; then + echo "" + echo "=================================================================" + echo "kttsd (from the kdeaccessibility package) is missing." + echo "KAlarm will not be able to speak alarm messages until you install" + echo "kttsd together with appropriate speech synthesis application(s)." + echo "=================================================================" +fi diff --git a/kalarm/configure.in.in b/kalarm/configure.in.in new file mode 100644 index 000000000..fdf300906 --- /dev/null +++ b/kalarm/configure.in.in @@ -0,0 +1,2 @@ +KDE_FIND_PATH(kmix, KMIX, [$kde_default_bindirs]) +KDE_FIND_PATH(kttsd, KTTSD, [$kde_default_bindirs]) diff --git a/kalarm/daemon.cpp b/kalarm/daemon.cpp new file mode 100644 index 000000000..22f1fb36d --- /dev/null +++ b/kalarm/daemon.cpp @@ -0,0 +1,774 @@ +/* + * daemon.cpp - interface with alarm daemon + * Program: kalarm + * Copyright © 2001-2007 by 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 "kalarm.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "kalarmd/kalarmd.h" +#include "kalarmd/alarmdaemoniface.h" +#include "kalarmd/alarmdaemoniface_stub.h" +#include "kalarmd/alarmguiiface.h" + +#include "alarmcalendar.h" +#include "kalarmapp.h" +#include "preferences.h" +#include "daemon.moc" + + +static const int REGISTER_TIMEOUT = 20; // seconds to wait before assuming registration with daemon has failed +static const char* NOTIFY_DCOP_OBJECT = "notify"; // DCOP name of KAlarm's interface for notification by alarm daemon + +static QString expandURL(const QString& urlString); + + +/*============================================================================= += Class: NotificationHandler += Handles the the alarm daemon's client notification DCOP interface. +=============================================================================*/ + +class NotificationHandler : public QObject, virtual public AlarmGuiIface +{ + public: + NotificationHandler(); + private: + // DCOP interface + void alarmDaemonUpdate(int calendarStatus, const QString& calendarURL); + void handleEvent(const QString& calendarURL, const QString& eventID); + void registered(bool reregister, int result, int version); +}; + + +Daemon* Daemon::mInstance = 0; +NotificationHandler* Daemon::mDcopHandler = 0; +QValueList Daemon::mQueuedEvents; +QValueList Daemon::mSavingEvents; +QTimer* Daemon::mStartTimer = 0; +QTimer* Daemon::mRegisterTimer = 0; +QTimer* Daemon::mStatusTimer = 0; +int Daemon::mStatusTimerCount = 0; +int Daemon::mStatusTimerInterval; +int Daemon::mStartTimeout = 0; +Daemon::Status Daemon::mStatus = Daemon::STOPPED; +bool Daemon::mRunning = false; +bool Daemon::mCalendarDisabled = false; +bool Daemon::mEnableCalPending = false; +bool Daemon::mRegisterFailMsg = false; + +// How frequently to check the daemon's status after starting it. +// This is equal to the length of time we wait after the daemon is registered with DCOP +// before we assume that it is ready to accept DCOP calls. +static const int startCheckInterval = 500; // 500 milliseconds + + +/****************************************************************************** +* Initialise. +* A Daemon instance needs to be constructed only in order for slots to work. +* All external access is via static methods. +*/ +void Daemon::initialise() +{ + if (!mInstance) + mInstance = new Daemon(); + connect(AlarmCalendar::activeCalendar(), SIGNAL(calendarSaved(AlarmCalendar*)), mInstance, SLOT(slotCalendarSaved(AlarmCalendar*))); +} + +/****************************************************************************** +* Initialise the daemon status timer. +*/ +void Daemon::createDcopHandler() +{ + if (mDcopHandler) + return; + mDcopHandler = new NotificationHandler(); + // Check if the alarm daemon is running, but don't start it yet, since + // the program is still initialising. + mRunning = isRunning(false); + + mStatusTimerInterval = Preferences::daemonTrayCheckInterval(); + Preferences::connect(SIGNAL(preferencesChanged()), mInstance, SLOT(slotPreferencesChanged())); + + mStatusTimer = new QTimer(mInstance); + connect(mStatusTimer, SIGNAL(timeout()), mInstance, SLOT(timerCheckIfRunning())); + mStatusTimer->start(mStatusTimerInterval * 1000); // check regularly if daemon is running +} + +/****************************************************************************** +* Start the alarm daemon if necessary, and register this application with it. +* Reply = false if the daemon definitely couldn't be started or registered with. +*/ +bool Daemon::start() +{ + kdDebug(5950) << "Daemon::start()\n"; + updateRegisteredStatus(); + switch (mStatus) + { + case STOPPED: + { + if (mStartTimer) + return true; // we're currently waiting for the daemon to start + // Start the alarm daemon. It is a KUniqueApplication, which means that + // there is automatically only one instance of the alarm daemon running. + QString execStr = locate("exe", QString::fromLatin1(DAEMON_APP_NAME)); + if (execStr.isEmpty()) + { + KMessageBox::error(0, i18n("Alarm daemon not found.")); + kdError() << "Daemon::startApp(): " DAEMON_APP_NAME " not found" << endl; + return false; + } + KApplication::kdeinitExec(execStr); + kdDebug(5950) << "Daemon::start(): Alarm daemon started" << endl; + mStartTimeout = 5000/startCheckInterval + 1; // check daemon status for 5 seconds before giving up + mStartTimer = new QTimer(mInstance); + connect(mStartTimer, SIGNAL(timeout()), mInstance, SLOT(checkIfStarted())); + mStartTimer->start(startCheckInterval); + mInstance->checkIfStarted(); + return true; + } + case RUNNING: + return true; // we're waiting for the daemon to be completely ready + case READY: + // Daemon is ready. Register this application with it. + if (!registerWith(false)) + return false; + break; + case REGISTERED: + break; + } + return true; +} + +/****************************************************************************** +* Register this application with the alarm daemon, and tell it to load the +* calendar. +* Set 'reregister' true in order to notify the daemon of a change in the +* 'disable alarms if stopped' setting. +*/ +bool Daemon::registerWith(bool reregister) +{ + if (mRegisterTimer) + return true; + switch (mStatus) + { + case STOPPED: + case RUNNING: + return false; + case REGISTERED: + if (!reregister) + return true; + break; + case READY: + break; + } + + bool disabledIfStopped = theApp()->alarmsDisabledIfStopped(); + kdDebug(5950) << (reregister ? "Daemon::reregisterWith(): " : "Daemon::registerWith(): ") << (disabledIfStopped ? "NO_START" : "COMMAND_LINE") << endl; + QCString appname = kapp->aboutData()->appName(); + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + if (reregister) + s.registerChange(appname, !disabledIfStopped); + else + s.registerApp(appname, kapp->aboutData()->programName(), QCString(NOTIFY_DCOP_OBJECT), AlarmCalendar::activeCalendar()->urlString(), !disabledIfStopped); + if (!s.ok()) + { + kdError(5950) << "Daemon::registerWith(" << reregister << "): DCOP error" << endl; + registrationResult(reregister, KAlarmd::FAILURE); + return false; + } + mRegisterTimer = new QTimer(mInstance); + connect(mRegisterTimer, SIGNAL(timeout()), mInstance, SLOT(registerTimerExpired())); + mRegisterTimer->start(REGISTER_TIMEOUT * 1000); // wait for the reply + return true; +} + +/****************************************************************************** +* Called when the daemon has notified us of the result of the register() DCOP call. +*/ +void Daemon::registrationResult(bool reregister, int result, int version) +{ + kdDebug(5950) << "Daemon::registrationResult(" << reregister << ") version: " << version << endl; + delete mRegisterTimer; + mRegisterTimer = 0; + bool failed = false; + QString errmsg; + if (version && version != DAEMON_VERSION_NUM) + { + failed = true; + kdError(5950) << "Daemon::registrationResult(" << reregister << "): kalarmd reports incompatible version " << version << endl; + errmsg = i18n("Cannot enable alarms.\nInstallation or configuration error: Alarm Daemon (%1) version is incompatible.") + .arg(QString::fromLatin1(DAEMON_APP_NAME)); + } + else + { + switch (result) + { + case KAlarmd::SUCCESS: + break; + case KAlarmd::NOT_FOUND: + // We've successfully registered with the daemon, but the daemon can't + // find the KAlarm executable so won't be able to restart KAlarm if + // KAlarm exits. + kdError(5950) << "Daemon::registrationResult(" << reregister << "): registerApp dcop call: " << kapp->aboutData()->appName() << " not found\n"; + KMessageBox::error(0, i18n("Alarms will be disabled if you stop KAlarm.\n" + "(Installation or configuration error: %1 cannot locate %2 executable.)") + .arg(QString::fromLatin1(DAEMON_APP_NAME)) + .arg(kapp->aboutData()->appName())); + break; + case KAlarmd::FAILURE: + default: + // Either the daemon reported an error in the registration DCOP call, + // there was a DCOP error calling the daemon, or a timeout on the reply. + kdError(5950) << "Daemon::registrationResult(" << reregister << "): registerApp dcop call failed -> " << result << endl; + failed = true; + if (!reregister) + { + errmsg = i18n("Cannot enable alarms:\nFailed to register with Alarm Daemon (%1)") + .arg(QString::fromLatin1(DAEMON_APP_NAME)); + } + break; + } + } + + if (failed) + { + if (!errmsg.isEmpty()) + { + if (mStatus == REGISTERED) + mStatus = READY; + if (!mRegisterFailMsg) + { + mRegisterFailMsg = true; + KMessageBox::error(0, errmsg); + } + } + return; + } + + if (!reregister) + { + // The alarm daemon has loaded the calendar + mStatus = REGISTERED; + mRegisterFailMsg = false; + kdDebug(5950) << "Daemon::start(): daemon startup complete" << endl; + } +} + +/****************************************************************************** +* Check whether the alarm daemon has started yet, and if so, register with it. +*/ +void Daemon::checkIfStarted() +{ + updateRegisteredStatus(); + bool err = false; + switch (mStatus) + { + case STOPPED: + if (--mStartTimeout > 0) + return; // wait a bit more to check again + // Output error message, but delete timer first to prevent + // multiple messages. + err = true; + break; + case RUNNING: + case READY: + case REGISTERED: + break; + } + delete mStartTimer; + mStartTimer = 0; + if (err) + { + kdError(5950) << "Daemon::checkIfStarted(): failed to start daemon" << endl; + KMessageBox::error(0, i18n("Cannot enable alarms:\nFailed to start Alarm Daemon (%1)").arg(QString::fromLatin1(DAEMON_APP_NAME))); + } +} + +/****************************************************************************** +* Check whether the alarm daemon has started yet, and if so, whether it is +* ready to accept DCOP calls. +*/ +void Daemon::updateRegisteredStatus(bool timeout) +{ + if (!kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) + { + mStatus = STOPPED; + mRegisterFailMsg = false; + } + else + { + switch (mStatus) + { + case STOPPED: + // The daemon has newly been detected as registered with DCOP. + // Wait for a short time to ensure that it is ready for DCOP calls. + mStatus = RUNNING; + QTimer::singleShot(startCheckInterval, mInstance, SLOT(slotStarted())); + break; + case RUNNING: + if (timeout) + { + mStatus = READY; + start(); + } + break; + case READY: + case REGISTERED: + break; + } + } + kdDebug(5950) << "Daemon::updateRegisteredStatus() -> " << mStatus << endl; +} + +/****************************************************************************** +* Stop the alarm daemon if it is running. +*/ +bool Daemon::stop() +{ + kdDebug(5950) << "Daemon::stop()" << endl; + if (kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) + { + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.quit(); + if (!s.ok()) + { + kdError(5950) << "Daemon::stop(): dcop call failed" << endl; + return false; + } + } + return true; +} + +/****************************************************************************** +* Reset the alarm daemon. +* Reply = true if daemon was told to reset +* = false if daemon is not running. +*/ +bool Daemon::reset() +{ + kdDebug(5950) << "Daemon::reset()" << endl; + if (!kapp->dcopClient()->isApplicationRegistered(DAEMON_APP_NAME)) + return false; + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.resetCalendar(QCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString()); + if (!s.ok()) + kdError(5950) << "Daemon::reset(): resetCalendar dcop send failed" << endl; + return true; +} + +/****************************************************************************** +* Tell the alarm daemon to reread the calendar file. +*/ +void Daemon::reload() +{ + kdDebug(5950) << "Daemon::reload()\n"; + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.reloadCalendar(QCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString()); + if (!s.ok()) + kdError(5950) << "Daemon::reload(): reloadCalendar dcop send failed" << endl; +} + +/****************************************************************************** +* Tell the alarm daemon to enable/disable monitoring of the calendar file. +*/ +void Daemon::enableCalendar(bool enable) +{ + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.enableCalendar(AlarmCalendar::activeCalendar()->urlString(), enable); + mEnableCalPending = false; +} + +/****************************************************************************** +* Tell the alarm daemon to enable/disable autostart at login. +*/ +void Daemon::enableAutoStart(bool enable) +{ + // Tell the alarm daemon in case it is running. + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.enableAutoStart(enable); + + // The return status doesn't report failure even if the daemon isn't running, + // so in case of failure, rewrite the config file in any case. + KConfig adconfig(locate("config", DAEMON_APP_NAME"rc")); + adconfig.setGroup(QString::fromLatin1(DAEMON_AUTOSTART_SECTION)); + adconfig.writeEntry(QString::fromLatin1(DAEMON_AUTOSTART_KEY), enable); + adconfig.sync(); +} + +/****************************************************************************** +* Notify the alarm daemon that the start-of-day time for date-only alarms has +* changed. +*/ +void Daemon::notifyTimeChanged() +{ + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.timeConfigChanged(); + if (!s.ok()) + kdError(5950) << "Daemon::timeConfigChanged(): dcop send failed" << endl; +} + +/****************************************************************************** +* Read the alarm daemon's autostart-at-login setting. +*/ +bool Daemon::autoStart() +{ + KConfig adconfig(locate("config", DAEMON_APP_NAME"rc")); + adconfig.setGroup(QString::fromLatin1(DAEMON_AUTOSTART_SECTION)); + return adconfig.readBoolEntry(QString::fromLatin1(DAEMON_AUTOSTART_KEY), true); +} + +/****************************************************************************** +* Notification that the alarm daemon has enabled/disabled monitoring of the +* calendar file. +*/ +void Daemon::calendarIsEnabled(bool enabled) +{ + mCalendarDisabled = !enabled; + emit mInstance->daemonRunning(enabled); +} + +/****************************************************************************** +* Tell the alarm daemon to stop or start monitoring the calendar file as +* appropriate. +*/ +void Daemon::setAlarmsEnabled(bool enable) +{ + kdDebug(5950) << "Daemon::setAlarmsEnabled(" << enable << ")\n"; + if (enable && !checkIfRunning()) + { + // The daemon is not running, so start it + if (!start()) + { + emit daemonRunning(false); + return; + } + mEnableCalPending = true; + setFastCheck(); + } + + // If the daemon is now running, tell it to enable/disable the calendar + if (checkIfRunning()) + enableCalendar(enable); +} + +/****************************************************************************** +* Return whether the alarm daemon is monitoring alarms. +*/ +bool Daemon::monitoringAlarms() +{ + bool ok = !mCalendarDisabled && isRunning(); + emit mInstance->daemonRunning(ok); + return ok; +} + +/****************************************************************************** +* Check whether the alarm daemon is currently running and available. +*/ +bool Daemon::isRunning(bool startdaemon) +{ + static bool runState = false; + updateRegisteredStatus(); + bool newRunState = (mStatus == READY || mStatus == REGISTERED); + if (newRunState != runState) + { + // Daemon's status has changed + runState = newRunState; + if (runState && startdaemon) + start(); // re-register with the daemon + } + return runState && (mStatus == REGISTERED); +} + +/****************************************************************************** +* Called by the timer to check whether the daemon is running. +*/ +void Daemon::timerCheckIfRunning() +{ + checkIfRunning(); + // Limit how long we check at the fast rate + if (mStatusTimerCount > 0 && --mStatusTimerCount <= 0) + mStatusTimer->changeInterval(mStatusTimerInterval * 1000); +} + +/****************************************************************************** +* Check whether the alarm daemon is currently running. +* If its status has changed, trigger GUI updates. +*/ +bool Daemon::checkIfRunning() +{ + bool newstatus = isRunning(); + if (newstatus != mRunning) + { + mRunning = newstatus; + int status = mRunning && !mCalendarDisabled; + emit mInstance->daemonRunning(status); + mStatusTimer->changeInterval(mStatusTimerInterval * 1000); // exit from fast checking + mStatusTimerCount = 0; + if (mRunning) + { + // The alarm daemon has started up + if (mEnableCalPending) + enableCalendar(true); // tell it to monitor the calendar, if appropriate + } + } + return mRunning; +} + +/****************************************************************************** +* Starts checking at a faster rate whether the daemon is running. +*/ +void Daemon::setFastCheck() +{ + mStatusTimer->start(500); // check new status every half second + mStatusTimerCount = 20; // don't check at this rate for more than 10 seconds +} + +/****************************************************************************** +* Called when a program setting has changed. +* If the system tray icon update interval has changed, reset the timer. +*/ +void Daemon::slotPreferencesChanged() +{ + int newInterval = Preferences::daemonTrayCheckInterval(); + if (newInterval != mStatusTimerInterval) + { + // Daemon check interval has changed + mStatusTimerInterval = newInterval; + if (mStatusTimerCount <= 0) // don't change if on fast rate + mStatusTimer->changeInterval(mStatusTimerInterval * 1000); + } +} + +/****************************************************************************** +* Create an "Alarms Enabled/Enable Alarms" action. +*/ +AlarmEnableAction* Daemon::createAlarmEnableAction(KActionCollection* actions, const char* name) +{ + AlarmEnableAction* a = new AlarmEnableAction(0, actions, name); + connect(a, SIGNAL(userClicked(bool)), mInstance, SLOT(setAlarmsEnabled(bool))); + connect(mInstance, SIGNAL(daemonRunning(bool)), a, SLOT(setCheckedActual(bool))); + return a; +} + +/****************************************************************************** +* Called when a calendar has been saved. +* If it's the active alarm calendar, notify the alarm daemon. +*/ +void Daemon::slotCalendarSaved(AlarmCalendar* cal) +{ + if (cal == AlarmCalendar::activeCalendar()) + { + int n = mSavingEvents.count(); + if (n) + { + // We have just saved a modified event originally triggered by the daemon. + // Notify the daemon of the event, and tell it to reload the calendar. + for (int i = 0; i < n - 1; ++i) + notifyEventHandled(mSavingEvents[i], false); + notifyEventHandled(mSavingEvents[n - 1], true); + mSavingEvents.clear(); + } + else + reload(); + } +} + +/****************************************************************************** +* Note an event ID which has been triggered by the alarm daemon. +*/ +void Daemon::queueEvent(const QString& eventId) +{ + mQueuedEvents += eventId; +} + +/****************************************************************************** +* Note an event ID which is currently being saved in the calendar file, if the +* event was originally triggered by the alarm daemon. +*/ +void Daemon::savingEvent(const QString& eventId) +{ + if (mQueuedEvents.remove(eventId) > 0) + mSavingEvents += eventId; +} + +/****************************************************************************** +* If the event ID has been triggered by the alarm daemon, tell the daemon that +* it has been processed, and whether to reload its calendar. +*/ +void Daemon::eventHandled(const QString& eventId, bool reloadCal) +{ + if (mQueuedEvents.remove(eventId) > 0) + notifyEventHandled(eventId, reloadCal); // it's a daemon event, so tell daemon that it's been handled + else if (reloadCal) + reload(); // not a daemon event, so simply tell the daemon to reload the calendar +} + +/****************************************************************************** +* Tell the daemon that an event has been processed, and whether to reload its +* calendar. +*/ +void Daemon::notifyEventHandled(const QString& eventId, bool reloadCal) +{ + kdDebug(5950) << "Daemon::notifyEventHandled(" << eventId << (reloadCal ? "): reload" : ")") << endl; + AlarmDaemonIface_stub s(DAEMON_APP_NAME, DAEMON_DCOP_OBJECT); + s.eventHandled(QCString(kapp->aboutData()->appName()), AlarmCalendar::activeCalendar()->urlString(), eventId, reloadCal); + if (!s.ok()) + kdError(5950) << "Daemon::notifyEventHandled(): eventHandled dcop send failed" << endl; +} + +/****************************************************************************** +* Return the maximum time (in seconds) elapsed since the last time the alarm +* daemon must have checked alarms. +*/ +int Daemon::maxTimeSinceCheck() +{ + return DAEMON_CHECK_INTERVAL; +} + + +/*============================================================================= += Class: NotificationHandler +=============================================================================*/ + +NotificationHandler::NotificationHandler() + : DCOPObject(NOTIFY_DCOP_OBJECT), + QObject() +{ + kdDebug(5950) << "NotificationHandler::NotificationHandler()\n"; +} + +/****************************************************************************** + * DCOP call from the alarm daemon to notify a change. + * The daemon notifies calendar statuses when we first register as a GUI, and whenever + * a calendar status changes. So we don't need to read its config files. + */ +void NotificationHandler::alarmDaemonUpdate(int calendarStatus, const QString& calendarURL) +{ + kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(" << calendarStatus << ")\n"; + KAlarmd::CalendarStatus status = KAlarmd::CalendarStatus(calendarStatus); + if (expandURL(calendarURL) != AlarmCalendar::activeCalendar()->urlString()) + return; // it's not a notification about KAlarm's calendar + bool enabled = false; + switch (status) + { + case KAlarmd::CALENDAR_UNAVAILABLE: + // Calendar is not available for monitoring + kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(CALENDAR_UNAVAILABLE)\n"; + break; + case KAlarmd::CALENDAR_DISABLED: + // Calendar is available for monitoring but is not currently being monitored + kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(DISABLE_CALENDAR)\n"; + break; + case KAlarmd::CALENDAR_ENABLED: + // Calendar is currently being monitored + kdDebug(5950) << "NotificationHandler::alarmDaemonUpdate(ENABLE_CALENDAR)\n"; + enabled = true; + break; + default: + return; + } + Daemon::calendarIsEnabled(enabled); +} + +/****************************************************************************** + * DCOP call from the alarm daemon to notify that an alarm is due. + */ +void NotificationHandler::handleEvent(const QString& url, const QString& eventId) +{ + QString id = eventId; + if (id.startsWith(QString::fromLatin1("ad:"))) + { + // It's a notification from the alarm deamon + id = id.mid(3); + Daemon::queueEvent(id); + } + theApp()->handleEvent(url, id); +} + +/****************************************************************************** + * DCOP call from the alarm daemon to notify the success or failure of a + * registration request from KAlarm. + */ +void NotificationHandler::registered(bool reregister, int result, int version) +{ + Daemon::registrationResult(reregister, result, version); +} + + +/*============================================================================= += Class: AlarmEnableAction +=============================================================================*/ + +AlarmEnableAction::AlarmEnableAction(int accel, QObject* parent, const char* name) + : KToggleAction(i18n("Enable &Alarms"), accel, parent, name), + mInitialised(false) +{ + setCheckedState(i18n("Disable &Alarms")); + setCheckedActual(false); // set the correct text + mInitialised = true; +} + +/****************************************************************************** +* Set the checked status and the correct text for the Alarms Enabled action. +*/ +void AlarmEnableAction::setCheckedActual(bool running) +{ + kdDebug(5950) << "AlarmEnableAction::setCheckedActual(" << running << ")\n"; + if (running != isChecked() || !mInitialised) + { + KToggleAction::setChecked(running); + emit switched(running); + } +} + +/****************************************************************************** +* Request a change in the checked status. +* The status is only actually changed when the alarm daemon run state changes. +*/ +void AlarmEnableAction::setChecked(bool check) +{ + kdDebug(5950) << "AlarmEnableAction::setChecked(" << check << ")\n"; + if (check != isChecked()) + { + if (check) + Daemon::allowRegisterFailMsg(); + emit userClicked(check); + } +} + + +/****************************************************************************** + * Expand a DCOP call parameter URL to a full URL. + * (We must store full URLs in the calendar data since otherwise later calls to + * reload or remove calendars won't necessarily find a match.) + */ +QString expandURL(const QString& urlString) +{ + if (urlString.isEmpty()) + return QString(); + return KURL(urlString).url(); +} diff --git a/kalarm/daemon.h b/kalarm/daemon.h new file mode 100644 index 000000000..04198904c --- /dev/null +++ b/kalarm/daemon.h @@ -0,0 +1,134 @@ +/* + * daemon.h - interface with alarm daemon + * Program: kalarm + * Copyright © 2001-2007 by 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. + */ + +#ifndef DAEMON_H +#define DAEMON_H + +#include +#include +#include + +#include +#include + +class KActionCollection; +class AlarmCalendar; +class AlarmEnableAction; +class NotificationHandler; + + +class Daemon : public QObject +{ + Q_OBJECT + public: + static void initialise(); + static void createDcopHandler(); + static bool isDcopHandlerReady() { return mDcopHandler; } + static AlarmEnableAction* createAlarmEnableAction(KActionCollection*, const char* name); + static bool start(); + static bool reregister() { return registerWith(true); } + static bool reset(); + static bool stop(); + static bool autoStart(); + static void enableAutoStart(bool enable); + static void notifyTimeChanged(); + static void setAlarmsEnabled() { mInstance->setAlarmsEnabled(true); } + static void checkStatus() { checkIfRunning(); } + static bool monitoringAlarms(); + static bool isRunning(bool startDaemon = true); + static int maxTimeSinceCheck(); + static bool isRegistered() { return mStatus == REGISTERED; } + static void allowRegisterFailMsg() { mRegisterFailMsg = false; } + + static void queueEvent(const QString& eventID); + static void savingEvent(const QString& eventID); + static void eventHandled(const QString& eventID, bool reloadCal); + + signals: + void daemonRunning(bool running); + + private slots: + void slotCalendarSaved(AlarmCalendar*); + void checkIfStarted(); + void slotStarted() { updateRegisteredStatus(true); } + void registerTimerExpired() { registrationResult((mStatus == REGISTERED), KAlarmd::FAILURE); } + + void setAlarmsEnabled(bool enable); + void timerCheckIfRunning(); + void slotPreferencesChanged(); + + private: + enum Status // daemon status. KEEP IN THIS ORDER!! + { + STOPPED, // daemon is not registered with DCOP + RUNNING, // daemon is newly registered with DCOP + READY, // daemon is ready to accept DCOP calls + REGISTERED // we have registered with the daemon + }; + Daemon() { } + static bool registerWith(bool reregister); + static void registrationResult(bool reregister, int result, int version = 0); + static void reload(); + static void notifyEventHandled(const QString& eventID, bool reloadCal); + static void updateRegisteredStatus(bool timeout = false); + static void enableCalendar(bool enable); + static void calendarIsEnabled(bool enabled); + static bool checkIfRunning(); + static void setFastCheck(); + + static Daemon* mInstance; // only one instance allowed + static NotificationHandler* mDcopHandler; // handles DCOP requests from daemon + static QValueList mQueuedEvents; // IDs of pending events that daemon has triggered + static QValueList mSavingEvents; // IDs of updated events that are currently being saved + static QTimer* mStartTimer; // timer to check daemon status after starting daemon + static QTimer* mRegisterTimer; // timer to check whether daemon has sent registration status + static QTimer* mStatusTimer; // timer for checking daemon status + static int mStatusTimerCount; // countdown for fast status checking + static int mStatusTimerInterval; // timer interval (seconds) for checking daemon status + static int mStartTimeout; // remaining number of times to check if alarm daemon has started + static Status mStatus; // daemon status + static bool mRunning; // whether the alarm daemon is currently running + static bool mCalendarDisabled; // monitoring of calendar is currently disabled by daemon + static bool mEnableCalPending; // waiting to tell daemon to enable calendar + static bool mRegisterFailMsg; // true if registration failure message has been displayed + + friend class NotificationHandler; +}; + +/*============================================================================= += Class: AlarmEnableAction +=============================================================================*/ + +class AlarmEnableAction : public KToggleAction +{ + Q_OBJECT + public: + AlarmEnableAction(int accel, QObject* parent, const char* name = 0); + public slots: + void setCheckedActual(bool); // set state and emit switched() signal + virtual void setChecked(bool); // request state change and emit userClicked() signal + signals: + void switched(bool); // state has changed (KToggleAction::toggled() is only emitted when clicked by user) + void userClicked(bool); // user has clicked the control (param = desired state) + private: + bool mInitialised; +}; + +#endif // DAEMON_H diff --git a/kalarm/dcophandler.cpp b/kalarm/dcophandler.cpp new file mode 100644 index 000000000..ab4105fe6 --- /dev/null +++ b/kalarm/dcophandler.cpp @@ -0,0 +1,768 @@ +/* + * dcophandler.cpp - handler for DCOP calls by other applications + * Program: kalarm + * Copyright © 2002-2006,2008 by 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 "kalarm.h" + +#include + +#include + +#include "alarmcalendar.h" +#include "daemon.h" +#include "functions.h" +#include "kalarmapp.h" +#include "kamail.h" +#include "karecurrence.h" +#include "mainwindow.h" +#include "preferences.h" +#include "dcophandler.moc" + +static const char* DCOP_OBJECT_NAME = "request"; // DCOP name of KAlarm's request interface +#ifdef OLD_DCOP +static const char* DCOP_OLD_OBJECT_NAME = "display"; +#endif + + +/*============================================================================= += DcopHandler += This class's function is to handle DCOP requests by other applications. +=============================================================================*/ +DcopHandler::DcopHandler() + : DCOPObject(DCOP_OBJECT_NAME), + QWidget() +{ + kdDebug(5950) << "DcopHandler::DcopHandler()\n"; +} + + +bool DcopHandler::cancelEvent(const QString& url,const QString& eventId) +{ + return theApp()->deleteEvent(url, eventId); +} + +bool DcopHandler::triggerEvent(const QString& url,const QString& eventId) +{ + return theApp()->triggerEvent(url, eventId); +} + +bool DcopHandler::scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int subRepeatInterval, int subRepeatCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurrence, subRepeatInterval)) + return false; + return scheduleMessage(message, start, lateCancel, flags, bgColor, fgColor, font, audioFile, reminderMins, recur, subRepeatInterval, subRepeatCount); +} + +bool DcopHandler::scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, + int recurType, int recurInterval, int recurCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, recurCount)) + return false; + return scheduleMessage(message, start, lateCancel, flags, bgColor, fgColor, font, audioFile, reminderMins, recur); +} + +bool DcopHandler::scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, + int recurType, int recurInterval, const QString& endDateTime) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, endDateTime)) + return false; + return scheduleMessage(message, start, lateCancel, flags, bgColor, fgColor, font, audioFile, reminderMins, recur); +} + +bool DcopHandler::scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int subRepeatInterval, int subRepeatCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurrence, subRepeatInterval)) + return false; + return scheduleFile(file, start, lateCancel, flags, bgColor, audioFile, reminderMins, recur, subRepeatInterval, subRepeatCount); +} + +bool DcopHandler::scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, int recurCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, recurCount)) + return false; + return scheduleFile(file, start, lateCancel, flags, bgColor, audioFile, reminderMins, recur); +} + +bool DcopHandler::scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, const QString& endDateTime) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, endDateTime)) + return false; + return scheduleFile(file, start, lateCancel, flags, bgColor, audioFile, reminderMins, recur); +} + +bool DcopHandler::scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int subRepeatInterval, int subRepeatCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurrence, subRepeatInterval)) + return false; + return scheduleCommand(commandLine, start, lateCancel, flags, recur, subRepeatInterval, subRepeatCount); +} + +bool DcopHandler::scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, int recurCount) +{ + DateTime start = convertStartDateTime(startDateTime); + if (!start.isValid()) + return false; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, recurCount)) + return false; + return scheduleCommand(commandLine, start, lateCancel, flags, recur); +} + +bool DcopHandler::scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, const QString& endDateTime) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, endDateTime)) + return false; + return scheduleCommand(commandLine, start, lateCancel, flags, recur); +} + +bool DcopHandler::scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int subRepeatInterval, int subRepeatCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurrence, subRepeatInterval)) + return false; + return scheduleEmail(fromID, addresses, subject, message, attachments, start, lateCancel, flags, recur, subRepeatInterval, subRepeatCount); +} + +bool DcopHandler::scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, int recurCount) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, recurCount)) + return false; + return scheduleEmail(fromID, addresses, subject, message, attachments, start, lateCancel, flags, recur); +} + +bool DcopHandler::scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, const QString& endDateTime) +{ + DateTime start; + KARecurrence recur; + if (!convertRecurrence(start, recur, startDateTime, recurType, recurInterval, endDateTime)) + return false; + return scheduleEmail(fromID, addresses, subject, message, attachments, start, lateCancel, flags, recur); +} + +bool DcopHandler::edit(const QString& eventID) +{ + return KAlarm::edit(eventID); +} + +bool DcopHandler::editNew(const QString& templateName) +{ + return KAlarm::editNew(templateName); +} + + +/****************************************************************************** +* Schedule a message alarm, after converting the parameters from strings. +*/ +bool DcopHandler::scheduleMessage(const QString& message, const DateTime& start, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& fontStr, + const KURL& audioFile, int reminderMins, const KARecurrence& recurrence, + int subRepeatInterval, int subRepeatCount) +{ + unsigned kaEventFlags = convertStartFlags(start, flags); + QColor bg = convertBgColour(bgColor); + if (!bg.isValid()) + return false; + QColor fg; + if (fgColor.isEmpty()) + fg = Preferences::defaultFgColour(); + else + { + fg.setNamedColor(fgColor); + if (!fg.isValid()) + { + kdError(5950) << "DCOP call: invalid foreground color: " << fgColor << endl; + return false; + } + } + QFont font; + if (fontStr.isEmpty()) + kaEventFlags |= KAEvent::DEFAULT_FONT; + else + { + if (!font.fromString(fontStr)) // N.B. this doesn't do good validation + { + kdError(5950) << "DCOP call: invalid font: " << fontStr << endl; + return false; + } + } + return theApp()->scheduleEvent(KAEvent::MESSAGE, message, start.dateTime(), lateCancel, kaEventFlags, bg, fg, font, + audioFile.url(), -1, reminderMins, recurrence, subRepeatInterval, subRepeatCount); +} + +/****************************************************************************** +* Schedule a file alarm, after converting the parameters from strings. +*/ +bool DcopHandler::scheduleFile(const KURL& file, + const DateTime& start, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, const KARecurrence& recurrence, + int subRepeatInterval, int subRepeatCount) +{ + unsigned kaEventFlags = convertStartFlags(start, flags); + QColor bg = convertBgColour(bgColor); + if (!bg.isValid()) + return false; + return theApp()->scheduleEvent(KAEvent::FILE, file.url(), start.dateTime(), lateCancel, kaEventFlags, bg, Qt::black, QFont(), + audioFile.url(), -1, reminderMins, recurrence, subRepeatInterval, subRepeatCount); +} + +/****************************************************************************** +* Schedule a command alarm, after converting the parameters from strings. +*/ +bool DcopHandler::scheduleCommand(const QString& commandLine, + const DateTime& start, int lateCancel, unsigned flags, + const KARecurrence& recurrence, int subRepeatInterval, int subRepeatCount) +{ + unsigned kaEventFlags = convertStartFlags(start, flags); + return theApp()->scheduleEvent(KAEvent::COMMAND, commandLine, start.dateTime(), lateCancel, kaEventFlags, Qt::black, Qt::black, QFont(), + QString::null, -1, 0, recurrence, subRepeatInterval, subRepeatCount); +} + +/****************************************************************************** +* Schedule an email alarm, after validating the addresses and attachments. +*/ +bool DcopHandler::scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, + const QString& message, const QString& attachments, + const DateTime& start, int lateCancel, unsigned flags, + const KARecurrence& recurrence, int subRepeatInterval, int subRepeatCount) +{ + unsigned kaEventFlags = convertStartFlags(start, flags); + uint senderId = 0; + if (!fromID.isEmpty()) + { + senderId = KAMail::identityUoid(fromID); + if (!senderId) + { + kdError(5950) << "DCOP call scheduleEmail(): unknown sender ID: " << fromID << endl; + return false; + } + } + EmailAddressList addrs; + QString bad = KAMail::convertAddresses(addresses, addrs); + if (!bad.isEmpty()) + { + kdError(5950) << "DCOP call scheduleEmail(): invalid email addresses: " << bad << endl; + return false; + } + if (addrs.isEmpty()) + { + kdError(5950) << "DCOP call scheduleEmail(): no email address\n"; + return false; + } + QStringList atts; + bad = KAMail::convertAttachments(attachments, atts); + if (!bad.isEmpty()) + { + kdError(5950) << "DCOP call scheduleEmail(): invalid email attachment: " << bad << endl; + return false; + } + return theApp()->scheduleEvent(KAEvent::EMAIL, message, start.dateTime(), lateCancel, kaEventFlags, Qt::black, Qt::black, QFont(), + QString::null, -1, 0, recurrence, subRepeatInterval, subRepeatCount, senderId, addrs, subject, atts); +} + + +/****************************************************************************** +* Convert the start date/time string to a DateTime. The date/time string is in +* the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] +*/ +DateTime DcopHandler::convertStartDateTime(const QString& startDateTime) +{ + DateTime start; + if (startDateTime.length() > 10) + { + // Both a date and a time are specified + start = QDateTime::fromString(startDateTime, Qt::ISODate); + } + else + { + // Check whether a time is specified + QString t; + if (startDateTime[0] == 'T') + t = startDateTime.mid(1); // it's a time: remove the leading 'T' + else if (!startDateTime[2].isDigit()) + t = startDateTime; // it's a time with no leading 'T' + + if (t.isEmpty()) + { + // It's a date + start = QDate::fromString(startDateTime, Qt::ISODate); + } + else + { + // It's a time, so use today as the date + start.set(QDate::currentDate(), QTime::fromString(t, Qt::ISODate)); + } + } + if (!start.isValid()) + kdError(5950) << "DCOP call: invalid start date/time: " << startDateTime << endl; + return start; +} + +/****************************************************************************** +* Convert the flag bits to KAEvent flag bits. +*/ +unsigned DcopHandler::convertStartFlags(const DateTime& start, unsigned flags) +{ + unsigned kaEventFlags = 0; + if (flags & REPEAT_AT_LOGIN) kaEventFlags |= KAEvent::REPEAT_AT_LOGIN; + if (flags & BEEP) kaEventFlags |= KAEvent::BEEP; + if (flags & SPEAK) kaEventFlags |= KAEvent::SPEAK; + if (flags & CONFIRM_ACK) kaEventFlags |= KAEvent::CONFIRM_ACK; + if (flags & REPEAT_SOUND) kaEventFlags |= KAEvent::REPEAT_SOUND; + if (flags & AUTO_CLOSE) kaEventFlags |= KAEvent::AUTO_CLOSE; + if (flags & EMAIL_BCC) kaEventFlags |= KAEvent::EMAIL_BCC; + if (flags & SCRIPT) kaEventFlags |= KAEvent::SCRIPT; + if (flags & EXEC_IN_XTERM) kaEventFlags |= KAEvent::EXEC_IN_XTERM; + if (flags & SHOW_IN_KORG) kaEventFlags |= KAEvent::COPY_KORGANIZER; + if (flags & DISABLED) kaEventFlags |= KAEvent::DISABLED; + if (start.isDateOnly()) kaEventFlags |= KAEvent::ANY_TIME; + return kaEventFlags; +} + +/****************************************************************************** +* Convert the background colour string to a QColor. +*/ +QColor DcopHandler::convertBgColour(const QString& bgColor) +{ + if (bgColor.isEmpty()) + return Preferences::defaultBgColour(); + QColor bg(bgColor); + if (!bg.isValid()) + kdError(5950) << "DCOP call: invalid background color: " << bgColor << endl; + return bg; +} + +bool DcopHandler::convertRecurrence(DateTime& start, KARecurrence& recurrence, + const QString& startDateTime, const QString& icalRecurrence, + int& subRepeatInterval) +{ + start = convertStartDateTime(startDateTime); + if (!start.isValid()) + return false; + if (!recurrence.set(icalRecurrence)) + return false; + if (subRepeatInterval && recurrence.type() == KARecurrence::NO_RECUR) + { + subRepeatInterval = 0; + kdWarning(5950) << "DCOP call: no recurrence specified, so sub-repetition ignored" << endl; + } + return true; +} + +bool DcopHandler::convertRecurrence(DateTime& start, KARecurrence& recurrence, const QString& startDateTime, + int recurType, int recurInterval, int recurCount) +{ + start = convertStartDateTime(startDateTime); + if (!start.isValid()) + return false; + return convertRecurrence(recurrence, start, recurType, recurInterval, recurCount, QDateTime()); +} + +bool DcopHandler::convertRecurrence(DateTime& start, KARecurrence& recurrence, const QString& startDateTime, + int recurType, int recurInterval, const QString& endDateTime) +{ + start = convertStartDateTime(startDateTime); + if (!start.isValid()) + return false; + QDateTime end; + if (endDateTime.find('T') < 0) + { + if (!start.isDateOnly()) + { + kdError(5950) << "DCOP call: alarm is date-only, but recurrence end is date/time" << endl; + return false; + } + end.setDate(QDate::fromString(endDateTime, Qt::ISODate)); + } + else + { + if (start.isDateOnly()) + { + kdError(5950) << "DCOP call: alarm is timed, but recurrence end is date-only" << endl; + return false; + } + end = QDateTime::fromString(endDateTime, Qt::ISODate); + } + if (!end.isValid()) + { + kdError(5950) << "DCOP call: invalid recurrence end date/time: " << endDateTime << endl; + return false; + } + return convertRecurrence(recurrence, start, recurType, recurInterval, 0, end); +} + +bool DcopHandler::convertRecurrence(KARecurrence& recurrence, const DateTime& start, int recurType, + int recurInterval, int recurCount, const QDateTime& end) +{ + KARecurrence::Type type; + switch (recurType) + { + case MINUTELY: type = KARecurrence::MINUTELY; break; + case DAILY: type = KARecurrence::DAILY; break; + case WEEKLY: type = KARecurrence::WEEKLY; break; + case MONTHLY: type = KARecurrence::MONTHLY_DAY; break; + case YEARLY: type = KARecurrence::ANNUAL_DATE; break; + break; + default: + kdError(5950) << "DCOP call: invalid recurrence type: " << recurType << endl; + return false; + } + recurrence.set(type, recurInterval, recurCount, start, end); + return true; +} + + +#ifdef OLD_DCOP +/*============================================================================= += DcopHandlerOld += This class's function is simply to act as a receiver for DCOP requests. +=============================================================================*/ +DcopHandlerOld::DcopHandlerOld() + : QWidget(), + DCOPObject(DCOP_OLD_OBJECT_NAME) +{ + kdDebug(5950) << "DcopHandlerOld::DcopHandlerOld()\n"; +} + +/****************************************************************************** +* Process a DCOP request. +*/ +bool DcopHandlerOld::process(const QCString& func, const QByteArray& data, QCString& replyType, QByteArray&) +{ + kdDebug(5950) << "DcopHandlerOld::process(): " << func << endl; + enum + { + OPERATION = 0x0007, // mask for main operation + HANDLE = 0x0001, + CANCEL = 0x0002, + TRIGGER = 0x0003, + SCHEDULE = 0x0004, + ALARM_TYPE = 0x00F0, // mask for SCHEDULE alarm type + MESSAGE = 0x0010, + FILE = 0x0020, + COMMAND = 0x0030, + EMAIL = 0x0040, + SCH_FLAGS = 0x0F00, // mask for SCHEDULE flags + REP_COUNT = 0x0100, + REP_END = 0x0200, + FONT = 0x0400, + PRE_096 = 0x1000, // old-style pre-0.9.6 deprecated method + PRE_091 = 0x2000 | PRE_096 // old-style pre-0.9.1 deprecated method + }; + replyType = "void"; + int function; + if (func == "handleEvent(const QString&,const QString&)" + || func == "handleEvent(QString,QString)") + function = HANDLE; + else if (func == "cancelEvent(const QString&,const QString&)" + || func == "cancelEvent(QString,QString)") + function = CANCEL; + else if (func == "triggerEvent(const QString&,const QString&)" + || func == "triggerEvent(QString,QString)") + function = TRIGGER; + + // scheduleMessage(message, dateTime, colour, colourfg, flags, audioURL, reminder, recurrence) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,const QColor&,Q_UINT32,const QString&,Q_INT32,const QString&)" + || func == "scheduleMessage(QString,QDateTime,QColor,QColor,Q_UINT32,QString,Q_UINT32,QString)") + function = SCHEDULE | MESSAGE; + // scheduleMessage(message, dateTime, colour, colourfg, font, flags, audioURL, reminder, recurrence) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,const QColor&,const QFont&,Q_UINT32,const QString&,Q_INT32,const QString&)" + || func == "scheduleMessage(QString,QDateTime,QColor,QColor,QFont,Q_UINT32,QString,Q_UINT32,QString)") + function = SCHEDULE | MESSAGE | FONT; + // scheduleFile(URL, dateTime, colour, flags, audioURL, reminder, recurrence) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,const QString&)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString,Q_UINT32,QString)") + function = SCHEDULE | FILE; + // scheduleCommand(commandLine, dateTime, flags, recurrence) + else if (func == "scheduleCommand(const QString&,const QDateTime&,Q_UINT32,const QString&)" + || func == "scheduleCommand(QString,QDateTime,Q_UINT32,QString)") + function = SCHEDULE | COMMAND; + // scheduleEmail(addresses, subject, message, attachments, dateTime, flags, recurrence) + else if (func == "scheduleEmail(const QString&,const QString&,const QString&,const QString&,const QDateTime&,Q_UINT32,const QString&)" + || func == "scheduleEmail(QString,QString,QString,QString,QDateTime,Q_UINT32,QString)") + function = SCHEDULE | EMAIL; + + // scheduleMessage(message, dateTime, colour, colourfg, flags, audioURL, reminder, recurType, interval, recurCount) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleMessage(QString,QDateTime,QColor,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | MESSAGE | REP_COUNT; + // scheduleFile(URL, dateTime, colour, flags, audioURL, reminder, recurType, interval, recurCount) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | FILE | REP_COUNT; + // scheduleCommand(commandLine, dateTime, flags, recurType, interval, recurCount) + else if (func == "scheduleCommand(const QString&,const QDateTime&,Q_UINT32,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleCommand(QString,QDateTime,Q_UINT32,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | COMMAND | REP_COUNT; + // scheduleEmail(addresses, subject, message, attachments, dateTime, flags, recurType, interval, recurCount) + else if (func == "scheduleEmail(const QString&,const QString&,const QString&,const QString&,const QDateTime&,Q_UINT32,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleEmail(QString,QString,QString,QString,QDateTime,Q_UINT32,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | EMAIL | REP_COUNT; + + // scheduleMessage(message, dateTime, colour, colourfg, flags, audioURL, reminder, recurType, interval, endTime) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleMessage(QString,QDateTime,QColor,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | MESSAGE | REP_END; + // scheduleFile(URL, dateTime, colour, flags, audioURL, reminder, recurType, interval, endTime) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | FILE | REP_END; + // scheduleCommand(commandLine, dateTime, flags, recurType, interval, endTime) + else if (func == "scheduleCommand(const QString&,const QDateTime&,Q_UINT32,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleCommand(QString,QDateTime,Q_UINT32,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | COMMAND | REP_END; + // scheduleEmail(addresses, subject, message, attachments, dateTime, flags, recurType, interval, endTime) + else if (func == "scheduleEmail(const QString&,const QString&,const QString&,const QString&,const QDateTime&,Q_UINT32,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleEmail(QString,QString,QString,QString,QDateTime,Q_UINT32,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | EMAIL | REP_END; + + // Deprecated methods: backwards compatibility with KAlarm pre-0.9.6 + // scheduleMessage(message, dateTime, colour, flags, audioURL, reminder, recurrence) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,const QString&)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString,Q_UINT32,QString)") + function = SCHEDULE | MESSAGE | PRE_096; + // scheduleMessage(message, dateTime, colour, font, flags, audioURL, reminder, recurrence) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,const QFont&,Q_UINT32,const QString&,Q_INT32,const QString&)" + || func == "scheduleMessage(QString,QDateTime,QColor,QFont,Q_UINT32,QString,Q_UINT32,QString)") + function = SCHEDULE | MESSAGE | FONT | PRE_096; + // scheduleMessage(message, dateTime, colour, flags, audioURL, reminder, recurType, interval, recurCount) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | MESSAGE | REP_COUNT | PRE_096; + // scheduleMessage(message, dateTime, colour, flags, audioURL, reminder, recurType, interval, endTime) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | MESSAGE | REP_END | PRE_096; + + // Deprecated methods: backwards compatibility with KAlarm pre-0.9.1 + // scheduleMessage(message, dateTime, colour, flags, audioURL) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString)") + function = SCHEDULE | MESSAGE | PRE_091; + // scheduleFile(URL, dateTime, colour, flags, audioURL) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString)") + function = SCHEDULE | FILE | PRE_091; + // scheduleMessage(message, dateTime, colour, flags, audioURL, recurType, interval, recurCount) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | MESSAGE | REP_COUNT | PRE_091; + // scheduleFile(URL, dateTime, colour, flags, audioURL, recurType, interval, recurCount) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,Q_INT32)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,Q_INT32)") + function = SCHEDULE | FILE | REP_COUNT | PRE_091; + // scheduleMessage(message, dateTime, colour, flags, audioURL, recurType, interval, endTime) + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | MESSAGE | REP_END | PRE_091; + // scheduleFile(URL, dateTime, colour, flags, audioURL, recurType, interval, endTime) + else if (func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,const QString&,Q_INT32,Q_INT32,const QDateTime&)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,QString,Q_INT32,Q_INT32,QDateTime)") + function = SCHEDULE | FILE | REP_END | PRE_091; + + // Obsolete methods: backwards compatibility with KAlarm pre-0.7 + else if (func == "scheduleMessage(const QString&,const QDateTime&,const QColor&,Q_UINT32,Q_INT32,Q_INT32)" + || func == "scheduleMessage(QString,QDateTime,QColor,Q_UINT32,Q_INT32,Q_INT32)" + || func == "scheduleFile(const QString&,const QDateTime&,const QColor&,Q_UINT32,Q_INT32,Q_INT32)" + || func == "scheduleFile(QString,QDateTime,QColor,Q_UINT32,Q_INT32,Q_INT32)" + || func == "scheduleCommand(const QString&,const QDateTime&,Q_UINT32,Q_INT32,Q_INT32)" + || func == "scheduleCommand(QString,QDateTime,Q_UINT32,Q_INT32,Q_INT32)" + // Obsolete methods: backwards compatibility with KAlarm pre-0.6 + || func == "cancelMessage(const QString&,const QString&)" + || func == "cancelMessage(QString,QString)" + || func == "displayMessage(const QString&,const QString&)" + || func == "displayMessage(QString,QString)") + { + kdError(5950) << "DcopHandlerOld::process(): obsolete DCOP function call: '" << func << "'" << endl; + return false; + } + else + { + kdError(5950) << "DcopHandlerOld::process(): unknown DCOP function" << endl; + return false; + } + + switch (function & OPERATION) + { + case HANDLE: // trigger or cancel event with specified ID from calendar file + case CANCEL: // cancel event with specified ID from calendar file + case TRIGGER: // trigger event with specified ID in calendar file + { + + QDataStream arg(data, IO_ReadOnly); + QString urlString, vuid; + arg >> urlString >> vuid; + switch (function) + { + case HANDLE: + return theApp()->handleEvent(urlString, vuid); + case CANCEL: + return theApp()->deleteEvent(urlString, vuid); + case TRIGGER: + return theApp()->triggerEvent(urlString, vuid); + } + break; + } + case SCHEDULE: // schedule a new event + { + KAEvent::Action action; + switch (function & ALARM_TYPE) + { + case MESSAGE: action = KAEvent::MESSAGE; break; + case FILE: action = KAEvent::FILE; break; + case COMMAND: action = KAEvent::COMMAND; break; + case EMAIL: action = KAEvent::EMAIL; break; + default: return false; + } + QDataStream arg(data, IO_ReadOnly); + QString text, audioFile, mailSubject; + float audioVolume = -1; + EmailAddressList mailAddresses; + QStringList mailAttachments; + QDateTime dateTime, endTime; + QColor bgColour; + QColor fgColour(Qt::black); + QFont font; + Q_UINT32 flags; + int lateCancel = 0; + KARecurrence recurrence; + Q_INT32 reminderMinutes = 0; + if (action == KAEvent::EMAIL) + { + QString addresses, attachments; + arg >> addresses >> mailSubject >> text >> attachments; + QString bad = KAMail::convertAddresses(addresses, mailAddresses); + if (!bad.isEmpty()) + { + kdError(5950) << "DcopHandlerOld::process(): invalid email addresses: " << bad << endl; + return false; + } + if (mailAddresses.isEmpty()) + { + kdError(5950) << "DcopHandlerOld::process(): no email address\n"; + return false; + } + bad = KAMail::convertAttachments(attachments, mailAttachments); + if (!bad.isEmpty()) + { + kdError(5950) << "DcopHandlerOld::process(): invalid email attachment: " << bad << endl; + return false; + } + } + else + arg >> text; + arg.readRawBytes((char*)&dateTime, sizeof(dateTime)); + if (action != KAEvent::COMMAND) + arg.readRawBytes((char*)&bgColour, sizeof(bgColour)); + if (action == KAEvent::MESSAGE && !(function & PRE_096)) + arg.readRawBytes((char*)&fgColour, sizeof(fgColour)); + if (function & FONT) + { + arg.readRawBytes((char*)&font, sizeof(font)); + arg >> flags; + } + else + { + arg >> flags; + flags |= KAEvent::DEFAULT_FONT; + } + if (flags & KAEvent::LATE_CANCEL) + lateCancel = 1; + if (action == KAEvent::MESSAGE || action == KAEvent::FILE) + { + arg >> audioFile; + if (!(function & PRE_091)) + arg >> reminderMinutes; + } + if (function & (REP_COUNT | REP_END)) + { + KARecurrence::Type recurType; + Q_INT32 recurCount = 0; + Q_INT32 recurInterval; + Q_INT32 type; + arg >> type >> recurInterval; + switch (type) + { + case 1: recurType = KARecurrence::MINUTELY; break; + case 3: recurType = KARecurrence::DAILY; break; + case 4: recurType = KARecurrence::WEEKLY; break; + case 6: recurType = KARecurrence::MONTHLY_DAY; break; + case 7: recurType = KARecurrence::ANNUAL_DATE; break; + default: + kdError(5950) << "DcopHandlerOld::process(): invalid simple repetition type: " << type << endl; + return false; + } + if (function & REP_COUNT) + arg >> recurCount; + else + arg.readRawBytes((char*)&endTime, sizeof(endTime)); + recurrence.set(recurType, recurInterval, recurCount, + DateTime(dateTime, flags & KAEvent::ANY_TIME), endTime); + } + else if (!(function & PRE_091)) + { + QString rule; + arg >> rule; + recurrence.set(rule); + } + return theApp()->scheduleEvent(action, text, dateTime, lateCancel, flags, bgColour, fgColour, font, audioFile, + audioVolume, reminderMinutes, recurrence, 0, 0, 0, mailAddresses, mailSubject, mailAttachments); + } + } + return false; +} +#endif // OLD_DCOP diff --git a/kalarm/dcophandler.h b/kalarm/dcophandler.h new file mode 100644 index 000000000..1ea6f05e5 --- /dev/null +++ b/kalarm/dcophandler.h @@ -0,0 +1,107 @@ +/* + * dcophandler.h - handler for DCOP calls by other applications + * Program: kalarm + * Copyright © 2001,2002,2004-2006,2008 by 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 + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef DCOPHANDLER_H +#define DCOPHANDLER_H + +#include +#include + +#include "datetime.h" +#include "kalarmiface.h" + + +class DcopHandler : public QWidget, virtual public KAlarmIface +{ + public: + DcopHandler(); + virtual bool cancelEvent(const QString& url,const QString& eventId); + virtual bool triggerEvent(const QString& url,const QString& eventId); + + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int subRepeatInterval, int subRepeatCount); + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, int recurCount); + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, const QString& endDateTime); + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int subRepeatInterval, int subRepeatCount); + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, int recurCount); + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, int recurType, int recurInterval, const QString& endDateTime); + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int subRepeatInterval, int subRepeatCount); + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, int recurCount); + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, const QString& endDateTime); + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int recurInterval, int recurCount); + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, int recurCount); + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int recurType, int recurInterval, const QString& endDateTime); + virtual bool edit(const QString& eventID); + virtual bool editNew(const QString& templateName); + + private: + static bool scheduleMessage(const QString& message, const DateTime& start, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& fontStr, + const KURL& audioFile, int reminderMins, const KARecurrence&, + int subRepeatInterval = 0, int subRepeatCount = 0); + static bool scheduleFile(const KURL& file, const DateTime& start, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, const KARecurrence&, + int subRepeatInterval = 0, int subRepeatCount = 0); + static bool scheduleCommand(const QString& commandLine, const DateTime& start, int lateCancel, unsigned flags, + const KARecurrence&, int subRepeatInterval = 0, int subRepeatCount = 0); + static bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const DateTime& start, int lateCancel, unsigned flags, + const KARecurrence&, int subRepeatInterval = 0, int subRepeatCount = 0); + static DateTime convertStartDateTime(const QString& startDateTime); + static unsigned convertStartFlags(const DateTime& start, unsigned flags); + static QColor convertBgColour(const QString& bgColor); + static bool convertRecurrence(DateTime& start, KARecurrence&, const QString& startDateTime, const QString& icalRecurrence, int& subRepeatInterval); + static bool convertRecurrence(DateTime& start, KARecurrence&, const QString& startDateTime, int recurType, int recurInterval, int recurCount); + static bool convertRecurrence(DateTime& start, KARecurrence&, const QString& startDateTime, int recurType, int recurInterval, const QString& endDateTime); + static bool convertRecurrence(KARecurrence&, const DateTime& start, int recurType, int recurInterval, int recurCount, const QDateTime& end); +}; + + +#ifdef OLD_DCOP +class DcopHandlerOld : public QWidget, public DCOPObject +{ + Q_OBJECT + public: + DcopHandlerOld(); + ~DcopHandlerOld() { } + virtual bool process(const QCString& func, const QByteArray& data, QCString& replyType, QByteArray& replyData); +}; +#endif + +#endif // DCOPHANDLER_H diff --git a/kalarm/deferdlg.cpp b/kalarm/deferdlg.cpp new file mode 100644 index 000000000..5e0e039c4 --- /dev/null +++ b/kalarm/deferdlg.cpp @@ -0,0 +1,177 @@ +/* + * deferdlg.cpp - dialogue to defer an alarm + * Program: kalarm + * Copyright © 2002-2006,2008 by 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 "kalarm.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include "alarmcalendar.h" +#include "alarmevent.h" +#include "alarmtimewidget.h" +#include "datetime.h" +#include "functions.h" +#include "kalarmapp.h" +#include "deferdlg.moc" + + +DeferAlarmDlg::DeferAlarmDlg(const QString& caption, const DateTime& initialDT, + bool cancelButton, QWidget* parent, const char* name) + : KDialogBase(parent, name, true, caption, Ok|Cancel|User1, Ok, false, i18n("Cancel &Deferral")) +{ + if (!cancelButton) + showButton(User1, false); + + QWidget* page = new QWidget(this); + setMainWidget(page); + QVBoxLayout* layout = new QVBoxLayout(page, 0, spacingHint()); + + mTimeWidget = new AlarmTimeWidget(AlarmTimeWidget::DEFER_TIME, page, "timeGroup"); + mTimeWidget->setDateTime(initialDT); + mTimeWidget->setMinDateTimeIsCurrent(); + connect(mTimeWidget, SIGNAL(pastMax()), SLOT(slotPastLimit())); + layout->addWidget(mTimeWidget); + layout->addSpacing(spacingHint()); + + setButtonWhatsThis(Ok, i18n("Defer the alarm until the specified time.")); + setButtonWhatsThis(User1, i18n("Cancel the deferred alarm. This does not affect future recurrences.")); +} + + +/****************************************************************************** +* Called when the OK button is clicked. +*/ +void DeferAlarmDlg::slotOk() +{ + mAlarmDateTime = mTimeWidget->getDateTime(&mDeferMinutes); + if (!mAlarmDateTime.isValid()) + return; + KAEvent::DeferLimitType limitType; + DateTime endTime; + if (!mLimitEventID.isEmpty()) + { + // Get the event being deferred + const KCal::Event* kcalEvent = AlarmCalendar::getEvent(mLimitEventID); + if (kcalEvent) + { + KAEvent event(*kcalEvent); + endTime = event.deferralLimit(&limitType); + } + } + else + { + endTime = mLimitDateTime; + limitType = mLimitDateTime.isValid() ? KAEvent::LIMIT_MAIN : KAEvent::LIMIT_NONE; + } + if (endTime.isValid() && mAlarmDateTime > endTime) + { + QString text; + switch (limitType) + { + case KAEvent::LIMIT_REPETITION: + text = i18n("Cannot defer past the alarm's next sub-repetition (currently %1)"); + break; + case KAEvent::LIMIT_RECURRENCE: + text = i18n("Cannot defer past the alarm's next recurrence (currently %1)"); + break; + case KAEvent::LIMIT_REMINDER: + text = i18n("Cannot defer past the alarm's next reminder (currently %1)"); + break; + case KAEvent::LIMIT_MAIN: + text = i18n("Cannot defer reminder past the main alarm time (%1)"); + break; + case KAEvent::LIMIT_NONE: + break; // can't happen with a valid endTime + } + KMessageBox::sorry(this, text.arg(endTime.formatLocale())); + } + else + accept(); +} + +/****************************************************************************** +* Select the 'Time from now' radio button and preset its value. +*/ +void DeferAlarmDlg::setDeferMinutes(int minutes) +{ + mTimeWidget->selectTimeFromNow(minutes); +} + +/****************************************************************************** +* Called the maximum date/time for the date/time edit widget has been passed. +*/ +void DeferAlarmDlg::slotPastLimit() +{ + enableButtonOK(false); +} + +/****************************************************************************** +* Set the time limit for deferral based on the next occurrence of the alarm +* with the specified ID. +*/ +void DeferAlarmDlg::setLimit(const DateTime& limit) +{ + mLimitEventID = QString::null; + mLimitDateTime = limit; + mTimeWidget->setMaxDateTime(mLimitDateTime); +} + +/****************************************************************************** +* Set the time limit for deferral based on the next occurrence of the alarm +* with the specified ID. +*/ +DateTime DeferAlarmDlg::setLimit(const QString& eventID) +{ + mLimitEventID = eventID; + const KCal::Event* kcalEvent = AlarmCalendar::getEvent(mLimitEventID); + if (kcalEvent) + { + KAEvent event(*kcalEvent); + mLimitDateTime = event.deferralLimit(); + } + else + mLimitDateTime = DateTime(); + mTimeWidget->setMaxDateTime(mLimitDateTime); + return mLimitDateTime; +} + +/****************************************************************************** +* Called when the Cancel Deferral button is clicked. +*/ +void DeferAlarmDlg::slotUser1() +{ + mAlarmDateTime = DateTime(); + accept(); +} + +/****************************************************************************** +* Called when the Cancel button is clicked. +*/ +void DeferAlarmDlg::slotCancel() +{ + reject(); +} diff --git a/kalarm/deferdlg.h b/kalarm/deferdlg.h new file mode 100644 index 000000000..69275a054 --- /dev/null +++ b/kalarm/deferdlg.h @@ -0,0 +1,58 @@ +/* + * deferdlg.h - dialogue to defer an alarm + * Program: kalarm + * Copyright © 2002-2004,2006 by 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. + */ + +#ifndef DEFERDLG_H +#define DEFERDLG_H + +#include +#include "datetime.h" + +class AlarmTimeWidget; + + +class DeferAlarmDlg : public KDialogBase +{ + Q_OBJECT + public: + DeferAlarmDlg(const QString& caption, const DateTime& initialDT, + bool cancelButton, QWidget* parent = 0, const char* name = 0); + void setLimit(const DateTime&); + DateTime setLimit(const QString& eventID); + const DateTime& getDateTime() const { return mAlarmDateTime; } + void setDeferMinutes(int mins); + int deferMinutes() const { return mDeferMinutes; } + + protected slots: + virtual void slotOk(); + virtual void slotCancel(); + virtual void slotUser1(); + + private slots: + void slotPastLimit(); + + private: + AlarmTimeWidget* mTimeWidget; + DateTime mAlarmDateTime; + DateTime mLimitDateTime; // latest date/time allowed for deferral + QString mLimitEventID; // event from whose recurrences to derive the limit date/time for deferral + int mDeferMinutes; // number of minutes deferral selected, or 0 if date/time entered +}; + +#endif // DEFERDLG_H diff --git a/kalarm/editdlg.cpp b/kalarm/editdlg.cpp new file mode 100644 index 000000000..232111000 --- /dev/null +++ b/kalarm/editdlg.cpp @@ -0,0 +1,2043 @@ +/* + * editdlg.cpp - dialogue to create or modify an alarm or alarm template + * Program: kalarm + * Copyright © 2001-2008 by 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 "kalarm.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "alarmcalendar.h" +#include "alarmtimewidget.h" +#include "checkbox.h" +#include "colourcombo.h" +#include "deferdlg.h" +#include "emailidcombo.h" +#include "fontcolourbutton.h" +#include "functions.h" +#include "kalarmapp.h" +#include "kamail.h" +#include "latecancel.h" +#include "lineedit.h" +#include "mainwindow.h" +#include "pickfileradio.h" +#include "preferences.h" +#include "radiobutton.h" +#include "recurrenceedit.h" +#include "reminder.h" +#include "shellprocess.h" +#include "soundpicker.h" +#include "specialactions.h" +#include "spinbox.h" +#include "templatepickdlg.h" +#include "timeedit.h" +#include "timespinbox.h" +#include "editdlg.moc" +#include "editdlgprivate.moc" + +using namespace KCal; + +static const char EDIT_DIALOG_NAME[] = "EditDialog"; +static const int maxDelayTime = 99*60 + 59; // < 100 hours + +/*============================================================================= += Class PickAlarmFileRadio +=============================================================================*/ +class PickAlarmFileRadio : public PickFileRadio +{ + public: + PickAlarmFileRadio(const QString& text, QButtonGroup* parent, const char* name = 0) + : PickFileRadio(text, parent, name) { } + virtual QString pickFile() // called when browse button is pressed to select a file to display + { + return KAlarm::browseFile(i18n("Choose Text or Image File to Display"), mDefaultDir, fileEdit()->text(), + QString::null, KFile::ExistingOnly, parentWidget(), "pickAlarmFile"); + } + private: + QString mDefaultDir; // default directory for file browse button +}; + +/*============================================================================= += Class PickLogFileRadio +=============================================================================*/ +class PickLogFileRadio : public PickFileRadio +{ + public: + PickLogFileRadio(QPushButton* b, LineEdit* e, const QString& text, QButtonGroup* parent, const char* name = 0) + : PickFileRadio(b, e, text, parent, name) { } + virtual QString pickFile() // called when browse button is pressed to select a log file + { + return KAlarm::browseFile(i18n("Choose Log File"), mDefaultDir, fileEdit()->text(), QString::null, + KFile::LocalOnly, parentWidget(), "pickLogFile"); + } + private: + QString mDefaultDir; // default directory for log file browse button +}; + +inline QString recurText(const KAEvent& event) +{ + QString r; + if (event.repeatCount()) + r = QString::fromLatin1("%1 / %2").arg(event.recurrenceText()).arg(event.repetitionText()); + else + r = event.recurrenceText(); + return i18n("&Recurrence - [%1]").arg(r); +} + +// Collect these widget labels together to ensure consistent wording and +// translations across different modules. +QString EditAlarmDlg::i18n_ConfirmAck() { return i18n("Confirm acknowledgment"); } +QString EditAlarmDlg::i18n_k_ConfirmAck() { return i18n("Confirm ac&knowledgment"); } +QString EditAlarmDlg::i18n_SpecialActions() { return i18n("Special Actions..."); } +QString EditAlarmDlg::i18n_ShowInKOrganizer() { return i18n("Show in KOrganizer"); } +QString EditAlarmDlg::i18n_g_ShowInKOrganizer() { return i18n("Show in KOr&ganizer"); } +QString EditAlarmDlg::i18n_EnterScript() { return i18n("Enter a script"); } +QString EditAlarmDlg::i18n_p_EnterScript() { return i18n("Enter a scri&pt"); } +QString EditAlarmDlg::i18n_ExecInTermWindow() { return i18n("Execute in terminal window"); } +QString EditAlarmDlg::i18n_w_ExecInTermWindow() { return i18n("Execute in terminal &window"); } +QString EditAlarmDlg::i18n_u_ExecInTermWindow() { return i18n("Exec&ute in terminal window"); } +QString EditAlarmDlg::i18n_g_LogToFile() { return i18n("Lo&g to file"); } +QString EditAlarmDlg::i18n_CopyEmailToSelf() { return i18n("Copy email to self"); } +QString EditAlarmDlg::i18n_e_CopyEmailToSelf() { return i18n("Copy &email to self"); } +QString EditAlarmDlg::i18n_s_CopyEmailToSelf() { return i18n("Copy email to &self"); } +QString EditAlarmDlg::i18n_EmailFrom() { return i18n("'From' email address", "From:"); } +QString EditAlarmDlg::i18n_f_EmailFrom() { return i18n("'From' email address", "&From:"); } +QString EditAlarmDlg::i18n_EmailTo() { return i18n("Email addressee", "To:"); } +QString EditAlarmDlg::i18n_EmailSubject() { return i18n("Email subject", "Subject:"); } +QString EditAlarmDlg::i18n_j_EmailSubject() { return i18n("Email subject", "Sub&ject:"); } + + +/****************************************************************************** + * Constructor. + * Parameters: + * Template = true to edit/create an alarm template + * = false to edit/create an alarm. + * event != to initialise the dialogue to show the specified event's data. + */ +EditAlarmDlg::EditAlarmDlg(bool Template, const QString& caption, QWidget* parent, const char* name, + const KAEvent* event, bool readOnly) + : KDialogBase(parent, (name ? name : Template ? "TemplEditDlg" : "EditDlg"), true, caption, + (readOnly ? Cancel|Try : Template ? Ok|Cancel|Try : Ok|Cancel|Try|Default), + (readOnly ? Cancel : Ok)), + mMainPageShown(false), + mRecurPageShown(false), + mRecurSetDefaultEndDate(true), + mTemplateName(0), + mSpecialActionsButton(0), + mReminderDeferral(false), + mReminderArchived(false), + mEmailRemoveButton(0), + mDeferGroup(0), + mTimeWidget(0), + mShowInKorganizer(0), + mDeferGroupHeight(0), + mTemplate(Template), + mDesiredReadOnly(readOnly), + mReadOnly(readOnly), + mSavedEvent(0) +{ + setButtonText(Default, i18n("Load Template...")); + QVBox* mainWidget = new QVBox(this); + mainWidget->setSpacing(spacingHint()); + setMainWidget(mainWidget); + if (mTemplate) + { + QHBox* box = new QHBox(mainWidget); + box->setSpacing(spacingHint()); + QLabel* label = new QLabel(i18n("Template name:"), box); + label->setFixedSize(label->sizeHint()); + mTemplateName = new QLineEdit(box); + mTemplateName->setReadOnly(mReadOnly); + label->setBuddy(mTemplateName); + QWhatsThis::add(box, i18n("Enter the name of the alarm template")); + box->setFixedHeight(box->sizeHint().height()); + } + mTabs = new QTabWidget(mainWidget); + mTabs->setMargin(marginHint()); + + QVBox* mainPageBox = new QVBox(mTabs); + mainPageBox->setSpacing(spacingHint()); + mTabs->addTab(mainPageBox, i18n("&Alarm")); + mMainPageIndex = 0; + PageFrame* mainPage = new PageFrame(mainPageBox); + connect(mainPage, SIGNAL(shown()), SLOT(slotShowMainPage())); + QVBoxLayout* topLayout = new QVBoxLayout(mainPage, 0, spacingHint()); + + // Recurrence tab + QVBox* recurTab = new QVBox(mTabs); + mainPageBox->setSpacing(spacingHint()); + mTabs->addTab(recurTab, QString::null); + mRecurPageIndex = 1; + mRecurrenceEdit = new RecurrenceEdit(readOnly, recurTab, "recurPage"); + connect(mRecurrenceEdit, SIGNAL(shown()), SLOT(slotShowRecurrenceEdit())); + connect(mRecurrenceEdit, SIGNAL(typeChanged(int)), SLOT(slotRecurTypeChange(int))); + connect(mRecurrenceEdit, SIGNAL(frequencyChanged()), SLOT(slotRecurFrequencyChange())); + connect(mRecurrenceEdit, SIGNAL(repeatNeedsInitialisation()), SLOT(slotSetSubRepetition())); + + // Alarm action + + mActionGroup = new ButtonGroup(i18n("Action"), mainPage, "actionGroup"); + connect(mActionGroup, SIGNAL(buttonSet(int)), SLOT(slotAlarmTypeChanged(int))); + topLayout->addWidget(mActionGroup, 1); + QBoxLayout* layout = new QVBoxLayout(mActionGroup, marginHint(), spacingHint()); + layout->addSpacing(fontMetrics().lineSpacing()/2); + QGridLayout* grid = new QGridLayout(layout, 1, 5); + + // Message radio button + mMessageRadio = new RadioButton(i18n("Te&xt"), mActionGroup, "messageButton"); + mMessageRadio->setFixedSize(mMessageRadio->sizeHint()); + QWhatsThis::add(mMessageRadio, + i18n("If checked, the alarm will display a text message.")); + grid->addWidget(mMessageRadio, 1, 0); + grid->setColStretch(1, 1); + + // File radio button + mFileRadio = new PickAlarmFileRadio(i18n("&File"), mActionGroup, "fileButton"); + mFileRadio->setFixedSize(mFileRadio->sizeHint()); + QWhatsThis::add(mFileRadio, + i18n("If checked, the alarm will display the contents of a text or image file.")); + grid->addWidget(mFileRadio, 1, 2); + grid->setColStretch(3, 1); + + // Command radio button + mCommandRadio = new RadioButton(i18n("Co&mmand"), mActionGroup, "cmdButton"); + mCommandRadio->setFixedSize(mCommandRadio->sizeHint()); + QWhatsThis::add(mCommandRadio, + i18n("If checked, the alarm will execute a shell command.")); + grid->addWidget(mCommandRadio, 1, 4); + grid->setColStretch(5, 1); + + // Email radio button + mEmailRadio = new RadioButton(i18n("&Email"), mActionGroup, "emailButton"); + mEmailRadio->setFixedSize(mEmailRadio->sizeHint()); + QWhatsThis::add(mEmailRadio, + i18n("If checked, the alarm will send an email.")); + grid->addWidget(mEmailRadio, 1, 6); + + initDisplayAlarms(mActionGroup); + layout->addWidget(mDisplayAlarmsFrame); + initCommand(mActionGroup); + layout->addWidget(mCommandFrame); + initEmail(mActionGroup); + layout->addWidget(mEmailFrame); + + // Deferred date/time: visible only for a deferred recurring event. + mDeferGroup = new QGroupBox(1, Qt::Vertical, i18n("Deferred Alarm"), mainPage, "deferGroup"); + topLayout->addWidget(mDeferGroup); + QLabel* label = new QLabel(i18n("Deferred to:"), mDeferGroup); + label->setFixedSize(label->sizeHint()); + mDeferTimeLabel = new QLabel(mDeferGroup); + + mDeferChangeButton = new QPushButton(i18n("C&hange..."), mDeferGroup); + mDeferChangeButton->setFixedSize(mDeferChangeButton->sizeHint()); + connect(mDeferChangeButton, SIGNAL(clicked()), SLOT(slotEditDeferral())); + QWhatsThis::add(mDeferChangeButton, i18n("Change the alarm's deferred time, or cancel the deferral")); + mDeferGroup->addSpace(0); + + layout = new QHBoxLayout(topLayout); + + // Date and time entry + if (mTemplate) + { + mTemplateTimeGroup = new ButtonGroup(i18n("Time"), mainPage, "templateGroup"); + connect(mTemplateTimeGroup, SIGNAL(buttonSet(int)), SLOT(slotTemplateTimeType(int))); + layout->addWidget(mTemplateTimeGroup); + grid = new QGridLayout(mTemplateTimeGroup, 2, 2, marginHint(), spacingHint()); + grid->addRowSpacing(0, fontMetrics().lineSpacing()/2); + // Get alignment to use in QGridLayout (AlignAuto doesn't work correctly there) + int alignment = QApplication::reverseLayout() ? Qt::AlignRight : Qt::AlignLeft; + + mTemplateDefaultTime = new RadioButton(i18n("&Default time"), mTemplateTimeGroup, "templateDefTimeButton"); + mTemplateDefaultTime->setFixedSize(mTemplateDefaultTime->sizeHint()); + mTemplateDefaultTime->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateDefaultTime, + i18n("Do not specify a start time for alarms based on this template. " + "The normal default start time will be used.")); + grid->addWidget(mTemplateDefaultTime, 0, 0, alignment); + + QHBox* box = new QHBox(mTemplateTimeGroup); + box->setSpacing(spacingHint()); + mTemplateUseTime = new RadioButton(i18n("Time:"), box, "templateTimeButton"); + mTemplateUseTime->setFixedSize(mTemplateUseTime->sizeHint()); + mTemplateUseTime->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateUseTime, + i18n("Specify a start time for alarms based on this template.")); + mTemplateTimeGroup->insert(mTemplateUseTime); + mTemplateTime = new TimeEdit(box, "templateTimeEdit"); + mTemplateTime->setFixedSize(mTemplateTime->sizeHint()); + mTemplateTime->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateTime, + QString("%1\n\n%2").arg(i18n("Enter the start time for alarms based on this template.")) + .arg(TimeSpinBox::shiftWhatsThis())); + box->setStretchFactor(new QWidget(box), 1); // left adjust the controls + box->setFixedHeight(box->sizeHint().height()); + grid->addWidget(box, 0, 1, alignment); + + mTemplateAnyTime = new RadioButton(i18n("An&y time"), mTemplateTimeGroup, "templateAnyTimeButton"); + mTemplateAnyTime->setFixedSize(mTemplateAnyTime->sizeHint()); + mTemplateAnyTime->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateAnyTime, + i18n("Set the '%1' option for alarms based on this template.").arg(i18n("Any time"))); + grid->addWidget(mTemplateAnyTime, 1, 0, alignment); + + box = new QHBox(mTemplateTimeGroup); + box->setSpacing(spacingHint()); + mTemplateUseTimeAfter = new RadioButton(AlarmTimeWidget::i18n_w_TimeFromNow(), box, "templateFromNowButton"); + mTemplateUseTimeAfter->setFixedSize(mTemplateUseTimeAfter->sizeHint()); + mTemplateUseTimeAfter->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateUseTimeAfter, + i18n("Set alarms based on this template to start after the specified time " + "interval from when the alarm is created.")); + mTemplateTimeGroup->insert(mTemplateUseTimeAfter); + mTemplateTimeAfter = new TimeSpinBox(1, maxDelayTime, box); + mTemplateTimeAfter->setValue(1439); + mTemplateTimeAfter->setFixedSize(mTemplateTimeAfter->sizeHint()); + mTemplateTimeAfter->setReadOnly(mReadOnly); + QWhatsThis::add(mTemplateTimeAfter, + QString("%1\n\n%2").arg(AlarmTimeWidget::i18n_TimeAfterPeriod()) + .arg(TimeSpinBox::shiftWhatsThis())); + box->setFixedHeight(box->sizeHint().height()); + grid->addWidget(box, 1, 1, alignment); + + layout->addStretch(); + } + else + { + mTimeWidget = new AlarmTimeWidget(i18n("Time"), AlarmTimeWidget::AT_TIME, mainPage, "timeGroup"); + connect(mTimeWidget, SIGNAL(anyTimeToggled(bool)), SLOT(slotAnyTimeToggled(bool))); + topLayout->addWidget(mTimeWidget); + } + + // Reminder + static const QString reminderText = i18n("Enter how long in advance of the main alarm to display a reminder alarm."); + mReminder = new Reminder(i18n("Rem&inder:"), + i18n("Check to additionally display a reminder in advance of the main alarm time(s)."), + QString("%1\n\n%2").arg(reminderText).arg(TimeSpinBox::shiftWhatsThis()), + true, true, mainPage); + mReminder->setFixedSize(mReminder->sizeHint()); + topLayout->addWidget(mReminder, 0, Qt::AlignAuto); + + // Late cancel selector - default = allow late display + mLateCancel = new LateCancelSelector(true, mainPage); + topLayout->addWidget(mLateCancel, 0, Qt::AlignAuto); + + // Acknowledgement confirmation required - default = no confirmation + layout = new QHBoxLayout(topLayout, 0); + mConfirmAck = createConfirmAckCheckbox(mainPage); + mConfirmAck->setFixedSize(mConfirmAck->sizeHint()); + layout->addWidget(mConfirmAck); + layout->addSpacing(2*spacingHint()); + layout->addStretch(); + + if (theApp()->korganizerEnabled()) + { + // Show in KOrganizer checkbox + mShowInKorganizer = new CheckBox(i18n_ShowInKOrganizer(), mainPage); + mShowInKorganizer->setFixedSize(mShowInKorganizer->sizeHint()); + QWhatsThis::add(mShowInKorganizer, i18n("Check to copy the alarm into KOrganizer's calendar")); + layout->addWidget(mShowInKorganizer); + } + + setButtonWhatsThis(Ok, i18n("Schedule the alarm at the specified time.")); + + // Initialise the state of all controls according to the specified event, if any + initialise(event); + if (mTemplateName) + mTemplateName->setFocus(); + + // Save the initial state of all controls so that we can later tell if they have changed + saveState((event && (mTemplate || !event->isTemplate())) ? event : 0); + + // Note the current desktop so that the dialog can be shown on it. + // If a main window is visible, the dialog will by KDE default always appear on its + // desktop. If the user invokes the dialog via the system tray on a different desktop, + // that can cause confusion. + mDesktop = KWin::currentDesktop(); +} + +EditAlarmDlg::~EditAlarmDlg() +{ + delete mSavedEvent; +} + +/****************************************************************************** + * Set up the dialog controls common to display alarms. + */ +void EditAlarmDlg::initDisplayAlarms(QWidget* parent) +{ + mDisplayAlarmsFrame = new QFrame(parent); + mDisplayAlarmsFrame->setFrameStyle(QFrame::NoFrame); + QBoxLayout* frameLayout = new QVBoxLayout(mDisplayAlarmsFrame, 0, spacingHint()); + + // Text message edit box + mTextMessageEdit = new TextEdit(mDisplayAlarmsFrame); + mTextMessageEdit->setWordWrap(KTextEdit::NoWrap); + QWhatsThis::add(mTextMessageEdit, i18n("Enter the text of the alarm message. It may be multi-line.")); + frameLayout->addWidget(mTextMessageEdit); + + // File name edit box + mFileBox = new QHBox(mDisplayAlarmsFrame); + frameLayout->addWidget(mFileBox); + mFileMessageEdit = new LineEdit(LineEdit::Url, mFileBox); + mFileMessageEdit->setAcceptDrops(true); + QWhatsThis::add(mFileMessageEdit, i18n("Enter the name or URL of a text or image file to display.")); + + // File browse button + mFileBrowseButton = new QPushButton(mFileBox); + mFileBrowseButton->setPixmap(SmallIcon("fileopen")); + mFileBrowseButton->setFixedSize(mFileBrowseButton->sizeHint()); + QToolTip::add(mFileBrowseButton, i18n("Choose a file")); + QWhatsThis::add(mFileBrowseButton, i18n("Select a text or image file to display.")); + mFileRadio->init(mFileBrowseButton, mFileMessageEdit); + + // Font and colour choice button and sample text + mFontColourButton = new FontColourButton(mDisplayAlarmsFrame); + mFontColourButton->setMaximumHeight(mFontColourButton->sizeHint().height()); + frameLayout->addWidget(mFontColourButton); + + QHBoxLayout* layout = new QHBoxLayout(frameLayout, 0, 0); + mBgColourBox = new QHBox(mDisplayAlarmsFrame); + mBgColourBox->setSpacing(spacingHint()); + layout->addWidget(mBgColourBox); + layout->addStretch(); + QLabel* label = new QLabel(i18n("&Background color:"), mBgColourBox); + mBgColourButton = new ColourCombo(mBgColourBox); + label->setBuddy(mBgColourButton); + QWhatsThis::add(mBgColourBox, i18n("Select the alarm message background color")); + + // Sound checkbox and file selector + layout = new QHBoxLayout(frameLayout); + mSoundPicker = new SoundPicker(mDisplayAlarmsFrame); + mSoundPicker->setFixedSize(mSoundPicker->sizeHint()); + layout->addWidget(mSoundPicker); + layout->addSpacing(2*spacingHint()); + layout->addStretch(); + + if (ShellProcess::authorised()) // don't display if shell commands not allowed (e.g. kiosk mode) + { + // Special actions button + mSpecialActionsButton = new SpecialActionsButton(i18n_SpecialActions(), mDisplayAlarmsFrame); + mSpecialActionsButton->setFixedSize(mSpecialActionsButton->sizeHint()); + layout->addWidget(mSpecialActionsButton); + } + + // Top-adjust the controls + mFilePadding = new QHBox(mDisplayAlarmsFrame); + frameLayout->addWidget(mFilePadding); + frameLayout->setStretchFactor(mFilePadding, 1); +} + +/****************************************************************************** + * Set up the command alarm dialog controls. + */ +void EditAlarmDlg::initCommand(QWidget* parent) +{ + mCommandFrame = new QFrame(parent); + mCommandFrame->setFrameStyle(QFrame::NoFrame); + QBoxLayout* frameLayout = new QVBoxLayout(mCommandFrame, 0, spacingHint()); + + mCmdTypeScript = new CheckBox(i18n_p_EnterScript(), mCommandFrame); + mCmdTypeScript->setFixedSize(mCmdTypeScript->sizeHint()); + connect(mCmdTypeScript, SIGNAL(toggled(bool)), SLOT(slotCmdScriptToggled(bool))); + QWhatsThis::add(mCmdTypeScript, i18n("Check to enter the contents of a script instead of a shell command line")); + frameLayout->addWidget(mCmdTypeScript, 0, Qt::AlignAuto); + + mCmdCommandEdit = new LineEdit(LineEdit::Url, mCommandFrame); + QWhatsThis::add(mCmdCommandEdit, i18n("Enter a shell command to execute.")); + frameLayout->addWidget(mCmdCommandEdit); + + mCmdScriptEdit = new TextEdit(mCommandFrame); + QWhatsThis::add(mCmdScriptEdit, i18n("Enter the contents of a script to execute")); + frameLayout->addWidget(mCmdScriptEdit); + + // What to do with command output + + mCmdOutputGroup = new ButtonGroup(i18n("Command Output"), mCommandFrame); + frameLayout->addWidget(mCmdOutputGroup); + QBoxLayout* layout = new QVBoxLayout(mCmdOutputGroup, marginHint(), spacingHint()); + layout->addSpacing(fontMetrics().lineSpacing()/2); + + // Execute in terminal window + RadioButton* button = new RadioButton(i18n_u_ExecInTermWindow(), mCmdOutputGroup, "execInTerm"); + button->setFixedSize(button->sizeHint()); + QWhatsThis::add(button, i18n("Check to execute the command in a terminal window")); + mCmdOutputGroup->insert(button, EXEC_IN_TERMINAL); + layout->addWidget(button, 0, Qt::AlignAuto); + + // Log file name edit box + QHBox* box = new QHBox(mCmdOutputGroup); + (new QWidget(box))->setFixedWidth(button->style().subRect(QStyle::SR_RadioButtonIndicator, button).width()); // indent the edit box +// (new QWidget(box))->setFixedWidth(button->style().pixelMetric(QStyle::PM_ExclusiveIndicatorWidth)); // indent the edit box + mCmdLogFileEdit = new LineEdit(LineEdit::Url, box); + mCmdLogFileEdit->setAcceptDrops(true); + QWhatsThis::add(mCmdLogFileEdit, i18n("Enter the name or path of the log file.")); + + // Log file browse button. + // The file browser dialogue is activated by the PickLogFileRadio class. + QPushButton* browseButton = new QPushButton(box); + browseButton->setPixmap(SmallIcon("fileopen")); + browseButton->setFixedSize(browseButton->sizeHint()); + QToolTip::add(browseButton, i18n("Choose a file")); + QWhatsThis::add(browseButton, i18n("Select a log file.")); + + // Log output to file + button = new PickLogFileRadio(browseButton, mCmdLogFileEdit, i18n_g_LogToFile(), mCmdOutputGroup, "cmdLog"); + button->setFixedSize(button->sizeHint()); + QWhatsThis::add(button, + i18n("Check to log the command output to a local file. The output will be appended to any existing contents of the file.")); + mCmdOutputGroup->insert(button, LOG_TO_FILE); + layout->addWidget(button, 0, Qt::AlignAuto); + layout->addWidget(box); + + // Discard output + button = new RadioButton(i18n("Discard"), mCmdOutputGroup, "cmdDiscard"); + button->setFixedSize(button->sizeHint()); + QWhatsThis::add(button, i18n("Check to discard command output.")); + mCmdOutputGroup->insert(button, DISCARD_OUTPUT); + layout->addWidget(button, 0, Qt::AlignAuto); + + // Top-adjust the controls + mCmdPadding = new QHBox(mCommandFrame); + frameLayout->addWidget(mCmdPadding); + frameLayout->setStretchFactor(mCmdPadding, 1); +} + +/****************************************************************************** + * Set up the email alarm dialog controls. + */ +void EditAlarmDlg::initEmail(QWidget* parent) +{ + mEmailFrame = new QFrame(parent); + mEmailFrame->setFrameStyle(QFrame::NoFrame); + QBoxLayout* layout = new QVBoxLayout(mEmailFrame, 0, spacingHint()); + QGridLayout* grid = new QGridLayout(layout, 3, 3, spacingHint()); + grid->setColStretch(1, 1); + + mEmailFromList = 0; + if (Preferences::emailFrom() == Preferences::MAIL_FROM_KMAIL) + { + // Email sender identity + QLabel* label = new QLabel(i18n_EmailFrom(), mEmailFrame); + label->setFixedSize(label->sizeHint()); + grid->addWidget(label, 0, 0); + + mEmailFromList = new EmailIdCombo(KAMail::identityManager(), mEmailFrame); + mEmailFromList->setMinimumSize(mEmailFromList->sizeHint()); + label->setBuddy(mEmailFromList); + QWhatsThis::add(mEmailFromList, + i18n("Your email identity, used to identify you as the sender when sending email alarms.")); + grid->addMultiCellWidget(mEmailFromList, 0, 0, 1, 2); + } + + // Email recipients + QLabel* label = new QLabel(i18n_EmailTo(), mEmailFrame); + label->setFixedSize(label->sizeHint()); + grid->addWidget(label, 1, 0); + + mEmailToEdit = new LineEdit(LineEdit::Emails, mEmailFrame); + mEmailToEdit->setMinimumSize(mEmailToEdit->sizeHint()); + QWhatsThis::add(mEmailToEdit, + i18n("Enter the addresses of the email recipients. Separate multiple addresses by " + "commas or semicolons.")); + grid->addWidget(mEmailToEdit, 1, 1); + + mEmailAddressButton = new QPushButton(mEmailFrame); + mEmailAddressButton->setPixmap(SmallIcon("contents")); + mEmailAddressButton->setFixedSize(mEmailAddressButton->sizeHint()); + connect(mEmailAddressButton, SIGNAL(clicked()), SLOT(openAddressBook())); + QToolTip::add(mEmailAddressButton, i18n("Open address book")); + QWhatsThis::add(mEmailAddressButton, i18n("Select email addresses from your address book.")); + grid->addWidget(mEmailAddressButton, 1, 2); + + // Email subject + label = new QLabel(i18n_j_EmailSubject(), mEmailFrame); + label->setFixedSize(label->sizeHint()); + grid->addWidget(label, 2, 0); + + mEmailSubjectEdit = new LineEdit(mEmailFrame); + mEmailSubjectEdit->setMinimumSize(mEmailSubjectEdit->sizeHint()); + label->setBuddy(mEmailSubjectEdit); + QWhatsThis::add(mEmailSubjectEdit, i18n("Enter the email subject.")); + grid->addMultiCellWidget(mEmailSubjectEdit, 2, 2, 1, 2); + + // Email body + mEmailMessageEdit = new TextEdit(mEmailFrame); + QWhatsThis::add(mEmailMessageEdit, i18n("Enter the email message.")); + layout->addWidget(mEmailMessageEdit); + + // Email attachments + grid = new QGridLayout(layout, 2, 3, spacingHint()); + label = new QLabel(i18n("Attachment&s:"), mEmailFrame); + label->setFixedSize(label->sizeHint()); + grid->addWidget(label, 0, 0); + + mEmailAttachList = new QComboBox(true, mEmailFrame); + mEmailAttachList->setMinimumSize(mEmailAttachList->sizeHint()); + mEmailAttachList->lineEdit()->setReadOnly(true); +QListBox* list = mEmailAttachList->listBox(); +QRect rect = list->geometry(); +list->setGeometry(rect.left() - 50, rect.top(), rect.width(), rect.height()); + label->setBuddy(mEmailAttachList); + QWhatsThis::add(mEmailAttachList, + i18n("Files to send as attachments to the email.")); + grid->addWidget(mEmailAttachList, 0, 1); + grid->setColStretch(1, 1); + + mEmailAddAttachButton = new QPushButton(i18n("Add..."), mEmailFrame); + connect(mEmailAddAttachButton, SIGNAL(clicked()), SLOT(slotAddAttachment())); + QWhatsThis::add(mEmailAddAttachButton, i18n("Add an attachment to the email.")); + grid->addWidget(mEmailAddAttachButton, 0, 2); + + mEmailRemoveButton = new QPushButton(i18n("Remo&ve"), mEmailFrame); + connect(mEmailRemoveButton, SIGNAL(clicked()), SLOT(slotRemoveAttachment())); + QWhatsThis::add(mEmailRemoveButton, i18n("Remove the highlighted attachment from the email.")); + grid->addWidget(mEmailRemoveButton, 1, 2); + + // BCC email to sender + mEmailBcc = new CheckBox(i18n_s_CopyEmailToSelf(), mEmailFrame); + mEmailBcc->setFixedSize(mEmailBcc->sizeHint()); + QWhatsThis::add(mEmailBcc, + i18n("If checked, the email will be blind copied to you.")); + grid->addMultiCellWidget(mEmailBcc, 1, 1, 0, 1, Qt::AlignAuto); +} + +/****************************************************************************** + * Initialise the dialogue controls from the specified event. + */ +void EditAlarmDlg::initialise(const KAEvent* event) +{ + mReadOnly = mDesiredReadOnly; + if (!mTemplate && event && event->action() == KAEvent::COMMAND && !ShellProcess::authorised()) + mReadOnly = true; // don't allow editing of existing command alarms in kiosk mode + setReadOnly(); + + mChanged = false; + mOnlyDeferred = false; + mExpiredRecurrence = false; + mKMailSerialNumber = 0; + bool deferGroupVisible = false; + if (event) + { + // Set the values to those for the specified event + if (mTemplate) + mTemplateName->setText(event->templateName()); + bool recurs = event->recurs(); + if ((recurs || event->repeatCount()) && !mTemplate && event->deferred()) + { + deferGroupVisible = true; + mDeferDateTime = event->deferDateTime(); + mDeferTimeLabel->setText(mDeferDateTime.formatLocale()); + mDeferGroup->show(); + } + if (event->defaultFont()) + mFontColourButton->setDefaultFont(); + else + mFontColourButton->setFont(event->font()); + mFontColourButton->setBgColour(event->bgColour()); + mFontColourButton->setFgColour(event->fgColour()); + mBgColourButton->setColour(event->bgColour()); + if (mTemplate) + { + // Editing a template + int afterTime = event->isTemplate() ? event->templateAfterTime() : -1; + bool noTime = !afterTime; + bool useTime = !event->mainDateTime().isDateOnly(); + int button = mTemplateTimeGroup->id(noTime ? mTemplateDefaultTime : + (afterTime > 0) ? mTemplateUseTimeAfter : + useTime ? mTemplateUseTime : mTemplateAnyTime); + mTemplateTimeGroup->setButton(button); + mTemplateTimeAfter->setValue(afterTime > 0 ? afterTime : 1); + if (!noTime && useTime) + mTemplateTime->setValue(event->mainDateTime().time()); + else + mTemplateTime->setValue(0); + } + else + { + if (event->isTemplate()) + { + // Initialising from an alarm template: use current date + QDateTime now = QDateTime::currentDateTime(); + int afterTime = event->templateAfterTime(); + if (afterTime >= 0) + { + mTimeWidget->setDateTime(now.addSecs(afterTime * 60)); + mTimeWidget->selectTimeFromNow(); + } + else + { + QDate d = now.date(); + QTime t = event->startDateTime().time(); + bool dateOnly = event->startDateTime().isDateOnly(); + if (!dateOnly && now.time() >= t) + d = d.addDays(1); // alarm time has already passed, so use tomorrow + mTimeWidget->setDateTime(DateTime(QDateTime(d, t), dateOnly)); + } + } + else + { + mExpiredRecurrence = recurs && event->mainExpired(); + mTimeWidget->setDateTime(recurs || event->uidStatus() == KAEvent::EXPIRED ? event->startDateTime() + : event->mainExpired() ? event->deferDateTime() : event->mainDateTime()); + } + } + + KAEvent::Action action = event->action(); + AlarmText altext; + if (event->commandScript()) + altext.setScript(event->cleanText()); + else + altext.setText(event->cleanText()); + setAction(action, altext); + if (action == KAEvent::MESSAGE && event->kmailSerialNumber() + && AlarmText::checkIfEmail(event->cleanText())) + mKMailSerialNumber = event->kmailSerialNumber(); + if (action == KAEvent::EMAIL) + mEmailAttachList->insertStringList(event->emailAttachments()); + + mLateCancel->setMinutes(event->lateCancel(), event->startDateTime().isDateOnly(), + TimePeriod::HOURS_MINUTES); + mLateCancel->showAutoClose(action == KAEvent::MESSAGE || action == KAEvent::FILE); + mLateCancel->setAutoClose(event->autoClose()); + mLateCancel->setFixedSize(mLateCancel->sizeHint()); + if (mShowInKorganizer) + mShowInKorganizer->setChecked(event->copyToKOrganizer()); + mConfirmAck->setChecked(event->confirmAck()); + int reminder = event->reminder(); + if (!reminder && event->reminderDeferral() && !recurs) + { + reminder = event->reminderDeferral(); + mReminderDeferral = true; + } + if (!reminder && event->reminderArchived() && recurs) + { + reminder = event->reminderArchived(); + mReminderArchived = true; + } + mReminder->setMinutes(reminder, (mTimeWidget ? mTimeWidget->anyTime() : mTemplateAnyTime->isOn())); + mReminder->setOnceOnly(event->reminderOnceOnly()); + mReminder->enableOnceOnly(event->recurs()); + if (mSpecialActionsButton) + mSpecialActionsButton->setActions(event->preAction(), event->postAction()); + mRecurrenceEdit->set(*event, (mTemplate || event->isTemplate())); // must be called after mTimeWidget is set up, to ensure correct date-only enabling + mTabs->setTabLabel(mTabs->page(mRecurPageIndex), recurText(*event)); + SoundPicker::Type soundType = event->speak() ? SoundPicker::SPEAK + : event->beep() ? SoundPicker::BEEP + : !event->audioFile().isEmpty() ? SoundPicker::PLAY_FILE + : SoundPicker::NONE; + mSoundPicker->set(soundType, event->audioFile(), event->soundVolume(), + event->fadeVolume(), event->fadeSeconds(), event->repeatSound()); + CmdLogType logType = event->commandXterm() ? EXEC_IN_TERMINAL + : !event->logFile().isEmpty() ? LOG_TO_FILE + : DISCARD_OUTPUT; + if (logType == LOG_TO_FILE) + mCmdLogFileEdit->setText(event->logFile()); // set file name before setting radio button + mCmdOutputGroup->setButton(logType); + mEmailToEdit->setText(event->emailAddresses(", ")); + mEmailSubjectEdit->setText(event->emailSubject()); + mEmailBcc->setChecked(event->emailBcc()); + if (mEmailFromList) + mEmailFromList->setCurrentIdentity(event->emailFromId()); + } + else + { + // Set the values to their defaults + if (!ShellProcess::authorised()) + { + // Don't allow shell commands in kiosk mode + mCommandRadio->setEnabled(false); + if (mSpecialActionsButton) + mSpecialActionsButton->setEnabled(false); + } + mFontColourButton->setDefaultFont(); + mFontColourButton->setBgColour(Preferences::defaultBgColour()); + mFontColourButton->setFgColour(Preferences::defaultFgColour()); + mBgColourButton->setColour(Preferences::defaultBgColour()); + QDateTime defaultTime = QDateTime::currentDateTime().addSecs(60); + if (mTemplate) + { + mTemplateTimeGroup->setButton(mTemplateTimeGroup->id(mTemplateDefaultTime)); + mTemplateTime->setValue(0); + mTemplateTimeAfter->setValue(1); + } + else + mTimeWidget->setDateTime(defaultTime); + mActionGroup->setButton(mActionGroup->id(mMessageRadio)); + mLateCancel->setMinutes((Preferences::defaultLateCancel() ? 1 : 0), false, TimePeriod::HOURS_MINUTES); + mLateCancel->showAutoClose(true); + mLateCancel->setAutoClose(Preferences::defaultAutoClose()); + mLateCancel->setFixedSize(mLateCancel->sizeHint()); + if (mShowInKorganizer) + mShowInKorganizer->setChecked(Preferences::defaultCopyToKOrganizer()); + mConfirmAck->setChecked(Preferences::defaultConfirmAck()); + if (mSpecialActionsButton) + mSpecialActionsButton->setActions(Preferences::defaultPreAction(), Preferences::defaultPostAction()); + mRecurrenceEdit->setDefaults(defaultTime); // must be called after mTimeWidget is set up, to ensure correct date-only enabling + slotRecurFrequencyChange(); // update the Recurrence text + mReminder->setMinutes(0, false); + mReminder->enableOnceOnly(mRecurrenceEdit->isTimedRepeatType()); // must be called after mRecurrenceEdit is set up + mSoundPicker->set(Preferences::defaultSoundType(), Preferences::defaultSoundFile(), + Preferences::defaultSoundVolume(), -1, 0, Preferences::defaultSoundRepeat()); + mCmdTypeScript->setChecked(Preferences::defaultCmdScript()); + mCmdLogFileEdit->setText(Preferences::defaultCmdLogFile()); // set file name before setting radio button + mCmdOutputGroup->setButton(Preferences::defaultCmdLogType()); + mEmailBcc->setChecked(Preferences::defaultEmailBcc()); + } + slotCmdScriptToggled(mCmdTypeScript->isChecked()); + + if (!deferGroupVisible) + mDeferGroup->hide(); + + bool enable = !!mEmailAttachList->count(); + mEmailAttachList->setEnabled(enable); + if (mEmailRemoveButton) + mEmailRemoveButton->setEnabled(enable); + AlarmCalendar* cal = AlarmCalendar::templateCalendar(); + bool empty = cal->isOpen() && !cal->events().count(); + enableButton(Default, !empty); +} + +/****************************************************************************** + * Set the read-only status of all non-template controls. + */ +void EditAlarmDlg::setReadOnly() +{ + // Common controls + mMessageRadio->setReadOnly(mReadOnly); + mFileRadio->setReadOnly(mReadOnly); + mCommandRadio->setReadOnly(mReadOnly); + mEmailRadio->setReadOnly(mReadOnly); + if (mTimeWidget) + mTimeWidget->setReadOnly(mReadOnly); + mLateCancel->setReadOnly(mReadOnly); + if (mReadOnly) + mDeferChangeButton->hide(); + else + mDeferChangeButton->show(); + if (mShowInKorganizer) + mShowInKorganizer->setReadOnly(mReadOnly); + + // Message alarm controls + mTextMessageEdit->setReadOnly(mReadOnly); + mFileMessageEdit->setReadOnly(mReadOnly); + mFontColourButton->setReadOnly(mReadOnly); + mBgColourButton->setReadOnly(mReadOnly); + mSoundPicker->setReadOnly(mReadOnly); + mConfirmAck->setReadOnly(mReadOnly); + mReminder->setReadOnly(mReadOnly); + if (mSpecialActionsButton) + mSpecialActionsButton->setReadOnly(mReadOnly); + if (mReadOnly) + { + mFileBrowseButton->hide(); + mFontColourButton->hide(); + } + else + { + mFileBrowseButton->show(); + mFontColourButton->show(); + } + + // Command alarm controls + mCmdTypeScript->setReadOnly(mReadOnly); + mCmdCommandEdit->setReadOnly(mReadOnly); + mCmdScriptEdit->setReadOnly(mReadOnly); + for (int id = DISCARD_OUTPUT; id < EXEC_IN_TERMINAL; ++id) + ((RadioButton*)mCmdOutputGroup->find(id))->setReadOnly(mReadOnly); + + // Email alarm controls + mEmailToEdit->setReadOnly(mReadOnly); + mEmailSubjectEdit->setReadOnly(mReadOnly); + mEmailMessageEdit->setReadOnly(mReadOnly); + mEmailBcc->setReadOnly(mReadOnly); + if (mEmailFromList) + mEmailFromList->setReadOnly(mReadOnly); + if (mReadOnly) + { + mEmailAddressButton->hide(); + mEmailAddAttachButton->hide(); + mEmailRemoveButton->hide(); + } + else + { + mEmailAddressButton->show(); + mEmailAddAttachButton->show(); + mEmailRemoveButton->show(); + } +} + +/****************************************************************************** + * Set the dialog's action and the action's text. + */ +void EditAlarmDlg::setAction(KAEvent::Action action, const AlarmText& alarmText) +{ + QString text = alarmText.displayText(); + bool script; + QRadioButton* radio; + switch (action) + { + case KAEvent::FILE: + radio = mFileRadio; + mFileMessageEdit->setText(text); + break; + case KAEvent::COMMAND: + radio = mCommandRadio; + script = alarmText.isScript(); + mCmdTypeScript->setChecked(script); + if (script) + mCmdScriptEdit->setText(text); + else + mCmdCommandEdit->setText(text); + break; + case KAEvent::EMAIL: + radio = mEmailRadio; + mEmailMessageEdit->setText(text); + break; + case KAEvent::MESSAGE: + default: + radio = mMessageRadio; + mTextMessageEdit->setText(text); + mKMailSerialNumber = 0; + if (alarmText.isEmail()) + { + mKMailSerialNumber = alarmText.kmailSerialNumber(); + + // Set up email fields also, in case the user wants an email alarm + mEmailToEdit->setText(alarmText.to()); + mEmailSubjectEdit->setText(alarmText.subject()); + mEmailMessageEdit->setText(alarmText.body()); + } + else if (alarmText.isScript()) + { + // Set up command script field also, in case the user wants a command alarm + mCmdScriptEdit->setText(text); + mCmdTypeScript->setChecked(true); + } + break; + } + mActionGroup->setButton(mActionGroup->id(radio)); +} + +/****************************************************************************** + * Create an "acknowledgement confirmation required" checkbox. + */ +CheckBox* EditAlarmDlg::createConfirmAckCheckbox(QWidget* parent, const char* name) +{ + CheckBox* widget = new CheckBox(i18n_k_ConfirmAck(), parent, name); + QWhatsThis::add(widget, + i18n("Check to be prompted for confirmation when you acknowledge the alarm.")); + return widget; +} + +/****************************************************************************** + * Save the state of all controls. + */ +void EditAlarmDlg::saveState(const KAEvent* event) +{ + delete mSavedEvent; + mSavedEvent = 0; + if (event) + mSavedEvent = new KAEvent(*event); + if (mTemplate) + { + mSavedTemplateName = mTemplateName->text(); + mSavedTemplateTimeType = mTemplateTimeGroup->selected(); + mSavedTemplateTime = mTemplateTime->time(); + mSavedTemplateAfterTime = mTemplateTimeAfter->value(); + } + mSavedTypeRadio = mActionGroup->selected(); + mSavedSoundType = mSoundPicker->sound(); + mSavedSoundFile = mSoundPicker->file(); + mSavedSoundVolume = mSoundPicker->volume(mSavedSoundFadeVolume, mSavedSoundFadeSeconds); + mSavedRepeatSound = mSoundPicker->repeat(); + mSavedConfirmAck = mConfirmAck->isChecked(); + mSavedFont = mFontColourButton->font(); + mSavedFgColour = mFontColourButton->fgColour(); + mSavedBgColour = mFileRadio->isOn() ? mBgColourButton->colour() : mFontColourButton->bgColour(); + mSavedReminder = mReminder->minutes(); + mSavedOnceOnly = mReminder->isOnceOnly(); + if (mSpecialActionsButton) + { + mSavedPreAction = mSpecialActionsButton->preAction(); + mSavedPostAction = mSpecialActionsButton->postAction(); + } + checkText(mSavedTextFileCommandMessage, false); + mSavedCmdScript = mCmdTypeScript->isChecked(); + mSavedCmdOutputRadio = mCmdOutputGroup->selected(); + mSavedCmdLogFile = mCmdLogFileEdit->text(); + if (mEmailFromList) + mSavedEmailFrom = mEmailFromList->currentIdentityName(); + mSavedEmailTo = mEmailToEdit->text(); + mSavedEmailSubject = mEmailSubjectEdit->text(); + mSavedEmailAttach.clear(); + for (int i = 0; i < mEmailAttachList->count(); ++i) + mSavedEmailAttach += mEmailAttachList->text(i); + mSavedEmailBcc = mEmailBcc->isChecked(); + if (mTimeWidget) + mSavedDateTime = mTimeWidget->getDateTime(0, false, false); + mSavedLateCancel = mLateCancel->minutes(); + mSavedAutoClose = mLateCancel->isAutoClose(); + if (mShowInKorganizer) + mSavedShowInKorganizer = mShowInKorganizer->isChecked(); + mSavedRecurrenceType = mRecurrenceEdit->repeatType(); +} + +/****************************************************************************** + * Check whether any of the controls has changed state since the dialog was + * first displayed. + * Reply = true if any non-deferral controls have changed, or if it's a new event. + * = false if no non-deferral controls have changed. In this case, + * mOnlyDeferred indicates whether deferral controls may have changed. + */ +bool EditAlarmDlg::stateChanged() const +{ + mChanged = true; + mOnlyDeferred = false; + if (!mSavedEvent) + return true; + QString textFileCommandMessage; + checkText(textFileCommandMessage, false); + if (mTemplate) + { + if (mSavedTemplateName != mTemplateName->text() + || mSavedTemplateTimeType != mTemplateTimeGroup->selected() + || mTemplateUseTime->isOn() && mSavedTemplateTime != mTemplateTime->time() + || mTemplateUseTimeAfter->isOn() && mSavedTemplateAfterTime != mTemplateTimeAfter->value()) + return true; + } + else + if (mSavedDateTime != mTimeWidget->getDateTime(0, false, false)) + return true; + if (mSavedTypeRadio != mActionGroup->selected() + || mSavedLateCancel != mLateCancel->minutes() + || mShowInKorganizer && mSavedShowInKorganizer != mShowInKorganizer->isChecked() + || textFileCommandMessage != mSavedTextFileCommandMessage + || mSavedRecurrenceType != mRecurrenceEdit->repeatType()) + return true; + if (mMessageRadio->isOn() || mFileRadio->isOn()) + { + if (mSavedSoundType != mSoundPicker->sound() + || mSavedConfirmAck != mConfirmAck->isChecked() + || mSavedFont != mFontColourButton->font() + || mSavedFgColour != mFontColourButton->fgColour() + || mSavedBgColour != (mFileRadio->isOn() ? mBgColourButton->colour() : mFontColourButton->bgColour()) + || mSavedReminder != mReminder->minutes() + || mSavedOnceOnly != mReminder->isOnceOnly() + || mSavedAutoClose != mLateCancel->isAutoClose()) + return true; + if (mSpecialActionsButton) + { + if (mSavedPreAction != mSpecialActionsButton->preAction() + || mSavedPostAction != mSpecialActionsButton->postAction()) + return true; + } + if (mSavedSoundType == SoundPicker::PLAY_FILE) + { + if (mSavedSoundFile != mSoundPicker->file()) + return true; + if (!mSavedSoundFile.isEmpty()) + { + float fadeVolume; + int fadeSecs; + if (mSavedRepeatSound != mSoundPicker->repeat() + || mSavedSoundVolume != mSoundPicker->volume(fadeVolume, fadeSecs) + || mSavedSoundFadeVolume != fadeVolume + || mSavedSoundFadeSeconds != fadeSecs) + return true; + } + } + } + else if (mCommandRadio->isOn()) + { + if (mSavedCmdScript != mCmdTypeScript->isChecked() + || mSavedCmdOutputRadio != mCmdOutputGroup->selected()) + return true; + if (mCmdOutputGroup->selectedId() == LOG_TO_FILE) + { + if (mSavedCmdLogFile != mCmdLogFileEdit->text()) + return true; + } + } + else if (mEmailRadio->isOn()) + { + QStringList emailAttach; + for (int i = 0; i < mEmailAttachList->count(); ++i) + emailAttach += mEmailAttachList->text(i); + if (mEmailFromList && mSavedEmailFrom != mEmailFromList->currentIdentityName() + || mSavedEmailTo != mEmailToEdit->text() + || mSavedEmailSubject != mEmailSubjectEdit->text() + || mSavedEmailAttach != emailAttach + || mSavedEmailBcc != mEmailBcc->isChecked()) + return true; + } + if (mRecurrenceEdit->stateChanged()) + return true; + if (mSavedEvent && mSavedEvent->deferred()) + mOnlyDeferred = true; + mChanged = false; + return false; +} + +/****************************************************************************** + * Get the currently entered dialogue data. + * The data is returned in the supplied KAEvent instance. + * Reply = false if the only change has been to an existing deferral. + */ +bool EditAlarmDlg::getEvent(KAEvent& event) +{ + if (mChanged) + { + // It's a new event, or the edit controls have changed + setEvent(event, mAlarmMessage, false); + return true; + } + + // Only the deferral time may have changed + event = *mSavedEvent; + if (mOnlyDeferred) + { + // Just modify the original event, to avoid expired recurring events + // being returned as rubbish. + if (mDeferDateTime.isValid()) + event.defer(mDeferDateTime, event.reminderDeferral(), false); + else + event.cancelDefer(); + } + return false; +} + +/****************************************************************************** +* Extract the data in the dialogue and set up a KAEvent from it. +* If 'trial' is true, the event is set up for a simple one-off test, ignoring +* recurrence, reminder, template etc. data. +*/ +void EditAlarmDlg::setEvent(KAEvent& event, const QString& text, bool trial) +{ + QDateTime dt; + if (!trial) + { + if (!mTemplate) + dt = mAlarmDateTime.dateTime(); + else if (mTemplateUseTime->isOn()) + dt = QDateTime(QDate(2000,1,1), mTemplateTime->time()); + } + KAEvent::Action type = getAlarmType(); + event.set(dt, text, (mFileRadio->isOn() ? mBgColourButton->colour() : mFontColourButton->bgColour()), + mFontColourButton->fgColour(), mFontColourButton->font(), + type, (trial ? 0 : mLateCancel->minutes()), getAlarmFlags()); + switch (type) + { + case KAEvent::MESSAGE: + if (AlarmText::checkIfEmail(text)) + event.setKMailSerialNumber(mKMailSerialNumber); + // fall through to FILE + case KAEvent::FILE: + { + float fadeVolume; + int fadeSecs; + float volume = mSoundPicker->volume(fadeVolume, fadeSecs); + event.setAudioFile(mSoundPicker->file(), volume, fadeVolume, fadeSecs); + if (!trial) + event.setReminder(mReminder->minutes(), mReminder->isOnceOnly()); + if (mSpecialActionsButton) + event.setActions(mSpecialActionsButton->preAction(), mSpecialActionsButton->postAction()); + break; + } + case KAEvent::EMAIL: + { + uint from = mEmailFromList ? mEmailFromList->currentIdentity() : 0; + event.setEmail(from, mEmailAddresses, mEmailSubjectEdit->text(), mEmailAttachments); + break; + } + case KAEvent::COMMAND: + if (mCmdOutputGroup->selectedId() == LOG_TO_FILE) + event.setLogFile(mCmdLogFileEdit->text()); + break; + default: + break; + } + if (!trial) + { + if (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR) + { + mRecurrenceEdit->updateEvent(event, !mTemplate); + QDateTime now = QDateTime::currentDateTime(); + bool dateOnly = mAlarmDateTime.isDateOnly(); + if (dateOnly && mAlarmDateTime.date() < now.date() + || !dateOnly && mAlarmDateTime.rawDateTime() < now) + { + // A timed recurrence has an entered start date which has + // already expired, so we must adjust the next repetition. + event.setNextOccurrence(now); + } + mAlarmDateTime = event.startDateTime(); + if (mDeferDateTime.isValid() && mDeferDateTime < mAlarmDateTime) + { + bool deferral = true; + bool deferReminder = false; + int reminder = mReminder->minutes(); + if (reminder) + { + DateTime remindTime = mAlarmDateTime.addMins(-reminder); + if (mDeferDateTime >= remindTime) + { + if (remindTime > QDateTime::currentDateTime()) + deferral = false; // ignore deferral if it's after next reminder + else if (mDeferDateTime > remindTime) + deferReminder = true; // it's the reminder which is being deferred + } + } + if (deferral) + event.defer(mDeferDateTime, deferReminder, false); + } + } + if (mTemplate) + { + int afterTime = mTemplateDefaultTime->isOn() ? 0 + : mTemplateUseTimeAfter->isOn() ? mTemplateTimeAfter->value() : -1; + event.setTemplate(mTemplateName->text(), afterTime); + } + } +} + +/****************************************************************************** + * Get the currently specified alarm flag bits. + */ +int EditAlarmDlg::getAlarmFlags() const +{ + bool displayAlarm = mMessageRadio->isOn() || mFileRadio->isOn(); + bool cmdAlarm = mCommandRadio->isOn(); + bool emailAlarm = mEmailRadio->isOn(); + return (displayAlarm && mSoundPicker->sound() == SoundPicker::BEEP ? KAEvent::BEEP : 0) + | (displayAlarm && mSoundPicker->sound() == SoundPicker::SPEAK ? KAEvent::SPEAK : 0) + | (displayAlarm && mSoundPicker->repeat() ? KAEvent::REPEAT_SOUND : 0) + | (displayAlarm && mConfirmAck->isChecked() ? KAEvent::CONFIRM_ACK : 0) + | (displayAlarm && mLateCancel->isAutoClose() ? KAEvent::AUTO_CLOSE : 0) + | (cmdAlarm && mCmdTypeScript->isChecked() ? KAEvent::SCRIPT : 0) + | (cmdAlarm && mCmdOutputGroup->selectedId() == EXEC_IN_TERMINAL ? KAEvent::EXEC_IN_XTERM : 0) + | (emailAlarm && mEmailBcc->isChecked() ? KAEvent::EMAIL_BCC : 0) + | (mShowInKorganizer && mShowInKorganizer->isChecked() ? KAEvent::COPY_KORGANIZER : 0) + | (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN ? KAEvent::REPEAT_AT_LOGIN : 0) + | ((mTemplate ? mTemplateAnyTime->isOn() : mAlarmDateTime.isDateOnly()) ? KAEvent::ANY_TIME : 0) + | (mFontColourButton->defaultFont() ? KAEvent::DEFAULT_FONT : 0); +} + +/****************************************************************************** + * Get the currently selected alarm type. + */ +KAEvent::Action EditAlarmDlg::getAlarmType() const +{ + return mFileRadio->isOn() ? KAEvent::FILE + : mCommandRadio->isOn() ? KAEvent::COMMAND + : mEmailRadio->isOn() ? KAEvent::EMAIL + : KAEvent::MESSAGE; +} + +/****************************************************************************** +* Called when the dialog is displayed. +* The first time through, sets the size to the same as the last time it was +* displayed. +*/ +void EditAlarmDlg::showEvent(QShowEvent* se) +{ + if (!mDeferGroupHeight) + { + mDeferGroupHeight = mDeferGroup->height() + spacingHint(); + QSize s; + if (KAlarm::readConfigWindowSize(EDIT_DIALOG_NAME, s)) + s.setHeight(s.height() + (mDeferGroup->isHidden() ? 0 : mDeferGroupHeight)); + else + s = minimumSize(); + resize(s); + } + KWin::setOnDesktop(winId(), mDesktop); // ensure it displays on the desktop expected by the user + KDialog::showEvent(se); +} + +/****************************************************************************** +* Called when the dialog's size has changed. +* Records the new size (adjusted to ignore the optional height of the deferred +* time edit widget) in the config file. +*/ +void EditAlarmDlg::resizeEvent(QResizeEvent* re) +{ + if (isVisible()) + { + QSize s = re->size(); + s.setHeight(s.height() - (mDeferGroup->isHidden() ? 0 : mDeferGroupHeight)); + KAlarm::writeConfigWindowSize(EDIT_DIALOG_NAME, s); + } + KDialog::resizeEvent(re); +} + +/****************************************************************************** +* Called when the OK button is clicked. +* Validate the input data. +*/ +void EditAlarmDlg::slotOk() +{ + if (!stateChanged()) + { + // No changes have been made except possibly to an existing deferral + if (!mOnlyDeferred) + reject(); + else + accept(); + return; + } + RecurrenceEdit::RepeatType recurType = mRecurrenceEdit->repeatType(); + if (mTimeWidget + && mTabs->currentPageIndex() == mRecurPageIndex && recurType == RecurrenceEdit::AT_LOGIN) + mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime()); + bool timedRecurrence = mRecurrenceEdit->isTimedRepeatType(); // does it recur other than at login? + if (mTemplate) + { + // Check that the template name is not blank and is unique + QString errmsg; + QString name = mTemplateName->text(); + if (name.isEmpty()) + errmsg = i18n("You must enter a name for the alarm template"); + else if (name != mSavedTemplateName) + { + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (cal && KAEvent::findTemplateName(*cal, name).valid()) + errmsg = i18n("Template name is already in use"); + } + if (!errmsg.isEmpty()) + { + mTemplateName->setFocus(); + KMessageBox::sorry(this, errmsg); + return; + } + } + else + { + QWidget* errWidget; + mAlarmDateTime = mTimeWidget->getDateTime(0, !timedRecurrence, false, &errWidget); + if (errWidget) + { + // It's more than just an existing deferral being changed, so the time matters + mTabs->setCurrentPage(mMainPageIndex); + errWidget->setFocus(); + mTimeWidget->getDateTime(); // display the error message now + return; + } + } + if (!checkCommandData() + || !checkEmailData()) + return; + if (!mTemplate) + { + if (timedRecurrence) + { + QDateTime now = QDateTime::currentDateTime(); + if (mAlarmDateTime.date() < now.date() + || mAlarmDateTime.date() == now.date() + && !mAlarmDateTime.isDateOnly() && mAlarmDateTime.time() < now.time()) + { + // A timed recurrence has an entered start date which + // has already expired, so we must adjust it. + KAEvent event; + getEvent(event); // this may adjust mAlarmDateTime + if (( mAlarmDateTime.date() < now.date() + || mAlarmDateTime.date() == now.date() + && !mAlarmDateTime.isDateOnly() && mAlarmDateTime.time() < now.time()) + && event.nextOccurrence(now, mAlarmDateTime, KAEvent::ALLOW_FOR_REPETITION) == KAEvent::NO_OCCURRENCE) + { + KMessageBox::sorry(this, i18n("Recurrence has already expired")); + return; + } + } + } + QString errmsg; + QWidget* errWidget = mRecurrenceEdit->checkData(mAlarmDateTime.dateTime(), errmsg); + if (errWidget) + { + mTabs->setCurrentPage(mRecurPageIndex); + errWidget->setFocus(); + KMessageBox::sorry(this, errmsg); + return; + } + } + if (recurType != RecurrenceEdit::NO_RECUR) + { + KAEvent recurEvent; + int longestRecurInterval = -1; + int reminder = mReminder->minutes(); + if (reminder && !mReminder->isOnceOnly()) + { + mRecurrenceEdit->updateEvent(recurEvent, false); + longestRecurInterval = recurEvent.longestRecurrenceInterval(); + if (longestRecurInterval && reminder >= longestRecurInterval) + { + mTabs->setCurrentPage(mMainPageIndex); + mReminder->setFocusOnCount(); + KMessageBox::sorry(this, i18n("Reminder period must be less than the recurrence interval, unless '%1' is checked." + ).arg(Reminder::i18n_first_recurrence_only())); + return; + } + } + if (mRecurrenceEdit->subRepeatCount()) + { + if (longestRecurInterval < 0) + { + mRecurrenceEdit->updateEvent(recurEvent, false); + longestRecurInterval = recurEvent.longestRecurrenceInterval(); + } + if (longestRecurInterval > 0 + && recurEvent.repeatInterval() * recurEvent.repeatCount() >= longestRecurInterval - reminder) + { + KMessageBox::sorry(this, i18n("The duration of a repetition within the recurrence must be less than the recurrence interval minus any reminder period")); + mRecurrenceEdit->activateSubRepetition(); // display the alarm repetition dialog again + return; + } + if (recurEvent.repeatInterval() % 1440 + && (mTemplate && mTemplateAnyTime->isOn() || !mTemplate && mAlarmDateTime.isDateOnly())) + { + KMessageBox::sorry(this, i18n("For a repetition within the recurrence, its period must be in units of days or weeks for a date-only alarm")); + mRecurrenceEdit->activateSubRepetition(); // display the alarm repetition dialog again + return; + } + } + } + if (checkText(mAlarmMessage)) + accept(); +} + +/****************************************************************************** +* Called when the Try button is clicked. +* Display/execute the alarm immediately for the user to check its configuration. +*/ +void EditAlarmDlg::slotTry() +{ + QString text; + if (checkText(text)) + { + if (mEmailRadio->isOn()) + { + if (!checkEmailData() + || KMessageBox::warningContinueCancel(this, i18n("Do you really want to send the email now to the specified recipient(s)?"), + i18n("Confirm Email"), i18n("&Send")) != KMessageBox::Continue) + return; + } + KAEvent event; + setEvent(event, text, true); + void* proc = theApp()->execAlarm(event, event.firstAlarm(), false, false); + if (proc) + { + if (mCommandRadio->isOn() && mCmdOutputGroup->selectedId() != EXEC_IN_TERMINAL) + { + theApp()->commandMessage((ShellProcess*)proc, this); + KMessageBox::information(this, i18n("Command executed:\n%1").arg(text)); + theApp()->commandMessage((ShellProcess*)proc, 0); + } + else if (mEmailRadio->isOn()) + { + QString bcc; + if (mEmailBcc->isChecked()) + bcc = i18n("\nBcc: %1").arg(Preferences::emailBccAddress()); + KMessageBox::information(this, i18n("Email sent to:\n%1%2").arg(mEmailAddresses.join("\n")).arg(bcc)); + } + } + } +} + +/****************************************************************************** +* Called when the Cancel button is clicked. +*/ +void EditAlarmDlg::slotCancel() +{ + reject(); +} + +/****************************************************************************** +* Called when the Load Template button is clicked. +* Prompt to select a template and initialise the dialogue with its contents. +*/ +void EditAlarmDlg::slotDefault() +{ + TemplatePickDlg dlg(this, "templPickDlg"); + if (dlg.exec() == QDialog::Accepted) + initialise(dlg.selectedTemplate()); +} + +/****************************************************************************** + * Called when the Change deferral button is clicked. + */ +void EditAlarmDlg::slotEditDeferral() +{ + if (!mTimeWidget) + return; + bool limit = true; + int repeatInterval; + int repeatCount = mRecurrenceEdit->subRepeatCount(&repeatInterval); + DateTime start = mSavedEvent->recurs() ? (mExpiredRecurrence ? DateTime() : mSavedEvent->mainDateTime()) + : mTimeWidget->getDateTime(0, !repeatCount, !mExpiredRecurrence); + if (!start.isValid()) + { + if (!mExpiredRecurrence) + return; + limit = false; + } + QDateTime now = QDateTime::currentDateTime(); + if (limit) + { + if (repeatCount && start < now) + { + // Sub-repetition - find the time of the next one + repeatInterval *= 60; + int repetition = (start.secsTo(now) + repeatInterval - 1) / repeatInterval; + if (repetition > repeatCount) + { + mTimeWidget->getDateTime(); // output the appropriate error message + return; + } + start = start.addSecs(repetition * repeatInterval); + } + } + + bool deferred = mDeferDateTime.isValid(); + DeferAlarmDlg deferDlg(i18n("Defer Alarm"), (deferred ? mDeferDateTime : DateTime(now.addSecs(60))), + deferred, this, "EditDeferDlg"); + if (limit) + { + // Don't allow deferral past the next recurrence + int reminder = mReminder->minutes(); + if (reminder) + { + DateTime remindTime = start.addMins(-reminder); + if (QDateTime::currentDateTime() < remindTime) + start = remindTime; + } + deferDlg.setLimit(start.addSecs(-60)); + } + if (deferDlg.exec() == QDialog::Accepted) + { + mDeferDateTime = deferDlg.getDateTime(); + mDeferTimeLabel->setText(mDeferDateTime.isValid() ? mDeferDateTime.formatLocale() : QString::null); + } +} + +/****************************************************************************** +* Called when the main page is shown. +* Sets the focus widget to the first edit field. +*/ +void EditAlarmDlg::slotShowMainPage() +{ + slotAlarmTypeChanged(-1); + if (!mMainPageShown) + { + if (mTemplateName) + mTemplateName->setFocus(); + mMainPageShown = true; + } + if (mTimeWidget) + { + if (!mReadOnly && mRecurPageShown && mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN) + mTimeWidget->setDateTime(mRecurrenceEdit->endDateTime()); + if (mReadOnly || mRecurrenceEdit->isTimedRepeatType()) + mTimeWidget->setMinDateTime(); // don't set a minimum date/time + else + mTimeWidget->setMinDateTimeIsCurrent(); // set the minimum date/time to track the clock + } +} + +/****************************************************************************** +* Called when the recurrence edit page is shown. +* The recurrence defaults are set to correspond to the start date. +* The first time, for a new alarm, the recurrence end date is set according to +* the alarm start time. +*/ +void EditAlarmDlg::slotShowRecurrenceEdit() +{ + mRecurPageIndex = mTabs->currentPageIndex(); + if (!mReadOnly && !mTemplate) + { + QDateTime now = QDateTime::currentDateTime(); + mAlarmDateTime = mTimeWidget->getDateTime(0, false, false); + bool expired = (mAlarmDateTime.dateTime() < now); + if (mRecurSetDefaultEndDate) + { + mRecurrenceEdit->setDefaultEndDate(expired ? now.date() : mAlarmDateTime.date()); + mRecurSetDefaultEndDate = false; + } + mRecurrenceEdit->setStartDate(mAlarmDateTime.date(), now.date()); + if (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN) + mRecurrenceEdit->setEndDateTime(expired ? now : mAlarmDateTime); + } + mRecurPageShown = true; +} + +/****************************************************************************** +* Called when the recurrence type selection changes. +* Enables/disables date-only alarms as appropriate. +* Enables/disables controls depending on at-login setting. +*/ +void EditAlarmDlg::slotRecurTypeChange(int repeatType) +{ + bool atLogin = (mRecurrenceEdit->repeatType() == RecurrenceEdit::AT_LOGIN); + if (!mTemplate) + { + bool recurs = (mRecurrenceEdit->repeatType() != RecurrenceEdit::NO_RECUR); + if (mDeferGroup) + mDeferGroup->setEnabled(recurs); + mTimeWidget->enableAnyTime(!recurs || repeatType != RecurrenceEdit::SUBDAILY); + if (atLogin) + { + mAlarmDateTime = mTimeWidget->getDateTime(0, false, false); + mRecurrenceEdit->setEndDateTime(mAlarmDateTime.dateTime()); + } + mReminder->enableOnceOnly(recurs && !atLogin); + } + mReminder->setEnabled(!atLogin); + mLateCancel->setEnabled(!atLogin); + if (mShowInKorganizer) + mShowInKorganizer->setEnabled(!atLogin); + slotRecurFrequencyChange(); +} + +/****************************************************************************** +* Called when the recurrence frequency selection changes, or the sub- +* repetition interval changes. +* Updates the recurrence frequency text. +*/ +void EditAlarmDlg::slotRecurFrequencyChange() +{ + slotSetSubRepetition(); + KAEvent event; + mRecurrenceEdit->updateEvent(event, false); + mTabs->setTabLabel(mTabs->page(mRecurPageIndex), recurText(event)); +} + +/****************************************************************************** +* Called when the Repetition within Recurrence button has been pressed to +* display the sub-repetition dialog. +* Alarm repetition has the following restrictions: +* 1) Not allowed for a repeat-at-login alarm +* 2) For a date-only alarm, the repeat interval must be a whole number of days. +* 3) The overall repeat duration must be less than the recurrence interval. +*/ +void EditAlarmDlg::slotSetSubRepetition() +{ + bool dateOnly = mTemplate ? mTemplateAnyTime->isOn() : mTimeWidget->anyTime(); + mRecurrenceEdit->setSubRepetition(mReminder->minutes(), dateOnly); +} + +/****************************************************************************** +* Validate and convert command alarm data. +*/ +bool EditAlarmDlg::checkCommandData() +{ + if (mCommandRadio->isOn() && mCmdOutputGroup->selectedId() == LOG_TO_FILE) + { + // Validate the log file name + QString file = mCmdLogFileEdit->text(); + QFileInfo info(file); + QDir::setCurrent(QDir::homeDirPath()); + bool err = file.isEmpty() || info.isDir(); + if (!err) + { + if (info.exists()) + { + err = !info.isWritable(); + } + else + { + QFileInfo dirinfo(info.dirPath(true)); // get absolute directory path + err = (!dirinfo.isDir() || !dirinfo.isWritable()); + } + } + if (err) + { + mTabs->setCurrentPage(mMainPageIndex); + mCmdLogFileEdit->setFocus(); + KMessageBox::sorry(this, i18n("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.absFilePath()); + } + return true; +} + +/****************************************************************************** +* Convert the email addresses to a list, and validate them. Convert the email +* attachments to a list. +*/ +bool EditAlarmDlg::checkEmailData() +{ + if (mEmailRadio->isOn()) + { + QString addrs = mEmailToEdit->text(); + if (addrs.isEmpty()) + mEmailAddresses.clear(); + else + { + QString bad = KAMail::convertAddresses(addrs, mEmailAddresses); + if (!bad.isEmpty()) + { + mEmailToEdit->setFocus(); + KMessageBox::error(this, i18n("Invalid email address:\n%1").arg(bad)); + return false; + } + } + if (mEmailAddresses.isEmpty()) + { + mEmailToEdit->setFocus(); + KMessageBox::error(this, i18n("No email address specified")); + return false; + } + + mEmailAttachments.clear(); + for (int i = 0; i < mEmailAttachList->count(); ++i) + { + QString att = mEmailAttachList->text(i); + switch (KAMail::checkAttachment(att)) + { + case 1: + mEmailAttachments.append(att); + break; + case 0: + break; // empty + case -1: + mEmailAttachList->setFocus(); + KMessageBox::error(this, i18n("Invalid email attachment:\n%1").arg(att)); + return false; + } + } + } + return true; +} + +/****************************************************************************** +* Called when one of the alarm action type radio buttons is clicked, +* to display the appropriate set of controls for that action type. +*/ +void EditAlarmDlg::slotAlarmTypeChanged(int) +{ + bool displayAlarm = false; + QWidget* focus = 0; + if (mMessageRadio->isOn()) + { + mFileBox->hide(); + mFilePadding->hide(); + mTextMessageEdit->show(); + mFontColourButton->show(); + mBgColourBox->hide(); + mSoundPicker->showSpeak(true); + mDisplayAlarmsFrame->show(); + mCommandFrame->hide(); + mEmailFrame->hide(); + mReminder->show(); + mConfirmAck->show(); + setButtonWhatsThis(Try, i18n("Display the alarm message now")); + focus = mTextMessageEdit; + displayAlarm = true; + } + else if (mFileRadio->isOn()) + { + mTextMessageEdit->hide(); + mFileBox->show(); + mFilePadding->show(); + mFontColourButton->hide(); + mBgColourBox->show(); + mSoundPicker->showSpeak(false); + mDisplayAlarmsFrame->show(); + mCommandFrame->hide(); + mEmailFrame->hide(); + mReminder->show(); + mConfirmAck->show(); + setButtonWhatsThis(Try, i18n("Display the file now")); + mFileMessageEdit->setNoSelect(); + focus = mFileMessageEdit; + displayAlarm = true; + } + else if (mCommandRadio->isOn()) + { + mDisplayAlarmsFrame->hide(); + mCommandFrame->show(); + mEmailFrame->hide(); + mReminder->hide(); + mConfirmAck->hide(); + setButtonWhatsThis(Try, i18n("Execute the specified command now")); + mCmdCommandEdit->setNoSelect(); + focus = mCmdCommandEdit; + } + else if (mEmailRadio->isOn()) + { + mDisplayAlarmsFrame->hide(); + mCommandFrame->hide(); + mEmailFrame->show(); + mReminder->hide(); + mConfirmAck->hide(); + setButtonWhatsThis(Try, i18n("Send the email to the specified addressees now")); + mEmailToEdit->setNoSelect(); + focus = mEmailToEdit; + } + mLateCancel->showAutoClose(displayAlarm); + mLateCancel->setFixedSize(mLateCancel->sizeHint()); + if (focus) + focus->setFocus(); +} + +/****************************************************************************** +* Called when one of the command type radio buttons is clicked, +* to display the appropriate edit field. +*/ +void EditAlarmDlg::slotCmdScriptToggled(bool on) +{ + if (on) + { + mCmdCommandEdit->hide(); + mCmdPadding->hide(); + mCmdScriptEdit->show(); + mCmdScriptEdit->setFocus(); + } + else + { + mCmdScriptEdit->hide(); + mCmdCommandEdit->show(); + mCmdPadding->show(); + mCmdCommandEdit->setFocus(); + } +} + +/****************************************************************************** +* Called when one of the template time radio buttons is clicked, +* to enable or disable the template time entry spin boxes. +*/ +void EditAlarmDlg::slotTemplateTimeType(int) +{ + mTemplateTime->setEnabled(mTemplateUseTime->isOn()); + mTemplateTimeAfter->setEnabled(mTemplateUseTimeAfter->isOn()); +} + +/****************************************************************************** +* Called when the "Any time" checkbox is toggled in the date/time widget. +* Sets the advance reminder and late cancel units to days if any time is checked. +*/ +void EditAlarmDlg::slotAnyTimeToggled(bool anyTime) +{ + if (mReminder->isReminder()) + mReminder->setDateOnly(anyTime); + mLateCancel->setDateOnly(anyTime); +} + +/****************************************************************************** + * Get a selection from the Address Book. + */ +void EditAlarmDlg::openAddressBook() +{ + KABC::Addressee a = KABC::AddresseeDialog::getAddressee(this); + if (a.isEmpty()) + return; + Person person(a.realName(), a.preferredEmail()); + QString addrs = mEmailToEdit->text().stripWhiteSpace(); + if (!addrs.isEmpty()) + addrs += ", "; + addrs += person.fullName(); + mEmailToEdit->setText(addrs); +} + +/****************************************************************************** + * Select a file to attach to the email. + */ +void EditAlarmDlg::slotAddAttachment() +{ + QString url = KAlarm::browseFile(i18n("Choose File to Attach"), mAttachDefaultDir, QString::null, + QString::null, KFile::ExistingOnly, this, "pickAttachFile"); + if (!url.isEmpty()) + { + mEmailAttachList->insertItem(url); + mEmailAttachList->setCurrentItem(mEmailAttachList->count() - 1); // select the new item + mEmailRemoveButton->setEnabled(true); + mEmailAttachList->setEnabled(true); + } +} + +/****************************************************************************** + * Remove the currently selected attachment from the email. + */ +void EditAlarmDlg::slotRemoveAttachment() +{ + int item = mEmailAttachList->currentItem(); + mEmailAttachList->removeItem(item); + int count = mEmailAttachList->count(); + if (item >= count) + mEmailAttachList->setCurrentItem(count - 1); + if (!count) + { + mEmailRemoveButton->setEnabled(false); + mEmailAttachList->setEnabled(false); + } +} + +/****************************************************************************** +* Clean up the alarm text, and if it's a file, check whether it's valid. +*/ +bool EditAlarmDlg::checkText(QString& result, bool showErrorMessage) const +{ + if (mMessageRadio->isOn()) + result = mTextMessageEdit->text(); + else if (mEmailRadio->isOn()) + result = mEmailMessageEdit->text(); + else if (mCommandRadio->isOn()) + { + if (mCmdTypeScript->isChecked()) + result = mCmdScriptEdit->text(); + else + result = mCmdCommandEdit->text(); + result = result.stripWhiteSpace(); + } + else if (mFileRadio->isOn()) + { + QString alarmtext = mFileMessageEdit->text().stripWhiteSpace(); + // Convert any relative file path to absolute + // (using home directory as the default) + enum Err { NONE = 0, BLANK, NONEXISTENT, DIRECTORY, UNREADABLE, NOT_TEXT_IMAGE }; + Err err = NONE; + KURL url; + int i = alarmtext.find(QString::fromLatin1("/")); + if (i > 0 && alarmtext[i - 1] == ':') + { + url = alarmtext; + url.cleanPath(); + alarmtext = url.prettyURL(); + KIO::UDSEntry uds; + if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) + err = NONEXISTENT; + else + { + KFileItem fi(uds, url); + if (fi.isDir()) err = DIRECTORY; + else if (!fi.isReadable()) err = UNREADABLE; + } + } + else if (alarmtext.isEmpty()) + err = BLANK; // blank file name + else + { + // It's a local file - convert to absolute path & check validity + QFileInfo info(alarmtext); + QDir::setCurrent(QDir::homeDirPath()); + alarmtext = info.absFilePath(); + url.setPath(alarmtext); + alarmtext = QString::fromLatin1("file:") + alarmtext; + if (!err) + { + if (info.isDir()) err = DIRECTORY; + else if (!info.exists()) err = NONEXISTENT; + else if (!info.isReadable()) err = UNREADABLE; + } + } + if (!err) + { + switch (KAlarm::fileType(KFileItem(KFileItem::Unknown, KFileItem::Unknown, url).mimetype())) + { + case KAlarm::TextFormatted: + case KAlarm::TextPlain: + case KAlarm::TextApplication: + case KAlarm::Image: + break; + default: + err = NOT_TEXT_IMAGE; + break; + } + } + if (err && showErrorMessage) + { + mFileMessageEdit->setFocus(); + QString errmsg; + switch (err) + { + case BLANK: + KMessageBox::sorry(const_cast(this), i18n("Please select a file to display")); + return false; + case NONEXISTENT: errmsg = i18n("%1\nnot found"); break; + case DIRECTORY: errmsg = i18n("%1\nis a folder"); break; + case UNREADABLE: errmsg = i18n("%1\nis not readable"); break; + case NOT_TEXT_IMAGE: errmsg = i18n("%1\nappears not to be a text or image file"); break; + case NONE: + default: + break; + } + if (KMessageBox::warningContinueCancel(const_cast(this), errmsg.arg(alarmtext)) + == KMessageBox::Cancel) + return false; + } + result = alarmtext; + } + return true; +} + + +/*============================================================================= += Class TextEdit += A text edit field with a minimum height of 3 text lines. += Provides KDE 2 compatibility. +=============================================================================*/ +TextEdit::TextEdit(QWidget* parent, const char* name) + : KTextEdit(parent, name) +{ + QSize tsize = sizeHint(); + tsize.setHeight(fontMetrics().lineSpacing()*13/4 + 2*frameWidth()); + setMinimumSize(tsize); +} + +void TextEdit::dragEnterEvent(QDragEnterEvent* e) +{ + if (KCal::ICalDrag::canDecode(e)) + e->accept(false); // don't accept "text/calendar" objects + KTextEdit::dragEnterEvent(e); +} diff --git a/kalarm/editdlg.h b/kalarm/editdlg.h new file mode 100644 index 000000000..8a8f40ce9 --- /dev/null +++ b/kalarm/editdlg.h @@ -0,0 +1,260 @@ +/* + * editdlg.h - dialogue to create or modify an alarm or alarm template + * Program: kalarm + * Copyright © 2001-2006,2008 by 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. + */ + +#ifndef EDITDLG_H +#define EDITDLG_H + +#include +#include + +#include + +#include "alarmevent.h" +#include "alarmtext.h" +#include "datetime.h" +#include "soundpicker.h" + +class QButton; +class QGroupBox; +class QComboBox; +class QTabWidget; +class QVBox; +class QHBox; +class EmailIdCombo; +class FontColourButton; +class ColourCombo; +class ButtonGroup; +class TimeEdit; +class RadioButton; +class CheckBox; +class LateCancelSelector; +class AlarmTimeWidget; +class RecurrenceEdit; +class Reminder; +class SpecialActionsButton; +class TimeSpinBox; +class LineEdit; +class TextEdit; +class PickAlarmFileRadio; + + +class EditAlarmDlg : public KDialogBase +{ + Q_OBJECT + public: + enum MessageType { MESSAGE, FILE }; + enum CmdLogType { DISCARD_OUTPUT, LOG_TO_FILE, EXEC_IN_TERMINAL }; + + EditAlarmDlg(bool Template, const QString& caption, QWidget* parent = 0, const char* name = 0, + const KAEvent* = 0, bool readOnly = false); + virtual ~EditAlarmDlg(); + bool getEvent(KAEvent&); + void setAction(KAEvent::Action, const AlarmText& = AlarmText()); + + static CheckBox* createConfirmAckCheckbox(QWidget* parent, const char* name = 0); + + static QString i18n_ConfirmAck(); // plain text of 'Confirm acknowledgement' checkbox + static QString i18n_k_ConfirmAck(); // text of 'Confirm acknowledgement' checkbox, with 'k' shortcut + static QString i18n_SpecialActions(); // plain text of 'Special Actions...' button + static QString i18n_ShowInKOrganizer(); // plain text of 'Show in KOrganizer' checkbox + static QString i18n_g_ShowInKOrganizer(); // text of 'Show in KOrganizer' checkbox, with 'G' shortcut + static QString i18n_EnterScript(); // plain text of 'Enter a script' checkbox + static QString i18n_p_EnterScript(); // text of 'Enter a script' checkbox, with 'P' shortcut + static QString i18n_ExecInTermWindow(); // plain text of 'Execute in terminal window' checkbox + static QString i18n_w_ExecInTermWindow(); // text of 'Execute in terminal window' radio button, with 'W' shortcut + static QString i18n_u_ExecInTermWindow(); // text of 'Execute in terminal window' radio button, with 'U' shortcut + static QString i18n_g_LogToFile(); // text of 'Log to file' radio button, with 'G' shortcut + static QString i18n_CopyEmailToSelf(); // plain text of 'Copy email to self' checkbox + static QString i18n_e_CopyEmailToSelf(); // text of 'Copy email to self' checkbox, with 'E' shortcut + static QString i18n_s_CopyEmailToSelf(); // text of 'Copy email to self' checkbox, with 'S' shortcut + static QString i18n_EmailFrom(); // plain text of 'From:' (email address) + static QString i18n_f_EmailFrom(); // text of 'From:' (email address), with 'F' shortcut + static QString i18n_EmailTo(); // plain text of 'To:' (email addressee) + static QString i18n_EmailSubject(); // plain text of 'Subject:' (email) + static QString i18n_j_EmailSubject(); // text of 'Subject:' (email), with 'J' shortcut + + protected: + virtual void resizeEvent(QResizeEvent*); + virtual void showEvent(QShowEvent*); + protected slots: + virtual void slotOk(); + virtual void slotCancel(); + virtual void slotTry(); + virtual void slotDefault(); // Load Template + private slots: + void slotRecurTypeChange(int repeatType); + void slotRecurFrequencyChange(); + void slotAlarmTypeChanged(int id); + void slotEditDeferral(); + void openAddressBook(); + void slotAddAttachment(); + void slotRemoveAttachment(); + void slotShowMainPage(); + void slotShowRecurrenceEdit(); + void slotAnyTimeToggled(bool anyTime); + void slotTemplateTimeType(int id); + void slotSetSubRepetition(); + void slotCmdScriptToggled(bool); + + private: + void initialise(const KAEvent*); + void setReadOnly(); + void setEvent(KAEvent&, const QString& text, bool trial); + KAEvent::Action getAlarmType() const; + int getAlarmFlags() const; + bool checkText(QString& result, bool showErrorMessage = true) const; + void setSoundPicker(); + void setRecurTabTitle(const KAEvent* = 0); + bool checkCommandData(); + bool checkEmailData(); + + void initDisplayAlarms(QWidget* parent); + void initCommand(QWidget* parent); + void initEmail(QWidget* parent); + void saveState(const KAEvent*); + bool stateChanged() const; + + QTabWidget* mTabs; // the tabs in the dialog + int mMainPageIndex; + int mRecurPageIndex; + bool mMainPageShown; // true once the main tab has been displayed + bool mRecurPageShown; // true once the recurrence tab has been displayed + bool mRecurSetDefaultEndDate; // adjust default end date/time when recurrence tab is displayed + + ButtonGroup* mActionGroup; + RadioButton* mMessageRadio; + RadioButton* mCommandRadio; + PickAlarmFileRadio* mFileRadio; + RadioButton* mEmailRadio; + QWidgetStack* mAlarmTypeStack; + + // Templates + QLineEdit* mTemplateName; + ButtonGroup* mTemplateTimeGroup; + RadioButton* mTemplateDefaultTime; // no alarm time is specified + RadioButton* mTemplateUseTimeAfter;// alarm time is specified as an offset from current + RadioButton* mTemplateAnyTime; // alarms have date only, no time + RadioButton* mTemplateUseTime; // an alarm time is specified + TimeSpinBox* mTemplateTimeAfter; // the specified offset from the current time + TimeEdit* mTemplateTime; // the alarm time which is specified + + // Display alarm options widgets + QFrame* mDisplayAlarmsFrame; + QHBox* mFileBox; + QHBox* mFilePadding; + SoundPicker* mSoundPicker; + CheckBox* mConfirmAck; + FontColourButton* mFontColourButton; // for text display option + ColourCombo* mBgColourButton; // for file display option + QHBox* mBgColourBox; + SpecialActionsButton* mSpecialActionsButton; + Reminder* mReminder; + bool mReminderDeferral; + bool mReminderArchived; + // Text message alarm widgets + TextEdit* mTextMessageEdit; // text message edit box + // Text file alarm widgets + LineEdit* mFileMessageEdit; // text file URL edit box + QPushButton* mFileBrowseButton; // text file browse button + QString mFileDefaultDir; // default directory for browse button + // Command alarm widgets + QFrame* mCommandFrame; + CheckBox* mCmdTypeScript; // entering a script + LineEdit* mCmdCommandEdit; // command line edit box + TextEdit* mCmdScriptEdit; // script edit box + ButtonGroup* mCmdOutputGroup; // what to do with command output + LineEdit* mCmdLogFileEdit; // log file URL edit box + QWidget* mCmdPadding; + // Email alarm widgets + QFrame* mEmailFrame; + EmailIdCombo* mEmailFromList; + LineEdit* mEmailToEdit; + QPushButton* mEmailAddressButton; // email open address book button + QLineEdit* mEmailSubjectEdit; + TextEdit* mEmailMessageEdit; // email body edit box + QComboBox* mEmailAttachList; + QPushButton* mEmailAddAttachButton; + QPushButton* mEmailRemoveButton; + CheckBox* mEmailBcc; + QString mAttachDefaultDir; + + QGroupBox* mDeferGroup; + QLabel* mDeferTimeLabel; + QPushButton* mDeferChangeButton; + + AlarmTimeWidget* mTimeWidget; + LateCancelSelector* mLateCancel; + CheckBox* mShowInKorganizer; + + RecurrenceEdit* mRecurrenceEdit; + + QString mAlarmMessage; // message text/file name/command/email message + DateTime mAlarmDateTime; + DateTime mDeferDateTime; + EmailAddressList mEmailAddresses; // list of addresses to send email to + QStringList mEmailAttachments; // list of email attachment file names + unsigned long mKMailSerialNumber; // if email text, message's KMail serial number, else 0 + int mDeferGroupHeight; // height added by deferred time widget + int mDesktop; // desktop to display the dialog in + bool mTemplate; // editing an alarm template + bool mExpiredRecurrence; // initially a recurrence which has expired + mutable bool mChanged; // controls other than deferral have changed since dialog was displayed + mutable bool mOnlyDeferred; // the only change made in the dialog was to the existing deferral + bool mDesiredReadOnly; // the specified read-only status of the dialogue + bool mReadOnly; // the actual read-only status of the dialogue + + // Initial state of all controls + KAEvent* mSavedEvent; + QString mSavedTemplateName; // mTemplateName value + QButton* mSavedTemplateTimeType; // selected button in mTemplateTimeGroup + QTime mSavedTemplateTime; // mTemplateTime value + int mSavedTemplateAfterTime; // mTemplateAfterTime value + QButton* mSavedTypeRadio; // mMessageRadio, etc + SoundPicker::Type mSavedSoundType; // mSoundPicker sound type + bool mSavedRepeatSound; // mSoundPicker repeat status + QString mSavedSoundFile; // mSoundPicker sound file + float mSavedSoundVolume; // mSoundPicker volume + float mSavedSoundFadeVolume;// mSoundPicker fade volume + int mSavedSoundFadeSeconds;// mSoundPicker fade time + bool mSavedConfirmAck; // mConfirmAck status + QFont mSavedFont; // mFontColourButton font + QColor mSavedBgColour; // mFontColourButton background colour + QColor mSavedFgColour; // mFontColourButton foreground colour + QString mSavedPreAction; // mSpecialActionsButton pre-alarm action + QString mSavedPostAction; // mSpecialActionsButton post-alarm action + int mSavedReminder; // mReminder value + bool mSavedOnceOnly; // mReminder once-only status + QString mSavedTextFileCommandMessage; // mTextMessageEdit/mFileMessageEdit/mCmdCommandEdit/mEmailMessageEdit value + QString mSavedEmailFrom; // mEmailFromList current value + QString mSavedEmailTo; // mEmailToEdit value + QString mSavedEmailSubject; // mEmailSubjectEdit value + QStringList mSavedEmailAttach; // mEmailAttachList values + bool mSavedEmailBcc; // mEmailBcc status + bool mSavedCmdScript; // mCmdTypeScript status + QButton* mSavedCmdOutputRadio; // selected button in mCmdOutputGroup + QString mSavedCmdLogFile; // mCmdLogFileEdit value + DateTime mSavedDateTime; // mTimeWidget value + int mSavedRecurrenceType; // RecurrenceEdit::RepeatType value + int mSavedLateCancel; // mLateCancel value + bool mSavedAutoClose; // mLateCancel->isAutoClose() value + bool mSavedShowInKorganizer; // mShowInKorganizer status +}; + +#endif // EDITDLG_H diff --git a/kalarm/editdlgprivate.h b/kalarm/editdlgprivate.h new file mode 100644 index 000000000..cbcddb62f --- /dev/null +++ b/kalarm/editdlgprivate.h @@ -0,0 +1,47 @@ +/* + * editdlgprivate.h - private classes for editdlg.cpp + * Program: kalarm + * Copyright © 2003-2005,2008 by 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. + */ + +#ifndef EDITDLGPRIVATE_H +#define EDITDLGPRIVATE_H + +#include + + +class PageFrame : public QFrame +{ + Q_OBJECT + public: + PageFrame(QWidget* parent = 0, const char* name = 0) : QFrame(parent, name) { } + protected: + virtual void showEvent(QShowEvent*) { emit shown(); } + signals: + void shown(); +}; + +class TextEdit : public KTextEdit +{ + Q_OBJECT + public: + TextEdit(QWidget* parent, const char* name = 0); + protected: + virtual void dragEnterEvent(QDragEnterEvent*); +}; + +#endif // EDITDLGPRIVATE_H diff --git a/kalarm/emailidcombo.cpp b/kalarm/emailidcombo.cpp new file mode 100644 index 000000000..fc4fc6e74 --- /dev/null +++ b/kalarm/emailidcombo.cpp @@ -0,0 +1,62 @@ +/* + * emailidcombo.cpp - email identity combo box with read-only option + * Program: kalarm + * Copyright (C) 2004 by 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 "emailidcombo.moc" + + +EmailIdCombo::EmailIdCombo(KPIM::IdentityManager* manager, QWidget* parent, const char* name) + : KPIM::IdentityCombo(manager, parent, name), + mReadOnly(false) +{ } + +void EmailIdCombo::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == LeftButton) + return; + } + KPIM::IdentityCombo::mousePressEvent(e); +} + +void EmailIdCombo::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + KPIM::IdentityCombo::mouseReleaseEvent(e); +} + +void EmailIdCombo::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + KPIM::IdentityCombo::mouseMoveEvent(e); +} + +void EmailIdCombo::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + KPIM::IdentityCombo::keyPressEvent(e); +} + +void EmailIdCombo::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + KPIM::IdentityCombo::keyReleaseEvent(e); +} diff --git a/kalarm/emailidcombo.h b/kalarm/emailidcombo.h new file mode 100644 index 000000000..d0399ec08 --- /dev/null +++ b/kalarm/emailidcombo.h @@ -0,0 +1,44 @@ +/* + * emailidcombo.h - email identity combo box with read-only option + * Program: kalarm + * Copyright © 2004,2006 by 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. + */ + +#ifndef EMAILIDCOMBO_H +#define EMAILIDCOMBO_H + +#include +#include "combobox.h" + + +class EmailIdCombo : public KPIM::IdentityCombo +{ + Q_OBJECT + public: + explicit EmailIdCombo(KPIM::IdentityManager*, QWidget* parent = 0, const char* name = 0); + void setReadOnly(bool ro) { mReadOnly = ro; } + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + bool mReadOnly; // value cannot be changed +}; + +#endif // EMAILIDCOMBO_H diff --git a/kalarm/eventlistviewbase.cpp b/kalarm/eventlistviewbase.cpp new file mode 100644 index 000000000..1a25e2ac0 --- /dev/null +++ b/kalarm/eventlistviewbase.cpp @@ -0,0 +1,466 @@ +/* + * eventlistviewbase.cpp - base classes for widget showing list of events + * Program: kalarm + * Copyright (c) 2004-2006 by 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 "kalarm.h" + +#include +#include + +#include +#include + +#include "find.h" +#include "eventlistviewbase.moc" + + +class EventListWhatsThisBase : public QWhatsThis +{ + public: + EventListWhatsThisBase(EventListViewBase* lv) : QWhatsThis(lv), mListView(lv) { } + virtual QString text(const QPoint&); + private: + EventListViewBase* mListView; +}; + + +/*============================================================================= += Class: EventListViewBase += Base class for displaying a list of events. +=============================================================================*/ + +EventListViewBase::EventListViewBase(QWidget* parent, const char* name) + : KListView(parent, name), + mFind(0), + mLastColumn(-1), + mLastColumnHeaderWidth(0) +{ + setAllColumnsShowFocus(true); + setShowSortIndicator(true); + + new EventListWhatsThisBase(this); +} + +void EventListViewBase::addLastColumn(const QString& title) +{ + addColumn(title); + mLastColumn = columns() - 1; + mLastColumnHeaderWidth = columnWidth(mLastColumn); + setColumnWidthMode(mLastColumn, QListView::Maximum); +} + +/****************************************************************************** +* Refresh the list by clearing it and redisplaying all the current alarms. +*/ +void EventListViewBase::refresh() +{ + QString currentID; + if (currentItem()) + currentID = currentItem()->event().id(); // save current item for restoration afterwards + clear(); + populate(); + resizeLastColumn(); + EventListViewItemBase* current = getEntry(currentID); + if (current) + { + setCurrentItem(current); + ensureItemVisible(current); + } +} + +/****************************************************************************** +* Get the item for a given event ID. +*/ +EventListViewItemBase* EventListViewBase::getEntry(const QString& eventID) const +{ + if (!eventID.isEmpty()) + { + for (EventListViewItemBase* item = firstChild(); item; item = item->nextSibling()) + if (item->event().id() == eventID) + return item; + } + return 0; +} + +/****************************************************************************** +* Add an event to every list instance. +* If 'selectionView' is non-null, the selection highlight is moved to the new +* event in that listView instance. +*/ +void EventListViewBase::addEvent(const KAEvent& event, const InstanceList& instanceList, EventListViewBase* selectionView) +{ + for (InstanceListConstIterator it = instanceList.begin(); it != instanceList.end(); ++it) + (*it)->addEntry(event, true, (*it == selectionView)); +} + +/****************************************************************************** +* Modify an event in every list instance. +* If 'selectionView' is non-null, the selection highlight is moved to the +* modified event in that listView instance. +*/ +void EventListViewBase::modifyEvent(const QString& oldEventID, const KAEvent& newEvent, + const InstanceList& instanceList, EventListViewBase* selectionView) +{ + for (InstanceListConstIterator it = instanceList.begin(); it != instanceList.end(); ++it) + { + EventListViewBase* v = *it; + EventListViewItemBase* item = v->getEntry(oldEventID); + if (item) + v->deleteEntry(item, false); + v->addEntry(newEvent, true, (v == selectionView)); + } +} + +/****************************************************************************** +* Delete an event from every displayed list. +*/ +void EventListViewBase::deleteEvent(const QString& eventID, const InstanceList& instanceList) +{ + for (InstanceListConstIterator it = instanceList.begin(); it != instanceList.end(); ++it) + { + EventListViewBase* v = *it; + EventListViewItemBase* item = v->getEntry(eventID); + if (item) + v->deleteEntry(item, true); + else + v->refresh(); + } +} + +/****************************************************************************** +* Add a new item to the list. +* If 'reselect' is true, select/highlight the new item. +*/ +EventListViewItemBase* EventListViewBase::addEntry(const KAEvent& event, bool setSize, bool reselect) +{ + if (!shouldShowEvent(event)) + return 0; + return addEntry(createItem(event), setSize, reselect); +} + +EventListViewItemBase* EventListViewBase::addEntry(EventListViewItemBase* item, bool setSize, bool reselect) +{ + if (setSize) + resizeLastColumn(); + if (reselect) + { + clearSelection(); + setSelected(item, true); + } + return item; +} + +/****************************************************************************** +* Update a specified item in the list. +* If 'reselect' is true, select the updated item. +*/ +EventListViewItemBase* EventListViewBase::updateEntry(EventListViewItemBase* item, const KAEvent& newEvent, bool setSize, bool reselect) +{ + deleteEntry(item); + return addEntry(newEvent, setSize, reselect); +} + +/****************************************************************************** +* Delete a specified item from the list. +*/ +void EventListViewBase::deleteEntry(EventListViewItemBase* item, bool setSize) +{ + if (item) + { + delete item; + if (setSize) + resizeLastColumn(); + emit itemDeleted(); + } +} + +/****************************************************************************** +* Called when the Find action is selected. +* Display the non-modal Find dialog. +*/ +void EventListViewBase::slotFind() +{ + if (!mFind) + { + mFind = new Find(this); + connect(mFind, SIGNAL(active(bool)), SIGNAL(findActive(bool))); + } + mFind->display(); +} + +/****************************************************************************** +* Called when the Find Next or Find Prev action is selected. +*/ +void EventListViewBase::findNext(bool forward) +{ + if (mFind) + mFind->findNext(forward); +} + +/****************************************************************************** +* Called when the Select All action is selected. +* Select all items in the list. +*/ +void EventListViewBase::slotSelectAll() +{ + if (selectionMode() == QListView::Multi || selectionMode() == QListView::Extended) + selectAll(true); +} + +/****************************************************************************** +* Called when the Deselect action is selected. +* Deselect all items in the list. +*/ +void EventListViewBase::slotDeselect() +{ + selectAll(false); +} + +/****************************************************************************** +* Check whether there are any selected items. +*/ +bool EventListViewBase::anySelected() const +{ + for (QListViewItem* item = KListView::firstChild(); item; item = item->nextSibling()) + if (isSelected(item)) + return true; + return false; +} + +/****************************************************************************** +* Get the single selected event. +* Reply = the event +* = 0 if no event is selected or multiple events are selected. +*/ +const KAEvent* EventListViewBase::selectedEvent() const +{ + EventListViewItemBase* sel = selectedItem(); + return sel ? &sel->event() : 0; +} + +/****************************************************************************** +* Fetch the single selected item. +* This method works in both Single and Multi selection mode, unlike +* QListView::selectedItem(). +* Reply = null if no items are selected, or if multiple items are selected. +*/ +EventListViewItemBase* EventListViewBase::selectedItem() const +{ + if (selectionMode() == QListView::Single) + return (EventListViewItemBase*)KListView::selectedItem(); + + QListViewItem* item = 0; + for (QListViewItem* it = firstChild(); it; it = it->nextSibling()) + { + if (isSelected(it)) + { + if (item) + return 0; + item = it; + } + } + return (EventListViewItemBase*)item; +} + +/****************************************************************************** +* Fetch all selected items. +*/ +QValueList EventListViewBase::selectedItems() const +{ + QValueList items; + for (QListViewItem* item = firstChild(); item; item = item->nextSibling()) + { + if (isSelected(item)) + items.append((EventListViewItemBase*)item); + } + return items; +} + +/****************************************************************************** +* Return how many items are selected. +*/ +int EventListViewBase::selectedCount() const +{ + int count = 0; + for (QListViewItem* item = firstChild(); item; item = item->nextSibling()) + { + if (isSelected(item)) + ++count; + } + return count; +} + +/****************************************************************************** +* Sets the last column in the list view to extend at least to the right hand +* edge of the list view. +*/ +void EventListViewBase::resizeLastColumn() +{ + int lastColumnWidth = mLastColumnHeaderWidth; + for (EventListViewItemBase* item = firstChild(); item; item = item->nextSibling()) + { + int mw = item->lastColumnWidth(); + if (mw > lastColumnWidth) + lastColumnWidth = mw; + } + QHeader* head = header(); + int x = head->sectionPos(mLastColumn); + int availableWidth = visibleWidth() - x; + int rightColWidth = 0; + int index = head->mapToIndex(mLastColumn); + if (index < mLastColumn) + { + // The last column has been dragged by the user to a different position. + // Ensure that the columns now to the right of it are still shown. + for (int i = index + 1; i <= mLastColumn; ++i) + rightColWidth += columnWidth(head->mapToSection(i)); + availableWidth -= rightColWidth; + } + if (availableWidth < lastColumnWidth) + availableWidth = lastColumnWidth; + setColumnWidth(mLastColumn, availableWidth); + if (contentsWidth() > x + availableWidth + rightColWidth) + resizeContents(x + availableWidth + rightColWidth, contentsHeight()); +} + +/****************************************************************************** +* Called when the widget's size has changed (before it is painted). +* Sets the last column in the list view to extend at least to the right hand +* edge of the list view. +*/ +void EventListViewBase::resizeEvent(QResizeEvent* re) +{ + KListView::resizeEvent(re); + resizeLastColumn(); +} + +/****************************************************************************** +* Called when the widget is first displayed. +* Sets the last column in the list view to extend at least to the right hand +* edge of the list view. +*/ +void EventListViewBase::showEvent(QShowEvent* se) +{ + KListView::showEvent(se); + resizeLastColumn(); +} + +/****************************************************************************** +* Find the height of one list item. +*/ +int EventListViewBase::itemHeight() +{ + EventListViewItemBase* item = firstChild(); + if (!item) + { + // The list is empty, so create a temporary item to find its height + QListViewItem* item = new QListViewItem(this, QString::null); + int height = item->height(); + delete item; + return height; + } + else + return item->height(); +} + + +/*============================================================================= += Class: EventListViewItemBase += Base class containing the details of one event for display in an +* EventListViewBase. +=============================================================================*/ +QPixmap* EventListViewItemBase::mTextIcon; +QPixmap* EventListViewItemBase::mFileIcon; +QPixmap* EventListViewItemBase::mCommandIcon; +QPixmap* EventListViewItemBase::mEmailIcon; +int EventListViewItemBase::mIconWidth = 0; + + +EventListViewItemBase::EventListViewItemBase(EventListViewBase* parent, const KAEvent& event) + : QListViewItem(parent), + mEvent(event) +{ + iconWidth(); // load the icons +} + +/****************************************************************************** +* Set the text for the last column, and find its width. +*/ +void EventListViewItemBase::setLastColumnText() +{ + EventListViewBase* parent = (EventListViewBase*)listView(); + setText(parent->lastColumn(), lastColumnText()); + mLastColumnWidth = width(parent->fontMetrics(), parent, parent->lastColumn()); +} + +/****************************************************************************** +* Return the width of the widest alarm type icon. +*/ +int EventListViewItemBase::iconWidth() +{ + if (!mIconWidth) + { + mTextIcon = new QPixmap(SmallIcon("message")); + mFileIcon = new QPixmap(SmallIcon("file")); + mCommandIcon = new QPixmap(SmallIcon("exec")); + mEmailIcon = new QPixmap(SmallIcon("mail_generic")); + if (mTextIcon) + mIconWidth = mTextIcon->width(); + if (mFileIcon && mFileIcon->width() > mIconWidth) + mIconWidth = mFileIcon->width(); + if (mCommandIcon && mCommandIcon->width() > mIconWidth) + mIconWidth = mCommandIcon->width(); + if (mEmailIcon && mEmailIcon->width() > mIconWidth) + mIconWidth = mEmailIcon->width(); + } + return mIconWidth; +} + +/****************************************************************************** +* Return the icon associated with the event's action. +*/ +QPixmap* EventListViewItemBase::eventIcon() const +{ + switch (mEvent.action()) + { + case KAAlarm::FILE: return mFileIcon; + case KAAlarm::COMMAND: return mCommandIcon; + case KAAlarm::EMAIL: return mEmailIcon; + case KAAlarm::MESSAGE: + default: return mTextIcon; + } +} + + +/*============================================================================= += Class: EventListWhatsThisBase += Sets What's This? text depending on where in the list view is clicked. +=============================================================================*/ + +QString EventListWhatsThisBase::text(const QPoint& pt) +{ + int column = -1; + QPoint viewportPt = mListView->viewport()->mapFrom(mListView, pt); + QRect frame = mListView->header()->frameGeometry(); + if (frame.contains(pt) + || mListView->itemAt(QPoint(mListView->itemMargin(), viewportPt.y())) && frame.contains(QPoint(pt.x(), frame.y()))) + column = mListView->header()->sectionAt(pt.x()); + return mListView->whatsThisText(column); +} + diff --git a/kalarm/eventlistviewbase.h b/kalarm/eventlistviewbase.h new file mode 100644 index 000000000..23831c71d --- /dev/null +++ b/kalarm/eventlistviewbase.h @@ -0,0 +1,132 @@ +/* + * eventlistviewbase.h - base classes for widget showing list of events + * Program: kalarm + * Copyright (c) 2004-2006 by 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. + */ + +#ifndef EVENTLISTVIEWBASE_H +#define EVENTLISTVIEWBASE_H + +#include "kalarm.h" + +#include +#include + +#include "alarmevent.h" + +class QPixmap; +class EventListViewItemBase; +class Find; + + +class EventListViewBase : public KListView +{ + Q_OBJECT + public: + typedef QValueList InstanceList; + typedef QValueListIterator InstanceListIterator; + typedef QValueListConstIterator InstanceListConstIterator; + + EventListViewBase(QWidget* parent = 0, const char* name = 0); + virtual ~EventListViewBase() { } + EventListViewItemBase* getEntry(const QString& eventID) const; + void addEvent(const KAEvent& e) { addEvent(e, instances(), this); } + void modifyEvent(const KAEvent& e) + { modifyEvent(e.id(), e, instances(), this); } + void modifyEvent(const QString& oldEventID, const KAEvent& newEvent) + { modifyEvent(oldEventID, newEvent, instances(), this); } + void deleteEvent(const QString& eventID) { deleteEvent(eventID, instances()); } + static void addEvent(const KAEvent&, const InstanceList&, EventListViewBase* selectionView); + static void modifyEvent(const KAEvent& e, const InstanceList& list, EventListViewBase* selectionView) + { modifyEvent(e.id(), e, list, selectionView); } + static void modifyEvent(const QString& oldEventID, const KAEvent& newEvent, const InstanceList&, EventListViewBase* selectionView); + static void deleteEvent(const QString& eventID, const InstanceList&); + static void undeleteEvent(const QString& oldEventID, const KAEvent& event, const InstanceList& list, EventListViewBase* selectionView) + { modifyEvent(oldEventID, event, list, selectionView); } + void resizeLastColumn(); + int itemHeight(); + EventListViewItemBase* currentItem() const { return (EventListViewItemBase*)KListView::currentItem(); } + EventListViewItemBase* firstChild() const { return (EventListViewItemBase*)KListView::firstChild(); } + bool anySelected() const; // are any items selected? + const KAEvent* selectedEvent() const; + EventListViewItemBase* selectedItem() const; + QValueList selectedItems() const; + int selectedCount() const; + int lastColumn() const { return mLastColumn; } + virtual QString whatsThisText(int column) const = 0; + virtual InstanceList instances() = 0; // return all instances + + public slots: + void refresh(); + virtual void slotFind(); + virtual void slotFindNext() { findNext(true); } + virtual void slotFindPrev() { findNext(false); } + virtual void slotSelectAll(); + virtual void slotDeselect(); + + signals: + void itemDeleted(); + void findActive(bool); + + protected: + virtual void populate() = 0; // populate the list with all desired events + virtual EventListViewItemBase* createItem(const KAEvent&) = 0; // only used by default addEntry() method + virtual bool shouldShowEvent(const KAEvent&) const { return true; } + EventListViewItemBase* addEntry(const KAEvent&, bool setSize = false, bool reselect = false); + EventListViewItemBase* addEntry(EventListViewItemBase*, bool setSize, bool reselect); + EventListViewItemBase* updateEntry(EventListViewItemBase*, const KAEvent& newEvent, bool setSize = false, bool reselect = false); + void addLastColumn(const QString& title); + virtual void showEvent(QShowEvent*); + virtual void resizeEvent(QResizeEvent*); + + private: + void deleteEntry(EventListViewItemBase*, bool setSize = false); + void findNext(bool forward); + + Find* mFind; // alarm search object + int mLastColumn; // index to last column + int mLastColumnHeaderWidth; +}; + + +class EventListViewItemBase : public QListViewItem +{ + public: + EventListViewItemBase(EventListViewBase* parent, const KAEvent&); + const KAEvent& event() const { return mEvent; } + QPixmap* eventIcon() const; + int lastColumnWidth() const { return mLastColumnWidth; } + EventListViewItemBase* nextSibling() const { return (EventListViewItemBase*)QListViewItem::nextSibling(); } + static int iconWidth(); + + protected: + void setLastColumnText(); + virtual QString lastColumnText() const = 0; // get the text to display in the last column + + private: + static QPixmap* mTextIcon; + static QPixmap* mFileIcon; + static QPixmap* mCommandIcon; + static QPixmap* mEmailIcon; + static int mIconWidth; // maximum width of any icon + + KAEvent mEvent; // the event for this item + int mLastColumnWidth; // width required to display message column +}; + +#endif // EVENTLISTVIEWBASE_H + diff --git a/kalarm/find.cpp b/kalarm/find.cpp new file mode 100644 index 000000000..7caf4bde0 --- /dev/null +++ b/kalarm/find.cpp @@ -0,0 +1,394 @@ +/* + * find.cpp - search facility + * Program: kalarm + * Copyright © 2005,2006,2008 by 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 "kalarm.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "alarmlistview.h" +#include "preferences.h" +#include "find.moc" + +// KAlarm-specific options for Find dialog +enum { + FIND_LIVE = KFindDialog::MinimumUserOption, + FIND_EXPIRED = KFindDialog::MinimumUserOption << 1, + FIND_MESSAGE = KFindDialog::MinimumUserOption << 2, + FIND_FILE = KFindDialog::MinimumUserOption << 3, + FIND_COMMAND = KFindDialog::MinimumUserOption << 4, + FIND_EMAIL = KFindDialog::MinimumUserOption << 5 +}; +static long FIND_KALARM_OPTIONS = FIND_LIVE | FIND_EXPIRED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL; + + +Find::Find(EventListViewBase* parent) + : QObject(parent), + mListView(parent), + mDialog(0), + mFind(0), + mOptions(0) +{ +} + +Find::~Find() +{ + delete mDialog; // automatically set to 0 + delete mFind; + mFind = 0; +} + +/****************************************************************************** +* Display the Find dialog. +*/ +void Find::display() +{ + if (!mOptions) + // Set defaults the first time the Find dialog is activated + mOptions = FIND_LIVE | FIND_EXPIRED | FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL; + bool noExpired = !Preferences::expiredKeepDays(); + bool showExpired = mListView->isA("AlarmListView") && ((AlarmListView*)mListView)->showingExpired(); + if (noExpired || !showExpired) // these settings could change between activations + mOptions &= ~FIND_EXPIRED; + + if (mDialog) + { + KWin::activateWindow(mDialog->winId()); + } + else + { +#ifdef MODAL_FIND + mDialog = new KFindDialog(mListView, "FindDlg", mOptions, mHistory, (mListView->selectedCount() > 1)); +#else + mDialog = new KFindDialog(false, mListView, "FindDlg", mOptions, mHistory, (mListView->selectedCount() > 1)); +#endif + mDialog->setHasSelection(false); + QWidget* kalarmWidgets = mDialog->findExtension(); + + // Alarm types + QBoxLayout* layout = new QVBoxLayout(kalarmWidgets, 0, KDialog::spacingHint()); + QGroupBox* group = new QGroupBox(i18n("Alarm Type"), kalarmWidgets); + layout->addWidget(group); + QGridLayout* grid = new QGridLayout(group, 2, 2, KDialog::marginHint(), KDialog::spacingHint()); + grid->addRowSpacing(0, mDialog->fontMetrics().lineSpacing()/2); + grid->setColStretch(1, 1); + + // Live & expired alarm selection + mLive = new QCheckBox(i18n("Acti&ve"), group); + mLive->setFixedSize(mLive->sizeHint()); + QWhatsThis::add(mLive, i18n("Check to include active alarms in the search.")); + grid->addWidget(mLive, 1, 0, Qt::AlignAuto); + + mExpired = new QCheckBox(i18n("Ex&pired"), group); + mExpired->setFixedSize(mExpired->sizeHint()); + QWhatsThis::add(mExpired, + i18n("Check to include expired alarms in the search. " + "This option is only available if expired alarms are currently being displayed.")); + grid->addWidget(mExpired, 1, 2, Qt::AlignAuto); + + mActiveExpiredSep = new KSeparator(Qt::Horizontal, kalarmWidgets); + grid->addMultiCellWidget(mActiveExpiredSep, 2, 2, 0, 2); + + // Alarm actions + mMessageType = new QCheckBox(i18n("Text"), group, "message"); + mMessageType->setFixedSize(mMessageType->sizeHint()); + QWhatsThis::add(mMessageType, i18n("Check to include text message alarms in the search.")); + grid->addWidget(mMessageType, 3, 0); + + mFileType = new QCheckBox(i18n("Fi&le"), group, "file"); + mFileType->setFixedSize(mFileType->sizeHint()); + QWhatsThis::add(mFileType, i18n("Check to include file alarms in the search.")); + grid->addWidget(mFileType, 3, 2); + + mCommandType = new QCheckBox(i18n("Co&mmand"), group, "command"); + mCommandType->setFixedSize(mCommandType->sizeHint()); + QWhatsThis::add(mCommandType, i18n("Check to include command alarms in the search.")); + grid->addWidget(mCommandType, 4, 0); + + mEmailType = new QCheckBox(i18n("&Email"), group, "email"); + mEmailType->setFixedSize(mEmailType->sizeHint()); + QWhatsThis::add(mEmailType, i18n("Check to include email alarms in the search.")); + grid->addWidget(mEmailType, 4, 2); + + // Set defaults + mLive->setChecked(mOptions & FIND_LIVE); + mExpired->setChecked(mOptions & FIND_EXPIRED); + mMessageType->setChecked(mOptions & FIND_MESSAGE); + mFileType->setChecked(mOptions & FIND_FILE); + mCommandType->setChecked(mOptions & FIND_COMMAND); + mEmailType->setChecked(mOptions & FIND_EMAIL); + +#ifndef MODAL_FIND + connect(mDialog, SIGNAL(okClicked()), this, SLOT(slotFind())); +#endif + } + + // Only display active/expired options if expired alarms are being kept + if (noExpired) + { + mLive->hide(); + mExpired->hide(); + mActiveExpiredSep->hide(); + } + else + { + mLive->show(); + mExpired->show(); + mActiveExpiredSep->show(); + } + + // Disable options where no displayed alarms match them + bool live = false; + bool expired = false; + bool text = false; + bool file = false; + bool command = false; + bool email = false; + for (EventListViewItemBase* item = mListView->firstChild(); item; item = item->nextSibling()) + { + const KAEvent& event = item->event(); + if (event.expired()) + expired = true; + else + live = true; + switch (event.action()) + { + case KAEvent::MESSAGE: text = true; break; + case KAEvent::FILE: file = true; break; + case KAEvent::COMMAND: command = true; break; + case KAEvent::EMAIL: email = true; break; + } + } + mLive->setEnabled(live); + mExpired->setEnabled(expired); + mMessageType->setEnabled(text); + mFileType->setEnabled(file); + mCommandType->setEnabled(command); + mEmailType->setEnabled(email); + + mDialog->setHasCursor(mListView->currentItem()); +#ifdef MODAL_FIND + if (mDialog->exec() == QDialog::Accepted) + slotFind(); + else + delete mDialog; +#else + mDialog->show(); +#endif +} + +/****************************************************************************** +* Called when the user requests a search by clicking the dialog OK button. +*/ +void Find::slotFind() +{ + if (!mDialog) + return; + mHistory = mDialog->findHistory(); // save search history so that it can be displayed again + mOptions = mDialog->options() & ~FIND_KALARM_OPTIONS; + mOptions |= (mLive->isEnabled() && mLive->isChecked() ? FIND_LIVE : 0) + | (mExpired->isEnabled() && mExpired->isChecked() ? FIND_EXPIRED : 0) + | (mMessageType->isEnabled() && mMessageType->isChecked() ? FIND_MESSAGE : 0) + | (mFileType->isEnabled() && mFileType->isChecked() ? FIND_FILE : 0) + | (mCommandType->isEnabled() && mCommandType->isChecked() ? FIND_COMMAND : 0) + | (mEmailType->isEnabled() && mEmailType->isChecked() ? FIND_EMAIL : 0); + if (!(mOptions & (FIND_LIVE | FIND_EXPIRED)) + || !(mOptions & (FIND_MESSAGE | FIND_FILE | FIND_COMMAND | FIND_EMAIL))) + { + KMessageBox::sorry(mDialog, i18n("No alarm types are selected to search")); + return; + } + + // Supply KFind with only those options which relate to the text within alarms + long options = mOptions & (KFindDialog::WholeWordsOnly | KFindDialog::CaseSensitive | KFindDialog::RegularExpression); + bool newFind = !mFind; + bool newPattern = (mDialog->pattern() != mLastPattern); + mLastPattern = mDialog->pattern(); + if (mFind) + { + mFind->resetCounts(); + mFind->setPattern(mLastPattern); + mFind->setOptions(options); + } + else + { +#ifdef MODAL_FIND + mFind = new KFind(mLastPattern, options, mListView); + mDialog->deleteLater(); // automatically set to 0 +#else + mFind = new KFind(mLastPattern, options, mListView, mDialog); +#endif + connect(mFind, SIGNAL(destroyed()), SLOT(slotKFindDestroyed())); + mFind->closeFindNextDialog(); // prevent 'Find Next' dialog appearing + } + + // Set the starting point for the search + mStartID = QString::null; + mNoCurrentItem = newPattern; + bool checkEnd = false; + if (newPattern) + { + mFound = false; + if (mOptions & KFindDialog::FromCursor) + { + EventListViewItemBase* item = mListView->currentItem(); + if (item) + { + mStartID = item->event().id(); + mNoCurrentItem = false; + checkEnd = true; + } + } + } + + // Execute the search + findNext(true, true, checkEnd, false); + if (mFind && newFind) + emit active(true); +} + +/****************************************************************************** +* Perform the search. +* If 'fromCurrent' is true, the search starts with the current search item; +* otherwise, it starts from the next item. +*/ +void Find::findNext(bool forward, bool sort, bool checkEnd, bool fromCurrent) +{ + if (sort) + mListView->sort(); // ensure the whole list is sorted, not just the visible items + + EventListViewItemBase* item = mNoCurrentItem ? 0 : mListView->currentItem(); + if (!fromCurrent) + item = nextItem(item, forward); + + // Search successive alarms until a match is found or the end is reached + bool found = false; + bool last = false; + for ( ; item && !last; item = nextItem(item, forward)) + { + const KAEvent& event = item->event(); + if (!fromCurrent && !mStartID.isNull() && mStartID == event.id()) + last = true; // we've wrapped round and reached the starting alarm again + fromCurrent = false; + bool live = !event.expired(); + if (live && !(mOptions & FIND_LIVE) + || !live && !(mOptions & FIND_EXPIRED)) + continue; // we're not searching this type of alarm + switch (event.action()) + { + case KAEvent::MESSAGE: + if (!(mOptions & FIND_MESSAGE)) + break; + mFind->setData(event.cleanText()); + found = (mFind->find() == KFind::Match); + break; + + case KAEvent::FILE: + if (!(mOptions & FIND_FILE)) + break; + mFind->setData(event.cleanText()); + found = (mFind->find() == KFind::Match); + break; + + case KAEvent::COMMAND: + if (!(mOptions & FIND_COMMAND)) + break; + mFind->setData(event.cleanText()); + found = (mFind->find() == KFind::Match); + break; + + case KAEvent::EMAIL: + if (!(mOptions & FIND_EMAIL)) + break; + mFind->setData(event.emailAddresses(", ")); + found = (mFind->find() == KFind::Match); + if (found) + break; + mFind->setData(event.emailSubject()); + found = (mFind->find() == KFind::Match); + if (found) + break; + mFind->setData(event.emailAttachments().join(", ")); + found = (mFind->find() == KFind::Match); + if (found) + break; + mFind->setData(event.cleanText()); + found = (mFind->find() == KFind::Match); + break; + } + if (found) + break; + } + + // Process the search result + mNoCurrentItem = !item; + if (found) + { + // A matching alarm was found - highlight it and make it current + mFound = true; + mListView->clearSelection(); + mListView->setSelected(item, true); + mListView->setCurrentItem(item); + mListView->ensureItemVisible(item); + } + else + { + // No match was found + if (mFound || checkEnd) + { + QString msg = forward ? i18n("End of alarm list reached.\nContinue from the beginning?") + : i18n("Beginning of alarm list reached.\nContinue from the end?"); + if (KMessageBox::questionYesNo(mListView, msg, QString::null, KStdGuiItem::cont(), KStdGuiItem::cancel()) == KMessageBox::Yes) + { + mNoCurrentItem = true; + findNext(forward, false); + return; + } + } + else + mFind->displayFinalDialog(); // display "no match was found" + mNoCurrentItem = false; // restart from the currently highlighted alarm if Find Next etc selected + } +} + +/****************************************************************************** +* Get the next alarm item to search. +*/ +EventListViewItemBase* Find::nextItem(EventListViewItemBase* item, bool forward) const +{ + QListViewItem* it; + if (mOptions & KFindDialog::FindBackwards) + forward = !forward; + if (forward) + it = item ? item->itemBelow() : mListView->firstChild(); + else + it = item ? item->itemAbove() : mListView->lastItem(); + return (EventListViewItemBase*)it; +} diff --git a/kalarm/find.h b/kalarm/find.h new file mode 100644 index 000000000..1bb4913b1 --- /dev/null +++ b/kalarm/find.h @@ -0,0 +1,75 @@ +/* + * find.h - search facility + * Program: kalarm + * Copyright © 2005,2006,2008 by 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. + */ + +#ifndef FIND_H +#define FIND_H + +#include +#include +#include + +class QCheckBox; +class KFindDialog; +class KFind; +class KSeparator; +class EventListViewBase; +class EventListViewItemBase; + + +class Find : public QObject +{ + Q_OBJECT + public: + explicit Find(EventListViewBase* parent); + ~Find(); + void display(); + void findNext(bool forward) { findNext(forward, true); } + + signals: + void active(bool); + + private slots: + void slotFind(); + void slotKFindDestroyed() { emit active(false); } + + private: + void findNext(bool forward, bool sort, bool checkEnd = false, bool fromCurrent = false); + EventListViewItemBase* nextItem(EventListViewItemBase*, bool forward) const; + + EventListViewBase* mListView; // parent list view + QGuardedPtr mDialog; + QCheckBox* mExpired; + QCheckBox* mLive; + KSeparator* mActiveExpiredSep; + QCheckBox* mMessageType; + QCheckBox* mFileType; + QCheckBox* mCommandType; + QCheckBox* mEmailType; + KFind* mFind; + QStringList mHistory; // list of history items for Find dialog + QString mLastPattern; // pattern used in last search + QString mStartID; // ID of first alarm searched if 'from cursor' was selected + long mOptions; // OR of find dialog options + bool mNoCurrentItem; // there is no current item for the purposes of searching + bool mFound; // true if any matches have been found +}; + +#endif // FIND_H + diff --git a/kalarm/fontcolour.cpp b/kalarm/fontcolour.cpp new file mode 100644 index 000000000..dae2e9856 --- /dev/null +++ b/kalarm/fontcolour.cpp @@ -0,0 +1,265 @@ +/* + * fontcolour.cpp - font and colour chooser widget + * Program: kalarm + * Copyright © 2001-2003,2005,2008 by 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kalarmapp.h" +#include "preferences.h" +#include "colourcombo.h" +#include "checkbox.h" +#include "fontcolour.moc" + + +FontColourChooser::FontColourChooser(QWidget *parent, const char *name, + bool onlyFixed, const QStringList &fontList, + const QString& frameLabel, bool editColours, bool fg, bool defaultFont, + int visibleListSize) + : QWidget(parent, name), + mFgColourButton(0), + mRemoveColourButton(0), + mColourList(Preferences::messageColours()), + mReadOnly(false) +{ + QVBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint()); + QWidget* page = this; + if (!frameLabel.isNull()) + { + page = new QGroupBox(frameLabel, this); + topLayout->addWidget(page); + topLayout = new QVBoxLayout(page, KDialog::marginHint(), KDialog::spacingHint()); + topLayout->addSpacing(fontMetrics().height() - KDialog::marginHint() + KDialog::spacingHint()); + } + QHBoxLayout* hlayout = new QHBoxLayout(topLayout); + QVBoxLayout* colourLayout = new QVBoxLayout(hlayout); + if (fg) + { + QHBox* box = new QHBox(page); // to group widgets for QWhatsThis text + box->setSpacing(KDialog::spacingHint()/2); + colourLayout->addWidget(box); + + QLabel* label = new QLabel(i18n("&Foreground color:"), box); + box->setStretchFactor(new QWidget(box), 0); + mFgColourButton = new ColourCombo(box); + connect(mFgColourButton, SIGNAL(activated(const QString&)), SLOT(setSampleColour())); + label->setBuddy(mFgColourButton); + QWhatsThis::add(box, i18n("Select the alarm message foreground color")); + } + + QHBox* box = new QHBox(page); // to group widgets for QWhatsThis text + box->setSpacing(KDialog::spacingHint()/2); + colourLayout->addWidget(box); + + QLabel* label = new QLabel(i18n("&Background color:"), box); + box->setStretchFactor(new QWidget(box), 0); + mBgColourButton = new ColourCombo(box); + connect(mBgColourButton, SIGNAL(activated(const QString&)), SLOT(setSampleColour())); + label->setBuddy(mBgColourButton); + QWhatsThis::add(box, i18n("Select the alarm message background color")); + hlayout->addStretch(); + + if (editColours) + { + QHBoxLayout* layout = new QHBoxLayout(topLayout); + QPushButton* button = new QPushButton(i18n("Add Co&lor..."), page); + button->setFixedSize(button->sizeHint()); + connect(button, SIGNAL(clicked()), SLOT(slotAddColour())); + QWhatsThis::add(button, i18n("Choose a new color to add to the color selection list.")); + layout->addWidget(button); + + mRemoveColourButton = new QPushButton(i18n("&Remove Color"), page); + mRemoveColourButton->setFixedSize(mRemoveColourButton->sizeHint()); + connect(mRemoveColourButton, SIGNAL(clicked()), SLOT(slotRemoveColour())); + QWhatsThis::add(mRemoveColourButton, + i18n("Remove the color currently shown in the background color chooser, from the color selection list.")); + layout->addWidget(mRemoveColourButton); + } + + if (defaultFont) + { + QHBoxLayout* layout = new QHBoxLayout(topLayout); + mDefaultFont = new CheckBox(i18n("Use &default font"), page); + mDefaultFont->setMinimumSize(mDefaultFont->sizeHint()); + connect(mDefaultFont, SIGNAL(toggled(bool)), SLOT(slotDefaultFontToggled(bool))); + QWhatsThis::add(mDefaultFont, + i18n("Check to use the default font current at the time the alarm is displayed.")); + layout->addWidget(mDefaultFont); + layout->addWidget(new QWidget(page)); // left adjust the widget + } + else + mDefaultFont = 0; + + mFontChooser = new KFontChooser(page, name, onlyFixed, fontList, false, visibleListSize); + mFontChooser->installEventFilter(this); // for read-only mode + const QObjectList* kids = mFontChooser->queryList(); + for (QObjectList::ConstIterator it = kids->constBegin(); it != kids->constEnd(); ++it) + (*it)->installEventFilter(this); + topLayout->addWidget(mFontChooser); + + slotDefaultFontToggled(false); +} + +void FontColourChooser::setDefaultFont() +{ + if (mDefaultFont) + mDefaultFont->setChecked(true); +} + +void FontColourChooser::setFont(const QFont& font, bool onlyFixed) +{ + if (mDefaultFont) + mDefaultFont->setChecked(false); + mFontChooser->setFont(font, onlyFixed); +} + +bool FontColourChooser::defaultFont() const +{ + return mDefaultFont ? mDefaultFont->isChecked() : false; +} + +QFont FontColourChooser::font() const +{ + return (mDefaultFont && mDefaultFont->isChecked()) ? QFont() : mFontChooser->font(); +} + +void FontColourChooser::setBgColour(const QColor& colour) +{ + mBgColourButton->setColor(colour); + mFontChooser->setBackgroundColor(colour); +} + +void FontColourChooser::setSampleColour() +{ + QColor bg = mBgColourButton->color(); + mFontChooser->setBackgroundColor(bg); + QColor fg = fgColour(); + mFontChooser->setColor(fg); + if (mRemoveColourButton) + mRemoveColourButton->setEnabled(!mBgColourButton->isCustomColour()); // no deletion of custom colour +} + +QColor FontColourChooser::bgColour() const +{ + return mBgColourButton->color(); +} + +QColor FontColourChooser::fgColour() const +{ + if (mFgColourButton) + return mFgColourButton->color(); + else + { + QColor bg = mBgColourButton->color(); + QPalette pal(bg, bg); + return pal.color(QPalette::Active, QColorGroup::Text); + } +} + +QString FontColourChooser::sampleText() const +{ + return mFontChooser->sampleText(); +} + +void FontColourChooser::setSampleText(const QString& text) +{ + mFontChooser->setSampleText(text); +} + +void FontColourChooser::setFgColour(const QColor& colour) +{ + if (mFgColourButton) + { + mFgColourButton->setColor(colour); + mFontChooser->setColor(colour); + } +} + +void FontColourChooser::setReadOnly(bool ro) +{ + if (ro != mReadOnly) + { + mReadOnly = ro; + if (mFgColourButton) + mFgColourButton->setReadOnly(ro); + mBgColourButton->setReadOnly(ro); + mDefaultFont->setReadOnly(ro); + } +} + +bool FontColourChooser::eventFilter(QObject*, QEvent* e) +{ + if (mReadOnly) + { + switch (e->type()) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::KeyPress: + case QEvent::KeyRelease: + return true; // prevent the event being handled + default: + break; + } + } + return false; +} + +void FontColourChooser::slotDefaultFontToggled(bool on) +{ + mFontChooser->setEnabled(!on); +} + +void FontColourChooser::setColours(const ColourList& colours) +{ + mColourList = colours; + mBgColourButton->setColours(mColourList); + mFontChooser->setBackgroundColor(mBgColourButton->color()); +} + +void FontColourChooser::slotAddColour() +{ + QColor colour; + if (KColorDialog::getColor(colour, this) == QDialog::Accepted) + { + mColourList.insert(colour); + mBgColourButton->setColours(mColourList); + } +} + +void FontColourChooser::slotRemoveColour() +{ + if (!mBgColourButton->isCustomColour()) + { + mColourList.remove(mBgColourButton->color()); + mBgColourButton->setColours(mColourList); + } +} + diff --git a/kalarm/fontcolour.h b/kalarm/fontcolour.h new file mode 100644 index 000000000..1e37b3836 --- /dev/null +++ b/kalarm/fontcolour.h @@ -0,0 +1,78 @@ +/* + * fontcolour.h - font and colour chooser widget + * Program: kalarm + * Copyright © 2001,2003,2008 by 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. + */ + +#ifndef FONTCOLOUR_H +#define FONTCOLOUR_H + +#include +#include +#include +#include +#include "colourlist.h" + +class ColourCombo; +class QPushButton; +class CheckBox; + + +class FontColourChooser : public QWidget +{ + Q_OBJECT +public: + explicit FontColourChooser(QWidget* parent = 0, const char* name = 0, + bool onlyFixed = false, + const QStringList& fontList = QStringList(), + const QString& frameLabel = i18n("Requested font"), + bool editColours = false, bool fg = true, bool defaultFont = false, + int visibleListSize = 8); + + void setDefaultFont(); + void setFont(const QFont&, bool onlyFixed = false); + bool defaultFont() const; + QFont font() const; + QColor fgColour() const; + QColor bgColour() const; + const ColourList& colours() const { return mColourList; } + void setFgColour(const QColor&); + void setBgColour(const QColor&); + void setColours(const ColourList&); + QString sampleText() const; + void setSampleText(const QString& text); + bool isReadOnly() const { return mReadOnly; } + void setReadOnly(bool); + virtual bool eventFilter(QObject*, QEvent*); + +private slots: + void setSampleColour(); + void slotDefaultFontToggled(bool); + void slotAddColour(); + void slotRemoveColour(); + +private: + ColourCombo* mFgColourButton; // or null + ColourCombo* mBgColourButton; + QPushButton* mRemoveColourButton; + KFontChooser* mFontChooser; + CheckBox* mDefaultFont; // or null + ColourList mColourList; + bool mReadOnly; +}; + +#endif diff --git a/kalarm/fontcolourbutton.cpp b/kalarm/fontcolourbutton.cpp new file mode 100644 index 000000000..b3a55359a --- /dev/null +++ b/kalarm/fontcolourbutton.cpp @@ -0,0 +1,161 @@ +/* + * fontcolourbutton.cpp - pushbutton widget to select a font and colour + * Program: kalarm + * Copyright © 2003-2005,2007,2008 by 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 "kalarm.h" + +#include +#include +#include + +#include +#include + +#include "fontcolour.h" +#include "preferences.h" +#include "pushbutton.h" +#include "fontcolourbutton.moc" + + +/*============================================================================= += Class FontColourButton += Font/colour selection button. +=============================================================================*/ + +FontColourButton::FontColourButton(QWidget* parent, const char* name) + : QFrame(parent, name), + mReadOnly(false) +{ + setFrameStyle(NoFrame); + QHBoxLayout* layout = new QHBoxLayout(this, 0, KDialog::spacingHint()); + + mButton = new PushButton(i18n("Font && Co&lor..."), this); + mButton->setFixedSize(mButton->sizeHint()); + connect(mButton, SIGNAL(clicked()), SLOT(slotButtonPressed())); + QWhatsThis::add(mButton, + i18n("Choose the font, and foreground and background color, for the alarm message.")); + layout->addWidget(mButton); + + // Font and colour sample display + mSample = new QLineEdit(this); + mSample->setMinimumHeight(QMAX(mSample->fontMetrics().lineSpacing(), mButton->height()*3/2)); + mSample->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::MinimumExpanding); + mSample->setText(i18n("The Quick Brown Fox Jumps Over The Lazy Dog")); + mSample->setCursorPosition(0); + mSample->setAlignment(Qt::AlignCenter); + QWhatsThis::add(mSample, + i18n("This sample text illustrates the current font and color settings. " + "You may edit it to test special characters.")); + layout->addWidget(mSample); +} + +void FontColourButton::setDefaultFont() +{ + mDefaultFont = true; + mSample->setFont(Preferences::messageFont()); +} + +void FontColourButton::setFont(const QFont& font) +{ + mDefaultFont = false; + mFont = font; + mSample->setFont(mFont); +} + +void FontColourButton::setBgColour(const QColor& colour) +{ + mBgColour = colour; + mSample->setPaletteBackgroundColor(mBgColour); +} + +void FontColourButton::setFgColour(const QColor& colour) +{ + mFgColour = colour; + mSample->setPaletteForegroundColor(mFgColour); +} + +/****************************************************************************** +* Called when the OK button is clicked. +* Display a font and colour selection dialog and get the selections. +*/ +void FontColourButton::slotButtonPressed() +{ + FontColourDlg dlg(mBgColour, mFgColour, mFont, mDefaultFont, + i18n("Choose Alarm Font & Color"), this, "fontColourDlg"); + dlg.setReadOnly(mReadOnly); + if (dlg.exec() == QDialog::Accepted) + { + mDefaultFont = dlg.defaultFont(); + mFont = dlg.font(); + mSample->setFont(mFont); + mBgColour = dlg.bgColour(); + mSample->setPaletteBackgroundColor(mBgColour); + mFgColour = dlg.fgColour(); + mSample->setPaletteForegroundColor(mFgColour); + emit selected(); + } +} + + +/*============================================================================= += Class FontColourDlg += Font/colour selection dialog. +=============================================================================*/ + +FontColourDlg::FontColourDlg(const QColor& bgColour, const QColor& fgColour, const QFont& font, + bool defaultFont, const QString& caption, QWidget* parent, const char* name) + : KDialogBase(parent, name, true, caption, Ok|Cancel, Ok, false), + mReadOnly(false) +{ + QWidget* page = new QWidget(this); + setMainWidget(page); + QVBoxLayout* layout = new QVBoxLayout(page, 0, spacingHint()); + mChooser = new FontColourChooser(page, 0, false, QStringList(), QString::null, false, true, true); + mChooser->setBgColour(bgColour); + mChooser->setFgColour(fgColour); + if (defaultFont) + mChooser->setDefaultFont(); + else + mChooser->setFont(font); + layout->addWidget(mChooser); + layout->addSpacing(KDialog::spacingHint()); +} + +/****************************************************************************** +* Called when the OK button is clicked. +*/ +void FontColourDlg::slotOk() +{ + if (mReadOnly) + { + reject(); + return; + } + mDefaultFont = mChooser->defaultFont(); + mFont = mChooser->font(); + mBgColour = mChooser->bgColour(); + mFgColour = mChooser->fgColour(); + accept(); +} + +void FontColourDlg::setReadOnly(bool ro) +{ + mReadOnly = ro; + mChooser->setReadOnly(mReadOnly); +} diff --git a/kalarm/fontcolourbutton.h b/kalarm/fontcolourbutton.h new file mode 100644 index 000000000..9bbe3d8db --- /dev/null +++ b/kalarm/fontcolourbutton.h @@ -0,0 +1,91 @@ +/* + * fontcolourbutton.h - pushbutton widget to select a font and colour + * Program: kalarm + * Copyright © 2003,2007 by 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. + */ + +#ifndef FONTCOLOURBUTTON_H +#define FONTCOLOURBUTTON_H + +#include +#include +#include +#include + +class QLineEdit; +class FontColourChooser; +class PushButton; + + +class FontColourButton : public QFrame +{ + Q_OBJECT + public: + FontColourButton(QWidget* parent = 0, const char* name = 0); + void setDefaultFont(); + void setFont(const QFont&); + void setBgColour(const QColor&); + void setFgColour(const QColor&); + bool defaultFont() const { return mDefaultFont; } + QFont font() const { return mFont; } + QColor bgColour() const { return mBgColour; } + QColor fgColour() const { return mFgColour; } + virtual void setReadOnly(bool ro) { mReadOnly = ro; } + virtual bool isReadOnly() const { return mReadOnly; } + + signals: + void selected(); + + protected slots: + void slotButtonPressed(); + + private: + PushButton* mButton; + QColor mBgColour, mFgColour; + QFont mFont; + QLineEdit* mSample; + bool mDefaultFont; + bool mReadOnly; +}; + + +// Font and colour selection dialog displayed by the push button +class FontColourDlg : public KDialogBase +{ + Q_OBJECT + public: + FontColourDlg(const QColor& bg, const QColor& fg, const QFont&, bool defaultFont, + const QString& caption, QWidget* parent = 0, const char* name = 0); + bool defaultFont() const { return mDefaultFont; } + QFont font() const { return mFont; } + QColor bgColour() const { return mBgColour; } + QColor fgColour() const { return mFgColour; } + void setReadOnly(bool); + bool isReadOnly() const { return mReadOnly; } + + protected slots: + virtual void slotOk(); + + private: + FontColourChooser* mChooser; + QColor mBgColour, mFgColour; + QFont mFont; + bool mDefaultFont; + bool mReadOnly; +}; + +#endif // FONTCOLOURBUTTON_H diff --git a/kalarm/functions.cpp b/kalarm/functions.cpp new file mode 100644 index 000000000..dd83fea43 --- /dev/null +++ b/kalarm/functions.cpp @@ -0,0 +1,1099 @@ +/* + * functions.cpp - miscellaneous functions + * Program: kalarm + * Copyright © 2001-2009 by 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 "kalarm.h" +#include "functions.h" + +#include "alarmcalendar.h" +#include "alarmevent.h" +#include "alarmlistview.h" +#include "daemon.h" +#include "kalarmapp.h" +#include "kamail.h" +#include "mainwindow.h" +#include "messagewin.h" +#include "preferences.h" +#include "shellprocess.h" +#include "templatelistview.h" +#include "templatemenuaction.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +namespace +{ +bool resetDaemonQueued = false; +QCString korganizerName = "korganizer"; +QString korgStartError; +#define KORG_DCOP_OBJECT "KOrganizerIface" +const char* KORG_DCOP_WINDOW = "KOrganizer MainWindow"; +const char* KMAIL_DCOP_WINDOW = "kmail-mainwindow#1"; + +bool sendToKOrganizer(const KAEvent&); +bool deleteFromKOrganizer(const QString& eventID); +bool runKOrganizer(); +} +#ifdef HAVE_XTEST +void x11_cancelScreenSaver(); +#endif + + +namespace KAlarm +{ + +/****************************************************************************** +* Display a main window with the specified event selected. +*/ +MainWindow* displayMainWindowSelected(const QString& eventID) +{ + MainWindow* win = MainWindow::firstWindow(); + if (!win) + { + if (theApp()->checkCalendarDaemon()) // ensure calendar is open and daemon started + { + win = MainWindow::create(); + win->show(); + } + } + else + { + // There is already a main window, so make it the active window + bool visible = win->isVisible(); + if (visible) + win->hide(); // in case it's on a different desktop + if (!visible || win->isMinimized()) + win->showNormal(); + win->raise(); + win->setActiveWindow(); + } + if (win && !eventID.isEmpty()) + win->selectEvent(eventID); + return win; +} + +/****************************************************************************** +* Create a New Alarm KAction. +*/ +KAction* createNewAlarmAction(const QString& label, QObject* receiver, const char* slot, KActionCollection* actions, const char* name) +{ + return new KAction(label, "filenew", KStdAccel::openNew(), receiver, slot, actions, name); +} + +/****************************************************************************** +* Create a New From Template KAction. +*/ +TemplateMenuAction* createNewFromTemplateAction(const QString& label, QObject* receiver, const char* slot, KActionCollection* actions, const char* name) +{ + return new TemplateMenuAction(label, "new_from_template", receiver, slot, actions, name); +} + +/****************************************************************************** +* Add a new active (non-expired) alarm. +* Save it in the calendar file and add it to every main window instance. +* If 'selectionView' is non-null, the selection highlight is moved to the new +* event in that listView instance. +* 'event' is updated with the actual event ID. +*/ +UpdateStatus addEvent(KAEvent& event, AlarmListView* selectionView, QWidget* errmsgParent, bool useEventID, bool allowKOrgUpdate) +{ + kdDebug(5950) << "KAlarm::addEvent(): " << event.id() << endl; + UpdateStatus status = UPDATE_OK; + if (!theApp()->checkCalendarDaemon()) // ensure calendar is open and daemon started + return UPDATE_FAILED; + else + { + // Save the event details in the calendar file, and get the new event ID + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (!cal->addEvent(event, useEventID)) + status = UPDATE_FAILED; + else if (!cal->save()) + status = SAVE_FAILED; + } + if (status == UPDATE_OK) + { + if (allowKOrgUpdate && event.copyToKOrganizer()) + { + if (!sendToKOrganizer(event)) // tell KOrganizer to show the event + status = UPDATE_KORG_ERR; + } + + // Update the window lists + AlarmListView::addEvent(event, selectionView); + return status; + } + + if (errmsgParent) + displayUpdateError(errmsgParent, status, ERR_ADD, 1); + return status; +} + +/****************************************************************************** +* Save the event in the expired calendar file and adjust every main window instance. +* The event's ID is changed to an expired ID if necessary. +*/ +bool addExpiredEvent(KAEvent& event) +{ + kdDebug(5950) << "KAlarm::addExpiredEvent(" << event.id() << ")\n"; + AlarmCalendar* cal = AlarmCalendar::expiredCalendarOpen(); + if (!cal) + return false; + bool archiving = (KAEvent::uidStatus(event.id()) == KAEvent::ACTIVE); + if (archiving) + event.setSaveDateTime(QDateTime::currentDateTime()); // time stamp to control purging + KCal::Event* kcalEvent = cal->addEvent(event); + cal->save(); + + // Update window lists + if (!archiving) + AlarmListView::addEvent(event, 0); + else if (kcalEvent) + AlarmListView::modifyEvent(KAEvent(*kcalEvent), 0); + return true; +} + +/****************************************************************************** +* Add a new template. +* Save it in the calendar file and add it to every template list view. +* If 'selectionView' is non-null, the selection highlight is moved to the new +* event in that listView instance. +* 'event' is updated with the actual event ID. +*/ +UpdateStatus addTemplate(KAEvent& event, TemplateListView* selectionView, QWidget* errmsgParent) +{ + kdDebug(5950) << "KAlarm::addTemplate(): " << event.id() << endl; + UpdateStatus status = UPDATE_OK; + + // Add the template to the calendar file + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (!cal || !cal->addEvent(event)) + status = UPDATE_FAILED; + else if (!cal->save()) + status = SAVE_FAILED; + else + { + cal->emitEmptyStatus(); + + // Update the window lists + TemplateListView::addEvent(event, selectionView); + return UPDATE_OK; + } + + if (errmsgParent) + displayUpdateError(errmsgParent, status, ERR_TEMPLATE, 1); + return status; +} + +/****************************************************************************** +* Modify an active (non-expired) alarm in the calendar file and in every main +* window instance. +* The new event will have a different event ID from the old one. +* If 'selectionView' is non-null, the selection highlight is moved to the +* modified event in that listView instance. +*/ +UpdateStatus modifyEvent(KAEvent& oldEvent, const KAEvent& newEvent, AlarmListView* selectionView, QWidget* errmsgParent) +{ + kdDebug(5950) << "KAlarm::modifyEvent(): '" << oldEvent.id() << endl; + + UpdateStatus status = UPDATE_OK; + if (!newEvent.valid()) + { + deleteEvent(oldEvent, true); + status = UPDATE_FAILED; + } + else + { + if (oldEvent.copyToKOrganizer()) + { + // Tell KOrganizer to delete its old event. + // But ignore errors, because the user could have manually + // deleted it since KAlarm asked KOrganizer to set it up. + deleteFromKOrganizer(oldEvent.id()); + } + + // Update the event in the calendar file, and get the new event ID + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (!cal->deleteEvent(oldEvent.id()) + || !cal->addEvent(const_cast(newEvent), true)) + status = UPDATE_FAILED; + else if (!cal->save()) + status = SAVE_FAILED; + if (status == UPDATE_OK) + { + if (newEvent.copyToKOrganizer()) + { + if (!sendToKOrganizer(newEvent)) // tell KOrganizer to show the new event + status = UPDATE_KORG_ERR; + } + + // Update the window lists + AlarmListView::modifyEvent(oldEvent.id(), newEvent, selectionView); + return status; + } + } + + if (errmsgParent) + displayUpdateError(errmsgParent, status, ERR_ADD, 1); + return status; +} + +/****************************************************************************** +* Update an active (non-expired) alarm from the calendar file and from every +* main window instance. +* The new event will have the same event ID as the old one. +* If 'selectionView' is non-null, the selection highlight is moved to the +* updated event in that listView instance. +* The event is not updated in KOrganizer, since this function is called when an +* existing alarm is rescheduled (due to recurrence or deferral). +*/ +UpdateStatus updateEvent(KAEvent& event, AlarmListView* selectionView, QWidget* errmsgParent, bool archiveOnDelete, bool incRevision) +{ + kdDebug(5950) << "KAlarm::updateEvent(): " << event.id() << endl; + + if (!event.valid()) + deleteEvent(event, archiveOnDelete); + else + { + // Update the event in the calendar file. + if (incRevision) + event.incrementRevision(); // ensure alarm daemon sees the event has changed + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + cal->updateEvent(event); + if (!cal->save()) + { + if (errmsgParent) + displayUpdateError(errmsgParent, SAVE_FAILED, ERR_ADD, 1); + return SAVE_FAILED; + } + + // Update the window lists + AlarmListView::modifyEvent(event, selectionView); + } + return UPDATE_OK; +} + +/****************************************************************************** +* Update a template in the calendar file and in every template list view. +* If 'selectionView' is non-null, the selection highlight is moved to the +* updated event in that listView instance. +*/ +UpdateStatus updateTemplate(const KAEvent& event, TemplateListView* selectionView, QWidget* errmsgParent) +{ + UpdateStatus status = UPDATE_OK; + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (!cal) + status = UPDATE_FAILED; + else + { + cal->updateEvent(event); + if (!cal->save()) + status = SAVE_FAILED; + else + { + TemplateListView::modifyEvent(event.id(), event, selectionView); + return UPDATE_OK; + } + } + + if (errmsgParent) + displayUpdateError(errmsgParent, SAVE_FAILED, ERR_TEMPLATE, 1); + return status; +} + +/****************************************************************************** +* Delete an alarm from the calendar file and from every main window instance. +* If the event is archived, the event's ID is changed to an expired ID if necessary. +*/ +UpdateStatus deleteEvent(KAEvent& event, bool archive, QWidget* errmsgParent) +{ + QString id = event.id(); + kdDebug(5950) << "KAlarm::deleteEvent(): " << id << endl; + + // Update the window lists + AlarmListView::deleteEvent(id); + + UpdateStatus status = UPDATE_OK; + AlarmCalendar* cal; + + // Delete the event from the calendar file + if (KAEvent::uidStatus(id) == KAEvent::EXPIRED) + { + cal = AlarmCalendar::expiredCalendarOpen(); + if (!cal) + status = UPDATE_FAILED; + } + else + { + if (event.copyToKOrganizer()) + { + // The event was shown in KOrganizer, so tell KOrganizer to + // delete it. Note that an error could occur if the user + // manually deleted it from KOrganizer since it was set up. + if (!deleteFromKOrganizer(event.id())) + status = UPDATE_KORG_ERR; + } + if (archive && event.toBeArchived()) + addExpiredEvent(event); // this changes the event ID to an expired ID + cal = AlarmCalendar::activeCalendar(); + } + if (status != UPDATE_FAILED) + { + if (!cal->deleteEvent(id, true)) // save calendar after deleting + status = SAVE_FAILED; + } + if (status > UPDATE_KORG_ERR && errmsgParent) + displayUpdateError(errmsgParent, SAVE_FAILED, ERR_DELETE, 1); + return status; +} + +/****************************************************************************** +* Delete a template from the calendar file and from every template list view. +*/ +UpdateStatus deleteTemplate(const KAEvent& event) +{ + QString id = event.id(); + + // Delete the template from the calendar file + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (!cal) + return UPDATE_FAILED; + if (!cal->deleteEvent(id, true)) // save calendar after deleting + return SAVE_FAILED; + cal->emitEmptyStatus(); + + // Update the window lists + TemplateListView::deleteEvent(id); + return UPDATE_OK; +} + +/****************************************************************************** +* Delete an alarm from the display calendar. +*/ +void deleteDisplayEvent(const QString& eventID) +{ + kdDebug(5950) << "KAlarm::deleteDisplayEvent(" << eventID << ")\n"; + + if (KAEvent::uidStatus(eventID) == KAEvent::DISPLAYING) + { + AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); + if (cal) + cal->deleteEvent(eventID, true); // save calendar after deleting + } +} + +/****************************************************************************** +* Undelete an expired alarm, and update every main window instance. +* The archive bit is set to ensure that it gets re-archived if it is deleted again. +* If 'selectionView' is non-null, the selection highlight is moved to the +* restored event in that listView instance. +*/ +UpdateStatus reactivateEvent(KAEvent& event, AlarmListView* selectionView, bool useEventID) +{ + QString id = event.id(); + kdDebug(5950) << "KAlarm::reactivateEvent(): " << id << endl; + + // Delete the event from the expired calendar file + if (KAEvent::uidStatus(id) == KAEvent::EXPIRED) + { + QDateTime now = QDateTime::currentDateTime(); + if (event.occursAfter(now, true)) + { + if (event.recurs() || event.repeatCount()) + event.setNextOccurrence(now); // skip any recurrences in the past + event.setArchive(); // ensure that it gets re-archived if it is deleted + + // Save the event details in the calendar file, and get the new event ID + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (!cal->addEvent(event, useEventID)) + return UPDATE_FAILED; + if (!cal->save()) + return SAVE_FAILED; + + UpdateStatus status = UPDATE_OK; + if (event.copyToKOrganizer()) + { + if (!sendToKOrganizer(event)) // tell KOrganizer to show the event + status = UPDATE_KORG_ERR; + } + + // Update the window lists + AlarmListView::undeleteEvent(id, event, selectionView); + + cal = AlarmCalendar::expiredCalendarOpen(); + if (cal) + cal->deleteEvent(id, true); // save calendar after deleting + return status; + } + } + return UPDATE_FAILED; +} + +/****************************************************************************** +* Enable or disable an alarm in the calendar file and in every main window instance. +* The new event will have the same event ID as the old one. +* If 'selectionView' is non-null, the selection highlight is moved to the +* updated event in that listView instance. +*/ +UpdateStatus enableEvent(KAEvent& event, AlarmListView* selectionView, bool enable) +{ + kdDebug(5950) << "KAlarm::enableEvent(" << enable << "): " << event.id() << endl; + + if (enable != event.enabled()) + { + event.setEnabled(enable); + + // Update the event in the calendar file + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + cal->updateEvent(event); + if (!cal->save()) + return SAVE_FAILED; + + // If we're disabling a display alarm, close any message window + if (!enable && event.displayAction()) + { + MessageWin* win = MessageWin::findEvent(event.id()); + delete win; + } + + // Update the window lists + AlarmListView::modifyEvent(event, selectionView); + } + return UPDATE_OK; +} + +/****************************************************************************** +* Display an error message about an error saving an event. +*/ +void displayUpdateError(QWidget* parent, UpdateStatus, UpdateError code, int nAlarms) +{ + QString errmsg; + switch (code) + { + case ERR_ADD: + errmsg = (nAlarms > 1) ? i18n("Error saving alarms") + : i18n("Error saving alarm"); + break; + case ERR_DELETE: + errmsg = (nAlarms > 1) ? i18n("Error deleting alarms") + : i18n("Error deleting alarm"); + break; + case ERR_REACTIVATE: + errmsg = (nAlarms > 1) ? i18n("Error saving reactivated alarms") + : i18n("Error saving reactivated alarm"); + break; + case ERR_TEMPLATE: + errmsg = i18n("Error saving alarm template"); + break; + } + KMessageBox::error(parent, errmsg); +} + +/****************************************************************************** +* Display an error message corresponding to a specified alarm update error code. +*/ +void displayKOrgUpdateError(QWidget* parent, KOrgUpdateError code, int nAlarms) +{ + QString errmsg; + switch (code) + { + case KORG_ERR_ADD: + errmsg = (nAlarms > 1) ? i18n("Unable to show alarms in KOrganizer") + : i18n("Unable to show alarm in KOrganizer"); + break; + case KORG_ERR_MODIFY: + errmsg = i18n("Unable to update alarm in KOrganizer"); + break; + case KORG_ERR_DELETE: + errmsg = (nAlarms > 1) ? i18n("Unable to delete alarms from KOrganizer") + : i18n("Unable to delete alarm from KOrganizer"); + break; + } + KMessageBox::error(parent, errmsg); +} + +/****************************************************************************** +* Display the alarm edit dialogue to edit a specified alarm. +*/ +bool edit(const QString& eventID) +{ + AlarmCalendar* cal; + switch (KAEvent::uidStatus(eventID)) + { + case KAEvent::ACTIVE: + cal = AlarmCalendar::activeCalendar(); + break; + case KAEvent::TEMPLATE: + cal = AlarmCalendar::templateCalendarOpen(); + break; + default: + kdError(5950) << "KAlarm::edit(" << eventID << "): event not active or template" << endl; + return false; + } + KCal::Event* kcalEvent = cal->event(eventID); + if (!kcalEvent) + { + kdError(5950) << "KAlarm::edit(): event ID not found: " << eventID << endl; + return false; + } + KAEvent event(*kcalEvent); + MainWindow::executeEdit(event); + return true; +} + +/****************************************************************************** +* Display the alarm edit dialogue to edit a new alarm, optionally preset with +* a template. +*/ +bool editNew(const QString& templateName) +{ + bool result = true; + if (!templateName.isEmpty()) + { + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (cal) + { + KAEvent templateEvent = KAEvent::findTemplateName(*cal, templateName); + if (templateEvent.valid()) + { + MainWindow::executeNew(templateEvent); + return true; + } + kdWarning(5950) << "KAlarm::editNew(" << templateName << "): template not found" << endl; + } + result = false; + } + MainWindow::executeNew(); + return result; +} + +/****************************************************************************** +* Returns a list of all alarm templates. +* If shell commands are disabled, command alarm templates are omitted. +*/ +QValueList templateList() +{ + QValueList templates; + AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen(); + if (cal) + { + bool includeCmdAlarms = ShellProcess::authorised(); + KCal::Event::List events = cal->events(); + for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + KAEvent event(*kcalEvent); + if (includeCmdAlarms || event.action() != KAEvent::COMMAND) + templates.append(event); + } + } + return templates; +} + +/****************************************************************************** +* To be called after an alarm has been edited. +* Prompt the user to re-enable alarms if they are currently disabled, and if +* it's an email alarm, warn if no 'From' email address is configured. +*/ +void outputAlarmWarnings(QWidget* parent, const KAEvent* event) +{ + if (event && event->action() == KAEvent::EMAIL + && Preferences::emailAddress().isEmpty()) + KMessageBox::information(parent, i18n("Please set the 'From' email address...", + "%1\nPlease set it in the Preferences dialog.").arg(KAMail::i18n_NeedFromEmailAddress())); + + if (!Daemon::monitoringAlarms()) + { + if (KMessageBox::warningYesNo(parent, i18n("Alarms are currently disabled.\nDo you want to enable alarms now?"), + QString::null, i18n("Enable"), i18n("Keep Disabled"), + QString::fromLatin1("EditEnableAlarms")) + == KMessageBox::Yes) + Daemon::setAlarmsEnabled(); + } +} + +/****************************************************************************** +* Reset the alarm daemon and reload the calendar. +* If the daemon is not already running, start it. +*/ +void resetDaemon() +{ + kdDebug(5950) << "KAlarm::resetDaemon()" << endl; + if (!resetDaemonQueued) + { + resetDaemonQueued = true; + theApp()->processQueue(); + } +} + +/****************************************************************************** +* This method must only be called from the main KAlarm queue processing loop, +* to prevent asynchronous calendar operations interfering with one another. +* +* If resetDaemon() has been called, reset the alarm daemon and reload the calendars. +* If the daemon is not already running, start it. +*/ +void resetDaemonIfQueued() +{ + if (resetDaemonQueued) + { + kdDebug(5950) << "KAlarm::resetDaemonIfNeeded()" << endl; + AlarmCalendar::activeCalendar()->reload(); + AlarmCalendar::expiredCalendar()->reload(); + + // Close any message windows for alarms which are now disabled + KAEvent event; + KCal::Event::List events = AlarmCalendar::activeCalendar()->events(); + for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + event.set(*kcalEvent); + if (!event.enabled() && event.displayAction()) + { + MessageWin* win = MessageWin::findEvent(event.id()); + delete win; + } + } + + MainWindow::refresh(); + if (!Daemon::reset()) + Daemon::start(); + resetDaemonQueued = false; + } +} + +/****************************************************************************** +* Start KMail if it isn't already running, and optionally iconise it. +* Reply = reason for failure to run KMail (which may be the empty string) +* = null string if success. +*/ +QString runKMail(bool minimise) +{ + QCString dcopName; + QString errmsg; + if (!runProgram("kmail", (minimise ? KMAIL_DCOP_WINDOW : ""), dcopName, errmsg)) + return i18n("Unable to start KMail\n(%1)").arg(errmsg); + return QString::null; +} + +/****************************************************************************** +* Start another program for DCOP access if it isn't already running. +* If 'windowName' is not empty, the program's window of that name is iconised. +* On exit, 'dcopName' contains the DCOP name to access the application, and +* 'errorMessage' contains an error message if failure. +* Reply = true if the program is now running. +*/ +bool runProgram(const QCString& program, const QCString& windowName, QCString& dcopName, QString& errorMessage) +{ + if (!kapp->dcopClient()->isApplicationRegistered(program)) + { + // KOrganizer is not already running, so start it + if (KApplication::startServiceByDesktopName(QString::fromLatin1(program), QString::null, &errorMessage, &dcopName)) + { + kdError(5950) << "runProgram(): couldn't start " << program << " (" << errorMessage << ")\n"; + return false; + } + // Minimise its window - don't use hide() since this would remove all + // trace of it from the panel if it is not configured to be docked in + // the system tray. + kapp->dcopClient()->send(dcopName, windowName, "minimize()", QString::null); + } + else if (dcopName.isEmpty()) + dcopName = program; + errorMessage = QString::null; + return true; +} + +/****************************************************************************** +* Read the size for the specified window from the config file, for the +* current screen resolution. +* Reply = true if size set in the config file, in which case 'result' is set +* = false if no size is set, in which case 'result' is unchanged. +*/ +bool readConfigWindowSize(const char* window, QSize& result) +{ + KConfig* config = KGlobal::config(); + config->setGroup(QString::fromLatin1(window)); + QWidget* desktop = KApplication::desktop(); + QSize s = QSize(config->readNumEntry(QString::fromLatin1("Width %1").arg(desktop->width()), 0), + config->readNumEntry(QString::fromLatin1("Height %1").arg(desktop->height()), 0)); + if (s.isEmpty()) + return false; + result = s; + return true; +} + +/****************************************************************************** +* Write the size for the specified window to the config file, for the +* current screen resolution. +*/ +void writeConfigWindowSize(const char* window, const QSize& size) +{ + KConfig* config = KGlobal::config(); + config->setGroup(QString::fromLatin1(window)); + QWidget* desktop = KApplication::desktop(); + config->writeEntry(QString::fromLatin1("Width %1").arg(desktop->width()), size.width()); + config->writeEntry(QString::fromLatin1("Height %1").arg(desktop->height()), size.height()); + config->sync(); +} + +/****************************************************************************** +* Return the current KAlarm version number. +*/ +int Version() +{ + static int version = 0; + if (!version) + version = getVersionNumber(KALARM_VERSION); + return version; +} + +/****************************************************************************** +* Convert the supplied KAlarm version string to a version number. +* Reply = version number (double digit for each of major, minor & issue number, +* e.g. 010203 for 1.2.3 +* = 0 if invalid version string. +*/ +int getVersionNumber(const QString& version, QString* subVersion) +{ + // N.B. Remember to change Version(int major, int minor, int rev) + // if the representation returned by this method changes. + if (subVersion) + *subVersion = QString::null; + int count = version.contains('.') + 1; + if (count < 2) + return 0; + bool ok; + unsigned vernum = version.section('.', 0, 0).toUInt(&ok) * 10000; // major version + if (!ok) + return 0; + unsigned v = version.section('.', 1, 1).toUInt(&ok); // minor version + if (!ok) + return 0; + vernum += (v < 99 ? v : 99) * 100; + if (count >= 3) + { + // Issue number: allow other characters to follow the last digit + QString issue = version.section('.', 2); + if (!issue.at(0).isDigit()) + return 0; + int n = issue.length(); + int i; + for (i = 0; i < n && issue.at(i).isDigit(); ++i) ; + if (subVersion) + *subVersion = issue.mid(i); + v = issue.left(i).toUInt(); // issue number + vernum += (v < 99 ? v : 99); + } + return vernum; +} + +/****************************************************************************** +* Check from its mime type whether a file appears to be a text or image file. +* If a text file, its type is distinguished. +* Reply = file type. +*/ +FileType fileType(const QString& mimetype) +{ + static const char* applicationTypes[] = { + "x-shellscript", "x-nawk", "x-awk", "x-perl", "x-python", + "x-desktop", "x-troff", 0 }; + static const char* formattedTextTypes[] = { + "html", "xml", 0 }; + + if (mimetype.startsWith(QString::fromLatin1("image/"))) + return Image; + int slash = mimetype.find('/'); + if (slash < 0) + return Unknown; + QString type = mimetype.mid(slash + 1); + const char* typel = type.latin1(); + if (mimetype.startsWith(QString::fromLatin1("application"))) + { + for (int i = 0; applicationTypes[i]; ++i) + if (!strcmp(typel, applicationTypes[i])) + return TextApplication; + } + else if (mimetype.startsWith(QString::fromLatin1("text"))) + { + for (int i = 0; formattedTextTypes[i]; ++i) + if (!strcmp(typel, formattedTextTypes[i])) + return TextFormatted; + return TextPlain; + } + return Unknown; +} + +/****************************************************************************** +* Display a modal dialogue to choose an existing file, initially highlighting +* any specified file. +* @param initialFile The file to initially highlight - must be a full path name or URL. +* @param defaultDir The directory to start in if @p initialFile is empty. If empty, +* the user's home directory will be used. Updated to the +* directory containing the selected file, if a file is chosen. +* @param mode OR of KFile::Mode values, e.g. ExistingOnly, LocalOnly. +* Reply = URL selected. If none is selected, URL.isEmpty() is true. +*/ +QString browseFile(const QString& caption, QString& defaultDir, const QString& initialFile, + const QString& filter, int mode, QWidget* parent, const char* name) +{ + QString initialDir = !initialFile.isEmpty() ? QString(initialFile).remove(QRegExp("/[^/]*$")) + : !defaultDir.isEmpty() ? defaultDir + : QDir::homeDirPath(); + KFileDialog fileDlg(initialDir, filter, parent, name, true); + fileDlg.setOperationMode(mode & KFile::ExistingOnly ? KFileDialog::Opening : KFileDialog::Saving); + fileDlg.setMode(KFile::File | mode); + fileDlg.setCaption(caption); + if (!initialFile.isEmpty()) + fileDlg.setSelection(initialFile); + if (fileDlg.exec() != QDialog::Accepted) + return QString::null; + KURL url = fileDlg.selectedURL(); + defaultDir = url.path(); + return (mode & KFile::LocalOnly) ? url.path() : url.prettyURL(); +} + +/****************************************************************************** +* Return the first day of the week for the user's locale. +* Reply = 1 (Mon) .. 7 (Sun). +*/ +int localeFirstDayOfWeek() +{ + static int firstDay = 0; + if (!firstDay) + firstDay = KGlobal::locale()->weekStartDay(); + return firstDay; +} + +/****************************************************************************** +* Return the supplied string with any accelerator code stripped out. +*/ +QString stripAccel(const QString& text) +{ + unsigned len = text.length(); + QString out = QDeepCopy(text); + QChar *corig = (QChar*)out.unicode(); + QChar *cout = corig; + QChar *cin = cout; + while (len) + { + if ( *cin == '&' ) + { + ++cin; + --len; + if ( !len ) + break; + } + *cout = *cin; + ++cout; + ++cin; + --len; + } + unsigned newlen = cout - corig; + if (newlen != out.length()) + out.truncate(newlen); + return out; +} + +/****************************************************************************** +* Cancel the screen saver, in case it is active. +* Only implemented if the X11 XTest extension is installed. +*/ +void cancelScreenSaver() +{ +#ifdef HAVE_XTEST + x11_cancelScreenSaver(); +#endif // HAVE_XTEST +} + +} // namespace KAlarm + + +namespace { + +/****************************************************************************** +* Tell KOrganizer to put an alarm in its calendar. +* It will be held by KOrganizer as a simple event, without alarms - KAlarm +* is still responsible for alarming. +*/ +bool sendToKOrganizer(const KAEvent& event) +{ + KCal::Event* kcalEvent = event.event(); + QString uid = KAEvent::uid(event.id(), KAEvent::KORGANIZER); + kcalEvent->setUid(uid); + kcalEvent->clearAlarms(); + QString userEmail; + switch (event.action()) + { + case KAEvent::MESSAGE: + case KAEvent::FILE: + case KAEvent::COMMAND: + kcalEvent->setSummary(event.cleanText()); + userEmail = Preferences::emailAddress(); + break; + case KAEvent::EMAIL: + { + QString from = event.emailFromId() + ? KAMail::identityManager()->identityForUoid(event.emailFromId()).fullEmailAddr() + : Preferences::emailAddress(); + AlarmText atext; + atext.setEmail(event.emailAddresses(", "), from, QString::null, QString::null, event.emailSubject(), QString::null); + kcalEvent->setSummary(atext.displayText()); + userEmail = from; + break; + } + } + kcalEvent->setOrganizer(KCal::Person(QString::null, userEmail)); + + // Translate the event into string format + KCal::ICalFormat format; + format.setTimeZone(QString::null, false); + QString iCal = format.toICalString(kcalEvent); +kdDebug(5950)<<"Korg->"<dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "addIncidence(QString)", data, replyType, replyData) + && replyType == "bool") + { + bool result; + QDataStream reply(replyData, IO_ReadOnly); + reply >> result; + if (result) + { + kdDebug(5950) << "sendToKOrganizer(" << uid << "): success\n"; + return true; + } + } + kdError(5950) << "sendToKOrganizer(): KOrganizer addEvent(" << uid << ") dcop call failed\n"; + return false; +} + +/****************************************************************************** +* Tell KOrganizer to delete an event from its calendar. +*/ +bool deleteFromKOrganizer(const QString& eventID) +{ + if (!runKOrganizer()) // start KOrganizer if it isn't already running + return false; + QString newID = KAEvent::uid(eventID, KAEvent::KORGANIZER); + QByteArray data, replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + arg << newID << true; + if (kapp->dcopClient()->call(korganizerName, KORG_DCOP_OBJECT, "deleteIncidence(QString,bool)", data, replyType, replyData) + && replyType == "bool") + { + bool result; + QDataStream reply(replyData, IO_ReadOnly); + reply >> result; + if (result) + { + kdDebug(5950) << "deleteFromKOrganizer(" << newID << "): success\n"; + return true; + } + } + kdError(5950) << "sendToKOrganizer(): KOrganizer deleteEvent(" << newID << ") dcop call failed\n"; + return false; +} + +/****************************************************************************** +* Start KOrganizer if not already running, and create its DCOP interface. +*/ +bool runKOrganizer() +{ + QString error; + QCString dcopService; + int result = KDCOPServiceStarter::self()->findServiceFor("DCOP/Organizer", QString::null, QString::null, &error, &dcopService); + if (result) + { + kdDebug(5950) << "Unable to start DCOP/Organizer: " << dcopService << " " << error << endl; + return false; + } + // If Kontact is running, there is be a load() method which needs to be called + // to load KOrganizer into Kontact. But if KOrganizer is running independently, + // the load() method doesn't exist. + QCString dummy; + if (!kapp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", QByteArray(), dummy, dummy)) + { + DCOPRef ref(dcopService, dcopService); // talk to the KUniqueApplication or its Kontact wrapper + DCOPReply reply = ref.call("load()"); + if (!reply.isValid() || !(bool)reply) + { + kdWarning(5950) << "Error loading " << dcopService << endl; + return false; + } + if (!kapp->dcopClient()->findObject(dcopService, KORG_DCOP_OBJECT, "", QByteArray(), dummy, dummy)) + { + kdWarning(5950) << "Unable to access KOrganizer's "KORG_DCOP_OBJECT" DCOP object" << endl; + return false; + } + } + return true; +} + +} // namespace + +#ifdef HAVE_XTEST +#include +#include +#include + +/****************************************************************************** +* Cancel the screen saver, in case it is active. +* Only implemented if the X11 XTest extension is installed. +*/ +void x11_cancelScreenSaver() +{ + kdDebug(5950) << "KAlarm::cancelScreenSaver()" << endl; + Display* display = qt_xdisplay(); + static int XTestKeyCode = 0; + if (!XTestKeyCode) + XTestKeyCode = XKeysymToKeycode(display, XK_Shift_L); + XTestFakeKeyEvent(display, XTestKeyCode, true, CurrentTime); + XTestFakeKeyEvent(display, XTestKeyCode, false, CurrentTime); + XSync(display, false); +} +#endif // HAVE_XTEST diff --git a/kalarm/functions.h b/kalarm/functions.h new file mode 100644 index 000000000..c5337cc48 --- /dev/null +++ b/kalarm/functions.h @@ -0,0 +1,130 @@ +/* + * functions.h - miscellaneous functions + * Program: kalarm + * Copyright © 2004-2006,2009 by 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. + */ + +#ifndef FUNCTIONS_H +#define FUNCTIONS_H + +/** @file functions.h - miscellaneous functions */ + +#include +#include + +#include "alarmevent.h" + +class QObject; +class QWidget; +class QString; +class KAction; +class KActionCollection; +namespace KCal { class Event; } +class KAEvent; +class MainWindow; +class AlarmListView; +class TemplateListView; +class TemplateMenuAction; + +namespace KAlarm +{ + +/** Return codes from fileType() */ +enum FileType { Unknown, TextPlain, TextFormatted, TextApplication, Image }; +/** Return codes from calendar update functions. + * The codes are ordered by severity. + */ +enum UpdateStatus { + UPDATE_OK, // update succeeded + UPDATE_KORG_ERR, // update succeeded, but KOrganizer update failed + UPDATE_ERROR, // update failed partially + UPDATE_FAILED, // update failed completely + SAVE_FAILED // calendar was updated in memory, but save failed +}; +/** Error codes supplied as parameter to displayUpdateError() */ +enum UpdateError { ERR_ADD, ERR_DELETE, ERR_REACTIVATE, ERR_TEMPLATE }; +/** Error codes supplied as parameter to displayKOrgUpdateError() */ +enum KOrgUpdateError { KORG_ERR_ADD, KORG_ERR_MODIFY, KORG_ERR_DELETE }; + + +/** Display a main window with the specified event selected */ +MainWindow* displayMainWindowSelected(const QString& eventID = QString::null); +bool readConfigWindowSize(const char* window, QSize&); +void writeConfigWindowSize(const char* window, const QSize&); +/** Check from its mime type whether a file appears to be a text or image file. + * If a text file, its type is distinguished. + */ +FileType fileType(const QString& mimetype); +/** Return current KAlarm version number */ +int Version(); +inline int Version(int major, int minor, int rev) { return major*10000 + minor*100 + rev; } +int getVersionNumber(const QString& version, QString* subVersion = 0); +/** Return which version of KAlarm was the first to use the current calendar/event format */ +inline int currentCalendarVersion() { return KAEvent::calVersion(); } +inline QString currentCalendarVersionString() { return KAEvent::calVersionString(); } +QString browseFile(const QString& caption, QString& defaultDir, const QString& initialFile = QString::null, + const QString& filter = QString::null, int mode = 0, QWidget* parent = 0, const char* name = 0); +bool edit(const QString& eventID); +bool editNew(const QString& templateName = QString::null); +/** Create a "New Alarm" KAction */ +KAction* createNewAlarmAction(const QString& label, QObject* receiver, const char* slot, KActionCollection*, const char* name); +/** Create a "New From Template" KAction */ +TemplateMenuAction* createNewFromTemplateAction(const QString& label, QObject* receiver, const char* slot, KActionCollection*, const char* name); +/** Returns a list of all alarm templates. + * If shell commands are disabled, command alarm templates are omitted. + */ +QValueList templateList(); +void outputAlarmWarnings(QWidget* parent, const KAEvent* = 0); +void resetDaemon(); +void resetDaemonIfQueued(); // must only be called from KAlarmApp::processQueue() +QString runKMail(bool minimise); +bool runProgram(const QCString& program, const QCString& windowName, QCString& dcopName, QString& errorMessage); + +UpdateStatus addEvent(KAEvent&, AlarmListView* selectionView, QWidget* errmsgParent = 0, bool useEventID = false, bool allowKOrgUpdate = true); +bool addExpiredEvent(KAEvent&); +UpdateStatus addTemplate(KAEvent&, TemplateListView* selectionView, QWidget* errmsgParent = 0); +UpdateStatus modifyEvent(KAEvent& oldEvent, const KAEvent& newEvent, AlarmListView* selectionView, QWidget* errmsgParent = 0); +UpdateStatus updateEvent(KAEvent&, AlarmListView* selectionView, QWidget* errmsgParent = 0, bool archiveOnDelete = true, bool incRevision = true); +UpdateStatus updateTemplate(const KAEvent&, TemplateListView* selectionView, QWidget* errmsgParent = 0); +UpdateStatus deleteEvent(KAEvent&, bool archive = true, QWidget* errmsgParent = 0); +UpdateStatus deleteTemplate(const KAEvent&); +void deleteDisplayEvent(const QString& eventID); +UpdateStatus reactivateEvent(KAEvent&, AlarmListView* selectionView, bool useEventID = false); +UpdateStatus enableEvent(KAEvent&, AlarmListView* selectionView, bool enable); +void displayUpdateError(QWidget* parent, UpdateStatus, UpdateError, int nAlarms); +void displayKOrgUpdateError(QWidget* parent, KOrgUpdateError, int nAlarms); + +void cancelScreenSaver(); +QString stripAccel(const QString&); + +int localeFirstDayOfWeek(); + +/* Given a standard KDE day number, return the day number in the week for the user's locale. + * Standard day number = 1 (Mon) .. 7 (Sun) + * Locale day number in week = 0 .. 6 + */ +inline int weekDay_to_localeDayInWeek(int weekDay) { return (weekDay + 7 - localeFirstDayOfWeek()) % 7; } + +/* Given a day number in the week for the user's locale, return the standard KDE day number. + * 'index' = 0 .. 6 + * Standard day number = 1 (Mon) .. 7 (Sun) + */ +inline int localeDayInWeek_to_weekDay(int index) { return (index + localeFirstDayOfWeek() - 1) % 7 + 1; } + +} // namespace KAlarm + +#endif // FUNCTIONS_H diff --git a/kalarm/hi16-app-kalarm.png b/kalarm/hi16-app-kalarm.png new file mode 100644 index 000000000..72b7ece31 Binary files /dev/null and b/kalarm/hi16-app-kalarm.png differ diff --git a/kalarm/hi32-app-kalarm.png b/kalarm/hi32-app-kalarm.png new file mode 100644 index 000000000..3c5b9f065 Binary files /dev/null and b/kalarm/hi32-app-kalarm.png differ diff --git a/kalarm/hi48-app-kalarm.png b/kalarm/hi48-app-kalarm.png new file mode 100644 index 000000000..8351a1ebb Binary files /dev/null and b/kalarm/hi48-app-kalarm.png differ diff --git a/kalarm/kalarm.desktop b/kalarm/kalarm.desktop new file mode 100644 index 000000000..970847004 --- /dev/null +++ b/kalarm/kalarm.desktop @@ -0,0 +1,75 @@ +# KDE Config File +[Desktop Entry] +Name=KAlarm +Name[af]=K-alarm +Name[cy]=KLarwm +Name[eo]=Alarmilo +Name[hi]=के-अलार्म +Name[ko]=KDE 알람 +Name[mk]=КАларм +Name[ne]=केडीई संसूचक +Name[nso]=KAlamo +Name[pl]=Alarm +Name[sv]=Kalarm +Name[ta]=Kஅலாரம் +Name[th]=เตือนการนัดหมาย - K +Name[ven]=Alamu ya K +Name[zh_TW]=KAlarm 鬧鐘 +Type=Application +Exec=kalarm -caption "%c" %i +Icon=kalarm +DocPath=kalarm/index.html +GenericName=Personal Alarm Scheduler +GenericName[af]=Persoonlike alarm skeduleerder +GenericName[bg]=Аларма +GenericName[bs]=Lični alarm +GenericName[ca]=Planificador d'alarmes personals +GenericName[cs]=Osobní plánovač alarmů +GenericName[cy]=Trefnlennydd Larwm Personol +GenericName[da]=Personlig skemalægning af alarm +GenericName[de]=Persönliche Termin-Erinnerung +GenericName[el]=Προσωπικός προγραμματιστής ειδοποιήσεων +GenericName[eo]=Persona alarmplanilo +GenericName[es]=Planificador de alarmas personales +GenericName[et]=Meeldetuletuste ajastaja +GenericName[eu]=Alarma pertsonalen programatzailea +GenericName[fa]=زمان‌بند هشدار شخصی +GenericName[fi]=Henkilökohtainen hälytysajastin +GenericName[fr]=Planificateur d'alarme personnel +GenericName[fy]=Persoanlike alarmplanner +GenericName[gl]=Progamador Persoal de Alarmas +GenericName[he]=מנהל זמן אישי +GenericName[hi]=निजी अलार्म शेड्यूलर +GenericName[hu]=Emlékeztetőkezelő +GenericName[is]=Áminningakerfi +GenericName[it]=Programmatore degli avvisi personali +GenericName[ja]=個人アラームスケジューラ +GenericName[ka]=პერსონალური მაღვიძარას მგეგმავი +GenericName[kk]=Дербес ескертулер жоспарлағышы +GenericName[km]=កម្មវិធី​កំណត់​ម៉ោង​រោទ៍​ផ្ទាល់​ខ្លួន +GenericName[lt]=Asmeninių žinučių-priminimų planuoklis +GenericName[mk]=Закажување лични аларми +GenericName[ms]=Penjadual Penggera Peribadi +GenericName[nb]=Personlig varslingsplanlegger +GenericName[nds]=Persöönlich Anstöötgever +GenericName[ne]=व्यक्तिगत संसूचक अनुसूचक +GenericName[nl]=Persoonlijke alarmplanner +GenericName[nn]=Planleggar for alarmar/påminningsmeldingar +GenericName[pl]=Program przypominający o zdarzeniach +GenericName[pt]=Gestor de Alarmes Pessoal +GenericName[pt_BR]=Agendador de Alarme Pessoal +GenericName[ru]=Напоминания +GenericName[sk]=Osobný plánovač alarmov +GenericName[sl]=Razporejevalnik osebnih alarmov +GenericName[sr]=Лични планер аларма +GenericName[sr@Latn]=Lični planer alarma +GenericName[sv]=Personlig alarmschemaläggning +GenericName[tg]=Идоракунии хотиррасониҳои шахсӣ +GenericName[tr]=Kişisel Alarm +GenericName[uk]=Персональний планувальник нагадувань +GenericName[zh_CN]=个人日程提醒 +GenericName[zh_TW]=個人鬧鐘排程程式 +Terminal=false +X-DCOP-ServiceType=Unique +X-KDE-StartupNotify=true +Categories=Qt;KDE;Utility;X-KDE-Utilities-PIM;Office;Calendar; diff --git a/kalarm/kalarm.h b/kalarm/kalarm.h new file mode 100644 index 000000000..e0af27f57 --- /dev/null +++ b/kalarm/kalarm.h @@ -0,0 +1,39 @@ +/* + * kalarm.h - global header file + * Program: kalarm + * Copyright © 2001-2008 by 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. + */ + +#ifndef KALARM_H +#define KALARM_H + + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define KALARM_VERSION "1.5.5" +#define KALARM_NAME "KAlarm" + +#include + +#define AUTOSTART_BY_KALARMD // temporary fix for autostart before session restoration + +#define OLD_DCOP // retain DCOP pre-1.2 compatibility + +#endif // KALARM_H + diff --git a/kalarm/kalarm.tray.desktop b/kalarm/kalarm.tray.desktop new file mode 100644 index 000000000..d161dc67e --- /dev/null +++ b/kalarm/kalarm.tray.desktop @@ -0,0 +1,74 @@ +# KDE Config File +[Desktop Entry] +Name=KAlarm +Name[af]=K-alarm +Name[cy]=KLarwm +Name[eo]=Alarmilo +Name[hi]=के-अलार्म +Name[ko]=KDE 알람 +Name[mk]=КАларм +Name[ne]=केडीई संसूचक +Name[nso]=KAlamo +Name[pl]=Alarm +Name[sv]=Kalarm +Name[ta]=Kஅலாரம் +Name[th]=เตือนการนัดหมาย - K +Name[ven]=Alamu ya K +Name[zh_TW]=KAlarm 鬧鐘 +Exec=kalarm --tray +Icon=kalarm +Type=Application +DocPath=kalarm/index.html +Comment=Personal Alarm Scheduler: start as system tray icon +Comment[af]=Persoonlike alarm skeduleerder: begin as stelsel laai ikoon +Comment[bg]=Аларма: стартиране като икона в системния панел +Comment[bs]=Lični alarm: pokreni kao ikonu u panelu +Comment[ca]=Planificador d'alarma personal; s'inicia com a una icona a la safata del sistema +Comment[cs]=Plánovač alarmů a připomenutí: spustit v systémové liště +Comment[cy]=Trefnlennydd Larwm Personol: cychwynnwch fel eicon yn y cafn cysawd +Comment[da]=Skemalægger personlig alarm: start som statusikon. +Comment[de]=Erinnerungsfunktion im Systemabschnitt der Kontrollleiste +Comment[el]=Προσωπικός προγραμματιστής ειδοποιήσεων: εκκίνηση σαν εικονίδιο στο πλαίσιο συστήματος +Comment[es]=Programador de alarma personal: comenzar como icono de la bandeja del sistema +Comment[et]=Häirete ja meeldetuletuste ajakava: käivitamine süsteemse doki ikoonina +Comment[eu]=Alarma pertsonalen programatzailea: abiatu sistemaren bandejako ikono bezala +Comment[fa]=زمان‌بند هشدار شخصی: آغاز به عنوان شمایل سینی سیستم +Comment[fi]=Henkilökohtainen hälytysajastin: käynnistä paneelikuvake +Comment[fr]=Planificateur d'alarme personnel : démarre dans la boîte à miniatures +Comment[fy]=Persoanlike alarmplanner: begjinne yn it systeemfak +Comment[gl]=Programador persoal de alarmas: iniciar como icona na bandexa do sistema +Comment[he]=מתזמן הודעות תזכורת: הפעלה בתור סמל במגש המערכת +Comment[hi]=निजी अलार्म शेड्यूलरः तंत्र तश्तरी प्रतीक की तरह प्रारंभ हों +Comment[hu]=Az emlékeztető üzenetek megjelenítőprogramja: indítás a paneltálcában +Comment[is]=Áminningakerfi: ræsir sem táknmynd á spjaldinu +Comment[it]=Programmatore personale degli avvisi: all'avvio si mette nel vassoio di sistema +Comment[ja]=個人アラームスケジューラ: システムトレイアイコンで起動 +Comment[ka]=პესონალური მაღვიძარას მგეგმავი: სისტემურ პანელის ხატულად იწყება +Comment[kk]=Дербес ескертулер жоспарлағышы: жүйелік сөреде орналасады +Comment[km]=កម្មវិធី​កំណត់​ម៉ោង​រោទ៍​ផ្ទាល់​ខ្លួន ៖ ចាប់ផ្ដើម​ជា​រូបតំណាង​ក្នុង​ថាស​ប្រព័ន្ធ +Comment[lt]=Žinučių-priminimų planuoklis: paleisti kaip sisteminio dėklo ženkliuką +Comment[mk]=Закажување лични аларми: стартувај во сис. лента +Comment[ms]=Penjadual Penggera Peribadi: mulakan sebagai ikon dulang sistem +Comment[nb]=Tidsplanlegger for alarm- og påminnelsesbeskjeder: start som ikon i systemkurven +Comment[nds]=Anstöötgever binnen den Systeemafsnitt starten +Comment[ne]=व्यक्तिगत संसूचक अनुसूचक: प्रणाली ट्रे प्रतिमा अनुरुप सुरु गर्नुहोस् +Comment[nl]=Persoonlijke alarmplanner: opstarten in systeemvak +Comment[nn]=Personleg alarmplanleggjar. Start som ikon i systemtrauet +Comment[pl]=Program przypominający o zdarzeniach: startuje w tacce systemowej +Comment[pt]=Escalonador de Alarmes Pessoal: iniciar como ícone da bandeja +Comment[pt_BR]=Agendador de Alarme Pessoal: iniciar como um ícone na bandeja do sistema +Comment[ru]=Персональный будильник (запускается в виде значка в панели задач) +Comment[sk]=Osobný plánovač alarmov: spustiť v systémovej lište +Comment[sl]=Razporejevalnik osebnih alarmov: zaženi kot ikono sistemske vrstice +Comment[sr]=Лични планер аларма: покреће се као икона у системској касети +Comment[sr@Latn]=Lični planer alarma: pokreće se kao ikona u sistemskoj kaseti +Comment[sv]=Personlig alarmschemaläggare: starta som en ikon i systembrickan +Comment[ta]=Personal Alarm Scheduler: கணினி தட்டு சின்னமாக துவங்கு +Comment[tg]=Соати рӯимизии зангдори шахсӣ (ба намуди ишорот дар панели вазифа ба кор дароварда мешавад) +Comment[tr]=Kişisel Alarm Zamanlayıcı +Comment[uk]=Персональний планувальник нагадувань: стартує як піктограма в лотку +Comment[zh_CN]=个人日程提醒程序:以系统托盘图标启动 +Comment[zh_TW]=警示/提醒訊息排程器:以系統列圖示的方式啟動 +Terminal=false +X-KDE-autostart-phase=2 +X-KDE-autostart-condition=kalarmrc:General:AutostartTrayDummy:false diff --git a/kalarm/kalarmapp.cpp b/kalarm/kalarmapp.cpp new file mode 100644 index 000000000..b222eeece --- /dev/null +++ b/kalarm/kalarmapp.cpp @@ -0,0 +1,2187 @@ +/* + * kalarmapp.cpp - the KAlarm application object + * Program: kalarm + * Copyright © 2001-2009 by 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 "kalarm.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "alarmcalendar.h" +#include "alarmlistview.h" +#include "birthdaydlg.h" +#include "editdlg.h" +#include "daemon.h" +#include "dcophandler.h" +#include "functions.h" +#include "kamail.h" +#include "karecurrence.h" +#include "mainwindow.h" +#include "messagebox.h" +#include "messagewin.h" +#include "preferences.h" +#include "prefdlg.h" +#include "shellprocess.h" +#include "traywindow.h" +#include "kalarmapp.moc" + +#include + + +static bool convWakeTime(const QCString& timeParam, QDateTime&, bool& noTime); +static bool convInterval(const QCString& timeParam, KARecurrence::Type&, int& timeInterval, bool allowMonthYear = false); + +/****************************************************************************** +* Find the maximum number of seconds late which a late-cancel alarm is allowed +* to be. This is calculated as the alarm daemon's check interval, plus a few +* seconds leeway to cater for any timing irregularities. +*/ +static inline int maxLateness(int lateCancel) +{ + static const int LATENESS_LEEWAY = 5; + int lc = (lateCancel >= 1) ? (lateCancel - 1)*60 : 0; + return Daemon::maxTimeSinceCheck() + LATENESS_LEEWAY + lc; +} + + +KAlarmApp* KAlarmApp::theInstance = 0; +int KAlarmApp::mActiveCount = 0; +int KAlarmApp::mFatalError = 0; +QString KAlarmApp::mFatalMessage; + + +/****************************************************************************** +* Construct the application. +*/ +KAlarmApp::KAlarmApp() + : KUniqueApplication(), + mInitialised(false), + mDcopHandler(new DcopHandler()), +#ifdef OLD_DCOP + mDcopHandlerOld(new DcopHandlerOld()), +#endif + mTrayWindow(0), + mPendingQuit(false), + mProcessingQueue(false), + mCheckingSystemTray(false), + mSessionClosingDown(false), + mRefreshExpiredAlarms(false), + mSpeechEnabled(false) +{ + Preferences::initialise(); + Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPreferencesChanged())); + KCal::CalFormat::setApplication(aboutData()->programName(), AlarmCalendar::icalProductId()); + KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); + + // Check if the system tray is supported by this window manager + mHaveSystemTray = true; // assume yes in lieu of a test which works + + if (AlarmCalendar::initialiseCalendars()) + { + connect(AlarmCalendar::expiredCalendar(), SIGNAL(purged()), SLOT(slotExpiredPurged())); + + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + mNoSystemTray = config->readBoolEntry(QString::fromLatin1("NoSystemTray"), false); + mSavedNoSystemTray = mNoSystemTray; + mOldRunInSystemTray = wantRunInSystemTray(); + mDisableAlarmsIfStopped = mOldRunInSystemTray && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); + mStartOfDay = Preferences::startOfDay(); + if (Preferences::hasStartOfDayChanged()) + mStartOfDay.setHMS(100,0,0); // start of day time has changed: flag it as invalid + DateTime::setStartOfDay(mStartOfDay); + mPrefsExpiredColour = Preferences::expiredColour(); + mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); + } + + // Check if the speech synthesis daemon is installed + mSpeechEnabled = (KTrader::self()->query("DCOP/Text-to-Speech", "Name == 'KTTSD'").count() > 0); + if (!mSpeechEnabled) + kdDebug(5950) << "KAlarmApp::KAlarmApp(): speech synthesis disabled (KTTSD not found)" << endl; + // Check if KOrganizer is installed + QString korg = QString::fromLatin1("korganizer"); + mKOrganizerEnabled = !locate("exe", korg).isNull() || !KStandardDirs::findExe(korg).isNull(); + if (!mKOrganizerEnabled) + kdDebug(5950) << "KAlarmApp::KAlarmApp(): KOrganizer options disabled (KOrganizer not found)" << endl; +} + +/****************************************************************************** +*/ +KAlarmApp::~KAlarmApp() +{ + while (!mCommandProcesses.isEmpty()) + { + ProcData* pd = mCommandProcesses.first(); + mCommandProcesses.pop_front(); + delete pd; + } + AlarmCalendar::terminateCalendars(); +} + +/****************************************************************************** +* Return the one and only KAlarmApp instance. +* If it doesn't already exist, it is created first. +*/ +KAlarmApp* KAlarmApp::getInstance() +{ + if (!theInstance) + { + theInstance = new KAlarmApp; + + if (mFatalError) + theInstance->quitFatal(); + else + { + // This is here instead of in the constructor to avoid recursion + Daemon::initialise(); // calendars must be initialised before calling this + } + } + return theInstance; +} + +/****************************************************************************** +* Restore the saved session if required. +*/ +bool KAlarmApp::restoreSession() +{ + if (!isRestored()) + return false; + if (mFatalError) + { + quitFatal(); + return false; + } + + // Process is being restored by session management. + kdDebug(5950) << "KAlarmApp::restoreSession(): Restoring\n"; + ++mActiveCount; + if (!initCheck(true)) // open the calendar file (needed for main windows) + { + --mActiveCount; + quitIf(1, true); // error opening the main calendar - quit + return true; + } + MainWindow* trayParent = 0; + for (int i = 1; KMainWindow::canBeRestored(i); ++i) + { + QString type = KMainWindow::classNameOfToplevel(i); + if (type == QString::fromLatin1("MainWindow")) + { + MainWindow* win = MainWindow::create(true); + win->restore(i, false); + if (win->isHiddenTrayParent()) + trayParent = win; + else + win->show(); + } + else if (type == QString::fromLatin1("MessageWin")) + { + MessageWin* win = new MessageWin; + win->restore(i, false); + if (win->isValid()) + win->show(); + else + delete win; + } + } + initCheck(); // register with the alarm daemon + + // Try to display the system tray icon if it is configured to be autostarted, + // or if we're in run-in-system-tray mode. + if (Preferences::autostartTrayIcon() + || MainWindow::count() && wantRunInSystemTray()) + { + displayTrayIcon(true, trayParent); + // Occasionally for no obvious reason, the main main window is + // shown when it should be hidden, so hide it just to be sure. + if (trayParent) + trayParent->hide(); + } + + --mActiveCount; + quitIf(0); // quit if no windows are open + return true; +} + +/****************************************************************************** +* Called for a KUniqueApplication when a new instance of the application is +* started. +*/ +int KAlarmApp::newInstance() +{ + kdDebug(5950)<<"KAlarmApp::newInstance()\n"; + if (mFatalError) + { + quitFatal(); + return 1; + } + ++mActiveCount; + int exitCode = 0; // default = success + static bool firstInstance = true; + bool dontRedisplay = false; + if (!firstInstance || !isRestored()) + { + QString usage; + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + + // Use a 'do' loop which is executed only once to allow easy error exits. + // Errors use 'break' to skip to the end of the function. + + // Note that DCOP handling is only set up once the command line parameters + // have been checked, since we mustn't register with the alarm daemon only + // to quit immediately afterwards. + do + { + #define USAGE(message) { usage = message; break; } + if (args->isSet("stop")) + { + // Stop the alarm daemon + kdDebug(5950)<<"KAlarmApp::newInstance(): stop\n"; + args->clear(); // free up memory + if (!Daemon::stop()) + { + exitCode = 1; + break; + } + dontRedisplay = true; // exit program if no other instances running + } + else + if (args->isSet("reset")) + { + // Reset the alarm daemon, if it's running. + // (If it's not running, it will reset automatically when it eventually starts.) + kdDebug(5950)<<"KAlarmApp::newInstance(): reset\n"; + args->clear(); // free up memory + Daemon::reset(); + dontRedisplay = true; // exit program if no other instances running + } + else + if (args->isSet("tray")) + { + // Display only the system tray icon + kdDebug(5950)<<"KAlarmApp::newInstance(): tray\n"; + args->clear(); // free up memory + if (!mHaveSystemTray) + { + exitCode = 1; + break; + } + if (!initCheck()) // open the calendar, register with daemon + { + exitCode = 1; + break; + } + if (!displayTrayIcon(true)) + { + exitCode = 1; + break; + } + } + else + if (args->isSet("handleEvent") || args->isSet("triggerEvent") || args->isSet("cancelEvent") || args->isSet("calendarURL")) + { + // Display or delete the event with the specified event ID + kdDebug(5950)<<"KAlarmApp::newInstance(): handle event\n"; + EventFunc function = EVENT_HANDLE; + int count = 0; + const char* option = 0; + if (args->isSet("handleEvent")) { function = EVENT_HANDLE; option = "handleEvent"; ++count; } + if (args->isSet("triggerEvent")) { function = EVENT_TRIGGER; option = "triggerEvent"; ++count; } + if (args->isSet("cancelEvent")) { function = EVENT_CANCEL; option = "cancelEvent"; ++count; } + if (!count) + USAGE(i18n("%1 requires %2, %3 or %4").arg(QString::fromLatin1("--calendarURL")).arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent"))) + if (count > 1) + USAGE(i18n("%1, %2, %3 mutually exclusive").arg(QString::fromLatin1("--handleEvent")).arg(QString::fromLatin1("--triggerEvent")).arg(QString::fromLatin1("--cancelEvent"))); + if (!initCheck(true)) // open the calendar, don't register with daemon yet + { + exitCode = 1; + break; + } + if (args->isSet("calendarURL")) + { + QString calendarUrl = args->getOption("calendarURL"); + if (KURL(calendarUrl).url() != AlarmCalendar::activeCalendar()->urlString()) + USAGE(i18n("%1: wrong calendar file").arg(QString::fromLatin1("--calendarURL"))) + } + QString eventID = args->getOption(option); + args->clear(); // free up memory + if (eventID.startsWith(QString::fromLatin1("ad:"))) + { + // It's a notification from the alarm deamon + eventID = eventID.mid(3); + Daemon::queueEvent(eventID); + } + setUpDcop(); // start processing DCOP calls + if (!handleEvent(eventID, function)) + { + exitCode = 1; + break; + } + } + else + if (args->isSet("edit")) + { + QString eventID = args->getOption("edit"); + if (!initCheck()) + { + exitCode = 1; + break; + } + if (!KAlarm::edit(eventID)) + { + USAGE(i18n("%1: Event %2 not found, or not editable").arg(QString::fromLatin1("--edit")).arg(eventID)) + exitCode = 1; + break; + } + } + else + if (args->isSet("edit-new") || args->isSet("edit-new-preset")) + { + QString templ; + if (args->isSet("edit-new-preset")) + templ = args->getOption("edit-new-preset"); + if (!initCheck()) + { + exitCode = 1; + break; + } + KAlarm::editNew(templ); + } + else + if (args->isSet("file") || args->isSet("exec") || args->isSet("mail") || args->count()) + { + // Display a message or file, execute a command, or send an email + KAEvent::Action action = KAEvent::MESSAGE; + QCString alMessage; + uint alFromID = 0; + EmailAddressList alAddresses; + QStringList alAttachments; + QCString alSubject; + if (args->isSet("file")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): file\n"; + if (args->isSet("exec")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--exec")).arg(QString::fromLatin1("--file"))) + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--file"))) + if (args->count()) + USAGE(i18n("message incompatible with %1").arg(QString::fromLatin1("--file"))) + alMessage = args->getOption("file"); + action = KAEvent::FILE; + } + else if (args->isSet("exec")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): exec\n"; + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--mail")).arg(QString::fromLatin1("--exec"))) + alMessage = args->getOption("exec"); + int n = args->count(); + for (int i = 0; i < n; ++i) + { + alMessage += ' '; + alMessage += args->arg(i); + } + action = KAEvent::COMMAND; + } + else if (args->isSet("mail")) + { + kdDebug(5950)<<"KAlarmApp::newInstance(): mail\n"; + if (args->isSet("subject")) + alSubject = args->getOption("subject"); + if (args->isSet("from-id")) + alFromID = KAMail::identityUoid(args->getOption("from-id")); + QCStringList params = args->getOptionList("mail"); + for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) + { + QString addr = QString::fromLocal8Bit(*i); + if (!KAMail::checkAddress(addr)) + USAGE(i18n("%1: invalid email address").arg(QString::fromLatin1("--mail"))) + alAddresses += KCal::Person(QString::null, addr); + } + params = args->getOptionList("attach"); + for (QCStringList::Iterator i = params.begin(); i != params.end(); ++i) + alAttachments += QString::fromLocal8Bit(*i); + alMessage = args->arg(0); + action = KAEvent::EMAIL; + } + else + { + kdDebug(5950)<<"KAlarmApp::newInstance(): message\n"; + alMessage = args->arg(0); + } + + if (action != KAEvent::EMAIL) + { + if (args->isSet("subject")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--subject")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("from-id")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--from-id")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("attach")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--attach")).arg(QString::fromLatin1("--mail"))) + if (args->isSet("bcc")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--bcc")).arg(QString::fromLatin1("--mail"))) + } + + bool alarmNoTime = false; + QDateTime alarmTime, endTime; + QColor bgColour = Preferences::defaultBgColour(); + QColor fgColour = Preferences::defaultFgColour(); + KARecurrence recurrence; + int repeatCount = 0; + int repeatInterval = 0; + if (args->isSet("color")) + { + // Background colour is specified + QCString colourText = args->getOption("color"); + if (static_cast(colourText)[0] == '0' + && tolower(static_cast(colourText)[1]) == 'x') + colourText.replace(0, 2, "#"); + bgColour.setNamedColor(colourText); + if (!bgColour.isValid()) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--color"))) + } + if (args->isSet("colorfg")) + { + // Foreground colour is specified + QCString colourText = args->getOption("colorfg"); + if (static_cast(colourText)[0] == '0' + && tolower(static_cast(colourText)[1]) == 'x') + colourText.replace(0, 2, "#"); + fgColour.setNamedColor(colourText); + if (!fgColour.isValid()) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--colorfg"))) + } + + if (args->isSet("time")) + { + QCString dateTime = args->getOption("time"); + if (!convWakeTime(dateTime, alarmTime, alarmNoTime)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--time"))) + } + else + alarmTime = QDateTime::currentDateTime(); + + bool haveRecurrence = args->isSet("recurrence"); + if (haveRecurrence) + { + if (args->isSet("login")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--recurrence"))) + if (args->isSet("until")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--recurrence"))) + QCString rule = args->getOption("recurrence"); + recurrence.set(QString::fromLocal8Bit(static_cast(rule))); + } + if (args->isSet("interval")) + { + // Repeat count is specified + int count; + if (args->isSet("login")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--login")).arg(QString::fromLatin1("--interval"))) + bool ok; + if (args->isSet("repeat")) + { + count = args->getOption("repeat").toInt(&ok); + if (!ok || !count || count < -1 || (count < 0 && haveRecurrence)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--repeat"))) + } + else if (haveRecurrence) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat"))) + else if (args->isSet("until")) + { + count = 0; + QCString dateTime = args->getOption("until"); + if (!convWakeTime(dateTime, endTime, alarmNoTime)) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--until"))) + if (endTime < alarmTime) + USAGE(i18n("%1 earlier than %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--time"))) + } + else + count = -1; + + // Get the recurrence interval + int interval; + KARecurrence::Type recurType; + if (!convInterval(args->getOption("interval"), recurType, interval, !haveRecurrence) + || interval < 0) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--interval"))) + if (alarmNoTime && recurType == KARecurrence::MINUTELY) + USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(QString::fromLatin1("--interval"))) + + if (haveRecurrence) + { + // There is a also a recurrence specified, so set up a sub-repetition + int longestInterval = recurrence.longestInterval(); + if (count * interval > longestInterval) + USAGE(i18n("Invalid %1 and %2 parameters: repetition is longer than %3 interval").arg(QString::fromLatin1("--interval")).arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--recurrence"))); + repeatCount = count; + repeatInterval = interval; + } + else + { + // There is no other recurrence specified, so convert the repetition + // parameters into a KCal::Recurrence + recurrence.set(recurType, interval, count, DateTime(alarmTime, alarmNoTime), endTime); + } + } + else + { + if (args->isSet("repeat")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--repeat")).arg(QString::fromLatin1("--interval"))) + if (args->isSet("until")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--until")).arg(QString::fromLatin1("--interval"))) + } + + QCString audioFile; + float audioVolume = -1; +#ifdef WITHOUT_ARTS + bool audioRepeat = false; +#else + bool audioRepeat = args->isSet("play-repeat"); +#endif + if (audioRepeat || args->isSet("play")) + { + // Play a sound with the alarm + if (audioRepeat && args->isSet("play")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat"))) + if (args->isSet("beep")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) + if (args->isSet("speak")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--speak")).arg(QString::fromLatin1(audioRepeat ? "--play-repeat" : "--play"))) + audioFile = args->getOption(audioRepeat ? "play-repeat" : "play"); +#ifndef WITHOUT_ARTS + if (args->isSet("volume")) + { + bool ok; + int volumepc = args->getOption("volume").toInt(&ok); + if (!ok || volumepc < 0 || volumepc > 100) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("--volume"))) + audioVolume = static_cast(volumepc) / 100; + } +#endif + } +#ifndef WITHOUT_ARTS + else if (args->isSet("volume")) + USAGE(i18n("%1 requires %2 or %3").arg(QString::fromLatin1("--volume")).arg(QString::fromLatin1("--play")).arg(QString::fromLatin1("--play-repeat"))) +#endif + if (args->isSet("speak")) + { + if (args->isSet("beep")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--beep")).arg(QString::fromLatin1("--speak"))) + if (!mSpeechEnabled) + USAGE(i18n("%1 requires speech synthesis to be configured using KTTSD").arg(QString::fromLatin1("--speak"))) + } + int reminderMinutes = 0; + bool onceOnly = args->isSet("reminder-once"); + if (args->isSet("reminder") || onceOnly) + { + // Issue a reminder alarm in advance of the main alarm + if (onceOnly && args->isSet("reminder")) + USAGE(i18n("%1 incompatible with %2").arg(QString::fromLatin1("--reminder")).arg(QString::fromLatin1("--reminder-once"))) + QString opt = onceOnly ? QString::fromLatin1("--reminder-once") : QString::fromLatin1("--reminder"); + if (args->isSet("exec")) + USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--exec"))) + if (args->isSet("mail")) + USAGE(i18n("%1 incompatible with %2").arg(opt).arg(QString::fromLatin1("--mail"))) + KARecurrence::Type recurType; + QString optval = args->getOption(onceOnly ? "reminder-once" : "reminder"); + if (!convInterval(args->getOption(onceOnly ? "reminder-once" : "reminder"), recurType, reminderMinutes)) + USAGE(i18n("Invalid %1 parameter").arg(opt)) + if (recurType == KARecurrence::MINUTELY && alarmNoTime) + USAGE(i18n("Invalid %1 parameter for date-only alarm").arg(opt)) + } + + int lateCancel = 0; + if (args->isSet("late-cancel")) + { + KARecurrence::Type recurType; + bool ok = convInterval(args->getOption("late-cancel"), recurType, lateCancel); + if (!ok || lateCancel <= 0) + USAGE(i18n("Invalid %1 parameter").arg(QString::fromLatin1("late-cancel"))) + } + else if (args->isSet("auto-close")) + USAGE(i18n("%1 requires %2").arg(QString::fromLatin1("--auto-close")).arg(QString::fromLatin1("--late-cancel"))) + + int flags = KAEvent::DEFAULT_FONT; + if (args->isSet("ack-confirm")) + flags |= KAEvent::CONFIRM_ACK; + if (args->isSet("auto-close")) + flags |= KAEvent::AUTO_CLOSE; + if (args->isSet("beep")) + flags |= KAEvent::BEEP; + if (args->isSet("speak")) + flags |= KAEvent::SPEAK; + if (args->isSet("korganizer")) + flags |= KAEvent::COPY_KORGANIZER; + if (args->isSet("disable")) + flags |= KAEvent::DISABLED; + if (audioRepeat) + flags |= KAEvent::REPEAT_SOUND; + if (args->isSet("login")) + flags |= KAEvent::REPEAT_AT_LOGIN; + if (args->isSet("bcc")) + flags |= KAEvent::EMAIL_BCC; + if (alarmNoTime) + flags |= KAEvent::ANY_TIME; + args->clear(); // free up memory + + // Display or schedule the event + if (!initCheck()) + { + exitCode = 1; + break; + } + if (!scheduleEvent(action, alMessage, alarmTime, lateCancel, flags, bgColour, fgColour, QFont(), audioFile, + audioVolume, reminderMinutes, recurrence, repeatInterval, repeatCount, + alFromID, alAddresses, alSubject, alAttachments)) + { + exitCode = 1; + break; + } + } + else + { + // No arguments - run interactively & display the main window + kdDebug(5950)<<"KAlarmApp::newInstance(): interactive\n"; + if (args->isSet("ack-confirm")) + usage += QString::fromLatin1("--ack-confirm "); + if (args->isSet("attach")) + usage += QString::fromLatin1("--attach "); + if (args->isSet("auto-close")) + usage += QString::fromLatin1("--auto-close "); + if (args->isSet("bcc")) + usage += QString::fromLatin1("--bcc "); + if (args->isSet("beep")) + usage += QString::fromLatin1("--beep "); + if (args->isSet("color")) + usage += QString::fromLatin1("--color "); + if (args->isSet("colorfg")) + usage += QString::fromLatin1("--colorfg "); + if (args->isSet("disable")) + usage += QString::fromLatin1("--disable "); + if (args->isSet("from-id")) + usage += QString::fromLatin1("--from-id "); + if (args->isSet("korganizer")) + usage += QString::fromLatin1("--korganizer "); + if (args->isSet("late-cancel")) + usage += QString::fromLatin1("--late-cancel "); + if (args->isSet("login")) + usage += QString::fromLatin1("--login "); + if (args->isSet("play")) + usage += QString::fromLatin1("--play "); +#ifndef WITHOUT_ARTS + if (args->isSet("play-repeat")) + usage += QString::fromLatin1("--play-repeat "); +#endif + if (args->isSet("reminder")) + usage += QString::fromLatin1("--reminder "); + if (args->isSet("reminder-once")) + usage += QString::fromLatin1("--reminder-once "); + if (args->isSet("speak")) + usage += QString::fromLatin1("--speak "); + if (args->isSet("subject")) + usage += QString::fromLatin1("--subject "); + if (args->isSet("time")) + usage += QString::fromLatin1("--time "); +#ifndef WITHOUT_ARTS + if (args->isSet("volume")) + usage += QString::fromLatin1("--volume "); +#endif + if (!usage.isEmpty()) + { + usage += i18n(": option(s) only valid with a message/%1/%2").arg(QString::fromLatin1("--file")).arg(QString::fromLatin1("--exec")); + break; + } + + args->clear(); // free up memory + if (!initCheck()) + { + exitCode = 1; + break; + } + + (MainWindow::create())->show(); + } + } while (0); // only execute once + + if (!usage.isEmpty()) + { + // Note: we can't use args->usage() since that also quits any other + // running 'instances' of the program. + std::cerr << usage.local8Bit().data() + << i18n("\nUse --help to get a list of available command line options.\n").local8Bit().data(); + exitCode = 1; + } + } + if (firstInstance && !dontRedisplay && !exitCode) + redisplayAlarms(); + + --mActiveCount; + firstInstance = false; + + // Quit the application if this was the last/only running "instance" of the program. + // Executing 'return' doesn't work very well since the program continues to + // run if no windows were created. + quitIf(exitCode); + return exitCode; +} + +/****************************************************************************** +* Quit the program, optionally only if there are no more "instances" running. +*/ +void KAlarmApp::quitIf(int exitCode, bool force) +{ + if (force) + { + // Quit regardless, except for message windows + MainWindow::closeAll(); + displayTrayIcon(false); + if (MessageWin::instanceCount()) + return; + } + else + { + // Quit only if there are no more "instances" running + mPendingQuit = false; + if (mActiveCount > 0 || MessageWin::instanceCount()) + return; + int mwcount = MainWindow::count(); + MainWindow* mw = mwcount ? MainWindow::firstWindow() : 0; + if (mwcount > 1 || mwcount && (!mw->isHidden() || !mw->isTrayParent())) + return; + // There are no windows left except perhaps a main window which is a hidden tray icon parent + if (mTrayWindow) + { + // There is a system tray icon. + // Don't exit unless the system tray doesn't seem to exist. + if (checkSystemTray()) + return; + } + if (!mDcopQueue.isEmpty() || !mCommandProcesses.isEmpty()) + { + // Don't quit yet if there are outstanding actions on the DCOP queue + mPendingQuit = true; + mPendingQuitCode = exitCode; + return; + } + } + + // This was the last/only running "instance" of the program, so exit completely. + kdDebug(5950) << "KAlarmApp::quitIf(" << exitCode << "): quitting" << endl; + BirthdayDlg::close(); + exit(exitCode); +} + +/****************************************************************************** +* Called when the Quit menu item is selected. +* Closes the system tray window and all main windows, but does not exit the +* program if other windows are still open. +*/ +void KAlarmApp::doQuit(QWidget* parent) +{ + kdDebug(5950) << "KAlarmApp::doQuit()\n"; + if (mDisableAlarmsIfStopped + && MessageBox::warningContinueCancel(parent, KMessageBox::Cancel, + i18n("Quitting will disable alarms\n(once any alarm message windows are closed)."), + QString::null, KStdGuiItem::quit(), Preferences::QUIT_WARN + ) != KMessageBox::Yes) + return; + quitIf(0, true); +} + +/****************************************************************************** +* Called when the session manager is about to close down the application. +*/ +void KAlarmApp::commitData(QSessionManager& sm) +{ + mSessionClosingDown = true; + KUniqueApplication::commitData(sm); + mSessionClosingDown = false; // reset in case shutdown is cancelled +} + +/****************************************************************************** +* Display an error message for a fatal error. Prevent further actions since +* the program state is unsafe. +*/ +void KAlarmApp::displayFatalError(const QString& message) +{ + if (!mFatalError) + { + mFatalError = 1; + mFatalMessage = message; + if (theInstance) + QTimer::singleShot(0, theInstance, SLOT(quitFatal())); + } +} + +/****************************************************************************** +* Quit the program, once the fatal error message has been acknowledged. +*/ +void KAlarmApp::quitFatal() +{ + switch (mFatalError) + { + case 0: + case 2: + return; + case 1: + mFatalError = 2; + KMessageBox::error(0, mFatalMessage); + mFatalError = 3; + // fall through to '3' + case 3: + if (theInstance) + theInstance->quitIf(1, true); + break; + } + QTimer::singleShot(1000, this, SLOT(quitFatal())); +} + +/****************************************************************************** +* The main processing loop for KAlarm. +* All KAlarm operations involving opening or updating calendar files are called +* from this loop to ensure that only one operation is active at any one time. +* This precaution is necessary because KAlarm's activities are mostly +* asynchronous, being in response to DCOP calls from the alarm daemon (or other +* programs) or timer events, any of which can be received in the middle of +* performing another operation. If a calendar file is opened or updated while +* another calendar operation is in progress, the program has been observed to +* hang, or the first calendar call has failed with data loss - clearly +* unacceptable!! +*/ +void KAlarmApp::processQueue() +{ + if (mInitialised && !mProcessingQueue) + { + kdDebug(5950) << "KAlarmApp::processQueue()\n"; + mProcessingQueue = true; + + // Reset the alarm daemon if it's been queued + KAlarm::resetDaemonIfQueued(); + + // Process DCOP calls + while (!mDcopQueue.isEmpty()) + { + DcopQEntry& entry = mDcopQueue.first(); + if (entry.eventId.isEmpty()) + { + // It's a new alarm + switch (entry.function) + { + case EVENT_TRIGGER: + execAlarm(entry.event, entry.event.firstAlarm(), false); + break; + case EVENT_HANDLE: + KAlarm::addEvent(entry.event, 0); + break; + case EVENT_CANCEL: + break; + } + } + else + handleEvent(entry.eventId, entry.function); + mDcopQueue.pop_front(); + } + + // Purge the expired alarms calendar if it's time to do so + AlarmCalendar::expiredCalendar()->purgeIfQueued(); + + // Now that the queue has been processed, quit if a quit was queued + if (mPendingQuit) + quitIf(mPendingQuitCode); + + mProcessingQueue = false; + } +} + +/****************************************************************************** +* Redisplay alarms which were being shown when the program last exited. +* Normally, these alarms will have been displayed by session restoration, but +* if the program crashed or was killed, we can redisplay them here so that +* they won't be lost. +*/ +void KAlarmApp::redisplayAlarms() +{ + AlarmCalendar* cal = AlarmCalendar::displayCalendar(); + if (cal->isOpen()) + { + KCal::Event::List events = cal->events(); + for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it) + { + KCal::Event* kcalEvent = *it; + KAEvent event(*kcalEvent); + event.setUid(KAEvent::ACTIVE); + if (!MessageWin::findEvent(event.id())) + { + // This event should be displayed, but currently isn't being + kdDebug(5950) << "KAlarmApp::redisplayAlarms(): " << event.id() << endl; + KAAlarm alarm = event.convertDisplayingAlarm(); + (new MessageWin(event, alarm, false, !alarm.repeatAtLogin()))->show(); + } + } + } +} + +/****************************************************************************** +* Called when the system tray main window is closed. +*/ +void KAlarmApp::removeWindow(TrayWindow*) +{ + mTrayWindow = 0; + quitIf(); +} + +/****************************************************************************** +* Display or close the system tray icon. +*/ +bool KAlarmApp::displayTrayIcon(bool show, MainWindow* parent) +{ + static bool creating = false; + if (show) + { + if (!mTrayWindow && !creating) + { + if (!mHaveSystemTray) + return false; + if (!MainWindow::count() && wantRunInSystemTray()) + { + creating = true; // prevent main window constructor from creating an additional tray icon + parent = MainWindow::create(); + creating = false; + } + mTrayWindow = new TrayWindow(parent ? parent : MainWindow::firstWindow()); + connect(mTrayWindow, SIGNAL(deleted()), SIGNAL(trayIconToggled())); + mTrayWindow->show(); + emit trayIconToggled(); + + // Set up a timer so that we can check after all events in the window system's + // event queue have been processed, whether the system tray actually exists + mCheckingSystemTray = true; + mSavedNoSystemTray = mNoSystemTray; + mNoSystemTray = false; + QTimer::singleShot(0, this, SLOT(slotSystemTrayTimer())); + } + } + else if (mTrayWindow) + { + delete mTrayWindow; + mTrayWindow = 0; + } + return true; +} + +/****************************************************************************** +* Called by a timer to check whether the system tray icon has been housed in +* the system tray. Because there is a delay between the system tray icon show +* event and the icon being reparented by the system tray, we have to use a +* timer to check whether the system tray has actually grabbed it, or whether +* the system tray probably doesn't exist. +*/ +void KAlarmApp::slotSystemTrayTimer() +{ + mCheckingSystemTray = false; + if (!checkSystemTray()) + quitIf(0); // exit the application if there are no open windows +} + +/****************************************************************************** +* Check whether the system tray icon has been housed in the system tray. +* If the system tray doesn't seem to exist, tell the alarm daemon to notify us +* of alarms regardless of whether we're running. +*/ +bool KAlarmApp::checkSystemTray() +{ + if (mCheckingSystemTray || !mTrayWindow) + return true; + if (mTrayWindow->inSystemTray() != !mSavedNoSystemTray) + { + kdDebug(5950) << "KAlarmApp::checkSystemTray(): changed -> " << mSavedNoSystemTray << endl; + mNoSystemTray = mSavedNoSystemTray = !mSavedNoSystemTray; + + // Store the new setting in the config file, so that if KAlarm exits and is then + // next activated by the daemon to display a message, it will register with the + // daemon with the correct NOTIFY type. If that happened when there was no system + // tray and alarms are disabled when KAlarm is not running, registering with + // NO_START_NOTIFY could result in alarms never being seen. + KConfig* config = kapp->config(); + config->setGroup(QString::fromLatin1("General")); + config->writeEntry(QString::fromLatin1("NoSystemTray"), mNoSystemTray); + config->sync(); + + // Update other settings and reregister with the alarm daemon + slotPreferencesChanged(); + } + else + { + kdDebug(5950) << "KAlarmApp::checkSystemTray(): no change = " << !mSavedNoSystemTray << endl; + mNoSystemTray = mSavedNoSystemTray; + } + return !mNoSystemTray; +} + +/****************************************************************************** +* Return the main window associated with the system tray icon. +*/ +MainWindow* KAlarmApp::trayMainWindow() const +{ + return mTrayWindow ? mTrayWindow->assocMainWindow() : 0; +} + +/****************************************************************************** +* Called when KAlarm preferences have changed. +*/ +void KAlarmApp::slotPreferencesChanged() +{ + bool newRunInSysTray = wantRunInSystemTray(); + if (newRunInSysTray != mOldRunInSystemTray) + { + // The system tray run mode has changed + ++mActiveCount; // prevent the application from quitting + MainWindow* win = mTrayWindow ? mTrayWindow->assocMainWindow() : 0; + delete mTrayWindow; // remove the system tray icon if it is currently shown + mTrayWindow = 0; + mOldRunInSystemTray = newRunInSysTray; + if (!newRunInSysTray) + { + if (win && win->isHidden()) + delete win; + } + displayTrayIcon(true); + --mActiveCount; + } + + bool newDisableIfStopped = wantRunInSystemTray() && !mNoSystemTray && Preferences::disableAlarmsIfStopped(); + if (newDisableIfStopped != mDisableAlarmsIfStopped) + { + mDisableAlarmsIfStopped = newDisableIfStopped; // N.B. this setting is used by Daemon::reregister() + Preferences::setQuitWarn(true); // since mode has changed, re-allow warning messages on Quit + Daemon::reregister(); // re-register with the alarm daemon + } + + // Change alarm times for date-only alarms if the start of day time has changed + if (Preferences::startOfDay() != mStartOfDay) + changeStartOfDay(); + + // In case the date for February 29th recurrences has changed + KARecurrence::setDefaultFeb29Type(Preferences::defaultFeb29Type()); + + if (Preferences::expiredColour() != mPrefsExpiredColour) + { + // The expired alarms text colour has changed + mRefreshExpiredAlarms = true; + mPrefsExpiredColour = Preferences::expiredColour(); + } + + if (Preferences::expiredKeepDays() != mPrefsExpiredKeepDays) + { + // How long expired alarms are being kept has changed. + // N.B. This also adjusts for any change in start-of-day time. + mPrefsExpiredKeepDays = Preferences::expiredKeepDays(); + AlarmCalendar::expiredCalendar()->setPurgeDays(mPrefsExpiredKeepDays); + } + + if (mRefreshExpiredAlarms) + { + mRefreshExpiredAlarms = false; + MainWindow::updateExpired(); + } +} + +/****************************************************************************** +* Change alarm times for date-only alarms after the start of day time has changed. +*/ +void KAlarmApp::changeStartOfDay() +{ + Daemon::notifyTimeChanged(); // tell the alarm daemon the new time + QTime sod = Preferences::startOfDay(); + DateTime::setStartOfDay(sod); + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (KAEvent::adjustStartOfDay(cal->events())) + cal->save(); + Preferences::updateStartOfDayCheck(); // now that calendar is updated, set OK flag in config file + mStartOfDay = sod; +} + +/****************************************************************************** +* Called when the expired alarms calendar has been purged. +* Updates the alarm list in all main windows. +*/ +void KAlarmApp::slotExpiredPurged() +{ + mRefreshExpiredAlarms = false; + MainWindow::updateExpired(); +} + +/****************************************************************************** +* Return whether the program is configured to be running in the system tray. +*/ +bool KAlarmApp::wantRunInSystemTray() const +{ + return Preferences::runInSystemTray() && mHaveSystemTray; +} + +/****************************************************************************** +* Called to schedule a new alarm, either in response to a DCOP notification or +* to command line options. +* Reply = true unless there was a parameter error or an error opening calendar file. +*/ +bool KAlarmApp::scheduleEvent(KAEvent::Action action, const QString& text, const QDateTime& dateTime, + int lateCancel, int flags, const QColor& bg, const QColor& fg, const QFont& font, + const QString& audioFile, float audioVolume, int reminderMinutes, + const KARecurrence& recurrence, int repeatInterval, int repeatCount, + uint mailFromID, const EmailAddressList& mailAddresses, + const QString& mailSubject, const QStringList& mailAttachments) +{ + kdDebug(5950) << "KAlarmApp::scheduleEvent(): " << text << endl; + if (!dateTime.isValid()) + return false; + QDateTime now = QDateTime::currentDateTime(); + if (lateCancel && dateTime < now.addSecs(-maxLateness(lateCancel))) + return true; // alarm time was already expired too long ago + QDateTime alarmTime = dateTime; + // Round down to the nearest minute to avoid scheduling being messed up + alarmTime.setTime(QTime(alarmTime.time().hour(), alarmTime.time().minute(), 0)); + + KAEvent event(alarmTime, text, bg, fg, font, action, lateCancel, flags); + if (reminderMinutes) + { + bool onceOnly = (reminderMinutes < 0); + event.setReminder((onceOnly ? -reminderMinutes : reminderMinutes), onceOnly); + } + if (!audioFile.isEmpty()) + event.setAudioFile(audioFile, audioVolume, -1, 0); + if (!mailAddresses.isEmpty()) + event.setEmail(mailFromID, mailAddresses, mailSubject, mailAttachments); + event.setRecurrence(recurrence); + event.setFirstRecurrence(); + event.setRepetition(repeatInterval, repeatCount - 1); + if (alarmTime <= now) + { + // Alarm is due for display already. + // First execute it once without adding it to the calendar file. + if (!mInitialised) + mDcopQueue.append(DcopQEntry(event, EVENT_TRIGGER)); + else + execAlarm(event, event.firstAlarm(), false); + // If it's a recurring alarm, reschedule it for its next occurrence + if (!event.recurs() + || event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE) + return true; + // It has recurrences in the future + } + + // Queue the alarm for insertion into the calendar file + mDcopQueue.append(DcopQEntry(event)); + if (mInitialised) + QTimer::singleShot(0, this, SLOT(processQueue())); + return true; +} + +/****************************************************************************** +* Called in response to a DCOP notification by the alarm daemon that an event +* should be handled, i.e. displayed or cancelled. +* Optionally display the event. Delete the event from the calendar file and +* from every main window instance. +*/ +bool KAlarmApp::handleEvent(const QString& urlString, const QString& eventID, EventFunc function) +{ + kdDebug(5950) << "KAlarmApp::handleEvent(DCOP): " << eventID << endl; + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); // this can be called before calendars have been initialised + if (cal && KURL(urlString).url() != cal->urlString()) + { + kdError(5950) << "KAlarmApp::handleEvent(DCOP): wrong calendar file " << urlString << endl; + Daemon::eventHandled(eventID, false); + return false; + } + mDcopQueue.append(DcopQEntry(function, eventID)); + if (mInitialised) + QTimer::singleShot(0, this, SLOT(processQueue())); + return true; +} + +/****************************************************************************** +* Either: +* a) Display the event and then delete it if it has no outstanding repetitions. +* b) Delete the event. +* c) Reschedule the event for its next repetition. If none remain, delete it. +* If the event is deleted, it is removed from the calendar file and from every +* main window instance. +*/ +bool KAlarmApp::handleEvent(const QString& eventID, EventFunc function) +{ + kdDebug(5950) << "KAlarmApp::handleEvent(): " << eventID << ", " << (function==EVENT_TRIGGER?"TRIGGER":function==EVENT_CANCEL?"CANCEL":function==EVENT_HANDLE?"HANDLE":"?") << endl; + KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(eventID); + if (!kcalEvent) + { + kdError(5950) << "KAlarmApp::handleEvent(): event ID not found: " << eventID << endl; + Daemon::eventHandled(eventID, false); + return false; + } + KAEvent event(*kcalEvent); + switch (function) + { + case EVENT_CANCEL: + KAlarm::deleteEvent(event, true); + break; + + case EVENT_TRIGGER: // handle it if it's due, else execute it regardless + case EVENT_HANDLE: // handle it if it's due + { + QDateTime now = QDateTime::currentDateTime(); + bool updateCalAndDisplay = false; + bool alarmToExecuteValid = false; + KAAlarm alarmToExecute; + // Check all the alarms in turn. + // Note that the main alarm is fetched before any other alarms. + for (KAAlarm alarm = event.firstAlarm(); alarm.valid(); alarm = event.nextAlarm(alarm)) + { + // Check if the alarm is due yet. + int secs = alarm.dateTime(true).dateTime().secsTo(now); + if (secs < 0) + { + // This alarm is definitely not due yet + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": not due\n"; + continue; + } + if (alarm.repeatAtLogin()) + { + // Alarm is to be displayed at every login. + // Check if the alarm has only just been set up. + // (The alarm daemon will immediately notify that it is due + // since it is set up with a time in the past.) + kdDebug(5950) << "KAlarmApp::handleEvent(): REPEAT_AT_LOGIN\n"; + if (secs < maxLateness(1)) + continue; + + // Check if the main alarm is already being displayed. + // (We don't want to display both at the same time.) + if (alarmToExecute.valid()) + continue; + + // Set the time to display if it's a display alarm + alarm.setTime(now); + } + if (alarm.lateCancel()) + { + // Alarm is due, and it is to be cancelled if too late. + kdDebug(5950) << "KAlarmApp::handleEvent(): LATE_CANCEL\n"; + bool late = false; + bool cancel = false; + if (alarm.dateTime().isDateOnly()) + { + // The alarm has no time, so cancel it if its date is too far past + int maxlate = alarm.lateCancel() / 1440; // maximum lateness in days + QDateTime limit(alarm.date().addDays(maxlate + 1), Preferences::startOfDay()); + if (now >= limit) + { + // It's too late to display the scheduled occurrence. + // Find the last previous occurrence of the alarm. + DateTime next; + KAEvent::OccurType type = event.previousOccurrence(now, next, true); + switch (type & ~KAEvent::OCCURRENCE_REPEAT) + { + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + limit.setDate(next.date().addDays(maxlate + 1)); + limit.setTime(Preferences::startOfDay()); + if (now >= limit) + { + if (type == KAEvent::LAST_RECURRENCE + || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) + cancel = true; // last occurrence (and there are no repetitions) + else + late = true; + } + break; + case KAEvent::NO_OCCURRENCE: + default: + late = true; + break; + } + } + } + else + { + // The alarm is timed. Allow it to be the permitted amount late before cancelling it. + int maxlate = maxLateness(alarm.lateCancel()); + if (secs > maxlate) + { + // It's over the maximum interval late. + // Find the most recent occurrence of the alarm. + DateTime next; + KAEvent::OccurType type = event.previousOccurrence(now, next, true); + switch (type & ~KAEvent::OCCURRENCE_REPEAT) + { + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + if (next.dateTime().secsTo(now) > maxlate) + { + if (type == KAEvent::LAST_RECURRENCE + || type == KAEvent::FIRST_OR_ONLY_OCCURRENCE && !event.recurs()) + cancel = true; // last occurrence (and there are no repetitions) + else + late = true; + } + break; + case KAEvent::NO_OCCURRENCE: + default: + late = true; + break; + } + } + } + + if (cancel) + { + // All recurrences are finished, so cancel the event + event.setArchive(); + cancelAlarm(event, alarm.type(), false); + updateCalAndDisplay = true; + continue; + } + if (late) + { + // The latest repetition was too long ago, so schedule the next one + rescheduleAlarm(event, alarm, false); + updateCalAndDisplay = true; + continue; + } + } + if (!alarmToExecuteValid) + { + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": execute\n"; + alarmToExecute = alarm; // note the alarm to be executed + alarmToExecuteValid = true; // only trigger one alarm for the event + } + else + kdDebug(5950) << "KAlarmApp::handleEvent(): alarm " << alarm.type() << ": skip\n"; + } + + // If there is an alarm to execute, do this last after rescheduling/cancelling + // any others. This ensures that the updated event is only saved once to the calendar. + if (alarmToExecute.valid()) + execAlarm(event, alarmToExecute, true, !alarmToExecute.repeatAtLogin()); + else + { + if (function == EVENT_TRIGGER) + { + // The alarm is to be executed regardless of whether it's due. + // Only trigger one alarm from the event - we don't want multiple + // identical messages, for example. + KAAlarm alarm = event.firstAlarm(); + if (alarm.valid()) + execAlarm(event, alarm, false); + } + if (updateCalAndDisplay) + KAlarm::updateEvent(event, 0); // update the window lists and calendar file + else if (function != EVENT_TRIGGER) + { + kdDebug(5950) << "KAlarmApp::handleEvent(): no action\n"; + Daemon::eventHandled(eventID, false); + } + } + break; + } + } + return true; +} + +/****************************************************************************** +* Called when an alarm is currently being displayed, to store a copy of the +* alarm in the displaying calendar, and to reschedule it for its next repetition. +* If no repetitions remain, cancel it. +*/ +void KAlarmApp::alarmShowing(KAEvent& event, KAAlarm::Type alarmType, const DateTime& alarmTime) +{ + kdDebug(5950) << "KAlarmApp::alarmShowing(" << event.id() << ", " << KAAlarm::debugType(alarmType) << ")\n"; + KCal::Event* kcalEvent = AlarmCalendar::activeCalendar()->event(event.id()); + if (!kcalEvent) + kdError(5950) << "KAlarmApp::alarmShowing(): event ID not found: " << event.id() << endl; + else + { + KAAlarm alarm = event.alarm(alarmType); + if (!alarm.valid()) + kdError(5950) << "KAlarmApp::alarmShowing(): alarm type not found: " << event.id() << ":" << alarmType << endl; + else + { + // Copy the alarm to the displaying calendar in case of a crash, etc. + KAEvent dispEvent; + dispEvent.setDisplaying(event, alarmType, alarmTime.dateTime()); + AlarmCalendar* cal = AlarmCalendar::displayCalendarOpen(); + if (cal) + { + cal->deleteEvent(dispEvent.id()); // in case it already exists + cal->addEvent(dispEvent); + cal->save(); + } + + rescheduleAlarm(event, alarm, true); + return; + } + } + Daemon::eventHandled(event.id(), false); +} + +/****************************************************************************** +* Called when an alarm action has completed, to perform any post-alarm actions. +*/ +void KAlarmApp::alarmCompleted(const KAEvent& event) +{ + if (!event.postAction().isEmpty() && ShellProcess::authorised()) + { + QString command = event.postAction(); + kdDebug(5950) << "KAlarmApp::alarmCompleted(" << event.id() << "): " << command << endl; + doShellCommand(command, event, 0, ProcData::POST_ACTION); + } +} + +/****************************************************************************** +* Reschedule the alarm for its next recurrence. If none remain, delete it. +* If the alarm is deleted and it is the last alarm for its event, the event is +* removed from the calendar file and from every main window instance. +*/ +void KAlarmApp::rescheduleAlarm(KAEvent& event, const KAAlarm& alarm, bool updateCalAndDisplay) +{ + kdDebug(5950) << "KAlarmApp::rescheduleAlarm()" << endl; + bool update = false; + if (alarm.reminder() || alarm.deferred()) + { + // It's an advance warning alarm or an extra deferred alarm, so delete it + event.removeExpiredAlarm(alarm.type()); + update = true; + } + else if (alarm.repeatAtLogin()) + { + // Leave an alarm which repeats at every login until its main alarm is deleted + if (updateCalAndDisplay && event.updated()) + update = true; + } + else + { + // Reschedule the alarm for its next recurrence. + KAEvent::OccurType type = event.setNextOccurrence(QDateTime::currentDateTime()); + switch (type) + { + case KAEvent::NO_OCCURRENCE: + // All repetitions are finished, so cancel the event + cancelAlarm(event, alarm.type(), updateCalAndDisplay); + break; + default: + if (!(type & KAEvent::OCCURRENCE_REPEAT)) + break; + // Next occurrence is a repeat, so fall through to recurrence handling + case KAEvent::RECURRENCE_DATE: + case KAEvent::RECURRENCE_DATE_TIME: + case KAEvent::LAST_RECURRENCE: + // The event is due by now and repetitions still remain, so rewrite the event + if (updateCalAndDisplay) + update = true; + else + { + event.cancelCancelledDeferral(); + event.setUpdated(); // note that the calendar file needs to be updated + } + break; + case KAEvent::FIRST_OR_ONLY_OCCURRENCE: + // The first occurrence is still due?!?, so don't do anything + break; + } + if (event.deferred()) + { + // Just in case there's also a deferred alarm, ensure it's removed + event.removeExpiredAlarm(KAAlarm::DEFERRED_ALARM); + update = true; + } + } + if (update) + { + event.cancelCancelledDeferral(); + KAlarm::updateEvent(event, 0); // update the window lists and calendar file + } +} + +/****************************************************************************** +* Delete the alarm. If it is the last alarm for its event, the event is removed +* from the calendar file and from every main window instance. +*/ +void KAlarmApp::cancelAlarm(KAEvent& event, KAAlarm::Type alarmType, bool updateCalAndDisplay) +{ + kdDebug(5950) << "KAlarmApp::cancelAlarm()" << endl; + event.cancelCancelledDeferral(); + if (alarmType == KAAlarm::MAIN_ALARM && !event.displaying() && event.toBeArchived()) + { + // The event is being deleted. Save it in the expired calendar file first. + QString id = event.id(); // save event ID since KAlarm::addExpiredEvent() changes it + KAlarm::addExpiredEvent(event); + event.setEventID(id); // restore event ID + } + event.removeExpiredAlarm(alarmType); + if (!event.alarmCount()) + KAlarm::deleteEvent(event, false); + else if (updateCalAndDisplay) + KAlarm::updateEvent(event, 0); // update the window lists and calendar file +} + +/****************************************************************************** +* Execute an alarm by displaying its message or file, or executing its command. +* Reply = ShellProcess instance if a command alarm +* != 0 if successful +* = 0 if the alarm is disabled, or if an error message was output. +*/ +void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, bool reschedule, bool allowDefer, bool noPreAction) +{ + if (!event.enabled()) + { + // The event is disabled. + if (reschedule) + rescheduleAlarm(event, alarm, true); + return 0; + } + + void* result = (void*)1; + event.setArchive(); + switch (alarm.action()) + { + case KAAlarm::MESSAGE: + case KAAlarm::FILE: + { + // Display a message or file, provided that the same event isn't already being displayed + MessageWin* win = MessageWin::findEvent(event.id()); + // Find if we're changing a reminder message to the real message + bool reminder = (alarm.type() & KAAlarm::REMINDER_ALARM); + bool replaceReminder = !reminder && win && (win->alarmType() & KAAlarm::REMINDER_ALARM); + if (!reminder && !event.deferred() + && (replaceReminder || !win) && !noPreAction + && !event.preAction().isEmpty() && ShellProcess::authorised()) + { + // It's not a reminder or a deferred alarm, and there is no message window + // (other than a reminder window) currently displayed for this alarm, + // and we need to execute a command before displaying the new window. + // Check whether the command is already being executed for this alarm. + for (QValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->event->id() == event.id() && (pd->flags & ProcData::PRE_ACTION)) + { + kdDebug(5950) << "KAlarmApp::execAlarm(): already executing pre-DISPLAY command" << endl; + return pd->process; // already executing - don't duplicate the action + } + } + + QString command = event.preAction(); + kdDebug(5950) << "KAlarmApp::execAlarm(): pre-DISPLAY command: " << command << endl; + int flags = (reschedule ? ProcData::RESCHEDULE : 0) | (allowDefer ? ProcData::ALLOW_DEFER : 0); + if (doShellCommand(command, event, &alarm, (flags | ProcData::PRE_ACTION))) + return result; // display the message after the command completes + // Error executing command - display the message even though it failed + } + if (!event.enabled()) + delete win; // event is disabled - close its window + else if (!win + || !win->hasDefer() && !alarm.repeatAtLogin() + || replaceReminder) + { + // Either there isn't already a message for this event, + // or there is a repeat-at-login message with no Defer + // button, which needs to be replaced with a new message, + // or the caption needs to be changed from "Reminder" to "Message". + if (win) + win->setRecreating(); // prevent post-alarm actions + delete win; + KAlarm::cancelScreenSaver(); + (new MessageWin(event, alarm, reschedule, allowDefer))->show(); + } + else + { + // Raise the existing message window and replay any sound + KAlarm::cancelScreenSaver(); + win->repeat(alarm); // N.B. this reschedules the alarm + } + break; + } + case KAAlarm::COMMAND: + { + int flags = event.commandXterm() ? ProcData::EXEC_IN_XTERM : 0; + QString command = event.cleanText(); + if (event.commandScript()) + { + // Store the command script in a temporary file for execution + kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: (script)" << endl; + QString tmpfile = createTempScriptFile(command, false, event, alarm); + if (tmpfile.isEmpty()) + result = 0; + else + result = doShellCommand(tmpfile, event, &alarm, (flags | ProcData::TEMP_FILE)); + } + else + { + kdDebug(5950) << "KAlarmApp::execAlarm(): COMMAND: " << command << endl; + result = doShellCommand(command, event, &alarm, flags); + } + if (reschedule) + rescheduleAlarm(event, alarm, true); + break; + } + case KAAlarm::EMAIL: + { + kdDebug(5950) << "KAlarmApp::execAlarm(): EMAIL to: " << event.emailAddresses(", ") << endl; + QStringList errmsgs; + if (!KAMail::send(event, errmsgs, (reschedule || allowDefer))) + result = 0; + if (!errmsgs.isEmpty()) + { + // Some error occurred, although the email may have been sent successfully + if (result) + kdDebug(5950) << "KAlarmApp::execAlarm(): copy error: " << errmsgs[1] << endl; + else + kdDebug(5950) << "KAlarmApp::execAlarm(): failed: " << errmsgs[1] << endl; + (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); + } + if (reschedule) + rescheduleAlarm(event, alarm, true); + break; + } + default: + return 0; + } + return result; +} + +/****************************************************************************** +* Execute a shell command line specified by an alarm. +* If the PRE_ACTION bit of 'flags' is set, the alarm will be executed via +* execAlarm() once the command completes, the execAlarm() parameters being +* derived from the remaining bits in 'flags'. +*/ +ShellProcess* KAlarmApp::doShellCommand(const QString& command, const KAEvent& event, const KAAlarm* alarm, int flags) +{ + kdDebug(5950) << "KAlarmApp::doShellCommand(" << command << ", " << event.id() << ")" << endl; + KProcess::Communication comms = KProcess::NoCommunication; + QString cmd; + QString tmpXtermFile; + if (flags & ProcData::EXEC_IN_XTERM) + { + // Execute the command in a terminal window. + cmd = Preferences::cmdXTermCommand(); + cmd.replace("%t", aboutData()->programName()); // set the terminal window title + if (cmd.find("%C") >= 0) + { + // Execute the command from a temporary script file + if (flags & ProcData::TEMP_FILE) + cmd.replace("%C", command); // the command is already calling a temporary file + else + { + tmpXtermFile = createTempScriptFile(command, true, event, *alarm); + if (tmpXtermFile.isEmpty()) + return 0; + cmd.replace("%C", tmpXtermFile); // %C indicates where to insert the command + } + } + else if (cmd.find("%W") >= 0) + { + // Execute the command from a temporary script file, + // with a sleep after the command is executed + tmpXtermFile = createTempScriptFile(command + QString::fromLatin1("\nsleep 86400\n"), true, event, *alarm); + if (tmpXtermFile.isEmpty()) + return 0; + cmd.replace("%W", tmpXtermFile); // %w indicates where to insert the command + } + else if (cmd.find("%w") >= 0) + { + // Append a sleep to the command. + // Quote the command in case it contains characters such as [>|;]. + QString exec = KShellProcess::quote(command + QString::fromLatin1("; sleep 86400")); + cmd.replace("%w", exec); // %w indicates where to insert the command string + } + else + { + // Set the command to execute. + // Put it in quotes in case it contains characters such as [>|;]. + QString exec = KShellProcess::quote(command); + if (cmd.find("%c") >= 0) + cmd.replace("%c", exec); // %c indicates where to insert the command string + else + cmd.append(exec); // otherwise, simply append the command string + } + } + else + { + cmd = command; + comms = KProcess::AllOutput; + } + ShellProcess* proc = new ShellProcess(cmd); + connect(proc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotCommandExited(ShellProcess*))); + QGuardedPtr logproc = 0; + if (comms == KProcess::AllOutput && !event.logFile().isEmpty()) + { + // Output is to be appended to a log file. + // Set up a logging process to write the command's output to. + connect(proc, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int))); + connect(proc, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotCommandOutput(KProcess*,char*,int))); + logproc = new ShellProcess(QString::fromLatin1("cat >>%1").arg(event.logFile())); + connect(logproc, SIGNAL(shellExited(ShellProcess*)), SLOT(slotLogProcExited(ShellProcess*))); + logproc->start(KProcess::Stdin); + QCString heading; + if (alarm && alarm->dateTime().isValid()) + { + QString dateTime = alarm->dateTime().isDateOnly() + ? KGlobal::locale()->formatDate(alarm->dateTime().date(), true) + : KGlobal::locale()->formatDateTime(alarm->dateTime().dateTime()); + heading.sprintf("\n******* KAlarm %s *******\n", dateTime.latin1()); + } + else + heading = "\n******* KAlarm *******\n"; + logproc->writeStdin(heading, heading.length()+1); + } + ProcData* pd = new ProcData(proc, logproc, new KAEvent(event), (alarm ? new KAAlarm(*alarm) : 0), flags); + if (flags & ProcData::TEMP_FILE) + pd->tempFiles += command; + if (!tmpXtermFile.isEmpty()) + pd->tempFiles += tmpXtermFile; + mCommandProcesses.append(pd); + if (proc->start(comms)) + return proc; + + // Error executing command - report it + kdError(5950) << "KAlarmApp::doShellCommand(): command failed to start\n"; + commandErrorMsg(proc, event, alarm, flags); + mCommandProcesses.remove(pd); + delete pd; + return 0; +} + +/****************************************************************************** +* Create a temporary script file containing the specified command string. +* Reply = path of temporary file, or null string if error. +*/ +QString KAlarmApp::createTempScriptFile(const QString& command, bool insertShell, const KAEvent& event, const KAAlarm& alarm) +{ + KTempFile tmpFile(QString::null, QString::null, 0700); + tmpFile.setAutoDelete(false); // don't delete file when it is destructed + QTextStream* stream = tmpFile.textStream(); + if (!stream) + kdError(5950) << "KAlarmApp::createTempScript(): Unable to create a temporary script file" << endl; + else + { + if (insertShell) + *stream << "#!" << ShellProcess::shellPath() << "\n"; + *stream << command; + tmpFile.close(); + if (tmpFile.status()) + kdError(5950) << "KAlarmApp::createTempScript(): Error " << tmpFile.status() << " writing to temporary script file" << endl; + else + return tmpFile.name(); + } + + QStringList errmsgs(i18n("Error creating temporary script file")); + (new MessageWin(event, alarm.dateTime(), errmsgs))->show(); + return QString::null; +} + +/****************************************************************************** +* Called when an executing command alarm sends output to stdout or stderr. +*/ +void KAlarmApp::slotCommandOutput(KProcess* proc, char* buffer, int bufflen) +{ +//kdDebug(5950) << "KAlarmApp::slotCommandOutput(): '" << QCString(buffer, bufflen+1) << "'\n"; + // Find this command in the command list + for (QValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc && pd->logProcess) + { + pd->logProcess->writeStdin(buffer, bufflen); + break; + } + } +} + +/****************************************************************************** +* Called when a logging process completes. +*/ +void KAlarmApp::slotLogProcExited(ShellProcess* proc) +{ + // Because it's held as a guarded pointer in the ProcData structure, + // we don't need to set any pointers to zero. + delete proc; +} + +/****************************************************************************** +* Called when a command alarm's execution completes. +*/ +void KAlarmApp::slotCommandExited(ShellProcess* proc) +{ + kdDebug(5950) << "KAlarmApp::slotCommandExited()\n"; + // Find this command in the command list + for (QValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc) + { + // Found the command + if (pd->logProcess) + pd->logProcess->stdinExit(); // terminate the logging process + + // Check its exit status + if (!proc->normalExit()) + { + QString errmsg = proc->errorMessage(); + kdWarning(5950) << "KAlarmApp::slotCommandExited(" << pd->event->cleanText() << "): " << errmsg << endl; + if (pd->messageBoxParent) + { + // Close the existing informational KMessageBox for this process + QObjectList* dialogs = pd->messageBoxParent->queryList("KDialogBase", 0, false, true); + KDialogBase* dialog = (KDialogBase*)dialogs->getFirst(); + delete dialog; + delete dialogs; + if (!pd->tempFile()) + { + errmsg += '\n'; + errmsg += proc->command(); + } + KMessageBox::error(pd->messageBoxParent, errmsg); + } + else + commandErrorMsg(proc, *pd->event, pd->alarm, pd->flags); + } + if (pd->preAction()) + execAlarm(*pd->event, *pd->alarm, pd->reschedule(), pd->allowDefer(), true); + mCommandProcesses.remove(it); + delete pd; + break; + } + } + + // If there are now no executing shell commands, quit if a quit was queued + if (mPendingQuit && mCommandProcesses.isEmpty()) + quitIf(mPendingQuitCode); +} + +/****************************************************************************** +* Output an error message for a shell command. +*/ +void KAlarmApp::commandErrorMsg(const ShellProcess* proc, const KAEvent& event, const KAAlarm* alarm, int flags) +{ + QStringList errmsgs; + if (flags & ProcData::PRE_ACTION) + errmsgs += i18n("Pre-alarm action:"); + else if (flags & ProcData::POST_ACTION) + errmsgs += i18n("Post-alarm action:"); + errmsgs += proc->errorMessage(); + if (!(flags & ProcData::TEMP_FILE)) + errmsgs += proc->command(); + (new MessageWin(event, (alarm ? alarm->dateTime() : DateTime()), errmsgs))->show(); +} + +/****************************************************************************** +* Notes that an informational KMessageBox is displayed for this process. +*/ +void KAlarmApp::commandMessage(ShellProcess* proc, QWidget* parent) +{ + // Find this command in the command list + for (QValueList::Iterator it = mCommandProcesses.begin(); it != mCommandProcesses.end(); ++it) + { + ProcData* pd = *it; + if (pd->process == proc) + { + pd->messageBoxParent = parent; + break; + } + } +} + +/****************************************************************************** +* Set up remaining DCOP handlers and start processing DCOP calls. +*/ +void KAlarmApp::setUpDcop() +{ + if (!mInitialised) + { + mInitialised = true; // we're now ready to handle DCOP calls + Daemon::createDcopHandler(); + QTimer::singleShot(0, this, SLOT(processQueue())); // process anything already queued + } +} + +/****************************************************************************** +* If this is the first time through, open the calendar file, optionally start +* the alarm daemon and register with it, and set up the DCOP handler. +*/ +bool KAlarmApp::initCheck(bool calendarOnly) +{ + bool startdaemon; + AlarmCalendar* cal = AlarmCalendar::activeCalendar(); + if (!cal->isOpen()) + { + kdDebug(5950) << "KAlarmApp::initCheck(): opening active calendar\n"; + + // First time through. Open the calendar file. + if (!cal->open()) + return false; + + if (!mStartOfDay.isValid()) + changeStartOfDay(); // start of day time has changed, so adjust date-only alarms + + /* Need to open the display calendar now, since otherwise if the daemon + * immediately notifies display alarms, they will often be processed while + * redisplayAlarms() is executing open() (but before open() completes), + * which causes problems!! + */ + AlarmCalendar::displayCalendar()->open(); + + /* Need to open the expired alarm calendar now, since otherwise if the daemon + * immediately notifies multiple alarms, the second alarm is likely to be + * processed while the calendar is executing open() (but before open() completes), + * which causes a hang!! + */ + AlarmCalendar::expiredCalendar()->open(); + AlarmCalendar::expiredCalendar()->setPurgeDays(theInstance->mPrefsExpiredKeepDays); + + startdaemon = true; + } + else + startdaemon = !Daemon::isRegistered(); + + if (!calendarOnly) + { + setUpDcop(); // start processing DCOP calls + if (startdaemon) + Daemon::start(); // make sure the alarm daemon is running + } + return true; +} + +/****************************************************************************** +* Convert the --time parameter string into a date/time or date value. +* The parameter is in the form [[[yyyy-]mm-]dd-]hh:mm or yyyy-mm-dd. +* Reply = true if successful. +*/ +static bool convWakeTime(const QCString& timeParam, QDateTime& dateTime, bool& noTime) +{ + if (timeParam.length() > 19) + return false; + char timeStr[20]; + strcpy(timeStr, timeParam); + int dt[5] = { -1, -1, -1, -1, -1 }; + char* s; + char* end; + // Get the minute value + if ((s = strchr(timeStr, ':')) == 0) + noTime = true; + else + { + noTime = false; + *s++ = 0; + dt[4] = strtoul(s, &end, 10); + if (end == s || *end || dt[4] >= 60) + return false; + // Get the hour value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[3] = strtoul(s, &end, 10); + if (end == s || *end || dt[3] >= 24) + return false; + } + bool dateSet = false; + if (s != timeStr) + { + dateSet = true; + // Get the day value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[2] = strtoul(s, &end, 10); + if (end == s || *end || dt[2] == 0 || dt[2] > 31) + return false; + if (s != timeStr) + { + // Get the month value + if ((s = strrchr(timeStr, '-')) == 0) + s = timeStr; + else + *s++ = 0; + dt[1] = strtoul(s, &end, 10); + if (end == s || *end || dt[1] == 0 || dt[1] > 12) + return false; + if (s != timeStr) + { + // Get the year value + dt[0] = strtoul(timeStr, &end, 10); + if (end == timeStr || *end) + return false; + } + } + } + + QDate date(dt[0], dt[1], dt[2]); + QTime time(0, 0, 0); + if (noTime) + { + // No time was specified, so the full date must have been specified + if (dt[0] < 0) + return false; + } + else + { + // Compile the values into a date/time structure + QDateTime now = QDateTime::currentDateTime(); + if (dt[0] < 0) + date.setYMD(now.date().year(), + (dt[1] < 0 ? now.date().month() : dt[1]), + (dt[2] < 0 ? now.date().day() : dt[2])); + time.setHMS(dt[3], dt[4], 0); + if (!dateSet && time < now.time()) + date = date.addDays(1); + } + if (!date.isValid()) + return false; + dateTime.setDate(date); + dateTime.setTime(time); + return true; +} + +/****************************************************************************** +* Convert a time interval command line parameter. +* 'timeInterval' receives the count for the recurType. If 'allowMonthYear' is +* false, 'timeInterval' is converted to minutes. +* Reply = true if successful. +*/ +static bool convInterval(const QCString& timeParam, KARecurrence::Type& recurType, int& timeInterval, bool allowMonthYear) +{ + QCString timeString = timeParam; + // Get the recurrence interval + bool ok = true; + uint interval = 0; + bool negative = (timeString[0] == '-'); + if (negative) + timeString = timeString.right(1); + uint length = timeString.length(); + switch (timeString[length - 1]) + { + case 'Y': + if (!allowMonthYear) + ok = false; + recurType = KARecurrence::ANNUAL_DATE; + timeString = timeString.left(length - 1); + break; + case 'W': + recurType = KARecurrence::WEEKLY; + timeString = timeString.left(length - 1); + break; + case 'D': + recurType = KARecurrence::DAILY; + timeString = timeString.left(length - 1); + break; + case 'M': + { + int i = timeString.find('H'); + if (i < 0) + { + if (!allowMonthYear) + ok = false; + recurType = KARecurrence::MONTHLY_DAY; + timeString = timeString.left(length - 1); + } + else + { + recurType = KARecurrence::MINUTELY; + interval = timeString.left(i).toUInt(&ok) * 60; + timeString = timeString.mid(i + 1, length - i - 2); + } + break; + } + default: // should be a digit + recurType = KARecurrence::MINUTELY; + break; + } + if (ok) + interval += timeString.toUInt(&ok); + if (!allowMonthYear) + { + // Convert time interval to minutes + switch (recurType) + { + case KARecurrence::WEEKLY: + interval *= 7; + // fall through to DAILY + case KARecurrence::DAILY: + interval *= 24*60; + break; + default: + break; + } + } + timeInterval = static_cast(interval); + if (negative) + timeInterval = -timeInterval; + return ok; +} + +KAlarmApp::ProcData::ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f) + : process(p), + logProcess(logp), + event(e), + alarm(a), + messageBoxParent(0), + flags(f) +{ } + +KAlarmApp::ProcData::~ProcData() +{ + while (!tempFiles.isEmpty()) + { + // Delete the temporary file called by the XTerm command + QFile f(tempFiles.first()); + f.remove(); + tempFiles.remove(tempFiles.begin()); + } + delete process; + delete event; + delete alarm; +} diff --git a/kalarm/kalarmapp.h b/kalarm/kalarmapp.h new file mode 100644 index 000000000..22c7649b3 --- /dev/null +++ b/kalarm/kalarmapp.h @@ -0,0 +1,189 @@ +/* + * kalarmapp.h - the KAlarm application object + * Program: kalarm + * Copyright © 2001-2008 by 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. + */ + +#ifndef KALARMAPP_H +#define KALARMAPP_H + +/** @file kalarmapp.h - the KAlarm application object */ + +#include +class QTimer; +class QDateTime; + +#include +#include +class KProcess; +namespace KCal { class Event; } + +#include "alarmevent.h" +class DcopHandler; +#ifdef OLD_DCOP +class DcopHandlerOld; +#endif +class AlarmCalendar; +class MainWindow; +class AlarmListView; +class MessageWin; +class TrayWindow; +class ShellProcess; + + +class KAlarmApp : public KUniqueApplication +{ + Q_OBJECT + public: + ~KAlarmApp(); + virtual int newInstance(); + static KAlarmApp* getInstance(); + bool checkCalendarDaemon() { return initCheck(); } + bool haveSystemTray() const { return mHaveSystemTray; } + bool wantRunInSystemTray() const; + bool alarmsDisabledIfStopped() const { return mDisableAlarmsIfStopped; } + bool speechEnabled() const { return mSpeechEnabled; } + bool korganizerEnabled() const { return mKOrganizerEnabled; } + bool restoreSession(); + bool sessionClosingDown() const { return mSessionClosingDown; } + void quitIf() { quitIf(0); } + void doQuit(QWidget* parent); + static void displayFatalError(const QString& message); + void addWindow(TrayWindow* w) { mTrayWindow = w; } + void removeWindow(TrayWindow*); + TrayWindow* trayWindow() const { return mTrayWindow; } + MainWindow* trayMainWindow() const; + bool displayTrayIcon(bool show, MainWindow* = 0); + bool trayIconDisplayed() const { return !!mTrayWindow; } + bool editNewAlarm(MainWindow* = 0); + virtual void commitData(QSessionManager&); + + void* execAlarm(KAEvent&, const KAAlarm&, bool reschedule, bool allowDefer = true, bool noPreAction = false); + void alarmShowing(KAEvent&, KAAlarm::Type, const DateTime&); + void alarmCompleted(const KAEvent&); + bool deleteEvent(const QString& eventID) { return handleEvent(eventID, EVENT_CANCEL); } + void commandMessage(ShellProcess*, QWidget* parent); + // Methods called indirectly by the DCOP interface + bool scheduleEvent(KAEvent::Action, const QString& text, const QDateTime&, + int lateCancel, int flags, const QColor& bg, const QColor& fg, + const QFont&, const QString& audioFile, float audioVolume, + int reminderMinutes, const KARecurrence& recurrence, + int repeatInterval, int repeatCount, + uint mailFromID = 0, const EmailAddressList& mailAddresses = EmailAddressList(), + const QString& mailSubject = QString::null, + const QStringList& mailAttachments = QStringList()); + bool handleEvent(const QString& calendarFile, const QString& eventID) { return handleEvent(calendarFile, eventID, EVENT_HANDLE); } + bool triggerEvent(const QString& calendarFile, const QString& eventID) { return handleEvent(calendarFile, eventID, EVENT_TRIGGER); } + bool deleteEvent(const QString& calendarFile, const QString& eventID) { return handleEvent(calendarFile, eventID, EVENT_CANCEL); } + public slots: + void processQueue(); + signals: + void trayIconToggled(); + protected: + KAlarmApp(); + private slots: + void quitFatal(); + void slotPreferencesChanged(); + void slotCommandOutput(KProcess*, char* buffer, int bufflen); + void slotLogProcExited(ShellProcess*); + void slotCommandExited(ShellProcess*); + void slotSystemTrayTimer(); + void slotExpiredPurged(); + private: + enum EventFunc + { + EVENT_HANDLE, // if the alarm is due, execute it and then reschedule it + EVENT_TRIGGER, // execute the alarm regardless, and then reschedule it if it already due + EVENT_CANCEL // delete the alarm + }; + struct ProcData + { + ProcData(ShellProcess* p, ShellProcess* logp, KAEvent* e, KAAlarm* a, int f = 0); + ~ProcData(); + enum { PRE_ACTION = 0x01, POST_ACTION = 0x02, RESCHEDULE = 0x04, ALLOW_DEFER = 0x08, + TEMP_FILE = 0x10, EXEC_IN_XTERM = 0x20 }; + bool preAction() const { return flags & PRE_ACTION; } + bool postAction() const { return flags & POST_ACTION; } + bool reschedule() const { return flags & RESCHEDULE; } + bool allowDefer() const { return flags & ALLOW_DEFER; } + bool tempFile() const { return flags & TEMP_FILE; } + bool execInXterm() const { return flags & EXEC_IN_XTERM; } + ShellProcess* process; + QGuardedPtr logProcess; + KAEvent* event; + KAAlarm* alarm; + QGuardedPtr messageBoxParent; + QStringList tempFiles; + int flags; + }; + struct DcopQEntry + { + DcopQEntry(EventFunc f, const QString& id) : function(f), eventId(id) { } + DcopQEntry(const KAEvent& e, EventFunc f = EVENT_HANDLE) : function(f), event(e) { } + DcopQEntry() { } + EventFunc function; + QString eventId; + KAEvent event; + }; + + bool initCheck(bool calendarOnly = false); + void quitIf(int exitCode, bool force = false); + void redisplayAlarms(); + bool checkSystemTray(); + void changeStartOfDay(); + void setUpDcop(); + bool handleEvent(const QString& calendarFile, const QString& eventID, EventFunc); + bool handleEvent(const QString& eventID, EventFunc); + void rescheduleAlarm(KAEvent&, const KAAlarm&, bool updateCalAndDisplay); + void cancelAlarm(KAEvent&, KAAlarm::Type, bool updateCalAndDisplay); + ShellProcess* doShellCommand(const QString& command, const KAEvent&, const KAAlarm*, int flags = 0); + QString createTempScriptFile(const QString& command, bool insertShell, const KAEvent&, const KAAlarm&); + void commandErrorMsg(const ShellProcess*, const KAEvent&, const KAAlarm*, int flags = 0); + + static KAlarmApp* theInstance; // the one and only KAlarmApp instance + static int mActiveCount; // number of active instances without main windows + static int mFatalError; // a fatal error has occurred - just wait to exit + static QString mFatalMessage; // fatal error message to output + bool mInitialised; // initialisation complete: ready to handle DCOP calls + DcopHandler* mDcopHandler; // the parent of the main DCOP receiver object +#ifdef OLD_DCOP + DcopHandlerOld* mDcopHandlerOld; // the parent of the old main DCOP receiver object +#endif + TrayWindow* mTrayWindow; // active system tray icon + QTime mStartOfDay; // start-of-day time currently in use + QColor mPrefsExpiredColour; // expired alarms text colour + int mPrefsExpiredKeepDays;// how long expired alarms are being kept + QValueList mCommandProcesses; // currently active command alarm processes + QValueList mDcopQueue; // DCOP command queue + int mPendingQuitCode; // exit code for a pending quit + bool mPendingQuit; // quit once the DCOP command and shell command queues have been processed + bool mProcessingQueue; // a mDcopQueue entry is currently being processed + bool mHaveSystemTray; // whether there is a system tray + bool mNoSystemTray; // no KDE system tray exists + bool mSavedNoSystemTray; // mNoSystemTray before mCheckingSystemTray was true + bool mCheckingSystemTray; // the existence of the system tray is being checked + bool mSessionClosingDown; // session manager is closing the application + bool mOldRunInSystemTray; // running continuously in system tray was selected + bool mDisableAlarmsIfStopped; // disable alarms whenever KAlarm is not running + bool mRefreshExpiredAlarms; // need to refresh the expired alarms display + bool mSpeechEnabled; // speech synthesis is enabled (kttsd exists) + bool mKOrganizerEnabled; // KOrganizer options are enabled (korganizer exists) +}; + +inline KAlarmApp* theApp() { return KAlarmApp::getInstance(); } + +#endif // KALARMAPP_H diff --git a/kalarm/kalarmd/Makefile.am b/kalarm/kalarmd/Makefile.am new file mode 100644 index 000000000..7770c6816 --- /dev/null +++ b/kalarm/kalarmd/Makefile.am @@ -0,0 +1,28 @@ +INCLUDES= -I$(top_srcdir) -I$(top_srcdir)/kalarm $(all_includes) + +noinst_LTLIBRARIES = libkalarmd.la + +libkalarmd_la_LDFLAGS = $(all_libraries) $(KDE_RPATH) +libkalarmd_la_LIBADD = $(LIB_KDECORE) $(top_builddir)/libkcal/libkcal.la +libkalarmd_la_SOURCES = alarmdaemoniface.stub alarmguiiface.stub + +bin_PROGRAMS = kalarmd + +kalarmd_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kalarmd_LDADD = $(LIB_KDEUI) $(top_builddir)/libkcal/libkcal.la libkalarmd.la +kalarmd_SOURCES = alarmdaemon.cpp admain.cpp adapp.cpp \ + adcalendar.cpp adconfigdata.cpp clientinfo.cpp \ + alarmdaemoniface.skel +kalarmd_COMPILE_FIRST = alarmguiiface_stub.h + +noinst_HEADERS = adapp.h alarmdaemon.h alarmdaemoniface.h alarmguiiface.h \ + adcalendar.h adconfigdata.h clientinfo.h kalarmd.h + +METASOURCES = AUTO + +autostart_DATA = kalarmd.autostart.desktop +autostartdir = $(datadir)/autostart + +app_DATA = kalarmd.desktop +appdir = $(kde_appsdir)/.hidden + diff --git a/kalarm/kalarmd/adapp.cpp b/kalarm/kalarmd/adapp.cpp new file mode 100644 index 000000000..77f31e4f0 --- /dev/null +++ b/kalarm/kalarmd/adapp.cpp @@ -0,0 +1,71 @@ +/* + * adapp.cpp - kalarmd application + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (c) 2001, 2004, 2005 by David Jarvie + * Copyright (c) 2000,2001 Cornelius Schumacher + * Copyright (c) 1997-1999 Preston Brown + * + * 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 "kalarmd.h" + +#include +#include + +#include "alarmdaemon.h" +#include "adapp.moc" + + +AlarmDaemonApp::AlarmDaemonApp() + : KUniqueApplication(false, false), + mAd(0) +{ + disableSessionManagement(); +} + +int AlarmDaemonApp::newInstance() +{ + kdDebug(5900) << "AlarmDaemonApp::newInstance()" << endl; + + /* Prevent the application being restored automatically by the session manager + * at session startup. Instead, the KDE autostart facility is used to start + * the application. This allows the user to configure whether or not it is to + * be started automatically, and also ensures that it is started in the correct + * phase of session startup, i.e. after clients have been restored by the + * session manager. + */ + disableSessionManagement(); + + // Check if we already have a running alarm daemon widget + if (mAd) + return 0; + + // Check if we are starting up at session startup + static bool restored = false; + bool autostart = false; + if (!restored && isRestored()) + restored = true; // make sure we restore only once + else + { + KCmdLineArgs* args = KCmdLineArgs::parsedArgs(); + autostart = args->isSet("autostart"); + args->clear(); // free up memory + } + + mAd = new AlarmDaemon(autostart, 0, DAEMON_DCOP_OBJECT); + + return 0; +} diff --git a/kalarm/kalarmd/adapp.h b/kalarm/kalarmd/adapp.h new file mode 100644 index 000000000..83d11d298 --- /dev/null +++ b/kalarm/kalarmd/adapp.h @@ -0,0 +1,42 @@ +/* + * adapp.h - kalarmd application + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (C) 2001, 2004 by David Jarvie + * Copyright (c) 2000,2001 Cornelius Schumacher + * Copyright (c) 1997-1999 Preston Brown + * + * 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. + */ + +#ifndef ADAPP_H +#define ADAPP_H + +#include + +class AlarmDaemon; + + +class AlarmDaemonApp : public KUniqueApplication +{ + Q_OBJECT + public: + AlarmDaemonApp(); + virtual int newInstance(); + + private: + AlarmDaemon* mAd; +}; + +#endif // ADAPP_H diff --git a/kalarm/kalarmd/adcalendar.cpp b/kalarm/kalarmd/adcalendar.cpp new file mode 100644 index 000000000..8af97cc0b --- /dev/null +++ b/kalarm/kalarmd/adcalendar.cpp @@ -0,0 +1,253 @@ +/* + * adcalendar.cpp - calendar file access + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (c) 2001, 2004-2006 by 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 +#include + +#include + +#include +#include +#include +#include + +#include "adcalendar.moc" + +QValueList ADCalendar::mCalendars; +ADCalendar::EventsMap ADCalendar::mEventsHandled; +ADCalendar::EventsMap ADCalendar::mEventsPending; +QStringList ADCalendar::mCalendarUrls; // never delete or reorder anything in this list! + + +ADCalendar::ADCalendar(const QString& url, const QCString& appname) + : KCal::CalendarLocal(QString::fromLatin1("UTC")), + mUrlString(url), + mAppName(appname), + mLoaded(false), + mLoadedConnected(false), + mUnregistered(false), + mEnabled(true) +{ + ADCalendar* cal = getCalendar(url); + if (cal) + { + kdError(5900) << "ADCalendar::ADCalendar(" << url << "): calendar already exists" << endl; + assert(0); + } + mUrlIndex = mCalendarUrls.findIndex(url); // get unique index for this URL + if (mUrlIndex < 0) + { + mUrlIndex = static_cast(mCalendarUrls.count()); + mCalendarUrls.append(url); + } + loadFile(false); + mCalendars.append(this); +} + +ADCalendar::~ADCalendar() +{ + clearEventsHandled(); + mCalendars.remove(this); +} + +/****************************************************************************** +* Load the calendar file. +*/ +bool ADCalendar::loadFile(bool reset) +{ + if (reset) + clearEventsHandled(); + if (!mTempFileName.isNull()) + { + // Don't try to load the file if already downloading it + kdError(5900) << "ADCalendar::loadFile(): already downloading another file\n"; + return false; + } + mLoaded = false; + KURL url(mUrlString); + if (url.isLocalFile()) + { + // It's a local file + loadLocalFile(url.path()); + emit loaded(this, mLoaded); + } + else + { + // It's a remote file. Download to a temporary file before loading it + KTempFile tempFile; + mTempFileName = tempFile.name(); + KURL dest; + dest.setPath(mTempFileName); + KIO::FileCopyJob* job = KIO::file_copy(url, dest, -1, true); + connect(job, SIGNAL(result(KIO::Job*)), SLOT(slotDownloadJobResult(KIO::Job*))); + } + return true; +} + +void ADCalendar::slotDownloadJobResult(KIO::Job *job) +{ + if (job->error()) + { + KURL url(mUrlString); + kdDebug(5900) << "Error downloading calendar from " << url.prettyURL() << endl; + job->showErrorDialog(0); + } + else + { + kdDebug(5900) << "--- Downloaded to " << mTempFileName << endl; + loadLocalFile(mTempFileName); + } + unlink(QFile::encodeName(mTempFileName)); + mTempFileName = QString::null; + emit loaded(this, mLoaded); +} + +void ADCalendar::loadLocalFile(const QString& filename) +{ + mLoaded = load(filename); + if (!mLoaded) + kdDebug(5900) << "ADCalendar::loadLocalFile(): Error loading calendar file '" << filename << "'\n"; + else + clearEventsHandled(true); // remove all events which no longer exist from handled list +} + +bool ADCalendar::setLoadedConnected() +{ + if (mLoadedConnected) + return true; + mLoadedConnected = true; + return false; +} + +/****************************************************************************** +* Check whether all the alarms for the event with the given ID have already +* been handled. +*/ +bool ADCalendar::eventHandled(const KCal::Event* event, const QValueList& alarmtimes) +{ + EventsMap::ConstIterator it = mEventsHandled.find(EventKey(event->uid(), mUrlIndex)); + if (it == mEventsHandled.end()) + return false; + + int oldCount = it.data().alarmTimes.count(); + int count = alarmtimes.count(); + for (int i = 0; i < count; ++i) + { + if (alarmtimes[i].isValid() + && (i >= oldCount // is it an additional alarm? + || !it.data().alarmTimes[i].isValid() // or has it just become due? + || it.data().alarmTimes[i].isValid() // or has it changed? + && alarmtimes[i] != it.data().alarmTimes[i])) + return false; // this alarm has changed + } + return true; +} + +/****************************************************************************** +* Remember that the event with the given ID has been handled. +* It must already be in the pending list. +*/ +void ADCalendar::setEventHandled(const QString& eventID) +{ + kdDebug(5900) << "ADCalendar::setEventHandled(" << eventID << ")\n"; + EventKey key(eventID, mUrlIndex); + + // Remove it from the pending list, and add it to the handled list + EventsMap::Iterator it = mEventsPending.find(key); + if (it != mEventsPending.end()) + { + setEventInMap(mEventsHandled, key, it.data().alarmTimes, it.data().eventSequence); + mEventsPending.remove(it); + } +} + +/****************************************************************************** +* Remember that the specified alarms for the event with the given ID have been +* notified to KAlarm, but no reply has come back yet. +*/ +void ADCalendar::setEventPending(const KCal::Event* event, const QValueList& alarmtimes) +{ + if (event) + { + kdDebug(5900) << "ADCalendar::setEventPending(" << event->uid() << ")\n"; + EventKey key(event->uid(), mUrlIndex); + setEventInMap(mEventsPending, key, alarmtimes, event->revision()); + } +} + +/****************************************************************************** +* Add a specified entry to the events pending or handled list. +*/ +void ADCalendar::setEventInMap(EventsMap& map, const EventKey& key, const QValueList& alarmtimes, int sequence) +{ + EventsMap::Iterator it = map.find(key); + if (it != map.end()) + { + // Update the existing entry for the event + it.data().alarmTimes = alarmtimes; + it.data().eventSequence = sequence; + } + else + map.insert(key, EventItem(sequence, alarmtimes)); +} + +/****************************************************************************** +* Clear all memory of events handled for the calendar. +*/ +void ADCalendar::clearEventsHandled(bool nonexistentOnly) +{ + clearEventMap(mEventsPending, nonexistentOnly); + clearEventMap(mEventsHandled, nonexistentOnly); +} + +/****************************************************************************** +* Clear the events pending or handled list of all events handled for the calendar. +*/ +void ADCalendar::clearEventMap(EventsMap& map, bool nonexistentOnly) +{ + for (EventsMap::Iterator it = map.begin(); it != map.end(); ) + { + if (it.key().calendarIndex == mUrlIndex + && (!nonexistentOnly || !event(it.key().eventID))) + { + EventsMap::Iterator i = it; + ++it; // prevent iterator becoming invalid with remove() + map.remove(i); + } + else + ++it; + } +} + +/****************************************************************************** +* Look up the calendar with the specified full calendar URL. +*/ +ADCalendar* ADCalendar::getCalendar(const QString& calendarURL) +{ + if (!calendarURL.isEmpty()) + { + for (ConstIterator it = begin(); it != end(); ++it) + { + if ((*it)->urlString() == calendarURL) + return *it; + } + } + return 0; +} diff --git a/kalarm/kalarmd/adcalendar.h b/kalarm/kalarmd/adcalendar.h new file mode 100644 index 000000000..656070bef --- /dev/null +++ b/kalarm/kalarmd/adcalendar.h @@ -0,0 +1,120 @@ +/* + * adcalendar.h - calendar file access + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (c) 2001, 2004-2006 by 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. + */ + +#ifndef ADCALENDAR_H +#define ADCALENDAR_H + +#include +namespace KIO { class Job; } +class ADCalendar; + + +// Alarm Daemon calendar access +class ADCalendar : public KCal::CalendarLocal +{ + Q_OBJECT + public: + typedef QValueList::ConstIterator ConstIterator; + + ~ADCalendar(); + + const QString& urlString() const { return mUrlString; } + const QCString& appName() const { return mAppName; } + + void setEnabled(bool enabled) { mEnabled = enabled; } + bool enabled() const { return mEnabled && !unregistered(); } + bool available() const { return loaded() && !unregistered(); } + + // Client has registered since calendar was constructed, but + // has not since added the calendar. Monitoring is disabled. + void setUnregistered(bool u) { mUnregistered = u; } + bool unregistered() const { return mUnregistered; } + + void setEventPending(const KCal::Event*, const QValueList&); + void setEventHandled(const QString& eventID); + void clearEventsHandled(bool nonexistentOnly = false); + bool eventHandled(const KCal::Event*, const QValueList&); + + bool loadFile(bool reset); + bool setLoadedConnected(); // check status of mLoadedConnected and set it to true + bool downloading() const { return !mTempFileName.isNull(); } + bool loaded() const { return mLoaded; } + + + static ConstIterator begin() { return mCalendars.begin(); } + static ConstIterator end() { return mCalendars.end(); } + static ADCalendar* getCalendar(const QString& calendarURL); + + signals: + void loaded(ADCalendar*, bool success); + + protected: + // Only ClientInfo can construct ADCalendar objects + friend class ClientInfo; + ADCalendar(const QString& url, const QCString& appname); + + private slots: + void slotDownloadJobResult(KIO::Job*); + + private: + struct EventKey + { + EventKey() : calendarIndex(-1) { } + EventKey(const QString& id, int cal) : eventID(id), calendarIndex(cal) { } + bool operator<(const EventKey& k) const + { return (calendarIndex == k.calendarIndex) + ? (eventID < k.eventID) : (calendarIndex < k.calendarIndex); + } + QString eventID; + int calendarIndex; + }; + struct EventItem + { + EventItem() : eventSequence(0) { } + EventItem(int seqno, const QValueList& alarmtimes) + : eventSequence(seqno), alarmTimes(alarmtimes) {} + int eventSequence; + QValueList alarmTimes; + }; + + typedef QMap EventsMap; // calendar/event ID, event sequence num + static EventsMap mEventsHandled; // IDs of already triggered events which have been processed by KAlarm + static EventsMap mEventsPending; // IDs of already triggered events not yet processed by KAlarm + static QStringList mCalendarUrls; // URLs of all calendars ever opened + static QValueList mCalendars; // list of all constructed calendars + + ADCalendar(const ADCalendar&); // prohibit copying + ADCalendar& operator=(const ADCalendar&); // prohibit copying + + void loadLocalFile(const QString& filename); + void clearEventMap(EventsMap&, bool nonexistentOnly); + void setEventInMap(EventsMap&, const EventKey&, const QValueList& alarmtimes, int sequence); + + QString mUrlString; // calendar file URL + QCString mAppName; // name of application owning this calendar + QString mTempFileName; // temporary file used if currently downloading, else null + int mUrlIndex; // unique index to URL in mCalendarUrls + bool mLoaded; // true if calendar file is currently loaded + bool mLoadedConnected; // true if the loaded() signal has been connected to AlarmDaemon + bool mUnregistered; // client has registered, but has not since added the calendar + bool mEnabled; // events are currently manually enabled +}; + +#endif // ADCALENDAR_H diff --git a/kalarm/kalarmd/adconfigdata.cpp b/kalarm/kalarmd/adconfigdata.cpp new file mode 100644 index 000000000..11dc6bf67 --- /dev/null +++ b/kalarm/kalarmd/adconfigdata.cpp @@ -0,0 +1,146 @@ +/* + * adcalendar.cpp - configuration file access + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (C) 2001, 2004 by 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 "kalarmd.h" + +#include +#include + +#include +#include +#include + +#include "adcalendar.h" +#include "adconfigdata.h" + +// Config file key strings +const QString CLIENT_GROUP(QString::fromLatin1("Client ")); +const QRegExp CLIENT_GROUP_SEARCH("^Client "); +// Client data file key strings +const QString CALENDAR_KEY(QString::fromLatin1("Calendar")); +const QString TITLE_KEY(QString::fromLatin1("Title")); +const QString DCOP_OBJECT_KEY(QString::fromLatin1("DCOP object")); +const QString START_CLIENT_KEY(QString::fromLatin1("Start")); + + +/****************************************************************************** +* Read the configuration file. +* Create the client list and open all calendar files. +*/ +void ADConfigData::readConfig() +{ + kdDebug(5900) << "ADConfigData::readConfig()" << endl; + ClientInfo::clear(); + KConfig* config = KGlobal::config(); + QStringList clients = config->groupList().grep(CLIENT_GROUP_SEARCH); + for (QStringList::Iterator cl = clients.begin(); cl != clients.end(); ++cl) + { + // Read this client's configuration + config->setGroup(*cl); + QString client = *cl; + client.remove(CLIENT_GROUP_SEARCH); + QString title = config->readEntry(TITLE_KEY, client); // read app title (default = app name) + QCString dcopObject = config->readEntry(DCOP_OBJECT_KEY).local8Bit(); + bool startClient = config->readBoolEntry(START_CLIENT_KEY, false); + QString calendar = config->readPathEntry(CALENDAR_KEY); + + // Verify the configuration + bool ok = false; + if (client.isEmpty() || KStandardDirs::findExe(client).isNull()) + kdError(5900) << "ADConfigData::readConfig(): group '" << *cl << "' deleted (client app not found)\n"; + else if (calendar.isEmpty()) + kdError(5900) << "ADConfigData::readConfig(): no calendar specified for '" << client << "'\n"; + else if (dcopObject.isEmpty()) + kdError(5900) << "ADConfigData::readConfig(): no DCOP object specified for '" << client << "'\n"; + else + { + ADCalendar* cal = ADCalendar::getCalendar(calendar); + if (cal) + kdError(5900) << "ADConfigData::readConfig(): calendar registered by multiple clients: " << calendar << endl; + else + ok = true; + } + if (!ok) + { + config->deleteGroup(*cl, true); + continue; + } + + // Create the client and calendar objects + new ClientInfo(client.local8Bit(), title, dcopObject, calendar, startClient); + kdDebug(5900) << "ADConfigData::readConfig(): client " << client << " : calendar " << calendar << endl; + } + + // Remove obsolete CheckInterval entry (if it exists) + config->setGroup("General"); + config->deleteEntry("CheckInterval"); + + // Save any updates + config->sync(); +} + +/****************************************************************************** +* Write a client application's details to the config file. +*/ +void ADConfigData::writeClient(const QCString& appName, const ClientInfo* cinfo) +{ + KConfig* config = KGlobal::config(); + config->setGroup(CLIENT_GROUP + QString::fromLocal8Bit(appName)); + config->writeEntry(TITLE_KEY, cinfo->title()); + config->writeEntry(DCOP_OBJECT_KEY, QString::fromLocal8Bit(cinfo->dcopObject())); + config->writeEntry(START_CLIENT_KEY, cinfo->startClient()); + config->writePathEntry(CALENDAR_KEY, cinfo->calendar()->urlString()); + config->sync(); +} + +/****************************************************************************** +* Remove a client application's details from the config file. +*/ +void ADConfigData::removeClient(const QCString& appName) +{ + KConfig* config = KGlobal::config(); + config->deleteGroup(CLIENT_GROUP + QString::fromLocal8Bit(appName)); + config->sync(); +} + +/****************************************************************************** +* Set the calendar file URL for a specified application. +*/ +void ADConfigData::setCalendar(const QCString& appName, ADCalendar* cal) +{ + KConfig* config = KGlobal::config(); + config->setGroup(CLIENT_GROUP + QString::fromLocal8Bit(appName)); + config->writePathEntry(CALENDAR_KEY, cal->urlString()); + config->sync(); +} + +/****************************************************************************** +* DCOP call to set autostart at login on or off. +*/ +void ADConfigData::enableAutoStart(bool on) +{ + kdDebug(5900) << "ADConfigData::enableAutoStart(" << on << ")\n"; + KConfig* config = KGlobal::config(); + config->reparseConfiguration(); + config->setGroup(QString::fromLatin1(DAEMON_AUTOSTART_SECTION)); + config->writeEntry(QString::fromLatin1(DAEMON_AUTOSTART_KEY), on); + config->sync(); +} + diff --git a/kalarm/kalarmd/adconfigdata.h b/kalarm/kalarmd/adconfigdata.h new file mode 100644 index 000000000..2c841fdff --- /dev/null +++ b/kalarm/kalarmd/adconfigdata.h @@ -0,0 +1,41 @@ +/* + * adconfigdata.h - configuration file access + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (C) 2001, 2004 by David Jarvie + * Based on the original, (c) 1998, 1999 Preston Brown + * + * 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. + */ + +#ifndef ADCONFIGDATA_H +#define ADCONFIGDATA_H + +#include "clientinfo.h" + +class ADCalendar; +class ClientInfo; + + +class ADConfigData +{ + public: + static void readConfig(); + static void writeClient(const QCString& appName, const ClientInfo*); + static void removeClient(const QCString& appName); + static void setCalendar(const QCString& appName, ADCalendar*); + static void enableAutoStart(bool); +}; + +#endif // ADCONFIGDATA_H diff --git a/kalarm/kalarmd/admain.cpp b/kalarm/kalarmd/admain.cpp new file mode 100644 index 000000000..c0a7792f1 --- /dev/null +++ b/kalarm/kalarmd/admain.cpp @@ -0,0 +1,59 @@ +/* + * admain.cpp - kalarmd main program + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (c) 2001,2004,2005 by David Jarvie + * Copyright (c) 2000,2001 Cornelius Schumacher + * Copyright (c) 1997-1999 Preston Brown + * + * 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 "kalarmd.h" + +#include + +#include +#include +#include +#include + +#include "adapp.h" + +static const KCmdLineOptions options[] = +{ + { "autostart", "", 0 }, + KCmdLineLastOption +}; + +int main(int argc, char** argv) +{ + KAboutData aboutData(DAEMON_APP_NAME, I18N_NOOP("KAlarm Daemon"), + DAEMON_VERSION, I18N_NOOP("KAlarm Alarm Daemon"), KAboutData::License_GPL, + "Copyright 1997-1999 Preston Brown\nCopyright 2000-2001 Cornelius Schumacher\nCopyright 2001,2004-2007 David Jarvie", 0, + "http://www.astrojar.org.uk/kalarm"); + aboutData.addAuthor("David Jarvie", I18N_NOOP("Maintainer"), "software@astrojar.org.uk"); + aboutData.addAuthor("Cornelius Schumacher", I18N_NOOP("Author"), "schumacher@kde.org"); + aboutData.addAuthor("Preston Brown", I18N_NOOP("Original Author"), "pbrown@kde.org"); + + KCmdLineArgs::init(argc, argv, &aboutData); + KCmdLineArgs::addCmdLineOptions(options); + KUniqueApplication::addCmdLineOptions(); + KStartupInfo::disableAutoAppStartedSending(); + + if (!AlarmDaemonApp::start()) + exit(0); + AlarmDaemonApp app; + return app.exec(); +} diff --git a/kalarm/kalarmd/alarmdaemon.cpp b/kalarm/kalarmd/alarmdaemon.cpp new file mode 100644 index 000000000..576678574 --- /dev/null +++ b/kalarm/kalarmd/alarmdaemon.cpp @@ -0,0 +1,614 @@ +/* + * alarmdaemon.cpp - alarm daemon control routines + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright © 2001,2004-2007 by David Jarvie + * Based on the original, (c) 1998, 1999 Preston Brown + * + * 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 "kalarmd.h" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "adcalendar.h" +#include "adconfigdata.h" +#include "alarmguiiface.h" +#include "alarmguiiface_stub.h" +#include "alarmdaemon.moc" + + +#ifdef AUTOSTART_KALARM +// Number of seconds to wait before autostarting KAlarm. +// Allow plenty of time for session restoration to happen first. +static const int KALARM_AUTOSTART_TIMEOUT = 30; +#endif +static const int SECS_PER_DAY = 3600 * 24; + +// KAlarm config file keys +static const QString START_OF_DAY(QString::fromLatin1("StartOfDay")); +static const QString AUTOSTART_TRAY(QString::fromLatin1("AutostartTray")); + + +AlarmDaemon::AlarmDaemon(bool autostart, QObject *parent, const char *name) + : DCOPObject(name), + QObject(parent, name), + mAlarmTimer(0) +{ + kdDebug(5900) << "AlarmDaemon::AlarmDaemon()" << endl; + ADConfigData::readConfig(); + + ADConfigData::enableAutoStart(true); // switch autostart on whenever the program is run + + readKAlarmConfig(); // read time-related KAlarm config items + +#ifdef AUTOSTART_KALARM + if (autostart) + { + /* The alarm daemon is being autostarted. + * Check if KAlarm needs to be autostarted in the system tray. + * This should ideally be handled internally by KAlarm, but is done by kalarmd + * for the following reason: + * KAlarm needs to be both session restored and autostarted, but KDE doesn't + * currently cater properly for this - there is no guarantee that the session + * restoration activation will come before the autostart activation. If they + * come in the wrong order, KAlarm won't know that it is supposed to restore + * itself and instead will simply open a new window. + */ + KConfig kaconfig(locate("config", "kalarmrc")); + kaconfig.setGroup(QString::fromLatin1("General")); + autostart = kaconfig.readBoolEntry(AUTOSTART_TRAY, false); + if (autostart) + { + kdDebug(5900) << "AlarmDaemon::AlarmDaemon(): wait to autostart KAlarm\n"; + QTimer::singleShot(KALARM_AUTOSTART_TIMEOUT * 1000, this, SLOT(autostartKAlarm())); + } + } + if (!autostart) +#endif + startMonitoring(); // otherwise, start monitoring alarms now +} + +/****************************************************************************** +* DCOP call to quit the program. +*/ +void AlarmDaemon::quit() +{ + kdDebug(5900) << "AlarmDaemon::quit()" << endl; + exit(0); +} + +/****************************************************************************** +* Called after a timer delay to autostart KAlarm in the system tray. +*/ +void AlarmDaemon::autostartKAlarm() +{ +#ifdef AUTOSTART_KALARM + if (mAlarmTimer) + { + kdDebug(5900) << "AlarmDaemon::autostartKAlarm(): KAlarm already registered\n"; + return; // KAlarm has already registered with us + } + kdDebug(5900) << "AlarmDaemon::autostartKAlarm(): starting KAlarm\n"; + QStringList args; + args << QString::fromLatin1("--tray"); + int ret = KApplication::kdeinitExec(QString::fromLatin1("kalarm"), args); + if (ret) + kdError(5900) << "AlarmDaemon::autostartKAlarm(): error=" << ret << endl; + else + kdDebug(5900) << "AlarmDaemon::autostartKAlarm(): success" << endl; + + startMonitoring(); +#endif +} + +/****************************************************************************** +* Start monitoring alarms. +*/ +void AlarmDaemon::startMonitoring() +{ + // Set up the alarm timer + mAlarmTimer = new QTimer(this); + connect(mAlarmTimer, SIGNAL(timeout()), SLOT(checkAlarmsSlot())); + setTimerStatus(); + + // Start monitoring calendar files. + // They are monitored until their client application registers, upon which + // monitoring ceases until KAlarm tells the daemon to monitor it. + checkAlarms(); +} + +/****************************************************************************** +* DCOP call to enable or disable monitoring of a calendar. +*/ +void AlarmDaemon::enableCal(const QString& urlString, bool enable) +{ + kdDebug(5900) << "AlarmDaemon::enableCal(" << urlString << ")" << endl; + ADCalendar* cal = ADCalendar::getCalendar(urlString); + if (cal) + { + cal->setEnabled(enable); + notifyCalStatus(cal); // notify KAlarm + } +} + +/****************************************************************************** +* DCOP call to reload, and optionally reset, the specified calendar. +*/ +void AlarmDaemon::reloadCal(const QCString& appname, const QString& urlString, bool reset) +{ + kdDebug(5900) << "AlarmDaemon::reloadCal(" << urlString << ")" << endl; + ADCalendar* cal = ADCalendar::getCalendar(urlString); + if (!cal || cal->appName() != appname) + return; + reloadCal(cal, reset); +} + +/****************************************************************************** +* Reload the specified calendar. +* If 'reset' is true, the data associated with the calendar is reset. +*/ +void AlarmDaemon::reloadCal(ADCalendar* cal, bool reset) +{ + kdDebug(5900) << "AlarmDaemon::reloadCal(): calendar" << endl; + if (!cal) + return; + if (!cal->downloading()) + { + cal->close(); + if (!cal->setLoadedConnected()) + connect(cal, SIGNAL(loaded(ADCalendar*, bool)), SLOT(calendarLoaded(ADCalendar*, bool))); + cal->loadFile(reset); + } + else if (reset) + cal->clearEventsHandled(); +} + +void AlarmDaemon::calendarLoaded(ADCalendar* cal, bool success) +{ + if (success) + kdDebug(5900) << "Calendar reloaded" << endl; + notifyCalStatus(cal); // notify KAlarm + setTimerStatus(); + checkAlarms(cal); +} + +/****************************************************************************** +* DCOP call to notify the daemon that an event has been handled, and optionally +* to tell it to reload the calendar. +*/ +void AlarmDaemon::eventHandled(const QCString& appname, const QString& calendarUrl, const QString& eventID, bool reload) +{ + QString urlString = expandURL(calendarUrl); + kdDebug(5900) << "AlarmDaemon::eventHandled(" << urlString << (reload ? "): reload" : ")") << endl; + ADCalendar* cal = ADCalendar::getCalendar(urlString); + if (!cal || cal->appName() != appname) + return; + cal->setEventHandled(eventID); + if (reload) + reloadCal(cal, false); +} + +/****************************************************************************** +* DCOP call to add an application to the list of client applications, +* and add it to the config file. +* N.B. This method must not return a bool because DCOPClient::call() can cause +* a hang if the daemon happens to send a notification to KAlarm at the +* same time as KAlarm calls this DCCOP method. +*/ +void AlarmDaemon::registerApp(const QCString& appName, const QString& appTitle, + const QCString& dcopObject, const QString& calendarUrl, + bool startClient) +{ + kdDebug(5900) << "AlarmDaemon::registerApp(" << appName << ", " << appTitle << ", " + << dcopObject << ", " << startClient << ")" << endl; + KAlarmd::RegisterResult result; + if (appName.isEmpty()) + result = KAlarmd::FAILURE; + else if (startClient && KStandardDirs::findExe(appName).isNull()) + { + kdError() << "AlarmDaemon::registerApp(): app not found" << endl; + result = KAlarmd::NOT_FOUND; + } + else + { + ADCalendar* keepCal = 0; + ClientInfo* client = ClientInfo::get(appName); + if (client) + { + // The application is already a client. + // If it's the same calendar file, don't delete its calendar object. + if (client->calendar() && client->calendar()->urlString() == calendarUrl) + { + keepCal = client->calendar(); + client->detachCalendar(); + } + ClientInfo::remove(appName); // this deletes the calendar if not detached + } + + if (keepCal) + client = new ClientInfo(appName, appTitle, dcopObject, keepCal, startClient); + else + client = new ClientInfo(appName, appTitle, dcopObject, calendarUrl, startClient); + client->calendar()->setUnregistered(false); + ADConfigData::writeClient(appName, client); + + ADConfigData::enableAutoStart(true); + setTimerStatus(); + notifyCalStatus(client->calendar()); + result = KAlarmd::SUCCESS; + } + + // Notify the client of whether the call succeeded. + AlarmGuiIface_stub stub(appName, dcopObject); + stub.registered(false, result, DAEMON_VERSION_NUM); + kdDebug(5900) << "AlarmDaemon::registerApp() -> " << result << endl; +} + +/****************************************************************************** +* DCOP call to change whether KAlarm should be started when an event needs to +* be notified to it. +* N.B. This method must not return a bool because DCOPClient::call() can cause +* a hang if the daemon happens to send a notification to KAlarm at the +* same time as KAlarm calls this DCCOP method. +*/ +void AlarmDaemon::registerChange(const QCString& appName, bool startClient) +{ + kdDebug(5900) << "AlarmDaemon::registerChange(" << appName << ", " << startClient << ")" << endl; + KAlarmd::RegisterResult result; + ClientInfo* client = ClientInfo::get(appName); + if (!client) + return; // can't access client to tell it the result + if (startClient && KStandardDirs::findExe(appName).isNull()) + { + kdError() << "AlarmDaemon::registerChange(): app not found" << endl; + result = KAlarmd::NOT_FOUND; + } + else + { + client->setStartClient(startClient); + ADConfigData::writeClient(appName, client); + result = KAlarmd::SUCCESS; + } + + // Notify the client of whether the call succeeded. + AlarmGuiIface_stub stub(appName, client->dcopObject()); + stub.registered(true, result, DAEMON_VERSION_NUM); + kdDebug(5900) << "AlarmDaemon::registerChange() -> " << result << endl; +} + +/****************************************************************************** +* DCOP call to set autostart at login on or off. +*/ +void AlarmDaemon::enableAutoStart(bool on) +{ + ADConfigData::enableAutoStart(on); +} + +/****************************************************************************** +* Check if any alarms are pending for any enabled calendar, and display the +* pending alarms. +* Called by the alarm timer. +*/ +void AlarmDaemon::checkAlarmsSlot() +{ + kdDebug(5901) << "AlarmDaemon::checkAlarmsSlot()" << endl; + if (mAlarmTimerSyncing) + { + // We've synched to the minute boundary. Now set timer to the check interval. + mAlarmTimer->changeInterval(DAEMON_CHECK_INTERVAL * 1000); + mAlarmTimerSyncing = false; + mAlarmTimerSyncCount = 10; // resynch every 10 minutes, in case of glitches + } + else if (--mAlarmTimerSyncCount <= 0) + { + int interval = DAEMON_CHECK_INTERVAL + 1 - QTime::currentTime().second(); + if (interval < DAEMON_CHECK_INTERVAL - 1) + { + // Need to re-synch to 1 second past the minute + mAlarmTimer->changeInterval(interval * 1000); + mAlarmTimerSyncing = true; + kdDebug(5900) << "Resynching alarm timer" << endl; + } + else + mAlarmTimerSyncCount = 10; + } + checkAlarms(); +} + +/****************************************************************************** +* Check if any alarms are pending for any enabled calendar, and display the +* pending alarms. +*/ +void AlarmDaemon::checkAlarms() +{ + kdDebug(5901) << "AlarmDaemon::checkAlarms()" << endl; + for (ADCalendar::ConstIterator it = ADCalendar::begin(); it != ADCalendar::end(); ++it) + checkAlarms(*it); +} + +/****************************************************************************** +* Check if any alarms are pending for a specified calendar, and display the +* pending alarms. +*/ +void AlarmDaemon::checkAlarms(ADCalendar* cal) +{ + kdDebug(5901) << "AlarmDaemons::checkAlarms(" << cal->urlString() << ")" << endl; + if (!cal->loaded() || !cal->enabled()) + return; + + QDateTime now = QDateTime::currentDateTime(); + kdDebug(5901) << " To: " << now.toString() << endl; + QValueList alarms = cal->alarmsTo(now); + if (!alarms.count()) + return; + QValueList eventsDone; + for (QValueList::ConstIterator it = alarms.begin(); it != alarms.end(); ++it) + { + KCal::Event* event = dynamic_cast((*it)->parent()); + if (!event || eventsDone.find(event) != eventsDone.end()) + continue; // either not an event, or the event has already been processed + eventsDone += event; + const QString& eventID = event->uid(); + kdDebug(5901) << "AlarmDaemon::checkAlarms(): event " << eventID << endl; + + // Check which of the alarms for this event are due. + // The times in 'alarmtimes' corresponding to due alarms are set. + // The times for non-due alarms are set invalid in 'alarmtimes'. + bool recurs = event->doesRecur(); + const QStringList cats = event->categories(); + bool floats = (cats.find(QString::fromLatin1("DATE")) != cats.end()); + QDateTime nextDateTime = event->dtStart(); + if (recurs) + { + QString prop = event->customProperty("KALARM", "NEXTRECUR"); + if (prop.length() >= 8) + { + // The next due recurrence time is specified + QDate d(prop.left(4).toInt(), prop.mid(4,2).toInt(), prop.mid(6,2).toInt()); + if (d.isValid()) + { + if (floats && prop.length() == 8) + nextDateTime = d; + else if (!floats && prop.length() == 15 && prop[8] == QChar('T')) + { + QTime t(prop.mid(9,2).toInt(), prop.mid(11,2).toInt(), prop.mid(13,2).toInt()); + if (t.isValid()) + nextDateTime = QDateTime(d, t); + } + } + } + } + if (floats) + nextDateTime.setTime(mStartOfDay); + QValueList alarmtimes; + KCal::Alarm::List alarms = event->alarms(); + for (KCal::Alarm::List::ConstIterator al = alarms.begin(); al != alarms.end(); ++al) + { + KCal::Alarm* alarm = *al; + QDateTime dt; + if (alarm->enabled()) + { + QDateTime dt1; + if (!alarm->hasTime()) + { + // Find the latest recurrence for the alarm. + // Need to do this for alarms with offsets in order to detect + // reminders due for recurrences. + int offset = alarm->hasStartOffset() ? alarm->startOffset().asSeconds() + : alarm->endOffset().asSeconds() + event->dtStart().secsTo(event->dtEnd()); + if (offset) + { + dt1 = nextDateTime.addSecs(floats ? (offset/SECS_PER_DAY)*SECS_PER_DAY : offset); + if (dt1 > now) + dt1 = QDateTime(); + } + } + // Get latest due repetition, or the recurrence time if none + dt = nextDateTime; + if (nextDateTime <= now && alarm->repeatCount() > 0) + { + int snoozeSecs = alarm->snoozeTime() * 60; + int offset = alarm->repeatCount() * snoozeSecs; + QDateTime lastRepetition = nextDateTime.addSecs(floats ? (offset/SECS_PER_DAY)*SECS_PER_DAY : offset); + if (lastRepetition <= now) + dt = lastRepetition; + else + { + int repetition = nextDateTime.secsTo(now) / snoozeSecs; + int offset = repetition * snoozeSecs; + dt = nextDateTime.addSecs(floats ? (offset/SECS_PER_DAY)*SECS_PER_DAY : offset); + } + } + if (!dt.isValid() || dt > now + || dt1.isValid() && dt1 > dt) // already tested dt1 <= now + dt = dt1; + } + alarmtimes.append(dt); + } + if (!cal->eventHandled(event, alarmtimes)) + { + if (notifyEvent(cal, eventID)) + cal->setEventPending(event, alarmtimes); + } + } +} + +/****************************************************************************** +* Send a DCOP message to KAlarm telling it that an alarm should now be handled. +* Reply = false if the event should be held pending until KAlarm can be started. +*/ +bool AlarmDaemon::notifyEvent(ADCalendar* calendar, const QString& eventID) +{ + if (!calendar) + return true; + QCString appname = calendar->appName(); + const ClientInfo* client = ClientInfo::get(appname); + if (!client) + { + kdDebug(5900) << "AlarmDaemon::notifyEvent(" << appname << "): unknown client" << endl; + return false; + } + kdDebug(5900) << "AlarmDaemon::notifyEvent(" << appname << ", " << eventID << "): notification type=" << client->startClient() << endl; + QString id = QString::fromLatin1("ad:") + eventID; // prefix to indicate that the notification if from the daemon + + // Check if the client application is running and ready to receive notification + bool registered = kapp->dcopClient()->isApplicationRegistered(static_cast(appname)); + bool ready = registered; + if (registered) + { + // It's running, but check if it has created our DCOP interface yet + QCStringList objects = kapp->dcopClient()->remoteObjects(appname); + if (objects.find(client->dcopObject()) == objects.end()) + ready = false; + } + if (!ready) + { + // KAlarm is not running, or is not yet ready to receive notifications. + if (!client->startClient()) + { + if (registered) + kdDebug(5900) << "AlarmDaemon::notifyEvent(): client not ready\n"; + else + kdDebug(5900) << "AlarmDaemon::notifyEvent(): don't start client\n"; + return false; + } + + // Start KAlarm, using the command line to specify the alarm + KProcess p; + QString cmd = locate("exe", appname); + if (cmd.isEmpty()) + { + kdDebug(5900) << "AlarmDaemon::notifyEvent(): '" << appname << "' not found" << endl; + return true; + } + p << cmd; + p << "--handleEvent" << id << "--calendarURL" << calendar->urlString(); + p.start(KProcess::DontCare); + kdDebug(5900) << "AlarmDaemon::notifyEvent(): used command line" << endl; + return true; + } + + // Notify the client by telling it the calendar URL and event ID + AlarmGuiIface_stub stub(appname, client->dcopObject()); + stub.handleEvent(calendar->urlString(), id); + if (!stub.ok()) + { + kdDebug(5900) << "AlarmDaemon::notifyEvent(): dcop send failed" << endl; + return false; + } + return true; +} + +/****************************************************************************** +* Starts or stops the alarm timer as necessary after a calendar is enabled/disabled. +*/ +void AlarmDaemon::setTimerStatus() +{ +#ifdef AUTOSTART_KALARM + if (!mAlarmTimer) + { + // KAlarm is now running, so start monitoring alarms + startMonitoring(); + return; // startMonitoring() calls this method + } + +#endif + // Count the number of currently loaded calendars whose names should be displayed + int nLoaded = 0; + for (ADCalendar::ConstIterator it = ADCalendar::begin(); it != ADCalendar::end(); ++it) + if ((*it)->loaded()) + ++nLoaded; + + // Start or stop the alarm timer if necessary + if (!mAlarmTimer->isActive() && nLoaded) + { + // Timeout every minute. + // But first synchronise to one second after the minute boundary. + int firstInterval = DAEMON_CHECK_INTERVAL + 1 - QTime::currentTime().second(); + mAlarmTimer->start(1000 * firstInterval); + mAlarmTimerSyncing = (firstInterval != DAEMON_CHECK_INTERVAL); + kdDebug(5900) << "Started alarm timer" << endl; + } + else if (mAlarmTimer->isActive() && !nLoaded) + { + mAlarmTimer->stop(); + kdDebug(5900) << "Stopped alarm timer" << endl; + } +} + +/****************************************************************************** +* Send a DCOP message to to the client which owns the specified calendar, +* notifying it of a change in calendar status. +*/ +void AlarmDaemon::notifyCalStatus(const ADCalendar* cal) +{ + ClientInfo* client = ClientInfo::get(cal); + if (!client) + return; + QCString appname = client->appName(); + if (kapp->dcopClient()->isApplicationRegistered(static_cast(appname))) + { + KAlarmd::CalendarStatus change = cal->available() ? (cal->enabled() ? KAlarmd::CALENDAR_ENABLED : KAlarmd::CALENDAR_DISABLED) + : KAlarmd::CALENDAR_UNAVAILABLE; + kdDebug(5900) << "AlarmDaemon::notifyCalStatus() sending:" << appname << " -> " << change << endl; + AlarmGuiIface_stub stub(appname, client->dcopObject()); + stub.alarmDaemonUpdate(change, cal->urlString()); + if (!stub.ok()) + kdError(5900) << "AlarmDaemon::notifyCalStatus(): dcop send failed:" << appname << endl; + } +} + +/****************************************************************************** +* Read all relevant items from KAlarm config. +* Executed on DCOP call to notify a time related value change in the KAlarm +* config file. +*/ +void AlarmDaemon::readKAlarmConfig() +{ + KConfig config(locate("config", "kalarmrc")); + config.setGroup(QString::fromLatin1("General")); + QDateTime defTime(QDate(1900,1,1), QTime()); + mStartOfDay = config.readDateTimeEntry(START_OF_DAY, &defTime).time(); + kdDebug(5900) << "AlarmDaemon::readKAlarmConfig()" << endl; +} + +/****************************************************************************** +* Expand a DCOP call parameter URL to a full URL. +* (We must store full URLs in the calendar data since otherwise later calls to +* reload or remove calendars won't necessarily find a match.) +*/ +QString AlarmDaemon::expandURL(const QString& urlString) +{ + if (urlString.isEmpty()) + return QString(); + return KURL(urlString).url(); +} diff --git a/kalarm/kalarmd/alarmdaemon.h b/kalarm/kalarmd/alarmdaemon.h new file mode 100644 index 000000000..6ab5d0f9a --- /dev/null +++ b/kalarm/kalarmd/alarmdaemon.h @@ -0,0 +1,79 @@ +/* + * alarmdaemon.h - alarm daemon control routines + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright © 2001,2004-2007 by David Jarvie + * Based on the original, (c) 1998, 1999 Preston Brown + * + * 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. + */ + +#ifndef ALARMDAEMON_H +#define ALARMDAEMON_H + +#include + +#include "alarmdaemoniface.h" + +class ADCalendar; + + +class AlarmDaemon : public QObject, virtual public AlarmDaemonIface +{ + Q_OBJECT + public: + AlarmDaemon(bool autostart, QObject* parent = 0, const char* name = 0); + + private slots: +//#ifdef AUTOSTART_KALARM + void autostartKAlarm(); +//#endif + void calendarLoaded(ADCalendar*, bool success); + void checkAlarmsSlot(); + void checkAlarms(); + + private: + // DCOP interface + void enableAutoStart(bool enable); + void enableCalendar(const QString& urlString, bool enable) + { enableCal(expandURL(urlString), enable); } + void reloadCalendar(const QCString& appname, const QString& urlString) + { reloadCal(appname, expandURL(urlString), false); } + void resetCalendar(const QCString& appname, const QString& urlString) + { reloadCal(appname, expandURL(urlString), true); } + void registerApp(const QCString& appName, const QString& appTitle, const QCString& dcopObject, + const QString& calendarUrl, bool startClient); + void registerChange(const QCString& appName, bool startClient); + void eventHandled(const QCString& appname, const QString& calendarURL, const QString& eventID, bool reload); + void timeConfigChanged() { readKAlarmConfig(); } + void quit(); + // Other methods + void readKAlarmConfig(); + void startMonitoring(); + void enableCal(const QString& urlString, bool enable); + void reloadCal(const QCString& appname, const QString& urlString, bool reset); + void reloadCal(ADCalendar*, bool reset); + void checkAlarms(ADCalendar*); + bool notifyEvent(ADCalendar*, const QString& eventID); + void notifyCalStatus(const ADCalendar*); + void setTimerStatus(); + static QString expandURL(const QString& urlString); + + QTimer* mAlarmTimer; + int mAlarmTimerSyncCount; // countdown to re-synching the alarm timer + bool mAlarmTimerSyncing; // true while alarm timer interval < 1 minute + QTime mStartOfDay; // start of day for date-only alarms +}; + +#endif // ALARMDAEMON_H diff --git a/kalarm/kalarmd/alarmdaemoniface.h b/kalarm/kalarmd/alarmdaemoniface.h new file mode 100644 index 000000000..708f5b281 --- /dev/null +++ b/kalarm/kalarmd/alarmdaemoniface.h @@ -0,0 +1,45 @@ +/* + * alarmdaemoniface.h - DCOP request interface + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright © 2001,2004-2007 by David Jarvie + * Copyright (c) 2000,2001 Cornelius Schumacher + * Copyright (c) 1997-1999 Preston Brown + * + * 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. + */ + +#ifndef ALARMDAEMONIFACE_H +#define ALARMDAEMONIFACE_H + +#include + + +class AlarmDaemonIface : virtual public DCOPObject +{ + K_DCOP + k_dcop: + virtual ASYNC enableAutoStart(bool enable) = 0; + virtual ASYNC enableCalendar(const QString& urlString, bool enable) = 0; + virtual ASYNC reloadCalendar(const QCString& appname, const QString& urlString) = 0; + virtual ASYNC resetCalendar(const QCString& appname, const QString& urlString) = 0; + virtual ASYNC registerApp(const QCString& appName, const QString& appTitle, const QCString& dcopObject, + const QString& calendarUrl, bool startClient) = 0; + virtual ASYNC registerChange(const QCString& appName, bool startClient) = 0; + virtual ASYNC eventHandled(const QCString& appname, const QString& calendarURL, const QString& eventID, bool reload) = 0; + virtual ASYNC timeConfigChanged() = 0; + virtual ASYNC quit() = 0; +}; + +#endif diff --git a/kalarm/kalarmd/alarmguiiface.h b/kalarm/kalarmd/alarmguiiface.h new file mode 100644 index 000000000..7e98c831d --- /dev/null +++ b/kalarm/kalarmd/alarmguiiface.h @@ -0,0 +1,71 @@ +/* + * alarmguiiface.h - DCOP interface which alarm daemon clients must implement + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright © 2001,2004,2007 by David Jarvie + * Based on the original, (c) 1998, 1999 Preston Brown + * Copyright (c) 2000,2001 Cornelius Schumacher + * Copyright (c) 1997-1999 Preston Brown + * + * 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. + */ + +#ifndef DAEMONGUIIFACE_H +#define DAEMONGUIIFACE_H + +#include + +namespace KAlarmd +{ + enum RegisterResult // result code of registerApp() DCOP call + { + FAILURE = 0, + SUCCESS = 1, + NOT_FOUND = 2 // notification type requires client start, but client executable not found + }; + + enum CalendarStatus // parameters to client notification + { + CALENDAR_ENABLED, // calendar is now being monitored + CALENDAR_DISABLED, // calendar is available but not being monitored + CALENDAR_UNAVAILABLE // calendar is unavailable for monitoring + }; +} + +/*============================================================================= += Class: AlarmGuiIface += Client applications should inherit from this class to receive notifications +* from the alarm daemon. +=============================================================================*/ +class AlarmGuiIface : virtual public DCOPObject +{ + K_DCOP + k_dcop: + /** Called to notify a change in status of the calendar. + @param calendarStatus new calendar status. Value is of type CalendarStatus. + */ + virtual ASYNC alarmDaemonUpdate(int calendarStatus, const QString& calendarURL) = 0; + + /** Called to notify that an alarm is due. + */ + virtual ASYNC handleEvent(const QString& calendarURL, const QString& eventID) = 0; + + /** Called to indicate success/failure of (re)register() call. + @param result success/failure code. Value is of type RegisterResult. + @param version kalarmd version, e.g. 50101 indicates 5.1.1. + */ + virtual ASYNC registered(bool reregister, int result, int version) = 0; +}; + +#endif diff --git a/kalarm/kalarmd/clientinfo.cpp b/kalarm/kalarmd/clientinfo.cpp new file mode 100644 index 000000000..21889082e --- /dev/null +++ b/kalarm/kalarmd/clientinfo.cpp @@ -0,0 +1,110 @@ +/* + * clientinfo.cpp - client application information + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (C) 2001, 2004 by 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 "adcalendar.h" +#include "clientinfo.h" + +QMap ClientInfo::mClients; + + +ClientInfo::ClientInfo(const QCString& appName, const QString& title, + const QCString& dcopObj, const QString& calendar, bool startClient) + : mAppName(appName), + mTitle(title), + mDcopObject(dcopObj), + mCalendar(new ADCalendar(calendar, appName)), + mStartClient(startClient) +{ + mClients[mAppName] = this; +} + +ClientInfo::ClientInfo(const QCString& appName, const QString& title, + const QCString& dcopObj, ADCalendar* calendar, bool startClient) + : mAppName(appName), + mTitle(title), + mDcopObject(dcopObj), + mCalendar(calendar), + mStartClient(startClient) +{ + mClients[mAppName] = this; +} + +ClientInfo::~ClientInfo() +{ + delete mCalendar; + mClients.remove(mAppName); +} + +/****************************************************************************** +* Set a new calendar for the specified client application. +*/ +ADCalendar* ClientInfo::setCalendar(const QString& url) +{ + if (url != mCalendar->urlString()) + { + delete mCalendar; + mCalendar = new ADCalendar(url, mAppName); + } + return mCalendar; +} + +/****************************************************************************** +* Return the ClientInfo object for the specified client application. +*/ +ClientInfo* ClientInfo::get(const QCString& appName) +{ + if (appName.isEmpty()) + return 0; + QMap::ConstIterator it = mClients.find(appName); + if (it == mClients.end()) + return 0; + return it.data(); +} + +/****************************************************************************** +* Return the ClientInfo object for client which owns the specified calendar. +*/ +ClientInfo* ClientInfo::get(const ADCalendar* cal) +{ + for (ClientInfo::ConstIterator it = ClientInfo::begin(); it != ClientInfo::end(); ++it) + if (it.data()->calendar() == cal) + return it.data(); + return 0; +} + +/****************************************************************************** +* Delete all clients. +*/ +void ClientInfo::clear() +{ + QMap::Iterator it; + while ((it = mClients.begin()) != mClients.end()) + delete it.data(); +} + +/****************************************************************************** +* Delete the client with the specified name. +*/ +void ClientInfo::remove(const QCString& appName) +{ + QMap::Iterator it = mClients.find(appName); + if (it != mClients.end()) + delete it.data(); +} diff --git a/kalarm/kalarmd/clientinfo.h b/kalarm/kalarmd/clientinfo.h new file mode 100644 index 000000000..fe6491d7e --- /dev/null +++ b/kalarm/kalarmd/clientinfo.h @@ -0,0 +1,72 @@ +/* + * clientinfo.h - client application information + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright (C) 2001, 2004 by David Jarvie + * Based on the original, (c) 1998, 1999 Preston Brown + * + * 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. + */ + +#ifndef _CALCLIENT_H +#define _CALCLIENT_H + +#include +#include +#include + +class ADCalendar; + + +/*============================================================================= += Class: ClientInfo += Details of a KAlarm client application. +=============================================================================*/ +class ClientInfo +{ + public: + typedef QMap::ConstIterator ConstIterator; + + ClientInfo(const QCString &appName, const QString &title, const QCString &dcopObj, + const QString& calendar, bool startClient); + ClientInfo(const QCString &appName, const QString &title, const QCString &dcopObj, + ADCalendar* calendar, bool startClient); + ~ClientInfo(); + ADCalendar* setCalendar(const QString& url); + void detachCalendar() { mCalendar = 0; } + void setStartClient(bool start) { mStartClient = start; } + + QCString appName() const { return mAppName; } + QString title() const { return mTitle; } + QCString dcopObject() const { return mDcopObject; } + ADCalendar* calendar() const { return mCalendar; } + bool startClient() const { return mStartClient; } + + static ConstIterator begin() { return mClients.begin(); } + static ConstIterator end() { return mClients.end(); } + static ClientInfo* get(const QCString& appName); + static ClientInfo* get(const ADCalendar*); + static void remove(const QCString& appName); + static void clear(); + + private: + static QMap mClients; // list of all constructed clients + QCString mAppName; // client's executable and DCOP name + QString mTitle; // application title for display purposes + QCString mDcopObject; // object to receive DCOP messages + ADCalendar* mCalendar; // this client's event calendar + bool mStartClient; // whether to notify events via command line if client app isn't running +}; + +#endif diff --git a/kalarm/kalarmd/kalarmd.autostart.desktop b/kalarm/kalarmd/kalarmd.autostart.desktop new file mode 100644 index 000000000..de5add486 --- /dev/null +++ b/kalarm/kalarmd/kalarmd.autostart.desktop @@ -0,0 +1,103 @@ +# KDE Config File +[Desktop Entry] +Name=KAlarm Daemon +Name[af]=KAlarm Bediener +Name[bg]=Демон на KAlarm +Name[br]=Diaoul KAlarm +Name[ca]=Dimoni KAlarm +Name[cs]=KAlarm démon +Name[da]=KAlarm Dæmon +Name[de]=KAlarm Erinnerungsprogramm +Name[el]=Δαίμονας KAlarm +Name[eo]=KAlarm-demono +Name[es]=Daemon de KAlarm +Name[et]=KAlarmi häiredeemon +Name[eu]=KAlarm deabrua +Name[fa]=شبح KAlarm +Name[fi]=Hälytyspalvelin +Name[fr]=Démon d'alarme +Name[ga]=Deamhan KAlarm +Name[gl]=Daemon de KAlarm +Name[he]=תהליך הרקע תזכורות +Name[hu]=KAlarm szolgáltatás +Name[is]=KAlarm þjónn +Name[it]=Demone degli avvisi +Name[ja]=KAlarm デーモン +Name[ka]=KAlarm დემონი +Name[kk]=KAlarm қызметі +Name[km]=ដេមិន KAlarm +Name[lt]=KAlarm tarnyba +Name[mk]=Даемон за КАларм +Name[ms]=Daemon KAlarm +Name[nb]=KAlarm-nisse +Name[nds]=KAlarm-Dämoon +Name[ne]=केडीई संसूचक डेइमन +Name[nn]=KAlarm-nisse +Name[pl]=Demon alarmowy +Name[pt]=Servidor do KAlarm +Name[pt_BR]=Servidor do KAlarm +Name[ru]=Служба уведомлений +Name[sk]=KAlarm démon +Name[sl]=Demon KAlarm +Name[sr]=Демон KAlarm-а +Name[sr@Latn]=Demon KAlarm-a +Name[sv]=Alarmdemon +Name[ta]=கேஅலாரம் டெமான் +Name[tr]=KAlarm Servis Programı +Name[uk]=Демон KAlarm +Name[zh_CN]=KAlarm 进程 +Name[zh_TW]=KAlarm 守護程式 +Exec=kalarmd --autostart +Icon=kalarmd +Type=Application +Comment=KAlarm alarm daemon autostart at login +Comment[af]=Begin KAlarm bediener outomaties tydens aanteken +Comment[bg]=Автоматично стартиране на процеса на алармата KAlarm при влизане в системата +Comment[ca]=Dimoni d'inici automàtic de l'alarma KAlarm en connectar +Comment[cs]=Automatické spouštění alarmovacího démona při startu +Comment[da]=KAlarm-alarmdæmon autostart ved login +Comment[de]=Autostart des KAlarm Erinnerungsprogramms von KOrganizer bei der Anmeldung +Comment[el]=Αυτόματη εκκίνηση του δαίμονα KAlarm κατά τη σύνδεση +Comment[es]=Inicio automático al ingresar del daemon de alarma de KAlarm +Comment[et]=KAlarmi häiredeemoni automaatne käivitamine +Comment[eu]=KAlarm alarma deabrua saioa hastean automatikoki abiatzen da +Comment[fa]=آغاز خودکار هشدار KAlarm در ورود +Comment[fi]=KOrganizer/KAlarm-hälytyspalvelimen automaattikäynnistys sisäänkirjautuessa +Comment[fr]=Le démon d'alarme de KOrganizer et de KAlarm démarre automatiquement lors de la connexion +Comment[fy]=KAlarm alarmdaemon automatysk begjinne by it oanmelden +Comment[gl]=Autoinicio á entrada do daemon de KAlarm +Comment[he]=הפעלה אוטומטית של תהליך הרקע תזכורות של KAlarm בעת ההפעלה +Comment[hu]=A KAlarm emlékeztető szolgáltatás automatikus elindítása +Comment[is]=Ræsa KAlarm áminningaþjónn sjálfkrafa við byrjun setu +Comment[it]=Avvio automatico del demone degli avvisi +Comment[ja]=KAlarm アラームデーモンのログイン時の自動起動 +Comment[ka]=KAlarm მაღვიძარას დემონის ავტოდაწყება შესვლისას +Comment[kk]=KAlarm қызметі жүйеге кіргенде жегіледі +Comment[km]=ចាប់ផ្ដើម​ដេមិន​រោទ៍​របស់ KAlarm ពេល​ចូល +Comment[lt]=KOrganizer/KAlarm priminimų tarnybos automatinis paleidimas prisiregistruojant +Comment[mk]=Даемон за аларми од КАларм - автом. старт при најава +Comment[ms]= Automula daemon penggera KAlarm semasa log masuk +Comment[nb]=start alarmnisse ved innlogging +Comment[nds]=KAlarm-Dämoon bi't Anmellen automaatsch starten +Comment[ne]=लगइनमा केडीई संसूचक संसूचक डेइमन स्वत: सुरुआत हुन्छ +Comment[nl]=KAlarm alarmdaemon automatisch starten bij login +Comment[nn]=Start alarmnisse ved innlogging +Comment[pl]=Demon alarmu KOrganizera uruchamiany przy zalogowaniu +Comment[pt]=Servidor de alarme do KAlarm auto-iniciado no arranque +Comment[pt_BR]=Servidor de alarmes do KAlarm inicia automaticamente no login +Comment[ru]=Служба уведомлений KDE +Comment[sk]=Automatické spustenie kAlarm démona pri štarte +Comment[sl]=Samodejni zagon alarmskega strežnika KAlarma ob zagonu +Comment[sr]=Аутоматско покретање алармног демона KAlarm-а по пријављивању +Comment[sr@Latn]=Automatsko pokretanje alarmnog demona KAlarm-a po prijavljivanju +Comment[sv]=Kalarm-alarmdemon, automatisk start vid inloggning +Comment[ta]=உள்நுழையும்போது கேஅலாரம் அலாரம் டெமான் தானாகவே துவங்கும் +Comment[tr]=KAlarm alarm servis programı (açılışta başlar) +Comment[uk]=Автозавантаження демона нагадувань KAlarm +Comment[zh_CN]=登录时自动启动 KAlarm 定时守护进程 +Comment[zh_TW]=登入時自動啟動 KAlarm 鬧鐘守護程式 +Terminal=false +NoDisplay=true +X-KDE-autostart-phase=2 +X-KDE-autostart-condition=kalarmdrc:General:Autostart:false +X-KDE-StartupNotify=true diff --git a/kalarm/kalarmd/kalarmd.desktop b/kalarm/kalarmd/kalarmd.desktop new file mode 100644 index 000000000..31d3a0fa7 --- /dev/null +++ b/kalarm/kalarmd/kalarmd.desktop @@ -0,0 +1,55 @@ +# KDE Config File +[Desktop Entry] +Name=KAlarm Daemon +Name[af]=KAlarm Bediener +Name[bg]=Демон на KAlarm +Name[br]=Diaoul KAlarm +Name[ca]=Dimoni KAlarm +Name[cs]=KAlarm démon +Name[da]=KAlarm Dæmon +Name[de]=KAlarm Erinnerungsprogramm +Name[el]=Δαίμονας KAlarm +Name[eo]=KAlarm-demono +Name[es]=Daemon de KAlarm +Name[et]=KAlarmi häiredeemon +Name[eu]=KAlarm deabrua +Name[fa]=شبح KAlarm +Name[fi]=Hälytyspalvelin +Name[fr]=Démon d'alarme +Name[ga]=Deamhan KAlarm +Name[gl]=Daemon de KAlarm +Name[he]=תהליך הרקע תזכורות +Name[hu]=KAlarm szolgáltatás +Name[is]=KAlarm þjónn +Name[it]=Demone degli avvisi +Name[ja]=KAlarm デーモン +Name[ka]=KAlarm დემონი +Name[kk]=KAlarm қызметі +Name[km]=ដេមិន KAlarm +Name[lt]=KAlarm tarnyba +Name[mk]=Даемон за КАларм +Name[ms]=Daemon KAlarm +Name[nb]=KAlarm-nisse +Name[nds]=KAlarm-Dämoon +Name[ne]=केडीई संसूचक डेइमन +Name[nn]=KAlarm-nisse +Name[pl]=Demon alarmowy +Name[pt]=Servidor do KAlarm +Name[pt_BR]=Servidor do KAlarm +Name[ru]=Служба уведомлений +Name[sk]=KAlarm démon +Name[sl]=Demon KAlarm +Name[sr]=Демон KAlarm-а +Name[sr@Latn]=Demon KAlarm-a +Name[sv]=Alarmdemon +Name[ta]=கேஅலாரம் டெமான் +Name[tr]=KAlarm Servis Programı +Name[uk]=Демон KAlarm +Name[zh_CN]=KAlarm 进程 +Name[zh_TW]=KAlarm 守護程式 +Exec=kalarmd +Icon=kalarmd +Type=Application +Terminal=false +X-DCOP-ServiceType=Unique +NoDisplay=true diff --git a/kalarm/kalarmd/kalarmd.h b/kalarm/kalarmd/kalarmd.h new file mode 100644 index 000000000..5e45243bf --- /dev/null +++ b/kalarm/kalarmd/kalarmd.h @@ -0,0 +1,40 @@ +/* + * kalarmd.h - global header file + * Program: KAlarm's alarm daemon (kalarmd) + * Copyright © 2004,2005,2007 by 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. + */ + +#ifndef KALARMD_H +#define KALARMD_H + +#ifdef HAVE_CONFIG_H +#include +#endif + +#define DAEMON_VERSION "4.3" // kalarmd version number string +#define DAEMON_VERSION_NUM 40300 // kalarmd version number integer +#define DAEMON_APP_NAME "kalarmd" // DCOP name of alarm daemon application +#define DAEMON_DCOP_OBJECT "ad" // DCOP name of kalarmd's DCOP interface + +#define DAEMON_CHECK_INTERVAL 60 // the daemon checks calendar files every minute + +#define DAEMON_AUTOSTART_SECTION "General" // daemon's config file section for autostart-at-login +#define DAEMON_AUTOSTART_KEY "Autostart" // daemon's config file entry for autostart-at-login + +#define AUTOSTART_KALARM // fix for KAlarm autostart before session restoration + +#endif // KALARMD_H diff --git a/kalarm/kalarmiface.h b/kalarm/kalarmiface.h new file mode 100644 index 000000000..cd3aaf0cc --- /dev/null +++ b/kalarm/kalarmiface.h @@ -0,0 +1,355 @@ +/* + * kalarmiface.h - DCOP interface to KAlarm + * Program: kalarm + * Copyright © 2004-2006,2008 by 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. + */ + +#ifndef KALARMIFACE_H +#define KALARMIFACE_H + +/** @file kalarmiface.h - DCOP interface to KAlarm */ + +// No forward declarations - dcopidl2cpp won't work +#include +#include +#include +class QString; + +/** KAlarmIface provides a DCOP interface for other applications to request + * KAlarm actions. + */ + +class KAlarmIface : virtual public DCOPObject +{ + K_DCOP + public: + /** Bit values for the @p flags parameter of "scheduleXxxx()" DCOP calls. + * The bit values may be OR'ed together. + * @li REPEAT_AT_LOGIN - repeat the alarm at every login. + * @li BEEP - sound an audible beep when the alarm is displayed. + * @li SPEAK - speak the alarm message when it is displayed. + * @li REPEAT_SOUND - repeat the sound file while the alarm is displayed. + * @li CONFIRM_ACK - closing the alarm message window requires a confirmation prompt. + * @li AUTO_CLOSE - auto-close the alarm window after the late-cancel period. + * @li SCRIPT - the command to execute is a script, not a shell command line. + * @li EXEC_IN_XTERM - execute the command alarm in a terminal window. + * @li EMAIL_BCC - send a blind copy the email to the user. + * @li SHOW_IN_KORG - show the alarm as an event in KOrganizer + * @li DISABLED - set the alarm status to disabled. + */ + enum Flags + { + REPEAT_AT_LOGIN = 0x01, // repeat alarm at every login + BEEP = 0x02, // sound audible beep when alarm is displayed + REPEAT_SOUND = 0x08, // repeat sound file while alarm is displayed + CONFIRM_ACK = 0x04, // closing the alarm message window requires confirmation prompt + AUTO_CLOSE = 0x10, // auto-close alarm window after late-cancel period + EMAIL_BCC = 0x20, // blind copy the email to the user + DISABLED = 0x40, // alarm is currently disabled + SCRIPT = 0x80, // command is a script, not a shell command line + EXEC_IN_XTERM = 0x100, // execute command alarm in terminal window + SPEAK = 0x200, // speak the alarm message when it is displayed + SHOW_IN_KORG = 0x400 // show the alarm as an event in KOrganizer + }; + /** Values for the @p repeatType parameter of "scheduleXxxx()" DCOP calls. + * @li MINUTELY - the repeat interval is measured in minutes. + * @li DAILY - the repeat interval is measured in days. + * @li WEEKLY - the repeat interval is measured in weeks. + * @li MONTHLY - the repeat interval is measured in months. + * @li YEARLY - the repeat interval is measured in years. + */ + enum RecurType + { + MINUTELY = 1, // the repeat interval is measured in minutes + DAILY = 2, // the repeat interval is measured in days + WEEKLY = 3, // the repeat interval is measured in weeks + MONTHLY = 4, // the repeat interval is measured in months + YEARLY = 5 // the repeat interval is measured in years + }; + + k_dcop: + /** Cancel (delete) an already scheduled alarm. + * @param url - The URL (not path) of the calendar file containing the event to be cancelled. + * Used only for integrity checking: the call will fail if it is not KAlarm's + * current calendar file. + * @param eventId - The unique ID of the event to be cancelled, as stored in the calendar file @p url. + */ + virtual bool cancelEvent(const QString& url, const QString& eventId) = 0; + + /** Trigger the immediate display or execution of an alarm, regardless of what time it is scheduled for. + * @param url - The URL (not path) of the calendar file containing the event to be triggered. + * Used only for integrity checking: the call will fail if it is not KAlarm's + * current calendar file. + * @param eventId - The unique ID of the event to be triggered, as stored in the calendar file @p url. + */ + virtual bool triggerEvent(const QString& url, const QString& eventId) = 0; + + /** Schedule a message display alarm. + * @param message The text of the message to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param fgColor The foreground colour for the alarm message, or QString::null for the current + * default foreground colour. The format of the string is the same as for @p bgColor. + * @param font The font for the alarm message, or QString::null for the default message font + * current at the time the message is displayed. The string should be in format + * returned by QFont::toString(). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param recurrence Recurrence specification using iCalendar syntax (defined in RFC2445). + * @param repeatInterval Simple repetition repeat interval in minutes, or 0 for no sub-repetition. + * @param repeatCount Simple repetition repeat count (after the first occurrence), or 0 for no sub-repetition. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int repeatInterval, int repeatCount) = 0; + /** Schedule a message display alarm. + * @param message The text of the message to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param fgColor The foreground colour for the alarm message, or QString::null for the current + * default foreground colour. The format of the string is the same as for @p bgColor. + * @param font The font for the alarm message, or QString::null for the default message font + * current at the time the message is displayed. The string should be in format + * returned by QFont::toString(). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param repeatCount Recurrence count (after the first occurrence), or 0 for no recurrence. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, + int repeatType, int repeatInterval, int repeatCount) = 0; + /** Schedule a message display alarm. + * @param message The text of the message to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param fgColor The foreground colour for the alarm message, or QString::null for the current + * default foreground colour. The format of the string is the same as for @p bgColor. + * @param font The font for the alarm message, or QString::null for the default message font + * current at the time the message is displayed. The string should be in format + * returned by QFont::toString(). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param endDateTime Date/time after which the recurrence will end. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleMessage(const QString& message, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& bgColor, const QString& fgColor, const QString& font, + const KURL& audioFile, int reminderMins, + int repeatType, int repeatInterval, const QString& endDateTime) = 0; + + /** Schedule a file display alarm. + * @param file The text or image file to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param recurrence Recurrence specification using iCalendar syntax (defined in RFC2445). + * @param repeatInterval Simple repetition repeat interval in minutes, or 0 for no sub-repetition. + * @param repeatCount Simple repetition repeat count (after the first occurrence), or 0 for no sub-repetition. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, const QString& recurrence, + int repeatInterval, int repeatCount) = 0; + /** Schedule a file display alarm. + * @param file The text or image file to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param repeatCount Recurrence count (after the first occurrence), or 0 for no recurrence. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, int repeatType, int repeatInterval, int repeatCount) = 0; + /** Schedule a file display alarm. + * @param file The text or image file to display. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param bgColor The background colour for the alarm message window, or QString::null for the + * current default background colour. The string may be in any of the formats + * accepted by QColor::QColor(const QString&). + * @param audioFile The audio file to play when the alarm is displayed, or QString::null for none. + * @param reminderMins The number of minutes in advance of the main alarm and its recurrences to display + * a reminder alarm, or 0 for no reminder. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param endDateTime Date/time after which the recurrence will end. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleFile(const KURL& file, const QString& startDateTime, int lateCancel, unsigned flags, const QString& bgColor, + const KURL& audioFile, int reminderMins, + int repeatType, int repeatInterval, const QString& endDateTime) = 0; + + /** Schedule a command execution alarm. + * @param commandLine The command line or command script to execute. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param recurrence Recurrence specification using iCalendar syntax (defined in RFC2445). + * @param repeatInterval Simple repetition repeat interval in minutes, or 0 for no sub-repetition. + * @param repeatCount Simple repetition repeat count (after the first occurrence), or 0 for no sub-repetition. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int repeatInterval, int repeatCount) = 0; + /** Schedule a command execution alarm. + * @param commandLine The command line or command script to execute. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param repeatCount Recurrence count (after the first occurrence), or 0 for no recurrence. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int repeatType, int repeatInterval, int repeatCount) = 0; + /** Schedule a command execution alarm. + * @param commandLine The command line or command script to execute. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param endDateTime Date/time after which the recurrence will end. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleCommand(const QString& commandLine, const QString& startDateTime, int lateCancel, unsigned flags, + int repeatType, int repeatInterval, const QString& endDateTime) = 0; + + /** Schedule an email alarm. + * @param fromID The KMail identity to use as the sender of the email, or QString::null to use KAlarm's default sender ID. + * @param addresses Comma-separated list of addresses to send the email to. + * @param subject Subject line of the email. + * @param message Email message's body text. + * @param attachments Comma- or semicolon-separated list of paths or URLs of files to send as + * attachments to the email. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in Flags enum. + * @param recurrence Recurrence specification using iCalendar syntax (defined in RFC2445). + * @param repeatInterval Simple repetition repeat interval in minutes, or 0 for no sub-repetition. + * @param repeatCount Simple repetition repeat count (after the first occurrence), or 0 for no sub-repetition. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + const QString& recurrence, int repeatInterval, int repeatCount) = 0; + /** Schedule an email alarm. + * @param fromID The KMail identity to use as the sender of the email, or QString::null to use KAlarm's default sender ID. + * @param addresses Comma-separated list of addresses to send the email to. + * @param subject Subject line of the email. + * @param message Email message's body text. + * @param attachments Comma- or semicolon-separated list of paths or URLs of files to send as + * attachments to the email. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in the Flags enum. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param repeatCount Recurrence count (after the first occurrence), or 0 for no recurrence. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int repeatType, int repeatInterval, int repeatCount) = 0; + /** Schedule an email alarm. + * @param fromID The KMail identity to use as the sender of the email, or QString::null to use KAlarm's default sender ID. + * @param addresses Comma-separated list of addresses to send the email to. + * @param subject Subject line of the email. + * @param message Email message's body text. + * @param attachments Comma- or semicolon-separated list of paths or URLs of files to send as + * attachments to the email. + * @param startDateTime Start date/time, in the format YYYY-MM-DD[THH:MM[:SS]] or [T]HH:MM[:SS] + * @param lateCancel Late-cancellation period in minutes, or 0 for no cancellation. + * @param flags OR of flag bits defined in the Flags enum. + * @param repeatType The time units to use for recurrence. The actual recurrence interval is equal to + * @p repeatType multiplied by @p repeatInterval. + * The value of @p repeatType must a value defined in the RecurType enum. + * @param repeatInterval Recurrence interval in units defined by @p repeatType, or 0 for no recurrence. + * @param endDateTime Date/time after which the recurrence will end. + * @return true if alarm was scheduled successfully, false if configuration errors were found. + */ + virtual bool scheduleEmail(const QString& fromID, const QString& addresses, const QString& subject, const QString& message, + const QString& attachments, const QString& startDateTime, int lateCancel, unsigned flags, + int repeatType, int repeatInterval, const QString& endDateTime) = 0; + /** Open the alarm edit dialog to edit an existing alarm. + * @param eventId The unique ID of the event to be edited, or QString::null to create a new alarm. + * @return false if the alarm could not be found or is read-only, true otherwise. + */ + virtual bool edit(const QString& eventID) = 0; + /** Open the alarm edit dialog to edit a new alarm. + * @param templateName Name of the alarm template to base the new alarm on, or QString::null if none. + * If a template is specified but cannot be found, the alarm edit dialog is still + * opened but is (obviously) not preset with the template. + * @return false if an alarm template was specified but could not be found, true otherwise. + */ + virtual bool editNew(const QString& templateName) = 0; +}; + +#endif // KALARMIFACE_H diff --git a/kalarm/kalarmui.rc b/kalarm/kalarmui.rc new file mode 100644 index 000000000..686271707 --- /dev/null +++ b/kalarm/kalarmui.rc @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + &File + + + + + + &View + + + + + + + + &Actions + + + + + + + + + + + + + + + &Settings + + + + + + + + + + + + + + + + + + + + diff --git a/kalarm/kamail.cpp b/kalarm/kamail.cpp new file mode 100644 index 000000000..3042a270f --- /dev/null +++ b/kalarm/kamail.cpp @@ -0,0 +1,1096 @@ +/* + * kamail.cpp - email functions + * Program: kalarm + * Copyright © 2002-2005,2008 by 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 "kalarm.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "alarmevent.h" +#include "functions.h" +#include "kalarmapp.h" +#include "mainwindow.h" +#include "preferences.h" +#include "kamail.h" + + +namespace HeaderParsing +{ +bool parseAddress( const char* & scursor, const char * const send, + KMime::Types::Address & result, bool isCRLF=false ); +bool parseAddressList( const char* & scursor, const char * const send, + QValueList & result, bool isCRLF=false ); +} + +namespace +{ +QString getHostName(); +} + +struct KAMailData +{ + KAMailData(const KAEvent& e, const QString& fr, const QString& bc, bool allownotify) + : event(e), from(fr), bcc(bc), allowNotify(allownotify) { } + const KAEvent& event; + QString from; + QString bcc; + bool allowNotify; +}; + + +QString KAMail::i18n_NeedFromEmailAddress() +{ return i18n("A 'From' email address must be configured in order to execute email alarms."); } + +QString KAMail::i18n_sent_mail() +{ return i18n("KMail folder name: this should be translated the same as in kmail", "sent-mail"); } + +KPIM::IdentityManager* KAMail::mIdentityManager = 0; +KPIM::IdentityManager* KAMail::identityManager() +{ + if (!mIdentityManager) + mIdentityManager = new KPIM::IdentityManager(true); // create a read-only kmail identity manager + return mIdentityManager; +} + + +/****************************************************************************** +* Send the email message specified in an event. +* Reply = true if the message was sent - 'errmsgs' may contain copy error messages. +* = false if the message was not sent - 'errmsgs' contains the error messages. +*/ +bool KAMail::send(const KAEvent& event, QStringList& errmsgs, bool allowNotify) +{ + QString err; + QString from; + KPIM::Identity identity; + if (!event.emailFromId()) + from = Preferences::emailAddress(); + else + { + identity = mIdentityManager->identityForUoid(event.emailFromId()); + if (identity.isNull()) + { + kdError(5950) << "KAMail::send(): identity" << event.emailFromId() << "not found" << endl; + errmsgs = errors(i18n("Invalid 'From' email address.\nKMail identity '%1' not found.").arg(event.emailFromId())); + return false; + } + from = identity.fullEmailAddr(); + if (from.isEmpty()) + { + kdError(5950) << "KAMail::send(): identity" << identity.identityName() << "uoid" << identity.uoid() << ": no email address" << endl; + errmsgs = errors(i18n("Invalid 'From' email address.\nEmail identity '%1' has no email address").arg(identity.identityName())); + return false; + } + } + if (from.isEmpty()) + { + switch (Preferences::emailFrom()) + { + case Preferences::MAIL_FROM_KMAIL: + errmsgs = errors(i18n("No 'From' email address is configured (no default KMail identity found)\nPlease set it in KMail or in the KAlarm Preferences dialog.")); + break; + case Preferences::MAIL_FROM_CONTROL_CENTRE: + errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the KDE Control Center or in the KAlarm Preferences dialog.")); + break; + case Preferences::MAIL_FROM_ADDR: + default: + errmsgs = errors(i18n("No 'From' email address is configured.\nPlease set it in the KAlarm Preferences dialog.")); + break; + } + return false; + } + KAMailData data(event, from, + (event.emailBcc() ? Preferences::emailBccAddress() : QString::null), + allowNotify); + kdDebug(5950) << "KAlarmApp::sendEmail(): To: " << event.emailAddresses(", ") + << "\nSubject: " << event.emailSubject() << endl; + + if (Preferences::emailClient() == Preferences::SENDMAIL) + { + // Use sendmail to send the message + QString textComplete; + QString command = KStandardDirs::findExe(QString::fromLatin1("sendmail"), + QString::fromLatin1("/sbin:/usr/sbin:/usr/lib")); + if (!command.isNull()) + { + command += QString::fromLatin1(" -f "); + command += KPIM::getEmailAddress(from); + command += QString::fromLatin1(" -oi -t "); + textComplete = initHeaders(data, false); + } + else + { + command = KStandardDirs::findExe(QString::fromLatin1("mail")); + if (command.isNull()) + { + errmsgs = errors(i18n("%1 not found").arg(QString::fromLatin1("sendmail"))); // give up + return false; + } + + command += QString::fromLatin1(" -s "); + command += KShellProcess::quote(event.emailSubject()); + + if (!data.bcc.isEmpty()) + { + command += QString::fromLatin1(" -b "); + command += KShellProcess::quote(data.bcc); + } + + command += ' '; + command += event.emailAddresses(" "); // locally provided, okay + } + + // Add the body and attachments to the message. + // (Sendmail requires attachments to have already been included in the message.) + err = appendBodyAttachments(textComplete, event); + if (!err.isNull()) + { + errmsgs = errors(err); + return false; + } + + // Execute the send command + FILE* fd = popen(command.local8Bit(), "w"); + if (!fd) + { + kdError(5950) << "KAMail::send(): Unable to open a pipe to " << command << endl; + errmsgs = errors(); + return false; + } + fwrite(textComplete.local8Bit(), textComplete.length(), 1, fd); + pclose(fd); + + if (Preferences::emailCopyToKMail()) + { + // Create a copy of the sent email in KMail's 'Sent-mail' folder + err = addToKMailFolder(data, "sent-mail", true); + if (!err.isNull()) + errmsgs = errors(err, false); // not a fatal error - continue + } + + if (allowNotify) + notifyQueued(event); + } + else + { + // Use KMail to send the message + err = sendKMail(data); + if (!err.isNull()) + { + errmsgs = errors(err); + return false; + } + } + return true; +} + +/****************************************************************************** +* Send the email message via KMail. +* Reply = reason for failure (which may be the empty string) +* = null string if success. +*/ +QString KAMail::sendKMail(const KAMailData& data) +{ + QString err = KAlarm::runKMail(true); + if (!err.isNull()) + return err; + + // KMail is now running. Determine which DCOP call to use. + bool useSend = false; + QCString sendFunction = "sendMessage(QString,QString,QString,QString,QString,QString,KURL::List)"; + QCStringList funcs = kapp->dcopClient()->remoteFunctions("kmail", "MailTransportServiceIface"); + for (QCStringList::Iterator it=funcs.begin(); it != funcs.end() && !useSend; ++it) + { + QCString func = DCOPClient::normalizeFunctionSignature(*it); + if (func.left(5) == "bool ") + { + func = func.mid(5); + func.replace(QRegExp(" [0-9A-Za-z_:]+"), ""); + useSend = (func == sendFunction); + } + } + + QByteArray callData; + QDataStream arg(callData, IO_WriteOnly); + kdDebug(5950) << "KAMail::sendKMail(): using " << (useSend ? "sendMessage()" : "dcopAddMessage()") << endl; + if (useSend) + { + // This version of KMail has the sendMessage() function, + // which transmits the message immediately. + arg << data.from; + arg << data.event.emailAddresses(", "); + arg << ""; // CC: + arg << data.bcc; + arg << data.event.emailSubject(); + arg << data.event.message(); + arg << KURL::List(data.event.emailAttachments()); + if (!callKMail(callData, "MailTransportServiceIface", sendFunction, "bool")) + return i18n("Error calling KMail"); + } + else + { + // KMail is an older version, so use dcopAddMessage() + // to add the message to the outbox for later transmission. + err = addToKMailFolder(data, "outbox", false); + if (!err.isNull()) + return err; + } + if (data.allowNotify) + notifyQueued(data.event); + return QString::null; +} + +/****************************************************************************** +* Add the message to a KMail folder. +* Reply = reason for failure (which may be the empty string) +* = null string if success. +*/ +QString KAMail::addToKMailFolder(const KAMailData& data, const char* folder, bool checkKmailRunning) +{ + QString err; + if (checkKmailRunning) + err = KAlarm::runKMail(true); + if (err.isNull()) + { + QString message = initHeaders(data, true); + err = appendBodyAttachments(message, data.event); + if (!err.isNull()) + return err; + + // Write to a temporary file for feeding to KMail + KTempFile tmpFile; + tmpFile.setAutoDelete(true); // delete file when it is destructed + QTextStream* stream = tmpFile.textStream(); + if (!stream) + { + kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Unable to open a temporary mail file" << endl; + return QString(""); + } + *stream << message; + tmpFile.close(); + if (tmpFile.status()) + { + kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): Error " << tmpFile.status() << " writing to temporary mail file" << endl; + return QString(""); + } + + // Notify KMail of the message in the temporary file + QByteArray callData; + QDataStream arg(callData, IO_WriteOnly); + arg << QString::fromLatin1(folder) << tmpFile.name(); + if (callKMail(callData, "KMailIface", "dcopAddMessage(QString,QString)", "int")) + return QString::null; + err = i18n("Error calling KMail"); + } + kdError(5950) << "KAMail::addToKMailFolder(" << folder << "): " << err << endl; + return err; +} + +/****************************************************************************** +* Call KMail via DCOP. The DCOP function must return an 'int'. +*/ +bool KAMail::callKMail(const QByteArray& callData, const QCString& iface, const QCString& function, const QCString& funcType) +{ + QCString replyType; + QByteArray replyData; + if (!kapp->dcopClient()->call("kmail", iface, function, callData, replyType, replyData) + || replyType != funcType) + { + QCString funcname = function; + funcname.replace(QRegExp("(.+$"), "()"); + kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call failed\n";; + return false; + } + QDataStream replyStream(replyData, IO_ReadOnly); + QCString funcname = function; + funcname.replace(QRegExp("(.+$"), "()"); + if (replyType == "int") + { + int result; + replyStream >> result; + if (result <= 0) + { + kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error code = " << result << endl; + return false; + } + } + else if (replyType == "bool") + { + bool result; + replyStream >> result; + if (!result) + { + kdError(5950) << "KAMail::callKMail(): kmail " << funcname << " call returned error\n"; + return false; + } + } + return true; +} + +/****************************************************************************** +* Create the headers part of the email. +*/ +QString KAMail::initHeaders(const KAMailData& data, bool dateId) +{ + QString message; + if (dateId) + { + struct timeval tod; + gettimeofday(&tod, 0); + time_t timenow = tod.tv_sec; + char buff[64]; + strftime(buff, sizeof(buff), "Date: %a, %d %b %Y %H:%M:%S %z", localtime(&timenow)); + QString from = data.from; + from.replace(QRegExp("^.*<"), QString::null).replace(QRegExp(">.*$"), QString::null); + message = QString::fromLatin1(buff); + message += QString::fromLatin1("\nMessage-Id: <%1.%2.%3>\n").arg(timenow).arg(tod.tv_usec).arg(from); + } + message += QString::fromLatin1("From: ") + data.from; + message += QString::fromLatin1("\nTo: ") + data.event.emailAddresses(", "); + if (!data.bcc.isEmpty()) + message += QString::fromLatin1("\nBcc: ") + data.bcc; + message += QString::fromLatin1("\nSubject: ") + data.event.emailSubject(); + message += QString::fromLatin1("\nX-Mailer: %1/" KALARM_VERSION).arg(kapp->aboutData()->programName()); + return message; +} + +/****************************************************************************** +* Append the body and attachments to the email text. +* Reply = reason for error +* = 0 if successful. +*/ +QString KAMail::appendBodyAttachments(QString& message, const KAEvent& event) +{ + static const char* textMimeTypes[] = { + "application/x-sh", "application/x-csh", "application/x-shellscript", + "application/x-nawk", "application/x-gawk", "application/x-awk", + "application/x-perl", "application/x-desktop", + 0 + }; + QStringList attachments = event.emailAttachments(); + if (!attachments.count()) + { + // There are no attachments, so simply append the message body + message += "\n\n"; + message += event.message(); + } + else + { + // There are attachments, so the message must be in MIME format + // Create a boundary string + time_t timenow; + time(&timenow); + QCString boundary; + boundary.sprintf("------------_%lu_-%lx=", 2*timenow, timenow); + message += QString::fromLatin1("\nMIME-Version: 1.0"); + message += QString::fromLatin1("\nContent-Type: multipart/mixed;\n boundary=\"%1\"\n").arg(boundary); + + if (!event.message().isEmpty()) + { + // There is a message body + message += QString::fromLatin1("\n--%1\nContent-Type: text/plain\nContent-Transfer-Encoding: 8bit\n\n").arg(boundary); + message += event.message(); + } + + // Append each attachment in turn + QString attachError = i18n("Error attaching file:\n%1"); + for (QStringList::Iterator at = attachments.begin(); at != attachments.end(); ++at) + { + QString attachment = (*at).local8Bit(); + KURL url(attachment); + url.cleanPath(); + KIO::UDSEntry uds; + if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) { + kdError(5950) << "KAMail::appendBodyAttachments(): not found: " << attachment << endl; + return i18n("Attachment not found:\n%1").arg(attachment); + } + KFileItem fi(uds, url); + if (fi.isDir() || !fi.isReadable()) { + kdError(5950) << "KAMail::appendBodyAttachments(): not file/not readable: " << attachment << endl; + return attachError.arg(attachment); + } + + // Check if the attachment is a text file + QString mimeType = fi.mimetype(); + bool text = mimeType.startsWith("text/"); + if (!text) + { + for (int i = 0; !text && textMimeTypes[i]; ++i) + text = (mimeType == textMimeTypes[i]); + } + + message += QString::fromLatin1("\n--%1").arg(boundary); + message += QString::fromLatin1("\nContent-Type: %2; name=\"%3\"").arg(mimeType).arg(fi.text()); + message += QString::fromLatin1("\nContent-Transfer-Encoding: %1").arg(QString::fromLatin1(text ? "8bit" : "BASE64")); + message += QString::fromLatin1("\nContent-Disposition: attachment; filename=\"%4\"\n\n").arg(fi.text()); + + // Read the file contents + QString tmpFile; + if (!KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow())) { + kdError(5950) << "KAMail::appendBodyAttachments(): load failure: " << attachment << endl; + return attachError.arg(attachment); + } + QFile file(tmpFile); + if (!file.open(IO_ReadOnly) ) { + kdDebug(5950) << "KAMail::appendBodyAttachments() tmp load error: " << attachment << endl; + return attachError.arg(attachment); + } + QIODevice::Offset size = file.size(); + char* contents = new char [size + 1]; + Q_LONG bytes = file.readBlock(contents, size); + file.close(); + contents[size] = 0; + bool atterror = false; + if (bytes == -1 || (QIODevice::Offset)bytes < size) { + kdDebug(5950) << "KAMail::appendBodyAttachments() read error: " << attachment << endl; + atterror = true; + } + else if (text) + { + // Text attachment doesn't need conversion + message += contents; + } + else + { + // Convert the attachment to BASE64 encoding + QIODevice::Offset base64Size; + char* base64 = base64Encode(contents, size, base64Size); + if (base64Size == (QIODevice::Offset)-1) { + kdDebug(5950) << "KAMail::appendBodyAttachments() base64 buffer overflow: " << attachment << endl; + atterror = true; + } + else + message += QString::fromLatin1(base64, base64Size); + delete[] base64; + } + delete[] contents; + if (atterror) + return attachError.arg(attachment); + } + message += QString::fromLatin1("\n--%1--\n.\n").arg(boundary); + } + return QString::null; +} + +/****************************************************************************** +* If any of the destination email addresses are non-local, display a +* notification message saying that an email has been queued for sending. +*/ +void KAMail::notifyQueued(const KAEvent& event) +{ + KMime::Types::Address addr; + QString localhost = QString::fromLatin1("localhost"); + QString hostname = getHostName(); + const EmailAddressList& addresses = event.emailAddresses(); + for (QValueList::ConstIterator it = addresses.begin(); it != addresses.end(); ++it) + { + QCString email = (*it).email().local8Bit(); + const char* em = email; + if (!email.isEmpty() + && HeaderParsing::parseAddress(em, em + email.length(), addr)) + { + QString domain = addr.mailboxList.first().addrSpec.domain; + if (!domain.isEmpty() && domain != localhost && domain != hostname) + { + QString text = (Preferences::emailClient() == Preferences::KMAIL) + ? i18n("An email has been queued to be sent by KMail") + : i18n("An email has been queued to be sent"); + KMessageBox::information(0, text, QString::null, Preferences::EMAIL_QUEUED_NOTIFY); + return; + } + } + } +} + +/****************************************************************************** +* Return whether any KMail identities exist. +*/ +bool KAMail::identitiesExist() +{ + identityManager(); // create identity manager if not already done + return mIdentityManager->begin() != mIdentityManager->end(); +} + +/****************************************************************************** +* Fetch the uoid of an email identity name or uoid string. +*/ +uint KAMail::identityUoid(const QString& identityUoidOrName) +{ + bool ok; + uint id = identityUoidOrName.toUInt(&ok); + if (!ok || identityManager()->identityForUoid(id).isNull()) + { + identityManager(); // fetch it if not already done + for (KPIM::IdentityManager::ConstIterator it = mIdentityManager->begin(); + it != mIdentityManager->end(); ++it) + { + if ((*it).identityName() == identityUoidOrName) + { + id = (*it).uoid(); + break; + } + } + } + return id; +} + +/****************************************************************************** +* Fetch the user's email address configured in the KDE Control Centre. +*/ +QString KAMail::controlCentreAddress() +{ + KEMailSettings e; + return e.getSetting(KEMailSettings::EmailAddress); +} + +/****************************************************************************** +* Parse a list of email addresses, optionally containing display names, +* entered by the user. +* Reply = the invalid item if error, else empty string. +*/ +QString KAMail::convertAddresses(const QString& items, EmailAddressList& list) +{ + list.clear(); + QCString addrs = items.local8Bit(); + const char* ad = static_cast(addrs); + + // parse an address-list + QValueList maybeAddressList; + if (!HeaderParsing::parseAddressList(ad, ad + addrs.length(), maybeAddressList)) + return QString::fromLocal8Bit(ad); // return the address in error + + // extract the mailboxes and complain if there are groups + for (QValueList::ConstIterator it = maybeAddressList.begin(); + it != maybeAddressList.end(); ++it) + { + QString bad = convertAddress(*it, list); + if (!bad.isEmpty()) + return bad; + } + return QString::null; +} + +#if 0 +/****************************************************************************** +* Parse an email address, optionally containing display name, entered by the +* user, and append it to the specified list. +* Reply = the invalid item if error, else empty string. +*/ +QString KAMail::convertAddress(const QString& item, EmailAddressList& list) +{ + QCString addr = item.local8Bit(); + const char* ad = static_cast(addr); + KMime::Types::Address maybeAddress; + if (!HeaderParsing::parseAddress(ad, ad + addr.length(), maybeAddress)) + return item; // error + return convertAddress(maybeAddress, list); +} +#endif + +/****************************************************************************** +* Convert a single KMime::Types address to a KCal::Person instance and append +* it to the specified list. +*/ +QString KAMail::convertAddress(KMime::Types::Address addr, EmailAddressList& list) +{ + if (!addr.displayName.isEmpty()) + { + kdDebug(5950) << "mailbox groups not allowed! Name: \"" << addr.displayName << "\"" << endl; + return addr.displayName; + } + const QValueList& mblist = addr.mailboxList; + for (QValueList::ConstIterator mb = mblist.begin(); + mb != mblist.end(); ++mb) + { + QString addrPart = (*mb).addrSpec.localPart; + if (!(*mb).addrSpec.domain.isEmpty()) + { + addrPart += QChar('@'); + addrPart += (*mb).addrSpec.domain; + } + list += KCal::Person((*mb).displayName, addrPart); + } + return QString::null; +} + +/* +QString KAMail::convertAddresses(const QString& items, QStringList& list) +{ + EmailAddressList addrs; + QString item = convertAddresses(items, addrs); + if (!item.isEmpty()) + return item; + for (EmailAddressList::Iterator ad = addrs.begin(); ad != addrs.end(); ++ad) + { + item = (*ad).fullName().local8Bit(); + switch (checkAddress(item)) + { + case 1: // OK + list += item; + break; + case 0: // null address + break; + case -1: // invalid address + return item; + } + } + return QString::null; +}*/ + +/****************************************************************************** +* Check the validity of an email address. +* Because internal email addresses don't have to abide by the usual internet +* email address rules, only some basic checks are made. +* Reply = 1 if alright, 0 if empty, -1 if error. +*/ +int KAMail::checkAddress(QString& address) +{ + address = address.stripWhiteSpace(); + // Check that there are no list separator characters present + if (address.find(',') >= 0 || address.find(';') >= 0) + return -1; + int n = address.length(); + if (!n) + return 0; + int start = 0; + int end = n - 1; + if (address[end] == '>') + { + // The email address is in <...> + if ((start = address.find('<')) < 0) + return -1; + ++start; + --end; + } + int i = address.find('@', start); + if (i >= 0) + { + if (i == start || i == end) // check @ isn't the first or last character +// || address.find('@', i + 1) >= 0) // check for multiple @ characters + return -1; + } +/* else + { + // Allow the @ character to be missing if it's a local user + if (!getpwnam(address.mid(start, end - start + 1).local8Bit())) + return false; + } + for (int i = start; i <= end; ++i) + { + char ch = address[i].latin1(); + if (ch == '.' || ch == '@' || ch == '-' || ch == '_' + || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') + || (ch >= '0' && ch <= '9')) + continue; + return false; + }*/ + return 1; +} + +/****************************************************************************** +* Convert a comma or semicolon delimited list of attachments into a +* QStringList. The items are checked for validity. +* Reply = the invalid item if error, else empty string. +*/ +QString KAMail::convertAttachments(const QString& items, QStringList& list) +{ + KURL url; + list.clear(); + int length = items.length(); + for (int next = 0; next < length; ) + { + // Find the first delimiter character (, or ;) + int i = items.find(',', next); + if (i < 0) + i = items.length(); + int sc = items.find(';', next); + if (sc < 0) + sc = items.length(); + if (sc < i) + i = sc; + QString item = items.mid(next, i - next).stripWhiteSpace(); + switch (checkAttachment(item)) + { + case 1: list += item; break; + case 0: break; // empty attachment name + case -1: + default: return item; // error + } + next = i + 1; + } + return QString::null; +} + +#if 0 +/****************************************************************************** +* Convert a comma or semicolon delimited list of attachments into a +* KURL::List. The items are checked for validity. +* Reply = the invalid item if error, else empty string. +*/ +QString KAMail::convertAttachments(const QString& items, KURL::List& list) +{ + KURL url; + list.clear(); + QCString addrs = items.local8Bit(); + int length = items.length(); + for (int next = 0; next < length; ) + { + // Find the first delimiter character (, or ;) + int i = items.find(',', next); + if (i < 0) + i = items.length(); + int sc = items.find(';', next); + if (sc < 0) + sc = items.length(); + if (sc < i) + i = sc; + QString item = items.mid(next, i - next); + switch (checkAttachment(item, &url)) + { + case 1: list += url; break; + case 0: break; // empty attachment name + case -1: + default: return item; // error + } + next = i + 1; + } + return QString::null; +} +#endif + +/****************************************************************************** +* Check for the existence of the attachment file. +* If non-null, '*url' receives the KURL of the attachment. +* Reply = 1 if attachment exists +* = 0 if null name +* = -1 if doesn't exist. +*/ +int KAMail::checkAttachment(QString& attachment, KURL* url) +{ + attachment = attachment.stripWhiteSpace(); + if (attachment.isEmpty()) + { + if (url) + *url = KURL(); + return 0; + } + // Check that the file exists + KURL u = KURL::fromPathOrURL(attachment); + u.cleanPath(); + if (url) + *url = u; + return checkAttachment(u) ? 1 : -1; +} + +/****************************************************************************** +* Check for the existence of the attachment file. +*/ +bool KAMail::checkAttachment(const KURL& url) +{ + KIO::UDSEntry uds; + if (!KIO::NetAccess::stat(url, uds, MainWindow::mainMainWindow())) + return false; // doesn't exist + KFileItem fi(uds, url); + if (fi.isDir() || !fi.isReadable()) + return false; + return true; +} + + +/****************************************************************************** +* Convert a block of memory to Base64 encoding. +* 'outSize' is set to the number of bytes used in the returned block, or to +* -1 if overflow. +* Reply = BASE64 buffer, which the caller must delete[] afterwards. +*/ +char* KAMail::base64Encode(const char* in, QIODevice::Offset size, QIODevice::Offset& outSize) +{ + const int MAX_LINELEN = 72; + static unsigned char dtable[65] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + + char* out = new char [2*size + 5]; + outSize = (QIODevice::Offset)-1; + QIODevice::Offset outIndex = 0; + int lineLength = 0; + for (QIODevice::Offset inIndex = 0; inIndex < size; ) + { + unsigned char igroup[3]; + int n; + for (n = 0; n < 3; ++n) + { + if (inIndex < size) + igroup[n] = (unsigned char)in[inIndex++]; + else + { + igroup[n] = igroup[2] = 0; + break; + } + } + + if (n > 0) + { + unsigned char ogroup[4]; + ogroup[0] = dtable[igroup[0] >> 2]; + ogroup[1] = dtable[((igroup[0] & 3) << 4) | (igroup[1] >> 4)]; + ogroup[2] = dtable[((igroup[1] & 0xF) << 2) | (igroup[2] >> 6)]; + ogroup[3] = dtable[igroup[2] & 0x3F]; + + if (n < 3) + { + ogroup[3] = '='; + if (n < 2) + ogroup[2] = '='; + } + if (outIndex >= size*2) + { + delete[] out; + return 0; + } + for (int i = 0; i < 4; ++i) + { + if (lineLength >= MAX_LINELEN) + { + out[outIndex++] = '\r'; + out[outIndex++] = '\n'; + lineLength = 0; + } + out[outIndex++] = ogroup[i]; + ++lineLength; + } + } + } + + if (outIndex + 2 < size*2) + { + out[outIndex++] = '\r'; + out[outIndex++] = '\n'; + } + outSize = outIndex; + return out; +} + +/****************************************************************************** +* Set the appropriate error messages for a given error string. +*/ +QStringList KAMail::errors(const QString& err, bool sendfail) +{ + QString error1 = sendfail ? i18n("Failed to send email") + : i18n("Error copying sent email to KMail %1 folder").arg(i18n_sent_mail()); + if (err.isEmpty()) + return QStringList(error1); + QStringList errs(QString::fromLatin1("%1:").arg(error1)); + errs += err; + return errs; +} + +/****************************************************************************** +* Get the body of an email, given its serial number. +*/ +QString KAMail::getMailBody(Q_UINT32 serialNumber) +{ + // Get the body of the email from KMail + QCString replyType; + QByteArray replyData; + QByteArray data; + QDataStream arg(data, IO_WriteOnly); + arg << serialNumber; + arg << (int)0; + QString body; + if (kapp->dcopClient()->call("kmail", "KMailIface", "getDecodedBodyPart(Q_UINT32,int)", data, replyType, replyData) + && replyType == "QString") + { + QDataStream reply_stream(replyData, IO_ReadOnly); + reply_stream >> body; + } + else + kdDebug(5950) << "KAMail::getMailBody(): kmail getDecodedBodyPart() call failed\n"; + return body; +} + +namespace +{ +/****************************************************************************** +* Get the local system's host name. +*/ +QString getHostName() +{ + char hname[256]; + if (gethostname(hname, sizeof(hname))) + return QString::null; + return QString::fromLocal8Bit(hname); +} +} + + +/*============================================================================= += HeaderParsing : modified and additional functions. += The following functions are modified from, or additional to, those in += libkdenetwork kmime_header_parsing.cpp. +=============================================================================*/ + +namespace HeaderParsing +{ + +using namespace KMime; +using namespace KMime::Types; +using namespace KMime::HeaderParsing; + +/****************************************************************************** +* New function. +* Allow a local user name to be specified as an email address. +*/ +bool parseUserName( const char* & scursor, const char * const send, + QString & result, bool isCRLF ) { + + QString maybeLocalPart; + QString tmp; + + if ( scursor != send ) { + // first, eat any whitespace + eatCFWS( scursor, send, isCRLF ); + + char ch = *scursor++; + switch ( ch ) { + case '.': // dot + case '@': + case '"': // quoted-string + return false; + + default: // atom + scursor--; // re-set scursor to point to ch again + tmp = QString::null; + if ( parseAtom( scursor, send, result, false /* no 8bit */ ) ) { + if (getpwnam(result.local8Bit())) + return true; + } + return false; // parseAtom can only fail if the first char is non-atext. + } + } + return false; +} + +/****************************************************************************** +* Modified function. +* Allow a local user name to be specified as an email address, and reinstate +* the original scursor on error return. +*/ +bool parseAddress( const char* & scursor, const char * const send, + Address & result, bool isCRLF ) { + // address := mailbox / group + + eatCFWS( scursor, send, isCRLF ); + if ( scursor == send ) return false; + + // first try if it's a single mailbox: + Mailbox maybeMailbox; + const char * oldscursor = scursor; + if ( parseMailbox( scursor, send, maybeMailbox, isCRLF ) ) { + // yes, it is: + result.displayName = QString::null; + result.mailboxList.append( maybeMailbox ); + return true; + } + scursor = oldscursor; + + // KAlarm: Allow a local user name to be specified + // no, it's not a single mailbox. Try if it's a local user name: + QString maybeUserName; + if ( parseUserName( scursor, send, maybeUserName, isCRLF ) ) { + // yes, it is: + maybeMailbox.displayName = QString::null; + maybeMailbox.addrSpec.localPart = maybeUserName; + maybeMailbox.addrSpec.domain = QString::null; + result.displayName = QString::null; + result.mailboxList.append( maybeMailbox ); + return true; + } + scursor = oldscursor; + + Address maybeAddress; + + // no, it's not a single mailbox. Try if it's a group: + if ( !parseGroup( scursor, send, maybeAddress, isCRLF ) ) + { + scursor = oldscursor; // KAlarm: reinstate original scursor on error return + return false; + } + + result = maybeAddress; + return true; +} + +/****************************************************************************** +* Modified function. +* Allow either ',' or ';' to be used as an email address separator. +*/ +bool parseAddressList( const char* & scursor, const char * const send, + QValueList
& result, bool isCRLF ) { + while ( scursor != send ) { + eatCFWS( scursor, send, isCRLF ); + // end of header: this is OK. + if ( scursor == send ) return true; + // empty entry: ignore: + if ( *scursor == ',' || *scursor == ';' ) { scursor++; continue; } // KAlarm: allow ';' as address separator + + // parse one entry + Address maybeAddress; + if ( !parseAddress( scursor, send, maybeAddress, isCRLF ) ) return false; + result.append( maybeAddress ); + + eatCFWS( scursor, send, isCRLF ); + // end of header: this is OK. + if ( scursor == send ) return true; + // comma separating entries: eat it. + if ( *scursor == ',' || *scursor == ';' ) scursor++; // KAlarm: allow ';' as address separator + } + return true; +} + +} // namespace HeaderParsing diff --git a/kalarm/kamail.h b/kalarm/kamail.h new file mode 100644 index 000000000..86c0482a5 --- /dev/null +++ b/kalarm/kamail.h @@ -0,0 +1,65 @@ +/* + * kamail.h - email functions + * Program: kalarm + * Copyright © 2002-2008 by 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. + */ + +#ifndef KAMAIL_H +#define KAMAIL_H + +#include +#include +class KURL; +class KAEvent; +class EmailAddressList; +namespace KPIM { class IdentityManager; } +namespace KMime { namespace Types { struct Address; } } + +struct KAMailData; + + +class KAMail +{ + public: + static bool send(const KAEvent&, QStringList& errmsgs, bool allowNotify = true); + static int checkAddress(QString& address); + static int checkAttachment(QString& attachment, KURL* = 0); + static bool checkAttachment(const KURL&); + static QString convertAddresses(const QString& addresses, EmailAddressList&); + static QString convertAttachments(const QString& attachments, QStringList& list); + static KPIM::IdentityManager* identityManager(); + static bool identitiesExist(); + static uint identityUoid(const QString& identityUoidOrName); + static QString controlCentreAddress(); + static QString getMailBody(Q_UINT32 serialNumber); + static QString i18n_NeedFromEmailAddress(); + static QString i18n_sent_mail(); + + private: + static KPIM::IdentityManager* mIdentityManager; // KMail identity manager + static QString sendKMail(const KAMailData&); + static QString initHeaders(const KAMailData&, bool dateId); + static QString appendBodyAttachments(QString& message, const KAEvent&); + static QString addToKMailFolder(const KAMailData&, const char* folder, bool checkKmailRunning); + static bool callKMail(const QByteArray& callData, const QCString& iface, const QCString& function, const QCString& funcType); + static QString convertAddress(KMime::Types::Address, EmailAddressList&); + static void notifyQueued(const KAEvent&); + static char* base64Encode(const char* in, QIODevice::Offset size, QIODevice::Offset& outSize); + static QStringList errors(const QString& error = QString::null, bool sendfail = true); +}; + +#endif // KAMAIL_H diff --git a/kalarm/karecurrence.cpp b/kalarm/karecurrence.cpp new file mode 100644 index 000000000..9461d71ec --- /dev/null +++ b/kalarm/karecurrence.cpp @@ -0,0 +1,876 @@ +/* + * karecurrence.cpp - recurrence with special yearly February 29th handling + * Program: kalarm + * Copyright © 2005,2006,2008 by 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 "kalarm.h" + +#include +#include + +#include + +#include "datetime.h" +#include "functions.h" +#include "karecurrence.h" + +using namespace KCal; + +/*============================================================================= += Class KARecurrence += The purpose of this class is to represent the restricted range of recurrence += types which are handled by KAlarm, and to translate between these and the += libkcal Recurrence class. In particular, it handles yearly recurrences on += 29th February specially: += += KARecurrence allows annual 29th February recurrences to fall on 28th += February or 1st March, or not at all, in non-leap years. It allows such += 29th February recurrences to be combined with the 29th of other months in += a simple way, represented simply as the 29th of multiple months including += February. For storage in the libkcal calendar, the 29th day of the month += recurrence for other months is combined with a last-day-of-February or a += 60th-day-of-the-year recurrence rule, thereby conforming to RFC2445. +=============================================================================*/ + + +KARecurrence::Feb29Type KARecurrence::mDefaultFeb29 = KARecurrence::FEB29_FEB29; + + +/****************************************************************************** +* Set up a KARecurrence from recurrence parameters, using the start date to +* determine the recurrence day/month as appropriate. +* Only a restricted subset of recurrence types is allowed. +* Reply = true if successful. +*/ +bool KARecurrence::set(Type recurType, int freq, int count, int f29, const DateTime& start, const QDateTime& end) +{ + mCachedType = -1; + RecurrenceRule::PeriodType rrtype; + switch (recurType) + { + case MINUTELY: rrtype = RecurrenceRule::rMinutely; break; + case DAILY: rrtype = RecurrenceRule::rDaily; break; + case WEEKLY: rrtype = RecurrenceRule::rWeekly; break; + case MONTHLY_DAY: rrtype = RecurrenceRule::rMonthly; break; + case ANNUAL_DATE: rrtype = RecurrenceRule::rYearly; break; + case NO_RECUR: rrtype = RecurrenceRule::rNone; break; + default: + return false; + } + if (!init(rrtype, freq, count, f29, start, end)) + return false; + switch (recurType) + { + case WEEKLY: + { + QBitArray days(7); + days.setBit(start.date().dayOfWeek() - 1); + addWeeklyDays(days); + break; + } + case MONTHLY_DAY: + addMonthlyDate(start.date().day()); + break; + case ANNUAL_DATE: + addYearlyDate(start.date().day()); + addYearlyMonth(start.date().month()); + break; + default: + break; + } + return true; +} + +/****************************************************************************** +* Initialise a KARecurrence from recurrence parameters. +* Reply = true if successful. +*/ +bool KARecurrence::init(RecurrenceRule::PeriodType recurType, int freq, int count, int f29, const DateTime& start, + const QDateTime& end) +{ + mCachedType = -1; + Feb29Type feb29Type = (f29 == -1) ? mDefaultFeb29 : static_cast(f29); + mFeb29Type = FEB29_FEB29; + clear(); + if (count < -1) + return false; + bool dateOnly = start.isDateOnly(); + if (!count && (!dateOnly && !end.isValid() + || dateOnly && !end.date().isValid())) + return false; + switch (recurType) + { + case RecurrenceRule::rMinutely: + case RecurrenceRule::rDaily: + case RecurrenceRule::rWeekly: + case RecurrenceRule::rMonthly: + case RecurrenceRule::rYearly: + break; + case rNone: + return true; + default: + return false; + } + setNewRecurrenceType(recurType, freq); + if (count) + setDuration(count); + else if (dateOnly) + setEndDate(end.date()); + else + setEndDateTime(end); + QDateTime startdt = start.dateTime(); + if (recurType == RecurrenceRule::rYearly + && feb29Type == FEB29_FEB28 || feb29Type == FEB29_MAR1) + { + int year = startdt.date().year(); + if (!QDate::leapYear(year) + && startdt.date().dayOfYear() == (feb29Type == FEB29_MAR1 ? 60 : 59)) + { + /* The event start date is February 28th or March 1st, but it + * is a recurrence on February 29th (recurring on February 28th + * or March 1st in non-leap years). Adjust the start date to + * be on February 29th in the last previous leap year. + * This is necessary because KARecurrence represents all types + * of 29th February recurrences by a simple 29th February. + */ + while (!QDate::leapYear(--year)) ; + startdt.setDate(QDate(year, 2, 29)); + } + mFeb29Type = feb29Type; + } + if (dateOnly) + setStartDate(startdt.date()); + else + setStartDateTime(startdt); + return true; +} + +/****************************************************************************** + * Initialise the recurrence from an iCalendar RRULE string. + */ +bool KARecurrence::set(const QString& icalRRULE) +{ + static QString RRULE = QString::fromLatin1("RRULE:"); + mCachedType = -1; + clear(); + if (icalRRULE.isEmpty()) + return true; + ICalFormat format; + if (!format.fromString(defaultRRule(true), + (icalRRULE.startsWith(RRULE) ? icalRRULE.mid(RRULE.length()) : icalRRULE))) + return false; + fix(); + return true; +} + +/****************************************************************************** +* Must be called after presetting with a KCal::Recurrence, to convert the +* recurrence to KARecurrence types: +* - Convert hourly recurrences to minutely. +* - Remove all but the first day in yearly date recurrences. +* - Check for yearly recurrences falling on February 29th and adjust them as +* necessary. A 29th of the month rule can be combined with either a 60th day +* of the year rule or a last day of February rule. +*/ +void KARecurrence::fix() +{ + mCachedType = -1; + mFeb29Type = FEB29_FEB29; + int convert = 0; + int days[2] = { 0, 0 }; + RecurrenceRule* rrules[2]; + RecurrenceRule::List rrulelist = rRules(); + RecurrenceRule::List::ConstIterator rr = rrulelist.begin(); + for (int i = 0; i < 2 && rr != rrulelist.end(); ++i, ++rr) + { + RecurrenceRule* rrule = *rr; + rrules[i] = rrule; + bool stop = true; + int rtype = recurrenceType(rrule); + switch (rtype) + { + case rHourly: + // Convert an hourly recurrence to a minutely one + rrule->setRecurrenceType(RecurrenceRule::rMinutely); + rrule->setFrequency(rrule->frequency() * 60); + // fall through to rMinutely + case rMinutely: + case rDaily: + case rWeekly: + case rMonthlyDay: + case rMonthlyPos: + case rYearlyPos: + if (!convert) + ++rr; // remove all rules except the first + break; + case rOther: + if (dailyType(rrule)) + { // it's a daily rule with BYDAYS + if (!convert) + ++rr; // remove all rules except the first + } + break; + case rYearlyDay: + { + // Ensure that the yearly day number is 60 (i.e. Feb 29th/Mar 1st) + if (convert) + { + // This is the second rule. + // Ensure that it can be combined with the first one. + if (days[0] != 29 + || rrule->frequency() != rrules[0]->frequency() + || rrule->startDt() != rrules[0]->startDt()) + break; + } + QValueList ds = rrule->byYearDays(); + if (!ds.isEmpty() && ds.first() == 60) + { + ++convert; // this rule needs to be converted + days[i] = 60; + stop = false; + break; + } + break; // not day 60, so remove this rule + } + case rYearlyMonth: + { + QValueList ds = rrule->byMonthDays(); + if (!ds.isEmpty()) + { + int day = ds.first(); + if (convert) + { + // This is the second rule. + // Ensure that it can be combined with the first one. + if (day == days[0] || day == -1 && days[0] == 60 + || rrule->frequency() != rrules[0]->frequency() + || rrule->startDt() != rrules[0]->startDt()) + break; + } + if (ds.count() > 1) + { + ds.clear(); // remove all but the first day + ds.append(day); + rrule->setByMonthDays(ds); + } + if (day == -1) + { + // Last day of the month - only combine if it's February + QValueList months = rrule->byMonths(); + if (months.count() != 1 || months.first() != 2) + day = 0; + } + if (day == 29 || day == -1) + { + ++convert; // this rule may need to be converted + days[i] = day; + stop = false; + break; + } + } + if (!convert) + ++rr; + break; + } + default: + break; + } + if (stop) + break; + } + + // Remove surplus rules + for ( ; rr != rrulelist.end(); ++rr) + { + removeRRule(*rr); + delete *rr; + } + + QDate end; + int count; + QValueList months; + if (convert == 2) + { + // There are two yearly recurrence rules to combine into a February 29th recurrence. + // Combine the two recurrence rules into a single rYearlyMonth rule falling on Feb 29th. + // Find the duration of the two RRULEs combined, using the shorter of the two if they differ. + if (days[0] != 29) + { + // Swap the two rules so that the 29th rule is the first + RecurrenceRule* rr = rrules[0]; + rrules[0] = rrules[1]; // the 29th rule + rrules[1] = rr; + int d = days[0]; + days[0] = days[1]; + days[1] = d; // the non-29th day + } + // If February is included in the 29th rule, remove it to avoid duplication + months = rrules[0]->byMonths(); + if (months.remove(2)) + rrules[0]->setByMonths(months); + + count = combineDurations(rrules[0], rrules[1], end); + mFeb29Type = (days[1] == 60) ? FEB29_MAR1 : FEB29_FEB28; + } + else if (convert == 1 && days[0] == 60) + { + // There is a single 60th day of the year rule. + // Convert it to a February 29th recurrence. + count = duration(); + if (!count) + end = endDate(); + mFeb29Type = FEB29_MAR1; + } + else + return; + + // Create the new February 29th recurrence + setNewRecurrenceType(RecurrenceRule::rYearly, frequency()); + RecurrenceRule* rrule = defaultRRule(); + months.append(2); + rrule->setByMonths(months); + QValueList ds; + ds.append(29); + rrule->setByMonthDays(ds); + if (count) + setDuration(count); + else + setEndDate(end); +} + +/****************************************************************************** +* Get the next time the recurrence occurs, strictly after a specified time. +*/ +QDateTime KARecurrence::getNextDateTime(const QDateTime& preDateTime) const +{ + switch (type()) + { + case ANNUAL_DATE: + case ANNUAL_POS: + { + Recurrence recur; + writeRecurrence(recur); + return recur.getNextDateTime(preDateTime); + } + default: + return Recurrence::getNextDateTime(preDateTime); + } +} + +/****************************************************************************** +* Get the previous time the recurrence occurred, strictly before a specified time. +*/ +QDateTime KARecurrence::getPreviousDateTime(const QDateTime& afterDateTime) const +{ + switch (type()) + { + case ANNUAL_DATE: + case ANNUAL_POS: + { + Recurrence recur; + writeRecurrence(recur); + return recur.getPreviousDateTime(afterDateTime); + } + default: + return Recurrence::getPreviousDateTime(afterDateTime); + } +} + +/****************************************************************************** +* Initialise a KCal::Recurrence to be the same as this instance. +* Additional recurrence rules are created as necessary if it recurs on Feb 29th. +*/ +void KARecurrence::writeRecurrence(KCal::Recurrence& recur) const +{ + recur.clear(); + recur.setStartDateTime(startDateTime()); + recur.setExDates(exDates()); + recur.setExDateTimes(exDateTimes()); + const RecurrenceRule* rrule = defaultRRuleConst(); + if (!rrule) + return; + int freq = frequency(); + int count = duration(); + static_cast(&recur)->setNewRecurrenceType(rrule->recurrenceType(), freq); + if (count) + recur.setDuration(count); + else + recur.setEndDateTime(endDateTime()); + switch (type()) + { + case DAILY: + if (rrule->byDays().isEmpty()) + break; + // fall through to rWeekly + case WEEKLY: + case MONTHLY_POS: + recur.defaultRRule(true)->setByDays(rrule->byDays()); + break; + case MONTHLY_DAY: + recur.defaultRRule(true)->setByMonthDays(rrule->byMonthDays()); + break; + case ANNUAL_POS: + recur.defaultRRule(true)->setByMonths(rrule->byMonths()); + recur.defaultRRule()->setByDays(rrule->byDays()); + break; + case ANNUAL_DATE: + { + QValueList months = rrule->byMonths(); + QValueList days = monthDays(); + bool special = (mFeb29Type != FEB29_FEB29 && !days.isEmpty() + && days.first() == 29 && months.remove(2)); + RecurrenceRule* rrule1 = recur.defaultRRule(); + rrule1->setByMonths(months); + rrule1->setByMonthDays(days); + if (!special) + break; + + // It recurs on the 29th February. + // Create an additional 60th day of the year, or last day of February, rule. + RecurrenceRule* rrule2 = new RecurrenceRule(); + rrule2->setRecurrenceType(RecurrenceRule::rYearly); + rrule2->setFrequency(freq); + rrule2->setStartDt(startDateTime()); + rrule2->setFloats(doesFloat()); + if (!count) + rrule2->setEndDt(endDateTime()); + if (mFeb29Type == FEB29_MAR1) + { + QValueList ds; + ds.append(60); + rrule2->setByYearDays(ds); + } + else + { + QValueList ds; + ds.append(-1); + rrule2->setByMonthDays(ds); + QValueList ms; + ms.append(2); + rrule2->setByMonths(ms); + } + + if (months.isEmpty()) + { + // Only February recurs. + // Replace the RRULE and keep the recurrence count the same. + if (count) + rrule2->setDuration(count); + recur.unsetRecurs(); + } + else + { + // Months other than February also recur on the 29th. + // Remove February from the list and add a separate RRULE for February. + if (count) + { + rrule1->setDuration(-1); + rrule2->setDuration(-1); + if (count > 0) + { + /* Adjust counts in the two rules to keep the correct occurrence total. + * Note that durationTo() always includes the start date. Since for an + * individual RRULE the start date may not actually be included, we need + * to decrement the count if the start date doesn't actually recur in + * this RRULE. + * Note that if the count is small, one of the rules may not recur at + * all. In that case, retain it so that the February 29th characteristic + * is not lost should the user later change the recurrence count. + */ + QDateTime end = endDateTime(); +kdDebug()<<"29th recurrence: count="< +#include +#include +#include +#include + +#include "checkbox.h" +#include "latecancel.moc" + + +// Collect these widget labels together to ensure consistent wording and +// translations across different modules. +QString LateCancelSelector::i18n_CancelIfLate() { return i18n("Cancel if late"); } +QString LateCancelSelector::i18n_n_CancelIfLate() { return i18n("Ca&ncel if late"); } +QString LateCancelSelector::i18n_AutoCloseWin() { return i18n("Auto-close window after this time"); } +QString LateCancelSelector::i18n_AutoCloseWinLC() { return i18n("Auto-close window after late-cancelation time"); } +QString LateCancelSelector::i18n_i_AutoCloseWinLC() { return i18n("Auto-close w&indow after late-cancelation time"); } + + +LateCancelSelector::LateCancelSelector(bool allowHourMinute, QWidget* parent, const char* name) + : QFrame(parent, name), + mDateOnly(false), + mReadOnly(false), + mAutoCloseShown(false) +{ + QString whatsThis = i18n("If checked, the alarm will be canceled if it cannot be triggered within the " + "specified period after its scheduled time. Possible reasons for not triggering " + "include your being logged off, X not running, or the alarm daemon not running.\n\n" + "If unchecked, the alarm will be triggered at the first opportunity after " + "its scheduled time, regardless of how late it is."); + + setFrameStyle(QFrame::NoFrame); + mLayout = new QVBoxLayout(this, 0, KDialog::spacingHint()); + + mStack = new QWidgetStack(this); + mCheckboxFrame = new QFrame(mStack); + mCheckboxFrame->setFrameStyle(QFrame::NoFrame); + mStack->addWidget(mCheckboxFrame, 1); + QBoxLayout* layout = new QVBoxLayout(mCheckboxFrame, 0, 0); + mCheckbox = new CheckBox(i18n_n_CancelIfLate(), mCheckboxFrame); + mCheckbox->setFixedSize(mCheckbox->sizeHint()); + connect(mCheckbox, SIGNAL(toggled(bool)), SLOT(slotToggled(bool))); + QWhatsThis::add(mCheckbox, whatsThis); + layout->addWidget(mCheckbox, 0, Qt::AlignAuto); + + mTimeSelectorFrame = new QFrame(mStack); + mTimeSelectorFrame->setFrameStyle(QFrame::NoFrame); + mStack->addWidget(mTimeSelectorFrame, 2); + layout = new QVBoxLayout(mTimeSelectorFrame, 0, 0); + mTimeSelector = new TimeSelector(i18n("Cancel if late by 10 minutes", "Ca&ncel if late by"), QString::null, + whatsThis, i18n("Enter how late will cause the alarm to be canceled"), + allowHourMinute, mTimeSelectorFrame); + connect(mTimeSelector, SIGNAL(toggled(bool)), SLOT(slotToggled(bool))); + layout->addWidget(mTimeSelector); + mLayout->addWidget(mStack); + + layout = new QHBoxLayout(mLayout, KDialog::spacingHint()); + layout->addSpacing(3*KDialog::spacingHint()); + mAutoClose = new CheckBox(i18n_AutoCloseWin(), this); + mAutoClose->setFixedSize(mAutoClose->sizeHint()); + QWhatsThis::add(mAutoClose, i18n("Automatically close the alarm window after the expiry of the late-cancelation period")); + layout->addWidget(mAutoClose); + layout->addStretch(); + + mAutoClose->hide(); + mAutoClose->setEnabled(false); +} + +/****************************************************************************** +* Set the read-only status. +*/ +void LateCancelSelector::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + mCheckbox->setReadOnly(mReadOnly); + mTimeSelector->setReadOnly(mReadOnly); + mAutoClose->setReadOnly(mReadOnly); + } +} + +int LateCancelSelector::minutes() const +{ + return mTimeSelector->minutes(); +} + +void LateCancelSelector::setMinutes(int minutes, bool dateOnly, TimePeriod::Units defaultUnits) +{ + slotToggled(minutes); + mTimeSelector->setMinutes(minutes, dateOnly, defaultUnits); +} + +void LateCancelSelector::setDateOnly(bool dateOnly) +{ + if (dateOnly != mDateOnly) + { + mDateOnly = dateOnly; + if (mTimeSelector->isChecked()) // don't change when it's not visible + mTimeSelector->setDateOnly(dateOnly); + } +} + +void LateCancelSelector::showAutoClose(bool show) +{ + if (show) + mAutoClose->show(); + else + mAutoClose->hide(); + mAutoCloseShown = show; + mLayout->activate(); +} + +bool LateCancelSelector::isAutoClose() const +{ + return mAutoCloseShown && mAutoClose->isEnabled() && mAutoClose->isChecked(); +} + +void LateCancelSelector::setAutoClose(bool autoClose) +{ + mAutoClose->setChecked(autoClose); +} + +/****************************************************************************** +* Called when either of the checkboxes is toggled. +*/ +void LateCancelSelector::slotToggled(bool on) +{ + mCheckbox->setChecked(on); + mTimeSelector->setChecked(on); + if (on) + { + mTimeSelector->setDateOnly(mDateOnly); + mStack->raiseWidget(mTimeSelectorFrame); + } + else + mStack->raiseWidget(mCheckboxFrame); + mAutoClose->setEnabled(on); +} diff --git a/kalarm/latecancel.h b/kalarm/latecancel.h new file mode 100644 index 000000000..ccbc389b3 --- /dev/null +++ b/kalarm/latecancel.h @@ -0,0 +1,69 @@ +/* + * latecancel.h - widget to specify cancellation if late + * Program: kalarm + * Copyright (C) 2004 by 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. + */ + +#ifndef LATECANCEL_H +#define LATECANCEL_H + +#include + +#include "timeperiod.h" +#include "timeselector.h" +class QBoxLayout; +class QWidgetStack; +class CheckBox; + + +class LateCancelSelector : public QFrame +{ + Q_OBJECT + public: + LateCancelSelector(bool allowHourMinute, QWidget* parent, const char* name = 0); + int minutes() const; + void setMinutes(int Minutes, bool dateOnly, TimePeriod::Units defaultUnits); + void setDateOnly(bool dateOnly); + void showAutoClose(bool show); + bool isAutoClose() const; + void setAutoClose(bool autoClose); + bool isReadOnly() const { return mReadOnly; } + void setReadOnly(bool); + + static QString i18n_CancelIfLate(); // plain text of 'Cancel if late' checkbox + static QString i18n_n_CancelIfLate(); // text of 'Cancel if late' checkbox, with 'N' shortcut + static QString i18n_AutoCloseWin(); // plain text of 'Auto-close window after this time' checkbox + static QString i18n_AutoCloseWinLC(); // plain text of 'Auto-close window after late-cancelation time' checkbox + static QString i18n_i_AutoCloseWinLC(); // text of 'Auto-close window after late-cancelation time' checkbox, with 'I' shortcut + + private slots: + void slotToggled(bool); + + private: + QBoxLayout* mLayout; // overall layout for the widget + QWidgetStack* mStack; // contains mCheckboxFrame and mTimeSelectorFrame + QFrame* mCheckboxFrame; + CheckBox* mCheckbox; // displayed when late cancellation is not selected + QFrame* mTimeSelectorFrame; + TimeSelector* mTimeSelector; // displayed when late cancellation is selected + CheckBox* mAutoClose; + bool mDateOnly; // hours/minutes units not allowed + bool mReadOnly; // widget is read-only + bool mAutoCloseShown; // auto-close checkbox is visible +}; + +#endif // LATECANCEL_H diff --git a/kalarm/lib/Makefile.am b/kalarm/lib/Makefile.am new file mode 100644 index 000000000..7d68a252b --- /dev/null +++ b/kalarm/lib/Makefile.am @@ -0,0 +1,22 @@ +INCLUDES = -I$(top_srcdir)/kalarm -I$(top_srcdir) $(all_includes) + +noinst_LTLIBRARIES = libkalarm.la + +libkalarm_la_METASOURCES = AUTO + +libkalarm_la_SOURCES = \ + buttongroup.cpp checkbox.cpp colourcombo.cpp colourlist.cpp \ + combobox.cpp dateedit.cpp datetime.cpp label.cpp messagebox.cpp \ + pushbutton.cpp radiobutton.cpp timeedit.cpp timespinbox.cpp \ + timeperiod.cpp shellprocess.cpp slider.cpp spinbox.cpp spinbox2.cpp \ + lineedit.cpp synchtimer.cpp + +noinst_HEADERS = \ + buttongroup.h checkbox.h colourcombo.h colourlist.h \ + combobox.h dateedit.h datetime.h label.h lineedit.h messagebox.h \ + pushbutton.h radiobutton.h timeedit.h timespinbox.h \ + timeperiod.h shellprocess.h slider.h spinbox.h spinbox2.h \ + synchtimer.h spinbox2private.h + +DOXYGEN_REFERENCES=kdecore kdeui libkdepim +include $(top_srcdir)/admin/Doxyfile.am diff --git a/kalarm/lib/buttongroup.cpp b/kalarm/lib/buttongroup.cpp new file mode 100644 index 000000000..b448df48a --- /dev/null +++ b/kalarm/lib/buttongroup.cpp @@ -0,0 +1,70 @@ +/* + * buttongroup.cpp - QButtonGroup with an extra signal and KDE 2 compatibility + * Program: kalarm + * Copyright (c) 2002, 2004 by 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 "kalarm.h" + +#include +#include +#include + +#include "buttongroup.moc" + + +ButtonGroup::ButtonGroup(QWidget* parent, const char* name) + : QButtonGroup(parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(const QString& title, QWidget* parent, const char* name) + : QButtonGroup(title, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(int strips, Qt::Orientation orient, QWidget* parent, const char* name) + : QButtonGroup(strips, orient, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +ButtonGroup::ButtonGroup(int strips, Qt::Orientation orient, const QString& title, QWidget* parent, const char* name) + : QButtonGroup(strips, orient, title, parent, name) +{ + connect(this, SIGNAL(clicked(int)), SIGNAL(buttonSet(int))); +} + +/****************************************************************************** + * Inserts a button in the group. + * This should really be a virtual method... + */ +int ButtonGroup::insert(QButton* button, int id) +{ + id = QButtonGroup::insert(button, id); + connect(button, SIGNAL(toggled(bool)), SLOT(slotButtonToggled(bool))); + return id; +} + +/****************************************************************************** + * Called when one of the member buttons is toggled. + */ +void ButtonGroup::slotButtonToggled(bool) +{ + emit buttonSet(selectedId()); +} diff --git a/kalarm/lib/buttongroup.h b/kalarm/lib/buttongroup.h new file mode 100644 index 000000000..1d647b420 --- /dev/null +++ b/kalarm/lib/buttongroup.h @@ -0,0 +1,90 @@ +/* + * buttongroup.h - QButtonGroup with an extra signal and Qt 2 compatibility + * Program: kalarm + * Copyright © 2002,2004,2006 by 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. + */ +#ifndef BUTTONGROUP_H +#define BUTTONGROUP_H + +#include + + +/** + * @short A QButtonGroup with signal on new selection, plus Qt 2 compatibility. + * + * The ButtonGroup class provides an enhanced version of the QButtonGroup class. + * + * It emits an additional signal, buttonSet(int), whenever any of its buttons + * changes state, for whatever reason, including programmatic control. (The + * QButtonGroup class only emits signals when buttons are clicked on by the user.) + * The class also provides Qt 2 compatibility. + * + * @author David Jarvie + */ +class ButtonGroup : public QButtonGroup +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ButtonGroup(QWidget* parent, const char* name = 0); + /** Constructor. + * @param title The title displayed for this button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(const QString& title, QWidget* parent, const char* name = 0); + /** Constructor. + * @param strips The number of rows or columns of buttons. + * @param orient The orientation (Qt::Horizontal or Qt::Vertical) of the button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(int strips, Qt::Orientation orient, QWidget* parent, const char* name = 0); + /** Constructor. + * @param strips The number of rows or columns of buttons. + * @param orient The orientation (Qt::Horizontal or Qt::Vertical) of the button group. + * @param title The title displayed for this button group. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + ButtonGroup(int strips, Qt::Orientation orient, const QString& title, QWidget* parent, const char* name = 0); + /** Inserts a button in the group. + * This overrides the insert() method of QButtonGroup, which should really be a virtual method... + * @param button The button to insert. + * @param id The identifier for the button. + * @return The identifier of the inserted button. + */ + int insert(QButton* button, int id = -1); + /** Sets the button with the specified identifier to be on. If this is an exclusive group, + * all other buttons in the group will be set off. The buttonSet() signal is emitted. + * @param id The identifier of the button to set on. + */ + virtual void setButton(int id) { QButtonGroup::setButton(id); emit buttonSet(id); } + private slots: + void slotButtonToggled(bool); + signals: + /** Signal emitted whenever whenever any button in the group changes state, + * for whatever reason. + * @param id The identifier of the button which is now selected. + */ + void buttonSet(int id); +}; + +#endif // BUTTONGROUP_H diff --git a/kalarm/lib/checkbox.cpp b/kalarm/lib/checkbox.cpp new file mode 100644 index 000000000..c600a4950 --- /dev/null +++ b/kalarm/lib/checkbox.cpp @@ -0,0 +1,133 @@ +/* + * checkbox.cpp - check box with read-only option + * Program: kalarm + * Copyright (c) 2002, 2003 by 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 "checkbox.moc" + + +CheckBox::CheckBox(QWidget* parent, const char* name) + : QCheckBox(parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +CheckBox::CheckBox(const QString& text, QWidget* parent, const char* name) + : QCheckBox(text, parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the checkbox can be toggled by the +* application, but not by the user. +*/ +void CheckBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +/****************************************************************************** +* Specify a widget to receive focus when the checkbox is clicked on. +*/ +void CheckBox::setFocusWidget(QWidget* w, bool enable) +{ + mFocusWidget = w; + mFocusWidgetEnable = enable; + if (w) + connect(this, SIGNAL(clicked()), SLOT(slotClicked())); + else + disconnect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); +} + +/****************************************************************************** +* Called when the checkbox is clicked. +* If it is now checked, focus is transferred to any specified focus widget. +*/ +void CheckBox::slotClicked() +{ + if (mFocusWidget && isChecked()) + { + if (mFocusWidgetEnable) + mFocusWidget->setEnabled(true); + mFocusWidget->setFocus(); + } +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the checkbox state are discarded. +*/ +void CheckBox::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QCheckBox::mousePressEvent(e); +} + +void CheckBox::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QCheckBox::mouseReleaseEvent(e); +} + +void CheckBox::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QCheckBox::mouseMoveEvent(e); +} + +void CheckBox::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + break; + default: + return; + } + QCheckBox::keyPressEvent(e); +} + +void CheckBox::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QCheckBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/checkbox.h b/kalarm/lib/checkbox.h new file mode 100644 index 000000000..72ad4aee3 --- /dev/null +++ b/kalarm/lib/checkbox.h @@ -0,0 +1,88 @@ +/* + * checkbox.h - check box with focus widget and read-only options + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by 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. + */ + +#ifndef CHECKBOX_H +#define CHECKBOX_H + +#include + + +/** + * @short A QCheckBox with focus widget and read-only options. + * + * The CheckBox class is a QCheckBox with the ability to transfer focus to another + * widget when checked, and with a read-only option. + * + * Another widget may be specified as the focus widget for the check box. Whenever + * the user clicks on the check box so as to set its state to checked, focus is + * automatically transferred to the focus widget. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class CheckBox : public QCheckBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit CheckBox(QWidget* parent, const char* name = 0); + /** Constructor. + * @param text Text to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + CheckBox(const QString& text, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the check box is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns the widget which receives focus when the user selects the check box by clicking on it. */ + QWidget* focusWidget() const { return mFocusWidget; } + /** Specifies a widget to receive focus when the user selects the check box by clicking on it. + * @param widget Widget to receive focus. + * @param enable If true, @p widget will be enabled before receiving focus. If + * false, the enabled state of @p widget will be left unchanged when + * the check box is clicked. + */ + void setFocusWidget(QWidget* widget, bool enable = true); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + protected slots: + void slotClicked(); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QCheckBox + QWidget* mFocusWidget; // widget to receive focus when button is clicked on + bool mFocusWidgetEnable; // enable focus widget before setting focus + bool mReadOnly; // value cannot be changed +}; + +#endif // CHECKBOX_H diff --git a/kalarm/lib/colourcombo.cpp b/kalarm/lib/colourcombo.cpp new file mode 100644 index 000000000..d5fa052ac --- /dev/null +++ b/kalarm/lib/colourcombo.cpp @@ -0,0 +1,239 @@ +/* + * colourcombo.cpp - colour selection combo box + * Program: kalarm + * Copyright (c) 2001 - 2003, 2005 by David Jarvie + * + * Some code taken from kdelibs/kdeui/kcolorcombo.cpp in the KDE libraries: + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * + * 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 + +#include +#include + +#include "preferences.h" +#include "colourcombo.moc" + + +ColourCombo::ColourCombo(QWidget* parent, const char* name, const QColor& defaultColour) + : QComboBox(parent, name), + mColourList(Preferences::messageColours()), + mSelectedColour(defaultColour), + mCustomColour(255, 255, 255), + mReadOnly(false), + mDisabled(false) +{ + addColours(); + connect(this, SIGNAL(activated(int)), SLOT(slotActivated(int))); + connect(this, SIGNAL(highlighted(int)), SLOT(slotHighlighted(int))); + Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPreferencesChanged())); +} + +void ColourCombo::setColour(const QColor& colour) +{ + mSelectedColour = colour; + addColours(); +} + +/****************************************************************************** +* Set a new colour selection. +*/ +void ColourCombo::setColours(const ColourList& colours) +{ + mColourList = colours; + if (mSelectedColour != mCustomColour + && !mColourList.contains(mSelectedColour)) + { + // The current colour has been deleted + mSelectedColour = mColourList.count() ? mColourList.first() : mCustomColour; + } + addColours(); +} + +/****************************************************************************** +* Called when the user changes the preference settings. +* If the colour list has changed, update the colours displayed. +*/ +void ColourCombo::slotPreferencesChanged() +{ + const ColourList& prefColours = Preferences::messageColours(); + if (prefColours != mColourList) + setColours(prefColours); // update the display with the new colours +} + +/****************************************************************************** +* Enable or disable the control. +* If it is disabled, its colour is set to the dialog background colour. +*/ +void ColourCombo::setEnabled(bool enable) +{ + if (enable && mDisabled) + { + mDisabled = false; + setColour(mSelectedColour); + } + else if (!enable && !mDisabled) + { + mSelectedColour = color(); + int end = count(); + if (end > 1) + { + // Add a dialog background colour item + QPixmap pm = *pixmap(1); + pm.fill(paletteBackgroundColor()); + insertItem(pm); + setCurrentItem(end); + } + mDisabled = true; + } + QComboBox::setEnabled(enable); +} + +void ColourCombo::slotActivated(int index) +{ + if (index) + mSelectedColour = mColourList[index - 1]; + else + { + if (KColorDialog::getColor(mCustomColour, this) == QDialog::Accepted) + { + QRect rect; + drawCustomItem(rect, false); + } + mSelectedColour = mCustomColour; + } + emit activated(mSelectedColour); +} + +void ColourCombo::slotHighlighted(int index) +{ + mSelectedColour = index ? mColourList[index - 1] : mCustomColour; + emit highlighted(mSelectedColour); +} + +/****************************************************************************** +* Initialise the items in the combo box to one for each colour in the list. +*/ +void ColourCombo::addColours() +{ + clear(); + + for (ColourList::const_iterator it = mColourList.begin(); ; ++it) + { + if (it == mColourList.end()) + { + mCustomColour = mSelectedColour; + break; + } + if (mSelectedColour == *it) + break; + } + + QRect rect; + drawCustomItem(rect, true); + + QPainter painter; + QPixmap pixmap(rect.width(), rect.height()); + int i = 1; + for (ColourList::const_iterator it = mColourList.begin(); it != mColourList.end(); ++i, ++it) + { + painter.begin(&pixmap); + QBrush brush(*it); + painter.fillRect(rect, brush); + painter.end(); + + insertItem(pixmap); + pixmap.detach(); + + if (*it == mSelectedColour.rgb()) + setCurrentItem(i); + } +} + +void ColourCombo::drawCustomItem(QRect& rect, bool insert) +{ + QPen pen; + if (qGray(mCustomColour.rgb()) < 128) + pen.setColor(Qt::white); + else + pen.setColor(Qt::black); + + QPainter painter; + QFontMetrics fm = QFontMetrics(painter.font()); + rect.setRect(0, 0, width(), fm.height() + 4); + QPixmap pixmap(rect.width(), rect.height()); + + painter.begin(&pixmap); + QBrush brush(mCustomColour); + painter.fillRect(rect, brush); + painter.setPen(pen); + painter.drawText(2, fm.ascent() + 2, i18n("Custom...")); + painter.end(); + + if (insert) + insertItem(pixmap); + else + changeItem(pixmap, 0); + pixmap.detach(); +} + +void ColourCombo::setReadOnly(bool ro) +{ + mReadOnly = ro; +} + +void ColourCombo::resizeEvent(QResizeEvent* re) +{ + QComboBox::resizeEvent(re); + addColours(); +} + +void ColourCombo::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QComboBox::mousePressEvent(e); +} + +void ColourCombo::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseReleaseEvent(e); +} + +void ColourCombo::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseMoveEvent(e); +} + +void ColourCombo::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QComboBox::keyPressEvent(e); +} + +void ColourCombo::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QComboBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/colourcombo.h b/kalarm/lib/colourcombo.h new file mode 100644 index 000000000..f3e4ebca6 --- /dev/null +++ b/kalarm/lib/colourcombo.h @@ -0,0 +1,102 @@ +/* + * colourcombo.h - colour selection combo box + * Program: kalarm + * Copyright © 2001-2003,2005,2006 by 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. + */ + +#ifndef COLOURCOMBO_H +#define COLOURCOMBO_H + +#include +#include "colourlist.h" + + +/** + * @short A colour selection combo box whose colour list can be specified. + * + * The ColourCombo class is a combo box allowing the user to select a colour. + * + * It is similar to KColorCombo but allows the list of colours to be restricted to those + * which are specified. The first item in the list is a custom colour entry, which allows + * the user to define an arbitrary colour. The remaining entries in the list are preset + * by the program. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class ColourCombo : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param defaultColour The colour which is selected by default. + */ + explicit ColourCombo(QWidget* parent = 0, const char* name = 0, const QColor& defaultColour = 0xFFFFFF); + /** Returns the selected colour. */ + QColor color() const { return mSelectedColour; } + /** Returns the selected colour. */ + QColor colour() const { return mSelectedColour; } + /** Sets the selected colour to @p c. */ + void setColor(const QColor& c) { setColour(c); } + /** Sets the selected colour to @p c. */ + void setColour(const QColor& c); + /** Initialises the list of colours to @p list. */ + void setColours(const ColourList& list); + /** Returns true if the first entry in the list, i.e. the custom colour, is selected. */ + bool isCustomColour() const { return !currentItem(); } + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the combo box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + signals: + /** Signal emitted when a new colour has been selected. */ + void activated(const QColor&); // a new colour box has been selected + /** Signal emitted when a new colour has been highlighted. */ + void highlighted(const QColor&); // a new item has been highlighted + public slots: + /** Enables or disables the widget. */ + virtual void setEnabled(bool enabled); + protected: + virtual void resizeEvent(QResizeEvent*); + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private slots: + void slotActivated(int index); + void slotHighlighted(int index); + void slotPreferencesChanged(); + private: + void addColours(); + void drawCustomItem(QRect&, bool insert); + + ColourList mColourList; // the sorted colours to display + QColor mSelectedColour; // currently selected colour + QColor mCustomColour; // current colour of the Custom item + bool mReadOnly; // value cannot be changed + bool mDisabled; +}; + +#endif // COLOURCOMBO_H diff --git a/kalarm/lib/colourlist.cpp b/kalarm/lib/colourlist.cpp new file mode 100644 index 000000000..e02a64466 --- /dev/null +++ b/kalarm/lib/colourlist.cpp @@ -0,0 +1,43 @@ +/* + * colourlist.cpp - an ordered list of colours + * Program: kalarm + * Copyright (C) 2003 by David Jarvie software@astrojar.org.uk + * + * 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 "colourlist.h" + + +ColourList::ColourList(const QColor* colours) +{ + while (colours->isValid()) + mList.append((*colours++).rgb()); +} + +void ColourList::insert(const QColor& colour) +{ + QRgb rgb = colour.rgb(); + for (QValueListIterator it = mList.begin(); it != mList.end(); ++it) + { + if (rgb <= *it) + { + if (rgb != *it) // don't insert duplicates + mList.insert(it, rgb); + return; + } + } + mList.append(rgb); +} diff --git a/kalarm/lib/colourlist.h b/kalarm/lib/colourlist.h new file mode 100644 index 000000000..ef641c04a --- /dev/null +++ b/kalarm/lib/colourlist.h @@ -0,0 +1,110 @@ +/* + * colourlist.h - an ordered list of colours + * Program: kalarm + * Copyright (C) 2003, 2005 by 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. + */ + +#ifndef COLOURLIST_H +#define COLOURLIST_H + +#include +#include +#include + + +/** + * @short Represents a sorted list of colours. + * + * The ColourList class holds a list of colours, sorted in RGB value order. + * + * It provides a sorted QValueList of colours in RGB value order, with iterators + * and other access methods which return either QRgb or QColor objects. + * + * @author David Jarvie + */ +class ColourList +{ + public: + typedef size_t size_type; + typedef QValueListConstIterator const_iterator; + + /** Constructs an empty list. */ + ColourList() { } + /** Copy constructor. */ + ColourList(const ColourList& l) : mList(l.mList) { } + /** Constructs a list whose values are preset to the colours in @p list. */ + ColourList(const QValueList& list) : mList(list) { qHeapSort(mList); } + /** Constructs a list whose values are preset to the colours in the @p list. + * Terminate @p list by an invalid colour. + */ + ColourList(const QColor* list); + /** Assignment operator. */ + ColourList& operator=(const ColourList& l) { mList = l.mList; return *this; } + /** Sets the list to comprise the colours in @p list. */ + ColourList& operator=(const QValueList& list) { mList = list; qHeapSort(mList); return *this; } + /** Removes all values from the list. */ + void clear() { mList.clear(); } + /** Adds the specified colour @p c to the list. */ + void insert(const QColor& c); + /** Removes the colour @p c from the list. */ + void remove(const QColor& c) { mList.remove(c.rgb()); } + /** Adds the specified colour @p c to the list. */ + ColourList& operator+=(const QColor& c) { insert(c); return *this; } + /** Adds the colours in @p list to this list. */ + ColourList& operator+=(const ColourList& list) { mList += list.mList; qHeapSort(mList); return *this; } + /** Returns true if the colours in the two lists are the same. */ + bool operator==(const ColourList& l) const { return mList == l.mList; } + /** Returns true if the colours in the two lists differ. */ + bool operator!=(const ColourList& l) const { return mList != l.mList; } + /** Returns the number of colours in the list. */ + size_type count() const { return mList.count(); } + /** Returns true if the list is empty. */ + bool isEmpty() const { return mList.isEmpty(); } + /** Returns an iterator pointing to the first colour in the list. */ + const_iterator begin() const { return mList.begin(); } + /** Returns an iterator pointing past the last colour in the list. */ + const_iterator end() const { return mList.end(); } + /** Returns an iterator pointing to the last colour in the list, or end() if the list is empty. */ + const_iterator fromLast() const { return mList.fromLast(); } + /** Returns an iterator pointing to the colour at position @p i in the list. */ + const_iterator at(size_type i) const { return mList.at(i); } + /** Returns true if the list contains the colour @p c. */ + size_type contains(const QColor& c) const { return mList.contains(c.rgb()); } + /** Returns an iterator pointing to the first occurrence of colour @p c in the list. + * Returns end() if colour @p c is not in the list. + */ + const_iterator find(const QColor& c) const { return mList.find(c.rgb()); } + /** Returns an iterator pointing to the first occurrence of colour @p c in the list, starting. + * from position @p it. Returns end() if colour @p c is not in the list. + */ + const_iterator find(const_iterator it, const QColor& c) const { return mList.find(it, c.rgb()); } + /** Returns the index to the first occurrence of colour @p c in the list. + * Returns -1 if colour @p c is not in the list. + */ + int findIndex(const QColor& c) const { return mList.findIndex(c.rgb()); } + /** Returns the first colour in the list. If the list is empty, the result is undefined. */ + QColor first() const { return QColor(mList.first()); } + /** Returns the last colour in the list. If the list is empty, the result is undefined. */ + QColor last() const { return QColor(mList.last()); } + /** Returns the colour at position @p i in the list. If the item does not exist, the result is undefined. */ + QColor operator[](size_type i) const { return QColor(mList[i]); } + private: + void sort(); + QValueList mList; +}; + +#endif // COLOURLIST_H diff --git a/kalarm/lib/combobox.cpp b/kalarm/lib/combobox.cpp new file mode 100644 index 000000000..7e0bea4bd --- /dev/null +++ b/kalarm/lib/combobox.cpp @@ -0,0 +1,78 @@ +/* + * combobox.cpp - combo box with read-only option + * Program: kalarm + * Copyright (c) 2002 by 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 +#include "combobox.moc" + + +ComboBox::ComboBox(QWidget* parent, const char* name) + : QComboBox(parent, name), + mReadOnly(false) +{ } + +ComboBox::ComboBox(bool rw, QWidget* parent, const char* name) + : QComboBox(rw, parent, name), + mReadOnly(false) +{ } + +void ComboBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + if (lineEdit()) + lineEdit()->setReadOnly(ro); + } +} + +void ComboBox::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QComboBox::mousePressEvent(e); +} + +void ComboBox::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseReleaseEvent(e); +} + +void ComboBox::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QComboBox::mouseMoveEvent(e); +} + +void ComboBox::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QComboBox::keyPressEvent(e); +} + +void ComboBox::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QComboBox::keyReleaseEvent(e); +} diff --git a/kalarm/lib/combobox.h b/kalarm/lib/combobox.h new file mode 100644 index 000000000..d33ac147e --- /dev/null +++ b/kalarm/lib/combobox.h @@ -0,0 +1,69 @@ +/* + * combobox.h - combo box with read-only option + * Program: kalarm + * Copyright © 2002,2005,2006 by 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. + */ + +#ifndef COMBOBOX_H +#define COMBOBOX_H + +#include + + +/** + * @short A QComboBox with read-only option. + * + * The ComboBox class is a QComboBox with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class ComboBox : public QComboBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ComboBox(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param rw True for a read-write combo box, false for a read-only combo box. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit ComboBox(bool rw, QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the combo box is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + bool mReadOnly; // value cannot be changed +}; + +#endif // COMBOBOX_H diff --git a/kalarm/lib/dateedit.cpp b/kalarm/lib/dateedit.cpp new file mode 100644 index 000000000..71553d704 --- /dev/null +++ b/kalarm/lib/dateedit.cpp @@ -0,0 +1,122 @@ +/* + * dateedit.cpp - date entry widget + * Program: kalarm + * Copyright © 2002-2007 by 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 +#include +#include + +#include "dateedit.moc" + + +DateEdit::DateEdit(QWidget* parent, const char* name) + : KDateEdit(parent, name) +{ + connect(this, SIGNAL(dateEntered(const QDate&)), SLOT(newDateEntered(const QDate&))); +} + +void DateEdit::setMinDate(const QDate& d, const QString& errorDate) +{ + mMinDate = d; + if (mMinDate.isValid() && date().isValid() && date() < mMinDate) + setDate(mMinDate); + mMinDateErrString = errorDate; +} + +void DateEdit::setMaxDate(const QDate& d, const QString& errorDate) +{ + mMaxDate = d; + if (mMaxDate.isValid() && date().isValid() && date() > mMaxDate) + setDate(mMaxDate); + mMaxDateErrString = errorDate; +} + +void DateEdit::setInvalid() +{ + setDate(QDate()); +} + +// Check a new date against any minimum or maximum date. +void DateEdit::newDateEntered(const QDate& newDate) +{ + if (newDate.isValid()) + { + if (mMinDate.isValid() && newDate < mMinDate) + { + pastLimitMessage(mMinDate, mMinDateErrString, + i18n("Date cannot be earlier than %1")); + setDate(mMinDate); + } + else if (mMaxDate.isValid() && newDate > mMaxDate) + { + pastLimitMessage(mMaxDate, mMaxDateErrString, + i18n("Date cannot be later than %1")); + setDate(mMaxDate); + } + } +} + +void DateEdit::pastLimitMessage(const QDate& limit, const QString& error, const QString& defaultError) +{ + QString errString = error; + if (errString.isNull()) + { + if (limit == QDate::currentDate()) + errString = i18n("today"); + else + errString = KGlobal::locale()->formatDate(limit, true); + errString = defaultError.arg(errString); + } + KMessageBox::sorry(this, errString); +} + +void DateEdit::mousePressEvent(QMouseEvent *e) +{ + if (isReadOnly()) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + KDateEdit::mousePressEvent(e); +} + +void DateEdit::mouseReleaseEvent(QMouseEvent* e) +{ + if (!isReadOnly()) + KDateEdit::mouseReleaseEvent(e); +} + +void DateEdit::mouseMoveEvent(QMouseEvent* e) +{ + if (!isReadOnly()) + KDateEdit::mouseMoveEvent(e); +} + +void DateEdit::keyPressEvent(QKeyEvent* e) +{ + if (!isReadOnly()) + KDateEdit::keyPressEvent(e); +} + +void DateEdit::keyReleaseEvent(QKeyEvent* e) +{ + if (!isReadOnly()) + KDateEdit::keyReleaseEvent(e); +} diff --git a/kalarm/lib/dateedit.h b/kalarm/lib/dateedit.h new file mode 100644 index 000000000..031a96741 --- /dev/null +++ b/kalarm/lib/dateedit.h @@ -0,0 +1,90 @@ +/* + * dateedit.h - date entry widget + * Program: kalarm + * Copyright © 2002-2007 by 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. + */ +#ifndef DATEEDIT_H +#define DATEEDIT_H + +#include + +/** + * @short Date edit widget with range limits. + * + * The DateEdit class provides a date editor with the ability to set limits on the + * dates which can be entered. + * + * Minimum and/or maximum permissible dates may be set, together with corresponding + * error messages. If the user tries to enter a date outside the allowed range, the + * appropriate error message (if any) is output using KMessageBox::sorry(). + * + * @author David Jarvie + */ +class DateEdit : public KDateEdit +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit DateEdit(QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget contains a valid date. */ + bool isValid() const { return date().isValid(); } + /** Returns the earliest date which can be entered. + * If there is no minimum date, returns an invalid date. + */ + const QDate& minDate() const { return mMinDate; } + /** Returns the latest date which can be entered. + * If there is no maximum date, returns an invalid date. + */ + const QDate& maxDate() const { return mMaxDate; } + /** Sets the earliest date which can be entered. + * @param date Earliest date allowed. If invalid, any minimum limit is removed. + * @param errorDate Error message to be displayed when a date earlier than + * @p date is entered. Set to QString::null to use the default error message. + */ + void setMinDate(const QDate& date, const QString& errorDate = QString::null); + /** Sets the latest date which can be entered. + * @param date Latest date allowed. If invalid, any maximum limit is removed. + * @param errorDate Error message to be displayed when a date later than + * @p date is entered. Set to QString::null to use the default error message. + */ + void setMaxDate(const QDate& date, const QString& errorDate = QString::null); + /** Sets the date held in the widget to an invalid date. */ + void setInvalid(); + + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + + private slots: + void newDateEntered(const QDate&); + + private: + void pastLimitMessage(const QDate& limit, const QString& error, const QString& defaultError); + + QDate mMinDate; // minimum allowed date, or invalid for no minimum + QDate mMaxDate; // maximum allowed date, or invalid for no maximum + QString mMinDateErrString; // error message when entered date < mMinDate + QString mMaxDateErrString; // error message when entered date > mMaxDate +}; + +#endif // DATEEDIT_H diff --git a/kalarm/lib/datetime.cpp b/kalarm/lib/datetime.cpp new file mode 100644 index 000000000..d7f5ee862 --- /dev/null +++ b/kalarm/lib/datetime.cpp @@ -0,0 +1,80 @@ +/* + * datetime.cpp - date/time representation with optional date-only value + * Program: kalarm + * Copyright (C) 2003, 2005 by 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 "kalarm.h" + +#include +#include + +#include "datetime.h" + +QTime DateTime::mStartOfDay; + +QTime DateTime::time() const +{ + return mDateOnly ? mStartOfDay : mDateTime.time(); +} + +QDateTime DateTime::dateTime() const +{ + return mDateOnly ? QDateTime(mDateTime.date(), mStartOfDay) : mDateTime; +} + +QString DateTime::formatLocale(bool shortFormat) const +{ + if (mDateOnly) + return KGlobal::locale()->formatDate(mDateTime.date(), shortFormat); + else if (mTimeValid) + return KGlobal::locale()->formatDateTime(mDateTime, shortFormat); + else + return QString::null; +} + +bool operator==(const DateTime& dt1, const DateTime& dt2) +{ + if (dt1.mDateTime.date() != dt2.mDateTime.date()) + return false; + if (dt1.mDateOnly && dt2.mDateOnly) + return true; + if (!dt1.mDateOnly && !dt2.mDateOnly) + { + bool valid1 = dt1.mTimeValid && dt1.mDateTime.time().isValid(); + bool valid2 = dt2.mTimeValid && dt2.mDateTime.time().isValid(); + if (!valid1 && !valid2) + return true; + if (!valid1 || !valid2) + return false; + return dt1.mDateTime.time() == dt2.mDateTime.time(); + } + return (dt1.mDateOnly ? dt2.mDateTime.time() : dt1.mDateTime.time()) == DateTime::startOfDay(); +} + +bool operator<(const DateTime& dt1, const DateTime& dt2) +{ + if (dt1.mDateTime.date() != dt2.mDateTime.date()) + return dt1.mDateTime.date() < dt2.mDateTime.date(); + if (dt1.mDateOnly && dt2.mDateOnly) + return false; + if (!dt1.mDateOnly && !dt2.mDateOnly) + return dt1.mDateTime.time() < dt2.mDateTime.time(); + QTime t = DateTime::startOfDay(); + if (dt1.mDateOnly) + return t < dt2.mDateTime.time(); + return dt1.mDateTime.time() < t; +} diff --git a/kalarm/lib/datetime.h b/kalarm/lib/datetime.h new file mode 100644 index 000000000..3b0831918 --- /dev/null +++ b/kalarm/lib/datetime.h @@ -0,0 +1,241 @@ +/* + * datetime.h - date/time representation with optional date-only value + * Program: kalarm + * Copyright © 2003,2005,2007 by 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. + */ +#ifndef DATETIME_H +#define DATETIME_H + +#include + + +/** + * @short A QDateTime with date-only option. + * + * The DateTime class holds a date, with or without a time. + * + * DateTime is very similar to the QDateTime class, with the additional option to + * hold a date-only value. This allows a single date-time representation to be used + * for both an event having a specific date and time, and an all-day event. + * + * The time assumed for date-only values is the start-of-day time set by setStartOfDay(). + * + * @author David Jarvie +*/ +class DateTime +{ + public: + /** Default constructor. + * Constructs an invalid date-time. + */ + DateTime() : mDateOnly(false), mTimeValid(false) { } + /** Constructor for a date-only value. */ + DateTime(const QDate& d) : mDateTime(d), mDateOnly(true) { } + /** Constructor for a date-time value. */ + DateTime(const QDate& d, const QTime& t) + : mDateTime(d, t), mDateOnly(false), mTimeValid(true) { } + /** Constructor for a date-time or date-only value. + * @param dt the date and time to use. + * @param dateOnly True to construct a date-only value; false to construct a date-time value. + */ + DateTime(const QDateTime& dt, bool dateOnly = false) + : mDateTime(dt), mDateOnly(dateOnly), mTimeValid(true) + { if (dateOnly) mDateTime.setTime(QTime()); } + /** Assignment operator. + * Sets the value to a specified date-time or date-only value. + */ + DateTime& operator=(const DateTime& dt) + { mDateTime = dt.mDateTime; mDateOnly = dt.mDateOnly; mTimeValid = dt.mTimeValid; return *this; } + /** Assignment operator. + * Sets the value to a specified date-time. + */ + DateTime& operator=(const QDateTime& dt) + { mDateTime = dt; mDateOnly = false; mTimeValid = true; return *this; } + /** Assignment operator. + * Sets the value to a specified date-only value. + */ + DateTime& operator=(const QDate& d) + { mDateTime.setDate(d); mDateTime.setTime(QTime()); mDateOnly = true; return *this; } + /** Returns true if the date is null and, if it is a date-time value, the time is also null. */ + bool isNull() const { return mDateTime.date().isNull() && (mDateOnly || mDateTime.time().isNull()); } + /** Returns true if the date is valid and, if it is a date-time value, the time is also valid. */ + bool isValid() const { return mDateTime.date().isValid() && (mDateOnly || mTimeValid && mDateTime.time().isValid()); } + /** Returns true if it is date-only value. */ + bool isDateOnly() const { return mDateOnly; } + /** Sets the value to be either date-only or date-time. + * @param d True to set the value to be date-only; false to set it to a date-time value. + */ + void setDateOnly(bool d) { if (d) mDateTime.setTime(QTime()); + else if (mDateOnly) mTimeValid = false; + mDateOnly = d; + } + /** Returns the date part of the value. */ + QDate date() const { return mDateTime.date(); } + /** Returns the time part of the value. + * If the value is date-only, the time returned is the start-of-day time set by setStartOfDay(). + */ + QTime time() const; + /** Returns the date and time of the value. + * If the value is date-only, the time part returned is equal to the start-of-day time set + * by setStartOfDay(). + */ + QDateTime dateTime() const; + /** Returns the date and time of the value. + * if the value is date-only, the time part returned is 00:00:00. + */ + QDateTime rawDateTime() const { return mDateTime; } + /** Sets a date-time or date-only value. + * @param dt the date-time to use. + * @param dateOnly True to set a date-only value; false to set a date-time value. + */ + void set(const QDateTime& dt, bool dateOnly = false) + { + mDateTime = dt; + mDateOnly = dateOnly; + if (dateOnly) + mDateTime.setTime(QTime()); + mTimeValid = true; + } + /** Sets a date-time value. */ + void set(const QDate& d, const QTime& t) + { mDateTime.setDate(d); mDateTime.setTime(t); mDateOnly = false; mTimeValid = true; } + /** Sets the time component of the value. + * The value is converted if necessary to be a date-time value. + */ + void setTime(const QTime& t) { mDateTime.setTime(t); mDateOnly = false; mTimeValid = true; } + /** Sets the value to a specified date-time value. + * @param secs The time_t date-time value, expressed as the number of seconds elapsed + * since 1970-01-01 00:00:00 UTC. + */ + void setTime_t(uint secs) { mDateTime.setTime_t(secs); mDateOnly = false; mTimeValid = true; } + /** Returns a DateTime value @p secs seconds later than the value of this object. + * If this object is date-only, @p secs is first rounded down to a whole number of days + * before adding the value. + */ + DateTime addSecs(int n) const + { + if (mDateOnly) + return DateTime(mDateTime.date().addDays(n / (3600*24)), true); + else + return DateTime(mDateTime.addSecs(n), false); + } + /** Returns a DateTime value @p mins minutes later than the value of this object. + * If this object is date-only, @p mins is first rounded down to a whole number of days + * before adding the value. + */ + DateTime addMins(int n) const + { + if (mDateOnly) + return DateTime(mDateTime.addDays(n / (60*24)), true); + else + return DateTime(mDateTime.addSecs(n * 60), false); + } + /** Returns a DateTime value @p n days later than the value of this object. */ + DateTime addDays(int n) const { return DateTime(mDateTime.addDays(n), mDateOnly); } + /** Returns a DateTime value @p n months later than the value of this object. */ + DateTime addMonths(int n) const { return DateTime(mDateTime.addMonths(n), mDateOnly); } + /** Returns a DateTime value @p n years later than the value of this object. */ + DateTime addYears(int n) const { return DateTime(mDateTime.addYears(n), mDateOnly); } + /** Returns the number of days from this date or date-time to @p dt. */ + int daysTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) : mDateTime.daysTo(dt.mDateTime); } + /** Returns the number of minutes from this date or date-time to @p dt. + * If either of the values is date-only, the result is calculated by simply + * taking the difference in dates and ignoring the times. + */ + int minsTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) * 24*60 : mDateTime.secsTo(dt.mDateTime) / 60; } + /** Returns the number of seconds from this date or date-time to @p dt. + * If either of the values is date-only, the result is calculated by simply + * taking the difference in dates and ignoring the times. + */ + int secsTo(const DateTime& dt) const + { return (mDateOnly || dt.mDateOnly) ? mDateTime.date().daysTo(dt.date()) * 24*3600 : mDateTime.secsTo(dt.mDateTime); } + /** Returns the value as a string. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString toString(Qt::DateFormat f = Qt::TextDate) const + { + if (mDateOnly) + return mDateTime.date().toString(f); + else if (mTimeValid) + return mDateTime.toString(f); + else + return QString::null; + } + /** Returns the value as a string. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString toString(const QString& format) const + { + if (mDateOnly) + return mDateTime.date().toString(format); + else if (mTimeValid) + return mDateTime.toString(format); + else + return QString::null; + } + /** Returns the value as a string, formatted according to the user's locale. + * If it is a date-time, both time and date are included in the output. + * If it is date-only, only the date is included in the output. + */ + QString formatLocale(bool shortFormat = true) const; + /** Sets the start-of-day time. + * The default value is midnight (0000 hrs). + */ + static void setStartOfDay(const QTime& sod) { mStartOfDay = sod; } + /** Returns the start-of-day time. */ + static QTime startOfDay() { return mStartOfDay; } + + friend bool operator==(const DateTime& dt1, const DateTime& dt2); + friend bool operator<(const DateTime& dt1, const DateTime& dt2); + + private: + static QTime mStartOfDay; + QDateTime mDateTime; + bool mDateOnly; + bool mTimeValid; // whether the time is potentially valid - applicable only if mDateOnly false +}; + +/** Returns true if the two values are equal. */ +bool operator==(const DateTime& dt1, const DateTime& dt2); +/** Returns true if the @p dt1 is earlier than @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +bool operator<(const DateTime& dt1, const DateTime& dt2); +/** Returns true if the two values are not equal. */ +inline bool operator!=(const DateTime& dt1, const DateTime& dt2) { return !operator==(dt1, dt2); } +/** Returns true if the @p dt1 is later than @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator>(const DateTime& dt1, const DateTime& dt2) { return operator<(dt2, dt1); } +/** Returns true if the @p dt1 is later than or equal to @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator>=(const DateTime& dt1, const DateTime& dt2) { return !operator<(dt1, dt2); } +/** Returns true if the @p dt1 is earlier than or equal to @p dt2. + * If the two values have the same date, and one value is date-only while the other is a date-time, the + * time used for the date-only value is the start-of-day time set in the KAlarm Preferences dialogue. + */ +inline bool operator<=(const DateTime& dt1, const DateTime& dt2) { return !operator<(dt2, dt1); } + +#endif // DATETIME_H diff --git a/kalarm/lib/label.cpp b/kalarm/lib/label.cpp new file mode 100644 index 000000000..c61ce76ad --- /dev/null +++ b/kalarm/lib/label.cpp @@ -0,0 +1,118 @@ +/* + * label.cpp - label with radiobutton buddy option + * Program: kalarm + * Copyright (C) 2004 by 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 "kalarm.h" +#include +#include "label.moc" + + +Label::Label(QWidget* parent, const char* name, WFlags f) + : QLabel(parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +Label::Label(const QString& text, QWidget* parent, const char* name, WFlags f) + : QLabel(text, parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +Label::Label(QWidget* buddy, const QString& text, QWidget* parent, const char* name, WFlags f) + : QLabel(buddy, text, parent, name, f), + mRadioButton(0), + mFocusWidget(0) +{ } + +/****************************************************************************** +* Set a buddy widget. +* If it (or its focus proxy) is a radio button, create a focus widget. +* When the accelerator key is pressed, the focus widget then receives focus. +* That event triggers the selection of the radio button. +*/ +void Label::setBuddy(QWidget* bud) +{ + if (mRadioButton) + disconnect(mRadioButton, SIGNAL(destroyed()), this, SLOT(buddyDead())); + QWidget* w = bud; + if (w) + { + while (w->focusProxy()) + w = w->focusProxy(); + if (!w->inherits("QRadioButton")) + w = 0; + } + if (!w) + { + // The buddy widget isn't a radio button + QLabel::setBuddy(bud); + delete mFocusWidget; + mFocusWidget = 0; + mRadioButton = 0; + } + else + { + // The buddy widget is a radio button, so set a different buddy + if (!mFocusWidget) + mFocusWidget = new LabelFocusWidget(this); + QLabel::setBuddy(mFocusWidget); + mRadioButton = (QRadioButton*)bud; + connect(mRadioButton, SIGNAL(destroyed()), this, SLOT(buddyDead())); + } +} + +void Label::buddyDead() +{ + delete mFocusWidget; + mFocusWidget = 0; + mRadioButton = 0; +} + +/****************************************************************************** +* Called when focus is transferred to the label's special focus widget. +* Transfer focus to the radio button and select it. +*/ +void Label::activated() +{ + if (mFocusWidget && mRadioButton) + { + mRadioButton->setFocus(); + mRadioButton->setChecked(true); + } +} + + +/*============================================================================= +* Class: LabelFocusWidget +=============================================================================*/ + +LabelFocusWidget::LabelFocusWidget(QWidget* parent, const char* name) + : QWidget(parent, name) +{ + setFocusPolicy(ClickFocus); + setFixedSize(QSize(1,1)); +} + +void LabelFocusWidget::focusInEvent(QFocusEvent*) +{ + Label* parent = (Label*)parentWidget(); + parent->activated(); + +} diff --git a/kalarm/lib/label.h b/kalarm/lib/label.h new file mode 100644 index 000000000..c65a7fcd6 --- /dev/null +++ b/kalarm/lib/label.h @@ -0,0 +1,96 @@ +/* + * label.h - label with radiobutton buddy option + * Program: kalarm + * Copyright © 2004-2006 by 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. + */ + +#ifndef LABEL_H +#define LABEL_H + +#include +class QRadioButton; +class LabelFocusWidget; + +/** + * @short A QLabel with option for a buddy radio button. + * + * The Label class provides a text display, with special behaviour when a radio + * button is set as a buddy. + * + * The Label object in effect acts as if it were part of the buddy radio button, + * in that when the label's accelerator key is pressed, the radio button receives + * focus and is switched on. When a non-radio button is specified as a buddy, the + * behaviour is the same as for QLabel. + * + * @author David Jarvie + */ +class Label : public QLabel +{ + Q_OBJECT + friend class LabelFocusWidget; + public: + /** Constructs an empty label. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + explicit Label(QWidget* parent, const char* name = 0, WFlags f = 0); + /** Constructs a label that displays @p text. + * @param text Text string to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + Label(const QString& text, QWidget* parent, const char* name = 0, WFlags f = 0); + /** Constructs a label, with a buddy widget, that displays @p text. + * @param buddy Buddy widget which receives the keyboard focus when the + * label's accelerator key is pressed. If @p buddy is a radio + * button, @p buddy is in addition selected when the + * accelerator key is pressed. + * @param text Text string to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + * @param f Flags. See QWidget constructor for details. + */ + Label(QWidget* buddy, const QString& text, QWidget* parent, const char* name = 0, WFlags f = 0); + /** Sets the label's buddy widget which receives the keyboard focus when the + * label's accelerator key is pressed. If @p buddy is a radio button, + * @p buddy is in addition selected when the accelerator key is pressed. + */ + virtual void setBuddy(QWidget* buddy); + protected: + virtual void drawContents(QPainter* p) { QLabel::drawContents(p); } + private slots: + void buddyDead(); + private: + void activated(); + QRadioButton* mRadioButton; // buddy widget if it's a radio button, else 0 + LabelFocusWidget* mFocusWidget; +}; + + +// Private class for use by Label +class LabelFocusWidget : public QWidget +{ + Q_OBJECT + public: + LabelFocusWidget(QWidget* parent, const char* name = 0); + protected: + virtual void focusInEvent(QFocusEvent*); +}; + +#endif // LABEL_H diff --git a/kalarm/lib/lineedit.cpp b/kalarm/lib/lineedit.cpp new file mode 100644 index 000000000..943d7b2d0 --- /dev/null +++ b/kalarm/lib/lineedit.cpp @@ -0,0 +1,200 @@ +/* + * lineedit.cpp - Line edit widget with extra drag and drop options + * Program: kalarm + * Copyright (C) 2003 - 2005 by 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 "kalarm.h" + +#include +#include + +#include +#include + +#include +#include +#include + +#include "lineedit.moc" + + +/*============================================================================= += Class LineEdit += Line edit which accepts drag and drop of text, URLs and/or email addresses. +* It has an option to prevent its contents being selected when it receives += focus. +=============================================================================*/ +LineEdit::LineEdit(Type type, QWidget* parent, const char* name) + : KLineEdit(parent, name), + mType(type), + mNoSelect(false), + mSetCursorAtEnd(false) +{ + init(); +} + +LineEdit::LineEdit(QWidget* parent, const char* name) + : KLineEdit(parent, name), + mType(Text), + mNoSelect(false), + mSetCursorAtEnd(false) +{ + init(); +} + +void LineEdit::init() +{ + if (mType == Url) + { + setCompletionMode(KGlobalSettings::CompletionShell); + KURLCompletion* comp = new KURLCompletion(KURLCompletion::FileCompletion); + comp->setReplaceHome(true); + setCompletionObject(comp); + setAutoDeleteCompletionObject(true); + } + else + setCompletionMode(KGlobalSettings::CompletionNone); +} + +/****************************************************************************** +* Called when the line edit receives focus. +* If 'noSelect' is true, prevent the contents being selected. +*/ +void LineEdit::focusInEvent(QFocusEvent* e) +{ + if (mNoSelect) + QFocusEvent::setReason(QFocusEvent::Other); + KLineEdit::focusInEvent(e); + if (mNoSelect) + { + QFocusEvent::resetReason(); + mNoSelect = false; + } +} + +void LineEdit::setText(const QString& text) +{ + KLineEdit::setText(text); + setCursorPosition(mSetCursorAtEnd ? text.length() : 0); +} + +void LineEdit::dragEnterEvent(QDragEnterEvent* e) +{ + if (KCal::ICalDrag::canDecode(e)) + e->accept(false); // don't accept "text/calendar" objects + e->accept(QTextDrag::canDecode(e) + || KURLDrag::canDecode(e) + || mType != Url && KPIM::MailListDrag::canDecode(e) + || mType == Emails && KVCardDrag::canDecode(e)); +} + +void LineEdit::dropEvent(QDropEvent* e) +{ + QString newText; + QStringList newEmails; + QString txt; + KPIM::MailList mailList; + KURL::List files; + KABC::Addressee::List addrList; + + if (mType != Url + && e->provides(KPIM::MailListDrag::format()) + && KPIM::MailListDrag::decode(e, mailList)) + { + // KMail message(s) - ignore all but the first + if (mailList.count()) + { + if (mType == Emails) + newText = mailList.first().from(); + else + setText(mailList.first().subject()); // replace any existing text + } + } + // This must come before KURLDrag + else if (mType == Emails + && KVCardDrag::canDecode(e) && KVCardDrag::decode(e, addrList)) + { + // KAddressBook entries + for (KABC::Addressee::List::Iterator it = addrList.begin(); it != addrList.end(); ++it) + { + QString em((*it).fullEmail()); + if (!em.isEmpty()) + newEmails.append(em); + } + } + else if (KURLDrag::decode(e, files) && files.count()) + { + // URL(s) + switch (mType) + { + case Url: + // URL entry field - ignore all but the first dropped URL + setText(files.first().prettyURL()); // replace any existing text + break; + case Emails: + { + // Email entry field - ignore all but mailto: URLs + QString mailto = QString::fromLatin1("mailto"); + for (KURL::List::Iterator it = files.begin(); it != files.end(); ++it) + { + if ((*it).protocol() == mailto) + newEmails.append((*it).path()); + } + break; + } + case Text: + newText = files.first().prettyURL(); + break; + } + } + else if (QTextDrag::decode(e, txt)) + { + // Plain text + if (mType == Emails) + { + // Remove newlines from a list of email addresses, and allow an eventual mailto: protocol + QString mailto = QString::fromLatin1("mailto:"); + newEmails = QStringList::split(QRegExp("[\r\n]+"), txt); + for (QStringList::Iterator it = newEmails.begin(); it != newEmails.end(); ++it) + { + if ((*it).startsWith(mailto)) + { + KURL url(*it); + *it = url.path(); + } + } + } + else + { + int newline = txt.find('\n'); + newText = (newline >= 0) ? txt.left(newline) : txt; + } + } + + if (newEmails.count()) + { + newText = newEmails.join(","); + int c = cursorPosition(); + if (c > 0) + newText.prepend(","); + if (c < static_cast(text().length())) + newText.append(","); + } + if (!newText.isEmpty()) + insert(newText); +} diff --git a/kalarm/lib/lineedit.h b/kalarm/lib/lineedit.h new file mode 100644 index 000000000..57f8378ee --- /dev/null +++ b/kalarm/lib/lineedit.h @@ -0,0 +1,94 @@ +/* + * lineedit.h - line edit widget with extra drag and drop options + * Program: kalarm + * Copyright (C) 2003 - 2005 by 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. + */ + +#ifndef LINEEDIT_H +#define LINEEDIT_H + +#include + + +/** + * @short Line edit widget with extra drag and drop options. + * + * The LineEdit class is a line edit widget which accepts specified types of drag and + * drop content. + * + * The widget will always accept drag and drop of text, except text/calendar mime type, + * and of URLs. It will accept additional mime types depending on its configuration: + * Text type accepts email address lists. + * Email type accepts email address lists and VCard data (e.g. from KAddressBook). + * + * The class also provides an option to prevent its contents being selected when the + * widget receives focus. + * + * @author David Jarvie + */ +class LineEdit : public KLineEdit +{ + Q_OBJECT + public: + /** Types of drag and drop content which will be accepted. + * @li Text - the line edit contains general text. It accepts text, a URL + * or an email from KMail (the subject line is used). If multiple + * URLs or emails are dropped, only the first is used; the + * rest are ignored. + * @li Url - the line edit contains a URL. It accepts text or a URL. If + * multiple URLs are dropped, only the first URL is used; the + * rest are ignored. + * @li Emails - the line edit contains email addresses. It accepts text, + * mailto: URLs, emails from KMail (the From address is used) + * or vcard data (e.g. from KAddressBook). If multiple emails + * are dropped, only the first is used; the rest are ignored. + * + */ + enum Type { Text, Url, Emails }; + /** Constructor. + * @param type The content type for the line edit. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit LineEdit(Type type, QWidget* parent = 0, const char* name = 0); + /** Constructs a line edit whose content type is Text. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit LineEdit(QWidget* parent = 0, const char* name = 0); + /** Prevents the line edit's contents being selected when the widget receives focus. */ + void setNoSelect() { mNoSelect = true; } + /** Sets whether the cursor should be set at the beginning or end of the text when + * setText() is called. + */ + void setCursorAtEnd(bool end = true) { mSetCursorAtEnd = end; } + public slots: + /** Sets the contents of the line edit to be @p str. */ + virtual void setText(const QString& str); + protected: + virtual void focusInEvent(QFocusEvent*); + virtual void dragEnterEvent(QDragEnterEvent*); + virtual void dropEvent(QDropEvent*); + private: + void init(); + + Type mType; + bool mNoSelect; + bool mSetCursorAtEnd; // setText() should position cursor at end +}; + +#endif // LINEEDIT_H diff --git a/kalarm/lib/messagebox.cpp b/kalarm/lib/messagebox.cpp new file mode 100644 index 000000000..514b45bc2 --- /dev/null +++ b/kalarm/lib/messagebox.cpp @@ -0,0 +1,178 @@ +/* + * messagebox.cpp - enhanced KMessageBox class + * Program: kalarm + * Copyright (C) 2004 by 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 "kalarm.h" +#include +#include "messagebox.h" + + +KConfig* MessageBox::mConfig = 0; +QMap MessageBox::mContinueDefaults; + + +/****************************************************************************** +* Set the default button for continue/cancel message boxes with the specified +* 'dontAskAgainName'. +*/ +void MessageBox::setContinueDefault(const QString& dontAskAgainName, ButtonCode defaultButton) +{ + mContinueDefaults[dontAskAgainName] = (defaultButton == Cancel ? Cancel : Continue); +} + +/****************************************************************************** +* Get the default button for continue/cancel message boxes with the specified +* 'dontAskAgainName'. +*/ +KMessageBox::ButtonCode MessageBox::getContinueDefault(const QString& dontAskAgainName) +{ + ButtonCode defaultButton = Continue; + if (!dontAskAgainName.isEmpty()) + { + QMap::ConstIterator it = mContinueDefaults.find(dontAskAgainName); + if (it != mContinueDefaults.end()) + defaultButton = it.data(); + } + return defaultButton; +} + +/****************************************************************************** +* Continue/cancel message box. +* If 'dontAskAgainName' is specified: +* 1) The message box will only be suppressed if the user chose Continue last time. +* 2) The default button is that last set with either setContinueDefault() or +* warningContinueCancel() for that 'dontAskAgainName' value. If neither method +* has set a default button, Continue is the default. +*/ +int MessageBox::warningContinueCancel(QWidget* parent, const QString& text, const QString& caption, + const KGuiItem& buttonContinue, const QString& dontAskAgainName) +{ + ButtonCode defaultButton = getContinueDefault(dontAskAgainName); + return warningContinueCancel(parent, defaultButton, text, caption, buttonContinue, dontAskAgainName); +} + +/****************************************************************************** +* Continue/cancel message box with the option as to which button is the default. +* If 'dontAskAgainName' is specified, the message box will only be suppressed +* if the user chose Continue last time. +*/ +int MessageBox::warningContinueCancel(QWidget* parent, ButtonCode defaultButton, const QString& text, + const QString& caption, const KGuiItem& buttonContinue, + const QString& dontAskAgainName) +{ + setContinueDefault(dontAskAgainName, defaultButton); + if (defaultButton != Cancel) + return KMessageBox::warningContinueCancel(parent, text, caption, buttonContinue, dontAskAgainName); + + // Cancel is the default button, so we have to use KMessageBox::warningYesNo() + if (!dontAskAgainName.isEmpty()) + { + ButtonCode b; + if (!shouldBeShownYesNo(dontAskAgainName, b) + && b != KMessageBox::Yes) + { + // Notification has been suppressed, but No (alias Cancel) is the default, + // so unsuppress notification. + saveDontShowAgain(dontAskAgainName, true, false); + } + } + return warningYesNo(parent, text, caption, buttonContinue, KStdGuiItem::cancel(), dontAskAgainName); +} + +/****************************************************************************** +* If there is no current setting for whether a non-yes/no message box should be +* shown, set it to 'defaultShow'. +* If a continue/cancel message box has Cancel as the default button, either +* setContinueDefault() or warningContinueCancel() must have been called +* previously to set this for this 'dontShowAgainName' value. +* Reply = true if 'defaultShow' was written. +*/ +bool MessageBox::setDefaultShouldBeShownContinue(const QString& dontShowAgainName, bool defaultShow) +{ + if (dontShowAgainName.isEmpty()) + return false; + // First check whether there is an existing setting + KConfig* config = mConfig ? mConfig : KGlobal::config(); + config->setGroup(QString::fromLatin1("Notification Messages")); + if (config->hasKey(dontShowAgainName)) + return false; + + // There is no current setting, so write one + saveDontShowAgainContinue(dontShowAgainName, !defaultShow); + return true; +} + +/****************************************************************************** +* Return whether a non-yes/no message box should be shown. +* If the message box has Cancel as the default button, either setContinueDefault() +* or warningContinueCancel() must have been called previously to set this for this +* 'dontShowAgainName' value. +*/ +bool MessageBox::shouldBeShownContinue(const QString& dontShowAgainName) +{ + if (getContinueDefault(dontShowAgainName) != Cancel) + return KMessageBox::shouldBeShownContinue(dontShowAgainName); + // Cancel is the default button, so we have to use a yes/no message box + ButtonCode b; + return shouldBeShownYesNo(dontShowAgainName, b); +} + + +/****************************************************************************** +* Save whether the yes/no message box should not be shown again. +* If 'dontShow' is true, the message box will be suppressed and it will return +* 'result'. +*/ +void MessageBox::saveDontShowAgainYesNo(const QString& dontShowAgainName, bool dontShow, ButtonCode result) +{ + saveDontShowAgain(dontShowAgainName, true, dontShow, (result == Yes ? "yes" : "no")); +} + +/****************************************************************************** +* Save whether a non-yes/no message box should not be shown again. +* If 'dontShow' is true, the message box will be suppressed and it will return +* Continue. +* If the message box has Cancel as the default button, either setContinueDefault() +* or warningContinueCancel() must have been called previously to set this for this +* 'dontShowAgainName' value. +*/ +void MessageBox::saveDontShowAgainContinue(const QString& dontShowAgainName, bool dontShow) +{ + if (getContinueDefault(dontShowAgainName) == Cancel) + saveDontShowAgainYesNo(dontShowAgainName, dontShow, Yes); + else + saveDontShowAgain(dontShowAgainName, false, dontShow); +} + +/****************************************************************************** +* Save whether the message box should not be shown again. +*/ +void MessageBox::saveDontShowAgain(const QString& dontShowAgainName, bool yesno, bool dontShow, const char* yesnoResult) +{ + if (dontShowAgainName.isEmpty()) + return; + KConfig* config = mConfig ? mConfig : KGlobal::config(); + config->setGroup(QString::fromLatin1("Notification Messages")); + bool global = (dontShowAgainName[0] == ':'); + if (yesno) + config->writeEntry(dontShowAgainName, QString::fromLatin1(dontShow ? yesnoResult : ""), true, global); + else + config->writeEntry(dontShowAgainName, !dontShow, true, global); + config->sync(); +} diff --git a/kalarm/lib/messagebox.h b/kalarm/lib/messagebox.h new file mode 100644 index 000000000..32e0d7325 --- /dev/null +++ b/kalarm/lib/messagebox.h @@ -0,0 +1,125 @@ +/* + * messagebox.h - enhanced KMessageBox class + * Program: kalarm + * Copyright (C) 2004 by 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. + */ + +#ifndef MESSAGEBOX_H +#define MESSAGEBOX_H + +#include +#include + + +/** + * @short Enhanced KMessageBox. + * + * The MessageBox class provides an extension to KMessageBox, including the option for + * Continue/Cancel message boxes to have a default button of Cancel. + * + * @author David Jarvie + */ +class MessageBox : public KMessageBox +{ + public: + /** MessageBox types. + * @li CONT_CANCEL_DEF_CONT - Continue/Cancel, with Continue as the default button. + * @li CONT_CANCEL_DEF_CANCEL - Continue/Cancel, with Cancel as the default button. + * @li YES_NO_DEF_NO - Yes/No, with No as the default button. + */ + enum AskType { // MessageBox types + CONT_CANCEL_DEF_CONT, // Continue/Cancel, with default = Continue + CONT_CANCEL_DEF_CANCEL, // Continue/Cancel, with default = Cancel + YES_NO_DEF_NO // Yes/No, with default = No + }; + /** Gets the default button for the Continue/Cancel message box with the specified + * "don't ask again" name. + * @param dontAskAgainName The identifier controlling whether the message box is suppressed. + */ + static ButtonCode getContinueDefault(const QString& dontAskAgainName); + /** Sets the default button for the Continue/Cancel message box with the specified + * "don't ask again" name. + * @param dontAskAgainName The identifier controlling whether the message box is suppressed. + * @param defaultButton The default button for the message box. Valid values are Continue or Cancel. + */ + static void setContinueDefault(const QString& dontAskAgainName, ButtonCode defaultButton); + /** Displays a Continue/Cancel message box with the option as to which button is the default. + * @param parent Parent widget + * @param defaultButton The default button for the message box. Valid values are Continue or Cancel. + * @param text Message string + * @param caption Caption (window title) of the message box + * @param buttonContinue The text for the first button (default = i18n("Continue")) + * @param dontAskAgainName If specified, the message box will only be suppressed + * if the user chose Continue last time. + */ + static int warningContinueCancel(QWidget* parent, ButtonCode defaultButton, const QString& text, + const QString& caption = QString::null, + const KGuiItem& buttonContinue = KStdGuiItem::cont(), + const QString& dontAskAgainName = QString::null); + /** Displays a Continue/Cancel message box. + * @param parent Parent widget + * @param text Message string + * @param caption Caption (window title) of the message box + * @param buttonContinue The text for the first button (default = i18n("Continue")) + * @param dontAskAgainName If specified, (1) The message box will only be suppressed + * if the user chose Continue last time, and (2) The default button is that last set + * with either setContinueDefault() or warningContinueCancel() for the same + * @p dontAskAgainName value. If neither method has been used to set a default button, + * Continue is the default. + */ + static int warningContinueCancel(QWidget* parent, const QString& text, const QString& caption = QString::null, + const KGuiItem& buttonContinue = KStdGuiItem::cont(), + const QString& dontAskAgainName = QString::null); + /** If there is no current setting for whether a non-Yes/No message box should be + * shown, sets it to @p defaultShow. + * If a Continue/Cancel message box has Cancel as the default button, either + * setContinueDefault() or warningContinueCancel() must have been called + * previously to set this for the specified @p dontShowAgainName value. + * @return true if @p defaultShow was written. + */ + static bool setDefaultShouldBeShownContinue(const QString& dontShowAgainName, bool defaultShow); + /** Returns whether a non-Yes/No message box should be shown. + * If the message box has Cancel as the default button, either setContinueDefault() + * or warningContinueCancel() must have been called previously to set this for the + * specified @p dontShowAgainName value. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + */ + static bool shouldBeShownContinue(const QString& dontShowAgainName); + /** Stores whether the Yes/No message box should or should not be shown again. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + * @param dontShow If true, the message box will be suppressed and will return @p result. + * @param result The button code to return if the message box is suppressed. + */ + static void saveDontShowAgainYesNo(const QString& dontShowAgainName, bool dontShow = true, ButtonCode result = No); + /** Stores whether a non-Yes/No message box should or should not be shown again. + * If the message box has Cancel as the default button, either setContinueDefault() + * or warningContinueCancel() must have been called previously to set this for the + * specified @p dontShowAgainName value. + * @param dontShowAgainName The identifier controlling whether the message box is suppressed. + * @param dontShow If true, the message box will be suppressed and will return Continue. + */ + static void saveDontShowAgainContinue(const QString& dontShowAgainName, bool dontShow = true); + /** Sets the KConfig object to be used by the MessageBox class. */ + static void setDontShowAskAgainConfig(KConfig* cfg) { mConfig = cfg; } + + private: + static void saveDontShowAgain(const QString& dontShowAgainName, bool yesno, bool dontShow, const char* yesnoResult = 0); + static KConfig* mConfig; + static QMap mContinueDefaults; +}; + +#endif diff --git a/kalarm/lib/pushbutton.cpp b/kalarm/lib/pushbutton.cpp new file mode 100644 index 000000000..8b279082c --- /dev/null +++ b/kalarm/lib/pushbutton.cpp @@ -0,0 +1,102 @@ +/* + * pushbutton.cpp - push button with read-only option + * Program: kalarm + * Copyright (c) 2002 by 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 "pushbutton.moc" + + +PushButton::PushButton(QWidget* parent, const char* name) + : QPushButton(parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +PushButton::PushButton(const QString& text, QWidget* parent, const char* name) + : QPushButton(text, parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +PushButton::PushButton(const QIconSet& icon, const QString& text, QWidget* parent, const char* name) + : QPushButton(icon, text, parent, name), + mFocusPolicy(focusPolicy()), + mReadOnly(false) +{ } + +void PushButton::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +void PushButton::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QPushButton::mousePressEvent(e); +} + +void PushButton::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QPushButton::mouseReleaseEvent(e); +} + +void PushButton::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QPushButton::mouseMoveEvent(e); +} + +void PushButton::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + break; + default: + return; + } + QPushButton::keyPressEvent(e); +} + +void PushButton::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QPushButton::keyReleaseEvent(e); +} diff --git a/kalarm/lib/pushbutton.h b/kalarm/lib/pushbutton.h new file mode 100644 index 000000000..6ef0f3a2a --- /dev/null +++ b/kalarm/lib/pushbutton.h @@ -0,0 +1,77 @@ +/* + * pushbutton.h - push button with read-only option + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by 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. + */ + +#ifndef PUSHBUTTON_H +#define PUSHBUTTON_H + +#include + + +/** + * @short A QPushButton with read-only option. + * + * The PushButton class is a QPushButton with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class PushButton : public QPushButton +{ + Q_OBJECT + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit PushButton(QWidget* parent, const char* name = 0); + /** Constructor for a push button which displays a text. + * @param text The text to show on the button. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + PushButton(const QString& text, QWidget* parent, const char* name = 0); + /** Constructor for a push button which displays an icon and a text. + * @param icon The icon to show on the button. + * @param text The text to show on the button. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + PushButton(const QIconSet& icon, const QString& text, QWidget* parent, const char* name = 0); + /** Sets whether the push button is read-only for the user. + * @param readOnly True to set the widget read-only, false to enable its action. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget is read only. */ + virtual bool isReadOnly() const { return mReadOnly; } + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QPushButton + bool mReadOnly; // value cannot be changed +}; + +#endif // PUSHBUTTON_H diff --git a/kalarm/lib/radiobutton.cpp b/kalarm/lib/radiobutton.cpp new file mode 100644 index 000000000..d66911eeb --- /dev/null +++ b/kalarm/lib/radiobutton.cpp @@ -0,0 +1,134 @@ +/* + * radiobutton.cpp - radio button with read-only option + * Program: kalarm + * Copyright (c) 2002, 2003 by 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 "radiobutton.moc" + + +RadioButton::RadioButton(QWidget* parent, const char* name) + : QRadioButton(parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +RadioButton::RadioButton(const QString& text, QWidget* parent, const char* name) + : QRadioButton(text, parent, name), + mFocusPolicy(focusPolicy()), + mFocusWidget(0), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the button can be toggled by the +* application, but not by the user. +*/ +void RadioButton::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + setFocusPolicy(ro ? QWidget::NoFocus : mFocusPolicy); + if (ro) + clearFocus(); + } +} + +/****************************************************************************** +* Specify a widget to receive focus when the button is clicked on. +*/ +void RadioButton::setFocusWidget(QWidget* w, bool enable) +{ + mFocusWidget = w; + mFocusWidgetEnable = enable; + if (w) + connect(this, SIGNAL(clicked()), SLOT(slotClicked())); + else + disconnect(this, SIGNAL(clicked()), this, SLOT(slotClicked())); +} + +/****************************************************************************** +* Called when the button is clicked. +* If it is now checked, focus is transferred to any specified focus widget. +*/ +void RadioButton::slotClicked() +{ + if (mFocusWidget && isChecked()) + { + if (mFocusWidgetEnable) + mFocusWidget->setEnabled(true); + mFocusWidget->setFocus(); + } +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the button state are discarded. +*/ +void RadioButton::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QRadioButton::mousePressEvent(e); +} + +void RadioButton::mouseReleaseEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QRadioButton::mouseReleaseEvent(e); +} + +void RadioButton::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QRadioButton::mouseMoveEvent(e); +} + +void RadioButton::keyPressEvent(QKeyEvent* e) +{ + if (mReadOnly) + switch (e->key()) + { + case Qt::Key_Up: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Down: + // Process keys which shift the focus + case Qt::Key_Escape: + break; + default: + return; + } + QRadioButton::keyPressEvent(e); +} + +void RadioButton::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QRadioButton::keyReleaseEvent(e); +} diff --git a/kalarm/lib/radiobutton.h b/kalarm/lib/radiobutton.h new file mode 100644 index 000000000..96bca04dd --- /dev/null +++ b/kalarm/lib/radiobutton.h @@ -0,0 +1,88 @@ +/* + * radiobutton.h - radio button with focus widget and read-only options + * Program: kalarm + * Copyright © 2002,2003,2005,2006 by 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. + */ + +#ifndef RADIOBUTTON_H +#define RADIOBUTTON_H + +#include + + +/** + * @short A QRadioButton with focus widget and read-only options. + * + * The RadioButton class is a QRadioButton with the ability to transfer focus to + * another widget when checked, and with a read-only option. + * + * Another widget may be specified as the focus widget for the radio button. Whenever + * the user clicks on the radio button so as to set its state to checked, focus is + * automatically transferred to the focus widget. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class RadioButton : public QRadioButton +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit RadioButton(QWidget* parent, const char* name = 0); + /** Constructor. + * @param text Text to display. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + RadioButton(const QString& text, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the radio button is read-only for the user. If read-only, + * its state cannot be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns the widget which receives focus when the button is clicked. */ + QWidget* focusWidget() const { return mFocusWidget; } + /** Specifies a widget to receive focus when the button is clicked. + * @param widget Widget to receive focus. + * @param enable If true, @p widget will be enabled before receiving focus. If + * false, the enabled state of @p widget will be left unchanged when + * the radio button is clicked. + */ + void setFocusWidget(QWidget* widget, bool enable = true); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + protected slots: + void slotClicked(); + private: + QWidget::FocusPolicy mFocusPolicy; // default focus policy for the QRadioButton + QWidget* mFocusWidget; // widget to receive focus when button is clicked on + bool mFocusWidgetEnable; // enable focus widget before setting focus + bool mReadOnly; // value cannot be changed +}; + +#endif // RADIOBUTTON_H diff --git a/kalarm/lib/shellprocess.cpp b/kalarm/lib/shellprocess.cpp new file mode 100644 index 000000000..1e37d2e74 --- /dev/null +++ b/kalarm/lib/shellprocess.cpp @@ -0,0 +1,208 @@ +/* + * shellprocess.cpp - execute a shell process + * Program: kalarm + * Copyright (c) 2004, 2005 by 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. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include "shellprocess.moc" + + +QCString ShellProcess::mShellName; +QCString ShellProcess::mShellPath; +bool ShellProcess::mInitialised = false; +bool ShellProcess::mAuthorised = false; + + +ShellProcess::ShellProcess(const QString& command) + : KShellProcess(shellName()), + mCommand(command), + mStatus(INACTIVE), + mStdinExit(false) +{ +} + +/****************************************************************************** +* Execute a command. +*/ +bool ShellProcess::start(Communication comm) +{ + if (!authorised()) + { + mStatus = UNAUTHORISED; + return false; + } + KShellProcess::operator<<(mCommand); + connect(this, SIGNAL(wroteStdin(KProcess*)), SLOT(writtenStdin(KProcess*))); + connect(this, SIGNAL(processExited(KProcess*)), SLOT(slotExited(KProcess*))); + if (!KShellProcess::start(KProcess::NotifyOnExit, comm)) + { + mStatus = START_FAIL; + return false; + } + mStatus = RUNNING; + return true; +} + +/****************************************************************************** +* Called when a shell process execution completes. +* Interprets the exit status according to which shell was called, and emits +* a shellExited() signal. +*/ +void ShellProcess::slotExited(KProcess* proc) +{ + kdDebug(5950) << "ShellProcess::slotExited()\n"; + mStdinQueue.clear(); + mStatus = SUCCESS; + if (!proc->normalExit()) + { + kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": died/killed\n"; + mStatus = DIED; + } + else + { + // Some shells report if the command couldn't be found, or is not executable + int status = proc->exitStatus(); + if (mShellName == "bash" && (status == 126 || status == 127) + || mShellName == "ksh" && status == 127) + { + kdWarning(5950) << "ShellProcess::slotExited(" << mCommand << ") " << mShellName << ": not found or not executable\n"; + mStatus = NOT_FOUND; + } + } + emit shellExited(this); +} + +/****************************************************************************** +* Write a string to STDIN. +*/ +void ShellProcess::writeStdin(const char* buffer, int bufflen) +{ + QCString scopy(buffer, bufflen+1); // construct a deep copy + bool write = mStdinQueue.isEmpty(); + mStdinQueue.append(scopy); + if (write) + KProcess::writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); +} + +/****************************************************************************** +* Called when output to STDIN completes. +* Send the next queued output, if any. +* Note that buffers written to STDIN must not be freed until the writtenStdin() +* signal has been processed. +*/ +void ShellProcess::writtenStdin(KProcess* proc) +{ + mStdinQueue.pop_front(); // free the buffer which has now been written + if (!mStdinQueue.isEmpty()) + proc->writeStdin(mStdinQueue.first(), mStdinQueue.first().length()); + else if (mStdinExit) + kill(); +} + +/****************************************************************************** +* Tell the process to exit once all STDIN strings have been written. +*/ +void ShellProcess::stdinExit() +{ + if (mStdinQueue.isEmpty()) + kill(); + else + mStdinExit = true; +} + +/****************************************************************************** +* Return the error message corresponding to the command exit status. +* Reply = null string if not yet exited, or if command successful. +*/ +QString ShellProcess::errorMessage() const +{ + switch (mStatus) + { + case UNAUTHORISED: + return i18n("Failed to execute command (shell access not authorized):"); + case START_FAIL: + case NOT_FOUND: + return i18n("Failed to execute command:"); + case DIED: + return i18n("Command execution error:"); + case INACTIVE: + case RUNNING: + case SUCCESS: + default: + return QString::null; + } +} + +/****************************************************************************** +* Determine which shell to use. +* This is a duplication of what KShellProcess does, but we need to know +* which shell is used in order to decide what its exit code means. +*/ +const QCString& ShellProcess::shellPath() +{ + if (mShellPath.isEmpty()) + { + // Get the path to the shell + mShellPath = "/bin/sh"; + QCString envshell = QCString(getenv("SHELL")).stripWhiteSpace(); + if (!envshell.isEmpty()) + { + struct stat fileinfo; + if (stat(envshell.data(), &fileinfo) != -1 // ensure file exists + && !S_ISDIR(fileinfo.st_mode) // and it's not a directory + && !S_ISCHR(fileinfo.st_mode) // and it's not a character device + && !S_ISBLK(fileinfo.st_mode) // and it's not a block device +#ifdef S_ISSOCK + && !S_ISSOCK(fileinfo.st_mode) // and it's not a socket +#endif + && !S_ISFIFO(fileinfo.st_mode) // and it's not a fifo + && !access(envshell.data(), X_OK)) // and it's executable + mShellPath = envshell; + } + + // Get the shell filename with the path stripped off + int i = mShellPath.findRev('/'); + if (i >= 0) + mShellName = mShellPath.mid(i + 1); + else + mShellName = mShellPath; + } + return mShellPath; +} + +/****************************************************************************** +* Check whether shell commands are allowed at all. +*/ +bool ShellProcess::authorised() +{ + if (!mInitialised) + { + mAuthorised = kapp->authorize("shell_access"); + mInitialised = true; + } + return mAuthorised; +} diff --git a/kalarm/lib/shellprocess.h b/kalarm/lib/shellprocess.h new file mode 100644 index 000000000..06a262a8d --- /dev/null +++ b/kalarm/lib/shellprocess.h @@ -0,0 +1,138 @@ +/* + * shellprocess.h - execute a process through the shell + * Program: kalarm + * Copyright © 2004-2006 by 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. + */ + +#ifndef SHELLPROCESS_H +#define SHELLPROCESS_H + +/** @file shellprocess.h - execute a process through the shell */ + +#include + + +/** + * @short Enhanced KShellProcess. + * + * The ShellProcess class runs a shell command and interprets the shell exit status + * as far as possible. It blocks execution if shell access is prohibited. It buffers + * data written to the process's stdin. + * + * Before executing any command, ShellProcess checks whether shell commands are + * allowed at all. If not (e.g. if the user is running in kiosk mode), it blocks + * execution. + * + * Derived from KShellProcess, this class additionally tries to interpret the shell + * exit status. Different shells use different exit codes. Currently, if bash or ksh + * report that the command could not be found or could not be executed, the NOT_FOUND + * status is returned. + * + * Writes to the process's stdin are buffered, so that unlike with KShellProcess, there + * is no need to wait for the write to complete before writing again. + * + * @author David Jarvie + */ +class ShellProcess : public KShellProcess +{ + Q_OBJECT + public: + /** Current status of the shell process. + * @li INACTIVE - start() has not yet been called to run the command. + * @li RUNNING - the command is currently running. + * @li SUCCESS - the command appears to have exited successfully. + * @li UNAUTHORISED - shell commands are not authorised for this user. + * @li DIED - the command didn't exit cleanly, i.e. was killed or died. + * @li NOT_FOUND - the command was either not found or not executable. + * @li START_FAIL - the command couldn't be started for other reasons. + */ + enum Status { + INACTIVE, // start() has not yet been called to run the command + RUNNING, // command is currently running + SUCCESS, // command appears to have exited successfully + UNAUTHORISED, // shell commands are not authorised for this user + DIED, // command didn't exit cleanly, i.e. was killed or died + NOT_FOUND, // command either not found or not executable + START_FAIL // command couldn't be started for other reasons + }; + /** Constructor. + * @param command The command line to be run when start() is called. + */ + explicit ShellProcess(const QString& command); + /** Executes the configured command. + * @param comm Which communication links should be established to the child process + * (stdin/stdout/stderr). + */ + bool start(Communication comm = NoCommunication); + /** Returns the current status of the shell process. */ + Status status() const { return mStatus; } + /** Returns whether the command was run successfully. + * @return True if the command has been run and appears to have exited successfully. + */ + bool normalExit() const { return mStatus == SUCCESS; } + /** Returns the command configured to be run. */ + const QString& command() const { return mCommand; } + /** Returns the error message corresponding to the command exit status. + * @return Error message if an error occurred. Null string if the command has not yet + * exited, or if the command ran successfully. + */ + QString errorMessage() const; + /** Writes a string to the process's STDIN. */ + void writeStdin(const char* buffer, int bufflen); + /** Tell the process to exit once any outstanding STDIN strings have been written. */ + void stdinExit(); + /** Returns whether the user is authorised to run shell commands. Shell commands may + * be prohibited in kiosk mode, for example. + */ + static bool authorised(); + /** Determines which shell to use. + * @return file name of shell, excluding path. + */ + static const QCString& shellName() { shellPath(); return mShellName; } + /** Determines which shell to use. + * @return path name of shell. + */ + static const QCString& shellPath(); + + signals: + /** Signal emitted when the shell process execution completes. It is not emitted + * if start() did not attempt to start the command execution, e.g. in kiosk mode. + */ + void shellExited(ShellProcess*); + + private slots: + void writtenStdin(KProcess*); + void slotExited(KProcess*); + + private: + // Prohibit the following inherited methods + ShellProcess& operator<<(const QString&); + ShellProcess& operator<<(const QCString&); + ShellProcess& operator<<(const QStringList&); + ShellProcess& operator<<(const char*); + + static QCString mShellName; // name of shell to be used + static QCString mShellPath; // path of shell to be used + static bool mInitialised; // true once static data has been initialised + static bool mAuthorised; // true if shell commands are authorised + QString mCommand; // copy of command to be executed + QValueList mStdinQueue; // queued strings to send to STDIN + Status mStatus; // current execution status + bool mStdinExit; // exit once STDIN queue has been written +}; + +#endif // SHELLPROCESS_H diff --git a/kalarm/lib/slider.cpp b/kalarm/lib/slider.cpp new file mode 100644 index 000000000..1a0999645 --- /dev/null +++ b/kalarm/lib/slider.cpp @@ -0,0 +1,85 @@ +/* + * slider.cpp - slider control with read-only option + * Program: kalarm + * Copyright (c) 2004 by 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 "slider.moc" + + +Slider::Slider(QWidget* parent, const char* name) + : QSlider(parent, name), + mReadOnly(false) +{ } + +Slider::Slider(Orientation o, QWidget* parent, const char* name) + : QSlider(o, parent, name), + mReadOnly(false) +{ } + +Slider::Slider(int minval, int maxval, int pageStep, int value, Orientation o, QWidget* parent, const char* name) + : QSlider(minval, maxval, pageStep, value, o, parent, name), + mReadOnly(false) +{ } + +/****************************************************************************** +* Set the read-only status. If read-only, the slider can be moved by the +* application, but not by the user. +*/ +void Slider::setReadOnly(bool ro) +{ + mReadOnly = ro; +} + +/****************************************************************************** +* Event handlers to intercept events if in read-only mode. +* Any events which could change the slider value are discarded. +*/ +void Slider::mousePressEvent(QMouseEvent* e) +{ + if (mReadOnly) + { + // Swallow up the event if it's the left button + if (e->button() == Qt::LeftButton) + return; + } + QSlider::mousePressEvent(e); +} + +void Slider::mouseReleaseEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QSlider::mouseReleaseEvent(e); +} + +void Slider::mouseMoveEvent(QMouseEvent* e) +{ + if (!mReadOnly) + QSlider::mouseMoveEvent(e); +} + +void Slider::keyPressEvent(QKeyEvent* e) +{ + if (!mReadOnly || e->key() == Qt::Key_Escape) + QSlider::keyPressEvent(e); +} + +void Slider::keyReleaseEvent(QKeyEvent* e) +{ + if (!mReadOnly) + QSlider::keyReleaseEvent(e); +} diff --git a/kalarm/lib/slider.h b/kalarm/lib/slider.h new file mode 100644 index 000000000..17635e68a --- /dev/null +++ b/kalarm/lib/slider.h @@ -0,0 +1,80 @@ +/* + * slider.h - slider control with read-only option + * Program: kalarm + * Copyright © 2004,2006 by 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. + */ + +#ifndef SLIDER_H +#define SLIDER_H + +#include + + +/** + * @short A QSlider with read-only option. + * + * The Slider class is a QSlider with a read-only option. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class Slider : public QSlider +{ + Q_OBJECT + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit Slider(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param orient The orientation of the slider, either Qt::Horizonal or Qt::Vertical. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit Slider(Orientation orient, QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the slider can have. + * @param maxValue The maximum value which the slider can have. + * @param pageStep The page step increment. + * @param value The initial value for the slider. + * @param orient The orientation of the slider, either Qt::Horizonal or Qt::Vertical. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + Slider(int minValue, int maxValue, int pageStep, int value, Orientation orient, + QWidget* parent = 0, const char* name = 0); + /** Returns true if the slider is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the slider is read-only for the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + protected: + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseReleaseEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void keyPressEvent(QKeyEvent*); + virtual void keyReleaseEvent(QKeyEvent*); + private: + bool mReadOnly; // value cannot be changed by the user +}; + +#endif // SLIDER_H diff --git a/kalarm/lib/spinbox.cpp b/kalarm/lib/spinbox.cpp new file mode 100644 index 000000000..bb9ef2441 --- /dev/null +++ b/kalarm/lib/spinbox.cpp @@ -0,0 +1,476 @@ +/* + * spinbox.cpp - spin box with read-only option and shift-click step value + * Program: kalarm + * Copyright © 2002,2004,2008 by 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 +#include +#include +#include "spinbox.moc" + + +SpinBox::SpinBox(QWidget* parent, const char* name) + : QSpinBox(0, 99999, 1, parent, name), + mMinValue(QSpinBox::minValue()), + mMaxValue(QSpinBox::maxValue()) +{ + init(); +} + +SpinBox::SpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name) + : QSpinBox(minValue, maxValue, step, parent, name), + mMinValue(minValue), + mMaxValue(maxValue) +{ + init(); +} + +void SpinBox::init() +{ + int step = QSpinBox::lineStep(); + mLineStep = step; + mLineShiftStep = step; + mCurrentButton = NO_BUTTON; + mShiftMouse = false; + mShiftMinBound = false; + mShiftMaxBound = false; + mSelectOnStep = true; + mReadOnly = false; + mSuppressSignals = false; + mEdited = false; + + // Find the spin widgets which are part of the spin boxes, in order to + // handle their shift-button presses. + QObjectList* spinwidgets = queryList("QSpinWidget", 0, false, true); + QSpinWidget* spin = (QSpinWidget*)spinwidgets->getFirst(); + if (spin) + spin->installEventFilter(this); // handle shift-button presses + delete spinwidgets; + editor()->installEventFilter(this); // handle shift-up/down arrow presses + +#if KDE_IS_VERSION(3,1,90) + // Detect when the text field is edited + connect(editor(), SIGNAL(textChanged(const QString&)), SLOT(textEdited())); +#endif +} + +void SpinBox::setReadOnly(bool ro) +{ + if ((int)ro != (int)mReadOnly) + { + mReadOnly = ro; + editor()->setReadOnly(ro); + if (ro) + setShiftStepping(false, mCurrentButton); + } +} + +int SpinBox::bound(int val) const +{ + return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val; +} + +void SpinBox::setMinValue(int val) +{ + mMinValue = val; + QSpinBox::setMinValue(val); + mShiftMinBound = false; +} + +void SpinBox::setMaxValue(int val) +{ + mMaxValue = val; + QSpinBox::setMaxValue(val); + mShiftMaxBound = false; +} + +void SpinBox::setLineStep(int step) +{ + mLineStep = step; + if (!mShiftMouse) + QSpinBox::setLineStep(step); +} + +void SpinBox::setLineShiftStep(int step) +{ + mLineShiftStep = step; + if (mShiftMouse) + QSpinBox::setLineStep(step); +} + +void SpinBox::stepUp() +{ + int step = QSpinBox::lineStep(); + addValue(step); + emit stepped(step); +} + +void SpinBox::stepDown() +{ + int step = -QSpinBox::lineStep(); + addValue(step); + emit stepped(step); +} + +/****************************************************************************** +* Adds a positive or negative increment to the current value, wrapping as appropriate. +* If 'current' is true, any temporary 'shift' values for the range are used instead +* of the real maximum and minimum values. +*/ +void SpinBox::addValue(int change, bool current) +{ + int newval = value() + change; + int maxval = current ? QSpinBox::maxValue() : mMaxValue; + int minval = current ? QSpinBox::minValue() : mMinValue; + if (wrapping()) + { + int range = maxval - minval + 1; + if (newval > maxval) + newval = minval + (newval - maxval - 1) % range; + else if (newval < minval) + newval = maxval - (minval - 1 - newval) % range; + } + else + { + if (newval > maxval) + newval = maxval; + else if (newval < minval) + newval = minval; + } + setValue(newval); +} + +void SpinBox::valueChange() +{ + if (!mSuppressSignals) + { + int val = value(); + if (mShiftMinBound && val >= mMinValue) + { + // Reinstate the minimum bound now that the value has returned to the normal range. + QSpinBox::setMinValue(mMinValue); + mShiftMinBound = false; + } + if (mShiftMaxBound && val <= mMaxValue) + { + // Reinstate the maximum bound now that the value has returned to the normal range. + QSpinBox::setMaxValue(mMaxValue); + mShiftMaxBound = false; + } + + bool focus = !mSelectOnStep && hasFocus(); + if (focus) + clearFocus(); // prevent selection of the spin box text + QSpinBox::valueChange(); + if (focus) + setFocus(); + } +} + +/****************************************************************************** +* Called whenever the line edit text is changed. +*/ +void SpinBox::textEdited() +{ + mEdited = true; +} + +void SpinBox::updateDisplay() +{ + mEdited = false; + QSpinBox::updateDisplay(); +} + +/****************************************************************************** +* Receives events destined for the spin widget or for the edit field. +*/ +bool SpinBox::eventFilter(QObject* obj, QEvent* e) +{ + if (obj == editor()) + { + int step = 0; + bool shift = false; + switch (e->type()) + { + case QEvent::KeyPress: + { + // Up and down arrow keys step the value + QKeyEvent* ke = (QKeyEvent*)e; + int key = ke->key(); + if (key == Qt::Key_Up) + step = 1; + else if (key == Qt::Key_Down) + step = -1; + shift = ((ke->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton); + break; + } + case QEvent::Wheel: + { + QWheelEvent* we = (QWheelEvent*)e; + step = (we->delta() > 0) ? 1 : -1; + shift = ((we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton); + break; + } +#if KDE_IS_VERSION(3,1,90) + case QEvent::Leave: + if (mEdited) + interpretText(); + break; +#endif + default: + break; + } + if (step) + { + if (mReadOnly) + return true; // discard up/down arrow keys or wheel + if (shift) + { + // Shift stepping + int val = value(); + if (step > 0) + step = mLineShiftStep - val % mLineShiftStep; + else + step = - ((val + mLineShiftStep - 1) % mLineShiftStep + 1); + } + else + step = (step > 0) ? mLineStep : -mLineStep; + addValue(step, false); + return true; + } + } + else + { + int etype = e->type(); // avoid switch compile warnings + switch (etype) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonDblClick: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->button() == Qt::LeftButton) + { + // It's a left button press. Set normal or shift stepping as appropriate. + if (mReadOnly) + return true; // discard the event + mCurrentButton = whichButton(me->pos()); + if (mCurrentButton == NO_BUTTON) + return true; + bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + return false; // forward event to the destination widget + } + break; + } + case QEvent::MouseButtonRelease: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->button() == Qt::LeftButton && mShiftMouse) + { + setShiftStepping(false, mCurrentButton); // cancel shift stepping + return false; // forward event to the destination widget + } + break; + } + case QEvent::MouseMove: + { + QMouseEvent* me = (QMouseEvent*)e; + if (me->state() & Qt::LeftButton) + { + // The left button is down. Track which spin button it's in. + if (mReadOnly) + return true; // discard the event + int newButton = whichButton(me->pos()); + if (newButton != mCurrentButton) + { + // The mouse has moved to a new spin button. + // Set normal or shift stepping as appropriate. + mCurrentButton = newButton; + bool shift = (me->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + } + return false; // forward event to the destination widget + } + break; + } + case QEvent::Wheel: + { + QWheelEvent* we = (QWheelEvent*)e; + bool shift = (we->state() & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if (setShiftStepping(shift, (we->delta() > 0 ? UP : DOWN))) + return true; // hide the event from the spin widget + return false; // forward event to the destination widget + } + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::AccelOverride: // this is needed to receive Shift presses! + { + QKeyEvent* ke = (QKeyEvent*)e; + int key = ke->key(); + int state = ke->state(); + if ((state & Qt::LeftButton) + && (key == Qt::Key_Shift || key == Qt::Key_Alt)) + { + // The left mouse button is down, and the Shift or Alt key has changed + if (mReadOnly) + return true; // discard the event + state ^= (key == Qt::Key_Shift) ? Qt::ShiftButton : Qt::AltButton; // new state + bool shift = (state & (Qt::ShiftButton | Qt::AltButton)) == Qt::ShiftButton; + if ((!shift && mShiftMouse) || (shift && !mShiftMouse)) + { + // The effective shift state has changed. + // Set normal or shift stepping as appropriate. + if (setShiftStepping(shift, mCurrentButton)) + return true; // hide the event from the spin widget + } + } + break; + } + } + } + return QSpinBox::eventFilter(obj, e); +} + +/****************************************************************************** +* Set spin widget stepping to the normal or shift increment. +*/ +bool SpinBox::setShiftStepping(bool shift, int currentButton) +{ + if (currentButton == NO_BUTTON) + shift = false; + if (shift && !mShiftMouse) + { + /* The value is to be stepped to a multiple of the shift increment. + * Adjust the value so that after the spin widget steps it, it will be correct. + * Then, if the mouse button is held down, the spin widget will continue to + * step by the shift amount. + */ + int val = value(); + int step = (currentButton == UP) ? mLineShiftStep : (currentButton == DOWN) ? -mLineShiftStep : 0; + int adjust = shiftStepAdjustment(val, step); + mShiftMouse = true; + if (adjust) + { + /* The value is to be stepped by other than the shift increment, + * presumably because it is being set to a multiple of the shift + * increment. Achieve this by making the adjustment here, and then + * allowing the normal step processing to complete the job by + * adding/subtracting the normal shift increment. + */ + if (!wrapping()) + { + // Prevent the step from going past the spinbox's range, or + // to the minimum value if that has a special text unless it is + // already at the minimum value + 1. + int newval = val + adjust + step; + int svt = specialValueText().isEmpty() ? 0 : 1; + int minval = mMinValue + svt; + if (newval <= minval || newval >= mMaxValue) + { + // Stepping to the minimum or maximum value + if (svt && newval <= mMinValue && val == mMinValue) + newval = mMinValue; + else + newval = (newval <= minval) ? minval : mMaxValue; + QSpinBox::setValue(newval); + emit stepped(step); + return true; + } + + // If the interim value will lie outside the spinbox's range, + // temporarily adjust the range to allow the value to be set. + int tempval = val + adjust; + if (tempval < mMinValue) + { + QSpinBox::setMinValue(tempval); + mShiftMinBound = true; + } + else if (tempval > mMaxValue) + { + QSpinBox::setMaxValue(tempval); + mShiftMaxBound = true; + } + } + + // Don't process changes since this new value will be stepped immediately + mSuppressSignals = true; + bool blocked = signalsBlocked(); + blockSignals(true); + addValue(adjust, true); + blockSignals(blocked); + mSuppressSignals = false; + } + QSpinBox::setLineStep(mLineShiftStep); + } + else if (!shift && mShiftMouse) + { + // Reinstate to normal (non-shift) stepping + QSpinBox::setLineStep(mLineStep); + QSpinBox::setMinValue(mMinValue); + QSpinBox::setMaxValue(mMaxValue); + mShiftMinBound = mShiftMaxBound = false; + mShiftMouse = false; + } + return false; +} + +/****************************************************************************** +* Return the initial adjustment to the value for a shift step up or down. +* The default is to step up or down to the nearest multiple of the shift +* increment, so the adjustment returned is for stepping up the decrement +* required to round down to a multiple of the shift increment <= current value, +* or for stepping down the increment required to round up to a multiple of the +* shift increment >= current value. +* This method's caller then adjusts the resultant value if necessary to cater +* for the widget's minimum/maximum value, and wrapping. +* This should really be a static method, but it needs to be virtual... +*/ +int SpinBox::shiftStepAdjustment(int oldValue, int shiftStep) +{ + if (oldValue == 0 || shiftStep == 0) + return 0; + if (shiftStep > 0) + { + if (oldValue >= 0) + return -(oldValue % shiftStep); + else + return (-oldValue - 1) % shiftStep + 1 - shiftStep; + } + else + { + shiftStep = -shiftStep; + if (oldValue >= 0) + return shiftStep - ((oldValue - 1) % shiftStep + 1); + else + return (-oldValue) % shiftStep; + } +} + +/****************************************************************************** +* Find which spin widget button a mouse event is in. +*/ +int SpinBox::whichButton(const QPoint& pos) +{ + if (upRect().contains(pos)) + return UP; + if (downRect().contains(pos)) + return DOWN; + return NO_BUTTON; +} diff --git a/kalarm/lib/spinbox.h b/kalarm/lib/spinbox.h new file mode 100644 index 000000000..0df910233 --- /dev/null +++ b/kalarm/lib/spinbox.h @@ -0,0 +1,156 @@ +/* + * spinbox.h - spin box with shift-click step value and read-only option + * Program: kalarm + * Copyright © 2002-2006,2008 by 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. + */ + +#ifndef SPINBOX_H +#define SPINBOX_H + +#include + + +/** + * @short Spin box with accelerated shift key stepping and read-only option. + * + * The SpinBox class provides a QSpinBox with accelerated stepping using the shift key. + * + * A separate step increment may optionally be specified for use when the shift key is + * held down. Typically this would be larger than the normal step. Then, when the user + * clicks the spin buttons, he/she can increment or decrement the value faster by holding + * the shift key down. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class SpinBox : public QSpinBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit SpinBox(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the spin box can have. + * @param maxValue The maximum value which the spin box can have. + * @param step The (unshifted) step interval. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + SpinBox(int minValue, int maxValue, int step = 1, QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the spin box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns whether the spin box value text is selected when its value is stepped. */ + bool selectOnStep() const { return mSelectOnStep; } + /** Sets whether the spin box value text should be selected when its value is stepped. */ + void setSelectOnStep(bool sel) { mSelectOnStep = sel; } + /** Adds a value to the current value of the spin box. */ + void addValue(int change) { addValue(change, false); } + /** Returns the minimum value of the spin box. */ + int minValue() const { return mMinValue; } + /** Returns the maximum value of the spin box. */ + int maxValue() const { return mMaxValue; } + /** Sets the minimum value of the spin box. */ + void setMinValue(int val); + /** Sets the maximum value of the spin box. */ + void setMaxValue(int val); + /** Sets the minimum and maximum values of the spin box. */ + void setRange(int minValue, int maxValue) { setMinValue(minValue); setMaxValue(maxValue); } + /** Returns the specified value clamped to the range of the spin box. */ + int bound(int val) const; + /** Returns the unshifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked without the shift key being pressed. + */ + int lineStep() const { return mLineStep; } + /** Sets the unshifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked without the shift key being pressed. + */ + void setLineStep(int step); + /** Returns the shifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked while the shift key is pressed. + */ + int lineShiftStep() const { return mLineShiftStep; } + /** Sets the shifted step increment, i.e. the amount by which the spin box value + * changes when a spin button is clicked while the shift key is pressed. + */ + void setLineShiftStep(int step); + public slots: + /** Increments the value of the spin box by the unshifted step increment. */ + virtual void stepUp(); + /** Decrements the value of the spin box by the unshifted step increment. */ + virtual void stepDown(); + signals: + /** Signal emitted when the spin box's value is stepped (by the shifted or unshifted increment). + * @param step The requested step in the spin box's value. Note that the actual change in value + * may have been less than this. + */ + void stepped(int step); + + protected: + /** A virtual method called whenever the value of the spin box has changed. */ + virtual void valueChange(); + /** Returns the initial adjustment to the value for a shift step up or down. + * The default is to step up or down to the nearest multiple of the shift + * increment, so the adjustment returned is for stepping up the decrement + * required to round down to a multiple of the shift increment <= current value, + * or for stepping down the increment required to round up to a multiple of the + * shift increment >= current value. + * This method's caller then adjusts the resultant value if necessary to cater + * for the widget's minimum/maximum value, and wrapping. + * This should really be a static method, but it needs to be virtual... + */ + virtual int shiftStepAdjustment(int oldValue, int shiftStep); + /** Receives events destined for the spin widget or for the edit field. */ + virtual bool eventFilter(QObject*, QEvent*); + /** Updates the contents of the embedded QLineEdit to reflect the current value + * using mapValueToText(). Also enables/disables the up/down push buttons accordingly. + */ + virtual void updateDisplay(); + + private slots: + void textEdited(); + private: + void init(); + void addValue(int change, bool current); + int whichButton(const QPoint&); + bool setShiftStepping(bool, int currentButton); + + enum { NO_BUTTON, UP, DOWN }; + + int mMinValue; + int mMaxValue; + int mLineStep; // step when spin arrows are pressed + int mLineShiftStep; // step when spin arrows are pressed with shift key + int mCurrentButton; // current spin widget button + bool mShiftMouse; // true while left button is being held down with shift key + bool mShiftMinBound; // true if a temporary minimum bound has been set during shift stepping + bool mShiftMaxBound; // true if a temporary maximum bound has been set during shift stepping + bool mSelectOnStep; // select the editor text whenever spin buttons are clicked (default) + bool mReadOnly; // value cannot be changed + bool mSuppressSignals; + bool mEdited; // text field has been edited +}; + +#endif // SPINBOX_H diff --git a/kalarm/lib/spinbox2.cpp b/kalarm/lib/spinbox2.cpp new file mode 100644 index 000000000..313d3eb56 --- /dev/null +++ b/kalarm/lib/spinbox2.cpp @@ -0,0 +1,511 @@ +/* + * spinbox2.cpp - spin box with extra pair of spin buttons (for Qt 3) + * Program: kalarm + * Copyright © 2001-2005,2008 by 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 + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "spinbox2.moc" +#include "spinbox2private.moc" + + +/* List of styles which need to display the extra pair of spin buttons as a + * left-to-right mirror image. This is only necessary when, for example, the + * corners of widgets are rounded. For most styles, it is better not to mirror + * the spin widgets so as to keep the normal lighting/shading on either side. + */ +static const char* mirrorStyles[] = { + "PlastikStyle", + 0 // list terminator +}; +static bool mirrorStyle(const QStyle&); + + +int SpinBox2::mReverseLayout = -1; + +SpinBox2::SpinBox2(QWidget* parent, const char* name) + : QFrame(parent, name), + mReverseWithLayout(true) +{ + mUpdown2Frame = new QFrame(this); + mSpinboxFrame = new QFrame(this); + mUpdown2 = new ExtraSpinBox(mUpdown2Frame, "updown2"); +// mSpinbox = new MainSpinBox(0, 1, 1, this, mSpinboxFrame); + mSpinbox = new MainSpinBox(this, mSpinboxFrame); + init(); +} + +SpinBox2::SpinBox2(int minValue, int maxValue, int step, int step2, QWidget* parent, const char* name) + : QFrame(parent, name), + mReverseWithLayout(true) +{ + mUpdown2Frame = new QFrame(this); + mSpinboxFrame = new QFrame(this); + mUpdown2 = new ExtraSpinBox(minValue, maxValue, step2, mUpdown2Frame, "updown2"); + mSpinbox = new MainSpinBox(minValue, maxValue, step, this, mSpinboxFrame); + setSteps(step, step2); + init(); +} + +void SpinBox2::init() +{ + if (mReverseLayout < 0) + mReverseLayout = QApplication::reverseLayout() ? 1 : 0; + mMinValue = mSpinbox->minValue(); + mMaxValue = mSpinbox->maxValue(); + mLineStep = mSpinbox->lineStep(); + mLineShiftStep = mSpinbox->lineShiftStep(); + mPageStep = mUpdown2->lineStep(); + mPageShiftStep = mUpdown2->lineShiftStep(); + mSpinbox->setSelectOnStep(false); // default + mUpdown2->setSelectOnStep(false); // always false + setFocusProxy(mSpinbox); + mUpdown2->setFocusPolicy(QWidget::NoFocus); + mSpinMirror = new SpinMirror(mUpdown2, mUpdown2Frame, this); + if (!mirrorStyle(style())) + mSpinMirror->hide(); // hide mirrored spin buttons when they are inappropriate + connect(mSpinbox, SIGNAL(valueChanged(int)), SLOT(valueChange())); + connect(mSpinbox, SIGNAL(valueChanged(int)), SIGNAL(valueChanged(int))); + connect(mSpinbox, SIGNAL(valueChanged(const QString&)), SIGNAL(valueChanged(const QString&))); + connect(mUpdown2, SIGNAL(stepped(int)), SLOT(stepPage(int))); + connect(mUpdown2, SIGNAL(styleUpdated()), SLOT(updateMirror())); +} + +void SpinBox2::setReadOnly(bool ro) +{ + if (static_cast(ro) != static_cast(mSpinbox->isReadOnly())) + { + mSpinbox->setReadOnly(ro); + mUpdown2->setReadOnly(ro); + mSpinMirror->setReadOnly(ro); + } +} + +void SpinBox2::setReverseWithLayout(bool reverse) +{ + if (reverse != mReverseWithLayout) + { + mReverseWithLayout = reverse; + setSteps(mLineStep, mPageStep); + setShiftSteps(mLineShiftStep, mPageShiftStep); + } +} + +void SpinBox2::setEnabled(bool enabled) +{ + QFrame::setEnabled(enabled); + updateMirror(); +} + +void SpinBox2::setWrapping(bool on) +{ + mSpinbox->setWrapping(on); + mUpdown2->setWrapping(on); +} + +QRect SpinBox2::up2Rect() const +{ + return mUpdown2->upRect(); +} + +QRect SpinBox2::down2Rect() const +{ + return mUpdown2->downRect(); +} + +void SpinBox2::setLineStep(int step) +{ + mLineStep = step; + if (reverseButtons()) + mUpdown2->setLineStep(step); // reverse layout, but still set the right buttons + else + mSpinbox->setLineStep(step); +} + +void SpinBox2::setSteps(int line, int page) +{ + mLineStep = line; + mPageStep = page; + if (reverseButtons()) + { + mUpdown2->setLineStep(line); // reverse layout, but still set the right buttons + mSpinbox->setLineStep(page); + } + else + { + mSpinbox->setLineStep(line); + mUpdown2->setLineStep(page); + } +} + +void SpinBox2::setShiftSteps(int line, int page) +{ + mLineShiftStep = line; + mPageShiftStep = page; + if (reverseButtons()) + { + mUpdown2->setLineShiftStep(line); // reverse layout, but still set the right buttons + mSpinbox->setLineShiftStep(page); + } + else + { + mSpinbox->setLineShiftStep(line); + mUpdown2->setLineShiftStep(page); + } +} + +void SpinBox2::setButtonSymbols(QSpinBox::ButtonSymbols newSymbols) +{ + if (mSpinbox->buttonSymbols() == newSymbols) + return; + mSpinbox->setButtonSymbols(newSymbols); + mUpdown2->setButtonSymbols(newSymbols); +} + +int SpinBox2::bound(int val) const +{ + return (val < mMinValue) ? mMinValue : (val > mMaxValue) ? mMaxValue : val; +} + +void SpinBox2::setMinValue(int val) +{ + mMinValue = val; + mSpinbox->setMinValue(val); + mUpdown2->setMinValue(val); +} + +void SpinBox2::setMaxValue(int val) +{ + mMaxValue = val; + mSpinbox->setMaxValue(val); + mUpdown2->setMaxValue(val); +} + +void SpinBox2::valueChange() +{ + int val = mSpinbox->value(); + bool blocked = mUpdown2->signalsBlocked(); + mUpdown2->blockSignals(true); + mUpdown2->setValue(val); + mUpdown2->blockSignals(blocked); +} + +/****************************************************************************** +* Called when the widget is about to be displayed. +* (At construction time, the spin button widths cannot be determined correctly, +* so we need to wait until now to definitively rearrange the widget.) +*/ +void SpinBox2::showEvent(QShowEvent*) +{ + arrange(); +} + +QSize SpinBox2::sizeHint() const +{ + getMetrics(); + QSize size = mSpinbox->sizeHint(); + size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap); + return size; +} + +QSize SpinBox2::minimumSizeHint() const +{ + getMetrics(); + QSize size = mSpinbox->minimumSizeHint(); + size.setWidth(size.width() - xSpinbox + wUpdown2 + wGap); + return size; +} + +void SpinBox2::styleChange(QStyle&) +{ + if (mirrorStyle(style())) + mSpinMirror->show(); // show rounded corners with Plastik etc. + else + mSpinMirror->hide(); // keep normal shading with other styles + arrange(); +} + +/****************************************************************************** +* Called when the extra pair of spin buttons has repainted after a style change. +* Updates the mirror image of the spin buttons. +*/ +void SpinBox2::updateMirror() +{ + mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + +/****************************************************************************** +* Set the positions and sizes of all the child widgets. +*/ +void SpinBox2::arrange() +{ + getMetrics(); + QRect arrowRect = QStyle::visualRect(QRect(0, 0, wUpdown2, height()), this); + mUpdown2Frame->setGeometry(arrowRect); + mUpdown2->setGeometry(-xUpdown2, 0, mUpdown2->width(), height()); + mSpinboxFrame->setGeometry(QStyle::visualRect(QRect(wUpdown2 + wGap, 0, width() - wUpdown2 - wGap, height()), this)); + mSpinbox->setGeometry(-xSpinbox, 0, mSpinboxFrame->width() + xSpinbox, height()); + mSpinMirror->resize(wUpdown2, mUpdown2->height()); + mSpinMirror->setGeometry(arrowRect); +//mSpinMirror->setGeometry(QStyle::visualRect(QRect(0, 11, wUpdown2, height()), this)); + mSpinMirror->setNormalButtons(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + +/****************************************************************************** +* Calculate the width and position of the extra pair of spin buttons. +* Style-specific adjustments are made for a better appearance. +*/ +void SpinBox2::getMetrics() const +{ + QRect rect = mUpdown2->style().querySubControlMetrics(QStyle::CC_SpinWidget, mUpdown2, QStyle::SC_SpinWidgetButtonField); + if (style().inherits("PlastikStyle")) + rect.setLeft(rect.left() - 1); // Plastik excludes left border from spin widget rectangle + xUpdown2 = mReverseLayout ? 0 : rect.left(); + wUpdown2 = mUpdown2->width() - rect.left(); + xSpinbox = mSpinbox->style().querySubControlMetrics(QStyle::CC_SpinWidget, mSpinbox, QStyle::SC_SpinWidgetEditField).left(); + wGap = 0; + + // Make style-specific adjustments for a better appearance + if (style().inherits("QMotifPlusStyle")) + { + xSpinbox = 0; // show the edit control left border + wGap = 2; // leave a space to the right of the left-hand pair of spin buttons + } +} + +/****************************************************************************** +* Called when the extra pair of spin buttons is clicked to step the value. +* Normally this is a page step, but with a right-to-left language where the +* button functions are reversed, this is a line step. +*/ +void SpinBox2::stepPage(int step) +{ + if (abs(step) == mUpdown2->lineStep()) + mSpinbox->setValue(mUpdown2->value()); + else + { + // It's a shift step + int oldValue = mSpinbox->value(); + if (!reverseButtons()) + { + // The button pairs have the normal function. + // Page shift stepping - step up or down to a multiple of the + // shift page increment, leaving unchanged the part of the value + // which is the remainder from the page increment. + if (oldValue >= 0) + oldValue -= oldValue % mUpdown2->lineStep(); + else + oldValue += (-oldValue) % mUpdown2->lineStep(); + } + int adjust = mSpinbox->shiftStepAdjustment(oldValue, step); + if (adjust == -step + && ((step > 0 && oldValue + step >= mSpinbox->maxValue()) + || (step < 0 && oldValue + step <= mSpinbox->minValue()))) + adjust = 0; // allow stepping to the minimum or maximum value + mSpinbox->addValue(adjust + step); + } + bool focus = mSpinbox->selectOnStep() && mUpdown2->hasFocus(); + if (focus) + mSpinbox->selectAll(); + + // Make the covering arrows image show the pressed arrow + mSpinMirror->redraw(QPixmap::grabWidget(mUpdown2Frame, 0, 0)); +} + + +/*============================================================================= += Class SpinBox2::MainSpinBox +=============================================================================*/ + +/****************************************************************************** +* Return the initial adjustment to the value for a shift step up or down, for +* the main (visible) spin box. +* Normally this is a line step, but with a right-to-left language where the +* button functions are reversed, this is a page step. +*/ +int SpinBox2::MainSpinBox::shiftStepAdjustment(int oldValue, int shiftStep) +{ + if (owner->reverseButtons()) + { + // The button pairs have the opposite function from normal. + // Page shift stepping - step up or down to a multiple of the + // shift page increment, leaving unchanged the part of the value + // which is the remainder from the page increment. + if (oldValue >= 0) + oldValue -= oldValue % lineStep(); + else + oldValue += (-oldValue) % lineStep(); + } + return SpinBox::shiftStepAdjustment(oldValue, shiftStep); +} + + +/*============================================================================= += Class ExtraSpinBox +=============================================================================*/ + +/****************************************************************************** +* Repaint the widget. +* If it's the first time since a style change, tell the parent SpinBox2 to +* update the SpinMirror with the new unpressed button image. We make the +* presumably reasonable assumption that when a style change occurs, the spin +* buttons are unpressed. +*/ +void ExtraSpinBox::paintEvent(QPaintEvent* e) +{ + SpinBox::paintEvent(e); + if (mNewStylePending) + { + mNewStylePending = false; + emit styleUpdated(); + } +} + + +/*============================================================================= += Class SpinMirror +=============================================================================*/ + +SpinMirror::SpinMirror(SpinBox* spinbox, QFrame* spinFrame, QWidget* parent, const char* name) + : QCanvasView(new QCanvas, parent, name), + mSpinbox(spinbox), + mSpinFrame(spinFrame), + mReadOnly(false) +{ + setVScrollBarMode(QScrollView::AlwaysOff); + setHScrollBarMode(QScrollView::AlwaysOff); + setFrameStyle(QFrame::NoFrame); + + // Find the spin widget which is part of the spin box, in order to + // pass on its shift-button presses. + QObjectList* spinwidgets = spinbox->queryList("QSpinWidget", 0, false, true); + mSpinWidget = (SpinBox*)spinwidgets->getFirst(); + delete spinwidgets; +} + +void SpinMirror::setNormalButtons(const QPixmap& px) +{ + mNormalButtons = px; + redraw(mNormalButtons); +} + +void SpinMirror::redraw() +{ + redraw(QPixmap::grabWidget(mSpinFrame, 0, 0)); +} + +void SpinMirror::redraw(const QPixmap& px) +{ + QCanvas* c = canvas(); + c->setBackgroundPixmap(px); + c->setAllChanged(); + c->update(); +} + +void SpinMirror::resize(int w, int h) +{ + canvas()->resize(w, h); + QCanvasView::resize(w, h); + resizeContents(w, h); + setWorldMatrix(QWMatrix(-1, 0, 0, 1, w - 1, 0)); // mirror left to right +} + +/****************************************************************************** +* Pass on all mouse events to the spinbox which we're covering up. +*/ +void SpinMirror::contentsMouseEvent(QMouseEvent* e) +{ + if (mReadOnly) + return; + QPoint pt = contentsToViewport(e->pos()); + pt.setX(pt.x() + mSpinbox->upRect().left()); + QApplication::postEvent(mSpinWidget, new QMouseEvent(e->type(), pt, e->button(), e->state())); + + // If the mouse button has been pressed or released, refresh the spin buttons + switch (e->type()) + { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + QTimer::singleShot(0, this, SLOT(redraw())); + break; + default: + break; + } +} + +/****************************************************************************** +* Pass on all mouse events to the spinbox which we're covering up. +*/ +void SpinMirror::contentsWheelEvent(QWheelEvent* e) +{ + if (mReadOnly) + return; + QPoint pt = contentsToViewport(e->pos()); + pt.setX(pt.x() + mSpinbox->upRect().left()); + QApplication::postEvent(mSpinWidget, new QWheelEvent(pt, e->delta(), e->state(), e->orientation())); +} + +/****************************************************************************** +* Pass on to the main spinbox events which are needed to activate mouseover and +* other graphic effects when the mouse cursor enters and leaves the widget. +*/ +bool SpinMirror::event(QEvent* e) +{ + switch (e->type()) + { + case QEvent::Leave: + case QEvent::Enter: + QApplication::postEvent(mSpinWidget, new QEvent(e->type())); + QTimer::singleShot(0, this, SLOT(redraw())); + break; + case QEvent::FocusIn: + mSpinbox->setFocus(); + QTimer::singleShot(0, this, SLOT(redraw())); + break; + default: + break; + } + return QCanvasView::event(e); +} + + +/*============================================================================= += Local functions +=============================================================================*/ + +/****************************************************************************** +* Determine whether the extra pair of spin buttons needs to be mirrored +* left-to-right in the specified style. +*/ +static bool mirrorStyle(const QStyle& style) +{ + for (const char** s = mirrorStyles; *s; ++s) + if (style.inherits(*s)) + return true; + return false; +} diff --git a/kalarm/lib/spinbox2.h b/kalarm/lib/spinbox2.h new file mode 100644 index 000000000..772eaee70 --- /dev/null +++ b/kalarm/lib/spinbox2.h @@ -0,0 +1,317 @@ +/* + * spinbox2.h - spin box with extra pair of spin buttons (for Qt 3) + * Program: kalarm + * Copyright © 2001-2007 by 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. + */ + +#ifndef SPINBOX2_H +#define SPINBOX2_H + +#include +#include + +class SpinMirror; +class ExtraSpinBox; +#include "spinbox.h" + + +/** + * @short Spin box with a pair of spin buttons on either side. + * + * The SpinBox2 class provides a spin box with two pairs of spin buttons, one on either side. + * + * It is designed as a base class for implementing such facilities as time spin boxes, where + * the hours and minutes values are separately displayed in the edit field. When the + * appropriate step increments are configured, the left spin arrows can then be used to + * change the hours value, while the right spin arrows can be used to change the minutes + * value. + * + * Rather than using SpinBox2 directly for time entry, use in preference TimeSpinBox or + * TimeEdit classes which are tailored from SpinBox2 for this purpose. + * + * Separate step increments may optionally be specified for use when the shift key is + * held down. Typically these would be larger than the normal steps. Then, when the user + * clicks the spin buttons, he/she can increment or decrement the value faster by holding + * the shift key down. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class SpinBox2 : public QFrame +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit SpinBox2(QWidget* parent = 0, const char* name = 0); + /** Constructor. + * @param minValue The minimum value which the spin box can have. + * @param maxValue The maximum value which the spin box can have. + * @param step The (unshifted) step interval for the right-hand spin buttons. + * @param step2 The (unshifted) step interval for the left-hand spin buttons. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + SpinBox2(int minValue, int maxValue, int step = 1, int step2 = 1, + QWidget* parent = 0, const char* name = 0); + /** Sets whether the spin box can be changed by the user. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mSpinbox->isReadOnly(); } + /** Sets whether the spin box value text should be selected when its value is stepped. */ + void setSelectOnStep(bool sel) { mSpinbox->setSelectOnStep(sel); } + /** Sets whether the spin button pairs should be reversed for a right-to-left language. + * The default is for them to be reversed. + */ + void setReverseWithLayout(bool reverse); + /** Returns whether the spin button pairs will be reversed for a right-to-left language. */ + bool reverseButtons() const { return mReverseLayout && !mReverseWithLayout; } + + /** Returns the spin box's text, including any prefix() and suffix(). */ + QString text() const { return mSpinbox->text(); } + /** Returns the prefix for the spin box's text. */ + virtual QString prefix() const { return mSpinbox->prefix(); } + /** Returns the suffix for the spin box's text. */ + virtual QString suffix() const { return mSpinbox->suffix(); } + /** Returns the spin box's text with no prefix(), suffix() or leading or trailing whitespace. */ + virtual QString cleanText() const { return mSpinbox->cleanText(); } + + /** Sets the special-value text which, if non-null, is displayed instead of a numeric + * value when the current value is equal to minValue(). + */ + virtual void setSpecialValueText(const QString& text) { mSpinbox->setSpecialValueText(text); } + /** Returns the special-value text which, if non-null, is displayed instead of a numeric + * value when the current value is equal to minValue(). + */ + QString specialValueText() const { return mSpinbox->specialValueText(); } + + /** Sets whether it is possible to step the value from the highest value to the + * lowest value and vice versa. + */ + virtual void setWrapping(bool on); + /** Returns whether it is possible to step the value from the highest value to the + * lowest value and vice versa. + */ + bool wrapping() const { return mSpinbox->wrapping(); } + + /** Set the text alignment of the widget */ + void setAlignment(int a) { mSpinbox->setAlignment(a); } + /** Sets the button symbols to use (arrows or plus/minus). */ + virtual void setButtonSymbols(QSpinBox::ButtonSymbols); + /** Returns the button symbols currently in use (arrows or plus/minus). */ + QSpinBox::ButtonSymbols buttonSymbols() const { return mSpinbox->buttonSymbols(); } + + /** Sets the validator to @p v. The validator controls what keyboard input is accepted + * when the user is editing the value field. + */ + virtual void setValidator(const QValidator* v) { mSpinbox->setValidator(v); } + /** Returns the current validator. The validator controls what keyboard input is accepted + * when the user is editing the value field. + */ + const QValidator* validator() const { return mSpinbox->validator(); } + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + /** Returns the minimum value of the spin box. */ + int minValue() const { return mMinValue; } + /** Returns the maximum value of the spin box. */ + int maxValue() const { return mMaxValue; } + /** Sets the minimum value of the spin box. */ + virtual void setMinValue(int val); + /** Sets the maximum value of the spin box. */ + virtual void setMaxValue(int val); + /** Sets the minimum and maximum values of the spin box. */ + void setRange(int minValue, int maxValue) { setMinValue(minValue); setMaxValue(maxValue); } + /** Returns the current value of the spin box. */ + int value() const { return mSpinbox->value(); } + /** Returns the specified value clamped to the range of the spin box. */ + int bound(int val) const; + + /** Returns the geometry of the right-hand "up" button. */ + QRect upRect() const { return mSpinbox->upRect(); } + /** Returns the geometry of the right-hand "down" button. */ + QRect downRect() const { return mSpinbox->downRect(); } + /** Returns the geometry of the left-hand "up" button. */ + QRect up2Rect() const; + /** Returns the geometry of the left-hand "down" button. */ + QRect down2Rect() const; + + /** Returns the unshifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked without the shift key being pressed. + */ + int lineStep() const { return mLineStep; } + /** Returns the shifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked while the shift key is pressed. + */ + int lineShiftStep() const { return mLineShiftStep; } + /** Returns the unshifted step increment for the left-hand spin buttons, + * i.e. the amount by which the spin box value changes when a left-hand + * spin button is clicked without the shift key being pressed. + */ + int pageStep() const { return mPageStep; } + /** Returns the shifted step increment for the left-hand spin buttons, + * i.e. the amount by which the spin box value changes when a left-hand + * spin button is clicked while the shift key is pressed. + */ + int pageShiftStep() const { return mPageShiftStep; } + /** Sets the unshifted step increment for the right-hand spin buttons, + * i.e. the amount by which the spin box value changes when a right-hand + * spin button is clicked without the shift key being pressed. + */ + void setLineStep(int step); + /** Sets the unshifted step increments for the two pairs of spin buttons, + * i.e. the amount by which the spin box value changes when a spin button + * is clicked without the shift key being pressed. + * @param line The step increment for the right-hand spin buttons. + * @param page The step increment for the left-hand spin buttons. + */ + void setSteps(int line, int page); + /** Sets the shifted step increments for the two pairs of spin buttons, + * i.e. the amount by which the spin box value changes when a spin button + * is clicked while the shift key is pressed. + * @param line The shift step increment for the right-hand spin buttons. + * @param page The shift step increment for the left-hand spin buttons. + */ + void setShiftSteps(int line, int page); + + /** Increments the current value by adding the unshifted step increment for + * the left-hand spin buttons. + */ + void addPage() { addValue(mPageStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the left-hand spin buttons. + */ + void subtractPage() { addValue(-mPageStep); } + /** Increments the current value by adding the unshifted step increment for + * the right-hand spin buttons. + */ + void addLine() { addValue(mLineStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the right-hand spin buttons. + */ + void subtractLine() { addValue(-mLineStep); } + /** Adjusts the current value by adding @p change. */ + void addValue(int change) { mSpinbox->addValue(change); } + + public slots: + /** Sets the current value to @p val. */ + virtual void setValue(int val) { mSpinbox->setValue(val); } + /** Sets the prefix which is prepended to the start of the displayed text. */ + virtual void setPrefix(const QString& text) { mSpinbox->setPrefix(text); } + /** Sets the suffix which is prepended to the start of the displayed text. */ + virtual void setSuffix(const QString& text) { mSpinbox->setSuffix(text); } + /** Increments the current value by adding the unshifted step increment for + * the right-hand spin buttons. + */ + virtual void stepUp() { addValue(mLineStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the right-hand spin buttons. + */ + virtual void stepDown() { addValue(-mLineStep); } + /** Increments the current value by adding the unshifted step increment for + * the left-hand spin buttons. + */ + virtual void pageUp() { addValue(mPageStep); } + /** Decrements the current value by subtracting the unshifted step increment for + * the left-hand spin buttons. + */ + virtual void pageDown() { addValue(-mPageStep); } + /** Selects all the text in the spin box's editor. */ + virtual void selectAll() { mSpinbox->selectAll(); } + /** Sets whether the widget is enabled. */ + virtual void setEnabled(bool enabled); + + signals: + /** Signal which is emitted whenever the value of the spin box changes. */ + void valueChanged(int value); + /** Signal which is emitted whenever the value of the spin box changes. */ + void valueChanged(const QString& valueText); + + protected: + virtual QString mapValueToText(int v) { return mSpinbox->mapValToText(v); } + virtual int mapTextToValue(bool* ok) { return mSpinbox->mapTextToVal(ok); } + virtual void resizeEvent(QResizeEvent*) { arrange(); } + virtual void showEvent(QShowEvent*); + virtual void styleChange(QStyle&); + virtual void getMetrics() const; + + mutable int wUpdown2; // width of second spin widget + mutable int xUpdown2; // x offset of visible area in 'mUpdown2' + mutable int xSpinbox; // x offset of visible area in 'mSpinbox' + mutable int wGap; // gap between mUpdown2Frame and mSpinboxFrame + + protected slots: + virtual void valueChange(); + virtual void stepPage(int); + + private slots: + void updateMirror(); + + private: + void init(); + void arrange(); + int whichButton(QObject* spinWidget, const QPoint&); + void setShiftStepping(bool on); + + // Visible spin box class. + // Declared here to allow use of mSpinBox in inline methods. + class MainSpinBox : public SpinBox + { + public: + MainSpinBox(SpinBox2* sb2, QWidget* parent, const char* name = 0) + : SpinBox(parent, name), owner(sb2) { } + MainSpinBox(int minValue, int maxValue, int step, SpinBox2* sb2, QWidget* parent, const char* name = 0) + : SpinBox(minValue, maxValue, step, parent, name), owner(sb2) { } + void setAlignment(int a) { editor()->setAlignment(a); } + virtual QString mapValueToText(int v) { return owner->mapValueToText(v); } + virtual int mapTextToValue(bool* ok) { return owner->mapTextToValue(ok); } + QString mapValToText(int v) { return SpinBox::mapValueToText(v); } + int mapTextToVal(bool* ok) { return SpinBox::mapTextToValue(ok); } + virtual int shiftStepAdjustment(int oldValue, int shiftStep); + private: + SpinBox2* owner; // owner SpinBox2 + }; + + enum { NO_BUTTON = -1, UP, DOWN, UP2, DOWN2 }; + + static int mReverseLayout; // widgets are mirrored right to left + QFrame* mUpdown2Frame; // contains visible part of the extra pair of spin buttons + QFrame* mSpinboxFrame; // contains the main spin box + ExtraSpinBox* mUpdown2; // the extra pair of spin buttons + MainSpinBox* mSpinbox; // the visible spin box + SpinMirror* mSpinMirror; // image of the extra pair of spin buttons + int mMinValue; + int mMaxValue; + int mLineStep; // right button increment + int mLineShiftStep; // right button increment with shift pressed + int mPageStep; // left button increment + int mPageShiftStep; // left button increment with shift pressed + bool mReverseWithLayout; // reverse button positions if reverse layout (default = true) + + friend class MainSpinBox; +}; + +#endif // SPINBOX2_H diff --git a/kalarm/lib/spinbox2private.h b/kalarm/lib/spinbox2private.h new file mode 100644 index 000000000..74fbeb66c --- /dev/null +++ b/kalarm/lib/spinbox2private.h @@ -0,0 +1,92 @@ +/* + * spinbox2private.h - private classes for SpinBox2 (for Qt 3) + * Program: kalarm + * Copyright © 2005,2006,2008 by 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. + */ + +#ifndef SPINBOX2PRIVATE_H +#define SPINBOX2PRIVATE_H + +#include +#include "spinbox.h" + + +/*============================================================================= += Class ExtraSpinBox +* Extra pair of spin buttons for SpinBox2. +* The widget is actually a whole spin box, but only the buttons are displayed. +=============================================================================*/ + +class ExtraSpinBox : public SpinBox +{ + Q_OBJECT + public: + explicit ExtraSpinBox(QWidget* parent, const char* name = 0) + : SpinBox(parent, name), mNewStylePending(false) { } + ExtraSpinBox(int minValue, int maxValue, int step, QWidget* parent, const char* name = 0) + : SpinBox(minValue, maxValue, step, parent, name), mNewStylePending(false) { } + signals: + void styleUpdated(); + protected: + virtual void paintEvent(QPaintEvent*); + virtual void styleChange(QStyle&) { mNewStylePending = true; } + private: + bool mNewStylePending; // style has changed, but not yet repainted +}; + + +/*============================================================================= += Class SpinMirror +* Displays the left-to-right mirror image of a pair of spin buttons, for use +* as the extra spin buttons in a SpinBox2. All mouse clicks are passed on to +* the real extra pair of spin buttons for processing. +* Mirroring in this way allows styles with rounded corners to display correctly. +=============================================================================*/ + +class SpinMirror : public QCanvasView +{ + Q_OBJECT + public: + explicit SpinMirror(SpinBox*, QFrame* spinFrame, QWidget* parent = 0, const char* name = 0); + void setReadOnly(bool ro) { mReadOnly = ro; } + bool isReadOnly() const { return mReadOnly; } + void setNormalButtons(const QPixmap&); + void redraw(const QPixmap&); + + public slots: + virtual void resize(int w, int h); + void redraw(); + + protected: + virtual void contentsMousePressEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseReleaseEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseMoveEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsMouseDoubleClickEvent(QMouseEvent* e) { contentsMouseEvent(e); } + virtual void contentsWheelEvent(QWheelEvent*); + virtual bool event(QEvent*); + + private: + void contentsMouseEvent(QMouseEvent*); + + SpinBox* mSpinbox; // spinbox whose spin buttons are being mirrored + QFrame* mSpinFrame; // widget containing mSpinbox's spin buttons + QWidget* mSpinWidget; // spin buttons in mSpinbox + QPixmap mNormalButtons; // image of spin buttons in unpressed state + bool mReadOnly; // value cannot be changed +}; + +#endif // SPINBOX2PRIVATE_H diff --git a/kalarm/lib/synchtimer.cpp b/kalarm/lib/synchtimer.cpp new file mode 100644 index 000000000..1b681d6eb --- /dev/null +++ b/kalarm/lib/synchtimer.cpp @@ -0,0 +1,233 @@ +/* + * synchtimer.cpp - timers which synchronise to time boundaries + * Program: kalarm + * Copyright (C) 2004, 2005 by 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 "kalarm.h" +#include +#include +#include "synchtimer.moc" + + +/*============================================================================= += Class: SynchTimer += Virtual base class for application-wide timer synchronised to a time boundary. +=============================================================================*/ + +SynchTimer::SynchTimer() +{ + mTimer = new QTimer(this, "mTimer"); +} + +SynchTimer::~SynchTimer() +{ + delete mTimer; + mTimer = 0; +} + +/****************************************************************************** +* Connect to the timer. The timer is started if necessary. +*/ +void SynchTimer::connecT(QObject* receiver, const char* member) +{ + Connection connection(receiver, member); + if (mConnections.find(connection) != mConnections.end()) + return; // the slot is already connected, so ignore request + connect(mTimer, SIGNAL(timeout()), receiver, member); + mConnections.append(connection); + if (!mTimer->isActive()) + { + connect(mTimer, SIGNAL(timeout()), this, SLOT(slotTimer())); + start(); + } +} + +/****************************************************************************** +* Disconnect from the timer. The timer is stopped if no longer needed. +*/ +void SynchTimer::disconnecT(QObject* receiver, const char* member) +{ + if (mTimer) + { + mTimer->disconnect(receiver, member); + if (member) + mConnections.remove(Connection(receiver, member)); + else + { + for (QValueList::Iterator it = mConnections.begin(); it != mConnections.end(); ) + { + if ((*it).receiver == receiver) + it = mConnections.remove(it); + else + ++it; + } + } + if (mConnections.isEmpty()) + { + mTimer->disconnect(); + mTimer->stop(); + } + } +} + + +/*============================================================================= += Class: MinuteTimer += Application-wide timer synchronised to the minute boundary. +=============================================================================*/ + +MinuteTimer* MinuteTimer::mInstance = 0; + +MinuteTimer* MinuteTimer::instance() +{ + if (!mInstance) + mInstance = new MinuteTimer; + return mInstance; +} + +/****************************************************************************** +* Called when the timer triggers, or to start the timer. +* Timers can under some circumstances wander off from the correct trigger time, +* so rather than setting a 1 minute interval, calculate the correct next +* interval each time it triggers. +*/ +void MinuteTimer::slotTimer() +{ + kdDebug(5950) << "MinuteTimer::slotTimer()" << endl; + int interval = 62 - QTime::currentTime().second(); + mTimer->start(interval * 1000, true); // execute a single shot +} + + +/*============================================================================= += Class: DailyTimer += Application-wide timer synchronised to midnight. +=============================================================================*/ + +QValueList DailyTimer::mFixedTimers; + +DailyTimer::DailyTimer(const QTime& timeOfDay, bool fixed) + : mTime(timeOfDay), + mFixed(fixed) +{ + if (fixed) + mFixedTimers.append(this); +} + +DailyTimer::~DailyTimer() +{ + if (mFixed) + mFixedTimers.remove(this); +} + +DailyTimer* DailyTimer::fixedInstance(const QTime& timeOfDay, bool create) +{ + for (QValueList::Iterator it = mFixedTimers.begin(); it != mFixedTimers.end(); ++it) + if ((*it)->mTime == timeOfDay) + return *it; + return create ? new DailyTimer(timeOfDay, true) : 0; +} + +/****************************************************************************** +* Disconnect from the timer signal which triggers at the given fixed time of day. +* If there are no remaining connections to that timer, it is destroyed. +*/ +void DailyTimer::disconnect(const QTime& timeOfDay, QObject* receiver, const char* member) +{ + DailyTimer* timer = fixedInstance(timeOfDay, false); + if (!timer) + return; + timer->disconnecT(receiver, member); + if (!timer->hasConnections()) + delete timer; +} + +/****************************************************************************** +* Change the time at which the variable timer triggers. +*/ +void DailyTimer::changeTime(const QTime& newTimeOfDay, bool triggerMissed) +{ + if (mFixed) + return; + if (mTimer->isActive()) + { + mTimer->stop(); + bool triggerNow = false; + if (triggerMissed) + { + QTime now = QTime::currentTime(); + if (now >= newTimeOfDay && now < mTime) + { + // The trigger time is now earlier and it has already arrived today. + // Trigger a timer event immediately. + triggerNow = true; + } + } + mTime = newTimeOfDay; + if (triggerNow) + mTimer->start(0, true); // trigger immediately + else + start(); + } + else + mTime = newTimeOfDay; +} + +/****************************************************************************** +* Initialise the timer to trigger at the specified time. +* This will either be today or tomorrow, depending on whether the trigger time +* has already passed. +*/ +void DailyTimer::start() +{ + // TIMEZONE = local time + QDateTime now = QDateTime::currentDateTime(); + // Find out whether to trigger today or tomorrow. + // In preference, use the last trigger date to determine this, since + // that will avoid possible errors due to daylight savings time changes. + bool today; + if (mLastDate.isValid()) + today = (mLastDate < now.date()); + else + today = (now.time() < mTime); + QDateTime next; + if (today) + next = QDateTime(now.date(), mTime); + else + next = QDateTime(now.date().addDays(1), mTime); + uint interval = next.toTime_t() - now.toTime_t(); + mTimer->start(interval * 1000, true); // execute a single shot + kdDebug(5950) << "DailyTimer::start(at " << mTime.hour() << ":" << mTime.minute() << "): interval = " << interval/3600 << ":" << (interval/60)%60 << ":" << interval%60 << endl; +} + +/****************************************************************************** +* Called when the timer triggers. +* Set the timer to trigger again tomorrow at the specified time. +* Note that if daylight savings time changes occur, this will not be 24 hours +* from now. +*/ +void DailyTimer::slotTimer() +{ + // TIMEZONE = local time + QDateTime now = QDateTime::currentDateTime(); + mLastDate = now.date(); + QDateTime next = QDateTime(mLastDate.addDays(1), mTime); + uint interval = next.toTime_t() - now.toTime_t(); + mTimer->start(interval * 1000, true); // execute a single shot + kdDebug(5950) << "DailyTimer::slotTimer(at " << mTime.hour() << ":" << mTime.minute() << "): interval = " << interval/3600 << ":" << (interval/60)%60 << ":" << interval%60 << endl; +} diff --git a/kalarm/lib/synchtimer.h b/kalarm/lib/synchtimer.h new file mode 100644 index 000000000..551fd1687 --- /dev/null +++ b/kalarm/lib/synchtimer.h @@ -0,0 +1,198 @@ +/* + * synchtimer.h - timers which synchronise to time boundaries + * Program: kalarm + * Copyright (C) 2004, 2005 by 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. + */ + +#ifndef SYNCHTIMER_H +#define SYNCHTIMER_H + +/* @file synchtimer.h - timers which synchronise to time boundaries */ + +#include +#include +#include +#include +class QTimer; + +/** SynchTimer is a virtual base class for application-wide timers synchronised + * to a time boundary. + * + * @author David Jarvie + */ +class SynchTimer : public QObject +{ + Q_OBJECT + public: + virtual ~SynchTimer(); + + struct Connection + { + Connection() { } + Connection(QObject* r, const char* s) : receiver(r), slot(s) { } + bool operator==(const Connection& c) const { return receiver == c.receiver && slot == c.slot; } + QObject* receiver; + const QCString slot; + }; + protected: + SynchTimer(); + virtual void start() = 0; + void connecT(QObject* receiver, const char* member); + void disconnecT(QObject* receiver, const char* member = 0); + bool hasConnections() const { return !mConnections.isEmpty(); } + + QTimer* mTimer; + + protected slots: + virtual void slotTimer() = 0; + + private slots: + void slotReceiverGone(QObject* r) { disconnecT(r); } + + private: + SynchTimer(const SynchTimer&); // prohibit copying + QValueList mConnections; // list of current clients +}; + + +/** MinuteTimer is an application-wide timer synchronised to the minute boundary. + * + * @author David Jarvie + */ +class MinuteTimer : public SynchTimer +{ + Q_OBJECT + public: + virtual ~MinuteTimer() { mInstance = 0; } + /** Connect to the timer signal. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(QObject* receiver, const char* member) + { instance()->connecT(receiver, member); } + /** Disconnect from the timer signal. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(QObject* receiver, const char* member = 0) + { if (mInstance) mInstance->disconnecT(receiver, member); } + + protected: + MinuteTimer() : SynchTimer() { } + static MinuteTimer* instance(); + virtual void start() { slotTimer(); } + + protected slots: + virtual void slotTimer(); + + private: + static MinuteTimer* mInstance; // the one and only instance +}; + + +/** DailyTimer is an application-wide timer synchronised to a specified time of day, local time. + * + * Daily timers come in two flavours: fixed, which can only be accessed through static methods, + * and variable, whose time can be adjusted and which are accessed through non-static methods. + * + * @author David Jarvie + */ +class DailyTimer : public SynchTimer +{ + Q_OBJECT + public: + virtual ~DailyTimer(); + /** Connect to the timer signal which triggers at the given fixed time of day. + * A new timer is created if necessary. + * @param timeOfDay Time at which the timer is to trigger. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(const QTime& timeOfDay, QObject* receiver, const char* member) + { fixedInstance(timeOfDay)->connecT(receiver, member); } + /** Disconnect from the timer signal which triggers at the given fixed time of day. + * If there are no remaining connections to that timer, it is destroyed. + * @param timeOfDay Time at which the timer triggers. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(const QTime& timeOfDay, QObject* receiver, const char* member = 0); + /** Change the time at which this variable timer triggers. + * @param newTimeOfDay New time at which the timer should trigger. + * @param triggerMissed If true, and if @p newTimeOfDay < @p oldTimeOfDay, and if the current + * time is between @p newTimeOfDay and @p oldTimeOfDay, the timer will be + * triggered immediately so as to avoid missing today's trigger. + */ + void changeTime(const QTime& newTimeOfDay, bool triggerMissed = true); + /** Return the current time of day at which this variable timer triggers. */ + QTime timeOfDay() const { return mTime; } + + protected: + /** Construct an instance. + * The constructor is protected to ensure that for variable timers, only derived classes + * can construct instances. This ensures that multiple timers are not created for the same + * use. + */ + DailyTimer(const QTime&, bool fixed); + /** Return the instance which triggers at the specified fixed time of day, + * optionally creating a new instance if necessary. + * @param timeOfDay Time at which the timer triggers. + * @param create If true, create a new instance if none already exists + * for @p timeOfDay. + * @return The instance for @p timeOfDay, or 0 if it does not exist. + */ + static DailyTimer* fixedInstance(const QTime& timeOfDay, bool create = true); + virtual void start(); + + protected slots: + virtual void slotTimer(); + + private: + static QValueList mFixedTimers; // list of timers whose trigger time is fixed + QTime mTime; + QDate mLastDate; // the date on which the timer was last triggered + bool mFixed; // the time at which the timer triggers cannot be changed +}; + + +/** MidnightTimer is an application-wide timer synchronised to midnight, local time. + * + * @author David Jarvie + */ +class MidnightTimer +{ + public: + /** Connect to the timer signal. + * @param receiver Receiving object. + * @param member Slot to activate. + */ + static void connect(QObject* receiver, const char* member) + { DailyTimer::connect(QTime(0,0), receiver, member); } + /** Disconnect from the timer signal. + * @param receiver Receiving object. + * @param member Slot to disconnect. If null, all slots belonging to + * @p receiver will be disconnected. + */ + static void disconnect(QObject* receiver, const char* member = 0) + { DailyTimer::disconnect(QTime(0,0), receiver, member); } + +}; + +#endif // SYNCHTIMER_H + diff --git a/kalarm/lib/timeedit.cpp b/kalarm/lib/timeedit.cpp new file mode 100644 index 000000000..8aabb90b6 --- /dev/null +++ b/kalarm/lib/timeedit.cpp @@ -0,0 +1,207 @@ +/* + * timeedit.cpp - time-of-day edit widget, with AM/PM shown depending on locale + * Program: kalarm + * Copyright (C) 2004 by 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 "kalarm.h" + +#include +#include + +#include "combobox.h" +#include "timespinbox.h" +#include "timeedit.moc" + + +TimeEdit::TimeEdit(QWidget* parent, const char* name) + : QHBox(parent, name), + mAmPm(0), + mAmIndex(-1), + mPmIndex(-1), + mReadOnly(false) +{ + bool use12hour = KGlobal::locale()->use12Clock(); + mSpinBox = new TimeSpinBox(!use12hour, this); + mSpinBox->setFixedSize(mSpinBox->sizeHint()); + connect(mSpinBox, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int))); + if (use12hour) + { + mAmPm = new ComboBox(this); + setAmPmCombo(1, 1); // add "am" and "pm" options to the combo box + mAmPm->setFixedSize(mAmPm->sizeHint()); + connect(mAmPm, SIGNAL(highlighted(int)), SLOT(slotAmPmChanged(int))); + } +} + +void TimeEdit::setReadOnly(bool ro) +{ + if (ro != mReadOnly) + { + mReadOnly = ro; + mSpinBox->setReadOnly(ro); + if (mAmPm) + mAmPm->setReadOnly(ro); + } +} + +int TimeEdit::value() const +{ + return mSpinBox->value(); +} + +bool TimeEdit::isValid() const +{ + return mSpinBox->isValid(); +} + +/****************************************************************************** + * Set the edit value as valid or invalid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ +void TimeEdit::setValid(bool valid) +{ + bool oldValid = mSpinBox->isValid(); + if (valid && !oldValid + || !valid && oldValid) + { + mSpinBox->setValid(valid); + if (mAmPm) + mAmPm->setCurrentItem(0); + } +} + +/****************************************************************************** + * Set the widget's value. + */ +void TimeEdit::setValue(int minutes) +{ + if (mAmPm) + { + int i = (minutes >= 720) ? mPmIndex : mAmIndex; + mAmPm->setCurrentItem(i >= 0 ? i : 0); + } + mSpinBox->setValue(minutes); +} + +bool TimeEdit::wrapping() const +{ + return mSpinBox->wrapping(); +} + +void TimeEdit::setWrapping(bool on) +{ + mSpinBox->setWrapping(on); +} + +int TimeEdit::minValue() const +{ + return mSpinBox->minValue(); +} + +int TimeEdit::maxValue() const +{ + return mSpinBox->maxValue(); +} + +void TimeEdit::setMinValue(int minutes) +{ + if (mAmPm) + setAmPmCombo((minutes < 720 ? 1 : 0), -1); // insert/remove "am" in combo box + mSpinBox->setMinValue(minutes); +} + +void TimeEdit::setMaxValue(int minutes) +{ + if (mAmPm) + setAmPmCombo(-1, (minutes < 720 ? 0 : 1)); // insert/remove "pm" in combo box + mSpinBox->setMaxValue(minutes); +} + +/****************************************************************************** + * Called when the spin box value has changed. + */ +void TimeEdit::slotValueChanged(int value) +{ + if (mAmPm) + { + bool pm = (mAmPm->currentItem() == mPmIndex); + if (pm && value < 720) + mAmPm->setCurrentItem(mAmIndex); + else if (!pm && value >= 720) + mAmPm->setCurrentItem(mPmIndex); + } + emit valueChanged(value); +} + +/****************************************************************************** + * Called when a new selection has been made by the user in the AM/PM combo box. + * Adjust the current time value by 12 hours. + */ +void TimeEdit::slotAmPmChanged(int item) +{ + if (mAmPm) + { + int value = mSpinBox->value(); + if (item == mPmIndex && value < 720) + mSpinBox->setValue(value + 720); + else if (item != mPmIndex && value >= 720) + mSpinBox->setValue(value - 720); + } +} + +/****************************************************************************** + * Set up the AM/PM combo box to contain the specified items. + */ +void TimeEdit::setAmPmCombo(int am, int pm) +{ + if (am > 0 && mAmIndex < 0) + { + // Insert "am" + mAmIndex = 0; + mAmPm->insertItem(KGlobal::locale()->translate("am"), mAmIndex); + if (mPmIndex >= 0) + mPmIndex = 1; + mAmPm->setCurrentItem(mPmIndex >= 0 ? mPmIndex : mAmIndex); + } + else if (am == 0 && mAmIndex >= 0) + { + // Remove "am" + mAmPm->removeItem(mAmIndex); + mAmIndex = -1; + if (mPmIndex >= 0) + mPmIndex = 0; + mAmPm->setCurrentItem(mPmIndex); + } + + if (pm > 0 && mPmIndex < 0) + { + // Insert "pm" + mPmIndex = mAmIndex + 1; + mAmPm->insertItem(KGlobal::locale()->translate("pm"), mPmIndex); + if (mAmIndex < 0) + mAmPm->setCurrentItem(mPmIndex); + } + else if (pm == 0 && mPmIndex >= 0) + { + // Remove "pm" + mAmPm->removeItem(mPmIndex); + mPmIndex = -1; + mAmPm->setCurrentItem(mAmIndex); + } +} diff --git a/kalarm/lib/timeedit.h b/kalarm/lib/timeedit.h new file mode 100644 index 000000000..5888c7665 --- /dev/null +++ b/kalarm/lib/timeedit.h @@ -0,0 +1,122 @@ +/* + * timeedit.h - time-of-day edit widget, with AM/PM shown depending on locale + * Program: kalarm + * Copyright (C) 2004 by 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. + */ + +#ifndef TIMEEDIT_H +#define TIMEEDIT_H + +#include +#include + +class ComboBox; +class TimeSpinBox; + + +/** + * @short Widget to enter a time of day. + * + * The TimeEdit class provides a widget to enter a time of day in hours and minutes, + * using a 12- or 24-hour clock according to the user's locale settings. + * + * It displays a TimeSpinBox widget to enter hours and minutes. If a 12-hour clock is + * being used, it also displays a combo box to select am or pm. + * + * TimeSpinBox displays a spin box with two pairs of spin buttons, one for hours and + * one for minutes. It provides accelerated stepping using the spin buttons, when the + * shift key is held down (inherited from SpinBox2). The default shift steps are 5 + * minutes and 6 hours. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class TimeEdit : public QHBox +{ + Q_OBJECT + public: + /** Constructor. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit TimeEdit(QWidget* parent = 0, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the widget is read-only for the user. If read-only, + * the time cannot be edited and the spin buttons and am/pm combo box + * are inactive. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Returns true if the widget contains a valid value. */ + bool isValid() const; + /** Sets whether the edit value is valid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + * @param valid True to set the value valid, false to set it invalid. + */ + void setValid(bool valid); + /** Returns the entered time as a value in minutes. */ + int value() const; + /** Returns the entered time as a QTime value. */ + QTime time() const { int m = value(); return QTime(m/60, m%60); } + /** Returns true if it is possible to step the value from the highest value to the lowest value and vice versa. */ + bool wrapping() const; + /** Sets whether it is possible to step the value from the highest value to the lowest value and vice versa. + * @param on True to enable wrapping, else false. + */ + void setWrapping(bool on); + /** Returns the minimum value of the widget in minutes. */ + int minValue() const; + /** Returns the maximum value of the widget in minutes. */ + int maxValue() const; + /** Returns the maximum value of the widget as a QTime value. */ + QTime maxTime() const { int mv = maxValue(); return QTime(mv/60, mv%60); } + /** Sets the minimum value of the widget. */ + void setMinValue(int minutes); + /** Sets the maximum value of the widget. */ + void setMaxValue(int minutes); + /** Sets the maximum value of the widget. */ + void setMaxValue(const QTime& time) { setMaxValue(time.hour()*60 + time.minute()); } + public slots: + /** Sets the value of the widget. */ + virtual void setValue(int minutes); + /** Sets the value of the widget. */ + void setValue(const QTime& t) { setValue(t.hour()*60 + t.minute()); } + signals: + /** This signal is emitted every time the value of the widget changes + * (for whatever reason). + * @param minutes The new value. + */ + void valueChanged(int minutes); + + private slots: + void slotValueChanged(int); + void slotAmPmChanged(int item); + private: + void setAmPmCombo(int am, int pm); + + TimeSpinBox* mSpinBox; // always holds the 24-hour time + ComboBox* mAmPm; + int mAmIndex; // mAmPm index to "am", or -1 if none + int mPmIndex; // mAmPm index to "pm", or -1 if none + bool mReadOnly; // the widget is read only +}; + +#endif // TIMEEDIT_H diff --git a/kalarm/lib/timeperiod.cpp b/kalarm/lib/timeperiod.cpp new file mode 100644 index 000000000..22a7c3179 --- /dev/null +++ b/kalarm/lib/timeperiod.cpp @@ -0,0 +1,384 @@ +/* + * timeperiod.h - time period data entry widget + * Program: kalarm + * Copyright © 2003,2004,2007,2008 by 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 "kalarm.h" + +#include +#include + +#include +#include + +#include "combobox.h" +#include "spinbox.h" +#include "timespinbox.h" +#include "timeperiod.moc" + + +// Collect these widget labels together to ensure consistent wording and +// translations across different modules. +QString TimePeriod::i18n_minutes() { return i18n("minutes"); } +QString TimePeriod::i18n_Minutes() { return i18n("Minutes"); } +QString TimePeriod::i18n_hours_mins() { return i18n("hours/minutes"); } +QString TimePeriod::i18n_Hours_Mins() { return i18n("Hours/Minutes"); } +QString TimePeriod::i18n_days() { return i18n("days"); } +QString TimePeriod::i18n_Days() { return i18n("Days"); } +QString TimePeriod::i18n_weeks() { return i18n("weeks"); } +QString TimePeriod::i18n_Weeks() { return i18n("Weeks"); } + +static const int maxMinutes = 1000*60-1; // absolute maximum value for hours:minutes = 999H59M + +/*============================================================================= += Class TimePeriod += Contains a time unit combo box, plus a time spinbox, to select a time period. +=============================================================================*/ + +TimePeriod::TimePeriod(bool allowHourMinute, QWidget* parent, const char* name) + : QHBox(parent, name), + mMaxDays(9999), + mNoHourMinute(!allowHourMinute), + mReadOnly(false) +{ + setSpacing(KDialog::spacingHint()); + + mSpinStack = new QWidgetStack(this); + mSpinBox = new SpinBox(mSpinStack); + mSpinBox->setLineStep(1); + mSpinBox->setLineShiftStep(10); + mSpinBox->setRange(1, mMaxDays); + connect(mSpinBox, SIGNAL(valueChanged(int)), SLOT(slotDaysChanged(int))); + mSpinStack->addWidget(mSpinBox, 0); + + mTimeSpinBox = new TimeSpinBox(0, 99999, mSpinStack); + mTimeSpinBox->setRange(1, maxMinutes); // max 999H59M + connect(mTimeSpinBox, SIGNAL(valueChanged(int)), SLOT(slotTimeChanged(int))); + mSpinStack->addWidget(mTimeSpinBox, 1); + + mSpinStack->setFixedSize(mSpinBox->sizeHint().expandedTo(mTimeSpinBox->sizeHint())); + mHourMinuteRaised = mNoHourMinute; + showHourMin(!mNoHourMinute); + + mUnitsCombo = new ComboBox(false, this); + if (mNoHourMinute) + mDateOnlyOffset = 2; + else + { + mDateOnlyOffset = 0; + mUnitsCombo->insertItem(i18n_minutes()); + mUnitsCombo->insertItem(i18n_hours_mins()); + } + mUnitsCombo->insertItem(i18n_days()); + mUnitsCombo->insertItem(i18n_weeks()); + mMaxUnitShown = WEEKS; + mUnitsCombo->setFixedSize(mUnitsCombo->sizeHint()); + connect(mUnitsCombo, SIGNAL(activated(int)), SLOT(slotUnitsSelected(int))); + + setFocusProxy(mUnitsCombo); + setTabOrder(mUnitsCombo, mSpinStack); +} + +void TimePeriod::setReadOnly(bool ro) +{ + if (ro != mReadOnly) + { + mReadOnly = ro; + mSpinBox->setReadOnly(ro); + mTimeSpinBox->setReadOnly(ro); + mUnitsCombo->setReadOnly(ro); + } +} + +/****************************************************************************** +* Set whether the editor text is to be selected whenever spin buttons are +* clicked. Default is to select them. +*/ +void TimePeriod::setSelectOnStep(bool sel) +{ + mSpinBox->setSelectOnStep(sel); + mTimeSpinBox->setSelectOnStep(sel); +} + +/****************************************************************************** +* Set the input focus on the count field. +*/ +void TimePeriod::setFocusOnCount() +{ + mSpinStack->setFocus(); +} + +/****************************************************************************** +* Set the maximum values for the hours:minutes and days/weeks spinboxes. +* If 'hourmin' = 0, the hours:minutes maximum is left unchanged. +*/ +void TimePeriod::setMaximum(int hourmin, int days) +{ + int oldmins = minutes(); + if (hourmin > 0) + { + if (hourmin > maxMinutes) + hourmin = maxMinutes; + mTimeSpinBox->setRange(1, hourmin); + } + mMaxDays = (days >= 0) ? days : 0; + adjustDayWeekShown(); + setUnitRange(); + int mins = minutes(); + if (mins != oldmins) + emit valueChanged(mins); +} + +/****************************************************************************** + * Get the specified number of minutes. + * Reply = 0 if error. + */ +int TimePeriod::minutes() const +{ + int factor = 0; + switch (mUnitsCombo->currentItem() + mDateOnlyOffset) + { + case HOURS_MINUTES: + return mTimeSpinBox->value(); + case MINUTES: factor = 1; break; + case DAYS: factor = 24*60; break; + case WEEKS: factor = 7*24*60; break; + } + return mSpinBox->value() * factor; +} + +/****************************************************************************** +* Initialise the controls with a specified time period. +* The time unit combo-box is initialised to 'defaultUnits', but if 'dateOnly' +* is true, it will never be initialised to minutes or hours/minutes. +*/ +void TimePeriod::setMinutes(int mins, bool dateOnly, TimePeriod::Units defaultUnits) +{ + int oldmins = minutes(); + if (!dateOnly && mNoHourMinute) + dateOnly = true; + int item; + if (mins) + { + int count = mins; + if (mins % (24*60)) + item = (defaultUnits == MINUTES && count <= mSpinBox->maxValue()) ? MINUTES : HOURS_MINUTES; + else if (mins % (7*24*60)) + { + item = DAYS; + count = mins / (24*60); + } + else + { + item = WEEKS; + count = mins / (7*24*60); + } + if (item < mDateOnlyOffset) + item = mDateOnlyOffset; + else if (item > mMaxUnitShown) + item = mMaxUnitShown; + mUnitsCombo->setCurrentItem(item - mDateOnlyOffset); + if (item == HOURS_MINUTES) + mTimeSpinBox->setValue(count); + else + mSpinBox->setValue(count); + item = setDateOnly(mins, dateOnly, false); + } + else + { + item = defaultUnits; + if (item < mDateOnlyOffset) + item = mDateOnlyOffset; + else if (item > mMaxUnitShown) + item = mMaxUnitShown; + mUnitsCombo->setCurrentItem(item - mDateOnlyOffset); + if (dateOnly && !mDateOnlyOffset || !dateOnly && mDateOnlyOffset) + item = setDateOnly(mins, dateOnly, false); + } + showHourMin(item == HOURS_MINUTES && !mNoHourMinute); + + int newmins = minutes(); + if (newmins != oldmins) + emit valueChanged(newmins); +} + +/****************************************************************************** +* Enable/disable hours/minutes units (if hours/minutes were permitted in the +* constructor). +*/ +TimePeriod::Units TimePeriod::setDateOnly(int mins, bool dateOnly, bool signal) +{ + int oldmins = 0; + if (signal) + oldmins = minutes(); + int index = mUnitsCombo->currentItem(); + Units units = static_cast(index + mDateOnlyOffset); + if (!mNoHourMinute) + { + if (!dateOnly && mDateOnlyOffset) + { + // Change from date-only to allow hours/minutes + mUnitsCombo->insertItem(i18n_minutes(), 0); + mUnitsCombo->insertItem(i18n_hours_mins(), 1); + mDateOnlyOffset = 0; + adjustDayWeekShown(); + mUnitsCombo->setCurrentItem(index += 2); + } + else if (dateOnly && !mDateOnlyOffset) + { + // Change from allowing hours/minutes to date-only + mUnitsCombo->removeItem(0); + mUnitsCombo->removeItem(0); + mDateOnlyOffset = 2; + if (index > 2) + index -= 2; + else + index = 0; + adjustDayWeekShown(); + mUnitsCombo->setCurrentItem(index); + if (units == HOURS_MINUTES || units == MINUTES) + { + // Set units to days and round up the warning period + units = DAYS; + mUnitsCombo->setCurrentItem(DAYS - mDateOnlyOffset); + mSpinBox->setValue((mins + 1439) / 1440); + } + showHourMin(false); + } + } + + if (signal) + { + int newmins = minutes(); + if (newmins != oldmins) + emit valueChanged(newmins); + } + return units; +} + +/****************************************************************************** +* Adjust the days/weeks units shown to suit the maximum days limit. +*/ +void TimePeriod::adjustDayWeekShown() +{ + Units newMaxUnitShown = (mMaxDays >= 7) ? WEEKS : (mMaxDays || mDateOnlyOffset) ? DAYS : HOURS_MINUTES; + if (newMaxUnitShown > mMaxUnitShown) + { + if (mMaxUnitShown < DAYS) + mUnitsCombo->insertItem(i18n_days()); + if (newMaxUnitShown == WEEKS) + mUnitsCombo->insertItem(i18n_weeks()); + } + else if (newMaxUnitShown < mMaxUnitShown) + { + if (mMaxUnitShown == WEEKS) + mUnitsCombo->removeItem(WEEKS - mDateOnlyOffset); + if (newMaxUnitShown < DAYS) + mUnitsCombo->removeItem(DAYS - mDateOnlyOffset); + } + mMaxUnitShown = newMaxUnitShown; +} + +/****************************************************************************** +* Set the maximum value which may be entered into the day/week count field, +* depending on the current unit selection. +*/ +void TimePeriod::setUnitRange() +{ + int maxval; + switch (static_cast(mUnitsCombo->currentItem() + mDateOnlyOffset)) + { + case WEEKS: + maxval = mMaxDays / 7; + if (maxval) + break; + mUnitsCombo->setCurrentItem(DAYS - mDateOnlyOffset); + // fall through to DAYS + case DAYS: + maxval = mMaxDays ? mMaxDays : 1; + break; + case MINUTES: + maxval = mTimeSpinBox->maxValue(); + break; + case HOURS_MINUTES: + default: + return; + } + mSpinBox->setRange(1, maxval); +} + +/****************************************************************************** +* Called when a new item is made current in the time units combo box. +*/ +void TimePeriod::slotUnitsSelected(int index) +{ + setUnitRange(); + showHourMin(index + mDateOnlyOffset == HOURS_MINUTES); + emit valueChanged(minutes()); +} + +/****************************************************************************** +* Called when the value of the days/weeks spin box changes. +*/ +void TimePeriod::slotDaysChanged(int) +{ + if (!mHourMinuteRaised) + emit valueChanged(minutes()); +} + +/****************************************************************************** +* Called when the value of the time spin box changes. +*/ +void TimePeriod::slotTimeChanged(int value) +{ + if (mHourMinuteRaised) + emit valueChanged(value); +} + +/****************************************************************************** + * Set the currently displayed count widget. + */ +void TimePeriod::showHourMin(bool hourMinute) +{ + if (hourMinute != mHourMinuteRaised) + { + mHourMinuteRaised = hourMinute; + if (hourMinute) + { + mSpinStack->raiseWidget(mTimeSpinBox); + mSpinStack->setFocusProxy(mTimeSpinBox); + } + else + { + mSpinStack->raiseWidget(mSpinBox); + mSpinStack->setFocusProxy(mSpinBox); + } + } +} + +/****************************************************************************** + * Set separate WhatsThis texts for the count spinboxes and the units combobox. + * If the hours:minutes text is omitted, both spinboxes are set to the same + * WhatsThis text. + */ +void TimePeriod::setWhatsThis(const QString& units, const QString& dayWeek, const QString& hourMin) +{ + QWhatsThis::add(mUnitsCombo, units); + QWhatsThis::add(mSpinBox, dayWeek); + QWhatsThis::add(mTimeSpinBox, (hourMin.isNull() ? dayWeek : hourMin)); +} diff --git a/kalarm/lib/timeperiod.h b/kalarm/lib/timeperiod.h new file mode 100644 index 000000000..ef1a3b5c3 --- /dev/null +++ b/kalarm/lib/timeperiod.h @@ -0,0 +1,146 @@ +/* + * timeperiod.cpp - time period data entry widget + * Program: kalarm + * Copyright © 2003,2004,2007,2008 by 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. + */ + +#ifndef TIMEPERIOD_H +#define TIMEPERIOD_H + +#include +#include + +class QWidgetStack; +class ComboBox; +class SpinBox; +class TimeSpinBox; + + +/** + * @short Time period entry widget. + * + * The TimePeriod class provides a widget for entering a time period as a number of + * weeks, days, hours and minutes, or minutes. + * + * It displays a combo box to select the time units (weeks, days, hours and minutes, or + * minutes) alongside a spin box to enter the number of units. The type of spin box + * displayed alters according to the units selection: day, week and minute values are + * entered in a normal spin box, while hours and minutes are entered in a time spin box + * (with two pairs of spin buttons, one for hours and one for minutes). + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class TimePeriod : public QHBox +{ + Q_OBJECT + public: + /** Units for the time period. + * @li MINUTES - the time period is entered as a number of minutes. + * @li HOURS_MINUTES - the time period is entered as an hours/minutes value. + * @li DAYS - the time period is entered as a number of days. + * @li WEEKS - the time period is entered as a number of weeks. + */ + enum Units { MINUTES, HOURS_MINUTES, DAYS, WEEKS }; + + /** Constructor. + * @param allowMinute Set false to prevent hours/minutes or minutes from + * being allowed as units; only days and weeks can ever be used, + * regardless of other method calls. Set true to allow minutes, + * hours/minutes, days or weeks as units. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + TimePeriod(bool allowMinute, QWidget* parent, const char* name = 0); + /** Returns true if the widget is read only. */ + bool isReadOnly() const { return mReadOnly; } + /** Sets whether the widget is read-only for the user. If read-only, + * the time period cannot be edited and the units combo box is inactive. + * @param readOnly True to set the widget read-only, false to set it read-write. + */ + virtual void setReadOnly(bool readOnly); + /** Gets the entered time period expressed in minutes. */ + int minutes() const; + /** Initialises the time period value. + * @param minutes The value of the time period to set, expressed as a number of minutes. + * @param dateOnly True to restrict the units available in the combo box to days or weeks. + * @param defaultUnits The units to display initially in the combo box. + */ + void setMinutes(int minutes, bool dateOnly, Units defaultUnits); + /** Enables or disables minutes and hours/minutes units in the combo box. To + * disable minutes and hours/minutes, set @p dateOnly true; to enable minutes + * and hours/minutes, set @p dateOnly false. But note that minutes and + * hours/minutes cannot be enabled if it was disallowed in the constructor. + */ + void setDateOnly(bool dateOnly) { setDateOnly(minutes(), dateOnly, true); } + /** Sets the maximum values for the minutes and hours/minutes, and days/weeks + * spin boxes. + * Set @p hourmin = 0 to leave the minutes and hours/minutes maximum unchanged. + */ + void setMaximum(int hourmin, int days); + /** Sets whether the editor text is to be selected whenever spin buttons are + * clicked. The default is to select it. + */ + void setSelectOnStep(bool select); + /** Sets the input focus to the count field. */ + void setFocusOnCount(); + /** Sets separate WhatsThis texts for the count spin boxes and the units combo box. + * If @p hourMin is omitted, both spin boxes are set to the same WhatsThis text. + */ + void setWhatsThis(const QString& units, const QString& dayWeek, const QString& hourMin = QString::null); + + static QString i18n_minutes(); // text of 'minutes' units, lower case + static QString i18n_Minutes(); // text of 'Minutes' units, initial capitals + static QString i18n_hours_mins(); // text of 'hours/minutes' units, lower case + static QString i18n_Hours_Mins(); // text of 'Hours/Minutes' units, initial capitals + static QString i18n_days(); // text of 'days' units, lower case + static QString i18n_Days(); // text of 'Days' units, initial capital + static QString i18n_weeks(); // text of 'weeks' units, lower case + static QString i18n_Weeks(); // text of 'Weeks' units, initial capital + + signals: + /** This signal is emitted whenever the value held in the widget changes. + * @param minutes The current value of the time period, expressed in minutes. + */ + void valueChanged(int minutes); // value has changed + + private slots: + void slotUnitsSelected(int index); + void slotDaysChanged(int); + void slotTimeChanged(int minutes); + + private: + Units setDateOnly(int minutes, bool dateOnly, bool signal); + void setUnitRange(); + void showHourMin(bool hourMin); + void adjustDayWeekShown(); + + QWidgetStack* mSpinStack; // displays either the days/weeks or hours:minutes spinbox + SpinBox* mSpinBox; // the minutes/days/weeks value spinbox + TimeSpinBox* mTimeSpinBox; // the hours:minutes value spinbox + ComboBox* mUnitsCombo; + int mMaxDays; // maximum day count + int mDateOnlyOffset; // for mUnitsCombo: 2 if minutes & hours/minutes are disabled, else 0 + Units mMaxUnitShown; // for mUnitsCombo: maximum units shown + bool mNoHourMinute; // hours/minutes cannot be displayed, ever + bool mReadOnly; // the widget is read only + bool mHourMinuteRaised; // hours:minutes spinbox is currently displayed +}; + +#endif // TIMEPERIOD_H diff --git a/kalarm/lib/timespinbox.cpp b/kalarm/lib/timespinbox.cpp new file mode 100644 index 000000000..7a22cd230 --- /dev/null +++ b/kalarm/lib/timespinbox.cpp @@ -0,0 +1,364 @@ +/* + * timespinbox.cpp - time spinbox widget + * Program: kalarm + * Copyright © 2001-2004,2007,2008 by 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 "kalarm.h" + +#include +#include +#include + +#include "timespinbox.moc" + + +class TimeSpinBox::TimeValidator : public QValidator +{ + public: + TimeValidator(int minMin, int maxMin, QWidget* parent, const char* name = 0) + : QValidator(parent, name), + minMinute(minMin), maxMinute(maxMin), m12Hour(false), mPm(false) { } + virtual State validate(QString&, int&) const; + int minMinute, maxMinute; + bool m12Hour; + bool mPm; +}; + + +/*============================================================================= += Class TimeSpinBox += This is a spin box displaying a time in the format hh:mm, with a pair of += spin buttons for each of the hour and minute values. += It can operate in 3 modes: += 1) a time of day using the 24-hour clock. += 2) a time of day using the 12-hour clock. The value is held as 0:00 - 23:59, += but is displayed as 12:00 - 11:59. This is for use in a TimeEdit widget. += 3) a length of time, not restricted to the length of a day. +=============================================================================*/ + +/****************************************************************************** + * Construct a wrapping 00:00 - 23:59, or 12:00 - 11:59 time spin box. + */ +TimeSpinBox::TimeSpinBox(bool use24hour, QWidget* parent, const char* name) + : SpinBox2(0, 1439, 1, 60, parent, name), + mMinimumValue(0), + m12Hour(!use24hour), + mPm(false), + mInvalid(false), + mEnteredSetValue(false) +{ + mValidator = new TimeValidator(0, 1439, this, "TimeSpinBox validator"); + mValidator->m12Hour = m12Hour; + setValidator(mValidator); + setWrapping(true); + setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language + setShiftSteps(5, 360); // shift-left button increments 5 min / 6 hours + setSelectOnStep(false); + setAlignment(Qt::AlignHCenter); + connect(this, SIGNAL(valueChanged(int)), SLOT(slotValueChanged(int))); +} + +/****************************************************************************** + * Construct a non-wrapping time spin box. + */ +TimeSpinBox::TimeSpinBox(int minMinute, int maxMinute, QWidget* parent, const char* name) + : SpinBox2(minMinute, maxMinute, 1, 60, parent, name), + mMinimumValue(minMinute), + m12Hour(false), + mInvalid(false), + mEnteredSetValue(false) +{ + mValidator = new TimeValidator(minMinute, maxMinute, this, "TimeSpinBox validator"); + setValidator(mValidator); + setReverseWithLayout(false); // keep buttons the same way round even if right-to-left language + setShiftSteps(5, 300); // shift-left button increments 5 min / 5 hours + setSelectOnStep(false); + setAlignment(QApplication::reverseLayout() ? Qt::AlignLeft : Qt::AlignRight); +} + +QString TimeSpinBox::shiftWhatsThis() +{ + return i18n("Press the Shift key while clicking the spin buttons to adjust the time by a larger step (6 hours / 5 minutes)."); +} + +QTime TimeSpinBox::time() const +{ + return QTime(value() / 60, value() % 60); +} + +QString TimeSpinBox::mapValueToText(int v) +{ + if (m12Hour) + { + if (v < 60) + v += 720; // convert 0:nn to 12:nn + else if (v >= 780) + v -= 720; // convert 13 - 23 hours to 1 - 11 + } + QString s; + s.sprintf((wrapping() ? "%02d:%02d" : "%d:%02d"), v/60, v%60); + return s; +} + +/****************************************************************************** + * Convert the user-entered text to a value in minutes. + * The allowed formats are: + * [hour]:[minute], where minute must be non-blank, or + * hhmm, 4 digits, where hour < 24. + */ +int TimeSpinBox::mapTextToValue(bool* ok) +{ + QString text = cleanText(); + int colon = text.find(':'); + if (colon >= 0) + { + // [h]:m format for any time value + QString hour = text.left(colon).stripWhiteSpace(); + QString minute = text.mid(colon + 1).stripWhiteSpace(); + if (!minute.isEmpty()) + { + bool okmin; + bool okhour = true; + int m = minute.toUInt(&okmin); + int h = 0; + if (!hour.isEmpty()) + h = hour.toUInt(&okhour); + if (okhour && okmin && m < 60) + { + if (m12Hour) + { + if (h == 0 || h > 12) + h = 100; // error + else if (h == 12) + h = 0; // convert 12:nn to 0:nn + if (mPm) + h += 12; // convert to PM + } + int t = h * 60 + m; + if (t >= mMinimumValue && t <= maxValue()) + { + if (ok) + *ok = true; + return t; + } + } + } + } + else if (text.length() == 4) + { + // hhmm format for time of day + bool okn; + int mins = text.toUInt(&okn); + if (okn) + { + int m = mins % 100; + int h = mins / 100; + if (m12Hour) + { + if (h == 0 || h > 12) + h = 100; // error + else if (h == 12) + h = 0; // convert 12:nn to 0:nn + if (mPm) + h += 12; // convert to PM + } + int t = h * 60 + m; + if (h < 24 && m < 60 && t >= mMinimumValue && t <= maxValue()) + { + if (ok) + *ok = true; + return t; + } + } + + } + if (ok) + *ok = false; + return 0; +} + +/****************************************************************************** + * Set the spin box as valid or invalid. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ +void TimeSpinBox::setValid(bool valid) +{ + if (valid && mInvalid) + { + mInvalid = false; + if (value() < mMinimumValue) + SpinBox2::setValue(mMinimumValue); + setSpecialValueText(QString()); + SpinBox2::setMinValue(mMinimumValue); + } + else if (!valid && !mInvalid) + { + mInvalid = true; + SpinBox2::setMinValue(mMinimumValue - 1); + setSpecialValueText(QString::fromLatin1("**:**")); + SpinBox2::setValue(mMinimumValue - 1); + } +} + +/****************************************************************************** +* Set the spin box's minimum value. +*/ +void TimeSpinBox::setMinValue(int minutes) +{ + mMinimumValue = minutes; + SpinBox2::setMinValue(mMinimumValue - (mInvalid ? 1 : 0)); +} + +/****************************************************************************** + * Set the spin box's value. + */ +void TimeSpinBox::setValue(int minutes) +{ + if (!mEnteredSetValue) + { + mEnteredSetValue = true; + mPm = (minutes >= 720); + if (minutes > maxValue()) + setValid(false); + else + { + if (mInvalid) + { + mInvalid = false; + setSpecialValueText(QString()); + SpinBox2::setMinValue(mMinimumValue); + } + SpinBox2::setValue(minutes); + mEnteredSetValue = false; + } + } +} + +/****************************************************************************** + * Step the spin box value. + * If it was invalid, set it valid and set the value to the minimum. + */ +void TimeSpinBox::stepUp() +{ + if (mInvalid) + setValid(true); + else + SpinBox2::stepUp(); +} + +void TimeSpinBox::stepDown() +{ + if (mInvalid) + setValid(true); + else + SpinBox2::stepDown(); +} + +bool TimeSpinBox::isValid() const +{ + return value() >= mMinimumValue; +} + +void TimeSpinBox::slotValueChanged(int value) +{ + mPm = mValidator->mPm = (value >= 720); +} + +QSize TimeSpinBox::sizeHint() const +{ + QSize sz = SpinBox2::sizeHint(); + QFontMetrics fm(font()); + return QSize(sz.width() + fm.width(":"), sz.height()); +} + +QSize TimeSpinBox::minimumSizeHint() const +{ + QSize sz = SpinBox2::minimumSizeHint(); + QFontMetrics fm(font()); + return QSize(sz.width() + fm.width(":"), sz.height()); +} + +/****************************************************************************** + * Validate the time spin box input. + * The entered time must either be 4 digits, or it must contain a colon, but + * hours may be blank. + */ +QValidator::State TimeSpinBox::TimeValidator::validate(QString& text, int& /*cursorPos*/) const +{ + QString cleanText = text.stripWhiteSpace(); + if (cleanText.isEmpty()) + return QValidator::Intermediate; + QValidator::State state = QValidator::Acceptable; + QString hour; + bool ok; + int hr = 0; + int mn = 0; + int colon = cleanText.find(':'); + if (colon >= 0) + { + QString minute = cleanText.mid(colon + 1); + if (minute.isEmpty()) + state = QValidator::Intermediate; + else if ((mn = minute.toUInt(&ok)) >= 60 || !ok) + return QValidator::Invalid; + + hour = cleanText.left(colon); + } + else if (maxMinute >= 1440) + { + // The hhmm form of entry is only allowed for time-of-day, i.e. <= 2359 + hour = cleanText; + state = QValidator::Intermediate; + } + else + { + if (cleanText.length() > 4) + return QValidator::Invalid; + if (cleanText.length() < 4) + state = QValidator::Intermediate; + hour = cleanText.left(2); + QString minute = cleanText.mid(2); + if (!minute.isEmpty() + && ((mn = minute.toUInt(&ok)) >= 60 || !ok)) + return QValidator::Invalid; + } + + if (!hour.isEmpty()) + { + hr = hour.toUInt(&ok); + if (m12Hour) + { + if (hr == 0 || hr > 12) + hr = 100; // error; + else if (hr == 12) + hr = 0; // convert 12:nn to 0:nn + if (mPm) + hr += 12; // convert to PM + } + if (!ok || hr > maxMinute/60) + return QValidator::Invalid; + } + if (state == QValidator::Acceptable) + { + int t = hr * 60 + mn; + if (t < minMinute || t > maxMinute) + return QValidator::Invalid; + } + return state; +} diff --git a/kalarm/lib/timespinbox.h b/kalarm/lib/timespinbox.h new file mode 100644 index 000000000..275fdf309 --- /dev/null +++ b/kalarm/lib/timespinbox.h @@ -0,0 +1,127 @@ +/* + * timespinbox.h - time spinbox widget + * Program: kalarm + * Copyright © 2001-2008 by 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. + */ + +#ifndef TIMESPINBOX_H +#define TIMESPINBOX_H + +#include +#include "spinbox2.h" + + +/** + * @short Hours/minutes time entry widget. + * + * The TimeSpinBox class provides a widget to enter a time consisting of an hours/minutes + * value. It can hold a time in any of 3 modes: a time of day using the 24-hour clock; a + * time of day using the 12-hour clock; or a length of time not restricted to 24 hours. + * + * Derived from SpinBox2, it displays a spin box with two pairs of spin buttons, one + * for hours and one for minutes. It provides accelerated stepping using the spin + * buttons, when the shift key is held down (inherited from SpinBox2). The default + * shift steps are 5 minutes and 6 hours. + * + * The widget may be set as read-only. This has the same effect as disabling it, except + * that its appearance is unchanged. + * + * @author David Jarvie + */ +class TimeSpinBox : public SpinBox2 +{ + Q_OBJECT + public: + /** Constructor for a wrapping time spin box which can be used to enter a time of day. + * @param use24hour True for entry of 24-hour clock times (range 00:00 to 23:59). + * False for entry of 12-hour clock times (range 12:00 to 11:59). + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + explicit TimeSpinBox(bool use24hour, QWidget* parent = 0, const char* name = 0); + /** Constructor for a non-wrapping time spin box which can be used to enter a length of time. + * @param minMinute The minimum value which the spin box can hold, in minutes. + * @param maxMinute The maximum value which the spin box can hold, in minutes. + * @param parent The parent object of this widget. + * @param name The name of this widget. + */ + TimeSpinBox(int minMinute, int maxMinute, QWidget* parent = 0, const char* name = 0); + /** Returns true if the spin box holds a valid value. + * An invalid value is displayed as asterisks. + */ + bool isValid() const; + /** Sets the spin box as holding a valid or invalid value. + * If newly invalid, the value is displayed as asterisks. + * If newly valid, the value is set to the minimum value. + */ + void setValid(bool); + /** Returns the current value held in the spin box. + * If an invalid value is displayed, returns a value lower than the minimum value. + */ + QTime time() const; + /** Sets the maximum value which can be held in the spin box. + * @param minutes The maximum value expressed in minutes. + */ + virtual void setMinValue(int minutes); + /** Sets the maximum value which can be held in the spin box. + * @param minutes The maximum value expressed in minutes. + */ + virtual void setMaxValue(int minutes) { SpinBox2::setMaxValue(minutes); } + /** Sets the maximum value which can be held in the spin box. */ + void setMaxValue(const QTime& t) { SpinBox2::setMaxValue(t.hour()*60 + t.minute()); } + /** Returns the maximum value which can be held in the spin box. */ + QTime maxTime() const { int mv = maxValue(); return QTime(mv/60, mv%60); } + /** Returns a text describing use of the shift key as an accelerator for + * the spin buttons, designed for incorporation into WhatsThis texts. + */ + static QString shiftWhatsThis(); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + public slots: + /** Sets the value of the spin box. + * @param minutes The new value of the spin box, expressed in minutes. + */ + virtual void setValue(int minutes); + /** Sets the value of the spin box. */ + void setValue(const QTime& t) { setValue(t.hour()*60 + t.minute()); } + /** Increments the spin box value. + * If the value was previously invalid, the spin box is set to the minimum value. + */ + virtual void stepUp(); + /** Decrements the spin box value. + * If the value was previously invalid, the spin box is set to the minimum value. + */ + virtual void stepDown(); + + protected: + virtual QString mapValueToText(int v); + virtual int mapTextToValue(bool* ok); + private slots: + void slotValueChanged(int value); + private: + class TimeValidator; + TimeValidator* mValidator; + int mMinimumValue; // real minimum value, excluding special value for "**:**" + bool m12Hour; // use 12-hour clock + bool mPm; // use PM for manually entered values (with 12-hour clock) + bool mInvalid; // value is currently invalid (asterisks) + bool mEnteredSetValue; // to prevent infinite recursion in setValue() +}; + +#endif // TIMESPINBOX_H diff --git a/kalarm/main.cpp b/kalarm/main.cpp new file mode 100644 index 000000000..0e1906ce2 --- /dev/null +++ b/kalarm/main.cpp @@ -0,0 +1,131 @@ +/* + * main.cpp + * Program: kalarm + * Copyright © 2001-2007 by 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 "kalarm.h" + +#include + +#include +#include +#include +#include + +#include "kalarmapp.h" + +#define PROGRAM_NAME "kalarm" + +static KCmdLineOptions options[] = +{ + { "a", 0, 0 }, + { "ack-confirm", I18N_NOOP("Prompt for confirmation when alarm is acknowledged"), 0 }, + { "A", 0, 0 }, + { "attach ", I18N_NOOP("Attach file to email (repeat as needed)"), 0 }, + { "auto-close", I18N_NOOP("Auto-close alarm window after --late-cancel period"), 0 }, + { "bcc", I18N_NOOP("Blind copy email to self"), 0 }, + { "b", 0, 0 }, + { "beep", I18N_NOOP("Beep when message is displayed"), 0 }, + { "colour", 0, 0 }, + { "c", 0, 0 }, + { "color ", I18N_NOOP("Message background color (name or hex 0xRRGGBB)"), 0 }, + { "colourfg", 0, 0 }, + { "C", 0, 0 }, + { "colorfg ", I18N_NOOP("Message foreground color (name or hex 0xRRGGBB)"), 0 }, + { "calendarURL ", I18N_NOOP("URL of calendar file"), 0 }, + { "cancelEvent ", I18N_NOOP("Cancel alarm with the specified event ID"), 0 }, + { "d", 0, 0 }, + { "disable", I18N_NOOP("Disable the alarm"), 0 }, + { "e", 0, 0 }, + { "!exec ", I18N_NOOP("Execute a shell command line"), 0 }, + { "edit ", I18N_NOOP("Display the alarm edit dialog to edit the specified alarm"), 0 }, + { "n", 0, 0 }, + { "edit-new", I18N_NOOP("Display the alarm edit dialog to edit a new alarm"), 0 }, + { "edit-new-preset ", I18N_NOOP("Display the alarm edit dialog, preset with a template"), 0 }, + { "f", 0, 0 }, + { "file ", I18N_NOOP("File to display"), 0 }, + { "F", 0, 0 }, + { "from-id ", I18N_NOOP("KMail identity to use as sender of email"), 0 }, + { "handleEvent ", I18N_NOOP("Trigger or cancel alarm with the specified event ID"), 0 }, + { "i", 0, 0 }, + { "interval ", I18N_NOOP("Interval between alarm repetitions"), 0 }, + { "k", 0, 0 }, + { "korganizer", I18N_NOOP("Show alarm as an event in KOrganizer"), 0 }, + { "l", 0, 0 }, + { "late-cancel ", I18N_NOOP("Cancel alarm if more than 'period' late when triggered"), "1" }, + { "L", 0, 0 }, + { "login", I18N_NOOP("Repeat alarm at every login"), 0 }, + { "m", 0, 0 }, + { "mail
", I18N_NOOP("Send an email to the given address (repeat as needed)"), 0 }, + { "p", 0, 0 }, + { "play ", I18N_NOOP("Audio file to play once"), 0 }, +#ifndef WITHOUT_ARTS + { "P", 0, 0 }, + { "play-repeat ", I18N_NOOP("Audio file to play repeatedly"), 0 }, +#endif + { "recurrence ", I18N_NOOP("Specify alarm recurrence using iCalendar syntax"), 0 }, + { "R", 0, 0 }, + { "reminder ", I18N_NOOP("Display reminder in advance of alarm"), 0 }, + { "reminder-once ", I18N_NOOP("Display reminder once, before first alarm recurrence"), 0 }, + { "r", 0, 0 }, + { "repeat ", I18N_NOOP("Number of times to repeat alarm (including initial occasion)"), 0 }, + { "reset", I18N_NOOP("Reset the alarm scheduling daemon"), 0 }, + { "s", 0, 0 }, + { "speak", I18N_NOOP("Speak the message when it is displayed"), 0 }, + { "stop", I18N_NOOP("Stop the alarm scheduling daemon"), 0 }, + { "S", 0, 0 }, + { "subject", I18N_NOOP("Email subject line"), 0 }, + { "t", 0, 0 }, + { "time