summaryrefslogtreecommitdiffstats
path: root/kalarm
diff options
context:
space:
mode:
Diffstat (limited to 'kalarm')
-rw-r--r--kalarm/ACKNOWLEDGEMENTS26
-rw-r--r--kalarm/AUTHORS11
-rw-r--r--kalarm/BUGS17
-rw-r--r--kalarm/COPYING341
-rw-r--r--kalarm/Changelog657
-rw-r--r--kalarm/INSTALL40
-rw-r--r--kalarm/Makefile.am76
-rw-r--r--kalarm/README60
-rw-r--r--kalarm/alarmcalendar.cpp908
-rw-r--r--kalarm/alarmcalendar.h114
-rw-r--r--kalarm/alarmevent.cpp3488
-rw-r--r--kalarm/alarmevent.h500
-rw-r--r--kalarm/alarmlistview.cpp711
-rw-r--r--kalarm/alarmlistview.h133
-rw-r--r--kalarm/alarmtext.cpp288
-rw-r--r--kalarm/alarmtext.h75
-rw-r--r--kalarm/alarmtimewidget.cpp556
-rw-r--r--kalarm/alarmtimewidget.h94
-rw-r--r--kalarm/birthdaydlg.cpp427
-rw-r--r--kalarm/birthdaydlg.h103
-rw-r--r--kalarm/calendarcompat.cpp150
-rw-r--r--kalarm/calendarcompat.h40
-rw-r--r--kalarm/configure.in.bot16
-rw-r--r--kalarm/configure.in.in2
-rw-r--r--kalarm/daemon.cpp774
-rw-r--r--kalarm/daemon.h134
-rw-r--r--kalarm/dcophandler.cpp768
-rw-r--r--kalarm/dcophandler.h107
-rw-r--r--kalarm/deferdlg.cpp177
-rw-r--r--kalarm/deferdlg.h58
-rw-r--r--kalarm/editdlg.cpp2043
-rw-r--r--kalarm/editdlg.h260
-rw-r--r--kalarm/editdlgprivate.h47
-rw-r--r--kalarm/emailidcombo.cpp62
-rw-r--r--kalarm/emailidcombo.h44
-rw-r--r--kalarm/eventlistviewbase.cpp466
-rw-r--r--kalarm/eventlistviewbase.h132
-rw-r--r--kalarm/find.cpp394
-rw-r--r--kalarm/find.h75
-rw-r--r--kalarm/fontcolour.cpp265
-rw-r--r--kalarm/fontcolour.h78
-rw-r--r--kalarm/fontcolourbutton.cpp161
-rw-r--r--kalarm/fontcolourbutton.h91
-rw-r--r--kalarm/functions.cpp1099
-rw-r--r--kalarm/functions.h130
-rw-r--r--kalarm/hi16-app-kalarm.pngbin0 -> 1036 bytes
-rw-r--r--kalarm/hi32-app-kalarm.pngbin0 -> 2521 bytes
-rw-r--r--kalarm/hi48-app-kalarm.pngbin0 -> 4300 bytes
-rw-r--r--kalarm/kalarm.desktop75
-rw-r--r--kalarm/kalarm.h39
-rw-r--r--kalarm/kalarm.tray.desktop74
-rw-r--r--kalarm/kalarmapp.cpp2187
-rw-r--r--kalarm/kalarmapp.h189
-rw-r--r--kalarm/kalarmd/Makefile.am28
-rw-r--r--kalarm/kalarmd/adapp.cpp71
-rw-r--r--kalarm/kalarmd/adapp.h42
-rw-r--r--kalarm/kalarmd/adcalendar.cpp253
-rw-r--r--kalarm/kalarmd/adcalendar.h120
-rw-r--r--kalarm/kalarmd/adconfigdata.cpp146
-rw-r--r--kalarm/kalarmd/adconfigdata.h41
-rw-r--r--kalarm/kalarmd/admain.cpp59
-rw-r--r--kalarm/kalarmd/alarmdaemon.cpp614
-rw-r--r--kalarm/kalarmd/alarmdaemon.h79
-rw-r--r--kalarm/kalarmd/alarmdaemoniface.h45
-rw-r--r--kalarm/kalarmd/alarmguiiface.h71
-rw-r--r--kalarm/kalarmd/clientinfo.cpp110
-rw-r--r--kalarm/kalarmd/clientinfo.h72
-rw-r--r--kalarm/kalarmd/kalarmd.autostart.desktop103
-rw-r--r--kalarm/kalarmd/kalarmd.desktop55
-rw-r--r--kalarm/kalarmd/kalarmd.h40
-rw-r--r--kalarm/kalarmiface.h355
-rw-r--r--kalarm/kalarmui.rc67
-rw-r--r--kalarm/kamail.cpp1096
-rw-r--r--kalarm/kamail.h65
-rw-r--r--kalarm/karecurrence.cpp876
-rw-r--r--kalarm/karecurrence.h89
-rw-r--r--kalarm/latecancel.cpp161
-rw-r--r--kalarm/latecancel.h69
-rw-r--r--kalarm/lib/Makefile.am22
-rw-r--r--kalarm/lib/buttongroup.cpp70
-rw-r--r--kalarm/lib/buttongroup.h90
-rw-r--r--kalarm/lib/checkbox.cpp133
-rw-r--r--kalarm/lib/checkbox.h88
-rw-r--r--kalarm/lib/colourcombo.cpp239
-rw-r--r--kalarm/lib/colourcombo.h102
-rw-r--r--kalarm/lib/colourlist.cpp43
-rw-r--r--kalarm/lib/colourlist.h110
-rw-r--r--kalarm/lib/combobox.cpp78
-rw-r--r--kalarm/lib/combobox.h69
-rw-r--r--kalarm/lib/dateedit.cpp122
-rw-r--r--kalarm/lib/dateedit.h90
-rw-r--r--kalarm/lib/datetime.cpp80
-rw-r--r--kalarm/lib/datetime.h241
-rw-r--r--kalarm/lib/label.cpp118
-rw-r--r--kalarm/lib/label.h96
-rw-r--r--kalarm/lib/lineedit.cpp200
-rw-r--r--kalarm/lib/lineedit.h94
-rw-r--r--kalarm/lib/messagebox.cpp178
-rw-r--r--kalarm/lib/messagebox.h125
-rw-r--r--kalarm/lib/pushbutton.cpp102
-rw-r--r--kalarm/lib/pushbutton.h77
-rw-r--r--kalarm/lib/radiobutton.cpp134
-rw-r--r--kalarm/lib/radiobutton.h88
-rw-r--r--kalarm/lib/shellprocess.cpp208
-rw-r--r--kalarm/lib/shellprocess.h138
-rw-r--r--kalarm/lib/slider.cpp85
-rw-r--r--kalarm/lib/slider.h80
-rw-r--r--kalarm/lib/spinbox.cpp476
-rw-r--r--kalarm/lib/spinbox.h156
-rw-r--r--kalarm/lib/spinbox2.cpp511
-rw-r--r--kalarm/lib/spinbox2.h317
-rw-r--r--kalarm/lib/spinbox2private.h92
-rw-r--r--kalarm/lib/synchtimer.cpp233
-rw-r--r--kalarm/lib/synchtimer.h198
-rw-r--r--kalarm/lib/timeedit.cpp207
-rw-r--r--kalarm/lib/timeedit.h122
-rw-r--r--kalarm/lib/timeperiod.cpp384
-rw-r--r--kalarm/lib/timeperiod.h146
-rw-r--r--kalarm/lib/timespinbox.cpp364
-rw-r--r--kalarm/lib/timespinbox.h127
-rw-r--r--kalarm/main.cpp131
-rw-r--r--kalarm/mainwindow.cpp1424
-rw-r--r--kalarm/mainwindow.h182
-rw-r--r--kalarm/mainwindowbase.cpp50
-rw-r--r--kalarm/mainwindowbase.h51
-rw-r--r--kalarm/messagewin.cpp1727
-rw-r--r--kalarm/messagewin.h165
-rw-r--r--kalarm/pickfileradio.cpp182
-rw-r--r--kalarm/pickfileradio.h120
-rw-r--r--kalarm/pixmaps/Makefile.am3
-rw-r--r--kalarm/pixmaps/cr16-action-file.pngbin0 -> 1080 bytes
-rw-r--r--kalarm/pixmaps/cr16-action-kalarm.pngbin0 -> 865 bytes
-rw-r--r--kalarm/pixmaps/cr16-action-message.pngbin0 -> 221 bytes
-rw-r--r--kalarm/pixmaps/cr16-action-new_from_template.pngbin0 -> 705 bytes
-rw-r--r--kalarm/pixmaps/cr16-action-playsound.pngbin0 -> 750 bytes
-rw-r--r--kalarm/pixmaps/cr22-action-kalarm.pngbin0 -> 1462 bytes
-rw-r--r--kalarm/pixmaps/cr22-action-kalarm_disabled.pngbin0 -> 1396 bytes
-rw-r--r--kalarm/pixmaps/cr22-action-new_from_template.pngbin0 -> 843 bytes
-rw-r--r--kalarm/prefdlg.cpp1363
-rw-r--r--kalarm/prefdlg.h270
-rw-r--r--kalarm/preferences.cpp705
-rw-r--r--kalarm/preferences.h235
-rw-r--r--kalarm/recurrenceedit.cpp1639
-rw-r--r--kalarm/recurrenceedit.h191
-rw-r--r--kalarm/recurrenceeditprivate.h196
-rw-r--r--kalarm/reminder.cpp159
-rw-r--r--kalarm/reminder.h60
-rw-r--r--kalarm/repetition.cpp364
-rw-r--r--kalarm/repetition.h99
-rw-r--r--kalarm/sounddlg.cpp472
-rw-r--r--kalarm/sounddlg.h97
-rw-r--r--kalarm/soundpicker.cpp292
-rw-r--r--kalarm/soundpicker.h135
-rw-r--r--kalarm/specialactions.cpp192
-rw-r--r--kalarm/specialactions.h96
-rw-r--r--kalarm/startdaytimer.cpp49
-rw-r--r--kalarm/startdaytimer.h66
-rw-r--r--kalarm/templatedlg.cpp241
-rw-r--r--kalarm/templatedlg.h62
-rw-r--r--kalarm/templatelistview.cpp131
-rw-r--r--kalarm/templatelistview.h88
-rw-r--r--kalarm/templatemenuaction.cpp84
-rw-r--r--kalarm/templatemenuaction.h46
-rw-r--r--kalarm/templatepickdlg.cpp87
-rw-r--r--kalarm/templatepickdlg.h43
-rw-r--r--kalarm/timeselector.cpp159
-rw-r--r--kalarm/timeselector.h61
-rw-r--r--kalarm/traywindow.cpp361
-rw-r--r--kalarm/traywindow.h71
-rw-r--r--kalarm/undo.cpp1127
-rw-r--r--kalarm/undo.h93
-rw-r--r--kalarm/uninstall.desktop2
172 files changed, 43493 insertions, 0 deletions
diff --git a/kalarm/ACKNOWLEDGEMENTS b/kalarm/ACKNOWLEDGEMENTS
new file mode 100644
index 00000000..0131c5db
--- /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 00000000..5f36e497
--- /dev/null
+++ b/kalarm/AUTHORS
@@ -0,0 +1,11 @@
+KAlarm author:
+David Jarvie <software@astrojar.org.uk>
+
+Alarm daemon author:
+David Jarvie <software@astrojar.org.uk>
+
+Original KOrganizer alarm daemon author:
+Preston Brown <pbrown@kde.org>
+
+KOrganizer alarm daemon maintainer:
+Cornelius Schumacher <schumacher@kde.org> \ No newline at end of file
diff --git a/kalarm/BUGS b/kalarm/BUGS
new file mode 100644
index 00000000..ff0c3839
--- /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 00000000..2e3f0efa
--- /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.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) 19yy <name of author>
+
+ 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.
+
+ <signature of Ty Coon>, 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 00000000..6c51c621
--- /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 00000000..34cab87a
--- /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 00000000..6d7c5658
--- /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 00000000..e52cf40b
--- /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 <software@astrojar.org.uk>.
diff --git a/kalarm/alarmcalendar.cpp b/kalarm/alarmcalendar.cpp
new file mode 100644
index 00000000..85c4f7bc
--- /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 <djarvie@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 "kalarm.h"
+#include <unistd.h>
+#include <time.h>
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qregexp.h>
+#include <qtimer.h>
+
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+#include <kstaticdeleter.h>
+#include <kconfig.h>
+#include <kaboutdata.h>
+#include <kio/netaccess.h>
+#include <kfileitem.h>
+#include <ktempfile.h>
+#include <kfiledialog.h>
+#include <dcopclient.h>
+#include <kdebug.h>
+
+extern "C" {
+#include <libical/ical.h>
+}
+
+#include <libkcal/vcaldrag.h>
+#include <libkcal/vcalformat.h>
+#include <libkcal/icalformat.h>
+
+#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<AlarmCalendar> 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 00000000..87abc2d1
--- /dev/null
+++ b/kalarm/alarmcalendar.h
@@ -0,0 +1,114 @@
+/*
+ * alarmcalendar.h - KAlarm calendar file access
+ * Program: kalarm
+ * Copyright © 2001-2006 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.
+ */
+
+#ifndef ALARMCALENDAR_H
+#define ALARMCALENDAR_H
+
+#include <qobject.h>
+#include <kurl.h>
+#include <libkcal/calendarlocal.h>
+#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 00000000..28b0a55a
--- /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 <djarvie@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 "kalarm.h"
+
+#include <stdlib.h>
+#include <time.h>
+#include <ctype.h>
+#include <qcolor.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#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<KAAlarm::SubType, AlarmData> 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<int>(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<int>(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<int>(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<int>(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<int>(list[0].toUInt());
+ int count = static_cast<int>(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<int>(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<int>(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="<<alarm.text()<<", time="<<alarm.time().toString()<<", valid time="<<alarm.time().isValid()<<endl;
+}
+
+/******************************************************************************
+ * Initialise the KAEvent with the specified parameters.
+ */
+void KAEvent::set(const QDateTime& dateTime, const QString& text, const QColor& bg, const QColor& fg,
+ const QFont& font, Action action, int lateCancel, int flags)
+{
+ clearRecur();
+ mStartDateTime.set(dateTime, flags & ANY_TIME);
+ mNextMainDateTime = mStartDateTime;
+ switch (action)
+ {
+ case MESSAGE:
+ case FILE:
+ case COMMAND:
+ case EMAIL:
+ mActionType = (KAAlarmEventBase::Type)action;
+ break;
+ default:
+ mActionType = T_MESSAGE;
+ break;
+ }
+ mText = (mActionType == T_COMMAND) ? text.stripWhiteSpace() : text;
+ mEventID = QString::null;
+ mTemplateName = QString::null;
+ mPreAction = QString::null;
+ mPostAction = QString::null;
+ mAudioFile = "";
+ mSoundVolume = -1;
+ mFadeVolume = -1;
+ mTemplateAfterTime = -1;
+ mFadeSeconds = 0;
+ mBgColour = bg;
+ mFgColour = fg;
+ mFont = font;
+ mAlarmCount = 1;
+ mLateCancel = lateCancel; // do this before setting flags
+ mDeferral = NO_DEFERRAL; // do this before setting flags
+
+ KAAlarmEventBase::set(flags & ~READ_ONLY_FLAGS);
+ mStartDateTime.setDateOnly(flags & ANY_TIME);
+ set_deferral((flags & DEFERRAL) ? NORMAL_DEFERRAL : NO_DEFERRAL);
+ mCommandXterm = flags & EXEC_IN_XTERM;
+ mCopyToKOrganizer = flags & COPY_KORGANIZER;
+ mEnabled = !(flags & DISABLED);
+
+ mKMailSerialNumber = 0;
+ mReminderMinutes = 0;
+ mArchiveReminderMinutes = 0;
+ mDeferDefaultMinutes = 0;
+ mArchiveRepeatAtLogin = false;
+ mReminderOnceOnly = false;
+ mDisplaying = false;
+ mMainExpired = false;
+ mArchive = false;
+ mUpdated = false;
+}
+
+void KAEvent::setLogFile(const QString& logfile)
+{
+ mLogFile = logfile;
+ if (!logfile.isEmpty())
+ mCommandXterm = false;
+}
+
+void KAEvent::setEmail(uint from, const EmailAddressList& addresses, const QString& subject, const QStringList& attachments)
+{
+ mEmailFromIdentity = from;
+ mEmailAddresses = addresses;
+ mEmailSubject = subject;
+ mEmailAttachments = attachments;
+}
+
+void KAEvent::setAudioFile(const QString& filename, float volume, float fadeVolume, int fadeSeconds)
+{
+ mAudioFile = filename;
+ mSoundVolume = filename.isEmpty() ? -1 : volume;
+ if (mSoundVolume >= 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<KAAlarm::SubType>((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("<<event.id()<<", "<<(alarmType==KAAlarm::MAIN_ALARM?"MAIN":alarmType==KAAlarm::REMINDER_ALARM?"REMINDER":alarmType==KAAlarm::DEFERRED_REMINDER_ALARM?"REMINDER_DEFERRAL":alarmType==KAAlarm::DEFERRED_ALARM?"DEFERRAL":"LOGIN")<<"): time="<<repeatAtLoginTime.toString()<<endl;
+ KAAlarm al = event.alarm(alarmType);
+ if (al.valid())
+ {
+ *this = event;
+ setUid(DISPLAYING);
+ mDisplaying = true;
+ mDisplayingTime = (alarmType == KAAlarm::AT_LOGIN_ALARM) ? repeatAtLoginTime : al.dateTime();
+ switch (al.type())
+ {
+ case KAAlarm::AT_LOGIN__ALARM: mDisplayingFlags = REPEAT_AT_LOGIN; break;
+ case KAAlarm::REMINDER__ALARM: mDisplayingFlags = REMINDER; break;
+ case KAAlarm::DEFERRED_REMINDER_TIME__ALARM: mDisplayingFlags = REMINDER | TIME_DEFERRAL; break;
+ case KAAlarm::DEFERRED_REMINDER_DATE__ALARM: mDisplayingFlags = REMINDER | DATE_DEFERRAL; break;
+ case KAAlarm::DEFERRED_TIME__ALARM: mDisplayingFlags = TIME_DEFERRAL; break;
+ case KAAlarm::DEFERRED_DATE__ALARM: mDisplayingFlags = DATE_DEFERRAL; break;
+ default: mDisplayingFlags = 0; break;
+ }
+ ++mAlarmCount;
+ mUpdated = true;
+ return true;
+ }
+ }
+ return false;
+}
+
+/******************************************************************************
+ * Return the original alarm which the displaying alarm refers to.
+ */
+KAAlarm KAEvent::convertDisplayingAlarm() const
+{
+ KAAlarm al;
+ if (mDisplaying)
+ {
+ al = alarm(KAAlarm::DISPLAYING_ALARM);
+ if (mDisplayingFlags & REPEAT_AT_LOGIN)
+ {
+ al.mRepeatAtLogin = true;
+ al.mType = KAAlarm::AT_LOGIN__ALARM;
+ }
+ else if (mDisplayingFlags & DEFERRAL)
+ {
+ al.mDeferred = true;
+ al.mType = (mDisplayingFlags == (REMINDER | DATE_DEFERRAL)) ? KAAlarm::DEFERRED_REMINDER_DATE__ALARM
+ : (mDisplayingFlags == (REMINDER | TIME_DEFERRAL)) ? KAAlarm::DEFERRED_REMINDER_TIME__ALARM
+ : (mDisplayingFlags == DATE_DEFERRAL) ? KAAlarm::DEFERRED_DATE__ALARM
+ : KAAlarm::DEFERRED_TIME__ALARM;
+ }
+ else if (mDisplayingFlags & REMINDER)
+ al.mType = KAAlarm::REMINDER__ALARM;
+ else
+ al.mType = KAAlarm::MAIN__ALARM;
+ }
+ return al;
+}
+
+/******************************************************************************
+ * Reinstate the original event from the 'displaying' event.
+ */
+void KAEvent::reinstateFromDisplaying(const KAEvent& dispEvent)
+{
+ if (dispEvent.mDisplaying)
+ {
+ *this = dispEvent;
+ setUid(ACTIVE);
+ mDisplaying = false;
+ --mAlarmCount;
+ mUpdated = true;
+ }
+}
+
+/******************************************************************************
+ * Determine whether the event will occur after the specified date/time.
+ * If 'includeRepetitions' is true and the alarm has a sub-repetition, it
+ * returns true if any repetitions occur after the specified date/time.
+ */
+bool KAEvent::occursAfter(const QDateTime& preDateTime, bool includeRepetitions) const
+{
+ QDateTime dt;
+ if (checkRecur() != KARecurrence::NO_RECUR)
+ {
+ if (mRecurrence->duration() < 0)
+ return true; // infinite recurrence
+ dt = mRecurrence->endDateTime();
+ }
+ else
+ dt = mNextMainDateTime.dateTime();
+ if (mStartDateTime.isDateOnly())
+ {
+ QDate pre = preDateTime.date();
+ if (preDateTime.time() < Preferences::startOfDay())
+ pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
+ if (pre < dt.date())
+ return true;
+ }
+ else if (preDateTime < dt)
+ return true;
+
+ if (includeRepetitions && mRepeatCount)
+ {
+ if (preDateTime < dt.addSecs(mRepeatCount * mRepeatInterval * 60))
+ return true;
+ }
+ return false;
+}
+
+/******************************************************************************
+ * Get the date/time of the next occurrence of the event, after the specified
+ * date/time.
+ * 'result' = date/time of next occurrence, or invalid date/time if none.
+ */
+KAEvent::OccurType KAEvent::nextOccurrence(const QDateTime& preDateTime, DateTime& result,
+ KAEvent::OccurOption includeRepetitions) const
+{
+ int repeatSecs = 0;
+ QDateTime pre = preDateTime;
+ if (includeRepetitions != IGNORE_REPETITION)
+ {
+ if (!mRepeatCount || !mRepeatInterval)
+ includeRepetitions = IGNORE_REPETITION;
+ else
+ {
+ repeatSecs = mRepeatInterval * 60;
+ pre = preDateTime.addSecs(-mRepeatCount * repeatSecs);
+ }
+ }
+
+ OccurType type;
+ bool recurs = (checkRecur() != KARecurrence::NO_RECUR);
+ if (recurs)
+ type = nextRecurrence(pre, result);
+ else if (pre < mNextMainDateTime.dateTime())
+ {
+ result = mNextMainDateTime;
+ type = FIRST_OR_ONLY_OCCURRENCE;
+ }
+ else
+ {
+ result = DateTime();
+ type = NO_OCCURRENCE;
+ }
+
+ if (type != NO_OCCURRENCE && result <= preDateTime && includeRepetitions != IGNORE_REPETITION)
+ {
+ // The next occurrence is a sub-repetition
+ int repetition = result.secsTo(preDateTime) / repeatSecs + 1;
+ DateTime repeatDT = result.addSecs(repetition * repeatSecs);
+ if (recurs)
+ {
+ // We've found a recurrence before the specified date/time, which has
+ // a sub-repetition after the date/time.
+ // However, if the intervals between recurrences vary, we could possibly
+ // have missed a later recurrence, which fits the criterion, so check again.
+ DateTime dt;
+ OccurType newType = previousOccurrence(repeatDT.dateTime(), dt, false);
+ if (dt > result)
+ {
+ type = newType;
+ result = dt;
+ if (includeRepetitions == RETURN_REPETITION && result <= preDateTime)
+ {
+ // The next occurrence is a sub-repetition
+ int repetition = result.secsTo(preDateTime) / repeatSecs + 1;
+ result = result.addSecs(repetition * repeatSecs);
+ type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
+ }
+ return type;
+ }
+ }
+ if (includeRepetitions == RETURN_REPETITION)
+ {
+ // The next occurrence is a sub-repetition
+ result = repeatDT;
+ type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
+ }
+ }
+ return type;
+}
+
+/******************************************************************************
+ * Get the date/time of the last previous occurrence of the event, before the
+ * specified date/time.
+ * If 'includeRepetitions' is true and the alarm has a sub-repetition, the
+ * last previous repetition is returned if appropriate.
+ * 'result' = date/time of previous occurrence, or invalid date/time if none.
+ */
+KAEvent::OccurType KAEvent::previousOccurrence(const QDateTime& afterDateTime, DateTime& result, bool includeRepetitions) const
+{
+ if (mStartDateTime >= afterDateTime)
+ {
+ result = QDateTime();
+ return NO_OCCURRENCE; // the event starts after the specified date/time
+ }
+
+ // Find the latest recurrence of the event
+ OccurType type;
+ if (checkRecur() == KARecurrence::NO_RECUR)
+ {
+ result = mStartDateTime;
+ type = FIRST_OR_ONLY_OCCURRENCE;
+ }
+ else
+ {
+ QDateTime recurStart = mRecurrence->startDateTime();
+ QDateTime after = afterDateTime;
+ if (mStartDateTime.isDateOnly() && afterDateTime.time() > Preferences::startOfDay())
+ after = after.addDays(1); // today's recurrence (if today recurs) has passed
+ QDateTime dt = mRecurrence->getPreviousDateTime(after);
+ result.set(dt, mStartDateTime.isDateOnly());
+ if (!dt.isValid())
+ return NO_OCCURRENCE;
+ if (dt == recurStart)
+ type = FIRST_OR_ONLY_OCCURRENCE;
+ else if (mRecurrence->getNextDateTime(dt).isValid())
+ type = result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
+ else
+ type = LAST_RECURRENCE;
+ }
+
+ if (includeRepetitions && mRepeatCount)
+ {
+ // Find the latest repetition which is before the specified time.
+ // N.B. This is coded to avoid 32-bit integer overflow which occurs
+ // in QDateTime::secsTo() for large enough time differences.
+ int repeatSecs = mRepeatInterval * 60;
+ DateTime lastRepetition = result.addSecs(mRepeatCount * repeatSecs);
+ if (lastRepetition < afterDateTime)
+ {
+ result = lastRepetition;
+ return static_cast<OccurType>(type | OCCURRENCE_REPEAT);
+ }
+ int repetition = (result.dateTime().secsTo(afterDateTime) - 1) / repeatSecs;
+ if (repetition > 0)
+ {
+ result = result.addSecs(repetition * repeatSecs);
+ return static_cast<OccurType>(type | OCCURRENCE_REPEAT);
+ }
+ }
+ return type;
+}
+
+/******************************************************************************
+ * Set the date/time of the event to the next scheduled occurrence after the
+ * specified date/time, provided that this is later than its current date/time.
+ * Any reminder alarm is adjusted accordingly.
+ * If the alarm has a sub-repetition, and a repetition of a previous
+ * recurrence occurs after the specified date/time, that repetition is set as
+ * the next occurrence.
+ */
+KAEvent::OccurType KAEvent::setNextOccurrence(const QDateTime& preDateTime)
+{
+ if (preDateTime < mNextMainDateTime.dateTime())
+ return FIRST_OR_ONLY_OCCURRENCE; // it might not be the first recurrence - tant pis
+ QDateTime pre = preDateTime;
+ // If there are repetitions, adjust the comparison date/time so that
+ // we find the earliest recurrence which has a repetition falling after
+ // the specified preDateTime.
+ if (mRepeatCount && mRepeatInterval)
+ pre = preDateTime.addSecs(-mRepeatCount * mRepeatInterval * 60);
+
+ DateTime dt;
+ OccurType type;
+ if (pre < mNextMainDateTime.dateTime())
+ {
+ dt = mNextMainDateTime;
+ type = FIRST_OR_ONLY_OCCURRENCE; // may not actually be the first occurrence
+ }
+ else if (checkRecur() != KARecurrence::NO_RECUR)
+ {
+ type = nextRecurrence(pre, dt);
+ if (type == NO_OCCURRENCE)
+ return NO_OCCURRENCE;
+ if (type != FIRST_OR_ONLY_OCCURRENCE && dt != mNextMainDateTime)
+ {
+ // Need to reschedule the next trigger date/time
+ mNextMainDateTime = dt;
+ // Reinstate the reminder (if any) for the rescheduled recurrence
+ if (mDeferral == REMINDER_DEFERRAL || mArchiveReminderMinutes)
+ {
+ if (mReminderOnceOnly)
+ {
+ if (mReminderMinutes)
+ set_archiveReminder();
+ }
+ else
+ set_reminder(mArchiveReminderMinutes);
+ }
+ if (mDeferral == REMINDER_DEFERRAL)
+ set_deferral(NO_DEFERRAL);
+ mUpdated = true;
+ }
+ }
+ else
+ return NO_OCCURRENCE;
+
+ if (mRepeatCount && mRepeatInterval)
+ {
+ int secs = dt.dateTime().secsTo(preDateTime);
+ if (secs >= 0)
+ {
+ // The next occurrence is a sub-repetition.
+ type = static_cast<OccurType>(type | OCCURRENCE_REPEAT);
+ mNextRepeat = (secs / (60 * mRepeatInterval)) + 1;
+ // Repetitions can't have a reminder, so remove any.
+ if (mReminderMinutes)
+ set_archiveReminder();
+ if (mDeferral == REMINDER_DEFERRAL)
+ set_deferral(NO_DEFERRAL);
+ mUpdated = true;
+ }
+ else if (mNextRepeat)
+ {
+ // The next occurrence is the main occurrence, not a repetition
+ mNextRepeat = 0;
+ mUpdated = true;
+ }
+ }
+ return type;
+}
+
+/******************************************************************************
+ * Get the date/time of the next recurrence of the event, after the specified
+ * date/time.
+ * 'result' = date/time of next occurrence, or invalid date/time if none.
+ */
+KAEvent::OccurType KAEvent::nextRecurrence(const QDateTime& preDateTime, DateTime& result) const
+{
+ QDateTime recurStart = mRecurrence->startDateTime();
+ QDateTime pre = preDateTime;
+ if (mStartDateTime.isDateOnly() && preDateTime.time() < Preferences::startOfDay())
+ {
+ pre = pre.addDays(-1); // today's recurrence (if today recurs) is still to come
+ pre.setTime(Preferences::startOfDay());
+ }
+ QDateTime dt = mRecurrence->getNextDateTime(pre);
+ result.set(dt, mStartDateTime.isDateOnly());
+ if (!dt.isValid())
+ return NO_OCCURRENCE;
+ if (dt == recurStart)
+ return FIRST_OR_ONLY_OCCURRENCE;
+ if (mRecurrence->duration() >= 0 && dt == mRecurrence->endDateTime())
+ return LAST_RECURRENCE;
+ return result.isDateOnly() ? RECURRENCE_DATE : RECURRENCE_DATE_TIME;
+}
+
+/******************************************************************************
+ * Return the recurrence interval as text suitable for display.
+ */
+QString KAEvent::recurrenceText(bool brief) const
+{
+ if (mRepeatAtLogin)
+ return brief ? i18n("Brief form of 'At Login'", "Login") : i18n("At login");
+ if (mRecurrence)
+ {
+ int frequency = mRecurrence->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<int>& days, int count, const QDate& end)
+{
+ if (!setRecur(RecurrenceRule::rMonthly, freq, count, end))
+ return false;
+ for (QValueListConstIterator<int> 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<MonthPos>& posns, int count, const QDate& end)
+{
+ if (!setRecur(RecurrenceRule::rMonthly, freq, count, end))
+ return false;
+ for (QValueListConstIterator<MonthPos> 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<int>& months, int day, KARecurrence::Feb29Type feb29, int count, const QDate& end)
+{
+ if (!setRecur(RecurrenceRule::rYearly, freq, count, end, feb29))
+ return false;
+ for (QValueListConstIterator<int> 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<MonthPos>& posns, const QValueList<int>& months, int count, const QDate& end)
+{
+ if (!setRecur(RecurrenceRule::rYearly, freq, count, end))
+ return false;
+ for (QValueListConstIterator<int> it = months.begin(); it != months.end(); ++it)
+ mRecurrence->addYearlyMonth(*it);
+ for (QValueListConstIterator<MonthPos> 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<KAEvent*>(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<KAEvent*>(this)->mRepeatCount = 0;
+ if (!mRepeatCount && mRepeatInterval)
+ const_cast<KAEvent*>(this)->mRepeatInterval = 0;
+}
+
+#if 0
+/******************************************************************************
+ * Convert a QValueList<WDayPos> to QValueList<MonthPos>.
+ */
+QValueList<KAEvent::MonthPos> KAEvent::convRecurPos(const QValueList<KCal::RecurrenceRule::WDayPos>& wdaypos)
+{
+ QValueList<MonthPos> mposns;
+ for (QValueList<KCal::RecurrenceRule::WDayPos>::ConstIterator it = wdaypos.begin(); it != wdaypos.end(); ++it)
+ {
+ int daybit = (*it).day() - 1;
+ int weeknum = (*it).pos();
+ bool found = false;
+ for (QValueList<MonthPos>::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<Alarm*>(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<Alarm*>(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<Person>& addresses)
+{
+ clear();
+ for (QValueList<Person>::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<Person>::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 00000000..ff8ab305
--- /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 <djarvie@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.
+ */
+
+#ifndef KALARMEVENT_H
+#define KALARMEVENT_H
+
+/** @file alarmevent.h - represents calendar alarms and events */
+
+#include <qcolor.h>
+#include <qfont.h>
+
+#include <libkcal/person.h>
+#include <libkcal/event.h>
+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<KCal::Person>
+{
+ public:
+ EmailAddressList() : QValueList<KCal::Person>() { }
+ EmailAddressList(const QValueList<KCal::Person>& list) { operator=(list); }
+ EmailAddressList& operator=(const QValueList<KCal::Person>&);
+ 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<Type>(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<int>& days, int count, const QDate& end);
+ bool setRecurMonthlyByPos(int freq, const QValueList<MonthPos>& pos, int count, const QDate& end);
+ bool setRecurAnnualByDate(int freq, const QValueList<int>& months, int day, KARecurrence::Feb29Type, int count, const QDate& end);
+ bool setRecurAnnualByPos(int freq, const QValueList<MonthPos>& pos, const QValueList<int>& months, int count, const QDate& end);
+// static QValueList<MonthPos> convRecurPos(const QValueList<KCal::RecurrenceRule::WDayPos>&);
+#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 00000000..19fcda4b
--- /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 <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 "kalarm.h"
+
+#include <qtooltip.h>
+#include <qpainter.h>
+#include <qstyle.h>
+#include <qheader.h>
+#include <qregexp.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include <libkcal/icaldrag.h>
+#include <libkcal/calendarlocal.h>
+
+#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<EventListViewBase*> AlarmListView::mInstanceList;
+bool AlarmListView::mDragging = false;
+
+
+AlarmListView::AlarmListView(const QValueList<int>& 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<AlarmListView*>(*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<AlarmListViewItem*>(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<int> 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<int> 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<EventListViewItemBase*> items = selectedItems();
+ if (!items.count())
+ return;
+ for (QValueList<EventListViewItemBase*>::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 00000000..63d5bf0b
--- /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 <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.
+ */
+
+#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<int>& 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<int> 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 00000000..2162f62a
--- /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 <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 "kalarm.h"
+#include <qstringlist.h>
+#include <klocale.h>
+
+#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<int>(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 00000000..8d643b3c
--- /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 <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.
+ */
+
+#ifndef ALARMTEXT_H
+#define ALARMTEXT_H
+
+#include <qstring.h>
+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 00000000..1e7e777b
--- /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 <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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+
+#include <kdialog.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+
+#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<AlarmTimeWidget*>(this), i18n("Invalid date"));
+ if (errorWidget)
+ *errorWidget = mDateEdit;
+ }
+ else
+ {
+ if (showErrorMessage)
+ KMessageBox::sorry(const_cast<AlarmTimeWidget*>(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<AlarmTimeWidget*>(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<AlarmTimeWidget*>(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<AlarmTimeWidget*>(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 00000000..0a93e9cf
--- /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 <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.
+ */
+
+#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 00000000..3c4f28ab
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlineedit.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kmessagebox.h>
+#include <kaccel.h>
+#include <kabc/addressbook.h>
+#include <kabc/stdaddressbook.h>
+#include <kdebug.h>
+
+#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<AddresseeItem*>(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<KAEvent> BirthdayDlg::events() const
+{
+ QValueList<KAEvent> 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<AddresseeItem*>(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<int> 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 00000000..b8dd202c
--- /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 <djarvie@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.
+ */
+#ifndef BIRTHDAYDLG_H
+#define BIRTHDAYDLG_H
+
+#include <qlineedit.h>
+#include <klistview.h>
+#include <kdialogbase.h>
+
+#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<KAEvent> 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 00000000..ada75e76
--- /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 <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 "kalarm.h"
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qstringlist.h>
+
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <kdebug.h>
+
+#include <libkcal/calendar.h>
+
+#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 00000000..a8cc3e81
--- /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 <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.
+ */
+
+#ifndef CALENDARCOMPAT_H
+#define CALENDARCOMPAT_H
+
+/* @file calendarcompat.h - compatibility for old calendar file formats */
+
+#include <qstring.h>
+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 00000000..9af89064
--- /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 00000000..fdf30090
--- /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 00000000..22f1fb36
--- /dev/null
+++ b/kalarm/daemon.cpp
@@ -0,0 +1,774 @@
+/*
+ * daemon.cpp - interface with alarm daemon
+ * Program: kalarm
+ * Copyright © 2001-2007 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 "kalarm.h"
+
+#include <qtimer.h>
+#include <qiconset.h>
+
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kaboutdata.h>
+#include <kmessagebox.h>
+#include <dcopclient.h>
+#include <kdebug.h>
+
+#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<QString> Daemon::mQueuedEvents;
+QValueList<QString> 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 00000000..04198904
--- /dev/null
+++ b/kalarm/daemon.h
@@ -0,0 +1,134 @@
+/*
+ * daemon.h - interface with alarm daemon
+ * Program: kalarm
+ * Copyright © 2001-2007 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.
+ */
+
+#ifndef DAEMON_H
+#define DAEMON_H
+
+#include <qobject.h>
+#include <qdatetime.h>
+#include <kaction.h>
+
+#include <kalarmd/kalarmd.h>
+#include <kalarmd/alarmguiiface.h>
+
+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<QString> mQueuedEvents; // IDs of pending events that daemon has triggered
+ static QValueList<QString> 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 00000000..ab4105fe
--- /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 <djarvie@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 "kalarm.h"
+
+#include <stdlib.h>
+
+#include <kdebug.h>
+
+#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 00000000..1ea6f05e
--- /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 <djarvie@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
+ * 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 <qwidget.h>
+#include <dcopobject.h>
+
+#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 00000000..5e0e039c
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qlayout.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include <libkcal/event.h>
+#include <libkcal/recurrence.h>
+
+#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 00000000..69275a05
--- /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 <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.
+ */
+
+#ifndef DEFERDLG_H
+#define DEFERDLG_H
+
+#include <kdialogbase.h>
+#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 00000000..23211100
--- /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 <djarvie@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 "kalarm.h"
+
+#include <limits.h>
+
+#include <qlayout.h>
+#include <qpopupmenu.h>
+#include <qvbox.h>
+#include <qgroupbox.h>
+#include <qwidgetstack.h>
+#include <qdragobject.h>
+#include <qlabel.h>
+#include <qmessagebox.h>
+#include <qtabwidget.h>
+#include <qvalidator.h>
+#include <qwhatsthis.h>
+#include <qtooltip.h>
+#include <qdir.h>
+#include <qstyle.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <kio/netaccess.h>
+#include <kfileitem.h>
+#include <kmessagebox.h>
+#include <kurldrag.h>
+#include <kurlcompletion.h>
+#include <kwin.h>
+#include <kwinmodule.h>
+#include <kstandarddirs.h>
+#include <kstdguiitem.h>
+#include <kabc/addresseedialog.h>
+#include <kdebug.h>
+
+#include <libkdepim/maillistdrag.h>
+#include <libkdepim/kvcarddrag.h>
+#include <libkcal/icaldrag.h>
+
+#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<EditAlarmDlg*>(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<EditAlarmDlg*>(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 00000000..8a8f40ce
--- /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 <djarvie@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.
+ */
+
+#ifndef EDITDLG_H
+#define EDITDLG_H
+
+#include <qdatetime.h>
+#include <qlineedit.h>
+
+#include <kdialogbase.h>
+
+#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 00000000..cbcddb62
--- /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 <djarvie@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.
+ */
+
+#ifndef EDITDLGPRIVATE_H
+#define EDITDLGPRIVATE_H
+
+#include <ktextedit.h>
+
+
+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 00000000..fc4fc6e7
--- /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 <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 "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 00000000..d0399ec0
--- /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 <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.
+ */
+
+#ifndef EMAILIDCOMBO_H
+#define EMAILIDCOMBO_H
+
+#include <libkpimidentities/identitycombo.h>
+#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 00000000..1a25e2ac
--- /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 <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 "kalarm.h"
+
+#include <qwhatsthis.h>
+#include <qheader.h>
+
+#include <kiconloader.h>
+#include <kdebug.h>
+
+#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<EventListViewItemBase*> EventListViewBase::selectedItems() const
+{
+ QValueList<EventListViewItemBase*> 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 00000000..23831c71
--- /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 <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.
+ */
+
+#ifndef EVENTLISTVIEWBASE_H
+#define EVENTLISTVIEWBASE_H
+
+#include "kalarm.h"
+
+#include <qvaluelist.h>
+#include <klistview.h>
+
+#include "alarmevent.h"
+
+class QPixmap;
+class EventListViewItemBase;
+class Find;
+
+
+class EventListViewBase : public KListView
+{
+ Q_OBJECT
+ public:
+ typedef QValueList<EventListViewBase*> InstanceList;
+ typedef QValueListIterator<EventListViewBase*> InstanceListIterator;
+ typedef QValueListConstIterator<EventListViewBase*> 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<EventListViewItemBase*> 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 00000000..7caf4bde
--- /dev/null
+++ b/kalarm/find.cpp
@@ -0,0 +1,394 @@
+/*
+ * find.cpp - search facility
+ * Program: kalarm
+ * Copyright © 2005,2006,2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <qgroupbox.h>
+#include <qcheckbox.h>
+
+#include <kfinddialog.h>
+#include <kfind.h>
+#include <kseparator.h>
+#include <kwin.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#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 00000000..1bb4913b
--- /dev/null
+++ b/kalarm/find.h
@@ -0,0 +1,75 @@
+/*
+ * find.h - search facility
+ * Program: kalarm
+ * Copyright © 2005,2006,2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef FIND_H
+#define FIND_H
+
+#include <qobject.h>
+#include <qguardedptr.h>
+#include <qstringlist.h>
+
+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<KFindDialog> 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 00000000..dae2e985
--- /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 <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 <qobjectlist.h>
+#include <qwidget.h>
+#include <qgroupbox.h>
+#include <qpushbutton.h>
+#include <qhbox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kcolordialog.h>
+
+#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 00000000..1e37b383
--- /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 <djarvie@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.
+ */
+
+#ifndef FONTCOLOUR_H
+#define FONTCOLOUR_H
+
+#include <kdeversion.h>
+#include <qwidget.h>
+#include <qstringlist.h>
+#include <kfontdialog.h>
+#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 00000000..b3a55359
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qcheckbox.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#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 00000000..9bbe3d8d
--- /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 <djarvie@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.
+ */
+
+#ifndef FONTCOLOURBUTTON_H
+#define FONTCOLOURBUTTON_H
+
+#include <qfont.h>
+#include <qcolor.h>
+#include <qframe.h>
+#include <kdialogbase.h>
+
+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 00000000..dd83fea4
--- /dev/null
+++ b/kalarm/functions.cpp
@@ -0,0 +1,1099 @@
+/*
+ * functions.cpp - miscellaneous functions
+ * Program: kalarm
+ * Copyright © 2001-2009 by David Jarvie <djarvie@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 "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 <qdeepcopy.h>
+#include <qdir.h>
+#include <qregexp.h>
+
+#include <kconfig.h>
+#include <kaction.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kstdguiitem.h>
+#include <kstdaccel.h>
+#include <kmessagebox.h>
+#include <kfiledialog.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <kdcopservicestarter.h>
+#include <kdebug.h>
+
+#include <libkcal/event.h>
+#include <libkcal/icalformat.h>
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identity.h>
+#include <libkcal/person.h>
+
+
+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<KAEvent&>(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<KAEvent> templateList()
+{
+ QValueList<KAEvent> 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<QString>(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->"<<iCal<<endl;
+ delete kcalEvent;
+
+ // Send the event to KOrganizer
+ if (!runKOrganizer()) // start KOrganizer if it isn't already running
+ return false;
+ QByteArray data, replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << iCal;
+ if (kapp->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 <X11/keysym.h>
+#include <X11/extensions/XTest.h>
+#include <qwindowdefs.h>
+
+/******************************************************************************
+* 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 00000000..c5337cc4
--- /dev/null
+++ b/kalarm/functions.h
@@ -0,0 +1,130 @@
+/*
+ * functions.h - miscellaneous functions
+ * Program: kalarm
+ * Copyright © 2004-2006,2009 by David Jarvie <djarvie@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.
+ */
+
+#ifndef FUNCTIONS_H
+#define FUNCTIONS_H
+
+/** @file functions.h - miscellaneous functions */
+
+#include <qsize.h>
+#include <qstring.h>
+
+#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<KAEvent> 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 00000000..72b7ece3
--- /dev/null
+++ b/kalarm/hi16-app-kalarm.png
Binary files differ
diff --git a/kalarm/hi32-app-kalarm.png b/kalarm/hi32-app-kalarm.png
new file mode 100644
index 00000000..3c5b9f06
--- /dev/null
+++ b/kalarm/hi32-app-kalarm.png
Binary files differ
diff --git a/kalarm/hi48-app-kalarm.png b/kalarm/hi48-app-kalarm.png
new file mode 100644
index 00000000..8351a1eb
--- /dev/null
+++ b/kalarm/hi48-app-kalarm.png
Binary files differ
diff --git a/kalarm/kalarm.desktop b/kalarm/kalarm.desktop
new file mode 100644
index 00000000..97084700
--- /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 00000000..e0af27f5
--- /dev/null
+++ b/kalarm/kalarm.h
@@ -0,0 +1,39 @@
+/*
+ * kalarm.h - global header file
+ * Program: kalarm
+ * Copyright © 2001-2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef KALARM_H
+#define KALARM_H
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define KALARM_VERSION "1.5.5"
+#define KALARM_NAME "KAlarm"
+
+#include <kdeversion.h>
+
+#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 00000000..d161dc67
--- /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 00000000..b222eeec
--- /dev/null
+++ b/kalarm/kalarmapp.cpp
@@ -0,0 +1,2187 @@
+/*
+ * kalarmapp.cpp - the KAlarm application object
+ * Program: kalarm
+ * Copyright © 2001-2009 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <iostream>
+
+#include <qobjectlist.h>
+#include <qtimer.h>
+#include <qregexp.h>
+#include <qfile.h>
+
+#include <kcmdlineargs.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kconfig.h>
+#include <kaboutdata.h>
+#include <dcopclient.h>
+#include <kprocess.h>
+#include <ktempfile.h>
+#include <kfileitem.h>
+#include <kstdguiitem.h>
+#include <ktrader.h>
+#include <kstaticdeleter.h>
+#include <kdebug.h>
+
+#include <libkcal/calformat.h>
+
+#include <kalarmd/clientinfo.h>
+
+#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 <netwm.h>
+
+
+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<const char*>(colourText)[0] == '0'
+ && tolower(static_cast<const char*>(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<const char*>(colourText)[0] == '0'
+ && tolower(static_cast<const char*>(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<const char*>(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<float>(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<ProcData*>::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<ShellProcess> 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<ProcData*>::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<ProcData*>::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<ProcData*>::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<int>(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 00000000..22c7649b
--- /dev/null
+++ b/kalarm/kalarmapp.h
@@ -0,0 +1,189 @@
+/*
+ * kalarmapp.h - the KAlarm application object
+ * Program: kalarm
+ * Copyright © 2001-2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef KALARMAPP_H
+#define KALARMAPP_H
+
+/** @file kalarmapp.h - the KAlarm application object */
+
+#include <qguardedptr.h>
+class QTimer;
+class QDateTime;
+
+#include <kuniqueapplication.h>
+#include <kurl.h>
+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<ShellProcess> logProcess;
+ KAEvent* event;
+ KAAlarm* alarm;
+ QGuardedPtr<QWidget> 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<ProcData*> mCommandProcesses; // currently active command alarm processes
+ QValueList<DcopQEntry> 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 00000000..7770c681
--- /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 00000000..77f31e4f
--- /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 <software@astrojar.org.uk>
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ * Copyright (c) 1997-1999 Preston Brown <pbrown@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 "kalarmd.h"
+
+#include <kcmdlineargs.h>
+#include <kdebug.h>
+
+#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 00000000..83d11d29
--- /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 <software@astrojar.org.uk>
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ * Copyright (c) 1997-1999 Preston Brown <pbrown@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.
+ */
+
+#ifndef ADAPP_H
+#define ADAPP_H
+
+#include <kuniqueapplication.h>
+
+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 00000000..8af97cc0
--- /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 <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 <unistd.h>
+#include <assert.h>
+
+#include <qfile.h>
+
+#include <ktempfile.h>
+#include <kio/job.h>
+#include <kio/jobclasses.h>
+#include <kdebug.h>
+
+#include "adcalendar.moc"
+
+QValueList<ADCalendar*> 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<int>(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<QDateTime>& 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<QDateTime>& 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<QDateTime>& 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 00000000..656070be
--- /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 <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.
+ */
+
+#ifndef ADCALENDAR_H
+#define ADCALENDAR_H
+
+#include <libkcal/calendarlocal.h>
+namespace KIO { class Job; }
+class ADCalendar;
+
+
+// Alarm Daemon calendar access
+class ADCalendar : public KCal::CalendarLocal
+{
+ Q_OBJECT
+ public:
+ typedef QValueList<ADCalendar*>::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<QDateTime>&);
+ void setEventHandled(const QString& eventID);
+ void clearEventsHandled(bool nonexistentOnly = false);
+ bool eventHandled(const KCal::Event*, const QValueList<QDateTime>&);
+
+ 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<QDateTime>& alarmtimes)
+ : eventSequence(seqno), alarmTimes(alarmtimes) {}
+ int eventSequence;
+ QValueList<QDateTime> alarmTimes;
+ };
+
+ typedef QMap<EventKey, EventItem> 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<ADCalendar*> 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<QDateTime>& 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 00000000..11dc6bf6
--- /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 <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 "kalarmd.h"
+
+#include <qregexp.h>
+#include <qstringlist.h>
+
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <kdebug.h>
+
+#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 00000000..2c841fdf
--- /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 <software@astrojar.org.uk>
+ * 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 00000000..c0a7792f
--- /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 <software@astrojar.org.uk>
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ * Copyright (c) 1997-1999 Preston Brown <pbrown@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 "kalarmd.h"
+
+#include <stdlib.h>
+
+#include <klocale.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <kstartupinfo.h>
+
+#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 00000000..57667857
--- /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 <software@astrojar.org.uk>
+ * 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 <unistd.h>
+#include <stdlib.h>
+
+#include <qtimer.h>
+#include <qfile.h>
+#include <qdatetime.h>
+
+#include <kapplication.h>
+#include <kstandarddirs.h>
+#include <kprocess.h>
+#include <kio/netaccess.h>
+#include <dcopclient.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include <libkcal/calendarlocal.h>
+#include <libkcal/icalformat.h>
+
+#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<KCal::Alarm*> alarms = cal->alarmsTo(now);
+ if (!alarms.count())
+ return;
+ QValueList<KCal::Event*> eventsDone;
+ for (QValueList<KCal::Alarm*>::ConstIterator it = alarms.begin(); it != alarms.end(); ++it)
+ {
+ KCal::Event* event = dynamic_cast<KCal::Event*>((*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<QDateTime> 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<const char*>(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<const char*>(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 00000000..6ab5d0f9
--- /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 <software@astrojar.org.uk>
+ * 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 <libkcal/calendarlocal.h>
+
+#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 00000000..708f5b28
--- /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 <software@astrojar.org.uk>
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ * Copyright (c) 1997-1999 Preston Brown <pbrown@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.
+ */
+
+#ifndef ALARMDAEMONIFACE_H
+#define ALARMDAEMONIFACE_H
+
+#include <dcopobject.h>
+
+
+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 00000000..7e98c831
--- /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 <software@astrojar.org.uk>
+ * Based on the original, (c) 1998, 1999 Preston Brown
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ * Copyright (c) 1997-1999 Preston Brown <pbrown@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.
+ */
+
+#ifndef DAEMONGUIIFACE_H
+#define DAEMONGUIIFACE_H
+
+#include <dcopobject.h>
+
+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 00000000..21889082
--- /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 <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 "adcalendar.h"
+#include "clientinfo.h"
+
+QMap<QCString, ClientInfo*> 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<QCString, ClientInfo*>::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<QCString, ClientInfo*>::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<QCString, ClientInfo*>::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 00000000..fe6491d7
--- /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 <software@astrojar.org.uk>
+ * 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 <qcstring.h>
+#include <qstring.h>
+#include <qmap.h>
+
+class ADCalendar;
+
+
+/*=============================================================================
+= Class: ClientInfo
+= Details of a KAlarm client application.
+=============================================================================*/
+class ClientInfo
+{
+ public:
+ typedef QMap<QCString, ClientInfo*>::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<QCString, ClientInfo*> 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 00000000..de5add48
--- /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 00000000..31d3a0fa
--- /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 00000000..5e45243b
--- /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 <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.
+ */
+
+#ifndef KALARMD_H
+#define KALARMD_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#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 00000000..cd3aaf0c
--- /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 <djarvie@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.
+ */
+
+#ifndef KALARMIFACE_H
+#define KALARMIFACE_H
+
+/** @file kalarmiface.h - DCOP interface to KAlarm */
+
+// No forward declarations - dcopidl2cpp won't work
+#include <dcopobject.h>
+#include <kurl.h>
+#include <qstringlist.h>
+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 00000000..68627170
--- /dev/null
+++ b/kalarm/kalarmui.rc
@@ -0,0 +1,67 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kalarm" version="143" >
+ <ToolBar noMerge="1" name="mainToolBar" >
+ <Action name="new" />
+ <Action name="newFromTempl" />
+ <Action name="copy" />
+ <Action name="modify" />
+ <Action name="delete" />
+ <Action name="view" />
+ <Separator />
+ <Action name="edit_undo" />
+ <Action name="edit_redo" />
+ <Separator />
+ <Action name="edit_find"/>
+ </ToolBar>
+ <MenuBar>
+ <Menu name="file" >
+ <text>&amp;File</text>
+ <Action name="templates" />
+ <Action name="importAlarms" />
+ <Action name="importBirthdays" />
+ </Menu>
+ <Menu name="view" >
+ <text>&amp;View</text>
+ <Action name="showAlarmTimes" />
+ <Action name="showTimeToAlarms" />
+ <Separator/>
+ <Action name="showExpiredAlarms" />
+ <Action name="showInSystemTray" />
+ </Menu>
+ <Menu name="actions" >
+ <text>&amp;Actions</text>
+ <Action name="new" />
+ <Action name="newFromTempl" />
+ <Action name="copy" />
+ <Action name="modify" />
+ <Action name="delete" />
+ <Action name="undelete" />
+ <Action name="view" />
+ <Action name="disable" />
+ <Action name="createTemplate" />
+ <Separator/>
+ <Action name="alarmEnable" />
+ <Action name="refreshAlarms" />
+ </Menu>
+ <Menu noMerge="1" name="settings" >
+ <text>&amp;Settings</text>
+ <Merge name="StandardToolBarMenuHandler" />
+ <Separator/>
+ <Action name="options_configure_keybinding" />
+ <Action name="options_configure_toolbars" />
+ <Action name="options_configure" />
+ </Menu>
+ </MenuBar>
+ <Menu name="listContext" >
+ <Action name="new" />
+ <Action name="newFromTempl" />
+ <Action name="copy" />
+ <Action name="modify" />
+ <Action name="delete" />
+ <Action name="undelete" />
+ <Action name="view" />
+ <Action name="disable" />
+ <Action name="createTemplate" />
+ </Menu>
+ <ActionProperties/>
+</kpartgui>
diff --git a/kalarm/kamail.cpp b/kalarm/kamail.cpp
new file mode 100644
index 00000000..3042a270
--- /dev/null
+++ b/kalarm/kamail.cpp
@@ -0,0 +1,1096 @@
+/*
+ * kamail.cpp - email functions
+ * Program: kalarm
+ * Copyright © 2002-2005,2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <pwd.h>
+
+#include <qfile.h>
+#include <qregexp.h>
+
+#include <kstandarddirs.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <kmessagebox.h>
+#include <kprocess.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kfileitem.h>
+#include <kio/netaccess.h>
+#include <ktempfile.h>
+#include <kemailsettings.h>
+#include <kdebug.h>
+
+#include <libkpimidentities/identitymanager.h>
+#include <libkpimidentities/identity.h>
+#include <libemailfunctions/email.h>
+#include <libkcal/person.h>
+
+#include <kmime_header_parsing.h>
+
+#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<KMime::Types::Address> & 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<KCal::Person>::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<const char*>(addrs);
+
+ // parse an address-list
+ QValueList<KMime::Types::Address> 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<KMime::Types::Address>::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<const char*>(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<KMime::Types::Mailbox>& mblist = addr.mailboxList;
+ for (QValueList<KMime::Types::Mailbox>::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<Address> & 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 00000000..86c0482a
--- /dev/null
+++ b/kalarm/kamail.h
@@ -0,0 +1,65 @@
+/*
+ * kamail.h - email functions
+ * Program: kalarm
+ * Copyright © 2002-2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef KAMAIL_H
+#define KAMAIL_H
+
+#include <qstring.h>
+#include <qstringlist.h>
+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 00000000..9461d71e
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qbitarray.h>
+#include <kdebug.h>
+
+#include <libkcal/icalformat.h>
+
+#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<Feb29Type>(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<int> 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<int> 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<int> 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<int> 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<int> 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<KARecurrence*>(&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<int> months = rrule->byMonths();
+ QValueList<int> 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<int> ds;
+ ds.append(60);
+ rrule2->setByYearDays(ds);
+ }
+ else
+ {
+ QValueList<int> ds;
+ ds.append(-1);
+ rrule2->setByMonthDays(ds);
+ QValueList<int> 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="<<count<<", end date="<<end.toString()<<endl;
+ int count1 = rrule1->durationTo(end)
+ - (rrule1->recursOn(startDate()) ? 0 : 1);
+ if (count1 > 0)
+ rrule1->setDuration(count1);
+ else
+ rrule1->setEndDt(startDateTime());
+ int count2 = rrule2->durationTo(end)
+ - (rrule2->recursOn(startDate()) ? 0 : 1);
+ if (count2 > 0)
+ rrule2->setDuration(count2);
+ else
+ rrule2->setEndDt(startDateTime());
+ }
+ }
+ }
+ recur.addRRule(rrule2);
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+/******************************************************************************
+* Return the date/time of the last recurrence.
+*/
+QDateTime KARecurrence::endDateTime() const
+{
+ if (mFeb29Type == FEB29_FEB29 || duration() <= 1)
+ {
+ /* Either it doesn't have any special February 29th treatment,
+ * it's infinite (count = -1), the end date is specified
+ * (count = 0), or it ends on the start date (count = 1).
+ * So just use the normal KCal end date calculation.
+ */
+ return Recurrence::endDateTime();
+ }
+
+ /* Create a temporary recurrence rule to find the end date.
+ * In a standard KCal recurrence, the 29th February only occurs once every
+ * 4 years. So shift the temporary recurrence date to the 28th to ensure
+ * that it occurs every year, thus giving the correct occurrence count.
+ */
+ RecurrenceRule* rrule = new RecurrenceRule();
+ rrule->setRecurrenceType(RecurrenceRule::rYearly);
+ QDateTime dt = startDateTime();
+ QDate d = dt.date();
+ switch (d.day())
+ {
+ case 29:
+ // The start date is definitely a recurrence date, so shift
+ // start date to the temporary recurrence date of the 28th
+ d.setYMD(d.year(), d.month(), 28);
+ break;
+ case 28:
+ if (d.month() != 2 || mFeb29Type != FEB29_FEB28 || QDate::leapYear(d.year()))
+ {
+ // Start date is not a recurrence date, so shift it to 27th
+ d.setYMD(d.year(), d.month(), 27);
+ }
+ break;
+ case 1:
+ if (d.month() == 3 && mFeb29Type == FEB29_MAR1 && !QDate::leapYear(d.year()))
+ {
+ // Start date is a March 1st recurrence date, so shift
+ // start date to the temporary recurrence date of the 28th
+ d.setYMD(d.year(), 2, 28);
+ }
+ break;
+ default:
+ break;
+ }
+ dt.setDate(d);
+ rrule->setStartDt(dt);
+ rrule->setFloats(doesFloat());
+ rrule->setFrequency(frequency());
+ rrule->setDuration(duration());
+ QValueList<int> ds;
+ ds.append(28);
+ rrule->setByMonthDays(ds);
+ rrule->setByMonths(defaultRRuleConst()->byMonths());
+ dt = rrule->endDt();
+ delete rrule;
+
+ // We've found the end date for a recurrence on the 28th. Unless that date
+ // is a real February 28th recurrence, adjust to the actual recurrence date.
+ if (mFeb29Type == FEB29_FEB28 && dt.date().month() == 2 && !QDate::leapYear(dt.date().year()))
+ return dt;
+ return dt.addDays(1);
+}
+
+/******************************************************************************
+* Return the date/time of the last recurrence.
+*/
+QDate KARecurrence::endDate() const
+{
+ QDateTime end = endDateTime();
+ return end.isValid() ? end.date() : QDate();
+}
+
+/******************************************************************************
+* Return whether the event will recur on the specified date.
+* The start date only returns true if it matches the recurrence rules.
+*/
+bool KARecurrence::recursOn(const QDate& dt) const
+{
+ if (!Recurrence::recursOn(dt))
+ return false;
+ if (dt != startDate())
+ return true;
+ // We know now that it isn't in EXDATES or EXRULES,
+ // so we just need to check if it's in RDATES or RRULES
+ if (rDates().contains(dt))
+ return true;
+ RecurrenceRule::List rulelist = rRules();
+ for (RecurrenceRule::List::ConstIterator rr = rulelist.begin(); rr != rulelist.end(); ++rr)
+ if ((*rr)->recursOn(dt))
+ return true;
+ DateTimeList dtlist = rDateTimes();
+ for (DateTimeList::ConstIterator rdt = dtlist.begin(); rdt != dtlist.end(); ++rdt)
+ if ((*rdt).date() == dt)
+ return true;
+ return false;
+}
+
+/******************************************************************************
+* Find the duration of two RRULEs combined.
+* Use the shorter of the two if they differ.
+*/
+int KARecurrence::combineDurations(const RecurrenceRule* rrule1, const RecurrenceRule* rrule2, QDate& end) const
+{
+ int count1 = rrule1->duration();
+ int count2 = rrule2->duration();
+ if (count1 == -1 && count2 == -1)
+ return -1;
+
+ // One of the RRULEs may not recur at all if the recurrence count is small.
+ // In this case, its end date will have been set to the start date.
+ if (count1 && !count2 && rrule2->endDt().date() == startDateTime().date())
+ return count1;
+ if (count2 && !count1 && rrule1->endDt().date() == startDateTime().date())
+ return count2;
+
+ /* The duration counts will be different even for RRULEs of the same length,
+ * because the first RRULE only actually occurs every 4 years. So we need to
+ * compare the end dates.
+ */
+ if (!count1 || !count2)
+ count1 = count2 = 0;
+ // Get the two rules sorted by end date.
+ QDateTime end1 = rrule1->endDt();
+ QDateTime end2 = rrule2->endDt();
+ if (end1.date() == end2.date())
+ {
+ end = end1.date();
+ return count1 + count2;
+ }
+ const RecurrenceRule* rr1; // earlier end date
+ const RecurrenceRule* rr2; // later end date
+ if (end2.isValid()
+ && (!end1.isValid() || end1.date() > end2.date()))
+ {
+ // Swap the two rules to make rr1 have the earlier end date
+ rr1 = rrule2;
+ rr2 = rrule1;
+ QDateTime e = end1;
+ end1 = end2;
+ end2 = e;
+ }
+ else
+ {
+ rr1 = rrule1;
+ rr2 = rrule2;
+ }
+
+ // Get the date of the next occurrence after the end of the earlier ending rule
+ RecurrenceRule rr(*rr1);
+ rr.setDuration(-1);
+ QDateTime next1(rr.getNextDate(end1).date());
+ if (!next1.isValid())
+ end = end1.date();
+ else
+ {
+ if (end2.isValid() && next1 > end2)
+ {
+ // The next occurrence after the end of the earlier ending rule
+ // is later than the end of the later ending rule. So simply use
+ // the end date of the later rule.
+ end = end2.date();
+ return count1 + count2;
+ }
+ QDate prev2 = rr2->getPreviousDate(next1).date();
+ end = (prev2 > end1.date()) ? prev2 : end1.date();
+ }
+ if (count2)
+ count2 = rr2->durationTo(end);
+ return count1 + count2;
+}
+
+/******************************************************************************
+ * Return the longest interval (in minutes) between recurrences.
+ * Reply = 0 if it never recurs.
+ */
+int KARecurrence::longestInterval() const
+{
+ int freq = frequency();
+ switch (type())
+ {
+ case MINUTELY:
+ return freq;
+
+ case DAILY:
+ {
+ QValueList<RecurrenceRule::WDayPos> days = defaultRRuleConst()->byDays();
+ if (days.isEmpty())
+ return freq * 1440;
+
+ // It recurs only on certain days of the week, so the maximum interval
+ // may be greater than the frequency.
+ bool ds[7] = { false, false, false, false, false, false, false };
+ for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
+ if ((*it).pos() == 0)
+ ds[(*it).day() - 1] = true;
+ if (freq % 7)
+ {
+ // It will recur on every day of the week in some week or other
+ // (except for those days which are excluded).
+ int first = -1;
+ int last = -1;
+ int maxgap = 1;
+ for (int i = 0; i < freq*7; i += freq)
+ {
+ if (ds[i % 7])
+ {
+ if (first < 0)
+ first = i;
+ else if (i - last > maxgap)
+ maxgap = i - last;
+ last = i;
+ }
+ }
+ int wrap = freq*7 - last + first;
+ if (wrap > maxgap)
+ maxgap = wrap;
+ return maxgap * 1440;
+ }
+ else
+ {
+ // It will recur on the same day of the week every time.
+ // Ensure that the day is a day which is not excluded.
+ return ds[startDate().dayOfWeek() - 1] ? freq * 1440 : 0;
+ }
+ }
+ case WEEKLY:
+ {
+ // Find which days of the week it recurs on, and if on more than
+ // one, reduce the maximum interval accordingly.
+ QBitArray ds = days();
+ int first = -1;
+ int last = -1;
+ int maxgap = 1;
+ for (int i = 0; i < 7; ++i)
+ {
+ if (ds.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1))
+ {
+ if (first < 0)
+ first = i;
+ else if (i - last > maxgap)
+ maxgap = i - last;
+ last = i;
+ }
+ }
+ if (first < 0)
+ break; // no days recur
+ int span = last - first;
+ if (freq > 1)
+ return (freq*7 - span) * 1440;
+ if (7 - span > maxgap)
+ return (7 - span) * 1440;
+ return maxgap * 1440;
+ }
+ case MONTHLY_DAY:
+ case MONTHLY_POS:
+ return freq * 1440 * 31;
+
+ case ANNUAL_DATE:
+ case ANNUAL_POS:
+ {
+ // Find which months of the year it recurs on, and if on more than
+ // one, reduce the maximum interval accordingly.
+ const QValueList<int> months = yearMonths(); // month list is sorted
+ if (months.isEmpty())
+ break; // no months recur
+ if (months.count() == 1)
+ return freq * 1440 * 365;
+ int first = -1;
+ int last = -1;
+ int maxgap = 0;
+ for (QValueListConstIterator<int> it = months.begin(); it != months.end(); ++it)
+ {
+ if (first < 0)
+ first = *it;
+ else
+ {
+ int span = QDate(2001, last, 1).daysTo(QDate(2001, *it, 1));
+ if (span > maxgap)
+ maxgap = span;
+ }
+ last = *it;
+ }
+ int span = QDate(2001, first, 1).daysTo(QDate(2001, last, 1));
+ if (freq > 1)
+ return (freq*365 - span) * 1440;
+ if (365 - span > maxgap)
+ return (365 - span) * 1440;
+ return maxgap * 1440;
+ }
+ default:
+ break;
+ }
+ return 0;
+}
+
+/******************************************************************************
+ * Return the recurrence's period type.
+ */
+KARecurrence::Type KARecurrence::type() const
+{
+ if (mCachedType == -1)
+ mCachedType = type(defaultRRuleConst());
+ return static_cast<Type>(mCachedType);
+}
+
+KARecurrence::Type KARecurrence::type(const RecurrenceRule* rrule)
+{
+ switch (recurrenceType(rrule))
+ {
+ case rMinutely: return MINUTELY;
+ case rDaily: return DAILY;
+ case rWeekly: return WEEKLY;
+ case rMonthlyDay: return MONTHLY_DAY;
+ case rMonthlyPos: return MONTHLY_POS;
+ case rYearlyMonth: return ANNUAL_DATE;
+ case rYearlyPos: return ANNUAL_POS;
+ default:
+ if (dailyType(rrule))
+ return DAILY;
+ return NO_RECUR;
+ }
+}
+
+/******************************************************************************
+ * Check if the rule is a daily rule with or without BYDAYS specified.
+ */
+bool KARecurrence::dailyType(const RecurrenceRule* rrule)
+{
+ if (rrule->recurrenceType() != RecurrenceRule::rDaily
+ || !rrule->bySeconds().isEmpty()
+ || !rrule->byMinutes().isEmpty()
+ || !rrule->byHours().isEmpty()
+ || !rrule->byWeekNumbers().isEmpty()
+ || !rrule->byMonthDays().isEmpty()
+ || !rrule->byMonths().isEmpty()
+ || !rrule->bySetPos().isEmpty()
+ || !rrule->byYearDays().isEmpty())
+ return false;
+ QValueList<RecurrenceRule::WDayPos> days = rrule->byDays();
+ if (days.isEmpty())
+ return true;
+ // Check that all the positions are zero (i.e. every time)
+ bool found = false;
+ for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = days.begin(); it != days.end(); ++it)
+ {
+ if ((*it).pos() != 0)
+ return false;
+ found = true;
+ }
+ return found;
+
+}
diff --git a/kalarm/karecurrence.h b/kalarm/karecurrence.h
new file mode 100644
index 00000000..ed0fc2fc
--- /dev/null
+++ b/kalarm/karecurrence.h
@@ -0,0 +1,89 @@
+/*
+ * karecurrence.h - recurrence with special yearly February 29th handling
+ * Program: kalarm
+ * Copyright © 2005,2006 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.
+ */
+
+#ifndef KARECURRENCE_H
+#define KARECURRENCE_H
+
+#include <libkcal/recurrence.h>
+class DateTime;
+
+
+class KARecurrence : public KCal::Recurrence
+{
+ public:
+ /** The recurrence's period type.
+ * This is a subset of the possible KCal recurrence types.
+ */
+ enum Type {
+ NO_RECUR, // does not recur
+ MINUTELY, // at an hours/minutes interval
+ DAILY, // daily
+ WEEKLY, // weekly, on specified weekdays
+ MONTHLY_POS, // monthly, on specified weekdays in a specified week of the month
+ MONTHLY_DAY, // monthly, on a specified day of the month
+ ANNUAL_DATE, // yearly, on a specified date in each of the specified months
+ ANNUAL_POS // yearly, on specified weekdays in the specified weeks of the specified months
+ };
+ /** The date on which a yearly February 29th recurrence falls in non-leap years */
+ enum Feb29Type {
+ FEB29_FEB29, // February 29th recurrences are omitted in non-leap years
+ FEB29_MAR1, // February 29th recurrences are on March 1st in non-leap years
+ FEB29_FEB28 // February 29th recurrences are on February 28th in non-leap years
+ };
+
+ KARecurrence() : KCal::Recurrence(), mFeb29Type(FEB29_FEB29), mCachedType(-1) { }
+ KARecurrence(const KCal::Recurrence& r) : KCal::Recurrence(r) { fix(); }
+ KARecurrence(const KARecurrence& r) : KCal::Recurrence(r), mFeb29Type(r.mFeb29Type), mCachedType(r.mCachedType) { }
+ bool set(const QString& icalRRULE);
+ bool set(Type t, int freq, int count, const DateTime& start, const QDateTime& end)
+ { return set(t, freq, count, -1, start, end); }
+ bool set(Type t, int freq, int count, const DateTime& start, const QDateTime& end, Feb29Type f29)
+ { return set(t, freq, count, f29, start, end); }
+ bool init(KCal::RecurrenceRule::PeriodType t, int freq, int count, const DateTime& start, const QDateTime& end)
+ { return init(t, freq, count, -1, start, end); }
+ bool init(KCal::RecurrenceRule::PeriodType t, int freq, int count, const DateTime& start, const QDateTime& end, Feb29Type f29)
+ { return init(t, freq, count, f29, start, end); }
+ void fix();
+ void writeRecurrence(KCal::Recurrence&) const;
+ QDateTime endDateTime() const;
+ QDate endDate() const;
+ bool recursOn(const QDate&) const;
+ QDateTime getNextDateTime(const QDateTime& preDateTime) const;
+ QDateTime getPreviousDateTime(const QDateTime& afterDateTime) const;
+ int longestInterval() const;
+ Type type() const;
+ static Type type(const KCal::RecurrenceRule*);
+ static bool dailyType(const KCal::RecurrenceRule*);
+ Feb29Type feb29Type() const { return mFeb29Type; }
+ static Feb29Type defaultFeb29Type() { return mDefaultFeb29; }
+ static void setDefaultFeb29Type(Feb29Type t) { mDefaultFeb29 = t; }
+
+ private:
+ bool set(Type, int freq, int count, int feb29Type, const DateTime& start, const QDateTime& end);
+ bool init(KCal::RecurrenceRule::PeriodType, int freq, int count, int feb29Type, const DateTime& start, const QDateTime& end);
+ int combineDurations(const KCal::RecurrenceRule*, const KCal::RecurrenceRule*, QDate& end) const;
+ int longestWeeklyInterval(const QBitArray& days, int frequency);
+
+ static Feb29Type mDefaultFeb29;
+ Feb29Type mFeb29Type; // yearly recurrence on Feb 29th (leap years) / Mar 1st (non-leap years)
+ mutable int mCachedType;
+};
+
+#endif // KARECURRENCE_H
diff --git a/kalarm/latecancel.cpp b/kalarm/latecancel.cpp
new file mode 100644
index 00000000..ed5563b1
--- /dev/null
+++ b/kalarm/latecancel.cpp
@@ -0,0 +1,161 @@
+/*
+ * latecancel.cpp - widget to specify cancellation if late
+ * Program: kalarm
+ * Copyright (C) 2004, 2005 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 "kalarm.h"
+
+#include <qwidgetstack.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+#include <klocale.h>
+#include <kdialog.h>
+
+#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 00000000..ccbc389b
--- /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 <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.
+ */
+
+#ifndef LATECANCEL_H
+#define LATECANCEL_H
+
+#include <qframe.h>
+
+#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 00000000..7d68a252
--- /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 00000000..b448df48
--- /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 <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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qbutton.h>
+#include <kdialog.h>
+
+#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 00000000..1d647b42
--- /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 <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.
+ */
+#ifndef BUTTONGROUP_H
+#define BUTTONGROUP_H
+
+#include <qbuttongroup.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..c600a495
--- /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 <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 "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 00000000..72ad4aee
--- /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 <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.
+ */
+
+#ifndef CHECKBOX_H
+#define CHECKBOX_H
+
+#include <qcheckbox.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..d5fa052a
--- /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 <software@astrojar.org.uk>
+ *
+ * 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 <qpainter.h>
+
+#include <klocale.h>
+#include <kcolordialog.h>
+
+#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 00000000..f3e4ebca
--- /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 <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.
+ */
+
+#ifndef COLOURCOMBO_H
+#define COLOURCOMBO_H
+
+#include <qcombobox.h>
+#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 <software@astrojar.org.uk>
+ */
+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 00000000..e02a6446
--- /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<QRgb> 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 00000000..ef641c04
--- /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 <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.
+ */
+
+#ifndef COLOURLIST_H
+#define COLOURLIST_H
+
+#include <qtl.h>
+#include <qcolor.h>
+#include <qvaluelist.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+class ColourList
+{
+ public:
+ typedef size_t size_type;
+ typedef QValueListConstIterator<QRgb> 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<QRgb>& 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<QRgb>& 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<QRgb> mList;
+};
+
+#endif // COLOURLIST_H
diff --git a/kalarm/lib/combobox.cpp b/kalarm/lib/combobox.cpp
new file mode 100644
index 00000000..7e0bea4b
--- /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 <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 <qlineedit.h>
+#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 00000000..d33ac147
--- /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 <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.
+ */
+
+#ifndef COMBOBOX_H
+#define COMBOBOX_H
+
+#include <qcombobox.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..71553d70
--- /dev/null
+++ b/kalarm/lib/dateedit.cpp
@@ -0,0 +1,122 @@
+/*
+ * dateedit.cpp - date entry widget
+ * Program: kalarm
+ * Copyright © 2002-2007 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 <kglobal.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#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 00000000..031a9674
--- /dev/null
+++ b/kalarm/lib/dateedit.h
@@ -0,0 +1,90 @@
+/*
+ * dateedit.h - date entry widget
+ * Program: kalarm
+ * Copyright © 2002-2007 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.
+ */
+#ifndef DATEEDIT_H
+#define DATEEDIT_H
+
+#include <libkdepim/kdateedit.h>
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..d7f5ee86
--- /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 <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 "kalarm.h"
+
+#include <kglobal.h>
+#include <klocale.h>
+
+#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 00000000..3b083191
--- /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 <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.
+ */
+#ifndef DATETIME_H
+#define DATETIME_H
+
+#include <qdatetime.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+*/
+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 00000000..c61ce76a
--- /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 <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 "kalarm.h"
+#include <qradiobutton.h>
+#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 00000000..c65a7fcd
--- /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 <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.
+ */
+
+#ifndef LABEL_H
+#define LABEL_H
+
+#include <qlabel.h>
+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 <software@astrojar.org.uk>
+ */
+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 00000000..943d7b2d
--- /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 <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 "kalarm.h"
+
+#include <qregexp.h>
+#include <qdragobject.h>
+
+#include <kurldrag.h>
+#include <kurlcompletion.h>
+
+#include <libkdepim/maillistdrag.h>
+#include <libkdepim/kvcarddrag.h>
+#include <libkcal/icaldrag.h>
+
+#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<int>(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 00000000..57f8378e
--- /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 <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.
+ */
+
+#ifndef LINEEDIT_H
+#define LINEEDIT_H
+
+#include <klineedit.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..514b45bc
--- /dev/null
+++ b/kalarm/lib/messagebox.cpp
@@ -0,0 +1,178 @@
+/*
+ * messagebox.cpp - enhanced KMessageBox class
+ * Program: kalarm
+ * Copyright (C) 2004 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 "kalarm.h"
+#include <kconfig.h>
+#include "messagebox.h"
+
+
+KConfig* MessageBox::mConfig = 0;
+QMap<QString, KMessageBox::ButtonCode> 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<QString, ButtonCode>::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 00000000..32e0d732
--- /dev/null
+++ b/kalarm/lib/messagebox.h
@@ -0,0 +1,125 @@
+/*
+ * messagebox.h - enhanced KMessageBox class
+ * Program: kalarm
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef MESSAGEBOX_H
+#define MESSAGEBOX_H
+
+#include <kstdguiitem.h>
+#include <kmessagebox.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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<QString, ButtonCode> mContinueDefaults;
+};
+
+#endif
diff --git a/kalarm/lib/pushbutton.cpp b/kalarm/lib/pushbutton.cpp
new file mode 100644
index 00000000..8b279082
--- /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 <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 "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 00000000..6ef0f3a2
--- /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 <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.
+ */
+
+#ifndef PUSHBUTTON_H
+#define PUSHBUTTON_H
+
+#include <qpushbutton.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..d66911ee
--- /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 <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 "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 00000000..96bca04d
--- /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 <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.
+ */
+
+#ifndef RADIOBUTTON_H
+#define RADIOBUTTON_H
+
+#include <qradiobutton.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..1e37d2e7
--- /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 <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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <kapplication.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#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 00000000..06a262a8
--- /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 <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.
+ */
+
+#ifndef SHELLPROCESS_H
+#define SHELLPROCESS_H
+
+/** @file shellprocess.h - execute a process through the shell */
+
+#include <kprocess.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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<QCString> 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 00000000..1a099964
--- /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 <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 "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 00000000..17635e68
--- /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 <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.
+ */
+
+#ifndef SLIDER_H
+#define SLIDER_H
+
+#include <qslider.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..bb9ef244
--- /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 <djarvie@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 <kdeversion.h>
+#include <qlineedit.h>
+#include <qobjectlist.h>
+#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 00000000..0df91023
--- /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 <djarvie@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.
+ */
+
+#ifndef SPINBOX_H
+#define SPINBOX_H
+
+#include <qspinbox.h>
+
+
+/**
+ * @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 <software@astrojar.org.uk>
+ */
+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 00000000..313d3eb5
--- /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 <djarvie@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 <qglobal.h>
+
+#include <stdlib.h>
+
+#include <qstyle.h>
+#include <qobjectlist.h>
+#include <qapplication.h>
+#include <qpixmap.h>
+#include <qcursor.h>
+#include <qtimer.h>
+#include <qwmatrix.h>
+
+#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<int>(ro) != static_cast<int>(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 00000000..772eaee7
--- /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 <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.
+ */
+
+#ifndef SPINBOX2_H
+#define SPINBOX2_H
+
+#include <qglobal.h>
+#include <qlineedit.h>
+
+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 <software@astrojar.org.uk>
+ */
+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 00000000..74fbeb66
--- /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 <djarvie@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.
+ */
+
+#ifndef SPINBOX2PRIVATE_H
+#define SPINBOX2PRIVATE_H
+
+#include <qcanvas.h>
+#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 00000000..1b681d6e
--- /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 <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 "kalarm.h"
+#include <qtimer.h>
+#include <kdebug.h>
+#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<Connection>::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*> 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<DailyTimer*>::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 00000000..551fd168
--- /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 <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.
+ */
+
+#ifndef SYNCHTIMER_H
+#define SYNCHTIMER_H
+
+/* @file synchtimer.h - timers which synchronise to time boundaries */
+
+#include <qobject.h>
+#include <qvaluelist.h>
+#include <qcstring.h>
+#include <qdatetime.h>
+class QTimer;
+
+/** SynchTimer is a virtual base class for application-wide timers synchronised
+ * to a time boundary.
+ *
+ * @author David Jarvie <software@astrojar.org.uk>
+ */
+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<Connection> mConnections; // list of current clients
+};
+
+
+/** MinuteTimer is an application-wide timer synchronised to the minute boundary.
+ *
+ * @author David Jarvie <software@astrojar.org.uk>
+ */
+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 <software@astrojar.org.uk>
+ */
+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<DailyTimer*> 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 <software@astrojar.org.uk>
+ */
+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 00000000..8aabb90b
--- /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 <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 "kalarm.h"
+
+#include <kglobal.h>
+#include <klocale.h>
+
+#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 00000000..5888c766
--- /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 <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.
+ */
+
+#ifndef TIMEEDIT_H
+#define TIMEEDIT_H
+
+#include <qdatetime.h>
+#include <qhbox.h>
+
+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 <software@astrojar.org.uk>
+ */
+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 00000000..22a7c317
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qwidgetstack.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kdialog.h>
+
+#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<Units>(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<Units>(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 00000000..ef1a3b5c
--- /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 <djarvie@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.
+ */
+
+#ifndef TIMEPERIOD_H
+#define TIMEPERIOD_H
+
+#include <qhbox.h>
+#include <qstring.h>
+
+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 <software@astrojar.org.uk>
+ */
+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 00000000..7a22cd23
--- /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 <djarvie@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 "kalarm.h"
+
+#include <qvalidator.h>
+#include <qlineedit.h>
+#include <klocale.h>
+
+#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 00000000..275fdf30
--- /dev/null
+++ b/kalarm/lib/timespinbox.h
@@ -0,0 +1,127 @@
+/*
+ * timespinbox.h - time spinbox widget
+ * Program: kalarm
+ * Copyright © 2001-2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef TIMESPINBOX_H
+#define TIMESPINBOX_H
+
+#include <qdatetime.h>
+#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 <software@astrojar.org.uk>
+ */
+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 00000000..0e1906ce
--- /dev/null
+++ b/kalarm/main.cpp
@@ -0,0 +1,131 @@
+/*
+ * main.cpp
+ * Program: kalarm
+ * Copyright © 2001-2007 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 "kalarm.h"
+
+#include <stdlib.h>
+
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#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 <url>", 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 <color>", I18N_NOOP("Message background color (name or hex 0xRRGGBB)"), 0 },
+ { "colourfg", 0, 0 },
+ { "C", 0, 0 },
+ { "colorfg <color>", I18N_NOOP("Message foreground color (name or hex 0xRRGGBB)"), 0 },
+ { "calendarURL <url>", I18N_NOOP("URL of calendar file"), 0 },
+ { "cancelEvent <eventID>", I18N_NOOP("Cancel alarm with the specified event ID"), 0 },
+ { "d", 0, 0 },
+ { "disable", I18N_NOOP("Disable the alarm"), 0 },
+ { "e", 0, 0 },
+ { "!exec <commandline>", I18N_NOOP("Execute a shell command line"), 0 },
+ { "edit <eventID>", 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 <templateName>", I18N_NOOP("Display the alarm edit dialog, preset with a template"), 0 },
+ { "f", 0, 0 },
+ { "file <url>", I18N_NOOP("File to display"), 0 },
+ { "F", 0, 0 },
+ { "from-id <ID>", I18N_NOOP("KMail identity to use as sender of email"), 0 },
+ { "handleEvent <eventID>", I18N_NOOP("Trigger or cancel alarm with the specified event ID"), 0 },
+ { "i", 0, 0 },
+ { "interval <period>", 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 <period>", 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 <address>", I18N_NOOP("Send an email to the given address (repeat as needed)"), 0 },
+ { "p", 0, 0 },
+ { "play <url>", I18N_NOOP("Audio file to play once"), 0 },
+#ifndef WITHOUT_ARTS
+ { "P", 0, 0 },
+ { "play-repeat <url>", I18N_NOOP("Audio file to play repeatedly"), 0 },
+#endif
+ { "recurrence <spec>", I18N_NOOP("Specify alarm recurrence using iCalendar syntax"), 0 },
+ { "R", 0, 0 },
+ { "reminder <period>", I18N_NOOP("Display reminder in advance of alarm"), 0 },
+ { "reminder-once <period>", I18N_NOOP("Display reminder once, before first alarm recurrence"), 0 },
+ { "r", 0, 0 },
+ { "repeat <count>", 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 <time>", I18N_NOOP("Trigger alarm at time [[[yyyy-]mm-]dd-]hh:mm, or date yyyy-mm-dd"), 0 },
+ { "tray", I18N_NOOP("Display system tray icon"), 0 },
+ { "triggerEvent <eventID>", I18N_NOOP("Trigger alarm with the specified event ID"), 0 },
+ { "u", 0, 0 },
+ { "until <time>", I18N_NOOP("Repeat until time [[[yyyy-]mm-]dd-]hh:mm, or date yyyy-mm-dd"), 0 },
+#ifndef WITHOUT_ARTS
+ { "V", 0, 0 },
+ { "volume <percent>", I18N_NOOP("Volume to play audio file"), 0 },
+#endif
+ { "+[message]", I18N_NOOP("Message text to display"), 0 },
+ KCmdLineLastOption
+};
+
+
+int main(int argc, char *argv[])
+{
+ KAboutData aboutData(PROGRAM_NAME, I18N_NOOP("KAlarm"), KALARM_VERSION,
+ I18N_NOOP("Personal alarm message, command and email scheduler for KDE"),
+ KAboutData::License_GPL,
+ "Copyright 2001-2007, David Jarvie", 0, "http://www.astrojar.org.uk/kalarm");
+ aboutData.addAuthor("David Jarvie", 0, "software@astrojar.org.uk");
+
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KCmdLineArgs::addCmdLineOptions(options);
+ KUniqueApplication::addCmdLineOptions();
+
+ if (!KAlarmApp::start())
+ {
+ // An instance of the application is already running
+ exit(0);
+ }
+
+ // This is the first time through
+ kdDebug(5950) << "main(): initialising\n";
+ KAlarmApp* app = KAlarmApp::getInstance();
+ app->restoreSession();
+ return app->exec();
+}
diff --git a/kalarm/mainwindow.cpp b/kalarm/mainwindow.cpp
new file mode 100644
index 00000000..01ddc83d
--- /dev/null
+++ b/kalarm/mainwindow.cpp
@@ -0,0 +1,1424 @@
+/*
+ * mainwindow.cpp - main application window
+ * Program: kalarm
+ * Copyright © 2001-2007 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <qiconset.h>
+#include <qdragobject.h>
+#include <qheader.h>
+
+#include <kmenubar.h>
+#include <ktoolbar.h>
+#include <kpopupmenu.h>
+#include <kaccel.h>
+#include <kaction.h>
+#include <kactionclasses.h>
+#include <kstdaction.h>
+#include <kiconloader.h>
+#include <kmessagebox.h>
+#include <kurldrag.h>
+#include <klocale.h>
+#include <kglobalsettings.h>
+#include <kconfig.h>
+#include <kkeydialog.h>
+#include <kedittoolbar.h>
+#include <kaboutdata.h>
+#include <dcopclient.h>
+#include <kdebug.h>
+
+#include <libkdepim/maillistdrag.h>
+#include <libkmime/kmime_content.h>
+#include <libkcal/calendarlocal.h>
+#include <libkcal/icaldrag.h>
+
+#include "alarmcalendar.h"
+#include "alarmevent.h"
+#include "alarmlistview.h"
+#include "alarmtext.h"
+#include "birthdaydlg.h"
+#include "daemon.h"
+#include "editdlg.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "kamail.h"
+#include "prefdlg.h"
+#include "preferences.h"
+#include "synchtimer.h"
+#include "templatepickdlg.h"
+#include "templatedlg.h"
+#include "traywindow.h"
+#include "mainwindow.moc"
+
+using namespace KCal;
+
+static const char* UI_FILE = "kalarmui.rc";
+static const char* WINDOW_NAME = "MainWindow";
+
+static const QString VIEW_GROUP = QString::fromLatin1("View");
+static const QString SHOW_TIME_KEY = QString::fromLatin1("ShowAlarmTime");
+static const QString SHOW_TIME_TO_KEY = QString::fromLatin1("ShowTimeToAlarm");
+static const QString SHOW_ARCHIVED_KEY = QString::fromLatin1("ShowArchivedAlarms");
+static const QString SHOW_RESOURCES_KEY = QString::fromLatin1("ShowResources");
+
+static QString undoText;
+static QString undoTextStripped;
+static QString undoIcon;
+static KShortcut undoShortcut;
+static QString redoText;
+static QString redoTextStripped;
+static QString redoIcon;
+static KShortcut redoShortcut;
+
+
+/*=============================================================================
+= Class: MainWindow
+=============================================================================*/
+
+MainWindow::WindowList MainWindow::mWindowList;
+TemplateDlg* MainWindow::mTemplateDlg = 0;
+
+// Collect these widget labels together to ensure consistent wording and
+// translations across different modules.
+QString MainWindow::i18n_a_ShowAlarmTimes() { return i18n("Show &Alarm Times"); }
+QString MainWindow::i18n_m_ShowAlarmTime() { return i18n("Show alarm ti&me"); }
+QString MainWindow::i18n_o_ShowTimeToAlarms() { return i18n("Show Time t&o Alarms"); }
+QString MainWindow::i18n_l_ShowTimeToAlarm() { return i18n("Show time unti&l alarm"); }
+QString MainWindow::i18n_ShowExpiredAlarms() { return i18n("Show Expired Alarms"); }
+QString MainWindow::i18n_e_ShowExpiredAlarms() { return i18n("Show &Expired Alarms"); }
+QString MainWindow::i18n_HideExpiredAlarms() { return i18n("Hide Expired Alarms"); }
+QString MainWindow::i18n_e_HideExpiredAlarms() { return i18n("Hide &Expired Alarms"); }
+
+
+/******************************************************************************
+* Construct an instance.
+* To avoid resize() events occurring while still opening the calendar (and
+* resultant crashes), the calendar is opened before constructing the instance.
+*/
+MainWindow* MainWindow::create(bool restored)
+{
+ theApp()->checkCalendarDaemon(); // ensure calendar is open and daemon started
+ return new MainWindow(restored);
+}
+
+MainWindow::MainWindow(bool restored)
+ : MainWindowBase(0, "MainWin", WGroupLeader | WStyle_ContextHelp | WDestructiveClose),
+ mMinuteTimerActive(false),
+ mHiddenTrayParent(false)
+{
+ kdDebug(5950) << "MainWindow::MainWindow()\n";
+ setAutoSaveSettings(QString::fromLatin1(WINDOW_NAME)); // save window sizes etc.
+ setPlainCaption(kapp->aboutData()->programName());
+ KConfig* config = KGlobal::config();
+ config->setGroup(VIEW_GROUP);
+ mShowExpired = config->readBoolEntry(SHOW_ARCHIVED_KEY, false);
+ mShowTime = config->readBoolEntry(SHOW_TIME_KEY, true);
+ mShowTimeTo = config->readBoolEntry(SHOW_TIME_TO_KEY, false);
+ if (!restored)
+ {
+ QSize s;
+ if (KAlarm::readConfigWindowSize(WINDOW_NAME, s))
+ resize(s);
+ }
+ config->setGroup(QString::fromLatin1(WINDOW_NAME));
+ QValueList<int> order = config->readIntListEntry(QString::fromLatin1("ColumnOrder"));
+
+ setAcceptDrops(true); // allow drag-and-drop onto this window
+ if (!mShowTimeTo)
+ mShowTime = true; // ensure at least one time column is visible
+ mListView = new AlarmListView(order, this, "listView");
+ mListView->selectTimeColumns(mShowTime, mShowTimeTo);
+ mListView->showExpired(mShowExpired);
+ setCentralWidget(mListView);
+ mListView->refresh(); // populate the alarm list
+ mListView->clearSelection();
+
+ connect(mListView, SIGNAL(itemDeleted()), SLOT(slotDeletion()));
+ connect(mListView, SIGNAL(selectionChanged()), SLOT(slotSelection()));
+ connect(mListView, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
+ SLOT(slotContextMenuRequested(QListViewItem*, const QPoint&, int)));
+ connect(mListView, SIGNAL(mouseButtonClicked(int, QListViewItem*, const QPoint&, int)),
+ SLOT(slotMouseClicked(int, QListViewItem*, const QPoint&, int)));
+ connect(mListView, SIGNAL(executed(QListViewItem*)), SLOT(slotDoubleClicked(QListViewItem*)));
+ connect(mListView->header(), SIGNAL(indexChange(int, int, int)), SLOT(columnsReordered()));
+ initActions();
+
+ mWindowList.append(this);
+ if (mWindowList.count() == 1 && Daemon::isDcopHandlerReady())
+ {
+ // It's the first main window, and the DCOP handler is ready
+ if (theApp()->wantRunInSystemTray())
+ theApp()->displayTrayIcon(true, this); // create system tray icon for run-in-system-tray mode
+ else if (theApp()->trayWindow())
+ theApp()->trayWindow()->setAssocMainWindow(this); // associate this window with the system tray icon
+ }
+ setUpdateTimer();
+}
+
+MainWindow::~MainWindow()
+{
+ kdDebug(5950) << "MainWindow::~MainWindow()\n";
+ mWindowList.remove(this);
+ if (theApp()->trayWindow())
+ {
+ if (isTrayParent())
+ delete theApp()->trayWindow();
+ else
+ theApp()->trayWindow()->removeWindow(this);
+ }
+ MinuteTimer::disconnect(this);
+ mMinuteTimerActive = false; // to ensure that setUpdateTimer() works correctly
+ setUpdateTimer();
+ MainWindow* main = mainMainWindow();
+ if (main)
+ KAlarm::writeConfigWindowSize(WINDOW_NAME, main->size());
+ KToolBar* tb = toolBar();
+ if (tb)
+ tb->saveSettings(KGlobal::config(), "Toolbars");
+ KGlobal::config()->sync(); // save any new window size to disc
+ theApp()->quitIf();
+}
+
+/******************************************************************************
+* Save settings to the session managed config file, for restoration
+* when the program is restored.
+*/
+void MainWindow::saveProperties(KConfig* config)
+{
+ config->writeEntry(QString::fromLatin1("HiddenTrayParent"), isTrayParent() && isHidden());
+ config->writeEntry(QString::fromLatin1("ShowExpired"), mShowExpired);
+ config->writeEntry(QString::fromLatin1("ShowTime"), mShowTime);
+ config->writeEntry(QString::fromLatin1("ShowTimeTo"), mShowTimeTo);
+}
+
+/******************************************************************************
+* Read settings from the session managed config file.
+* This function is automatically called whenever the app is being
+* restored. Read in whatever was saved in saveProperties().
+*/
+void MainWindow::readProperties(KConfig* config)
+{
+ mHiddenTrayParent = config->readBoolEntry(QString::fromLatin1("HiddenTrayParent"));
+ mShowExpired = config->readBoolEntry(QString::fromLatin1("ShowExpired"));
+ mShowTime = config->readBoolEntry(QString::fromLatin1("ShowTime"));
+ mShowTimeTo = config->readBoolEntry(QString::fromLatin1("ShowTimeTo"));
+}
+
+/******************************************************************************
+* Get the main main window, i.e. the parent of the system tray icon, or if
+* none, the first main window to be created. Visible windows take precedence
+* over hidden ones.
+*/
+MainWindow* MainWindow::mainMainWindow()
+{
+ MainWindow* tray = theApp()->trayWindow() ? theApp()->trayWindow()->assocMainWindow() : 0;
+ if (tray && tray->isVisible())
+ return tray;
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ if ((*it)->isVisible())
+ return *it;
+ if (tray)
+ return tray;
+ if (mWindowList.isEmpty())
+ return 0;
+ return mWindowList.first();
+}
+
+/******************************************************************************
+* Check whether this main window is the parent of the system tray icon.
+*/
+bool MainWindow::isTrayParent() const
+{
+ return theApp()->wantRunInSystemTray() && theApp()->trayMainWindow() == this;
+}
+
+/******************************************************************************
+* Close all main windows.
+*/
+void MainWindow::closeAll()
+{
+ while (!mWindowList.isEmpty())
+ delete mWindowList.first(); // N.B. the destructor removes the window from the list
+}
+
+/******************************************************************************
+* Called when the window'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.
+* Records the new size in the config file.
+*/
+void MainWindow::resizeEvent(QResizeEvent* re)
+{
+ // Save the window's new size only if it's the first main window
+ if (mainMainWindow() == this)
+ KAlarm::writeConfigWindowSize(WINDOW_NAME, re->size());
+ MainWindowBase::resizeEvent(re);
+}
+
+/******************************************************************************
+* Called when the window 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 MainWindow::showEvent(QShowEvent* se)
+{
+ setUpdateTimer();
+ slotUpdateTimeTo();
+ MainWindowBase::showEvent(se);
+}
+
+/******************************************************************************
+* Display the window.
+*/
+void MainWindow::show()
+{
+ MainWindowBase::show();
+ if (mMenuError)
+ {
+ // Show error message now that the main window has been displayed.
+ // Waiting until now lets the user easily associate the message with
+ // the main window which is faulty.
+ KMessageBox::error(this, i18n("Failure to create menus\n(perhaps %1 missing or corrupted)").arg(QString::fromLatin1(UI_FILE)));
+ mMenuError = false;
+ }
+}
+
+/******************************************************************************
+* Called after the window is hidden.
+*/
+void MainWindow::hideEvent(QHideEvent* he)
+{
+ setUpdateTimer();
+ MainWindowBase::hideEvent(he);
+}
+
+/******************************************************************************
+* Called when the list's column order is changed.
+* Save the new column order as the default the next time the program is run.
+*/
+void MainWindow::columnsReordered()
+{
+ KConfig* config = KGlobal::config();
+ config->setGroup(QString::fromLatin1(WINDOW_NAME));
+ config->writeEntry(QString::fromLatin1("ColumnOrder"), mListView->columnOrder());
+ config->sync();
+}
+
+/******************************************************************************
+* Initialise the menu, toolbar and main window actions.
+*/
+void MainWindow::initActions()
+{
+ KActionCollection* actions = actionCollection();
+ mActionTemplates = new KAction(i18n("&Templates..."), 0, this, SLOT(slotTemplates()), actions, "templates");
+ mActionNew = KAlarm::createNewAlarmAction(i18n("&New..."), this, SLOT(slotNew()), actions, "new");
+ mActionNewFromTemplate = KAlarm::createNewFromTemplateAction(i18n("New &From Template"), this, SLOT(slotNewFromTemplate(const KAEvent&)), actions, "newFromTempl");
+ mActionCreateTemplate = new KAction(i18n("Create Tem&plate..."), 0, this, SLOT(slotNewTemplate()), actions, "createTemplate");
+ mActionCopy = new KAction(i18n("&Copy..."), "editcopy", Qt::SHIFT+Qt::Key_Insert, this, SLOT(slotCopy()), actions, "copy");
+ mActionModify = new KAction(i18n("&Edit..."), "edit", Qt::CTRL+Qt::Key_E, this, SLOT(slotModify()), actions, "modify");
+ mActionDelete = new KAction(i18n("&Delete"), "editdelete", Qt::Key_Delete, this, SLOT(slotDelete()), actions, "delete");
+ mActionReactivate = new KAction(i18n("Reac&tivate"), 0, Qt::CTRL+Qt::Key_R, this, SLOT(slotReactivate()), actions, "undelete");
+ mActionEnable = new KAction(QString::null, 0, Qt::CTRL+Qt::Key_B, this, SLOT(slotEnable()), actions, "disable");
+ mActionView = new KAction(i18n("&View"), "viewmag", Qt::CTRL+Qt::Key_W, this, SLOT(slotView()), actions, "view");
+ mActionShowTime = new KToggleAction(i18n_a_ShowAlarmTimes(), Qt::CTRL+Qt::Key_M, this, SLOT(slotShowTime()), actions, "showAlarmTimes");
+ mActionShowTime->setCheckedState(i18n("Hide &Alarm Times"));
+ mActionShowTimeTo = new KToggleAction(i18n_o_ShowTimeToAlarms(), Qt::CTRL+Qt::Key_I, this, SLOT(slotShowTimeTo()), actions, "showTimeToAlarms");
+ mActionShowTimeTo->setCheckedState(i18n("Hide Time t&o Alarms"));
+ mActionShowExpired = new KToggleAction(i18n_e_ShowExpiredAlarms(), "history", Qt::CTRL+Qt::Key_P, this, SLOT(slotShowExpired()), actions, "showExpiredAlarms");
+ mActionShowExpired->setCheckedState(i18n_e_HideExpiredAlarms());
+ mActionToggleTrayIcon = new KToggleAction(i18n("Show in System &Tray"), 0, this, SLOT(slotToggleTrayIcon()), actions, "showInSystemTray");
+ mActionToggleTrayIcon->setCheckedState(i18n("Hide From System &Tray"));
+ new KAction(i18n("Import &Alarms..."), 0, this, SLOT(slotImportAlarms()), actions, "importAlarms");
+ new KAction(i18n("Import &Birthdays..."), 0, this, SLOT(slotBirthdays()), actions, "importBirthdays");
+ new KAction(i18n("&Refresh Alarms"), "reload", 0, this, SLOT(slotResetDaemon()), actions, "refreshAlarms");
+ Daemon::createAlarmEnableAction(actions, "alarmEnable");
+ if (undoText.isNull())
+ {
+ // Get standard texts, etc., for Undo and Redo actions
+ KAction* act = KStdAction::undo(this, 0, actions);
+ undoIcon = act->icon();
+ undoShortcut = act->shortcut();
+ undoText = act->text();
+ undoTextStripped = KAlarm::stripAccel(undoText);
+ delete act;
+ act = KStdAction::redo(this, 0, actions);
+ redoIcon = act->icon();
+ redoShortcut = act->shortcut();
+ redoText = act->text();
+ redoTextStripped = KAlarm::stripAccel(redoText);
+ delete act;
+ }
+ mActionUndo = new KToolBarPopupAction(undoText, undoIcon, undoShortcut, this, SLOT(slotUndo()), actions, "edit_undo");
+ mActionRedo = new KToolBarPopupAction(redoText, redoIcon, redoShortcut, this, SLOT(slotRedo()), actions, "edit_redo");
+ KStdAction::find(mListView, SLOT(slotFind()), actions);
+ mActionFindNext = KStdAction::findNext(mListView, SLOT(slotFindNext()), actions);
+ mActionFindPrev = KStdAction::findPrev(mListView, SLOT(slotFindPrev()), actions);
+ KStdAction::selectAll(mListView, SLOT(slotSelectAll()), actions);
+ KStdAction::deselect(mListView, SLOT(slotDeselect()), actions);
+ KStdAction::quit(this, SLOT(slotQuit()), actions);
+ KStdAction::keyBindings(this, SLOT(slotConfigureKeys()), actions);
+ KStdAction::configureToolbars(this, SLOT(slotConfigureToolbar()), actions);
+ KStdAction::preferences(this, SLOT(slotPreferences()), actions);
+ setStandardToolBarMenuEnabled(true);
+ createGUI(UI_FILE);
+
+ mContextMenu = static_cast<KPopupMenu*>(factory()->container("listContext", this));
+ mActionsMenu = static_cast<KPopupMenu*>(factory()->container("actions", this));
+ mMenuError = (!mContextMenu || !mActionsMenu);
+ connect(mActionsMenu, SIGNAL(aboutToShow()), SLOT(updateActionsMenu()));
+ connect(mActionUndo->popupMenu(), SIGNAL(aboutToShow()), SLOT(slotInitUndoMenu()));
+ connect(mActionUndo->popupMenu(), SIGNAL(activated(int)), SLOT(slotUndoItem(int)));
+ connect(mActionRedo->popupMenu(), SIGNAL(aboutToShow()), SLOT(slotInitRedoMenu()));
+ connect(mActionRedo->popupMenu(), SIGNAL(activated(int)), SLOT(slotRedoItem(int)));
+ connect(Undo::instance(), SIGNAL(changed(const QString&, const QString&)), SLOT(slotUndoStatus(const QString&, const QString&)));
+ connect(mListView, SIGNAL(findActive(bool)), SLOT(slotFindActive(bool)));
+ Preferences::connect(SIGNAL(preferencesChanged()), this, SLOT(slotPrefsChanged()));
+ connect(theApp(), SIGNAL(trayIconToggled()), SLOT(updateTrayIconAction()));
+
+ // Set menu item states
+ setEnableText(true);
+ mActionShowTime->setChecked(mShowTime);
+ mActionShowTimeTo->setChecked(mShowTimeTo);
+ mActionShowExpired->setChecked(mShowExpired);
+ slotPrefsChanged(); // set the correct text for this action
+ mActionUndo->setEnabled(Undo::haveUndo());
+ mActionRedo->setEnabled(Undo::haveRedo());
+ mActionFindNext->setEnabled(false);
+ mActionFindPrev->setEnabled(false);
+
+ mActionCopy->setEnabled(false);
+ mActionModify->setEnabled(false);
+ mActionDelete->setEnabled(false);
+ mActionReactivate->setEnabled(false);
+ mActionView->setEnabled(false);
+ mActionEnable->setEnabled(false);
+ mActionCreateTemplate->setEnabled(false);
+
+ KToolBar* tb = toolBar();
+ if (tb)
+ tb->applySettings(KGlobal::config(), "Toolbars");
+
+ Undo::emitChanged(); // set the Undo/Redo menu texts
+ Daemon::checkStatus();
+ Daemon::monitoringAlarms();
+}
+
+/******************************************************************************
+* Enable or disable the Templates menu item in every main window instance.
+*/
+void MainWindow::enableTemplateMenuItem(bool enable)
+{
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ (*it)->mActionTemplates->setEnabled(enable);
+}
+
+/******************************************************************************
+* Refresh the alarm list in every main window instance.
+*/
+void MainWindow::refresh()
+{
+ kdDebug(5950) << "MainWindow::refresh()\n";
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ (*it)->mListView->refresh();
+}
+
+/******************************************************************************
+* Refresh the alarm list in every main window instance which is displaying
+* expired alarms.
+* Called when an expired alarm setting changes in the user preferences.
+*/
+void MainWindow::updateExpired()
+{
+ kdDebug(5950) << "MainWindow::updateExpired()\n";
+ bool enableShowExpired = Preferences::expiredKeepDays();
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ {
+ MainWindow* w = *it;
+ if (w->mShowExpired)
+ {
+ if (!enableShowExpired)
+ w->slotShowExpired();
+ else
+ w->mListView->refresh();
+ }
+ w->mActionShowExpired->setEnabled(enableShowExpired);
+ }
+}
+
+/******************************************************************************
+* Start or stop the timer which updates the time-to-alarm values every minute.
+* Should be called whenever a main window is created or destroyed, or shown or
+* hidden.
+*/
+void MainWindow::setUpdateTimer()
+{
+ // Check whether any windows need to be updated
+ MainWindow* needTimer = 0;
+ MainWindow* timerWindow = 0;
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ {
+ MainWindow* w = *it;
+ if (w->isVisible() && w->mListView->showingTimeTo())
+ needTimer = w;
+ if (w->mMinuteTimerActive)
+ timerWindow = w;
+ }
+
+ // Start or stop the update timer if necessary
+ if (needTimer && !timerWindow)
+ {
+ // Timeout every minute.
+ needTimer->mMinuteTimerActive = true;
+ MinuteTimer::connect(needTimer, SLOT(slotUpdateTimeTo()));
+ kdDebug(5950) << "MainWindow::setUpdateTimer(): started timer" << endl;
+ }
+ else if (!needTimer && timerWindow)
+ {
+ timerWindow->mMinuteTimerActive = false;
+ MinuteTimer::disconnect(timerWindow);
+ kdDebug(5950) << "MainWindow::setUpdateTimer(): stopped timer" << endl;
+ }
+}
+/******************************************************************************
+* Update the time-to-alarm values for each main window which is displaying them.
+*/
+void MainWindow::slotUpdateTimeTo()
+{
+ kdDebug(5950) << "MainWindow::slotUpdateTimeTo()" << endl;
+ for (WindowList::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ {
+ MainWindow* w = *it;
+ if (w->isVisible() && w->mListView->showingTimeTo())
+ w->mListView->updateTimeToAlarms();
+ }
+}
+
+/******************************************************************************
+* Select an alarm in the displayed list.
+*/
+void MainWindow::selectEvent(const QString& eventID)
+{
+ mListView->clearSelection();
+ AlarmListViewItem* item = mListView->getEntry(eventID);
+ if (item)
+ {
+ mListView->setSelected(item, true);
+ mListView->setCurrentItem(item);
+ mListView->ensureItemVisible(item);
+ }
+}
+
+/******************************************************************************
+* Called when the New button is clicked to edit a new alarm to add to the list.
+*/
+void MainWindow::slotNew()
+{
+ executeNew(this);
+}
+
+/******************************************************************************
+* Execute a New Alarm dialog, optionally either presetting it to the supplied
+* event, or setting the action and text.
+*/
+void MainWindow::executeNew(MainWindow* win, const KAEvent* evnt, KAEvent::Action action, const AlarmText& text)
+{
+ EditAlarmDlg editDlg(false, i18n("New Alarm"), win, 0, evnt);
+ if (!text.isEmpty())
+ editDlg.setAction(action, text);
+ if (editDlg.exec() == QDialog::Accepted)
+ {
+ KAEvent event;
+ editDlg.getEvent(event);
+
+ // Add the alarm to the displayed lists and to the calendar file
+ if (KAlarm::addEvent(event, (win ? win->mListView : 0), &editDlg) == KAlarm::UPDATE_KORG_ERR)
+ KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_ADD, 1);
+ Undo::saveAdd(event);
+
+ KAlarm::outputAlarmWarnings(&editDlg, &event);
+ }
+}
+
+/******************************************************************************
+* Called when a template is selected from the New From Template popup menu.
+* Executes a New Alarm dialog, preset from the selected template.
+*/
+void MainWindow::slotNewFromTemplate(const KAEvent& tmplate)
+{
+ executeNew(this, &tmplate);
+}
+
+/******************************************************************************
+* Called when the New Template button is clicked to create a new template
+* based on the currently selected alarm.
+*/
+void MainWindow::slotNewTemplate()
+{
+ AlarmListViewItem* item = mListView->selectedItem();
+ if (item)
+ {
+ KAEvent event = item->event();
+ TemplateDlg::createTemplate(&event, this);
+ }
+}
+
+/******************************************************************************
+* Called when the Copy button is clicked to edit a copy of an existing alarm,
+* to add to the list.
+*/
+void MainWindow::slotCopy()
+{
+ AlarmListViewItem* item = mListView->selectedItem();
+ if (item)
+ executeNew(this, &item->event());
+}
+
+/******************************************************************************
+* Called when the Modify button is clicked to edit the currently highlighted
+* alarm in the list.
+*/
+void MainWindow::slotModify()
+{
+ AlarmListViewItem* item = mListView->selectedItem();
+ if (item)
+ {
+ KAEvent event = item->event();
+ executeEdit(event, this);
+ }
+}
+
+/******************************************************************************
+* Open the Edit Alarm dialogue to edit the specified alarm.
+*/
+void MainWindow::executeEdit(KAEvent& event, MainWindow* win)
+{
+ EditAlarmDlg editDlg(false, i18n("Edit Alarm"), win, 0, &event);
+ if (editDlg.exec() == QDialog::Accepted)
+ {
+ KAEvent newEvent;
+ bool changeDeferral = !editDlg.getEvent(newEvent);
+
+ // Update the event in the displays and in the calendar file
+ AlarmListView* view = win ? win->mListView : 0;
+ if (changeDeferral)
+ {
+ // The only change has been to an existing deferral
+ if (KAlarm::updateEvent(newEvent, view, &editDlg, true, false) != KAlarm::UPDATE_OK) // keep the same event ID
+ return; // failed to save event
+ }
+ else
+ {
+ if (KAlarm::modifyEvent(event, newEvent, view, &editDlg) == KAlarm::UPDATE_KORG_ERR)
+ KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_MODIFY, 1);
+ }
+ Undo::saveEdit(event, newEvent);
+
+ KAlarm::outputAlarmWarnings(&editDlg, &newEvent);
+ }
+}
+
+/******************************************************************************
+* Called when the View button is clicked to view the currently highlighted
+* alarm in the list.
+*/
+void MainWindow::slotView()
+{
+ AlarmListViewItem* item = mListView->selectedItem();
+ if (item)
+ {
+ KAEvent event = item->event();
+ EditAlarmDlg editDlg(false, (event.expired() ? i18n("Expired Alarm") + " [" + i18n("read-only") + ']'
+ : i18n("View Alarm")),
+ this, 0, &event, true);
+ editDlg.exec();
+ }
+}
+
+/******************************************************************************
+* Called when the Delete button is clicked to delete the currently highlighted
+* alarms in the list.
+*/
+void MainWindow::slotDelete()
+{
+ QValueList<EventListViewItemBase*> items = mListView->selectedItems();
+ // Copy the events to be deleted, in case any are deleted by being
+ // triggered while the confirmation prompt is displayed.
+ QValueList<KAEvent> events;
+ QValueList<KAEvent> origEvents;
+ for (QValueList<EventListViewItemBase*>::Iterator iit = items.begin(); iit != items.end(); ++iit)
+ {
+ AlarmListViewItem* item = (AlarmListViewItem*)(*iit);
+ events.append(item->event());
+ origEvents.append(item->event());
+ }
+ if (Preferences::confirmAlarmDeletion())
+ {
+ int n = items.count();
+ if (KMessageBox::warningContinueCancel(this, i18n("Do you really want to delete the selected alarm?",
+ "Do you really want to delete the %n selected alarms?", n),
+ i18n("Delete Alarm", "Delete Alarms", n),
+ KGuiItem(i18n("&Delete"), "editdelete"),
+ Preferences::CONFIRM_ALARM_DELETION)
+ != KMessageBox::Continue)
+ return;
+ }
+
+ int warnErr = 0;
+ int warnKOrg = 0;
+ AlarmCalendar::activeCalendar()->startUpdate(); // prevent multiple saves of the calendars until we're finished
+ AlarmCalendar::expiredCalendar()->startUpdate();
+ for (QValueList<KAEvent>::Iterator it = events.begin(); it != events.end(); ++it)
+ {
+ // Delete the event from the calendar and displays
+ switch (KAlarm::deleteEvent(*it))
+ {
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ ++warnErr;
+ break;
+ case KAlarm::UPDATE_KORG_ERR:
+ ++warnKOrg;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!AlarmCalendar::activeCalendar()->endUpdate()) // save the calendars now
+ warnErr = events.count();
+ AlarmCalendar::expiredCalendar()->endUpdate();
+ Undo::saveDeletes(origEvents);
+
+ if (warnErr)
+ KAlarm::displayUpdateError(this, KAlarm::UPDATE_FAILED, KAlarm::ERR_DELETE, warnErr);
+ else if (warnKOrg)
+ KAlarm::displayKOrgUpdateError(this, KAlarm::KORG_ERR_DELETE, warnKOrg);
+}
+
+/******************************************************************************
+* Called when the Reactivate button is clicked to reinstate the currently
+* highlighted expired alarms in the list.
+*/
+void MainWindow::slotReactivate()
+{
+ int warnErr = 0;
+ int warnKOrg = 0;
+ QValueList<KAEvent> events;
+ QValueList<EventListViewItemBase*> items = mListView->selectedItems();
+ mListView->clearSelection();
+ AlarmCalendar::activeCalendar()->startUpdate(); // prevent multiple saves of the calendars until we're finished
+ AlarmCalendar::expiredCalendar()->startUpdate();
+ for (QValueList<EventListViewItemBase*>::Iterator it = items.begin(); it != items.end(); ++it)
+ {
+ // Add the alarm to the displayed lists and to the calendar file
+ AlarmListViewItem* item = (AlarmListViewItem*)(*it);
+ KAEvent event = item->event();
+ events.append(event);
+ switch (KAlarm::reactivateEvent(event, mListView, true))
+ {
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ ++warnErr;
+ break;
+ case KAlarm::UPDATE_KORG_ERR:
+ ++warnKOrg;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!AlarmCalendar::activeCalendar()->endUpdate()) // save the calendars now
+ warnErr = items.count();
+ AlarmCalendar::expiredCalendar()->endUpdate();
+ Undo::saveReactivates(events);
+
+ if (warnErr)
+ KAlarm::displayUpdateError(this, KAlarm::UPDATE_FAILED, KAlarm::ERR_REACTIVATE, warnErr);
+ else if (warnKOrg)
+ KAlarm::displayKOrgUpdateError(this, KAlarm::KORG_ERR_ADD, warnKOrg);
+}
+
+/******************************************************************************
+* Called when the Enable/Disable button is clicked to enable or disable the
+* currently highlighted alarms in the list.
+*/
+void MainWindow::slotEnable()
+{
+ bool enable = mActionEnableEnable; // save since changed in response to KAlarm::enableEvent()
+ int warnErr = 0;
+ QValueList<EventListViewItemBase*> items = mListView->selectedItems();
+ AlarmCalendar::activeCalendar()->startUpdate(); // prevent multiple saves of the calendars until we're finished
+ for (QValueList<EventListViewItemBase*>::Iterator it = items.begin(); it != items.end(); ++it)
+ {
+ AlarmListViewItem* item = (AlarmListViewItem*)(*it);
+ KAEvent event = item->event();
+
+ // Enable the alarm in the displayed lists and in the calendar file
+ if (KAlarm::enableEvent(event, mListView, enable) != KAlarm::UPDATE_OK)
+ ++warnErr;
+ }
+ if (!AlarmCalendar::activeCalendar()->endUpdate()) // save the calendars now
+ warnErr = items.count();
+ if (warnErr)
+ KAlarm::displayUpdateError(this, KAlarm::UPDATE_FAILED, KAlarm::ERR_ADD, warnErr);
+}
+
+/******************************************************************************
+* Called when the Show Alarm Times menu item is selected or deselected.
+*/
+void MainWindow::slotShowTime()
+{
+ mShowTime = !mShowTime;
+ mActionShowTime->setChecked(mShowTime);
+ if (!mShowTime && !mShowTimeTo)
+ slotShowTimeTo(); // at least one time column must be displayed
+ else
+ {
+ mListView->selectTimeColumns(mShowTime, mShowTimeTo);
+ KConfig* config = KGlobal::config();
+ config->setGroup(VIEW_GROUP);
+ config->writeEntry(SHOW_TIME_KEY, mShowTime);
+ config->writeEntry(SHOW_TIME_TO_KEY, mShowTimeTo);
+ }
+}
+
+/******************************************************************************
+* Called when the Show Time To Alarms menu item is selected or deselected.
+*/
+void MainWindow::slotShowTimeTo()
+{
+ mShowTimeTo = !mShowTimeTo;
+ mActionShowTimeTo->setChecked(mShowTimeTo);
+ if (!mShowTimeTo && !mShowTime)
+ slotShowTime(); // at least one time column must be displayed
+ else
+ {
+ mListView->selectTimeColumns(mShowTime, mShowTimeTo);
+ KConfig* config = KGlobal::config();
+ config->setGroup(VIEW_GROUP);
+ config->writeEntry(SHOW_TIME_KEY, mShowTime);
+ config->writeEntry(SHOW_TIME_TO_KEY, mShowTimeTo);
+ }
+ setUpdateTimer();
+}
+
+/******************************************************************************
+* Called when the Show Expired Alarms menu item is selected or deselected.
+*/
+void MainWindow::slotShowExpired()
+{
+ mShowExpired = !mShowExpired;
+ mActionShowExpired->setChecked(mShowExpired);
+ mActionShowExpired->setToolTip(mShowExpired ? i18n_HideExpiredAlarms() : i18n_ShowExpiredAlarms());
+ mListView->showExpired(mShowExpired);
+ mListView->refresh();
+ KConfig* config = KGlobal::config();
+ config->setGroup(VIEW_GROUP);
+ config->writeEntry(SHOW_ARCHIVED_KEY, mShowExpired);
+}
+
+/******************************************************************************
+* Called when the Import Alarms menu item is selected, to merge alarms from an
+* external calendar into the current calendars.
+*/
+void MainWindow::slotImportAlarms()
+{
+ if (AlarmCalendar::importAlarms(this))
+ mListView->refresh();
+}
+
+/******************************************************************************
+* Called when the Import Birthdays menu item is selected, to display birthdays
+* from the address book for selection as alarms.
+*/
+void MainWindow::slotBirthdays()
+{
+ BirthdayDlg dlg(this);
+ if (dlg.exec() == QDialog::Accepted)
+ {
+ QValueList<KAEvent> events = dlg.events();
+ if (events.count())
+ {
+ mListView->clearSelection();
+ int warnErr = 0;
+ int warnKOrg = 0;
+ for (QValueList<KAEvent>::Iterator ev = events.begin(); ev != events.end(); ++ev)
+ {
+ // Add alarm to the displayed lists and to the calendar file
+ switch (KAlarm::addEvent(*ev, mListView))
+ {
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ ++warnErr;
+ break;
+ case KAlarm::UPDATE_KORG_ERR:
+ ++warnKOrg;
+ break;
+ default:
+ break;
+ }
+ }
+ if (warnErr)
+ KAlarm::displayUpdateError(this, KAlarm::UPDATE_FAILED, KAlarm::ERR_ADD, warnErr);
+ else if (warnKOrg)
+ KAlarm::displayKOrgUpdateError(this, KAlarm::KORG_ERR_ADD, warnKOrg);
+ KAlarm::outputAlarmWarnings(&dlg);
+ }
+ }
+}
+
+/******************************************************************************
+* Called when the Templates menu item is selected, to display the alarm
+* template editing dialogue.
+*/
+void MainWindow::slotTemplates()
+{
+ if (!mTemplateDlg)
+ {
+ mTemplateDlg = TemplateDlg::create(this);
+ enableTemplateMenuItem(false); // disable menu item in all windows
+ connect(mTemplateDlg, SIGNAL(finished()), SLOT(slotTemplatesEnd()));
+ mTemplateDlg->show();
+ }
+}
+
+/******************************************************************************
+* Called when the alarm template editing dialogue has exited.
+*/
+void MainWindow::slotTemplatesEnd()
+{
+ if (mTemplateDlg)
+ {
+ mTemplateDlg->delayedDestruct(); // this deletes the dialogue once it is safe to do so
+ mTemplateDlg = 0;
+ enableTemplateMenuItem(true); // re-enable menu item in all windows
+ }
+}
+
+/******************************************************************************
+* Called when the Display System Tray Icon menu item is selected.
+*/
+void MainWindow::slotToggleTrayIcon()
+{
+ theApp()->displayTrayIcon(!theApp()->trayIconDisplayed(), this);
+}
+
+/******************************************************************************
+* Called when the user preferences have changed.
+*/
+void MainWindow::slotPrefsChanged()
+{
+ mActionShowExpired->setEnabled(Preferences::expiredKeepDays());
+ updateTrayIconAction();
+}
+
+/******************************************************************************
+* Called when the system tray icon is created or destroyed.
+* Set the system tray icon menu text according to whether or not the system
+* tray icon is currently visible.
+*/
+void MainWindow::updateTrayIconAction()
+{
+ mActionToggleTrayIcon->setEnabled(theApp()->haveSystemTray() && !theApp()->wantRunInSystemTray());
+ mActionToggleTrayIcon->setChecked(theApp()->trayIconDisplayed());
+}
+
+/******************************************************************************
+* Called when the Actions menu is about to be displayed.
+* Update the status of the Alarms Enabled menu item.
+*/
+void MainWindow::updateActionsMenu()
+{
+ Daemon::checkStatus(); // update the Alarms Enabled item status
+}
+
+/******************************************************************************
+* Called when the active status of Find changes.
+*/
+void MainWindow::slotFindActive(bool active)
+{
+ mActionFindNext->setEnabled(active);
+ mActionFindPrev->setEnabled(active);
+}
+
+/******************************************************************************
+* Called when the Undo action is selected.
+*/
+void MainWindow::slotUndo()
+{
+ Undo::undo(this, KAlarm::stripAccel(mActionUndo->text()));
+}
+
+/******************************************************************************
+* Called when the Redo action is selected.
+*/
+void MainWindow::slotRedo()
+{
+ Undo::redo(this, KAlarm::stripAccel(mActionRedo->text()));
+}
+
+/******************************************************************************
+* Called when an Undo item is selected.
+*/
+void MainWindow::slotUndoItem(int id)
+{
+ Undo::undo(id, this, Undo::actionText(Undo::UNDO, id));
+}
+
+/******************************************************************************
+* Called when a Redo item is selected.
+*/
+void MainWindow::slotRedoItem(int id)
+{
+ Undo::redo(id, this, Undo::actionText(Undo::REDO, id));
+}
+
+/******************************************************************************
+* Called when the Undo menu is about to show.
+* Populates the menu.
+*/
+void MainWindow::slotInitUndoMenu()
+{
+ initUndoMenu(mActionUndo->popupMenu(), Undo::UNDO);
+}
+
+/******************************************************************************
+* Called when the Redo menu is about to show.
+* Populates the menu.
+*/
+void MainWindow::slotInitRedoMenu()
+{
+ initUndoMenu(mActionRedo->popupMenu(), Undo::REDO);
+}
+
+/******************************************************************************
+* Populate the undo or redo menu.
+*/
+void MainWindow::initUndoMenu(KPopupMenu* menu, Undo::Type type)
+{
+ menu->clear();
+ const QString& action = (type == Undo::UNDO) ? undoTextStripped : redoTextStripped;
+ QValueList<int> ids = Undo::ids(type);
+ for (QValueList<int>::ConstIterator it = ids.begin(); it != ids.end(); ++it)
+ {
+ int id = *it;
+ QString actText = Undo::actionText(type, id);
+ QString descrip = Undo::description(type, id);
+ QString text = descrip.isEmpty()
+ ? i18n("Undo/Redo [action]", "%1 %2").arg(action).arg(actText)
+ : i18n("Undo [action]: message", "%1 %2: %3").arg(action).arg(actText).arg(descrip);
+ menu->insertItem(text, id);
+ }
+}
+
+/******************************************************************************
+* Called when the status of the Undo or Redo list changes.
+* Change the Undo or Redo text to include the action which would be undone/redone.
+*/
+void MainWindow::slotUndoStatus(const QString& undo, const QString& redo)
+{
+ if (undo.isNull())
+ {
+ mActionUndo->setEnabled(false);
+ mActionUndo->setText(undoText);
+ }
+ else
+ {
+ mActionUndo->setEnabled(true);
+ mActionUndo->setText(QString("%1 %2").arg(undoText).arg(undo));
+ }
+ if (redo.isNull())
+ {
+ mActionRedo->setEnabled(false);
+ mActionRedo->setText(redoText);
+ }
+ else
+ {
+ mActionRedo->setEnabled(true);
+ mActionRedo->setText(QString("%1 %2").arg(redoText).arg(redo));
+ }
+}
+
+/******************************************************************************
+* Called when the Reset Daemon menu item is selected.
+*/
+void MainWindow::slotResetDaemon()
+{
+ KAlarm::resetDaemon();
+}
+
+/******************************************************************************
+* Called when the "Configure KAlarm" menu item is selected.
+*/
+void MainWindow::slotPreferences()
+{
+ KAlarmPrefDlg::display();
+}
+
+/******************************************************************************
+* Called when the Configure Keys menu item is selected.
+*/
+void MainWindow::slotConfigureKeys()
+{
+ KKeyDialog::configure(actionCollection(), this);
+}
+
+/******************************************************************************
+* Called when the Configure Toolbars menu item is selected.
+*/
+void MainWindow::slotConfigureToolbar()
+{
+ saveMainWindowSettings(KGlobal::config(), WINDOW_NAME);
+ KEditToolbar dlg(factory());
+ connect(&dlg, SIGNAL(newToolbarConfig()), this, SLOT(slotNewToolbarConfig()));
+ dlg.exec();
+}
+
+/******************************************************************************
+* Called when OK or Apply is clicked in the Configure Toolbars dialog, to save
+* the new configuration.
+*/
+void MainWindow::slotNewToolbarConfig()
+{
+ createGUI(UI_FILE);
+ applyMainWindowSettings(KGlobal::config(), WINDOW_NAME);
+}
+
+/******************************************************************************
+* Called when the Quit menu item is selected.
+*/
+void MainWindow::slotQuit()
+{
+ theApp()->doQuit(this);
+}
+
+/******************************************************************************
+* Called when the user or the session manager attempts to close the window.
+*/
+void MainWindow::closeEvent(QCloseEvent* ce)
+{
+ if (!theApp()->sessionClosingDown() && isTrayParent())
+ {
+ // The user (not the session manager) wants to close the window.
+ // It's the parent window of the system tray icon, so just hide
+ // it to prevent the system tray icon closing.
+ hide();
+ theApp()->quitIf();
+ ce->ignore();
+ }
+ else
+ ce->accept();
+}
+
+/******************************************************************************
+* Called when an item is deleted from the ListView.
+* Disables the actions if no item is still selected.
+*/
+void MainWindow::slotDeletion()
+{
+ if (!mListView->selectedCount())
+ {
+ kdDebug(5950) << "MainWindow::slotDeletion(true)\n";
+ mActionCreateTemplate->setEnabled(false);
+ mActionCopy->setEnabled(false);
+ mActionModify->setEnabled(false);
+ mActionView->setEnabled(false);
+ mActionDelete->setEnabled(false);
+ mActionReactivate->setEnabled(false);
+ mActionEnable->setEnabled(false);
+ }
+}
+
+/******************************************************************************
+* Called when the drag cursor enters the window.
+*/
+void MainWindow::dragEnterEvent(QDragEnterEvent* e)
+{
+ executeDragEnterEvent(e);
+}
+
+/******************************************************************************
+* Called when the drag cursor enters a main or system tray window, to accept
+* or reject the dragged object.
+*/
+void MainWindow::executeDragEnterEvent(QDragEnterEvent* e)
+{
+ if (KCal::ICalDrag::canDecode(e))
+ e->accept(!AlarmListView::dragging()); // don't accept "text/calendar" objects from KAlarm
+ else
+ e->accept(QTextDrag::canDecode(e)
+ || KURLDrag::canDecode(e)
+ || KPIM::MailListDrag::canDecode(e));
+}
+
+/******************************************************************************
+* Called when an object is dropped on the window.
+* If the object is recognised, the edit alarm dialog is opened appropriately.
+*/
+void MainWindow::dropEvent(QDropEvent* e)
+{
+ executeDropEvent(this, e);
+}
+
+static QString getMailHeader(const char* header, KMime::Content& content)
+{
+ KMime::Headers::Base* hd = content.getHeaderByType(header);
+ return hd ? hd->asUnicodeString() : QString::null;
+}
+
+/******************************************************************************
+* Called when an object is dropped on a main or system tray window, to
+* evaluate the action required and extract the text.
+*/
+void MainWindow::executeDropEvent(MainWindow* win, QDropEvent* e)
+{
+ KAEvent::Action action = KAEvent::MESSAGE;
+ QString text;
+ QByteArray bytes;
+ AlarmText alarmText;
+ KPIM::MailList mailList;
+ KURL::List files;
+ KCal::CalendarLocal calendar(QString::fromLatin1("UTC"));
+ calendar.setLocalTime(); // default to local time (i.e. no time zone)
+#ifndef NDEBUG
+ QCString fmts;
+ for (int idbg = 0; e->format(idbg); ++idbg)
+ {
+ if (idbg) fmts += ", ";
+ fmts += e->format(idbg);
+ }
+ kdDebug(5950) << "MainWindow::executeDropEvent(): " << fmts << endl;
+#endif
+
+ /* The order of the tests below matters, since some dropped objects
+ * provide more than one mime type.
+ * Don't change them without careful thought !!
+ */
+ if (e->provides("message/rfc822")
+ && !(bytes = e->encodedData("message/rfc822")).isEmpty())
+ {
+ // Email message(s). Ignore all but the first.
+ kdDebug(5950) << "MainWindow::executeDropEvent(email)" << endl;
+ QCString mails(bytes.data(), bytes.size());
+ KMime::Content content;
+ content.setContent(mails);
+ content.parse();
+ QString body;
+ if (content.textContent())
+ content.textContent()->decodedText(body, true, true); // strip trailing newlines & spaces
+ unsigned long sernum = 0;
+ if (e->provides(KPIM::MailListDrag::format())
+ && KPIM::MailListDrag::decode(e, mailList)
+ && mailList.count())
+ {
+ // Get its KMail serial number to allow the KMail message
+ // to be called up from the alarm message window.
+ sernum = mailList.first().serialNumber();
+ }
+ alarmText.setEmail(getMailHeader("To", content),
+ getMailHeader("From", content),
+ getMailHeader("Cc", content),
+ getMailHeader("Date", content),
+ getMailHeader("Subject", content),
+ body, sernum);
+ }
+ else if (KURLDrag::decode(e, files) && files.count())
+ {
+ kdDebug(5950) << "MainWindow::executeDropEvent(URL)" << endl;
+ action = KAEvent::FILE;
+ alarmText.setText(files.first().prettyURL());
+ }
+ else if (e->provides(KPIM::MailListDrag::format())
+ && KPIM::MailListDrag::decode(e, mailList))
+ {
+ // KMail message(s). Ignore all but the first.
+ kdDebug(5950) << "MainWindow::executeDropEvent(KMail_list)" << endl;
+ if (!mailList.count())
+ return;
+ KPIM::MailSummary& summary = mailList.first();
+ QDateTime dt;
+ dt.setTime_t(summary.date());
+ QString body = KAMail::getMailBody(summary.serialNumber());
+ alarmText.setEmail(summary.to(), summary.from(), QString::null,
+ KGlobal::locale()->formatDateTime(dt), summary.subject(),
+ body, summary.serialNumber());
+ }
+ else if (KCal::ICalDrag::decode(e, &calendar))
+ {
+ // iCalendar - ignore all but the first event
+ kdDebug(5950) << "MainWindow::executeDropEvent(iCalendar)" << endl;
+ KCal::Event::List events = calendar.rawEvents();
+ if (!events.isEmpty())
+ {
+ KAEvent ev(*events.first());
+ executeNew(win, &ev);
+ }
+ return;
+ }
+ else if (QTextDrag::decode(e, text))
+ {
+ kdDebug(5950) << "MainWindow::executeDropEvent(text)" << endl;
+ alarmText.setText(text);
+ }
+ else
+ return;
+
+ if (!alarmText.isEmpty())
+ executeNew(win, 0, action, alarmText);
+}
+
+/******************************************************************************
+* Called when the selected items in the ListView changes.
+* Selects the new current item, and enables the actions appropriately.
+*/
+void MainWindow::slotSelection()
+{
+ // Find which item has been selected, and whether more than one is selected
+ QValueList<EventListViewItemBase*> items = mListView->selectedItems();
+ int count = items.count();
+ AlarmListViewItem* item = (AlarmListViewItem*)((count == 1) ? items.first() : 0);
+ bool enableReactivate = true;
+ bool enableEnableDisable = true;
+ bool enableEnable = false;
+ bool enableDisable = false;
+ QDateTime now = QDateTime::currentDateTime();
+ for (QValueList<EventListViewItemBase*>::Iterator it = items.begin(); it != items.end(); ++it)
+ {
+ const KAEvent& event = ((AlarmListViewItem*)(*it))->event();
+ if (enableReactivate
+ && (!event.expired() || !event.occursAfter(now, true)))
+ enableReactivate = false;
+ if (enableEnableDisable)
+ {
+ if (event.expired())
+ enableEnableDisable = enableEnable = enableDisable = false;
+ else
+ {
+ if (!enableEnable && !event.enabled())
+ enableEnable = true;
+ if (!enableDisable && event.enabled())
+ enableDisable = true;
+ }
+ }
+ }
+
+ kdDebug(5950) << "MainWindow::slotSelection(true)\n";
+ mActionCreateTemplate->setEnabled(count == 1);
+ mActionCopy->setEnabled(count == 1);
+ mActionModify->setEnabled(item && !mListView->expired(item));
+ mActionView->setEnabled(count == 1);
+ mActionDelete->setEnabled(count);
+ mActionReactivate->setEnabled(count && enableReactivate);
+ mActionEnable->setEnabled(enableEnable || enableDisable);
+ if (enableEnable || enableDisable)
+ setEnableText(enableEnable);
+}
+
+/******************************************************************************
+* Called when a context menu is requested either by a mouse click or by a
+* key press.
+*/
+void MainWindow::slotContextMenuRequested(QListViewItem* item, const QPoint& pt, int)
+{
+ kdDebug(5950) << "MainWindow::slotContextMenuRequested()" << endl;
+ if (mContextMenu)
+ mContextMenu->popup(pt);
+}
+
+/******************************************************************************
+* Called when the mouse is clicked on the ListView.
+* Deselects the current item and disables the actions if appropriate.
+* Note that if a right button click is handled by slotContextMenuRequested().
+*/
+void MainWindow::slotMouseClicked(int button, QListViewItem* item, const QPoint& pt, int)
+{
+ if (button != Qt::RightButton && !item)
+ {
+ kdDebug(5950) << "MainWindow::slotMouseClicked(left)" << endl;
+ mListView->clearSelection();
+ mActionCreateTemplate->setEnabled(false);
+ mActionCopy->setEnabled(false);
+ mActionModify->setEnabled(false);
+ mActionView->setEnabled(false);
+ mActionDelete->setEnabled(false);
+ mActionReactivate->setEnabled(false);
+ mActionEnable->setEnabled(false);
+ }
+}
+
+/******************************************************************************
+* Called when the mouse is double clicked on the ListView.
+* Displays the Edit Alarm dialog, for the clicked item if applicable.
+*/
+void MainWindow::slotDoubleClicked(QListViewItem* item)
+{
+ kdDebug(5950) << "MainWindow::slotDoubleClicked()\n";
+ if (item)
+ {
+ if (mListView->expired((AlarmListViewItem*)item))
+ slotView();
+ else
+ slotModify();
+ }
+ else
+ slotNew();
+}
+
+/******************************************************************************
+* Set the text of the Enable/Disable menu action.
+*/
+void MainWindow::setEnableText(bool enable)
+{
+ mActionEnableEnable = enable;
+ mActionEnable->setText(enable ? i18n("Ena&ble") : i18n("Disa&ble"));
+}
+
+/******************************************************************************
+* Display or hide the specified main window.
+* This should only be called when the application doesn't run in the system tray.
+*/
+MainWindow* MainWindow::toggleWindow(MainWindow* win)
+{
+ if (win && mWindowList.find(win) != mWindowList.end())
+ {
+ // A window is specified (and it exists)
+ if (win->isVisible())
+ {
+ // The window is visible, so close it
+ win->close();
+ return 0;
+ }
+ else
+ {
+ // The window is hidden, so display it
+ win->hide(); // in case it's on a different desktop
+ win->showNormal();
+ win->raise();
+ win->setActiveWindow();
+ return win;
+ }
+ }
+
+ // No window is specified, or the window doesn't exist. Open a new one.
+ win = create();
+ win->show();
+ return win;
+}
diff --git a/kalarm/mainwindow.h b/kalarm/mainwindow.h
new file mode 100644
index 00000000..7a11b8b8
--- /dev/null
+++ b/kalarm/mainwindow.h
@@ -0,0 +1,182 @@
+/*
+ * mainwindow.h - main application window
+ * Program: kalarm
+ * Copyright © 2001-2007 by David Jarvie <djarvie@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.
+ */
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+/** @file mainwindow.h - main application window */
+
+#include "alarmevent.h"
+#include "alarmtext.h"
+#include "mainwindowbase.h"
+#include "undo.h"
+
+class QListViewItem;
+class KAction;
+class KToggleAction;
+class KToolBarPopupAction;
+class KPopupMenu;
+class ActionAlarmsEnabled;
+class AlarmListView;
+class TemplateDlg;
+class TemplateMenuAction;
+
+
+class MainWindow : public MainWindowBase
+{
+ Q_OBJECT
+
+ public:
+ static MainWindow* create(bool restored = false);
+ ~MainWindow();
+ bool isTrayParent() const;
+ bool isHiddenTrayParent() const { return mHiddenTrayParent; }
+ bool showingExpired() const { return mShowExpired; }
+ void selectEvent(const QString& eventID);
+
+ static void refresh();
+ static void updateExpired();
+ static void addEvent(const KAEvent&, MainWindow*);
+ static void executeNew(MainWindow* w = 0, KAEvent::Action a = KAEvent::MESSAGE, const AlarmText& t = AlarmText())
+ { executeNew(w, 0, a, t); }
+ static void executeNew(const KAEvent& e, MainWindow* w = 0)
+ { executeNew(w, &e); }
+ static void executeEdit(KAEvent&, MainWindow* = 0);
+ static void executeDragEnterEvent(QDragEnterEvent*);
+ static void executeDropEvent(MainWindow*, QDropEvent*);
+ static void closeAll();
+ static MainWindow* toggleWindow(MainWindow*);
+ static MainWindow* mainMainWindow();
+ static MainWindow* firstWindow() { return mWindowList.first(); }
+ static int count() { return mWindowList.count(); }
+
+ static QString i18n_a_ShowAlarmTimes(); // text of 'Show Alarm Times' checkbox, with 'A' shortcut
+ static QString i18n_m_ShowAlarmTime(); // text of 'Show alarm time' checkbox, with 'M' shortcut
+ static QString i18n_o_ShowTimeToAlarms(); // text of 'Show Time to Alarms' checkbox, with 'O' shortcut
+ static QString i18n_l_ShowTimeToAlarm(); // text of 'Show time until alarm' checkbox, with 'L' shortcut
+ static QString i18n_ShowExpiredAlarms(); // plain text of 'Show Expired Alarms' action
+ static QString i18n_e_ShowExpiredAlarms(); // text of 'Show Expired Alarms' checkbox, with 'E' shortcut
+ static QString i18n_HideExpiredAlarms(); // plain text of 'Hide Expired Alarms' action
+ static QString i18n_e_HideExpiredAlarms(); // text of 'Hide Expired Alarms' action, with 'E' shortcut
+
+ public slots:
+ virtual void show();
+
+ protected:
+ virtual void resizeEvent(QResizeEvent*);
+ virtual void showEvent(QShowEvent*);
+ virtual void hideEvent(QHideEvent*);
+ virtual void closeEvent(QCloseEvent*);
+ virtual void dragEnterEvent(QDragEnterEvent*);
+ virtual void dropEvent(QDropEvent*);
+ virtual void saveProperties(KConfig*);
+ virtual void readProperties(KConfig*);
+
+ private slots:
+ void slotNew();
+ void slotNewFromTemplate(const KAEvent&);
+ void slotNewTemplate();
+ void slotCopy();
+ void slotModify();
+ void slotDelete();
+ void slotReactivate();
+ void slotView();
+ void slotEnable();
+ void slotToggleTrayIcon();
+ void slotResetDaemon();
+ void slotImportAlarms();
+ void slotBirthdays();
+ void slotTemplates();
+ void slotTemplatesEnd();
+ void slotPreferences();
+ void slotConfigureKeys();
+ void slotConfigureToolbar();
+ void slotNewToolbarConfig();
+ void slotQuit();
+ void slotDeletion();
+ void slotSelection();
+ void slotContextMenuRequested(QListViewItem*, const QPoint&, int);
+ void slotMouseClicked(int button, QListViewItem*, const QPoint&, int);
+ void slotDoubleClicked(QListViewItem*);
+ void slotShowTime();
+ void slotShowTimeTo();
+ void slotShowExpired();
+ void slotUpdateTimeTo();
+ void slotUndo();
+ void slotUndoItem(int id);
+ void slotRedo();
+ void slotRedoItem(int id);
+ void slotInitUndoMenu();
+ void slotInitRedoMenu();
+ void slotUndoStatus(const QString&, const QString&);
+ void slotFindActive(bool);
+ void slotPrefsChanged();
+ void updateTrayIconAction();
+ void updateActionsMenu();
+ void columnsReordered();
+
+ private:
+ typedef QValueList<MainWindow*> WindowList;
+
+ MainWindow(bool restored);
+ void createListView(bool recreate);
+ void initActions();
+ void setEnableText(bool enable);
+ static KAEvent::Action getDropAction(QDropEvent*, QString& text);
+ static void executeNew(MainWindow*, const KAEvent*, KAEvent::Action = KAEvent::MESSAGE, const AlarmText& = AlarmText());
+ static void initUndoMenu(KPopupMenu*, Undo::Type);
+ static void setUpdateTimer();
+ static void enableTemplateMenuItem(bool);
+
+ static WindowList mWindowList; // active main windows
+ static TemplateDlg* mTemplateDlg; // the one and only template dialogue
+
+ AlarmListView* mListView;
+ KAction* mActionTemplates;
+ KAction* mActionNew;
+ TemplateMenuAction* mActionNewFromTemplate;
+ KAction* mActionCreateTemplate;
+ KAction* mActionCopy;
+ KAction* mActionModify;
+ KAction* mActionView;
+ KAction* mActionDelete;
+ KAction* mActionReactivate;
+ KAction* mActionEnable;
+ KAction* mActionFindNext;
+ KAction* mActionFindPrev;
+ KToolBarPopupAction* mActionUndo;
+ KToolBarPopupAction* mActionRedo;
+ KToggleAction* mActionToggleTrayIcon;
+ KToggleAction* mActionShowTime;
+ KToggleAction* mActionShowTimeTo;
+ KToggleAction* mActionShowExpired;
+ KPopupMenu* mActionsMenu;
+ KPopupMenu* mContextMenu;
+ bool mMinuteTimerActive; // minute timer is active
+ bool mHiddenTrayParent; // on session restoration, hide this window
+ bool mShowExpired; // include expired alarms in the displayed list
+ bool mShowTime; // show alarm times
+ bool mShowTimeTo; // show time-to-alarms
+ bool mActionEnableEnable; // Enable/Disable action is set to "Enable"
+ bool mMenuError; // error occurred creating menus: need to show error message
+};
+
+#endif // MAINWINDOW_H
+
diff --git a/kalarm/mainwindowbase.cpp b/kalarm/mainwindowbase.cpp
new file mode 100644
index 00000000..2f0cfa16
--- /dev/null
+++ b/kalarm/mainwindowbase.cpp
@@ -0,0 +1,50 @@
+/*
+ * mainwindowbase.cpp - base class for main application windows
+ * Program: kalarm
+ * Copyright (C) 2002, 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 "kalarm.h"
+#include <kdeversion.h>
+#include "kalarmapp.h"
+#include "mainwindowbase.moc"
+
+
+/******************************************************************************
+* Called when a close event is received.
+* Only quits the application if there is no system tray icon displayed.
+*/
+void MainWindowBase::closeEvent(QCloseEvent* ce)
+{
+ disableQuit = theApp()->trayIconDisplayed();
+ KMainWindow::closeEvent(ce);
+ disableQuit = false;
+ ce->accept(); // allow window to close even if it's the last main window
+}
+
+/******************************************************************************
+* Called when the window is being closed.
+* Returns true if the application should quit.
+*/
+bool MainWindowBase::queryExit()
+{
+#if KDE_IS_VERSION(3,1,90)
+ if (kapp->sessionSaving())
+ return true;
+#endif
+ return disableQuit ? false : KMainWindow::queryExit();
+}
diff --git a/kalarm/mainwindowbase.h b/kalarm/mainwindowbase.h
new file mode 100644
index 00000000..32e33973
--- /dev/null
+++ b/kalarm/mainwindowbase.h
@@ -0,0 +1,51 @@
+/*
+ * mainwindowbase.h - base class for main application windows
+ * Program: kalarm
+ * Copyright © 2002 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.
+ */
+
+#ifndef MAINWINDOWBASE_H
+#define MAINWINDOWBASE_H
+
+#include <kmainwindow.h>
+
+
+/**
+ * The MainWindowBase class is a base class for KAlarm's main window and message window.
+ * When the window is closed, it only allows the application to quit if there is no
+ * system tray icon displayed.
+ *
+ * @author David Jarvie <software@astrojar.org.uk>
+ */
+class MainWindowBase : public KMainWindow
+{
+ Q_OBJECT
+
+ public:
+ explicit MainWindowBase(QWidget* parent = 0, const char* name = 0, WFlags f = WType_TopLevel | WDestructiveClose)
+ : KMainWindow(parent, name, f), disableQuit(false) { }
+
+ protected:
+ virtual void closeEvent(QCloseEvent*);
+ virtual bool queryExit();
+
+ private:
+ bool disableQuit; // allow the application to quit
+};
+
+#endif // MAINWINDOWBASE_H
+
diff --git a/kalarm/messagewin.cpp b/kalarm/messagewin.cpp
new file mode 100644
index 00000000..acee6b9b
--- /dev/null
+++ b/kalarm/messagewin.cpp
@@ -0,0 +1,1727 @@
+/*
+ * messagewin.cpp - displays an alarm message
+ * Program: kalarm
+ * Copyright © 2001-2009 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <qfile.h>
+#include <qfileinfo.h>
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qlabel.h>
+#include <qwhatsthis.h>
+#include <qtooltip.h>
+#include <qdragobject.h>
+#include <qtextedit.h>
+#include <qtimer.h>
+
+#include <kstandarddirs.h>
+#include <kaction.h>
+#include <kstdguiitem.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include <kiconloader.h>
+#include <kdialog.h>
+#include <ktextbrowser.h>
+#include <kglobalsettings.h>
+#include <kmimetype.h>
+#include <kmessagebox.h>
+#include <kwin.h>
+#include <kwinmodule.h>
+#include <kprocess.h>
+#include <kio/netaccess.h>
+#include <knotifyclient.h>
+#include <kpushbutton.h>
+#ifdef WITHOUT_ARTS
+#include <kaudioplayer.h>
+#else
+#include <arts/kartsdispatcher.h>
+#include <arts/kartsserver.h>
+#include <arts/kplayobjectfactory.h>
+#include <arts/kplayobject.h>
+#endif
+#include <dcopclient.h>
+#include <kdebug.h>
+
+#include "alarmcalendar.h"
+#include "deferdlg.h"
+#include "editdlg.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "mainwindow.h"
+#include "preferences.h"
+#include "synchtimer.h"
+#include "messagewin.moc"
+
+using namespace KCal;
+
+#ifndef WITHOUT_ARTS
+static const char* KMIX_APP_NAME = "kmix";
+static const char* KMIX_DCOP_OBJECT = "Mixer0";
+static const char* KMIX_DCOP_WINDOW = "kmix-mainwindow#1";
+#endif
+static const char* KMAIL_DCOP_OBJECT = "KMailIface";
+
+// The delay for enabling message window buttons if a zero delay is
+// configured, i.e. the windows are placed far from the cursor.
+static const int proximityButtonDelay = 1000; // (milliseconds)
+static const int proximityMultiple = 10; // multiple of button height distance from cursor for proximity
+
+static bool wantModal();
+
+// A text label widget which can be scrolled and copied with the mouse
+class MessageText : public QTextEdit
+{
+ public:
+ MessageText(const QString& text, const QString& context = QString::null, QWidget* parent = 0, const char* name = 0)
+ : QTextEdit(text, context, parent, name)
+ {
+ setReadOnly(true);
+ setWordWrap(QTextEdit::NoWrap);
+ }
+ int scrollBarHeight() const { return horizontalScrollBar()->height(); }
+ int scrollBarWidth() const { return verticalScrollBar()->width(); }
+ virtual QSize sizeHint() const { return QSize(contentsWidth() + scrollBarWidth(), contentsHeight() + scrollBarHeight()); }
+};
+
+
+class MWMimeSourceFactory : public QMimeSourceFactory
+{
+ public:
+ MWMimeSourceFactory(const QString& absPath, KTextBrowser*);
+ virtual ~MWMimeSourceFactory();
+ virtual const QMimeSource* data(const QString& abs_name) const;
+ private:
+ // Prohibit the following methods
+ virtual void setData(const QString&, QMimeSource*) {}
+ virtual void setExtensionType(const QString&, const char*) {}
+
+ QString mTextFile;
+ QCString mMimeType;
+ mutable const QMimeSource* mLast;
+};
+
+
+// Basic flags for the window
+static const Qt::WFlags WFLAGS = Qt::WStyle_StaysOnTop | Qt::WDestructiveClose;
+
+// Error message bit masks
+enum {
+ ErrMsg_Speak = 0x01,
+ ErrMsg_AudioFile = 0x02,
+ ErrMsg_Volume = 0x04
+};
+
+
+QValueList<MessageWin*> MessageWin::mWindowList;
+QMap<QString, unsigned> MessageWin::mErrorMessages;
+
+
+/******************************************************************************
+* Construct the message window for the specified alarm.
+* Other alarms in the supplied event may have been updated by the caller, so
+* the whole event needs to be stored for updating the calendar file when it is
+* displayed.
+*/
+MessageWin::MessageWin(const KAEvent& event, const KAAlarm& alarm, bool reschedule_event, bool allowDefer)
+ : MainWindowBase(0, "MessageWin", WFLAGS | Qt::WGroupLeader | Qt::WStyle_ContextHelp
+ | (wantModal() ? 0 : Qt::WX11BypassWM)),
+ mMessage(event.cleanText()),
+ mFont(event.font()),
+ mBgColour(event.bgColour()),
+ mFgColour(event.fgColour()),
+ mDateTime((alarm.type() & KAAlarm::REMINDER_ALARM) ? event.mainDateTime(true) : alarm.dateTime(true)),
+ mEventID(event.id()),
+ mAudioFile(event.audioFile()),
+ mVolume(event.soundVolume()),
+ mFadeVolume(event.fadeVolume()),
+ mFadeSeconds(QMIN(event.fadeSeconds(), 86400)),
+ mDefaultDeferMinutes(event.deferDefaultMinutes()),
+ mAlarmType(alarm.type()),
+ mAction(event.action()),
+ mKMailSerialNumber(event.kmailSerialNumber()),
+ mRestoreHeight(0),
+ mAudioRepeat(event.repeatSound()),
+ mConfirmAck(event.confirmAck()),
+ mShowEdit(!mEventID.isEmpty()),
+ mNoDefer(!allowDefer || alarm.repeatAtLogin()),
+ mInvalid(false),
+ mArtsDispatcher(0),
+ mPlayObject(0),
+ mOldVolume(-1),
+ mEvent(event),
+ mEditButton(0),
+ mDeferButton(0),
+ mSilenceButton(0),
+ mDeferDlg(0),
+ mWinModule(0),
+ mFlags(event.flags()),
+ mLateCancel(event.lateCancel()),
+ mErrorWindow(false),
+ mNoPostAction(alarm.type() & KAAlarm::REMINDER_ALARM),
+ mRecreating(false),
+ mBeep(event.beep()),
+ mSpeak(event.speak()),
+ mRescheduleEvent(reschedule_event),
+ mShown(false),
+ mPositioning(false),
+ mNoCloseConfirm(false),
+ mDisableDeferral(false)
+{
+ kdDebug(5950) << "MessageWin::MessageWin(event)" << endl;
+ // Set to save settings automatically, but don't save window size.
+ // File alarm window size is saved elsewhere.
+ setAutoSaveSettings(QString::fromLatin1("MessageWin"), false);
+ initView();
+ mWindowList.append(this);
+ if (event.autoClose())
+ mCloseTime = alarm.dateTime().dateTime().addSecs(event.lateCancel() * 60);
+}
+
+/******************************************************************************
+* Construct the message window for a specified error message.
+*/
+MessageWin::MessageWin(const KAEvent& event, const DateTime& alarmDateTime, const QStringList& errmsgs)
+ : MainWindowBase(0, "MessageWin", WFLAGS | Qt::WGroupLeader | Qt::WStyle_ContextHelp),
+ mMessage(event.cleanText()),
+ mDateTime(alarmDateTime),
+ mEventID(event.id()),
+ mAlarmType(KAAlarm::MAIN_ALARM),
+ mAction(event.action()),
+ mKMailSerialNumber(0),
+ mErrorMsgs(errmsgs),
+ mRestoreHeight(0),
+ mConfirmAck(false),
+ mShowEdit(false),
+ mNoDefer(true),
+ mInvalid(false),
+ mArtsDispatcher(0),
+ mPlayObject(0),
+ mEvent(event),
+ mEditButton(0),
+ mDeferButton(0),
+ mSilenceButton(0),
+ mDeferDlg(0),
+ mWinModule(0),
+ mErrorWindow(true),
+ mNoPostAction(true),
+ mRecreating(false),
+ mRescheduleEvent(false),
+ mShown(false),
+ mPositioning(false),
+ mNoCloseConfirm(false),
+ mDisableDeferral(false)
+{
+ kdDebug(5950) << "MessageWin::MessageWin(errmsg)" << endl;
+ initView();
+ mWindowList.append(this);
+}
+
+/******************************************************************************
+* Construct the message window for restoration by session management.
+* The window is initialised by readProperties().
+*/
+MessageWin::MessageWin()
+ : MainWindowBase(0, "MessageWin", WFLAGS),
+ mArtsDispatcher(0),
+ mPlayObject(0),
+ mEditButton(0),
+ mDeferButton(0),
+ mSilenceButton(0),
+ mDeferDlg(0),
+ mWinModule(0),
+ mErrorWindow(false),
+ mRecreating(false),
+ mRescheduleEvent(false),
+ mShown(false),
+ mPositioning(false),
+ mNoCloseConfirm(false),
+ mDisableDeferral(false)
+{
+ kdDebug(5950) << "MessageWin::MessageWin(restore)\n";
+ mWindowList.append(this);
+}
+
+/******************************************************************************
+* Destructor. Perform any post-alarm actions before tidying up.
+*/
+MessageWin::~MessageWin()
+{
+ kdDebug(5950) << "MessageWin::~MessageWin(" << mEventID << ")" << endl;
+ stopPlay();
+ delete mWinModule;
+ mWinModule = 0;
+ mErrorMessages.remove(mEventID);
+ mWindowList.remove(this);
+ if (!mRecreating)
+ {
+ if (!mNoPostAction && !mEvent.postAction().isEmpty())
+ theApp()->alarmCompleted(mEvent);
+ if (!mWindowList.count())
+ theApp()->quitIf();
+ }
+}
+
+/******************************************************************************
+* Construct the message window.
+*/
+void MessageWin::initView()
+{
+ bool reminder = (!mErrorWindow && (mAlarmType & KAAlarm::REMINDER_ALARM));
+ int leading = fontMetrics().leading();
+ setCaption((mAlarmType & KAAlarm::REMINDER_ALARM) ? i18n("Reminder") : i18n("Message"));
+ QWidget* topWidget = new QWidget(this, "messageWinTop");
+ setCentralWidget(topWidget);
+ QVBoxLayout* topLayout = new QVBoxLayout(topWidget, KDialog::marginHint(), KDialog::spacingHint());
+
+ if (mDateTime.isValid())
+ {
+ // Show the alarm date/time, together with an "Advance reminder" text where appropriate
+ QFrame* frame = 0;
+ QVBoxLayout* layout = topLayout;
+ if (reminder)
+ {
+ frame = new QFrame(topWidget);
+ frame->setFrameStyle(QFrame::Box | QFrame::Raised);
+ topLayout->addWidget(frame, 0, Qt::AlignHCenter);
+ layout = new QVBoxLayout(frame, leading + frame->frameWidth(), leading);
+ }
+
+ // Alarm date/time
+ QLabel* label = new QLabel(frame ? frame : topWidget);
+ label->setText(mDateTime.isDateOnly()
+ ? KGlobal::locale()->formatDate(mDateTime.date(), true)
+ : KGlobal::locale()->formatDateTime(mDateTime.dateTime()));
+ if (!frame)
+ label->setFrameStyle(QFrame::Box | QFrame::Raised);
+ label->setFixedSize(label->sizeHint());
+ layout->addWidget(label, 0, Qt::AlignHCenter);
+ QWhatsThis::add(label,
+ i18n("The scheduled date/time for the message (as opposed to the actual time of display)."));
+
+ if (frame)
+ {
+ label = new QLabel(frame);
+ label->setText(i18n("Reminder"));
+ label->setFixedSize(label->sizeHint());
+ layout->addWidget(label, 0, Qt::AlignHCenter);
+ frame->setFixedSize(frame->sizeHint());
+ }
+ }
+
+ if (!mErrorWindow)
+ {
+ // It's a normal alarm message window
+ switch (mAction)
+ {
+ case KAEvent::FILE:
+ {
+ // Display the file name
+ QLabel* label = new QLabel(mMessage, topWidget);
+ label->setFrameStyle(QFrame::Box | QFrame::Raised);
+ label->setFixedSize(label->sizeHint());
+ QWhatsThis::add(label, i18n("The file whose contents are displayed below"));
+ topLayout->addWidget(label, 0, Qt::AlignHCenter);
+
+ // Display contents of file
+ bool opened = false;
+ bool dir = false;
+ QString tmpFile;
+ KURL url(mMessage);
+ if (KIO::NetAccess::download(url, tmpFile, MainWindow::mainMainWindow()))
+ {
+ QFile qfile(tmpFile);
+ QFileInfo info(qfile);
+ if (!(dir = info.isDir()))
+ {
+ opened = true;
+ KTextBrowser* view = new KTextBrowser(topWidget, "fileContents");
+ MWMimeSourceFactory msf(tmpFile, view);
+ view->setMinimumSize(view->sizeHint());
+ topLayout->addWidget(view);
+
+ // Set the default size to 20 lines square.
+ // Note that after the first file has been displayed, this size
+ // is overridden by the user-set default stored in the config file.
+ // So there is no need to calculate an accurate size.
+ int h = 20*view->fontMetrics().lineSpacing() + 2*view->frameWidth();
+ view->resize(QSize(h, h).expandedTo(view->sizeHint()));
+ QWhatsThis::add(view, i18n("The contents of the file to be displayed"));
+ }
+ KIO::NetAccess::removeTempFile(tmpFile);
+ }
+ if (!opened)
+ {
+ // File couldn't be opened
+ bool exists = KIO::NetAccess::exists(url, true, MainWindow::mainMainWindow());
+ mErrorMsgs += dir ? i18n("File is a folder") : exists ? i18n("Failed to open file") : i18n("File not found");
+ }
+ break;
+ }
+ case KAEvent::MESSAGE:
+ {
+ // Message label
+ // Using MessageText instead of QLabel allows scrolling and mouse copying
+ MessageText* text = new MessageText(mMessage, QString::null, topWidget);
+ text->setFrameStyle(QFrame::NoFrame);
+ text->setPaper(mBgColour);
+ text->setPaletteForegroundColor(mFgColour);
+ text->setFont(mFont);
+ int lineSpacing = text->fontMetrics().lineSpacing();
+ QSize s = text->sizeHint();
+ int h = s.height();
+ text->setMaximumHeight(h + text->scrollBarHeight());
+ text->setMinimumHeight(QMIN(h, lineSpacing*4));
+ text->setMaximumWidth(s.width() + text->scrollBarWidth());
+ QWhatsThis::add(text, i18n("The alarm message"));
+ int vspace = lineSpacing/2;
+ int hspace = lineSpacing - KDialog::marginHint();
+ topLayout->addSpacing(vspace);
+ topLayout->addStretch();
+ // Don't include any horizontal margins if message is 2/3 screen width
+ if (!mWinModule)
+ mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
+ if (text->sizeHint().width() >= mWinModule->workArea().width()*2/3)
+ topLayout->addWidget(text, 1, Qt::AlignHCenter);
+ else
+ {
+ QBoxLayout* layout = new QHBoxLayout(topLayout);
+ layout->addSpacing(hspace);
+ layout->addWidget(text, 1, Qt::AlignHCenter);
+ layout->addSpacing(hspace);
+ }
+ if (!reminder)
+ topLayout->addStretch();
+ break;
+ }
+ case KAEvent::COMMAND:
+ case KAEvent::EMAIL:
+ default:
+ break;
+ }
+
+ if (reminder)
+ {
+ // Reminder: show remaining time until the actual alarm
+ mRemainingText = new QLabel(topWidget);
+ mRemainingText->setFrameStyle(QFrame::Box | QFrame::Raised);
+ mRemainingText->setMargin(leading);
+ if (mDateTime.isDateOnly() || QDate::currentDate().daysTo(mDateTime.date()) > 0)
+ {
+ setRemainingTextDay();
+ MidnightTimer::connect(this, SLOT(setRemainingTextDay())); // update every day
+ }
+ else
+ {
+ setRemainingTextMinute();
+ MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute
+ }
+ topLayout->addWidget(mRemainingText, 0, Qt::AlignHCenter);
+ topLayout->addSpacing(KDialog::spacingHint());
+ topLayout->addStretch();
+ }
+ }
+ else
+ {
+ // It's an error message
+ switch (mAction)
+ {
+ case KAEvent::EMAIL:
+ {
+ // Display the email addresses and subject.
+ QFrame* frame = new QFrame(topWidget);
+ frame->setFrameStyle(QFrame::Box | QFrame::Raised);
+ QWhatsThis::add(frame, i18n("The email to send"));
+ topLayout->addWidget(frame, 0, Qt::AlignHCenter);
+ QGridLayout* grid = new QGridLayout(frame, 2, 2, KDialog::marginHint(), KDialog::spacingHint());
+
+ QLabel* label = new QLabel(i18n("Email addressee", "To:"), frame);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 0, 0, Qt::AlignLeft);
+ label = new QLabel(mEvent.emailAddresses("\n"), frame);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 0, 1, Qt::AlignLeft);
+
+ label = new QLabel(i18n("Email subject", "Subject:"), frame);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 1, 0, Qt::AlignLeft);
+ label = new QLabel(mEvent.emailSubject(), frame);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 1, 1, Qt::AlignLeft);
+ break;
+ }
+ case KAEvent::COMMAND:
+ case KAEvent::FILE:
+ case KAEvent::MESSAGE:
+ default:
+ // Just display the error message strings
+ break;
+ }
+ }
+
+ if (!mErrorMsgs.count())
+ topWidget->setBackgroundColor(mBgColour);
+ else
+ {
+ setCaption(i18n("Error"));
+ QBoxLayout* layout = new QHBoxLayout(topLayout);
+ layout->setMargin(2*KDialog::marginHint());
+ layout->addStretch();
+ QLabel* label = new QLabel(topWidget);
+ label->setPixmap(DesktopIcon("error"));
+ label->setFixedSize(label->sizeHint());
+ layout->addWidget(label, 0, Qt::AlignRight);
+ QBoxLayout* vlayout = new QVBoxLayout(layout);
+ for (QStringList::Iterator it = mErrorMsgs.begin(); it != mErrorMsgs.end(); ++it)
+ {
+ label = new QLabel(*it, topWidget);
+ label->setFixedSize(label->sizeHint());
+ vlayout->addWidget(label, 0, Qt::AlignLeft);
+ }
+ layout->addStretch();
+ }
+
+ QGridLayout* grid = new QGridLayout(1, 4);
+ topLayout->addLayout(grid);
+ grid->setColStretch(0, 1); // keep the buttons right-adjusted in the window
+ int gridIndex = 1;
+
+ // Close button
+ mOkButton = new KPushButton(KStdGuiItem::close(), topWidget);
+ // Prevent accidental acknowledgement of the message if the user is typing when the window appears
+ mOkButton->clearFocus();
+ mOkButton->setFocusPolicy(QWidget::ClickFocus); // don't allow keyboard selection
+ mOkButton->setFixedSize(mOkButton->sizeHint());
+ connect(mOkButton, SIGNAL(clicked()), SLOT(close()));
+ grid->addWidget(mOkButton, 0, gridIndex++, AlignHCenter);
+ QWhatsThis::add(mOkButton, i18n("Acknowledge the alarm"));
+
+ if (mShowEdit)
+ {
+ // Edit button
+ mEditButton = new QPushButton(i18n("&Edit..."), topWidget);
+ mEditButton->setFocusPolicy(QWidget::ClickFocus); // don't allow keyboard selection
+ mEditButton->setFixedSize(mEditButton->sizeHint());
+ connect(mEditButton, SIGNAL(clicked()), SLOT(slotEdit()));
+ grid->addWidget(mEditButton, 0, gridIndex++, AlignHCenter);
+ QWhatsThis::add(mEditButton, i18n("Edit the alarm."));
+ }
+
+ if (!mNoDefer)
+ {
+ // Defer button
+ mDeferButton = new QPushButton(i18n("&Defer..."), topWidget);
+ mDeferButton->setFocusPolicy(QWidget::ClickFocus); // don't allow keyboard selection
+ mDeferButton->setFixedSize(mDeferButton->sizeHint());
+ connect(mDeferButton, SIGNAL(clicked()), SLOT(slotDefer()));
+ grid->addWidget(mDeferButton, 0, gridIndex++, AlignHCenter);
+ QWhatsThis::add(mDeferButton,
+ i18n("Defer the alarm until later.\n"
+ "You will be prompted to specify when the alarm should be redisplayed."));
+
+ setDeferralLimit(mEvent); // ensure that button is disabled when alarm can't be deferred any more
+ }
+
+#ifndef WITHOUT_ARTS
+ if (!mAudioFile.isEmpty() && (mVolume || mFadeVolume > 0))
+ {
+ // Silence button to stop sound repetition
+ QPixmap pixmap = MainBarIcon("player_stop");
+ mSilenceButton = new QPushButton(topWidget);
+ mSilenceButton->setPixmap(pixmap);
+ mSilenceButton->setFixedSize(mSilenceButton->sizeHint());
+ connect(mSilenceButton, SIGNAL(clicked()), SLOT(stopPlay()));
+ grid->addWidget(mSilenceButton, 0, gridIndex++, AlignHCenter);
+ QToolTip::add(mSilenceButton, i18n("Stop sound"));
+ QWhatsThis::add(mSilenceButton, i18n("Stop playing the sound"));
+ // To avoid getting in a mess, disable the button until sound playing has been set up
+ mSilenceButton->setEnabled(false);
+ }
+#endif
+
+ KIconLoader iconLoader;
+ if (mKMailSerialNumber)
+ {
+ // KMail button
+ QPixmap pixmap = iconLoader.loadIcon(QString::fromLatin1("kmail"), KIcon::MainToolbar);
+ mKMailButton = new QPushButton(topWidget);
+ mKMailButton->setPixmap(pixmap);
+ mKMailButton->setFixedSize(mKMailButton->sizeHint());
+ connect(mKMailButton, SIGNAL(clicked()), SLOT(slotShowKMailMessage()));
+ grid->addWidget(mKMailButton, 0, gridIndex++, AlignHCenter);
+ QToolTip::add(mKMailButton, i18n("Locate this email in KMail", "Locate in KMail"));
+ QWhatsThis::add(mKMailButton, i18n("Locate and highlight this email in KMail"));
+ }
+ else
+ mKMailButton = 0;
+
+ // KAlarm button
+ QPixmap pixmap = iconLoader.loadIcon(QString::fromLatin1(kapp->aboutData()->appName()), KIcon::MainToolbar);
+ mKAlarmButton = new QPushButton(topWidget);
+ mKAlarmButton->setPixmap(pixmap);
+ mKAlarmButton->setFixedSize(mKAlarmButton->sizeHint());
+ connect(mKAlarmButton, SIGNAL(clicked()), SLOT(displayMainWindow()));
+ grid->addWidget(mKAlarmButton, 0, gridIndex++, AlignHCenter);
+ QString actKAlarm = i18n("Activate KAlarm");
+ QToolTip::add(mKAlarmButton, actKAlarm);
+ QWhatsThis::add(mKAlarmButton, actKAlarm);
+
+ // Disable all buttons initially, to prevent accidental clicking on if they happen to be
+ // under the mouse just as the window appears.
+ mOkButton->setEnabled(false);
+ if (mDeferButton)
+ mDeferButton->setEnabled(false);
+ if (mEditButton)
+ mEditButton->setEnabled(false);
+ if (mKMailButton)
+ mKMailButton->setEnabled(false);
+ mKAlarmButton->setEnabled(false);
+
+ topLayout->activate();
+ setMinimumSize(QSize(grid->sizeHint().width() + 2*KDialog::marginHint(), sizeHint().height()));
+
+ bool modal = !(getWFlags() & Qt::WX11BypassWM);
+
+ unsigned long wstate = (modal ? NET::Modal : 0) | NET::Sticky | NET::KeepAbove;
+ WId winid = winId();
+ KWin::setState(winid, wstate);
+ KWin::setOnAllDesktops(winid, true);
+}
+
+/******************************************************************************
+* Set the remaining time text in a reminder window.
+* Called at the start of every day (at the user-defined start-of-day time).
+*/
+void MessageWin::setRemainingTextDay()
+{
+ QString text;
+ int days = QDate::currentDate().daysTo(mDateTime.date());
+ if (days <= 0 && !mDateTime.isDateOnly())
+ {
+ // The alarm is due today, so start refreshing every minute
+ MidnightTimer::disconnect(this, SLOT(setRemainingTextDay()));
+ setRemainingTextMinute();
+ MinuteTimer::connect(this, SLOT(setRemainingTextMinute())); // update every minute
+ }
+ else
+ {
+ if (days <= 0)
+ text = i18n("Today");
+ else if (days % 7)
+ text = i18n("Tomorrow", "in %n days' time", days);
+ else
+ text = i18n("in 1 week's time", "in %n weeks' time", days/7);
+ }
+ mRemainingText->setText(text);
+}
+
+/******************************************************************************
+* Set the remaining time text in a reminder window.
+* Called on every minute boundary.
+*/
+void MessageWin::setRemainingTextMinute()
+{
+ QString text;
+ int mins = (QDateTime::currentDateTime().secsTo(mDateTime.dateTime()) + 59) / 60;
+ if (mins < 60)
+ text = i18n("in 1 minute's time", "in %n minutes' time", (mins > 0 ? mins : 0));
+ else if (mins % 60 == 0)
+ text = i18n("in 1 hour's time", "in %n hours' time", mins/60);
+ else if (mins % 60 == 1)
+ text = i18n("in 1 hour 1 minute's time", "in %n hours 1 minute's time", mins/60);
+ else
+ text = i18n("in 1 hour %1 minutes' time", "in %n hours %1 minutes' time", mins/60).arg(mins%60);
+ mRemainingText->setText(text);
+}
+
+/******************************************************************************
+* Save settings to the session managed config file, for restoration
+* when the program is restored.
+*/
+void MessageWin::saveProperties(KConfig* config)
+{
+ if (mShown && !mErrorWindow)
+ {
+ config->writeEntry(QString::fromLatin1("EventID"), mEventID);
+ config->writeEntry(QString::fromLatin1("AlarmType"), mAlarmType);
+ config->writeEntry(QString::fromLatin1("Message"), mMessage);
+ config->writeEntry(QString::fromLatin1("Type"), mAction);
+ config->writeEntry(QString::fromLatin1("Font"), mFont);
+ config->writeEntry(QString::fromLatin1("BgColour"), mBgColour);
+ config->writeEntry(QString::fromLatin1("FgColour"), mFgColour);
+ config->writeEntry(QString::fromLatin1("ConfirmAck"), mConfirmAck);
+ if (mDateTime.isValid())
+ {
+ config->writeEntry(QString::fromLatin1("Time"), mDateTime.dateTime());
+ config->writeEntry(QString::fromLatin1("DateOnly"), mDateTime.isDateOnly());
+ }
+ if (mCloseTime.isValid())
+ config->writeEntry(QString::fromLatin1("Expiry"), mCloseTime);
+#ifndef WITHOUT_ARTS
+ if (mAudioRepeat && mSilenceButton && mSilenceButton->isEnabled())
+ {
+ // Only need to restart sound file playing if it's being repeated
+ config->writePathEntry(QString::fromLatin1("AudioFile"), mAudioFile);
+ config->writeEntry(QString::fromLatin1("Volume"), static_cast<int>(mVolume * 100));
+ }
+#endif
+ config->writeEntry(QString::fromLatin1("Speak"), mSpeak);
+ config->writeEntry(QString::fromLatin1("Height"), height());
+ config->writeEntry(QString::fromLatin1("DeferMins"), mDefaultDeferMinutes);
+ config->writeEntry(QString::fromLatin1("NoDefer"), mNoDefer);
+ config->writeEntry(QString::fromLatin1("NoPostAction"), mNoPostAction);
+ config->writeEntry(QString::fromLatin1("KMailSerial"), mKMailSerialNumber);
+ }
+ else
+ config->writeEntry(QString::fromLatin1("Invalid"), true);
+}
+
+/******************************************************************************
+* Read settings from the session managed config file.
+* This function is automatically called whenever the app is being restored.
+* Read in whatever was saved in saveProperties().
+*/
+void MessageWin::readProperties(KConfig* config)
+{
+ mInvalid = config->readBoolEntry(QString::fromLatin1("Invalid"), false);
+ mEventID = config->readEntry(QString::fromLatin1("EventID"));
+ mAlarmType = KAAlarm::Type(config->readNumEntry(QString::fromLatin1("AlarmType")));
+ mMessage = config->readEntry(QString::fromLatin1("Message"));
+ mAction = KAEvent::Action(config->readNumEntry(QString::fromLatin1("Type")));
+ mFont = config->readFontEntry(QString::fromLatin1("Font"));
+ mBgColour = config->readColorEntry(QString::fromLatin1("BgColour"));
+ mFgColour = config->readColorEntry(QString::fromLatin1("FgColour"));
+ mConfirmAck = config->readBoolEntry(QString::fromLatin1("ConfirmAck"));
+ QDateTime invalidDateTime;
+ QDateTime dt = config->readDateTimeEntry(QString::fromLatin1("Time"), &invalidDateTime);
+ bool dateOnly = config->readBoolEntry(QString::fromLatin1("DateOnly"));
+ mDateTime.set(dt, dateOnly);
+ mCloseTime = config->readDateTimeEntry(QString::fromLatin1("Expiry"), &invalidDateTime);
+#ifndef WITHOUT_ARTS
+ mAudioFile = config->readPathEntry(QString::fromLatin1("AudioFile"));
+ mVolume = static_cast<float>(config->readNumEntry(QString::fromLatin1("Volume"))) / 100;
+ mFadeVolume = -1;
+ mFadeSeconds = 0;
+ if (!mAudioFile.isEmpty())
+ mAudioRepeat = true;
+#endif
+ mSpeak = config->readBoolEntry(QString::fromLatin1("Speak"));
+ mRestoreHeight = config->readNumEntry(QString::fromLatin1("Height"));
+ mDefaultDeferMinutes = config->readNumEntry(QString::fromLatin1("DeferMins"));
+ mNoDefer = config->readBoolEntry(QString::fromLatin1("NoDefer"));
+ mNoPostAction = config->readBoolEntry(QString::fromLatin1("NoPostAction"));
+ mKMailSerialNumber = config->readUnsignedLongNumEntry(QString::fromLatin1("KMailSerial"));
+ mShowEdit = false;
+ kdDebug(5950) << "MessageWin::readProperties(" << mEventID << ")" << endl;
+ if (mAlarmType != KAAlarm::INVALID_ALARM)
+ {
+ // Recreate the event from the calendar file (if possible)
+ if (!mEventID.isEmpty())
+ {
+ const Event* kcalEvent = AlarmCalendar::activeCalendar()->event(mEventID);
+ if (!kcalEvent)
+ {
+ // It's not in the active calendar, so try the displaying calendar
+ AlarmCalendar* cal = AlarmCalendar::displayCalendar();
+ if (cal->isOpen())
+ kcalEvent = cal->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
+ }
+ if (kcalEvent)
+ {
+ mEvent.set(*kcalEvent);
+ mEvent.setUid(KAEvent::ACTIVE); // in case it came from the display calendar
+ mShowEdit = true;
+ }
+ }
+ initView();
+ }
+}
+
+/******************************************************************************
+* Returns the existing message window (if any) which is displaying the event
+* with the specified ID.
+*/
+MessageWin* MessageWin::findEvent(const QString& eventID)
+{
+ for (QValueList<MessageWin*>::Iterator it = mWindowList.begin(); it != mWindowList.end(); ++it)
+ {
+ MessageWin* w = *it;
+ if (w->mEventID == eventID && !w->mErrorWindow)
+ return w;
+ }
+ return 0;
+}
+
+/******************************************************************************
+* Beep and play the audio file, as appropriate.
+*/
+void MessageWin::playAudio()
+{
+ if (mBeep)
+ {
+ // Beep using two methods, in case the sound card/speakers are switched off or not working
+ KNotifyClient::beep(); // beep through the sound card & speakers
+ QApplication::beep(); // beep through the internal speaker
+ }
+ if (!mAudioFile.isEmpty())
+ {
+ if (!mVolume && mFadeVolume <= 0)
+ return; // ensure zero volume doesn't play anything
+#ifdef WITHOUT_ARTS
+ QString play = mAudioFile;
+ QString file = QString::fromLatin1("file:");
+ if (mAudioFile.startsWith(file))
+ play = mAudioFile.mid(file.length());
+ KAudioPlayer::play(QFile::encodeName(play));
+#else
+ // An audio file is specified. Because loading it may take some time,
+ // call it on a timer to allow the window to display first.
+ QTimer::singleShot(0, this, SLOT(slotPlayAudio()));
+#endif
+ }
+ else if (mSpeak)
+ {
+ // The message is to be spoken. In case of error messges,
+ // call it on a timer to allow the window to display first.
+ QTimer::singleShot(0, this, SLOT(slotSpeak()));
+ }
+}
+
+/******************************************************************************
+* Speak the message.
+* Called asynchronously to avoid delaying the display of the message.
+*/
+void MessageWin::slotSpeak()
+{
+ DCOPClient* client = kapp->dcopClient();
+ if (!client->isApplicationRegistered("kttsd"))
+ {
+ // kttsd is not running, so start it
+ QString error;
+ if (kapp->startServiceByDesktopName("kttsd", QStringList(), &error))
+ {
+ kdDebug(5950) << "MessageWin::slotSpeak(): failed to start kttsd: " << error << endl;
+ if (!haveErrorMessage(ErrMsg_Speak))
+ {
+ KMessageBox::detailedError(0, i18n("Unable to speak message"), error);
+ clearErrorMessage(ErrMsg_Speak);
+ }
+ return;
+ }
+ }
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << mMessage << "";
+ if (!client->send("kttsd", "KSpeech", "sayMessage(QString,QString)", data))
+ {
+ kdDebug(5950) << "MessageWin::slotSpeak(): sayMessage() DCOP error" << endl;
+ if (!haveErrorMessage(ErrMsg_Speak))
+ {
+ KMessageBox::detailedError(0, i18n("Unable to speak message"), i18n("DCOP Call sayMessage failed"));
+ clearErrorMessage(ErrMsg_Speak);
+ }
+ }
+}
+
+/******************************************************************************
+* Play the audio file.
+* Called asynchronously to avoid delaying the display of the message.
+*/
+void MessageWin::slotPlayAudio()
+{
+#ifndef WITHOUT_ARTS
+ // First check that it exists, to avoid possible crashes if the filename is badly specified
+ MainWindow* mmw = MainWindow::mainMainWindow();
+ KURL url(mAudioFile);
+ if (!url.isValid() || !KIO::NetAccess::exists(url, true, mmw)
+ || !KIO::NetAccess::download(url, mLocalAudioFile, mmw))
+ {
+ kdError(5950) << "MessageWin::playAudio(): Open failure: " << mAudioFile << endl;
+ if (!haveErrorMessage(ErrMsg_AudioFile))
+ {
+ KMessageBox::error(this, i18n("Cannot open audio file:\n%1").arg(mAudioFile));
+ clearErrorMessage(ErrMsg_AudioFile);
+ }
+ return;
+ }
+ if (!mArtsDispatcher)
+ {
+ mFadeTimer = 0;
+ mPlayTimer = new QTimer(this);
+ connect(mPlayTimer, SIGNAL(timeout()), SLOT(checkAudioPlay()));
+ mArtsDispatcher = new KArtsDispatcher;
+ mPlayedOnce = false;
+ mAudioFileStart = QTime::currentTime();
+ initAudio(true);
+ if (!mPlayObject->object().isNull())
+ checkAudioPlay();
+ if (!mUsingKMix && mVolume >= 0)
+ {
+ // Output error message now that everything else has been done.
+ // (Outputting it earlier would delay things until it is acknowledged.)
+ kdWarning(5950) << "Unable to set master volume (KMix: " << mKMixError << ")\n";
+ if (!haveErrorMessage(ErrMsg_Volume))
+ {
+ KMessageBox::information(this, i18n("Unable to set master volume\n(Error accessing KMix:\n%1)").arg(mKMixError),
+ QString::null, QString::fromLatin1("KMixError"));
+ clearErrorMessage(ErrMsg_Volume);
+ }
+ }
+ }
+#endif
+}
+
+#ifndef WITHOUT_ARTS
+/******************************************************************************
+* Set up the audio file for playing.
+*/
+void MessageWin::initAudio(bool firstTime)
+{
+ KArtsServer aserver;
+ Arts::SoundServerV2 sserver = aserver.server();
+ KDE::PlayObjectFactory factory(sserver);
+ mPlayObject = factory.createPlayObject(mLocalAudioFile, true);
+ if (firstTime)
+ {
+ // Save the existing sound volume setting for restoration afterwards,
+ // and set the desired volume for the alarm.
+ mUsingKMix = false;
+ float volume = mVolume; // initial volume
+ if (volume >= 0)
+ {
+ // The volume has been specified
+ if (mFadeVolume >= 0)
+ volume = mFadeVolume; // fading, so adjust the initial volume
+
+ // Get the current master volume from KMix
+ int vol = getKMixVolume();
+ if (vol >= 0)
+ {
+ mOldVolume = vol; // success
+ mUsingKMix = true;
+ setKMixVolume(static_cast<int>(volume * 100));
+ }
+ }
+ if (!mUsingKMix)
+ {
+ /* Adjust within the current master volume, because either
+ * a) the volume is not specified, in which case we want to play
+ * at 100% of the current master volume setting, or
+ * b) KMix is not available to set the master volume.
+ */
+ mOldVolume = sserver.outVolume().scaleFactor(); // save volume for restoration afterwards
+ sserver.outVolume().scaleFactor(volume >= 0 ? volume : 1);
+ }
+ }
+ mSilenceButton->setEnabled(true);
+ mPlayed = false;
+ connect(mPlayObject, SIGNAL(playObjectCreated()), SLOT(checkAudioPlay()));
+ if (!mPlayObject->object().isNull())
+ checkAudioPlay();
+}
+#endif
+
+/******************************************************************************
+* Called when the audio file has loaded and is ready to play, or on a timer
+* when play is expected to have completed.
+* If it is ready to play, start playing it (for the first time or repeated).
+* If play has not yet completed, wait a bit longer.
+*/
+void MessageWin::checkAudioPlay()
+{
+#ifndef WITHOUT_ARTS
+ if (!mPlayObject)
+ return;
+ if (mPlayObject->state() == Arts::posIdle)
+ {
+ // The file has loaded and is ready to play, or play has completed
+ if (mPlayedOnce && !mAudioRepeat)
+ {
+ // Play has completed
+ stopPlay();
+ return;
+ }
+
+ // Start playing the file, either for the first time or again
+ kdDebug(5950) << "MessageWin::checkAudioPlay(): start\n";
+ if (!mPlayedOnce)
+ {
+ // Start playing the file for the first time
+ QTime now = QTime::currentTime();
+ mAudioFileLoadSecs = mAudioFileStart.secsTo(now);
+ if (mAudioFileLoadSecs < 0)
+ mAudioFileLoadSecs += 86400;
+ if (mVolume >= 0 && mFadeVolume >= 0 && mFadeSeconds > 0)
+ {
+ // Set up volume fade
+ mAudioFileStart = now;
+ mFadeTimer = new QTimer(this);
+ connect(mFadeTimer, SIGNAL(timeout()), SLOT(slotFade()));
+ mFadeTimer->start(1000); // adjust volume every second
+ }
+ mPlayedOnce = true;
+ }
+ if (mAudioFileLoadSecs < 3)
+ {
+ /* The aRts library takes several attempts before a PlayObject can
+ * be replayed, leaving a gap of perhaps 5 seconds between plays.
+ * So if loading the file takes a short time, it's better to reload
+ * the PlayObject rather than try to replay the same PlayObject.
+ */
+ if (mPlayed)
+ {
+ // Playing has completed. Start playing again.
+ delete mPlayObject;
+ initAudio(false);
+ if (mPlayObject->object().isNull())
+ return;
+ }
+ mPlayed = true;
+ mPlayObject->play();
+ }
+ else
+ {
+ // The file is slow to load, so attempt to replay the PlayObject
+ static Arts::poTime t0((long)0, (long)0, 0, std::string());
+ Arts::poTime current = mPlayObject->currentTime();
+ if (current.seconds || current.ms)
+ mPlayObject->seek(t0);
+ else
+ mPlayObject->play();
+ }
+ }
+
+ // The sound file is still playing
+ Arts::poTime overall = mPlayObject->overallTime();
+ Arts::poTime current = mPlayObject->currentTime();
+ int time = 1000*(overall.seconds - current.seconds) + overall.ms - current.ms;
+ if (time < 0)
+ time = 0;
+ kdDebug(5950) << "MessageWin::checkAudioPlay(): wait for " << (time+100) << "ms\n";
+ mPlayTimer->start(time + 100, true);
+#endif
+}
+
+/******************************************************************************
+* Called when play completes, the Silence button is clicked, or the window is
+* closed, to reset the sound volume and terminate audio access.
+*/
+void MessageWin::stopPlay()
+{
+#ifndef WITHOUT_ARTS
+ if (mArtsDispatcher)
+ {
+ // Restore the sound volume to what it was before the sound file
+ // was played, provided that nothing else has modified it since.
+ if (!mUsingKMix)
+ {
+ KArtsServer aserver;
+ Arts::StereoVolumeControl svc = aserver.server().outVolume();
+ float currentVolume = svc.scaleFactor();
+ float eventVolume = mVolume;
+ if (eventVolume < 0)
+ eventVolume = 1;
+ if (currentVolume == eventVolume)
+ svc.scaleFactor(mOldVolume);
+ }
+ else if (mVolume >= 0)
+ {
+ int eventVolume = static_cast<int>(mVolume * 100);
+ int currentVolume = getKMixVolume();
+ // Volume returned isn't always exactly equal to volume set
+ if (currentVolume < 0 || abs(currentVolume - eventVolume) < 5)
+ setKMixVolume(static_cast<int>(mOldVolume));
+ }
+ }
+ delete mPlayObject; mPlayObject = 0;
+ delete mArtsDispatcher; mArtsDispatcher = 0;
+ if (!mLocalAudioFile.isEmpty())
+ {
+ KIO::NetAccess::removeTempFile(mLocalAudioFile); // removes it only if it IS a temporary file
+ mLocalAudioFile = QString::null;
+ }
+ if (mSilenceButton)
+ mSilenceButton->setEnabled(false);
+#endif
+}
+
+/******************************************************************************
+* Called every second to fade the volume when the audio file starts playing.
+*/
+void MessageWin::slotFade()
+{
+#ifndef WITHOUT_ARTS
+ QTime now = QTime::currentTime();
+ int elapsed = mAudioFileStart.secsTo(now);
+ if (elapsed < 0)
+ elapsed += 86400; // it's the next day
+ float volume;
+ if (elapsed >= mFadeSeconds)
+ {
+ // The fade has finished. Set to normal volume.
+ volume = mVolume;
+ delete mFadeTimer;
+ mFadeTimer = 0;
+ if (!mVolume)
+ {
+ kdDebug(5950) << "MessageWin::slotFade(0)\n";
+ stopPlay();
+ return;
+ }
+ }
+ else
+ volume = mFadeVolume + ((mVolume - mFadeVolume) * elapsed) / mFadeSeconds;
+ kdDebug(5950) << "MessageWin::slotFade(" << volume << ")\n";
+ if (mArtsDispatcher)
+ {
+ if (mUsingKMix)
+ setKMixVolume(static_cast<int>(volume * 100));
+ else if (mArtsDispatcher)
+ {
+ KArtsServer aserver;
+ aserver.server().outVolume().scaleFactor(volume);
+ }
+ }
+#endif
+}
+
+#ifndef WITHOUT_ARTS
+/******************************************************************************
+* Get the master volume from KMix.
+* Reply < 0 if failure.
+*/
+int MessageWin::getKMixVolume()
+{
+ if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running
+ return -1;
+ QByteArray data, replyData;
+ QCString replyType;
+ QDataStream arg(data, IO_WriteOnly);
+ if (!kapp->dcopClient()->call(mKMixName, KMIX_DCOP_OBJECT, "masterVolume()", data, replyType, replyData)
+ || replyType != "int")
+ return -1;
+ int result;
+ QDataStream reply(replyData, IO_ReadOnly);
+ reply >> result;
+ return (result >= 0) ? result : 0;
+}
+
+/******************************************************************************
+* Set the master volume using KMix.
+*/
+void MessageWin::setKMixVolume(int percent)
+{
+ if (!mUsingKMix)
+ return;
+ if (!KAlarm::runProgram(KMIX_APP_NAME, KMIX_DCOP_WINDOW, mKMixName, mKMixError)) // start KMix if it isn't already running
+ return;
+ QByteArray data;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << percent;
+ if (!kapp->dcopClient()->send(mKMixName, KMIX_DCOP_OBJECT, "setMasterVolume(int)", data))
+ kdError(5950) << "MessageWin::setKMixVolume(): kmix dcop call failed\n";
+}
+#endif
+
+/******************************************************************************
+* Raise the alarm window, re-output any required audio notification, and
+* reschedule the alarm in the calendar file.
+*/
+void MessageWin::repeat(const KAAlarm& alarm)
+{
+ if (mDeferDlg)
+ {
+ // Cancel any deferral dialogue so that the user notices something's going on,
+ // and also because the deferral time limit will have changed.
+ delete mDeferDlg;
+ mDeferDlg = 0;
+ }
+ const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID);
+ if (kcalEvent)
+ {
+ mAlarmType = alarm.type(); // store new alarm type for use if it is later deferred
+ if (!mDeferDlg || Preferences::modalMessages())
+ {
+ raise();
+ playAudio();
+ }
+ KAEvent event(*kcalEvent);
+ mDeferButton->setEnabled(true);
+ setDeferralLimit(event); // ensure that button is disabled when alarm can't be deferred any more
+ theApp()->alarmShowing(event, mAlarmType, mDateTime);
+ }
+}
+
+/******************************************************************************
+* Display the window.
+* If windows are being positioned away from the mouse cursor, it is initially
+* positioned at the top left to slightly reduce the number of times the
+* windows need to be moved in showEvent().
+*/
+void MessageWin::show()
+{
+ if (mCloseTime.isValid())
+ {
+ // Set a timer to auto-close the window
+ int delay = QDateTime::currentDateTime().secsTo(mCloseTime);
+ if (delay < 0)
+ delay = 0;
+ QTimer::singleShot(delay * 1000, this, SLOT(close()));
+ if (!delay)
+ return; // don't show the window if auto-closing is already due
+ }
+ if (Preferences::messageButtonDelay() == 0)
+ move(0, 0);
+ MainWindowBase::show();
+}
+
+/******************************************************************************
+* Returns the window's recommended size exclusive of its frame.
+* For message windows, the size if limited to fit inside the working area of
+* the desktop.
+*/
+QSize MessageWin::sizeHint() const
+{
+ if (mAction != KAEvent::MESSAGE)
+ return MainWindowBase::sizeHint();
+ if (!mWinModule)
+ mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
+ QSize frame = frameGeometry().size();
+ QSize contents = geometry().size();
+ QSize desktop = mWinModule->workArea().size();
+ QSize maxSize(desktop.width() - (frame.width() - contents.width()),
+ desktop.height() - (frame.height() - contents.height()));
+ return MainWindowBase::sizeHint().boundedTo(maxSize);
+}
+
+/******************************************************************************
+* Called when the window is shown.
+* The first time, output any required audio notification, and reschedule or
+* delete the event from the calendar file.
+*/
+void MessageWin::showEvent(QShowEvent* se)
+{
+ MainWindowBase::showEvent(se);
+ if (!mShown)
+ {
+ if (mErrorWindow)
+ enableButtons(); // don't bother repositioning error messages
+ else
+ {
+ /* Set the window size.
+ * Note that the frame thickness is not yet known when this
+ * method is called, so for large windows the size needs to be
+ * set again later.
+ */
+ QSize s = sizeHint(); // fit the window round the message
+ if (mAction == KAEvent::FILE && !mErrorMsgs.count())
+ KAlarm::readConfigWindowSize("FileMessage", s);
+ resize(s);
+
+ mButtonDelay = Preferences::messageButtonDelay() * 1000;
+ if (!mButtonDelay)
+ {
+ /* Try to ensure that the window can't accidentally be acknowledged
+ * by the user clicking the mouse just as it appears.
+ * To achieve this, move the window so that the OK button is as far away
+ * from the cursor as possible. If the buttons are still too close to the
+ * cursor, disable the buttons for a short time.
+ * N.B. This can't be done in show(), since the geometry of the window
+ * is not known until it is displayed. Unfortunately by moving the
+ * window in showEvent(), a flicker is unavoidable.
+ * See the Qt documentation on window geometry for more details.
+ */
+ // PROBLEM: The frame size is not known yet!
+
+ /* Find the usable area of the desktop or, if the desktop comprises
+ * multiple screens, the usable area of the current screen. (If the
+ * message is displayed on a screen other than that currently being
+ * worked with, it might not be noticed.)
+ */
+ QPoint cursor = QCursor::pos();
+ if (!mWinModule)
+ mWinModule = new KWinModule(0, KWinModule::INFO_DESKTOP);
+ QRect desk = mWinModule->workArea();
+ QDesktopWidget* dw = QApplication::desktop();
+ if (dw->numScreens() > 1)
+ desk &= dw->screenGeometry(dw->screenNumber(cursor));
+
+ QRect frame = frameGeometry();
+ QRect rect = geometry();
+ // Find the offsets from the outside of the frame to the edges of the OK button
+ QRect button(mOkButton->mapToParent(QPoint(0, 0)), mOkButton->mapToParent(mOkButton->rect().bottomRight()));
+ int buttonLeft = button.left() + rect.left() - frame.left();
+ int buttonRight = width() - button.right() + frame.right() - rect.right();
+ int buttonTop = button.top() + rect.top() - frame.top();
+ int buttonBottom = height() - button.bottom() + frame.bottom() - rect.bottom();
+
+ int centrex = (desk.width() + buttonLeft - buttonRight) / 2;
+ int centrey = (desk.height() + buttonTop - buttonBottom) / 2;
+ int x = (cursor.x() < centrex) ? desk.right() - frame.width() : desk.left();
+ int y = (cursor.y() < centrey) ? desk.bottom() - frame.height() : desk.top();
+
+ // Find the enclosing rectangle for the new button positions
+ // and check if the cursor is too near
+ QRect buttons = mOkButton->geometry().unite(mKAlarmButton->geometry());
+ buttons.moveBy(rect.left() + x - frame.left(), rect.top() + y - frame.top());
+ int minDistance = proximityMultiple * mOkButton->height();
+ if ((abs(cursor.x() - buttons.left()) < minDistance
+ || abs(cursor.x() - buttons.right()) < minDistance)
+ && (abs(cursor.y() - buttons.top()) < minDistance
+ || abs(cursor.y() - buttons.bottom()) < minDistance))
+ mButtonDelay = proximityButtonDelay; // too near - disable buttons initially
+
+ if (x != frame.left() || y != frame.top())
+ {
+ mPositioning = true;
+ move(x, y);
+ }
+ }
+ if (!mPositioning)
+ displayComplete(); // play audio, etc.
+ if (mAction == KAEvent::MESSAGE)
+ {
+ // Set the window size once the frame size is known
+ QTimer::singleShot(0, this, SLOT(setMaxSize()));
+ }
+ }
+ mShown = true;
+ }
+}
+
+/******************************************************************************
+* Called when the window has been moved.
+*/
+void MessageWin::moveEvent(QMoveEvent* e)
+{
+ MainWindowBase::moveEvent(e);
+ if (mPositioning)
+ {
+ // The window has just been initially positioned
+ mPositioning = false;
+ displayComplete(); // play audio, etc.
+ }
+}
+
+/******************************************************************************
+* Reset the iniital window size if it exceeds the working area of the desktop.
+*/
+void MessageWin::setMaxSize()
+{
+ QSize s = sizeHint();
+ if (width() > s.width() || height() > s.height())
+ resize(s);
+}
+
+/******************************************************************************
+* Called when the window has been displayed properly (in its correct position),
+* to play sounds and reschedule the event.
+*/
+void MessageWin::displayComplete()
+{
+ playAudio();
+ if (mRescheduleEvent)
+ theApp()->alarmShowing(mEvent, mAlarmType, mDateTime);
+
+ // Enable the window's buttons either now or after the configured delay
+ if (mButtonDelay > 0)
+ QTimer::singleShot(mButtonDelay, this, SLOT(enableButtons()));
+ else
+ enableButtons();
+}
+
+/******************************************************************************
+* Enable the window's buttons.
+*/
+void MessageWin::enableButtons()
+{
+ mOkButton->setEnabled(true);
+ mKAlarmButton->setEnabled(true);
+ if (mDeferButton && !mDisableDeferral)
+ mDeferButton->setEnabled(true);
+ if (mEditButton)
+ mEditButton->setEnabled(true);
+ if (mKMailButton)
+ mKMailButton->setEnabled(true);
+}
+
+/******************************************************************************
+* Called when the window's size has changed (before it is painted).
+*/
+void MessageWin::resizeEvent(QResizeEvent* re)
+{
+ if (mRestoreHeight)
+ {
+ // Restore the window height on session restoration
+ if (mRestoreHeight != re->size().height())
+ {
+ QSize size = re->size();
+ size.setHeight(mRestoreHeight);
+ resize(size);
+ }
+ else if (isVisible())
+ mRestoreHeight = 0;
+ }
+ else
+ {
+ if (mShown && mAction == KAEvent::FILE && !mErrorMsgs.count())
+ KAlarm::writeConfigWindowSize("FileMessage", re->size());
+ MainWindowBase::resizeEvent(re);
+ }
+}
+
+/******************************************************************************
+* Called when a close event is received.
+* Only quits the application if there is no system tray icon displayed.
+*/
+void MessageWin::closeEvent(QCloseEvent* ce)
+{
+ // Don't prompt or delete the alarm from the display calendar if the session is closing
+ if (!mErrorWindow && !theApp()->sessionClosingDown())
+ {
+ if (mConfirmAck && !mNoCloseConfirm)
+ {
+ // Ask for confirmation of acknowledgement. Use warningYesNo() because its default is No.
+ if (KMessageBox::warningYesNo(this, i18n("Do you really want to acknowledge this alarm?"),
+ i18n("Acknowledge Alarm"), i18n("&Acknowledge"), KStdGuiItem::cancel())
+ != KMessageBox::Yes)
+ {
+ ce->ignore();
+ return;
+ }
+ }
+ if (!mEventID.isNull())
+ {
+ // Delete from the display calendar
+ KAlarm::deleteDisplayEvent(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
+ }
+ }
+ MainWindowBase::closeEvent(ce);
+}
+
+/******************************************************************************
+* Called when the KMail button is clicked.
+* Tells KMail to display the email message displayed in this message window.
+*/
+void MessageWin::slotShowKMailMessage()
+{
+ kdDebug(5950) << "MessageWin::slotShowKMailMessage()\n";
+ if (!mKMailSerialNumber)
+ return;
+ QString err = KAlarm::runKMail(false);
+ if (!err.isNull())
+ {
+ KMessageBox::sorry(this, err);
+ return;
+ }
+ QCString replyType;
+ QByteArray data, replyData;
+ QDataStream arg(data, IO_WriteOnly);
+ arg << (Q_UINT32)mKMailSerialNumber << QString::null;
+ if (kapp->dcopClient()->call("kmail", KMAIL_DCOP_OBJECT, "showMail(Q_UINT32,QString)", data, replyType, replyData)
+ && replyType == "bool")
+ {
+ bool result;
+ QDataStream replyStream(replyData, IO_ReadOnly);
+ replyStream >> result;
+ if (result)
+ return; // success
+ }
+ kdError(5950) << "MessageWin::slotShowKMailMessage(): kmail dcop call failed\n";
+ KMessageBox::sorry(this, i18n("Unable to locate this email in KMail"));
+}
+
+/******************************************************************************
+* Called when the Edit... button is clicked.
+* Displays the alarm edit dialog.
+*/
+void MessageWin::slotEdit()
+{
+ kdDebug(5950) << "MessageWin::slotEdit()" << endl;
+ EditAlarmDlg editDlg(false, i18n("Edit Alarm"), this, "editDlg", &mEvent);
+ if (editDlg.exec() == QDialog::Accepted)
+ {
+ KAEvent event;
+ editDlg.getEvent(event);
+
+ // Update the displayed lists and the calendar file
+ KAlarm::UpdateStatus status;
+ if (AlarmCalendar::activeCalendar()->event(mEventID))
+ {
+ // The old alarm hasn't expired yet, so replace it
+ status = KAlarm::modifyEvent(mEvent, event, 0, &editDlg);
+ Undo::saveEdit(mEvent, event);
+ }
+ else
+ {
+ // The old event has expired, so simply create a new one
+ status = KAlarm::addEvent(event, 0, &editDlg);
+ Undo::saveAdd(event);
+ }
+
+ if (status == KAlarm::UPDATE_KORG_ERR)
+ KAlarm::displayKOrgUpdateError(&editDlg, KAlarm::KORG_ERR_MODIFY, 1);
+ KAlarm::outputAlarmWarnings(&editDlg, &event);
+
+ // Close the alarm window
+ mNoCloseConfirm = true; // allow window to close without confirmation prompt
+ close();
+ }
+}
+
+/******************************************************************************
+* Set up to disable the defer button when the deferral limit is reached.
+*/
+void MessageWin::setDeferralLimit(const KAEvent& event)
+{
+ if (mDeferButton)
+ {
+ mDeferLimit = event.deferralLimit().dateTime();
+ MidnightTimer::connect(this, SLOT(checkDeferralLimit())); // check every day
+ mDisableDeferral = false;
+ checkDeferralLimit();
+ }
+}
+
+/******************************************************************************
+* Check whether the deferral limit has been reached.
+* If so, disable the Defer button.
+* N.B. Ideally, just a single QTimer::singleShot() call would be made to disable
+* the defer button at the corret time. But for a 32-bit integer, the
+* milliseconds parameter overflows in about 25 days, so instead a daily
+* check is done until the day when the deferral limit is reached, followed
+* by a non-overflowing QTimer::singleShot() call.
+*/
+void MessageWin::checkDeferralLimit()
+{
+ if (!mDeferButton || !mDeferLimit.isValid())
+ return;
+ int n = QDate::currentDate().daysTo(mDeferLimit.date());
+ if (n > 0)
+ return;
+ MidnightTimer::disconnect(this, SLOT(checkDeferralLimit()));
+ if (n == 0)
+ {
+ // The deferral limit will be reached today
+ n = QTime::currentTime().secsTo(mDeferLimit.time());
+ if (n > 0)
+ {
+ QTimer::singleShot(n * 1000, this, SLOT(checkDeferralLimit()));
+ return;
+ }
+ }
+ mDeferButton->setEnabled(false);
+ mDisableDeferral = true;
+}
+
+/******************************************************************************
+* Called when the Defer... button is clicked.
+* Displays the defer message dialog.
+*/
+void MessageWin::slotDefer()
+{
+ mDeferDlg = new DeferAlarmDlg(i18n("Defer Alarm"), QDateTime::currentDateTime().addSecs(60),
+ false, this, "deferDlg");
+ if (mDefaultDeferMinutes > 0)
+ mDeferDlg->setDeferMinutes(mDefaultDeferMinutes);
+ mDeferDlg->setLimit(mEventID);
+ if (!Preferences::modalMessages())
+ lower();
+ if (mDeferDlg->exec() == QDialog::Accepted)
+ {
+ DateTime dateTime = mDeferDlg->getDateTime();
+ int delayMins = mDeferDlg->deferMinutes();
+ const Event* kcalEvent = mEventID.isNull() ? 0 : AlarmCalendar::activeCalendar()->event(mEventID);
+ if (kcalEvent)
+ {
+ // The event still exists in the calendar file.
+ KAEvent event(*kcalEvent);
+ bool repeat = event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true);
+ event.setDeferDefaultMinutes(delayMins);
+ KAlarm::updateEvent(event, 0, mDeferDlg, true, !repeat);
+ if (event.deferred())
+ mNoPostAction = true;
+ }
+ else
+ {
+ KAEvent event;
+ kcalEvent = AlarmCalendar::displayCalendar()->event(KAEvent::uid(mEventID, KAEvent::DISPLAYING));
+ if (kcalEvent)
+ {
+ event.reinstateFromDisplaying(KAEvent(*kcalEvent));
+ event.defer(dateTime, (mAlarmType & KAAlarm::REMINDER_ALARM), true);
+ }
+ else
+ {
+ // The event doesn't exist any more !?!, so create a new one
+ event.set(dateTime.dateTime(), mMessage, mBgColour, mFgColour, mFont, mAction, mLateCancel, mFlags);
+ event.setAudioFile(mAudioFile, mVolume, mFadeVolume, mFadeSeconds);
+ event.setArchive();
+ event.setEventID(mEventID);
+ }
+ event.setDeferDefaultMinutes(delayMins);
+ // Add the event back into the calendar file, retaining its ID
+ // and not updating KOrganizer
+ KAlarm::addEvent(event, 0, mDeferDlg, true, false);
+ if (event.deferred())
+ mNoPostAction = true;
+ if (kcalEvent)
+ {
+ event.setUid(KAEvent::EXPIRED);
+ KAlarm::deleteEvent(event, false);
+ }
+ }
+ if (theApp()->wantRunInSystemTray())
+ {
+ // Alarms are to be displayed only if the system tray icon is running,
+ // so start it if necessary so that the deferred alarm will be shown.
+ theApp()->displayTrayIcon(true);
+ }
+ mNoCloseConfirm = true; // allow window to close without confirmation prompt
+ close();
+ }
+ else
+ raise();
+ delete mDeferDlg;
+ mDeferDlg = 0;
+}
+
+/******************************************************************************
+* Called when the KAlarm icon button in the message window is clicked.
+* Displays the main window, with the appropriate alarm selected.
+*/
+void MessageWin::displayMainWindow()
+{
+ KAlarm::displayMainWindowSelected(mEventID);
+}
+
+/******************************************************************************
+* Check whether the specified error message is already displayed for this
+* alarm, and note that it will now be displayed.
+* Reply = true if message is already displayed.
+*/
+bool MessageWin::haveErrorMessage(unsigned msg) const
+{
+ if (!mErrorMessages.contains(mEventID))
+ mErrorMessages.insert(mEventID, 0);
+ bool result = (mErrorMessages[mEventID] & msg);
+ mErrorMessages[mEventID] |= msg;
+ return result;
+}
+
+void MessageWin::clearErrorMessage(unsigned msg) const
+{
+ if (mErrorMessages.contains(mEventID))
+ {
+ if (mErrorMessages[mEventID] == msg)
+ mErrorMessages.remove(mEventID);
+ else
+ mErrorMessages[mEventID] &= ~msg;
+ }
+}
+
+
+/******************************************************************************
+* Check whether the message window should be modal, i.e. with title bar etc.
+* Normally this follows the Preferences setting, but if there is a full screen
+* window displayed, on X11 the message window has to bypass the window manager
+* in order to display on top of it (which has the side effect that it will have
+* no window decoration).
+*/
+bool wantModal()
+{
+ bool modal = Preferences::modalMessages();
+ if (modal)
+ {
+ KWinModule wm(0, KWinModule::INFO_DESKTOP);
+ KWin::WindowInfo wi = KWin::windowInfo(wm.activeWindow(), NET::WMState);
+ modal = !(wi.valid() && wi.hasState(NET::FullScreen));
+ }
+ return modal;
+}
+
+
+/*=============================================================================
+= Class MWMimeSourceFactory
+* Gets the mime type of a text file from not only its extension (as per
+* QMimeSourceFactory), but also from its contents. This allows the detection
+* of plain text files without file name extensions.
+=============================================================================*/
+MWMimeSourceFactory::MWMimeSourceFactory(const QString& absPath, KTextBrowser* view)
+ : QMimeSourceFactory(),
+ mMimeType("text/plain"),
+ mLast(0)
+{
+ view->setMimeSourceFactory(this);
+ QString type = KMimeType::findByPath(absPath)->name();
+ switch (KAlarm::fileType(type))
+ {
+ case KAlarm::TextPlain:
+ case KAlarm::TextFormatted:
+ mMimeType = type.latin1();
+ // fall through to 'TextApplication'
+ case KAlarm::TextApplication:
+ default:
+ // It's assumed to be a text file
+ mTextFile = absPath;
+ view->QTextBrowser::setSource(absPath);
+ break;
+
+ case KAlarm::Image:
+ // It's an image file
+ QString text = "<img source=\"";
+ text += absPath;
+ text += "\">";
+ view->setText(text);
+ break;
+ }
+ setFilePath(QFileInfo(absPath).dirPath(true));
+}
+
+MWMimeSourceFactory::~MWMimeSourceFactory()
+{
+ delete mLast;
+}
+
+const QMimeSource* MWMimeSourceFactory::data(const QString& abs_name) const
+{
+ if (abs_name == mTextFile)
+ {
+ QFileInfo fi(abs_name);
+ if (fi.isReadable())
+ {
+ QFile f(abs_name);
+ if (f.open(IO_ReadOnly) && f.size())
+ {
+ QByteArray ba(f.size());
+ f.readBlock(ba.data(), ba.size());
+ QStoredDrag* sr = new QStoredDrag(mMimeType);
+ sr->setEncodedData(ba);
+ delete mLast;
+ mLast = sr;
+ return sr;
+ }
+ }
+ }
+ return QMimeSourceFactory::data(abs_name);
+}
diff --git a/kalarm/messagewin.h b/kalarm/messagewin.h
new file mode 100644
index 00000000..0a10b39c
--- /dev/null
+++ b/kalarm/messagewin.h
@@ -0,0 +1,165 @@
+/*
+ * messagewin.h - displays an alarm message
+ * Program: kalarm
+ * Copyright © 2001-2007 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.
+ */
+
+#ifndef MESSAGEWIN_H
+#define MESSAGEWIN_H
+
+/** @file messagewin.h - displays an alarm message */
+
+#include <qmap.h>
+
+#include "mainwindowbase.h"
+#include "alarmevent.h"
+
+class QPushButton;
+class KPushButton;
+class QLabel;
+class QTimer;
+class KWinModule;
+class AlarmTimeWidget;
+class DeferAlarmDlg;
+class KArtsDispatcher;
+namespace KDE { class PlayObject; }
+
+/**
+ * MessageWin: A window to display an alarm message
+ */
+class MessageWin : public MainWindowBase
+{
+ Q_OBJECT
+ public:
+ MessageWin(); // for session management restoration only
+ MessageWin(const KAEvent&, const KAAlarm&, bool reschedule_event = true, bool allowDefer = true);
+ MessageWin(const KAEvent&, const DateTime& alarmDateTime, const QStringList& errmsgs);
+ ~MessageWin();
+ void repeat(const KAAlarm&);
+ void setRecreating() { mRecreating = true; }
+ const DateTime& dateTime() { return mDateTime; }
+ KAAlarm::Type alarmType() const { return mAlarmType; }
+ bool hasDefer() const { return !!mDeferButton; }
+ bool isValid() const { return !mInvalid; }
+ virtual void show();
+ virtual QSize sizeHint() const;
+ static int instanceCount() { return mWindowList.count(); }
+ static MessageWin* findEvent(const QString& eventID);
+
+ protected:
+ virtual void showEvent(QShowEvent*);
+ virtual void moveEvent(QMoveEvent*);
+ virtual void resizeEvent(QResizeEvent*);
+ virtual void closeEvent(QCloseEvent*);
+ virtual void saveProperties(KConfig*);
+ virtual void readProperties(KConfig*);
+
+ private slots:
+ void slotEdit();
+ void slotDefer();
+ void checkDeferralLimit();
+ void displayMainWindow();
+ void slotShowKMailMessage();
+ void slotSpeak();
+ void slotPlayAudio();
+ void checkAudioPlay();
+ void stopPlay();
+ void slotFade();
+ void enableButtons();
+ void setRemainingTextDay();
+ void setRemainingTextMinute();
+ void setMaxSize();
+
+ private:
+ void initView();
+#ifndef WITHOUT_ARTS
+ void initAudio(bool firstTime);
+ int getKMixVolume();
+ void setKMixVolume(int percent);
+#endif
+ void displayComplete();
+ void playAudio();
+ void setDeferralLimit(const KAEvent&);
+ bool haveErrorMessage(unsigned msg) const;
+ void clearErrorMessage(unsigned msg) const;
+
+ static QValueList<MessageWin*> mWindowList; // list of existing message windows
+ static QMap<QString, unsigned> mErrorMessages; // error messages currently displayed, by event ID
+ // Properties needed by readProperties()
+ QString mMessage;
+ QFont mFont;
+ QColor mBgColour, mFgColour;
+ DateTime mDateTime; // date/time displayed in the message window
+ QDateTime mCloseTime; // time at which window should be auto-closed
+ QString mEventID;
+ QString mAudioFile;
+ float mVolume;
+ float mFadeVolume;
+ int mFadeSeconds;
+ int mDefaultDeferMinutes;
+ KAAlarm::Type mAlarmType;
+ KAEvent::Action mAction;
+ unsigned long mKMailSerialNumber; // if email text, message's KMail serial number, else 0
+ QStringList mErrorMsgs;
+ int mRestoreHeight;
+ bool mAudioRepeat;
+ bool mConfirmAck;
+ bool mShowEdit; // display the Edit button
+ bool mNoDefer; // don't display a Defer option
+ bool mInvalid; // restored window is invalid
+ // Sound file playing
+ KArtsDispatcher* mArtsDispatcher;
+ KDE::PlayObject* mPlayObject;
+ QCString mKMixName; // DCOP name for KMix
+ QString mKMixError; // error message starting KMix
+ QTimer* mPlayTimer; // timer for repeating the sound file
+ QTimer* mFadeTimer; // timer for fading the sound volume
+ float mOldVolume; // volume before volume was set for sound file
+ QString mLocalAudioFile; // local copy of audio file
+ QTime mAudioFileStart; // time when audio file loading first started, or when play first started
+ int mAudioFileLoadSecs; // how many seconds it took to load audio file
+ bool mPlayedOnce; // the sound file has started playing at least once
+ bool mPlayed; // the PlayObject->play() has been called
+ // Miscellaneous
+ KAEvent mEvent; // the whole event, for updating the calendar file
+ QLabel* mRemainingText; // the remaining time (for a reminder window)
+ KPushButton* mOkButton;
+ QPushButton* mEditButton;
+ QPushButton* mDeferButton;
+ QPushButton* mSilenceButton;
+ QPushButton* mKAlarmButton;
+ QPushButton* mKMailButton;
+ DeferAlarmDlg* mDeferDlg;
+ QDateTime mDeferLimit; // last time to which the message can currently be deferred
+ mutable KWinModule* mWinModule;
+ int mFlags;
+ int mLateCancel;
+ int mButtonDelay; // delay (ms) after window is shown before buttons are enabled
+ bool mErrorWindow; // the window is simply an error message
+ bool mNoPostAction; // don't execute any post-alarm action
+ bool mRecreating; // window is about to be deleted and immediately recreated
+ bool mBeep;
+ bool mSpeak; // the message should be spoken via kttsd
+ bool mRescheduleEvent; // true to delete event after message has been displayed
+ bool mShown; // true once the window has been displayed
+ bool mPositioning; // true when the window is being positioned initially
+ bool mNoCloseConfirm; // the Defer or Edit button is closing the dialog
+ bool mUsingKMix; // master volume is being set using kmix
+ bool mDisableDeferral; // true if past deferral limit, so don't enable Defer button
+};
+
+#endif // MESSAGEWIN_H
diff --git a/kalarm/pickfileradio.cpp b/kalarm/pickfileradio.cpp
new file mode 100644
index 00000000..c0027650
--- /dev/null
+++ b/kalarm/pickfileradio.cpp
@@ -0,0 +1,182 @@
+/*
+ * pickfileradio.cpp - radio button with an associated file picker
+ * Program: kalarm
+ * Copyright (C) 2005 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 "kalarm.h"
+
+#include <qbuttongroup.h>
+#include <qpushbutton.h>
+#include <qtimer.h>
+
+#include <kdebug.h>
+
+#include "lineedit.h"
+#include "pickfileradio.moc"
+
+
+PickFileRadio::PickFileRadio(QPushButton* button, LineEdit* edit, const QString& text, QButtonGroup* parent, const char* name)
+ : RadioButton(text, parent, name),
+ mGroup(parent),
+ mEdit(edit),
+ mButton(button),
+ mLastId(-1), // set to an invalid value
+ mRevertId(false)
+{
+ Q_ASSERT(parent);
+ Q_ASSERT(button);
+ mButton->setEnabled(false);
+ connect(mButton, SIGNAL(clicked()), SLOT(slotPickFile()));
+ if (mEdit)
+ mEdit->setEnabled(false);
+ connect(mGroup, SIGNAL(buttonSet(int)), SLOT(slotSelectionChanged(int)));
+}
+
+PickFileRadio::PickFileRadio(const QString& text, QButtonGroup* parent, const char* name)
+ : RadioButton(text, parent, name),
+ mGroup(parent),
+ mEdit(0),
+ mButton(0),
+ mLastId(-1), // set to an invalid value
+ mRevertId(false)
+{
+ Q_ASSERT(parent);
+}
+
+void PickFileRadio::init(QPushButton* button, LineEdit* edit)
+{
+ Q_ASSERT(button);
+ mEdit = edit;
+ mButton = button;
+ mButton->setEnabled(false);
+ connect(mButton, SIGNAL(clicked()), SLOT(slotPickFile()));
+ if (mEdit)
+ mEdit->setEnabled(false);
+ connect(mGroup, SIGNAL(buttonSet(int)), SLOT(slotSelectionChanged(int)));
+ setReadOnly(RadioButton::isReadOnly());
+}
+
+void PickFileRadio::setReadOnly(bool ro)
+{
+ RadioButton::setReadOnly(ro);
+ if (mButton)
+ {
+ if (mEdit)
+ mEdit->setReadOnly(ro);
+ if (ro)
+ mButton->hide();
+ else
+ mButton->show();
+ }
+}
+
+void PickFileRadio::setFile(const QString& file)
+{
+ mFile = file;
+}
+
+QString PickFileRadio::file() const
+{
+ return mEdit ? mEdit->text() : mFile;
+}
+
+/******************************************************************************
+* Set the radio button enabled or disabled.
+* Adjusts the enabled/disabled state of other controls appropriately.
+*/
+void PickFileRadio::setEnabled(bool enable)
+{
+ Q_ASSERT(mButton);
+ RadioButton::setEnabled(enable);
+ enable = enable && mGroup->selected() == this;
+ if (enable)
+ {
+ if (!pickFileIfNone())
+ enable = false; // revert to previously selected type
+ }
+ mButton->setEnabled(enable);
+ if (mEdit)
+ mEdit->setEnabled(enable);
+}
+
+/******************************************************************************
+* Called when the selected radio button changes.
+*/
+void PickFileRadio::slotSelectionChanged(int id)
+{
+ if (id == mLastId || mRevertId)
+ return;
+ int radioId = mGroup->id(this);
+ if (mLastId == radioId)
+ {
+ mButton->setEnabled(false);
+ if (mEdit)
+ mEdit->setEnabled(false);
+ }
+ else if (id == radioId)
+ {
+ if (!pickFileIfNone())
+ return; // revert to previously selected type
+ mButton->setEnabled(true);
+ if (mEdit)
+ mEdit->setEnabled(true);
+ }
+ mLastId = id;
+}
+
+/******************************************************************************
+* Prompt for a file name if there is none currently entered.
+*/
+bool PickFileRadio::pickFileIfNone()
+{
+ if (mEdit)
+ mFile = mEdit->text();
+ if (!mFile.isEmpty())
+ return true;
+ slotPickFile();
+ return !mFile.isEmpty();
+}
+
+/******************************************************************************
+* Called when the file picker button is clicked.
+*/
+void PickFileRadio::slotPickFile()
+{
+ mFile = pickFile();
+ if (mEdit)
+ mEdit->setText(mFile);
+ if (mFile.isEmpty())
+ {
+ // No file is selected, so revert to the previous radio button selection.
+ // But wait a moment before setting the radio button, or it won't work.
+ mRevertId = true; // prevent picker dialogue popping up twice
+ QTimer::singleShot(0, this, SLOT(setLastId()));
+ }
+}
+
+/******************************************************************************
+* Select the previously selected radio button in the group.
+*/
+void PickFileRadio::setLastId()
+{
+ if (mLastId == -1)
+ setOn(false); // we don't know the previous selection, so just turn this button off
+ else
+ mGroup->setButton(mLastId);
+ mRevertId = false;
+}
diff --git a/kalarm/pickfileradio.h b/kalarm/pickfileradio.h
new file mode 100644
index 00000000..5486a8a6
--- /dev/null
+++ b/kalarm/pickfileradio.h
@@ -0,0 +1,120 @@
+/*
+ * pickfileradio.h - radio button with an associated file picker
+ * Program: kalarm
+ * Copyright (C) 2005 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.
+ */
+
+#ifndef PICKFILERADIO_H
+#define PICKFILERADIO_H
+
+/** @file pickfileradio.h - radio button with an associated file picker */
+
+#include "radiobutton.h"
+
+class QPushButton;
+class LineEdit;
+
+/**
+ * @short Radio button with associated file picker controls.
+ *
+ * The PickFileRadio class is a radio button with an associated button to choose
+ * a file, and an optional file name edit box. Its purpose is to ensure that while
+ * the radio button is selected, the chosen file name is never blank.
+ *
+ * To achieve this, whenever the button is newly selected and the
+ * file name is currently blank, the file picker dialogue is displayed to choose a
+ * file. If the dialogue exits without a file being chosen, the radio button selection
+ * is reverted to the previously selected button in the parent button group.
+ *
+ * The class handles the activation of the file picker dialogue (via a virtual method
+ * which must be supplied by deriving a class from this one). It also handles all
+ * enabling and disabling of the browse button and edit box when the enable state of
+ * the radio button is changed, and when the radio button selection changes.
+ *
+ * @author David Jarvie <software@astrojar.org.uk>
+ */
+class PickFileRadio : public RadioButton
+{
+ Q_OBJECT
+ public:
+ /** Constructor.
+ * @param button Push button to invoke the file picker dialogue.
+ * @param edit File name edit widget, or null if there is none.
+ * @param text Radio button's text.
+ * @param parent Button group which is to be the parent object for the radio button.
+ * @param name The name of this widget.
+ */
+ PickFileRadio(QPushButton* button, LineEdit* edit, const QString& text, QButtonGroup* parent, const char* name = 0);
+ /** Constructor.
+ * The init() method must be called before the widget can be used.
+ * @param text Radio button's text.
+ * @param parent Button group which is to be the parent object for the radio button.
+ * @param name The name of this widget.
+ */
+ PickFileRadio(const QString& text, QButtonGroup* parent, const char* name = 0);
+ /** Initialises the widget.
+ * @param button Push button to invoke the file picker dialogue.
+ * @param edit File name edit widget, or null if there is none.
+ */
+ void init(QPushButton* button, LineEdit* edit = 0);
+ /** Sets whether the radio button and associated widgets are read-only for the user.
+ * If read-only, their states cannot be changed by the user.
+ * @param readOnly True to set the widgets read-only, false to set them read-write.
+ */
+ virtual void setReadOnly(bool readOnly);
+ /** Chooses a file, for example by displaying a file selection dialogue.
+ * This method is called when the push button is clicked - the client
+ * should not activate a file selection dialogue directly.
+ * @return Selected file name, or QString::null if no selection made.
+ */
+ virtual QString pickFile() = 0;
+ /** Notifies the widget of the currently selected file name.
+ * This should only be used when no file name edit box is used.
+ * It should be called to initialise the widget's data, and also any time the file
+ * name is changed without using the push button.
+ */
+ void setFile(const QString& file);
+ /** Returns the currently selected file name. */
+ QString file() const;
+ /** Returns the associated file name edit widget, or null if none. */
+ LineEdit* fileEdit() const { return mEdit; }
+ /** Returns the associated file browse push button. */
+ QPushButton* pushButton() const { return mButton; }
+
+ public slots:
+ /** Enables or disables the radio button, and adjusts the enabled state of the
+ * associated browse button and file name edit box.
+ */
+ virtual void setEnabled(bool);
+
+ private slots:
+ void slotSelectionChanged(int id);
+ void slotPickFile();
+ void setLastId();
+
+ private:
+ bool pickFileIfNone();
+
+ QButtonGroup* mGroup; // button group which radio button is in
+ LineEdit* mEdit; // file name edit box, or null if none
+ QPushButton* mButton; // push button to pick a file
+ QString mFile; // saved file name (if mEdit is null)
+ int mLastId; // previous radio button selected
+ bool mRevertId; // true to revert to the previous radio button selection
+};
+
+#endif // PICKFILERADIO_H
diff --git a/kalarm/pixmaps/Makefile.am b/kalarm/pixmaps/Makefile.am
new file mode 100644
index 00000000..e901fb44
--- /dev/null
+++ b/kalarm/pixmaps/Makefile.am
@@ -0,0 +1,3 @@
+kalarmicondir = $(kde_datadir)/kalarm/icons
+kalarmicon_ICON = AUTO
+KDE_ICON = kalarm
diff --git a/kalarm/pixmaps/cr16-action-file.png b/kalarm/pixmaps/cr16-action-file.png
new file mode 100644
index 00000000..037c2da9
--- /dev/null
+++ b/kalarm/pixmaps/cr16-action-file.png
Binary files differ
diff --git a/kalarm/pixmaps/cr16-action-kalarm.png b/kalarm/pixmaps/cr16-action-kalarm.png
new file mode 100644
index 00000000..ae4234de
--- /dev/null
+++ b/kalarm/pixmaps/cr16-action-kalarm.png
Binary files differ
diff --git a/kalarm/pixmaps/cr16-action-message.png b/kalarm/pixmaps/cr16-action-message.png
new file mode 100644
index 00000000..d0f509d6
--- /dev/null
+++ b/kalarm/pixmaps/cr16-action-message.png
Binary files differ
diff --git a/kalarm/pixmaps/cr16-action-new_from_template.png b/kalarm/pixmaps/cr16-action-new_from_template.png
new file mode 100644
index 00000000..03b26d88
--- /dev/null
+++ b/kalarm/pixmaps/cr16-action-new_from_template.png
Binary files differ
diff --git a/kalarm/pixmaps/cr16-action-playsound.png b/kalarm/pixmaps/cr16-action-playsound.png
new file mode 100644
index 00000000..31e51f04
--- /dev/null
+++ b/kalarm/pixmaps/cr16-action-playsound.png
Binary files differ
diff --git a/kalarm/pixmaps/cr22-action-kalarm.png b/kalarm/pixmaps/cr22-action-kalarm.png
new file mode 100644
index 00000000..e2e17452
--- /dev/null
+++ b/kalarm/pixmaps/cr22-action-kalarm.png
Binary files differ
diff --git a/kalarm/pixmaps/cr22-action-kalarm_disabled.png b/kalarm/pixmaps/cr22-action-kalarm_disabled.png
new file mode 100644
index 00000000..5a98c4d4
--- /dev/null
+++ b/kalarm/pixmaps/cr22-action-kalarm_disabled.png
Binary files differ
diff --git a/kalarm/pixmaps/cr22-action-new_from_template.png b/kalarm/pixmaps/cr22-action-new_from_template.png
new file mode 100644
index 00000000..1eb51837
--- /dev/null
+++ b/kalarm/pixmaps/cr22-action-new_from_template.png
Binary files differ
diff --git a/kalarm/prefdlg.cpp b/kalarm/prefdlg.cpp
new file mode 100644
index 00000000..2b07c499
--- /dev/null
+++ b/kalarm/prefdlg.cpp
@@ -0,0 +1,1363 @@
+/*
+ * prefdlg.cpp - program preferences dialog
+ * Program: kalarm
+ * Copyright © 2001-2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <qobjectlist.h>
+#include <qlayout.h>
+#include <qbuttongroup.h>
+#include <qvbox.h>
+#include <qlineedit.h>
+#include <qcheckbox.h>
+#include <qradiobutton.h>
+#include <qpushbutton.h>
+#include <qcombobox.h>
+#include <qwhatsthis.h>
+#include <qtooltip.h>
+#include <qstyle.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kshell.h>
+#include <kmessagebox.h>
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kcolorcombo.h>
+#include <kstdguiitem.h>
+#ifdef Q_WS_X11
+#include <kwin.h>
+#endif
+#include <kdebug.h>
+
+#include <kalarmd/kalarmd.h>
+
+#include "alarmcalendar.h"
+#include "alarmtimewidget.h"
+#include "daemon.h"
+#include "editdlg.h"
+#include "fontcolour.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "kamail.h"
+#include "label.h"
+#include "latecancel.h"
+#include "mainwindow.h"
+#include "preferences.h"
+#include "radiobutton.h"
+#include "recurrenceedit.h"
+#ifndef WITHOUT_ARTS
+#include "sounddlg.h"
+#endif
+#include "soundpicker.h"
+#include "specialactions.h"
+#include "timeedit.h"
+#include "timespinbox.h"
+#include "traywindow.h"
+#include "prefdlg.moc"
+
+// Command strings for executing commands in different types of terminal windows.
+// %t = window title parameter
+// %c = command to execute in terminal
+// %w = command to execute in terminal, with 'sleep 86400' appended
+// %C = temporary command file to execute in terminal
+// %W = temporary command file to execute in terminal, with 'sleep 86400' appended
+static QString xtermCommands[] = {
+ QString::fromLatin1("xterm -sb -hold -title %t -e %c"),
+ QString::fromLatin1("konsole --noclose -T %t -e ${SHELL:-sh} -c %c"),
+ QString::fromLatin1("gnome-terminal -t %t -e %W"),
+ QString::fromLatin1("eterm --pause -T %t -e %C"), // some systems use eterm...
+ QString::fromLatin1("Eterm --pause -T %t -e %C"), // while some use Eterm
+ QString::fromLatin1("rxvt -title %t -e ${SHELL:-sh} -c %w"),
+ QString::null // end of list indicator - don't change!
+};
+
+
+/*=============================================================================
+= Class KAlarmPrefDlg
+=============================================================================*/
+
+KAlarmPrefDlg* KAlarmPrefDlg::mInstance = 0;
+
+void KAlarmPrefDlg::display()
+{
+ if (!mInstance)
+ {
+ mInstance = new KAlarmPrefDlg;
+ mInstance->show();
+ }
+ else
+ {
+#ifdef Q_WS_X11
+ KWin::WindowInfo info = KWin::windowInfo(mInstance->winId(), static_cast<unsigned long>(NET::WMGeometry | NET::WMDesktop));
+ KWin::setCurrentDesktop(info.desktop());
+#endif
+ mInstance->showNormal(); // un-minimise it if necessary
+ mInstance->raise();
+ mInstance->setActiveWindow();
+ }
+}
+
+KAlarmPrefDlg::KAlarmPrefDlg()
+ : KDialogBase(IconList, i18n("Preferences"), Help | Default | Ok | Apply | Cancel, Ok, 0, "PrefDlg", false, true)
+{
+ setWFlags(Qt::WDestructiveClose);
+ setIconListAllVisible(true);
+
+ QVBox* frame = addVBoxPage(i18n("General"), i18n("General"), DesktopIcon("misc"));
+ mMiscPage = new MiscPrefTab(frame);
+
+ frame = addVBoxPage(i18n("Email"), i18n("Email Alarm Settings"), DesktopIcon("mail_generic"));
+ mEmailPage = new EmailPrefTab(frame);
+
+ frame = addVBoxPage(i18n("View"), i18n("View Settings"), DesktopIcon("view_choose"));
+ mViewPage = new ViewPrefTab(frame);
+
+ frame = addVBoxPage(i18n("Font & Color"), i18n("Default Font and Color"), DesktopIcon("colorize"));
+ mFontColourPage = new FontColourPrefTab(frame);
+
+ frame = addVBoxPage(i18n("Edit"), i18n("Default Alarm Edit Settings"), DesktopIcon("edit"));
+ mEditPage = new EditPrefTab(frame);
+
+ restore();
+ adjustSize();
+}
+
+KAlarmPrefDlg::~KAlarmPrefDlg()
+{
+ mInstance = 0;
+}
+
+// Restore all defaults in the options...
+void KAlarmPrefDlg::slotDefault()
+{
+ kdDebug(5950) << "KAlarmPrefDlg::slotDefault()" << endl;
+ mFontColourPage->setDefaults();
+ mEmailPage->setDefaults();
+ mViewPage->setDefaults();
+ mEditPage->setDefaults();
+ mMiscPage->setDefaults();
+}
+
+void KAlarmPrefDlg::slotHelp()
+{
+ kapp->invokeHelp("preferences");
+}
+
+// Apply the preferences that are currently selected
+void KAlarmPrefDlg::slotApply()
+{
+ kdDebug(5950) << "KAlarmPrefDlg::slotApply()" << endl;
+ QString errmsg = mEmailPage->validate();
+ if (!errmsg.isEmpty())
+ {
+ showPage(pageIndex(mEmailPage->parentWidget()));
+ if (KMessageBox::warningYesNo(this, errmsg) != KMessageBox::Yes)
+ {
+ mValid = false;
+ return;
+ }
+ }
+ errmsg = mEditPage->validate();
+ if (!errmsg.isEmpty())
+ {
+ showPage(pageIndex(mEditPage->parentWidget()));
+ KMessageBox::sorry(this, errmsg);
+ mValid = false;
+ return;
+ }
+ mValid = true;
+ mFontColourPage->apply(false);
+ mEmailPage->apply(false);
+ mViewPage->apply(false);
+ mEditPage->apply(false);
+ mMiscPage->apply(false);
+ Preferences::syncToDisc();
+}
+
+// Apply the preferences that are currently selected
+void KAlarmPrefDlg::slotOk()
+{
+ kdDebug(5950) << "KAlarmPrefDlg::slotOk()" << endl;
+ mValid = true;
+ slotApply();
+ if (mValid)
+ KDialogBase::slotOk();
+}
+
+// Discard the current preferences and close the dialogue
+void KAlarmPrefDlg::slotCancel()
+{
+ kdDebug(5950) << "KAlarmPrefDlg::slotCancel()" << endl;
+ restore();
+ KDialogBase::slotCancel();
+}
+
+// Discard the current preferences and use the present ones
+void KAlarmPrefDlg::restore()
+{
+ kdDebug(5950) << "KAlarmPrefDlg::restore()" << endl;
+ mFontColourPage->restore();
+ mEmailPage->restore();
+ mViewPage->restore();
+ mEditPage->restore();
+ mMiscPage->restore();
+}
+
+
+/*=============================================================================
+= Class PrefsTabBase
+=============================================================================*/
+int PrefsTabBase::mIndentWidth = 0;
+
+PrefsTabBase::PrefsTabBase(QVBox* frame)
+ : QWidget(frame),
+ mPage(frame)
+{
+ if (!mIndentWidth)
+ mIndentWidth = style().subRect(QStyle::SR_RadioButtonIndicator, this).width();
+}
+
+void PrefsTabBase::apply(bool syncToDisc)
+{
+ Preferences::save(syncToDisc);
+}
+
+
+
+/*=============================================================================
+= Class MiscPrefTab
+=============================================================================*/
+
+MiscPrefTab::MiscPrefTab(QVBox* frame)
+ : PrefsTabBase(frame)
+{
+ // Get alignment to use in QGridLayout (AlignAuto doesn't work correctly there)
+ int alignment = QApplication::reverseLayout() ? Qt::AlignRight : Qt::AlignLeft;
+
+ QGroupBox* group = new QButtonGroup(i18n("Run Mode"), mPage, "modeGroup");
+ QGridLayout* grid = new QGridLayout(group, 6, 2, KDialog::marginHint(), KDialog::spacingHint());
+ grid->setColStretch(2, 1);
+ grid->addColSpacing(0, indentWidth());
+ grid->addColSpacing(1, indentWidth());
+ grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);
+
+ // Run-on-demand radio button
+ mRunOnDemand = new QRadioButton(i18n("&Run only on demand"), group, "runDemand");
+ mRunOnDemand->setFixedSize(mRunOnDemand->sizeHint());
+ connect(mRunOnDemand, SIGNAL(toggled(bool)), SLOT(slotRunModeToggled(bool)));
+ QWhatsThis::add(mRunOnDemand,
+ i18n("Check to run KAlarm only when required.\n\n"
+ "Notes:\n"
+ "1. Alarms are displayed even when KAlarm is not running, since alarm monitoring is done by the alarm daemon.\n"
+ "2. With this option selected, the system tray icon can be displayed or hidden independently of KAlarm."));
+ grid->addMultiCellWidget(mRunOnDemand, 1, 1, 0, 2, alignment);
+
+ // Run-in-system-tray radio button
+ mRunInSystemTray = new QRadioButton(i18n("Run continuously in system &tray"), group, "runTray");
+ mRunInSystemTray->setFixedSize(mRunInSystemTray->sizeHint());
+ connect(mRunInSystemTray, SIGNAL(toggled(bool)), SLOT(slotRunModeToggled(bool)));
+ QWhatsThis::add(mRunInSystemTray,
+ i18n("Check to run KAlarm continuously in the KDE system tray.\n\n"
+ "Notes:\n"
+ "1. With this option selected, closing the system tray icon will quit KAlarm.\n"
+ "2. You do not need to select this option in order for alarms to be displayed, since alarm monitoring is done by the alarm daemon."
+ " Running in the system tray simply provides easy access and a status indication."));
+ grid->addMultiCellWidget(mRunInSystemTray, 2, 2, 0, 2, alignment);
+
+ // Run continuously options
+ mDisableAlarmsIfStopped = new QCheckBox(i18n("Disa&ble alarms while not running"), group, "disableAl");
+ mDisableAlarmsIfStopped->setFixedSize(mDisableAlarmsIfStopped->sizeHint());
+ connect(mDisableAlarmsIfStopped, SIGNAL(toggled(bool)), SLOT(slotDisableIfStoppedToggled(bool)));
+ QWhatsThis::add(mDisableAlarmsIfStopped,
+ i18n("Check to disable alarms whenever KAlarm is not running. Alarms will only appear while the system tray icon is visible."));
+ grid->addMultiCellWidget(mDisableAlarmsIfStopped, 3, 3, 1, 2, alignment);
+
+ mQuitWarn = new QCheckBox(i18n("Warn before &quitting"), group, "disableAl");
+ mQuitWarn->setFixedSize(mQuitWarn->sizeHint());
+ QWhatsThis::add(mQuitWarn,
+ i18n("Check to display a warning prompt before quitting KAlarm."));
+ grid->addWidget(mQuitWarn, 4, 2, alignment);
+
+ mAutostartTrayIcon = new QCheckBox(i18n("Autostart at &login"), group, "autoTray");
+#ifdef AUTOSTART_BY_KALARMD
+ connect(mAutostartTrayIcon, SIGNAL(toggled(bool)), SLOT(slotAutostartToggled(bool)));
+#endif
+ grid->addMultiCellWidget(mAutostartTrayIcon, 5, 5, 0, 2, alignment);
+
+ // Autostart alarm daemon
+ mAutostartDaemon = new QCheckBox(i18n("Start alarm monitoring at lo&gin"), group, "startDaemon");
+ mAutostartDaemon->setFixedSize(mAutostartDaemon->sizeHint());
+ connect(mAutostartDaemon, SIGNAL(clicked()), SLOT(slotAutostartDaemonClicked()));
+ QWhatsThis::add(mAutostartDaemon,
+ i18n("Automatically start alarm monitoring whenever you start KDE, by running the alarm daemon (%1).\n\n"
+ "This option should always be checked unless you intend to discontinue use of KAlarm.")
+ .arg(QString::fromLatin1(DAEMON_APP_NAME)));
+ grid->addMultiCellWidget(mAutostartDaemon, 6, 6, 0, 2, alignment);
+
+ group->setFixedHeight(group->sizeHint().height());
+
+ // Start-of-day time
+ QHBox* itemBox = new QHBox(mPage);
+ QHBox* box = new QHBox(itemBox); // this is to control the QWhatsThis text display area
+ box->setSpacing(KDialog::spacingHint());
+ QLabel* label = new QLabel(i18n("&Start of day for date-only alarms:"), box);
+ mStartOfDay = new TimeEdit(box);
+ mStartOfDay->setFixedSize(mStartOfDay->sizeHint());
+ label->setBuddy(mStartOfDay);
+ static const QString startOfDayText = i18n("The earliest time of day at which a date-only alarm (i.e. "
+ "an alarm with \"any time\" specified) will be triggered.");
+ QWhatsThis::add(box, QString("%1\n\n%2").arg(startOfDayText).arg(TimeSpinBox::shiftWhatsThis()));
+ itemBox->setStretchFactor(new QWidget(itemBox), 1); // left adjust the controls
+ itemBox->setFixedHeight(box->sizeHint().height());
+
+ // Confirm alarm deletion?
+ itemBox = new QHBox(mPage); // this is to allow left adjustment
+ mConfirmAlarmDeletion = new QCheckBox(i18n("Con&firm alarm deletions"), itemBox, "confirmDeletion");
+ mConfirmAlarmDeletion->setMinimumSize(mConfirmAlarmDeletion->sizeHint());
+ QWhatsThis::add(mConfirmAlarmDeletion,
+ i18n("Check to be prompted for confirmation each time you delete an alarm."));
+ itemBox->setStretchFactor(new QWidget(itemBox), 1); // left adjust the controls
+ itemBox->setFixedHeight(itemBox->sizeHint().height());
+
+ // Expired alarms
+ group = new QGroupBox(i18n("Expired Alarms"), mPage);
+ grid = new QGridLayout(group, 2, 2, KDialog::marginHint(), KDialog::spacingHint());
+ grid->setColStretch(1, 1);
+ grid->addColSpacing(0, indentWidth());
+ grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);
+ mKeepExpired = new QCheckBox(i18n("Keep alarms after e&xpiry"), group, "keepExpired");
+ mKeepExpired->setFixedSize(mKeepExpired->sizeHint());
+ connect(mKeepExpired, SIGNAL(toggled(bool)), SLOT(slotExpiredToggled(bool)));
+ QWhatsThis::add(mKeepExpired,
+ i18n("Check to store alarms after expiry or deletion (except deleted alarms which were never triggered)."));
+ grid->addMultiCellWidget(mKeepExpired, 1, 1, 0, 1, alignment);
+
+ box = new QHBox(group);
+ box->setSpacing(KDialog::spacingHint());
+ mPurgeExpired = new QCheckBox(i18n("Discard ex&pired alarms after:"), box, "purgeExpired");
+ mPurgeExpired->setMinimumSize(mPurgeExpired->sizeHint());
+ connect(mPurgeExpired, SIGNAL(toggled(bool)), SLOT(slotExpiredToggled(bool)));
+ mPurgeAfter = new SpinBox(box);
+ mPurgeAfter->setMinValue(1);
+ mPurgeAfter->setLineShiftStep(10);
+ mPurgeAfter->setMinimumSize(mPurgeAfter->sizeHint());
+ mPurgeAfterLabel = new QLabel(i18n("da&ys"), box);
+ mPurgeAfterLabel->setMinimumSize(mPurgeAfterLabel->sizeHint());
+ mPurgeAfterLabel->setBuddy(mPurgeAfter);
+ QWhatsThis::add(box,
+ i18n("Uncheck to store expired alarms indefinitely. Check to enter how long expired alarms should be stored."));
+ grid->addWidget(box, 2, 1, alignment);
+
+ mClearExpired = new QPushButton(i18n("Clear Expired Alar&ms"), group);
+ mClearExpired->setFixedSize(mClearExpired->sizeHint());
+ connect(mClearExpired, SIGNAL(clicked()), SLOT(slotClearExpired()));
+ QWhatsThis::add(mClearExpired,
+ i18n("Delete all existing expired alarms."));
+ grid->addWidget(mClearExpired, 3, 1, alignment);
+ group->setFixedHeight(group->sizeHint().height());
+
+ // Terminal window to use for command alarms
+ group = new QGroupBox(i18n("Terminal for Command Alarms"), mPage);
+ QWhatsThis::add(group,
+ i18n("Choose which application to use when a command alarm is executed in a terminal window"));
+ grid = new QGridLayout(group, 1, 3, KDialog::marginHint(), KDialog::spacingHint());
+ grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);
+ int row = 0;
+
+ mXtermType = new QButtonGroup(group);
+ mXtermType->hide();
+ QString whatsThis = i18n("The parameter is a command line, e.g. 'xterm -e'", "Check to execute command alarms in a terminal window by '%1'");
+ int index = 0;
+ mXtermFirst = -1;
+ for (mXtermCount = 0; !xtermCommands[mXtermCount].isNull(); ++mXtermCount)
+ {
+ QString cmd = xtermCommands[mXtermCount];
+ QStringList args = KShell::splitArgs(cmd);
+ if (args.isEmpty() || KStandardDirs::findExe(args[0]).isEmpty())
+ continue;
+ QRadioButton* radio = new QRadioButton(args[0], group);
+ radio->setMinimumSize(radio->sizeHint());
+ mXtermType->insert(radio, mXtermCount);
+ if (mXtermFirst < 0)
+ mXtermFirst = mXtermCount; // note the id of the first button
+ cmd.replace("%t", kapp->aboutData()->programName());
+ cmd.replace("%c", "<command>");
+ cmd.replace("%w", "<command; sleep>");
+ cmd.replace("%C", "[command]");
+ cmd.replace("%W", "[command; sleep]");
+ QWhatsThis::add(radio, whatsThis.arg(cmd));
+ grid->addWidget(radio, (row = index/3 + 1), index % 3, Qt::AlignAuto);
+ ++index;
+ }
+
+ box = new QHBox(group);
+ grid->addMultiCellWidget(box, row + 1, row + 1, 0, 2, Qt::AlignAuto);
+ QRadioButton* radio = new QRadioButton(i18n("Other:"), box);
+ radio->setFixedSize(radio->sizeHint());
+ connect(radio, SIGNAL(toggled(bool)), SLOT(slotOtherTerminalToggled(bool)));
+ mXtermType->insert(radio, mXtermCount);
+ if (mXtermFirst < 0)
+ mXtermFirst = mXtermCount; // note the id of the first button
+ mXtermCommand = new QLineEdit(box);
+ QWhatsThis::add(box,
+ i18n("Enter the full command line needed to execute a command in your chosen terminal window. "
+ "By default the alarm's command string will be appended to what you enter here. "
+ "See the KAlarm Handbook for details of special codes to tailor the command line."));
+
+ mPage->setStretchFactor(new QWidget(mPage), 1); // top adjust the widgets
+}
+
+void MiscPrefTab::restore()
+{
+ mAutostartDaemon->setChecked(Daemon::autoStart());
+ bool systray = Preferences::mRunInSystemTray;
+ mRunInSystemTray->setChecked(systray);
+ mRunOnDemand->setChecked(!systray);
+ mDisableAlarmsIfStopped->setChecked(Preferences::mDisableAlarmsIfStopped);
+ mQuitWarn->setChecked(Preferences::quitWarn());
+ mAutostartTrayIcon->setChecked(Preferences::mAutostartTrayIcon);
+ mConfirmAlarmDeletion->setChecked(Preferences::confirmAlarmDeletion());
+ mStartOfDay->setValue(Preferences::mStartOfDay);
+ setExpiredControls(Preferences::mExpiredKeepDays);
+ QString xtermCmd = Preferences::cmdXTermCommand();
+ int id = mXtermFirst;
+ if (!xtermCmd.isEmpty())
+ {
+ for ( ; id < mXtermCount; ++id)
+ {
+ if (mXtermType->find(id) && xtermCmd == xtermCommands[id])
+ break;
+ }
+ }
+ mXtermType->setButton(id);
+ mXtermCommand->setEnabled(id == mXtermCount);
+ mXtermCommand->setText(id == mXtermCount ? xtermCmd : "");
+ slotDisableIfStoppedToggled(true);
+}
+
+void MiscPrefTab::apply(bool syncToDisc)
+{
+ // First validate anything entered in Other X-terminal command
+ int xtermID = mXtermType->selectedId();
+ if (xtermID >= mXtermCount)
+ {
+ QString cmd = mXtermCommand->text();
+ if (cmd.isEmpty())
+ xtermID = -1; // 'Other' is only acceptable if it's non-blank
+ else
+ {
+ QStringList args = KShell::splitArgs(cmd);
+ cmd = args.isEmpty() ? QString::null : args[0];
+ if (KStandardDirs::findExe(cmd).isEmpty())
+ {
+ mXtermCommand->setFocus();
+ if (KMessageBox::warningContinueCancel(this, i18n("Command to invoke terminal window not found:\n%1").arg(cmd))
+ != KMessageBox::Continue)
+ return;
+ }
+ }
+ }
+ if (xtermID < 0)
+ {
+ xtermID = mXtermFirst;
+ mXtermType->setButton(mXtermFirst);
+ }
+
+ bool systray = mRunInSystemTray->isChecked();
+ Preferences::mRunInSystemTray = systray;
+ Preferences::mDisableAlarmsIfStopped = mDisableAlarmsIfStopped->isChecked();
+ if (mQuitWarn->isEnabled())
+ Preferences::setQuitWarn(mQuitWarn->isChecked());
+ Preferences::mAutostartTrayIcon = mAutostartTrayIcon->isChecked();
+#ifdef AUTOSTART_BY_KALARMD
+ bool newAutostartDaemon = mAutostartDaemon->isChecked() || Preferences::mAutostartTrayIcon;
+#else
+ bool newAutostartDaemon = mAutostartDaemon->isChecked();
+#endif
+ if (newAutostartDaemon != Daemon::autoStart())
+ Daemon::enableAutoStart(newAutostartDaemon);
+ Preferences::setConfirmAlarmDeletion(mConfirmAlarmDeletion->isChecked());
+ int sod = mStartOfDay->value();
+ Preferences::mStartOfDay.setHMS(sod/60, sod%60, 0);
+ Preferences::mExpiredKeepDays = !mKeepExpired->isChecked() ? 0
+ : mPurgeExpired->isChecked() ? mPurgeAfter->value() : -1;
+ Preferences::mCmdXTermCommand = (xtermID < mXtermCount) ? xtermCommands[xtermID] : mXtermCommand->text();
+ PrefsTabBase::apply(syncToDisc);
+}
+
+void MiscPrefTab::setDefaults()
+{
+ mAutostartDaemon->setChecked(true);
+ bool systray = Preferences::default_runInSystemTray;
+ mRunInSystemTray->setChecked(systray);
+ mRunOnDemand->setChecked(!systray);
+ mDisableAlarmsIfStopped->setChecked(Preferences::default_disableAlarmsIfStopped);
+ mQuitWarn->setChecked(Preferences::default_quitWarn);
+ mAutostartTrayIcon->setChecked(Preferences::default_autostartTrayIcon);
+ mConfirmAlarmDeletion->setChecked(Preferences::default_confirmAlarmDeletion);
+ mStartOfDay->setValue(Preferences::default_startOfDay);
+ setExpiredControls(Preferences::default_expiredKeepDays);
+ mXtermType->setButton(mXtermFirst);
+ mXtermCommand->setEnabled(false);
+ slotDisableIfStoppedToggled(true);
+}
+
+void MiscPrefTab::slotAutostartDaemonClicked()
+{
+ if (!mAutostartDaemon->isChecked()
+ && KMessageBox::warningYesNo(this,
+ i18n("You should not uncheck this option unless you intend to discontinue use of KAlarm"),
+ QString::null, KStdGuiItem::cont(), KStdGuiItem::cancel()
+ ) != KMessageBox::Yes)
+ mAutostartDaemon->setChecked(true);
+}
+
+void MiscPrefTab::slotRunModeToggled(bool)
+{
+ bool systray = mRunInSystemTray->isOn();
+ mAutostartTrayIcon->setText(systray ? i18n("Autostart at &login") : i18n("Autostart system tray &icon at login"));
+ QWhatsThis::add(mAutostartTrayIcon, (systray ? i18n("Check to run KAlarm whenever you start KDE.")
+ : i18n("Check to display the system tray icon whenever you start KDE.")));
+ mDisableAlarmsIfStopped->setEnabled(systray);
+ slotDisableIfStoppedToggled(true);
+}
+
+/******************************************************************************
+* If autostart at login is selected, the daemon must be autostarted so that it
+* can autostart KAlarm, in which case disable the daemon autostart option.
+*/
+void MiscPrefTab::slotAutostartToggled(bool)
+{
+#ifdef AUTOSTART_BY_KALARMD
+ mAutostartDaemon->setEnabled(!mAutostartTrayIcon->isChecked());
+#endif
+}
+
+void MiscPrefTab::slotDisableIfStoppedToggled(bool)
+{
+ bool enable = mDisableAlarmsIfStopped->isEnabled() && mDisableAlarmsIfStopped->isChecked();
+ mQuitWarn->setEnabled(enable);
+}
+
+void MiscPrefTab::setExpiredControls(int purgeDays)
+{
+ mKeepExpired->setChecked(purgeDays);
+ mPurgeExpired->setChecked(purgeDays > 0);
+ mPurgeAfter->setValue(purgeDays > 0 ? purgeDays : 0);
+ slotExpiredToggled(true);
+}
+
+void MiscPrefTab::slotExpiredToggled(bool)
+{
+ bool keep = mKeepExpired->isChecked();
+ bool after = keep && mPurgeExpired->isChecked();
+ mPurgeExpired->setEnabled(keep);
+ mPurgeAfter->setEnabled(after);
+ mPurgeAfterLabel->setEnabled(keep);
+ mClearExpired->setEnabled(keep);
+}
+
+void MiscPrefTab::slotClearExpired()
+{
+ AlarmCalendar* cal = AlarmCalendar::expiredCalendarOpen();
+ if (cal)
+ cal->purgeAll();
+}
+
+void MiscPrefTab::slotOtherTerminalToggled(bool on)
+{
+ mXtermCommand->setEnabled(on);
+}
+
+
+/*=============================================================================
+= Class EmailPrefTab
+=============================================================================*/
+
+EmailPrefTab::EmailPrefTab(QVBox* frame)
+ : PrefsTabBase(frame),
+ mAddressChanged(false),
+ mBccAddressChanged(false)
+{
+ QHBox* box = new QHBox(mPage);
+ box->setSpacing(2*KDialog::spacingHint());
+ QLabel* label = new QLabel(i18n("Email client:"), box);
+ mEmailClient = new ButtonGroup(box);
+ mEmailClient->hide();
+ RadioButton* radio = new RadioButton(i18n("&KMail"), box, "kmail");
+ radio->setMinimumSize(radio->sizeHint());
+ mEmailClient->insert(radio, Preferences::KMAIL);
+ radio = new RadioButton(i18n("&Sendmail"), box, "sendmail");
+ radio->setMinimumSize(radio->sizeHint());
+ mEmailClient->insert(radio, Preferences::SENDMAIL);
+ connect(mEmailClient, SIGNAL(buttonSet(int)), SLOT(slotEmailClientChanged(int)));
+ box->setFixedHeight(box->sizeHint().height());
+ QWhatsThis::add(box,
+ i18n("Choose how to send email when an email alarm is triggered.\n"
+ "KMail: The email is sent automatically via KMail. KMail is started first if necessary.\n"
+ "Sendmail: The email is sent automatically. This option will only work if "
+ "your system is configured to use sendmail or a sendmail compatible mail transport agent."));
+
+ box = new QHBox(mPage); // this is to allow left adjustment
+ mEmailCopyToKMail = new QCheckBox(i18n("Co&py sent emails into KMail's %1 folder").arg(KAMail::i18n_sent_mail()), box);
+ mEmailCopyToKMail->setFixedSize(mEmailCopyToKMail->sizeHint());
+ QWhatsThis::add(mEmailCopyToKMail,
+ i18n("After sending an email, store a copy in KMail's %1 folder").arg(KAMail::i18n_sent_mail()));
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
+ box->setFixedHeight(box->sizeHint().height());
+
+ // Your Email Address group box
+ QGroupBox* group = new QGroupBox(i18n("Your Email Address"), mPage);
+ QGridLayout* grid = new QGridLayout(group, 6, 3, KDialog::marginHint(), KDialog::spacingHint());
+ grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);
+ grid->setColStretch(1, 1);
+
+ // 'From' email address controls ...
+ label = new Label(EditAlarmDlg::i18n_f_EmailFrom(), group);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 1, 0);
+ mFromAddressGroup = new ButtonGroup(group);
+ mFromAddressGroup->hide();
+ connect(mFromAddressGroup, SIGNAL(buttonSet(int)), SLOT(slotFromAddrChanged(int)));
+
+ // Line edit to enter a 'From' email address
+ radio = new RadioButton(group);
+ mFromAddressGroup->insert(radio, Preferences::MAIL_FROM_ADDR);
+ radio->setFixedSize(radio->sizeHint());
+ label->setBuddy(radio);
+ grid->addWidget(radio, 1, 1);
+ mEmailAddress = new QLineEdit(group);
+ connect(mEmailAddress, SIGNAL(textChanged(const QString&)), SLOT(slotAddressChanged()));
+ QString whatsThis = i18n("Your email address, used to identify you as the sender when sending email alarms.");
+ QWhatsThis::add(radio, whatsThis);
+ QWhatsThis::add(mEmailAddress, whatsThis);
+ radio->setFocusWidget(mEmailAddress);
+ grid->addWidget(mEmailAddress, 1, 2);
+
+ // 'From' email address to be taken from Control Centre
+ radio = new RadioButton(i18n("&Use address from Control Center"), group);
+ radio->setFixedSize(radio->sizeHint());
+ mFromAddressGroup->insert(radio, Preferences::MAIL_FROM_CONTROL_CENTRE);
+ QWhatsThis::add(radio,
+ i18n("Check to use the email address set in the KDE Control Center, to identify you as the sender when sending email alarms."));
+ grid->addMultiCellWidget(radio, 2, 2, 1, 2, Qt::AlignAuto);
+
+ // 'From' email address to be picked from KMail's identities when the email alarm is configured
+ radio = new RadioButton(i18n("Use KMail &identities"), group);
+ radio->setFixedSize(radio->sizeHint());
+ mFromAddressGroup->insert(radio, Preferences::MAIL_FROM_KMAIL);
+ QWhatsThis::add(radio,
+ i18n("Check to use KMail's email identities to identify you as the sender when sending email alarms. "
+ "For existing email alarms, KMail's default identity will be used. "
+ "For new email alarms, you will be able to pick which of KMail's identities to use."));
+ grid->addMultiCellWidget(radio, 3, 3, 1, 2, Qt::AlignAuto);
+
+ // 'Bcc' email address controls ...
+ grid->addRowSpacing(4, KDialog::spacingHint());
+ label = new Label(i18n("'Bcc' email address", "&Bcc:"), group);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 5, 0);
+ mBccAddressGroup = new ButtonGroup(group);
+ mBccAddressGroup->hide();
+ connect(mBccAddressGroup, SIGNAL(buttonSet(int)), SLOT(slotBccAddrChanged(int)));
+
+ // Line edit to enter a 'Bcc' email address
+ radio = new RadioButton(group);
+ radio->setFixedSize(radio->sizeHint());
+ mBccAddressGroup->insert(radio, Preferences::MAIL_FROM_ADDR);
+ label->setBuddy(radio);
+ grid->addWidget(radio, 5, 1);
+ mEmailBccAddress = new QLineEdit(group);
+ whatsThis = i18n("Your email address, used for blind copying email alarms to yourself. "
+ "If you want blind copies to be sent to your account on the computer which KAlarm runs on, you can simply enter your user login name.");
+ QWhatsThis::add(radio, whatsThis);
+ QWhatsThis::add(mEmailBccAddress, whatsThis);
+ radio->setFocusWidget(mEmailBccAddress);
+ grid->addWidget(mEmailBccAddress, 5, 2);
+
+ // 'Bcc' email address to be taken from Control Centre
+ radio = new RadioButton(i18n("Us&e address from Control Center"), group);
+ radio->setFixedSize(radio->sizeHint());
+ mBccAddressGroup->insert(radio, Preferences::MAIL_FROM_CONTROL_CENTRE);
+ QWhatsThis::add(radio,
+ i18n("Check to use the email address set in the KDE Control Center, for blind copying email alarms to yourself."));
+ grid->addMultiCellWidget(radio, 6, 6, 1, 2, Qt::AlignAuto);
+
+ group->setFixedHeight(group->sizeHint().height());
+
+ box = new QHBox(mPage); // this is to allow left adjustment
+ mEmailQueuedNotify = new QCheckBox(i18n("&Notify when remote emails are queued"), box);
+ mEmailQueuedNotify->setFixedSize(mEmailQueuedNotify->sizeHint());
+ QWhatsThis::add(mEmailQueuedNotify,
+ i18n("Display a notification message whenever an email alarm has queued an email for sending to a remote system. "
+ "This could be useful if, for example, you have a dial-up connection, so that you can then ensure that the email is actually transmitted."));
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
+ box->setFixedHeight(box->sizeHint().height());
+
+ mPage->setStretchFactor(new QWidget(mPage), 1); // top adjust the widgets
+}
+
+void EmailPrefTab::restore()
+{
+ mEmailClient->setButton(Preferences::mEmailClient);
+ mEmailCopyToKMail->setChecked(Preferences::emailCopyToKMail());
+ setEmailAddress(Preferences::mEmailFrom, Preferences::mEmailAddress);
+ setEmailBccAddress((Preferences::mEmailBccFrom == Preferences::MAIL_FROM_CONTROL_CENTRE), Preferences::mEmailBccAddress);
+ mEmailQueuedNotify->setChecked(Preferences::emailQueuedNotify());
+ mAddressChanged = mBccAddressChanged = false;
+}
+
+void EmailPrefTab::apply(bool syncToDisc)
+{
+ int client = mEmailClient->id(mEmailClient->selected());
+ Preferences::mEmailClient = (client >= 0) ? Preferences::MailClient(client) : Preferences::default_emailClient;
+ Preferences::mEmailCopyToKMail = mEmailCopyToKMail->isChecked();
+ Preferences::setEmailAddress(static_cast<Preferences::MailFrom>(mFromAddressGroup->selectedId()), mEmailAddress->text().stripWhiteSpace());
+ Preferences::setEmailBccAddress((mBccAddressGroup->selectedId() == Preferences::MAIL_FROM_CONTROL_CENTRE), mEmailBccAddress->text().stripWhiteSpace());
+ Preferences::setEmailQueuedNotify(mEmailQueuedNotify->isChecked());
+ PrefsTabBase::apply(syncToDisc);
+}
+
+void EmailPrefTab::setDefaults()
+{
+ mEmailClient->setButton(Preferences::default_emailClient);
+ setEmailAddress(Preferences::default_emailFrom(), Preferences::default_emailAddress);
+ setEmailBccAddress((Preferences::default_emailBccFrom == Preferences::MAIL_FROM_CONTROL_CENTRE), Preferences::default_emailBccAddress);
+ mEmailQueuedNotify->setChecked(Preferences::default_emailQueuedNotify);
+}
+
+void EmailPrefTab::setEmailAddress(Preferences::MailFrom from, const QString& address)
+{
+ mFromAddressGroup->setButton(from);
+ mEmailAddress->setText(from == Preferences::MAIL_FROM_ADDR ? address.stripWhiteSpace() : QString());
+}
+
+void EmailPrefTab::setEmailBccAddress(bool useControlCentre, const QString& address)
+{
+ mBccAddressGroup->setButton(useControlCentre ? Preferences::MAIL_FROM_CONTROL_CENTRE : Preferences::MAIL_FROM_ADDR);
+ mEmailBccAddress->setText(useControlCentre ? QString() : address.stripWhiteSpace());
+}
+
+void EmailPrefTab::slotEmailClientChanged(int id)
+{
+ mEmailCopyToKMail->setEnabled(id == Preferences::SENDMAIL);
+}
+
+void EmailPrefTab::slotFromAddrChanged(int id)
+{
+ mEmailAddress->setEnabled(id == Preferences::MAIL_FROM_ADDR);
+ mAddressChanged = true;
+}
+
+void EmailPrefTab::slotBccAddrChanged(int id)
+{
+ mEmailBccAddress->setEnabled(id == Preferences::MAIL_FROM_ADDR);
+ mBccAddressChanged = true;
+}
+
+QString EmailPrefTab::validate()
+{
+ if (mAddressChanged)
+ {
+ mAddressChanged = false;
+ QString errmsg = validateAddr(mFromAddressGroup, mEmailAddress, KAMail::i18n_NeedFromEmailAddress());
+ if (!errmsg.isEmpty())
+ return errmsg;
+ }
+ if (mBccAddressChanged)
+ {
+ mBccAddressChanged = false;
+ return validateAddr(mBccAddressGroup, mEmailBccAddress, i18n("No valid 'Bcc' email address is specified."));
+ }
+ return QString::null;
+}
+
+QString EmailPrefTab::validateAddr(ButtonGroup* group, QLineEdit* addr, const QString& msg)
+{
+ QString errmsg = i18n("%1\nAre you sure you want to save your changes?").arg(msg);
+ switch (group->selectedId())
+ {
+ case Preferences::MAIL_FROM_CONTROL_CENTRE:
+ if (!KAMail::controlCentreAddress().isEmpty())
+ return QString::null;
+ errmsg = i18n("No email address is currently set in the KDE Control Center. %1").arg(errmsg);
+ break;
+ case Preferences::MAIL_FROM_KMAIL:
+ if (KAMail::identitiesExist())
+ return QString::null;
+ errmsg = i18n("No KMail identities currently exist. %1").arg(errmsg);
+ break;
+ case Preferences::MAIL_FROM_ADDR:
+ if (!addr->text().stripWhiteSpace().isEmpty())
+ return QString::null;
+ break;
+ }
+ return errmsg;
+}
+
+
+/*=============================================================================
+= Class FontColourPrefTab
+=============================================================================*/
+
+FontColourPrefTab::FontColourPrefTab(QVBox* frame)
+ : PrefsTabBase(frame)
+{
+ mFontChooser = new FontColourChooser(mPage, 0, false, QStringList(), i18n("Message Font && Color"), true, false);
+ mPage->setStretchFactor(mFontChooser, 1);
+
+ QFrame* layoutBox = new QFrame(mPage);
+ QHBoxLayout* hlayout = new QHBoxLayout(layoutBox);
+ QVBoxLayout* colourLayout = new QVBoxLayout(hlayout, KDialog::spacingHint());
+ hlayout->addStretch();
+
+ QHBox* box = new QHBox(layoutBox); // to group widgets for QWhatsThis text
+ box->setSpacing(KDialog::spacingHint()/2);
+ colourLayout->addWidget(box);
+ QLabel* label1 = new QLabel(i18n("Di&sabled alarm color:"), box);
+ box->setStretchFactor(new QWidget(box), 1);
+ mDisabledColour = new KColorCombo(box);
+ label1->setBuddy(mDisabledColour);
+ QWhatsThis::add(box,
+ i18n("Choose the text color in the alarm list for disabled alarms."));
+
+ box = new QHBox(layoutBox); // to group widgets for QWhatsThis text
+ box->setSpacing(KDialog::spacingHint()/2);
+ colourLayout->addWidget(box);
+ QLabel* label2 = new QLabel(i18n("E&xpired alarm color:"), box);
+ box->setStretchFactor(new QWidget(box), 1);
+ mExpiredColour = new KColorCombo(box);
+ label2->setBuddy(mExpiredColour);
+ QWhatsThis::add(box,
+ i18n("Choose the text color in the alarm list for expired alarms."));
+}
+
+void FontColourPrefTab::restore()
+{
+ mFontChooser->setBgColour(Preferences::mDefaultBgColour);
+ mFontChooser->setColours(Preferences::mMessageColours);
+ mFontChooser->setFont(Preferences::mMessageFont);
+ mDisabledColour->setColor(Preferences::mDisabledColour);
+ mExpiredColour->setColor(Preferences::mExpiredColour);
+}
+
+void FontColourPrefTab::apply(bool syncToDisc)
+{
+ Preferences::mDefaultBgColour = mFontChooser->bgColour();
+ Preferences::mMessageColours = mFontChooser->colours();
+ Preferences::mMessageFont = mFontChooser->font();
+ Preferences::mDisabledColour = mDisabledColour->color();
+ Preferences::mExpiredColour = mExpiredColour->color();
+ PrefsTabBase::apply(syncToDisc);
+}
+
+void FontColourPrefTab::setDefaults()
+{
+ mFontChooser->setBgColour(Preferences::default_defaultBgColour);
+ mFontChooser->setColours(Preferences::default_messageColours);
+ mFontChooser->setFont(Preferences::default_messageFont());
+ mDisabledColour->setColor(Preferences::default_disabledColour);
+ mExpiredColour->setColor(Preferences::default_expiredColour);
+}
+
+
+/*=============================================================================
+= Class EditPrefTab
+=============================================================================*/
+
+EditPrefTab::EditPrefTab(QVBox* frame)
+ : PrefsTabBase(frame)
+{
+ // Get alignment to use in QLabel::setAlignment(alignment | Qt::WordBreak)
+ // (AlignAuto doesn't work correctly there)
+ int alignment = QApplication::reverseLayout() ? Qt::AlignRight : Qt::AlignLeft;
+
+ int groupTopMargin = fontMetrics().lineSpacing()/2;
+ QString defsetting = i18n("The default setting for \"%1\" in the alarm edit dialog.");
+ QString soundSetting = i18n("Check to select %1 as the default setting for \"%2\" in the alarm edit dialog.");
+
+ // DISPLAY ALARMS
+ QGroupBox* group = new QGroupBox(i18n("Display Alarms"), mPage);
+ QBoxLayout* layout = new QVBoxLayout(group, KDialog::marginHint(), KDialog::spacingHint());
+ layout->addSpacing(groupTopMargin);
+
+ mConfirmAck = new QCheckBox(EditAlarmDlg::i18n_k_ConfirmAck(), group, "defConfAck");
+ mConfirmAck->setMinimumSize(mConfirmAck->sizeHint());
+ QWhatsThis::add(mConfirmAck, defsetting.arg(EditAlarmDlg::i18n_ConfirmAck()));
+ layout->addWidget(mConfirmAck, 0, Qt::AlignAuto);
+
+ mAutoClose = new QCheckBox(LateCancelSelector::i18n_i_AutoCloseWinLC(), group, "defAutoClose");
+ mAutoClose->setMinimumSize(mAutoClose->sizeHint());
+ QWhatsThis::add(mAutoClose, defsetting.arg(LateCancelSelector::i18n_AutoCloseWin()));
+ layout->addWidget(mAutoClose, 0, Qt::AlignAuto);
+
+ QHBox* box = new QHBox(group);
+ box->setSpacing(KDialog::spacingHint());
+ layout->addWidget(box);
+ QLabel* label = new QLabel(i18n("Reminder &units:"), box);
+ label->setFixedSize(label->sizeHint());
+ mReminderUnits = new QComboBox(box, "defWarnUnits");
+ mReminderUnits->insertItem(TimePeriod::i18n_Minutes(), TimePeriod::MINUTES);
+ mReminderUnits->insertItem(TimePeriod::i18n_Hours_Mins(), TimePeriod::HOURS_MINUTES);
+ mReminderUnits->insertItem(TimePeriod::i18n_Days(), TimePeriod::DAYS);
+ mReminderUnits->insertItem(TimePeriod::i18n_Weeks(), TimePeriod::WEEKS);
+ mReminderUnits->setFixedSize(mReminderUnits->sizeHint());
+ label->setBuddy(mReminderUnits);
+ QWhatsThis::add(box,
+ i18n("The default units for the reminder in the alarm edit dialog."));
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the control
+
+ mSpecialActionsButton = new SpecialActionsButton(EditAlarmDlg::i18n_SpecialActions(), box);
+ mSpecialActionsButton->setFixedSize(mSpecialActionsButton->sizeHint());
+
+ // SOUND
+ QButtonGroup* bgroup = new QButtonGroup(SoundPicker::i18n_Sound(), mPage, "soundGroup");
+ layout = new QVBoxLayout(bgroup, KDialog::marginHint(), KDialog::spacingHint());
+ layout->addSpacing(groupTopMargin);
+
+ QBoxLayout* hlayout = new QHBoxLayout(layout, KDialog::spacingHint());
+ mSound = new QComboBox(false, bgroup, "defSound");
+ mSound->insertItem(SoundPicker::i18n_None()); // index 0
+ mSound->insertItem(SoundPicker::i18n_Beep()); // index 1
+ mSound->insertItem(SoundPicker::i18n_File()); // index 2
+ if (theApp()->speechEnabled())
+ mSound->insertItem(SoundPicker::i18n_Speak()); // index 3
+ mSound->setMinimumSize(mSound->sizeHint());
+ QWhatsThis::add(mSound, defsetting.arg(SoundPicker::i18n_Sound()));
+ hlayout->addWidget(mSound);
+ hlayout->addStretch(1);
+
+#ifndef WITHOUT_ARTS
+ mSoundRepeat = new QCheckBox(i18n("Repea&t sound file"), bgroup, "defRepeatSound");
+ mSoundRepeat->setMinimumSize(mSoundRepeat->sizeHint());
+ QWhatsThis::add(mSoundRepeat, i18n("sound file \"Repeat\" checkbox", "The default setting for sound file \"%1\" in the alarm edit dialog.").arg(SoundDlg::i18n_Repeat()));
+ hlayout->addWidget(mSoundRepeat);
+#endif
+
+ box = new QHBox(bgroup); // this is to control the QWhatsThis text display area
+ box->setSpacing(KDialog::spacingHint());
+ mSoundFileLabel = new QLabel(i18n("Sound &file:"), box);
+ mSoundFileLabel->setFixedSize(mSoundFileLabel->sizeHint());
+ mSoundFile = new QLineEdit(box);
+ mSoundFileLabel->setBuddy(mSoundFile);
+ mSoundFileBrowse = new QPushButton(box);
+ mSoundFileBrowse->setPixmap(SmallIcon("fileopen"));
+ mSoundFileBrowse->setFixedSize(mSoundFileBrowse->sizeHint());
+ connect(mSoundFileBrowse, SIGNAL(clicked()), SLOT(slotBrowseSoundFile()));
+ QToolTip::add(mSoundFileBrowse, i18n("Choose a sound file"));
+ QWhatsThis::add(box,
+ i18n("Enter the default sound file to use in the alarm edit dialog."));
+ box->setFixedHeight(box->sizeHint().height());
+ layout->addWidget(box);
+ bgroup->setFixedHeight(bgroup->sizeHint().height());
+
+ // COMMAND ALARMS
+ group = new QGroupBox(i18n("Command Alarms"), mPage);
+ layout = new QVBoxLayout(group, KDialog::marginHint(), KDialog::spacingHint());
+ layout->addSpacing(groupTopMargin);
+ layout = new QHBoxLayout(layout, KDialog::spacingHint());
+
+ mCmdScript = new QCheckBox(EditAlarmDlg::i18n_p_EnterScript(), group, "defCmdScript");
+ mCmdScript->setMinimumSize(mCmdScript->sizeHint());
+ QWhatsThis::add(mCmdScript, defsetting.arg(EditAlarmDlg::i18n_EnterScript()));
+ layout->addWidget(mCmdScript);
+ layout->addStretch();
+
+ mCmdXterm = new QCheckBox(EditAlarmDlg::i18n_w_ExecInTermWindow(), group, "defCmdXterm");
+ mCmdXterm->setMinimumSize(mCmdXterm->sizeHint());
+ QWhatsThis::add(mCmdXterm, defsetting.arg(EditAlarmDlg::i18n_ExecInTermWindow()));
+ layout->addWidget(mCmdXterm);
+
+ // EMAIL ALARMS
+ group = new QGroupBox(i18n("Email Alarms"), mPage);
+ layout = new QVBoxLayout(group, KDialog::marginHint(), KDialog::spacingHint());
+ layout->addSpacing(groupTopMargin);
+
+ // BCC email to sender
+ mEmailBcc = new QCheckBox(EditAlarmDlg::i18n_e_CopyEmailToSelf(), group, "defEmailBcc");
+ mEmailBcc->setMinimumSize(mEmailBcc->sizeHint());
+ QWhatsThis::add(mEmailBcc, defsetting.arg(EditAlarmDlg::i18n_CopyEmailToSelf()));
+ layout->addWidget(mEmailBcc, 0, Qt::AlignAuto);
+
+ // MISCELLANEOUS
+ // Show in KOrganizer
+ mCopyToKOrganizer = new QCheckBox(EditAlarmDlg::i18n_g_ShowInKOrganizer(), mPage, "defShowKorg");
+ mCopyToKOrganizer->setMinimumSize(mCopyToKOrganizer->sizeHint());
+ QWhatsThis::add(mCopyToKOrganizer, defsetting.arg(EditAlarmDlg::i18n_ShowInKOrganizer()));
+
+ // Late cancellation
+ box = new QHBox(mPage);
+ box->setSpacing(KDialog::spacingHint());
+ mLateCancel = new QCheckBox(LateCancelSelector::i18n_n_CancelIfLate(), box, "defCancelLate");
+ mLateCancel->setMinimumSize(mLateCancel->sizeHint());
+ QWhatsThis::add(mLateCancel, defsetting.arg(LateCancelSelector::i18n_CancelIfLate()));
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the control
+
+ // Recurrence
+ QHBox* itemBox = new QHBox(box); // this is to control the QWhatsThis text display area
+ itemBox->setSpacing(KDialog::spacingHint());
+ label = new QLabel(i18n("&Recurrence:"), itemBox);
+ label->setFixedSize(label->sizeHint());
+ mRecurPeriod = new QComboBox(itemBox, "defRecur");
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_NoRecur());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_AtLogin());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_HourlyMinutely());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_Daily());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_Weekly());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_Monthly());
+ mRecurPeriod->insertItem(RecurrenceEdit::i18n_Yearly());
+ mRecurPeriod->setFixedSize(mRecurPeriod->sizeHint());
+ label->setBuddy(mRecurPeriod);
+ QWhatsThis::add(itemBox,
+ i18n("The default setting for the recurrence rule in the alarm edit dialog."));
+ box->setFixedHeight(itemBox->sizeHint().height());
+
+ // How to handle February 29th in yearly recurrences
+ QVBox* vbox = new QVBox(mPage); // this is to control the QWhatsThis text display area
+ vbox->setSpacing(KDialog::spacingHint());
+ label = new QLabel(i18n("In non-leap years, repeat yearly February 29th alarms on:"), vbox);
+ label->setAlignment(alignment | Qt::WordBreak);
+ itemBox = new QHBox(vbox);
+ itemBox->setSpacing(2*KDialog::spacingHint());
+ mFeb29 = new QButtonGroup(itemBox);
+ mFeb29->hide();
+ QWidget* widget = new QWidget(itemBox);
+ widget->setFixedWidth(3*KDialog::spacingHint());
+ QRadioButton* radio = new QRadioButton(i18n("February 2&8th"), itemBox);
+ radio->setMinimumSize(radio->sizeHint());
+ mFeb29->insert(radio, KARecurrence::FEB29_FEB28);
+ radio = new QRadioButton(i18n("March &1st"), itemBox);
+ radio->setMinimumSize(radio->sizeHint());
+ mFeb29->insert(radio, KARecurrence::FEB29_MAR1);
+ radio = new QRadioButton(i18n("Do &not repeat"), itemBox);
+ radio->setMinimumSize(radio->sizeHint());
+ mFeb29->insert(radio, KARecurrence::FEB29_FEB29);
+ itemBox->setFixedHeight(itemBox->sizeHint().height());
+ QWhatsThis::add(vbox,
+ i18n("For yearly recurrences, choose what date, if any, alarms due on February 29th should occur in non-leap years.\n"
+ "Note that the next scheduled occurrence of existing alarms is not re-evaluated when you change this setting."));
+
+ mPage->setStretchFactor(new QWidget(mPage), 1); // top adjust the widgets
+}
+
+void EditPrefTab::restore()
+{
+ mAutoClose->setChecked(Preferences::mDefaultAutoClose);
+ mConfirmAck->setChecked(Preferences::mDefaultConfirmAck);
+ mReminderUnits->setCurrentItem(Preferences::mDefaultReminderUnits);
+ mSpecialActionsButton->setActions(Preferences::mDefaultPreAction, Preferences::mDefaultPostAction);
+ mSound->setCurrentItem(soundIndex(Preferences::mDefaultSoundType));
+ mSoundFile->setText(Preferences::mDefaultSoundFile);
+#ifndef WITHOUT_ARTS
+ mSoundRepeat->setChecked(Preferences::mDefaultSoundRepeat);
+#endif
+ mCmdScript->setChecked(Preferences::mDefaultCmdScript);
+ mCmdXterm->setChecked(Preferences::mDefaultCmdLogType == EditAlarmDlg::EXEC_IN_TERMINAL);
+ mEmailBcc->setChecked(Preferences::mDefaultEmailBcc);
+ mCopyToKOrganizer->setChecked(Preferences::mDefaultCopyToKOrganizer);
+ mLateCancel->setChecked(Preferences::mDefaultLateCancel);
+ mRecurPeriod->setCurrentItem(recurIndex(Preferences::mDefaultRecurPeriod));
+ mFeb29->setButton(Preferences::mDefaultFeb29Type);
+}
+
+void EditPrefTab::apply(bool syncToDisc)
+{
+ Preferences::mDefaultAutoClose = mAutoClose->isChecked();
+ Preferences::mDefaultConfirmAck = mConfirmAck->isChecked();
+ Preferences::mDefaultReminderUnits = static_cast<TimePeriod::Units>(mReminderUnits->currentItem());
+ Preferences::mDefaultPreAction = mSpecialActionsButton->preAction();
+ Preferences::mDefaultPostAction = mSpecialActionsButton->postAction();
+ switch (mSound->currentItem())
+ {
+ case 3: Preferences::mDefaultSoundType = SoundPicker::SPEAK; break;
+ case 2: Preferences::mDefaultSoundType = SoundPicker::PLAY_FILE; break;
+ case 1: Preferences::mDefaultSoundType = SoundPicker::BEEP; break;
+ case 0:
+ default: Preferences::mDefaultSoundType = SoundPicker::NONE; break;
+ }
+ Preferences::mDefaultSoundFile = mSoundFile->text();
+#ifndef WITHOUT_ARTS
+ Preferences::mDefaultSoundRepeat = mSoundRepeat->isChecked();
+#endif
+ Preferences::mDefaultCmdScript = mCmdScript->isChecked();
+ Preferences::mDefaultCmdLogType = (mCmdXterm->isChecked() ? EditAlarmDlg::EXEC_IN_TERMINAL : EditAlarmDlg::DISCARD_OUTPUT);
+ Preferences::mDefaultEmailBcc = mEmailBcc->isChecked();
+ Preferences::mDefaultCopyToKOrganizer = mCopyToKOrganizer->isChecked();
+ Preferences::mDefaultLateCancel = mLateCancel->isChecked() ? 1 : 0;
+ switch (mRecurPeriod->currentItem())
+ {
+ case 6: Preferences::mDefaultRecurPeriod = RecurrenceEdit::ANNUAL; break;
+ case 5: Preferences::mDefaultRecurPeriod = RecurrenceEdit::MONTHLY; break;
+ case 4: Preferences::mDefaultRecurPeriod = RecurrenceEdit::WEEKLY; break;
+ case 3: Preferences::mDefaultRecurPeriod = RecurrenceEdit::DAILY; break;
+ case 2: Preferences::mDefaultRecurPeriod = RecurrenceEdit::SUBDAILY; break;
+ case 1: Preferences::mDefaultRecurPeriod = RecurrenceEdit::AT_LOGIN; break;
+ case 0:
+ default: Preferences::mDefaultRecurPeriod = RecurrenceEdit::NO_RECUR; break;
+ }
+ int feb29 = mFeb29->selectedId();
+ Preferences::mDefaultFeb29Type = (feb29 >= 0) ? static_cast<KARecurrence::Feb29Type>(feb29) : Preferences::default_defaultFeb29Type;
+ PrefsTabBase::apply(syncToDisc);
+}
+
+void EditPrefTab::setDefaults()
+{
+ mAutoClose->setChecked(Preferences::default_defaultAutoClose);
+ mConfirmAck->setChecked(Preferences::default_defaultConfirmAck);
+ mReminderUnits->setCurrentItem(Preferences::default_defaultReminderUnits);
+ mSpecialActionsButton->setActions(Preferences::default_defaultPreAction, Preferences::default_defaultPostAction);
+ mSound->setCurrentItem(soundIndex(Preferences::default_defaultSoundType));
+ mSoundFile->setText(Preferences::default_defaultSoundFile);
+#ifndef WITHOUT_ARTS
+ mSoundRepeat->setChecked(Preferences::default_defaultSoundRepeat);
+#endif
+ mCmdScript->setChecked(Preferences::default_defaultCmdScript);
+ mCmdXterm->setChecked(Preferences::default_defaultCmdLogType == EditAlarmDlg::EXEC_IN_TERMINAL);
+ mEmailBcc->setChecked(Preferences::default_defaultEmailBcc);
+ mCopyToKOrganizer->setChecked(Preferences::default_defaultCopyToKOrganizer);
+ mLateCancel->setChecked(Preferences::default_defaultLateCancel);
+ mRecurPeriod->setCurrentItem(recurIndex(Preferences::default_defaultRecurPeriod));
+ mFeb29->setButton(Preferences::default_defaultFeb29Type);
+}
+
+void EditPrefTab::slotBrowseSoundFile()
+{
+ QString defaultDir;
+ QString url = SoundPicker::browseFile(defaultDir, mSoundFile->text());
+ if (!url.isEmpty())
+ mSoundFile->setText(url);
+}
+
+int EditPrefTab::soundIndex(SoundPicker::Type type)
+{
+ switch (type)
+ {
+ case SoundPicker::SPEAK: return 3;
+ case SoundPicker::PLAY_FILE: return 2;
+ case SoundPicker::BEEP: return 1;
+ case SoundPicker::NONE:
+ default: return 0;
+ }
+}
+
+int EditPrefTab::recurIndex(RecurrenceEdit::RepeatType type)
+{
+ switch (type)
+ {
+ case RecurrenceEdit::ANNUAL: return 6;
+ case RecurrenceEdit::MONTHLY: return 5;
+ case RecurrenceEdit::WEEKLY: return 4;
+ case RecurrenceEdit::DAILY: return 3;
+ case RecurrenceEdit::SUBDAILY: return 2;
+ case RecurrenceEdit::AT_LOGIN: return 1;
+ case RecurrenceEdit::NO_RECUR:
+ default: return 0;
+ }
+}
+
+QString EditPrefTab::validate()
+{
+ if (mSound->currentItem() == SoundPicker::PLAY_FILE && mSoundFile->text().isEmpty())
+ {
+ mSoundFile->setFocus();
+ return i18n("You must enter a sound file when %1 is selected as the default sound type").arg(SoundPicker::i18n_File());;
+ }
+ return QString::null;
+}
+
+
+/*=============================================================================
+= Class ViewPrefTab
+=============================================================================*/
+
+ViewPrefTab::ViewPrefTab(QVBox* frame)
+ : PrefsTabBase(frame)
+{
+ QGroupBox* group = new QGroupBox(i18n("System Tray Tooltip"), mPage);
+ QGridLayout* grid = new QGridLayout(group, 5, 3, KDialog::marginHint(), KDialog::spacingHint());
+ grid->setColStretch(2, 1);
+ grid->addColSpacing(0, indentWidth());
+ grid->addColSpacing(1, indentWidth());
+ grid->addRowSpacing(0, fontMetrics().lineSpacing()/2);
+
+ mTooltipShowAlarms = new QCheckBox(i18n("Show next &24 hours' alarms"), group, "tooltipShow");
+ mTooltipShowAlarms->setMinimumSize(mTooltipShowAlarms->sizeHint());
+ connect(mTooltipShowAlarms, SIGNAL(toggled(bool)), SLOT(slotTooltipAlarmsToggled(bool)));
+ QWhatsThis::add(mTooltipShowAlarms,
+ i18n("Specify whether to include in the system tray tooltip, a summary of alarms due in the next 24 hours"));
+ grid->addMultiCellWidget(mTooltipShowAlarms, 1, 1, 0, 2, Qt::AlignAuto);
+
+ QHBox* box = new QHBox(group);
+ box->setSpacing(KDialog::spacingHint());
+ mTooltipMaxAlarms = new QCheckBox(i18n("Ma&ximum number of alarms to show:"), box, "tooltipMax");
+ mTooltipMaxAlarms->setMinimumSize(mTooltipMaxAlarms->sizeHint());
+ connect(mTooltipMaxAlarms, SIGNAL(toggled(bool)), SLOT(slotTooltipMaxToggled(bool)));
+ mTooltipMaxAlarmCount = new SpinBox(1, 99, 1, box);
+ mTooltipMaxAlarmCount->setLineShiftStep(5);
+ mTooltipMaxAlarmCount->setMinimumSize(mTooltipMaxAlarmCount->sizeHint());
+ QWhatsThis::add(box,
+ i18n("Uncheck to display all of the next 24 hours' alarms in the system tray tooltip. "
+ "Check to enter an upper limit on the number to be displayed."));
+ grid->addMultiCellWidget(box, 2, 2, 1, 2, Qt::AlignAuto);
+
+ mTooltipShowTime = new QCheckBox(MainWindow::i18n_m_ShowAlarmTime(), group, "tooltipTime");
+ mTooltipShowTime->setMinimumSize(mTooltipShowTime->sizeHint());
+ connect(mTooltipShowTime, SIGNAL(toggled(bool)), SLOT(slotTooltipTimeToggled(bool)));
+ QWhatsThis::add(mTooltipShowTime,
+ i18n("Specify whether to show in the system tray tooltip, the time at which each alarm is due"));
+ grid->addMultiCellWidget(mTooltipShowTime, 3, 3, 1, 2, Qt::AlignAuto);
+
+ mTooltipShowTimeTo = new QCheckBox(MainWindow::i18n_l_ShowTimeToAlarm(), group, "tooltipTimeTo");
+ mTooltipShowTimeTo->setMinimumSize(mTooltipShowTimeTo->sizeHint());
+ connect(mTooltipShowTimeTo, SIGNAL(toggled(bool)), SLOT(slotTooltipTimeToToggled(bool)));
+ QWhatsThis::add(mTooltipShowTimeTo,
+ i18n("Specify whether to show in the system tray tooltip, how long until each alarm is due"));
+ grid->addMultiCellWidget(mTooltipShowTimeTo, 4, 4, 1, 2, Qt::AlignAuto);
+
+ box = new QHBox(group); // this is to control the QWhatsThis text display area
+ box->setSpacing(KDialog::spacingHint());
+ mTooltipTimeToPrefixLabel = new QLabel(i18n("&Prefix:"), box);
+ mTooltipTimeToPrefixLabel->setFixedSize(mTooltipTimeToPrefixLabel->sizeHint());
+ mTooltipTimeToPrefix = new QLineEdit(box);
+ mTooltipTimeToPrefixLabel->setBuddy(mTooltipTimeToPrefix);
+ QWhatsThis::add(box,
+ i18n("Enter the text to be displayed in front of the time until the alarm, in the system tray tooltip"));
+ box->setFixedHeight(box->sizeHint().height());
+ grid->addWidget(box, 5, 2, Qt::AlignAuto);
+ group->setMaximumHeight(group->sizeHint().height());
+
+ mModalMessages = new QCheckBox(i18n("Message &windows have a title bar and take keyboard focus"), mPage, "modalMsg");
+ mModalMessages->setMinimumSize(mModalMessages->sizeHint());
+ QWhatsThis::add(mModalMessages,
+ i18n("Specify the characteristics of alarm message windows:\n"
+ "- If checked, the window is a normal window with a title bar, which grabs keyboard input when it is displayed.\n"
+ "- If unchecked, the window does not interfere with your typing when "
+ "it is displayed, but it has no title bar and cannot be moved or resized."));
+
+ QHBox* itemBox = new QHBox(mPage); // this is to control the QWhatsThis text display area
+ box = new QHBox(itemBox);
+ box->setSpacing(KDialog::spacingHint());
+ QLabel* label = new QLabel(i18n("System tray icon &update interval:"), box);
+ mDaemonTrayCheckInterval = new SpinBox(1, 9999, 1, box, "daemonCheck");
+ mDaemonTrayCheckInterval->setLineShiftStep(10);
+ mDaemonTrayCheckInterval->setMinimumSize(mDaemonTrayCheckInterval->sizeHint());
+ label->setBuddy(mDaemonTrayCheckInterval);
+ label = new QLabel(i18n("seconds"), box);
+ QWhatsThis::add(box,
+ i18n("How often to update the system tray icon to indicate whether or not the Alarm Daemon is monitoring alarms."));
+ itemBox->setStretchFactor(new QWidget(itemBox), 1); // left adjust the controls
+ itemBox->setFixedHeight(box->sizeHint().height());
+
+ mPage->setStretchFactor(new QWidget(mPage), 1); // top adjust the widgets
+}
+
+void ViewPrefTab::restore()
+{
+ setTooltip(Preferences::mTooltipAlarmCount,
+ Preferences::mShowTooltipAlarmTime,
+ Preferences::mShowTooltipTimeToAlarm,
+ Preferences::mTooltipTimeToPrefix);
+ mModalMessages->setChecked(Preferences::mModalMessages);
+ mDaemonTrayCheckInterval->setValue(Preferences::mDaemonTrayCheckInterval);
+}
+
+void ViewPrefTab::apply(bool syncToDisc)
+{
+ int n = mTooltipShowAlarms->isChecked() ? -1 : 0;
+ if (n && mTooltipMaxAlarms->isChecked())
+ n = mTooltipMaxAlarmCount->value();
+ Preferences::mTooltipAlarmCount = n;
+ Preferences::mShowTooltipAlarmTime = mTooltipShowTime->isChecked();
+ Preferences::mShowTooltipTimeToAlarm = mTooltipShowTimeTo->isChecked();
+ Preferences::mTooltipTimeToPrefix = mTooltipTimeToPrefix->text();
+ Preferences::mModalMessages = mModalMessages->isChecked();
+ Preferences::mDaemonTrayCheckInterval = mDaemonTrayCheckInterval->value();
+ PrefsTabBase::apply(syncToDisc);
+}
+
+void ViewPrefTab::setDefaults()
+{
+ setTooltip(Preferences::default_tooltipAlarmCount,
+ Preferences::default_showTooltipAlarmTime,
+ Preferences::default_showTooltipTimeToAlarm,
+ Preferences::default_tooltipTimeToPrefix);
+ mModalMessages->setChecked(Preferences::default_modalMessages);
+ mDaemonTrayCheckInterval->setValue(Preferences::default_daemonTrayCheckInterval);
+}
+
+void ViewPrefTab::setTooltip(int maxAlarms, bool time, bool timeTo, const QString& prefix)
+{
+ if (!timeTo)
+ time = true; // ensure that at least one time option is ticked
+
+ // Set the states of the controls without calling signal
+ // handlers, since these could change the checkboxes' states.
+ mTooltipShowAlarms->blockSignals(true);
+ mTooltipShowTime->blockSignals(true);
+ mTooltipShowTimeTo->blockSignals(true);
+
+ mTooltipShowAlarms->setChecked(maxAlarms);
+ mTooltipMaxAlarms->setChecked(maxAlarms > 0);
+ mTooltipMaxAlarmCount->setValue(maxAlarms > 0 ? maxAlarms : 1);
+ mTooltipShowTime->setChecked(time);
+ mTooltipShowTimeTo->setChecked(timeTo);
+ mTooltipTimeToPrefix->setText(prefix);
+
+ mTooltipShowAlarms->blockSignals(false);
+ mTooltipShowTime->blockSignals(false);
+ mTooltipShowTimeTo->blockSignals(false);
+
+ // Enable/disable controls according to their states
+ slotTooltipTimeToToggled(timeTo);
+ slotTooltipAlarmsToggled(maxAlarms);
+}
+
+void ViewPrefTab::slotTooltipAlarmsToggled(bool on)
+{
+ mTooltipMaxAlarms->setEnabled(on);
+ mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isChecked());
+ mTooltipShowTime->setEnabled(on);
+ mTooltipShowTimeTo->setEnabled(on);
+ on = on && mTooltipShowTimeTo->isChecked();
+ mTooltipTimeToPrefix->setEnabled(on);
+ mTooltipTimeToPrefixLabel->setEnabled(on);
+}
+
+void ViewPrefTab::slotTooltipMaxToggled(bool on)
+{
+ mTooltipMaxAlarmCount->setEnabled(on && mTooltipMaxAlarms->isEnabled());
+}
+
+void ViewPrefTab::slotTooltipTimeToggled(bool on)
+{
+ if (!on && !mTooltipShowTimeTo->isChecked())
+ mTooltipShowTimeTo->setChecked(true);
+}
+
+void ViewPrefTab::slotTooltipTimeToToggled(bool on)
+{
+ if (!on && !mTooltipShowTime->isChecked())
+ mTooltipShowTime->setChecked(true);
+ on = on && mTooltipShowTimeTo->isEnabled();
+ mTooltipTimeToPrefix->setEnabled(on);
+ mTooltipTimeToPrefixLabel->setEnabled(on);
+}
diff --git a/kalarm/prefdlg.h b/kalarm/prefdlg.h
new file mode 100644
index 00000000..a5f1ee9a
--- /dev/null
+++ b/kalarm/prefdlg.h
@@ -0,0 +1,270 @@
+/*
+ * prefdlg.h - program preferences dialog
+ * Program: kalarm
+ * Copyright © 2001-2007 by David Jarvie <djarvie@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.
+ */
+
+#ifndef PREFDLG_H
+#define PREFDLG_H
+
+#include <qsize.h>
+#include <qdatetime.h>
+#include <ktabctl.h>
+#include <kdialogbase.h>
+
+#include "preferences.h"
+#include "recurrenceedit.h"
+#include "soundpicker.h"
+
+class QButtonGroup;
+class QCheckBox;
+class QRadioButton;
+class QPushButton;
+class QComboBox;
+class QLineEdit;
+class KColorCombo;
+class FontColourChooser;
+class ButtonGroup;
+class TimeEdit;
+class SpinBox;
+class SpecialActionsButton;
+
+class FontColourPrefTab;
+class EditPrefTab;
+class EmailPrefTab;
+class ViewPrefTab;
+class MiscPrefTab;
+
+
+// The Preferences dialog
+class KAlarmPrefDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ static void display();
+ ~KAlarmPrefDlg();
+
+ FontColourPrefTab* mFontColourPage;
+ EditPrefTab* mEditPage;
+ EmailPrefTab* mEmailPage;
+ ViewPrefTab* mViewPage;
+ MiscPrefTab* mMiscPage;
+
+ protected slots:
+ virtual void slotOk();
+ virtual void slotApply();
+ virtual void slotHelp();
+ virtual void slotDefault();
+ virtual void slotCancel();
+
+ private:
+ KAlarmPrefDlg();
+ void restore();
+
+ static KAlarmPrefDlg* mInstance;
+ bool mValid;
+};
+
+// Base class for each tab in the Preferences dialog
+class PrefsTabBase : public QWidget
+{
+ Q_OBJECT
+ public:
+ PrefsTabBase(QVBox*);
+
+ void setPreferences();
+ virtual void restore() = 0;
+ virtual void apply(bool syncToDisc) = 0;
+ virtual void setDefaults() = 0;
+ static int indentWidth() { return mIndentWidth; }
+
+ protected:
+ QVBox* mPage;
+
+ private:
+ static int mIndentWidth; // indent width for checkboxes etc.
+};
+
+
+// Miscellaneous tab of the Preferences dialog
+class MiscPrefTab : public PrefsTabBase
+{
+ Q_OBJECT
+ public:
+ MiscPrefTab(QVBox*);
+
+ virtual void restore();
+ virtual void apply(bool syncToDisc);
+ virtual void setDefaults();
+
+ private slots:
+ void slotAutostartDaemonClicked();
+ void slotRunModeToggled(bool);
+ void slotDisableIfStoppedToggled(bool);
+ void slotExpiredToggled(bool);
+ void slotClearExpired();
+ void slotOtherTerminalToggled(bool);
+//#ifdef AUTOSTART_BY_KALARMD
+ void slotAutostartToggled(bool);
+//#endif
+
+ private:
+ void setExpiredControls(int purgeDays);
+
+ QCheckBox* mAutostartDaemon;
+ QRadioButton* mRunInSystemTray;
+ QRadioButton* mRunOnDemand;
+ QCheckBox* mDisableAlarmsIfStopped;
+ QCheckBox* mQuitWarn;
+ QCheckBox* mAutostartTrayIcon;
+ QCheckBox* mConfirmAlarmDeletion;
+ QCheckBox* mKeepExpired;
+ QCheckBox* mPurgeExpired;
+ SpinBox* mPurgeAfter;
+ QLabel* mPurgeAfterLabel;
+ QPushButton* mClearExpired;
+ TimeEdit* mStartOfDay;
+ QButtonGroup* mXtermType;
+ QLineEdit* mXtermCommand;
+ int mXtermFirst; // id of first terminal window radio button
+ int mXtermCount; // number of terminal window types
+};
+
+
+// Email tab of the Preferences dialog
+class EmailPrefTab : public PrefsTabBase
+{
+ Q_OBJECT
+ public:
+ EmailPrefTab(QVBox*);
+
+ QString validate();
+ virtual void restore();
+ virtual void apply(bool syncToDisc);
+ virtual void setDefaults();
+
+ private slots:
+ void slotEmailClientChanged(int);
+ void slotFromAddrChanged(int);
+ void slotBccAddrChanged(int);
+ void slotAddressChanged() { mAddressChanged = true; }
+
+ private:
+ void setEmailAddress(Preferences::MailFrom, const QString& address);
+ void setEmailBccAddress(bool useControlCentre, const QString& address);
+ QString validateAddr(ButtonGroup*, QLineEdit* addr, const QString& msg);
+
+ ButtonGroup* mEmailClient;
+ ButtonGroup* mFromAddressGroup;
+ QLineEdit* mEmailAddress;
+ ButtonGroup* mBccAddressGroup;
+ QLineEdit* mEmailBccAddress;
+ QCheckBox* mEmailQueuedNotify;
+ QCheckBox* mEmailCopyToKMail;
+ bool mAddressChanged;
+ bool mBccAddressChanged;
+};
+
+
+// Edit defaults tab of the Preferences dialog
+class EditPrefTab : public PrefsTabBase
+{
+ Q_OBJECT
+ public:
+ EditPrefTab(QVBox*);
+
+ QString validate();
+ virtual void restore();
+ virtual void apply(bool syncToDisc);
+ virtual void setDefaults();
+
+ private slots:
+ void slotBrowseSoundFile();
+
+ private:
+ QCheckBox* mAutoClose;
+ QCheckBox* mConfirmAck;
+ QComboBox* mReminderUnits;
+ SpecialActionsButton* mSpecialActionsButton;
+ QCheckBox* mCmdScript;
+ QCheckBox* mCmdXterm;
+ QCheckBox* mEmailBcc;
+ QComboBox* mSound;
+ QLabel* mSoundFileLabel;
+ QLineEdit* mSoundFile;
+ QPushButton* mSoundFileBrowse;
+ QCheckBox* mSoundRepeat;
+ QCheckBox* mCopyToKOrganizer;
+ QCheckBox* mLateCancel;
+ QComboBox* mRecurPeriod;
+ QButtonGroup* mFeb29;
+
+ static int soundIndex(SoundPicker::Type);
+ static int recurIndex(RecurrenceEdit::RepeatType);
+};
+
+
+// View tab of the Preferences dialog
+class ViewPrefTab : public PrefsTabBase
+{
+ Q_OBJECT
+ public:
+ ViewPrefTab(QVBox*);
+
+ virtual void restore();
+ virtual void apply(bool syncToDisc);
+ virtual void setDefaults();
+
+ private slots:
+ void slotTooltipAlarmsToggled(bool);
+ void slotTooltipMaxToggled(bool);
+ void slotTooltipTimeToggled(bool);
+ void slotTooltipTimeToToggled(bool);
+
+ private:
+ void setTooltip(int maxAlarms, bool time, bool timeTo, const QString& prefix);
+
+ QCheckBox* mTooltipShowAlarms;
+ QCheckBox* mTooltipMaxAlarms;
+ SpinBox* mTooltipMaxAlarmCount;
+ QCheckBox* mTooltipShowTime;
+ QCheckBox* mTooltipShowTimeTo;
+ QLineEdit* mTooltipTimeToPrefix;
+ QLabel* mTooltipTimeToPrefixLabel;
+ QCheckBox* mModalMessages;
+ SpinBox* mDaemonTrayCheckInterval;
+};
+
+
+// Font & Colour tab of the Preferences dialog
+class FontColourPrefTab : public PrefsTabBase
+{
+ Q_OBJECT
+ public:
+ FontColourPrefTab(QVBox*);
+
+ virtual void restore();
+ virtual void apply(bool syncToDisc);
+ virtual void setDefaults();
+
+ private:
+ FontColourChooser* mFontChooser;
+ KColorCombo* mDisabledColour;
+ KColorCombo* mExpiredColour;
+};
+
+#endif // PREFDLG_H
diff --git a/kalarm/preferences.cpp b/kalarm/preferences.cpp
new file mode 100644
index 00000000..ad96098e
--- /dev/null
+++ b/kalarm/preferences.cpp
@@ -0,0 +1,705 @@
+/*
+ * preferences.cpp - program preference settings
+ * Program: kalarm
+ * Copyright © 2001-2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <kapplication.h>
+#include <kglobalsettings.h>
+#include <kmessagebox.h>
+
+#include <libkpimidentities/identity.h>
+#include <libkpimidentities/identitymanager.h>
+
+#include "daemon.h"
+#include "functions.h"
+#include "kamail.h"
+#include "messagebox.h"
+#include "preferences.moc"
+
+
+static QString translateXTermPath(KConfig*, const QString& cmdline, bool write);
+
+Preferences* Preferences::mInstance = 0;
+
+// Default config file settings
+QColor defaultMessageColours[] = { Qt::red, Qt::green, Qt::blue, Qt::cyan, Qt::magenta, Qt::yellow, Qt::white, Qt::lightGray, Qt::black, QColor() };
+const ColourList Preferences::default_messageColours(defaultMessageColours);
+const QColor Preferences::default_defaultBgColour(Qt::red);
+const QColor Preferences::default_defaultFgColour(Qt::black);
+QFont Preferences::mDefault_messageFont; // initialised in constructor
+const QTime Preferences::default_startOfDay(0, 0);
+const bool Preferences::default_runInSystemTray = true;
+const bool Preferences::default_disableAlarmsIfStopped = true;
+const bool Preferences::default_quitWarn = true;
+const bool Preferences::default_autostartTrayIcon = true;
+const bool Preferences::default_confirmAlarmDeletion = true;
+const bool Preferences::default_modalMessages = true;
+const int Preferences::default_messageButtonDelay = 0; // (seconds)
+const int Preferences::default_tooltipAlarmCount = 5;
+const bool Preferences::default_showTooltipAlarmTime = true;
+const bool Preferences::default_showTooltipTimeToAlarm = true;
+const QString Preferences::default_tooltipTimeToPrefix = QString::fromLatin1("+");
+const int Preferences::default_daemonTrayCheckInterval = 10; // (seconds)
+const bool Preferences::default_emailCopyToKMail = false;
+const bool Preferences::default_emailQueuedNotify = false;
+const QColor Preferences::default_disabledColour(Qt::lightGray);
+const QColor Preferences::default_expiredColour(Qt::darkRed);
+const int Preferences::default_expiredKeepDays = 7;
+const QString Preferences::default_defaultSoundFile = QString::null;
+const float Preferences::default_defaultSoundVolume = -1;
+const int Preferences::default_defaultLateCancel = 0;
+const bool Preferences::default_defaultAutoClose = false;
+const bool Preferences::default_defaultCopyToKOrganizer = false;
+const bool Preferences::default_defaultSoundRepeat = false;
+const SoundPicker::Type Preferences::default_defaultSoundType = SoundPicker::NONE;
+const bool Preferences::default_defaultConfirmAck = false;
+const bool Preferences::default_defaultCmdScript = false;
+const EditAlarmDlg::CmdLogType Preferences::default_defaultCmdLogType = EditAlarmDlg::DISCARD_OUTPUT;
+const bool Preferences::default_defaultEmailBcc = false;
+const QString Preferences::default_emailAddress = QString::null;
+const QString Preferences::default_emailBccAddress = QString::null;
+const Preferences::MailClient Preferences::default_emailClient = KMAIL;
+const Preferences::MailFrom Preferences::default_emailBccFrom = MAIL_FROM_CONTROL_CENTRE;
+const RecurrenceEdit::RepeatType Preferences::default_defaultRecurPeriod = RecurrenceEdit::NO_RECUR;
+const KARecurrence::Feb29Type Preferences::default_defaultFeb29Type = KARecurrence::FEB29_MAR1;
+const TimePeriod::Units Preferences::default_defaultReminderUnits = TimePeriod::HOURS_MINUTES;
+const QString Preferences::default_defaultPreAction;
+const QString Preferences::default_defaultPostAction;
+
+Preferences::MailFrom Preferences::default_emailFrom()
+{
+ return KAMail::identitiesExist() ? MAIL_FROM_KMAIL : MAIL_FROM_CONTROL_CENTRE;
+}
+
+// Active config file settings
+ColourList Preferences::mMessageColours;
+QColor Preferences::mDefaultBgColour;
+QFont Preferences::mMessageFont;
+QTime Preferences::mStartOfDay;
+bool Preferences::mRunInSystemTray;
+bool Preferences::mDisableAlarmsIfStopped;
+bool Preferences::mAutostartTrayIcon;
+KARecurrence::Feb29Type Preferences::mDefaultFeb29Type;
+bool Preferences::mModalMessages;
+int Preferences::mMessageButtonDelay;
+int Preferences::mTooltipAlarmCount;
+bool Preferences::mShowTooltipAlarmTime;
+bool Preferences::mShowTooltipTimeToAlarm;
+QString Preferences::mTooltipTimeToPrefix;
+int Preferences::mDaemonTrayCheckInterval;
+QString Preferences::mEmailAddress;
+QString Preferences::mEmailBccAddress;
+Preferences::MailClient Preferences::mEmailClient;
+Preferences::MailFrom Preferences::mEmailFrom;
+Preferences::MailFrom Preferences::mEmailBccFrom;
+bool Preferences::mEmailCopyToKMail;
+QString Preferences::mCmdXTermCommand;
+QColor Preferences::mDisabledColour;
+QColor Preferences::mExpiredColour;
+int Preferences::mExpiredKeepDays;
+// Default settings for Edit Alarm dialog
+QString Preferences::mDefaultSoundFile;
+float Preferences::mDefaultSoundVolume;
+int Preferences::mDefaultLateCancel;
+bool Preferences::mDefaultAutoClose;
+bool Preferences::mDefaultCopyToKOrganizer;
+SoundPicker::Type Preferences::mDefaultSoundType;
+bool Preferences::mDefaultSoundRepeat;
+bool Preferences::mDefaultConfirmAck;
+bool Preferences::mDefaultEmailBcc;
+bool Preferences::mDefaultCmdScript;
+EditAlarmDlg::CmdLogType Preferences::mDefaultCmdLogType;
+QString Preferences::mDefaultCmdLogFile;
+RecurrenceEdit::RepeatType Preferences::mDefaultRecurPeriod;
+TimePeriod::Units Preferences::mDefaultReminderUnits;
+QString Preferences::mDefaultPreAction;
+QString Preferences::mDefaultPostAction;
+// Change tracking
+QTime Preferences::mOldStartOfDay;
+bool Preferences::mStartOfDayChanged;
+
+
+static const QString defaultFeb29RecurType = QString::fromLatin1("Mar1");
+static const QString defaultEmailClient = QString::fromLatin1("kmail");
+
+// Config file entry names
+static const QString GENERAL_SECTION = QString::fromLatin1("General");
+static const QString VERSION_NUM = QString::fromLatin1("Version");
+static const QString MESSAGE_COLOURS = QString::fromLatin1("MessageColours");
+static const QString MESSAGE_BG_COLOUR = QString::fromLatin1("MessageBackgroundColour");
+static const QString MESSAGE_FONT = QString::fromLatin1("MessageFont");
+static const QString RUN_IN_SYSTEM_TRAY = QString::fromLatin1("RunInSystemTray");
+static const QString DISABLE_IF_STOPPED = QString::fromLatin1("DisableAlarmsIfStopped");
+static const QString AUTOSTART_TRAY = QString::fromLatin1("AutostartTray");
+static const QString FEB29_RECUR_TYPE = QString::fromLatin1("Feb29Recur");
+static const QString MODAL_MESSAGES = QString::fromLatin1("ModalMessages");
+static const QString MESSAGE_BUTTON_DELAY = QString::fromLatin1("MessageButtonDelay");
+static const QString TOOLTIP_ALARM_COUNT = QString::fromLatin1("TooltipAlarmCount");
+static const QString TOOLTIP_ALARM_TIME = QString::fromLatin1("ShowTooltipAlarmTime");
+static const QString TOOLTIP_TIME_TO_ALARM = QString::fromLatin1("ShowTooltipTimeToAlarm");
+static const QString TOOLTIP_TIME_TO_PREFIX = QString::fromLatin1("TooltipTimeToPrefix");
+static const QString DAEMON_TRAY_INTERVAL = QString::fromLatin1("DaemonTrayCheckInterval");
+static const QString EMAIL_CLIENT = QString::fromLatin1("EmailClient");
+static const QString EMAIL_COPY_TO_KMAIL = QString::fromLatin1("EmailCopyToKMail");
+static const QString EMAIL_FROM = QString::fromLatin1("EmailFrom");
+static const QString EMAIL_BCC_ADDRESS = QString::fromLatin1("EmailBccAddress");
+static const QString CMD_XTERM_COMMAND = QString::fromLatin1("CmdXTerm");
+static const QString START_OF_DAY = QString::fromLatin1("StartOfDay");
+static const QString START_OF_DAY_CHECK = QString::fromLatin1("Sod");
+static const QString DISABLED_COLOUR = QString::fromLatin1("DisabledColour");
+static const QString EXPIRED_COLOUR = QString::fromLatin1("ExpiredColour");
+static const QString EXPIRED_KEEP_DAYS = QString::fromLatin1("ExpiredKeepDays");
+static const QString DEFAULTS_SECTION = QString::fromLatin1("Defaults");
+static const QString DEF_LATE_CANCEL = QString::fromLatin1("DefLateCancel");
+static const QString DEF_AUTO_CLOSE = QString::fromLatin1("DefAutoClose");
+static const QString DEF_CONFIRM_ACK = QString::fromLatin1("DefConfirmAck");
+static const QString DEF_COPY_TO_KORG = QString::fromLatin1("DefCopyKOrg");
+static const QString DEF_SOUND_TYPE = QString::fromLatin1("DefSoundType");
+static const QString DEF_SOUND_FILE = QString::fromLatin1("DefSoundFile");
+static const QString DEF_SOUND_VOLUME = QString::fromLatin1("DefSoundVolume");
+static const QString DEF_SOUND_REPEAT = QString::fromLatin1("DefSoundRepeat");
+static const QString DEF_CMD_SCRIPT = QString::fromLatin1("DefCmdScript");
+static const QString DEF_CMD_LOG_TYPE = QString::fromLatin1("DefCmdLogType");
+static const QString DEF_LOG_FILE = QString::fromLatin1("DefLogFile");
+static const QString DEF_EMAIL_BCC = QString::fromLatin1("DefEmailBcc");
+static const QString DEF_RECUR_PERIOD = QString::fromLatin1("DefRecurPeriod");
+static const QString DEF_REMIND_UNITS = QString::fromLatin1("RemindUnits");
+static const QString DEF_PRE_ACTION = QString::fromLatin1("DefPreAction");
+static const QString DEF_POST_ACTION = QString::fromLatin1("DefPostAction");
+
+// Config file entry name for temporary use
+static const QString TEMP = QString::fromLatin1("Temp");
+
+// Values for EmailFrom entry
+static const QString FROM_CONTROL_CENTRE = QString::fromLatin1("@ControlCenter");
+static const QString FROM_KMAIL = QString::fromLatin1("@KMail");
+
+// Config file entry names for notification messages
+const QString Preferences::QUIT_WARN = QString::fromLatin1("QuitWarn");
+const QString Preferences::CONFIRM_ALARM_DELETION = QString::fromLatin1("ConfirmAlarmDeletion");
+const QString Preferences::EMAIL_QUEUED_NOTIFY = QString::fromLatin1("EmailQueuedNotify");
+
+static const int SODxor = 0x82451630;
+inline int Preferences::startOfDayCheck()
+{
+ // Combine with a 'random' constant to prevent 'clever' people fiddling the
+ // value, and thereby screwing things up.
+ return QTime().msecsTo(mStartOfDay) ^ SODxor;
+}
+
+
+void Preferences::initialise()
+{
+ if (!mInstance)
+ {
+ // Initialise static variables here to avoid static initialisation
+ // sequencing errors.
+ mDefault_messageFont = QFont(KGlobalSettings::generalFont().family(), 16, QFont::Bold);
+
+ mInstance = new Preferences;
+
+ convertOldPrefs(); // convert preferences written by previous KAlarm versions
+ read();
+
+ // Set the default button for the Quit warning message box to Cancel
+ MessageBox::setContinueDefault(QUIT_WARN, KMessageBox::Cancel);
+ MessageBox::setDefaultShouldBeShownContinue(QUIT_WARN, default_quitWarn);
+ MessageBox::setDefaultShouldBeShownContinue(EMAIL_QUEUED_NOTIFY, default_emailQueuedNotify);
+ MessageBox::setDefaultShouldBeShownContinue(CONFIRM_ALARM_DELETION, default_confirmAlarmDeletion);
+ }
+}
+
+void Preferences::connect(const char* signal, const QObject* receiver, const char* member)
+{
+ initialise();
+ QObject::connect(mInstance, signal, receiver, member);
+}
+
+void Preferences::emitStartOfDayChanged()
+{
+ emit startOfDayChanged(mOldStartOfDay);
+}
+
+void Preferences::emitPreferencesChanged()
+{
+ emit preferencesChanged();
+}
+
+/******************************************************************************
+* Read preference values from the config file.
+*/
+void Preferences::read()
+{
+ initialise();
+
+ KConfig* config = KGlobal::config();
+ config->setGroup(GENERAL_SECTION);
+ QStringList cols = config->readListEntry(MESSAGE_COLOURS);
+ if (!cols.count())
+ mMessageColours = default_messageColours;
+ else
+ {
+ mMessageColours.clear();
+ for (QStringList::Iterator it = cols.begin(); it != cols.end(); ++it)
+ {
+ QColor c((*it));
+ if (c.isValid())
+ mMessageColours.insert(c);
+ }
+ }
+ mDefaultBgColour = config->readColorEntry(MESSAGE_BG_COLOUR, &default_defaultBgColour);
+ mMessageFont = config->readFontEntry(MESSAGE_FONT, &mDefault_messageFont);
+ mRunInSystemTray = config->readBoolEntry(RUN_IN_SYSTEM_TRAY, default_runInSystemTray);
+ mDisableAlarmsIfStopped = config->readBoolEntry(DISABLE_IF_STOPPED, default_disableAlarmsIfStopped);
+ mAutostartTrayIcon = config->readBoolEntry(AUTOSTART_TRAY, default_autostartTrayIcon);
+ mModalMessages = config->readBoolEntry(MODAL_MESSAGES, default_modalMessages);
+ mMessageButtonDelay = config->readNumEntry(MESSAGE_BUTTON_DELAY, default_messageButtonDelay);
+ if (mMessageButtonDelay > 10)
+ mMessageButtonDelay = 10; // prevent windows being unusable for a long time
+ if (mMessageButtonDelay < -1)
+ mMessageButtonDelay = -1;
+ mTooltipAlarmCount = static_cast<int>(config->readUnsignedNumEntry(TOOLTIP_ALARM_COUNT, default_tooltipAlarmCount));
+ if (mTooltipAlarmCount < 1)
+ mTooltipAlarmCount = 1;
+ mShowTooltipAlarmTime = config->readBoolEntry(TOOLTIP_ALARM_TIME, default_showTooltipAlarmTime);
+ mShowTooltipTimeToAlarm = config->readBoolEntry(TOOLTIP_TIME_TO_ALARM, default_showTooltipTimeToAlarm);
+ mTooltipTimeToPrefix = config->readEntry(TOOLTIP_TIME_TO_PREFIX, default_tooltipTimeToPrefix);
+ mDaemonTrayCheckInterval = static_cast<int>(config->readUnsignedNumEntry(DAEMON_TRAY_INTERVAL, default_daemonTrayCheckInterval));
+ if (mDaemonTrayCheckInterval < 1)
+ mDaemonTrayCheckInterval = 1;
+ QCString client = config->readEntry(EMAIL_CLIENT, defaultEmailClient).local8Bit(); // don't use readPathEntry() here (values are hard-coded)
+ mEmailClient = (client == "sendmail" ? SENDMAIL : KMAIL);
+ mEmailCopyToKMail = config->readBoolEntry(EMAIL_COPY_TO_KMAIL, default_emailCopyToKMail);
+ QString from = config->readEntry(EMAIL_FROM, emailFrom(default_emailFrom(), false, false));
+ mEmailFrom = emailFrom(from);
+ QString bccFrom = config->readEntry(EMAIL_BCC_ADDRESS, emailFrom(default_emailBccFrom, false, true));
+ mEmailBccFrom = emailFrom(bccFrom);
+ if (mEmailFrom == MAIL_FROM_CONTROL_CENTRE || mEmailBccFrom == MAIL_FROM_CONTROL_CENTRE)
+ mEmailAddress = mEmailBccAddress = KAMail::controlCentreAddress();
+ if (mEmailFrom == MAIL_FROM_ADDR)
+ mEmailAddress = from;
+ if (mEmailBccFrom == MAIL_FROM_ADDR)
+ mEmailBccAddress = bccFrom;
+ mCmdXTermCommand = translateXTermPath(config, config->readEntry(CMD_XTERM_COMMAND), false);
+ QDateTime defStartOfDay(QDate(1900,1,1), default_startOfDay);
+ mStartOfDay = config->readDateTimeEntry(START_OF_DAY, &defStartOfDay).time();
+ mOldStartOfDay.setHMS(0,0,0);
+ int sod = config->readNumEntry(START_OF_DAY_CHECK, 0);
+ if (sod)
+ mOldStartOfDay = mOldStartOfDay.addMSecs(sod ^ SODxor);
+ mDisabledColour = config->readColorEntry(DISABLED_COLOUR, &default_disabledColour);
+ mExpiredColour = config->readColorEntry(EXPIRED_COLOUR, &default_expiredColour);
+ mExpiredKeepDays = config->readNumEntry(EXPIRED_KEEP_DAYS, default_expiredKeepDays);
+
+ config->setGroup(DEFAULTS_SECTION);
+ mDefaultLateCancel = static_cast<int>(config->readUnsignedNumEntry(DEF_LATE_CANCEL, default_defaultLateCancel));
+ mDefaultAutoClose = config->readBoolEntry(DEF_AUTO_CLOSE, default_defaultAutoClose);
+ mDefaultConfirmAck = config->readBoolEntry(DEF_CONFIRM_ACK, default_defaultConfirmAck);
+ mDefaultCopyToKOrganizer = config->readBoolEntry(DEF_COPY_TO_KORG, default_defaultCopyToKOrganizer);
+ int soundType = config->readNumEntry(DEF_SOUND_TYPE, default_defaultSoundType);
+ mDefaultSoundType = (soundType < 0 || soundType > SoundPicker::SPEAK)
+ ? default_defaultSoundType : (SoundPicker::Type)soundType;
+ mDefaultSoundVolume = static_cast<float>(config->readDoubleNumEntry(DEF_SOUND_VOLUME, default_defaultSoundVolume));
+#ifdef WITHOUT_ARTS
+ mDefaultSoundRepeat = false;
+#else
+ mDefaultSoundRepeat = config->readBoolEntry(DEF_SOUND_REPEAT, default_defaultSoundRepeat);
+#endif
+ mDefaultSoundFile = config->readPathEntry(DEF_SOUND_FILE);
+ mDefaultCmdScript = config->readBoolEntry(DEF_CMD_SCRIPT, default_defaultCmdScript);
+ int logType = config->readNumEntry(DEF_CMD_LOG_TYPE, default_defaultCmdLogType);
+ mDefaultCmdLogType = (logType < EditAlarmDlg::DISCARD_OUTPUT || logType > EditAlarmDlg::EXEC_IN_TERMINAL)
+ ? default_defaultCmdLogType : (EditAlarmDlg::CmdLogType)logType;
+ mDefaultCmdLogFile = config->readPathEntry(DEF_LOG_FILE);
+ mDefaultEmailBcc = config->readBoolEntry(DEF_EMAIL_BCC, default_defaultEmailBcc);
+ int recurPeriod = config->readNumEntry(DEF_RECUR_PERIOD, default_defaultRecurPeriod);
+ mDefaultRecurPeriod = (recurPeriod < RecurrenceEdit::SUBDAILY || recurPeriod > RecurrenceEdit::ANNUAL)
+ ? default_defaultRecurPeriod : (RecurrenceEdit::RepeatType)recurPeriod;
+ QCString feb29 = config->readEntry(FEB29_RECUR_TYPE, defaultFeb29RecurType).local8Bit();
+ mDefaultFeb29Type = (feb29 == "Mar1") ? KARecurrence::FEB29_MAR1 : (feb29 == "Feb28") ? KARecurrence::FEB29_FEB28 : KARecurrence::FEB29_FEB29;
+ QString remindUnits = config->readEntry(DEF_REMIND_UNITS);
+ mDefaultReminderUnits = (remindUnits == QString::fromLatin1("Minutes")) ? TimePeriod::MINUTES
+ : (remindUnits == QString::fromLatin1("HoursMinutes")) ? TimePeriod::HOURS_MINUTES
+ : (remindUnits == QString::fromLatin1("Days")) ? TimePeriod::DAYS
+ : (remindUnits == QString::fromLatin1("Weeks")) ? TimePeriod::WEEKS : default_defaultReminderUnits;
+ mDefaultPreAction = config->readEntry(DEF_PRE_ACTION, default_defaultPreAction);
+ mDefaultPostAction = config->readEntry(DEF_POST_ACTION, default_defaultPostAction);
+ mInstance->emitPreferencesChanged();
+ mStartOfDayChanged = (mStartOfDay != mOldStartOfDay);
+ if (mStartOfDayChanged)
+ {
+ mInstance->emitStartOfDayChanged();
+ mOldStartOfDay = mStartOfDay;
+ }
+}
+
+/******************************************************************************
+* Save preference values to the config file.
+*/
+void Preferences::save(bool syncToDisc)
+{
+ KConfig* config = KGlobal::config();
+ config->setGroup(GENERAL_SECTION);
+ config->writeEntry(VERSION_NUM, KALARM_VERSION);
+ QStringList colours;
+ for (ColourList::const_iterator it = mMessageColours.begin(); it != mMessageColours.end(); ++it)
+ colours.append(QColor(*it).name());
+ config->writeEntry(MESSAGE_COLOURS, colours);
+ config->writeEntry(MESSAGE_BG_COLOUR, mDefaultBgColour);
+ config->writeEntry(MESSAGE_FONT, mMessageFont);
+ config->writeEntry(RUN_IN_SYSTEM_TRAY, mRunInSystemTray);
+ config->writeEntry(DISABLE_IF_STOPPED, mDisableAlarmsIfStopped);
+ config->writeEntry(AUTOSTART_TRAY, mAutostartTrayIcon);
+ config->writeEntry(MODAL_MESSAGES, mModalMessages);
+ config->writeEntry(MESSAGE_BUTTON_DELAY, mMessageButtonDelay);
+ config->writeEntry(TOOLTIP_ALARM_COUNT, mTooltipAlarmCount);
+ config->writeEntry(TOOLTIP_ALARM_TIME, mShowTooltipAlarmTime);
+ config->writeEntry(TOOLTIP_TIME_TO_ALARM, mShowTooltipTimeToAlarm);
+ config->writeEntry(TOOLTIP_TIME_TO_PREFIX, mTooltipTimeToPrefix);
+ config->writeEntry(DAEMON_TRAY_INTERVAL, mDaemonTrayCheckInterval);
+ config->writeEntry(EMAIL_CLIENT, (mEmailClient == SENDMAIL ? "sendmail" : "kmail"));
+ config->writeEntry(EMAIL_COPY_TO_KMAIL, mEmailCopyToKMail);
+ config->writeEntry(EMAIL_FROM, emailFrom(mEmailFrom, true, false));
+ config->writeEntry(EMAIL_BCC_ADDRESS, emailFrom(mEmailBccFrom, true, true));
+ config->writeEntry(CMD_XTERM_COMMAND, translateXTermPath(config, mCmdXTermCommand, true));
+ config->writeEntry(START_OF_DAY, QDateTime(QDate(1900,1,1), mStartOfDay));
+ // Start-of-day check value is only written once the start-of-day time has been processed.
+ config->writeEntry(DISABLED_COLOUR, mDisabledColour);
+ config->writeEntry(EXPIRED_COLOUR, mExpiredColour);
+ config->writeEntry(EXPIRED_KEEP_DAYS, mExpiredKeepDays);
+
+ config->setGroup(DEFAULTS_SECTION);
+ config->writeEntry(DEF_LATE_CANCEL, mDefaultLateCancel);
+ config->writeEntry(DEF_AUTO_CLOSE, mDefaultAutoClose);
+ config->writeEntry(DEF_CONFIRM_ACK, mDefaultConfirmAck);
+ config->writeEntry(DEF_COPY_TO_KORG, mDefaultCopyToKOrganizer);
+ config->writeEntry(DEF_SOUND_TYPE, mDefaultSoundType);
+ config->writePathEntry(DEF_SOUND_FILE, mDefaultSoundFile);
+ config->writeEntry(DEF_SOUND_VOLUME, static_cast<double>(mDefaultSoundVolume));
+ config->writeEntry(DEF_SOUND_REPEAT, mDefaultSoundRepeat);
+ config->writeEntry(DEF_CMD_SCRIPT, mDefaultCmdScript);
+ config->writeEntry(DEF_CMD_LOG_TYPE, mDefaultCmdLogType);
+ config->writePathEntry(DEF_LOG_FILE, mDefaultCmdLogFile);
+ config->writeEntry(DEF_EMAIL_BCC, mDefaultEmailBcc);
+ config->writeEntry(DEF_RECUR_PERIOD, mDefaultRecurPeriod);
+ config->writeEntry(FEB29_RECUR_TYPE, (mDefaultFeb29Type == KARecurrence::FEB29_MAR1 ? "Mar1" : mDefaultFeb29Type == KARecurrence::FEB29_FEB28 ? "Feb28" : "None"));
+ QString value;
+ switch (mDefaultReminderUnits)
+ {
+ case TimePeriod::MINUTES: value = QString::fromLatin1("Minutes"); break;
+ case TimePeriod::HOURS_MINUTES: value = QString::fromLatin1("HoursMinutes"); break;
+ case TimePeriod::DAYS: value = QString::fromLatin1("Days"); break;
+ case TimePeriod::WEEKS: value = QString::fromLatin1("Weeks"); break;
+ default: value = QString::null; break;
+ }
+ config->writeEntry(DEF_REMIND_UNITS, value);
+ config->writeEntry(DEF_PRE_ACTION, mDefaultPreAction);
+ config->writeEntry(DEF_POST_ACTION, mDefaultPostAction);
+
+ if (syncToDisc)
+ config->sync();
+ mInstance->emitPreferencesChanged();
+ if (mStartOfDay != mOldStartOfDay)
+ {
+ mInstance->emitStartOfDayChanged();
+ mOldStartOfDay = mStartOfDay;
+ }
+}
+
+void Preferences::syncToDisc()
+{
+ KGlobal::config()->sync();
+}
+
+void Preferences::updateStartOfDayCheck()
+{
+ KConfig* config = KGlobal::config();
+ config->setGroup(GENERAL_SECTION);
+ config->writeEntry(START_OF_DAY_CHECK, startOfDayCheck());
+ config->sync();
+ mStartOfDayChanged = false;
+}
+
+QString Preferences::emailFrom(Preferences::MailFrom from, bool useAddress, bool bcc)
+{
+ switch (from)
+ {
+ case MAIL_FROM_KMAIL:
+ return FROM_KMAIL;
+ case MAIL_FROM_CONTROL_CENTRE:
+ return FROM_CONTROL_CENTRE;
+ case MAIL_FROM_ADDR:
+ return useAddress ? (bcc ? mEmailBccAddress : mEmailAddress) : QString::null;
+ default:
+ return QString::null;
+ }
+}
+
+Preferences::MailFrom Preferences::emailFrom(const QString& str)
+{
+ if (str == FROM_KMAIL)
+ return MAIL_FROM_KMAIL;
+ if (str == FROM_CONTROL_CENTRE)
+ return MAIL_FROM_CONTROL_CENTRE;
+ return MAIL_FROM_ADDR;
+}
+
+/******************************************************************************
+* Get user's default 'From' email address.
+*/
+QString Preferences::emailAddress()
+{
+ switch (mEmailFrom)
+ {
+ case MAIL_FROM_KMAIL:
+ return KAMail::identityManager()->defaultIdentity().fullEmailAddr();
+ case MAIL_FROM_CONTROL_CENTRE:
+ return KAMail::controlCentreAddress();
+ case MAIL_FROM_ADDR:
+ return mEmailAddress;
+ default:
+ return QString::null;
+ }
+}
+
+QString Preferences::emailBccAddress()
+{
+ switch (mEmailBccFrom)
+ {
+ case MAIL_FROM_CONTROL_CENTRE:
+ return KAMail::controlCentreAddress();
+ case MAIL_FROM_ADDR:
+ return mEmailBccAddress;
+ default:
+ return QString::null;
+ }
+}
+
+void Preferences::setEmailAddress(Preferences::MailFrom from, const QString& address)
+{
+ switch (from)
+ {
+ case MAIL_FROM_KMAIL:
+ break;
+ case MAIL_FROM_CONTROL_CENTRE:
+ mEmailAddress = KAMail::controlCentreAddress();
+ break;
+ case MAIL_FROM_ADDR:
+ mEmailAddress = address;
+ break;
+ default:
+ return;
+ }
+ mEmailFrom = from;
+}
+
+void Preferences::setEmailBccAddress(bool useControlCentre, const QString& address)
+{
+ if (useControlCentre)
+ mEmailBccAddress = KAMail::controlCentreAddress();
+ else
+ mEmailBccAddress = address;
+ mEmailBccFrom = useControlCentre ? MAIL_FROM_CONTROL_CENTRE : MAIL_FROM_ADDR;
+}
+
+/******************************************************************************
+* Called to allow or suppress output of the specified message dialog, where the
+* dialog has a checkbox to turn notification off.
+*/
+void Preferences::setNotify(const QString& messageID, bool notify)
+{
+ MessageBox::saveDontShowAgainContinue(messageID, !notify);
+}
+
+/******************************************************************************
+* Return whether the specified message dialog is output, where the dialog has
+* a checkbox to turn notification off.
+* Reply = false if message has been suppressed (by preferences or by selecting
+* "don't ask again")
+* = true in all other cases.
+*/
+bool Preferences::notifying(const QString& messageID)
+{
+ return MessageBox::shouldBeShownContinue(messageID);
+}
+
+/******************************************************************************
+* If the preferences were written by a previous version of KAlarm, do any
+* necessary conversions.
+*/
+void Preferences::convertOldPrefs()
+{
+ KConfig* config = KGlobal::config();
+ config->setGroup(GENERAL_SECTION);
+ int version = KAlarm::getVersionNumber(config->readEntry(VERSION_NUM));
+ if (version >= KAlarm::Version(1,4,22))
+ return; // config format is up to date
+
+ if (version <= KAlarm::Version(1,4,21))
+ {
+ // Convert KAlarm 1.4.21 preferences
+ static const QString OLD_REMIND_UNITS = QString::fromLatin1("DefRemindUnits");
+ config->setGroup(DEFAULTS_SECTION);
+ int intUnit = config->readNumEntry(OLD_REMIND_UNITS, 0);
+ QString strUnit = (intUnit == 1) ? QString::fromLatin1("Days")
+ : (intUnit == 2) ? QString::fromLatin1("Weeks")
+ : QString::fromLatin1("HoursMinutes");
+ config->deleteEntry(OLD_REMIND_UNITS);
+ config->writeEntry(DEF_REMIND_UNITS, strUnit);
+ }
+
+ if (version <= KAlarm::Version(1,4,20))
+ {
+ // Convert KAlarm 1.4.20 preferences
+ static const QString VIEW_SECTION = QString::fromLatin1("View");
+ static const QString SHOW_ARCHIVED_ALARMS = QString::fromLatin1("ShowArchivedAlarms");
+ static const QString SHOW_EXPIRED_ALARMS = QString::fromLatin1("ShowExpiredAlarms");
+ static const QString SHOW_ALARM_TIME = QString::fromLatin1("ShowAlarmTime");
+ static const QString SHOW_TIME_TO_ALARM = QString::fromLatin1("ShowTimeToAlarm");
+ config->setGroup(GENERAL_SECTION);
+ bool showExpired = config->readBoolEntry(SHOW_EXPIRED_ALARMS, false);
+ bool showTime = config->readBoolEntry(SHOW_ALARM_TIME, true);
+ bool showTimeTo = config->readBoolEntry(SHOW_TIME_TO_ALARM, false);
+ config->deleteEntry(SHOW_EXPIRED_ALARMS);
+ config->deleteEntry(SHOW_ALARM_TIME);
+ config->deleteEntry(SHOW_TIME_TO_ALARM);
+ config->setGroup(VIEW_SECTION);
+ config->writeEntry(SHOW_ARCHIVED_ALARMS, showExpired);
+ config->writeEntry(SHOW_ALARM_TIME, showTime);
+ config->writeEntry(SHOW_TIME_TO_ALARM, showTimeTo);
+ }
+
+ if (version <= KAlarm::Version(1,4,5))
+ {
+ // Convert KAlarm 1.4.5 preferences
+ static const QString DEF_SOUND = QString::fromLatin1("DefSound");
+ config->setGroup(DEFAULTS_SECTION);
+ bool sound = config->readBoolEntry(DEF_SOUND, false);
+ if (!sound)
+ config->writeEntry(DEF_SOUND_TYPE, SoundPicker::NONE);
+ config->deleteEntry(DEF_SOUND);
+ }
+
+ if (version < KAlarm::Version(1,3,0))
+ {
+ // Convert KAlarm pre-1.3 preferences
+ static const QString EMAIL_ADDRESS = QString::fromLatin1("EmailAddress");
+ static const QString EMAIL_USE_CTRL_CENTRE = QString::fromLatin1("EmailUseControlCenter");
+ static const QString EMAIL_BCC_USE_CTRL_CENTRE = QString::fromLatin1("EmailBccUseControlCenter");
+ QMap<QString, QString> entries = config->entryMap(GENERAL_SECTION);
+ if (entries.find(EMAIL_FROM) == entries.end()
+ && entries.find(EMAIL_USE_CTRL_CENTRE) != entries.end())
+ {
+ // Preferences were written by KAlarm pre-1.2.1
+ config->setGroup(GENERAL_SECTION);
+ bool useCC = false;
+ bool bccUseCC = false;
+ const bool default_emailUseControlCentre = true;
+ const bool default_emailBccUseControlCentre = true;
+ useCC = config->readBoolEntry(EMAIL_USE_CTRL_CENTRE, default_emailUseControlCentre);
+ // EmailBccUseControlCenter was missing in preferences written by KAlarm pre-0.9.5
+ bccUseCC = config->hasKey(EMAIL_BCC_USE_CTRL_CENTRE)
+ ? config->readBoolEntry(EMAIL_BCC_USE_CTRL_CENTRE, default_emailBccUseControlCentre)
+ : useCC;
+ config->writeEntry(EMAIL_FROM, (useCC ? FROM_CONTROL_CENTRE : config->readEntry(EMAIL_ADDRESS)));
+ config->writeEntry(EMAIL_BCC_ADDRESS, (bccUseCC ? FROM_CONTROL_CENTRE : config->readEntry(EMAIL_BCC_ADDRESS)));
+ config->deleteEntry(EMAIL_ADDRESS);
+ config->deleteEntry(EMAIL_BCC_USE_CTRL_CENTRE);
+ config->deleteEntry(EMAIL_USE_CTRL_CENTRE);
+ }
+ // Convert KAlarm 1.2 preferences
+ static const QString DEF_CMD_XTERM = QString::fromLatin1("DefCmdXterm");
+ config->setGroup(DEFAULTS_SECTION);
+ if (config->hasKey(DEF_CMD_XTERM))
+ {
+ config->writeEntry(DEF_CMD_LOG_TYPE,
+ (config->readBoolEntry(DEF_CMD_XTERM, false) ? EditAlarmDlg::EXEC_IN_TERMINAL : EditAlarmDlg::DISCARD_OUTPUT));
+ config->deleteEntry(DEF_CMD_XTERM);
+ }
+ }
+ config->setGroup(GENERAL_SECTION);
+ config->writeEntry(VERSION_NUM, KALARM_VERSION);
+ config->sync();
+}
+
+/******************************************************************************
+* Translate an X terminal command path to/from config file format.
+* Note that only a home directory specification at the start of the path is
+* translated, so there's no need to worry about missing out some of the
+* executable's path due to quotes etc.
+* N.B. Calling KConfig::read/writePathEntry() on the entire command line
+* causes a crash on some systems, so it's necessary to extract the
+* executable path first before processing.
+*/
+QString translateXTermPath(KConfig* config, const QString& cmdline, bool write)
+{
+ QString params;
+ QString cmd = cmdline;
+ if (cmdline.isEmpty())
+ return cmdline;
+ // Strip any leading quote
+ QChar quote = cmdline[0];
+ char q = static_cast<char>(quote);
+ bool quoted = (q == '"' || q == '\'');
+ if (quoted)
+ cmd = cmdline.mid(1);
+ // Split the command at the first non-escaped space
+ for (int i = 0, count = cmd.length(); i < count; ++i)
+ {
+ switch (cmd[i].latin1())
+ {
+ case '\\':
+ ++i;
+ continue;
+ case '"':
+ case '\'':
+ if (cmd[i] != quote)
+ continue;
+ // fall through to ' '
+ case ' ':
+ params = cmd.mid(i);
+ cmd = cmd.left(i);
+ break;
+ default:
+ continue;
+ }
+ break;
+ }
+ // Translate any home directory specification at the start of the
+ // executable's path.
+ if (write)
+ {
+ config->writePathEntry(TEMP, cmd);
+ cmd = config->readEntry(TEMP);
+ }
+ else
+ {
+ config->writeEntry(TEMP, cmd);
+ cmd = config->readPathEntry(TEMP);
+ }
+ config->deleteEntry(TEMP);
+ if (quoted)
+ return quote + cmd + params;
+ else
+ return cmd + params;
+}
diff --git a/kalarm/preferences.h b/kalarm/preferences.h
new file mode 100644
index 00000000..f0eeb4ea
--- /dev/null
+++ b/kalarm/preferences.h
@@ -0,0 +1,235 @@
+/*
+ * preferences.h - program preference settings
+ * Program: kalarm
+ * Copyright © 2001-2007 by David Jarvie <djarvie@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.
+ */
+
+#ifndef PREFERENCES_H
+#define PREFERENCES_H
+
+#include "kalarm.h"
+
+#include <qobject.h>
+#include <qcolor.h>
+#include <qfont.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+class QWidget;
+
+#include "colourlist.h"
+#include "editdlg.h"
+#include "karecurrence.h"
+#include "recurrenceedit.h"
+#include "soundpicker.h"
+#include "timeperiod.h"
+
+
+// Settings configured in the Preferences dialog
+class Preferences : public QObject
+{
+ Q_OBJECT
+ public:
+ enum MailClient { SENDMAIL, KMAIL };
+ enum MailFrom { MAIL_FROM_KMAIL, MAIL_FROM_CONTROL_CENTRE, MAIL_FROM_ADDR };
+ enum CmdLogType { DISCARD_OUTPUT, LOG_TO_FILE, EXEC_IN_TERMINAL };
+
+ static void initialise();
+ static void save(bool syncToDisc = true);
+ static void syncToDisc();
+ static void updateStartOfDayCheck();
+ static void connect(const char* signal, const QObject* receiver, const char* member);
+
+ // Access to settings
+ static const ColourList& messageColours() { return mMessageColours; }
+ static QColor defaultBgColour() { return mDefaultBgColour; }
+ static QColor defaultFgColour() { return default_defaultFgColour; }
+ static const QFont& messageFont() { return mMessageFont; }
+ static const QTime& startOfDay() { return mStartOfDay; }
+ static bool hasStartOfDayChanged() { return mStartOfDayChanged; }
+ static bool runInSystemTray() { return mRunInSystemTray; }
+ static bool disableAlarmsIfStopped() { return mDisableAlarmsIfStopped; }
+ static bool quitWarn() { return notifying(QUIT_WARN); }
+ static void setQuitWarn(bool yes) { setNotify(QUIT_WARN, yes); }
+ static bool autostartTrayIcon() { return mAutostartTrayIcon; }
+ static bool confirmAlarmDeletion() { return notifying(CONFIRM_ALARM_DELETION); }
+ static void setConfirmAlarmDeletion(bool yes){ setNotify(CONFIRM_ALARM_DELETION, yes); }
+ static bool modalMessages() { return mModalMessages; }
+ static int messageButtonDelay() { return mMessageButtonDelay; }
+ static int tooltipAlarmCount() { return mTooltipAlarmCount; }
+ static bool showTooltipAlarmTime() { return mShowTooltipAlarmTime; }
+ static bool showTooltipTimeToAlarm() { return mShowTooltipTimeToAlarm; }
+ static const QString& tooltipTimeToPrefix() { return mTooltipTimeToPrefix; }
+ static int daemonTrayCheckInterval() { return mDaemonTrayCheckInterval; }
+ static MailClient emailClient() { return mEmailClient; }
+ static bool emailCopyToKMail() { return mEmailCopyToKMail && mEmailClient == SENDMAIL; }
+ static bool emailQueuedNotify() { return notifying(EMAIL_QUEUED_NOTIFY); }
+ static void setEmailQueuedNotify(bool yes) { setNotify(EMAIL_QUEUED_NOTIFY, yes); }
+ static MailFrom emailFrom() { return mEmailFrom; }
+ static bool emailBccUseControlCentre() { return mEmailBccFrom == MAIL_FROM_CONTROL_CENTRE; }
+ static QString emailAddress();
+ static QString emailBccAddress();
+ static QString cmdXTermCommand() { return mCmdXTermCommand; }
+ static QColor disabledColour() { return mDisabledColour; }
+ static QColor expiredColour() { return mExpiredColour; }
+ static int expiredKeepDays() { return mExpiredKeepDays; }
+ static SoundPicker::Type defaultSoundType() { return mDefaultSoundType; }
+ static const QString& defaultSoundFile() { return mDefaultSoundFile; }
+ static float defaultSoundVolume() { return mDefaultSoundVolume; }
+ static bool defaultSoundRepeat() { return mDefaultSoundRepeat; }
+ static int defaultLateCancel() { return mDefaultLateCancel; }
+ static bool defaultAutoClose() { return mDefaultAutoClose; }
+ static bool defaultConfirmAck() { return mDefaultConfirmAck; }
+ static bool defaultCopyToKOrganizer() { return mDefaultCopyToKOrganizer; }
+ static bool defaultCmdScript() { return mDefaultCmdScript; }
+ static EditAlarmDlg::CmdLogType
+ defaultCmdLogType() { return mDefaultCmdLogType; }
+ static QString defaultCmdLogFile() { return mDefaultCmdLogFile; }
+ static bool defaultEmailBcc() { return mDefaultEmailBcc; }
+ static RecurrenceEdit::RepeatType
+ defaultRecurPeriod() { return mDefaultRecurPeriod; }
+ static KARecurrence::Feb29Type
+ defaultFeb29Type() { return mDefaultFeb29Type; }
+ static TimePeriod::Units defaultReminderUnits() { return mDefaultReminderUnits; }
+ static const QString& defaultPreAction() { return mDefaultPreAction; }
+ static const QString& defaultPostAction() { return mDefaultPostAction; }
+
+ // Config file entry names for notification messages
+ static const QString QUIT_WARN;
+ static const QString CONFIRM_ALARM_DELETION;
+ static const QString EMAIL_QUEUED_NOTIFY;
+
+ // Default values for settings
+ static const ColourList default_messageColours;
+ static const QColor default_defaultBgColour;
+ static const QColor default_defaultFgColour;
+ static const QFont& default_messageFont() { return mDefault_messageFont; };
+ static const QTime default_startOfDay;
+ static const bool default_runInSystemTray;
+ static const bool default_disableAlarmsIfStopped;
+ static const bool default_quitWarn;
+ static const bool default_autostartTrayIcon;
+ static const bool default_confirmAlarmDeletion;
+ static const bool default_modalMessages;
+ static const int default_messageButtonDelay;
+ static const int default_tooltipAlarmCount;
+ static const bool default_showTooltipAlarmTime;
+ static const bool default_showTooltipTimeToAlarm;
+ static const QString default_tooltipTimeToPrefix;
+ static const int default_daemonTrayCheckInterval;
+ static const MailClient default_emailClient;
+ static const bool default_emailCopyToKMail;
+ static MailFrom default_emailFrom();
+ static const bool default_emailQueuedNotify;
+ static const MailFrom default_emailBccFrom;
+ static const QString default_emailAddress;
+ static const QString default_emailBccAddress;
+ static const QColor default_disabledColour;
+ static const QColor default_expiredColour;
+ static const int default_expiredKeepDays;
+ static const QString default_defaultSoundFile;
+ static const float default_defaultSoundVolume;
+ static const int default_defaultLateCancel;
+ static const bool default_defaultAutoClose;
+ static const bool default_defaultCopyToKOrganizer;
+ static const SoundPicker::Type default_defaultSoundType;
+ static const bool default_defaultSoundRepeat;
+ static const bool default_defaultConfirmAck;
+ static const bool default_defaultCmdScript;
+ static const EditAlarmDlg::CmdLogType default_defaultCmdLogType;
+ static const bool default_defaultEmailBcc;
+ static const RecurrenceEdit::RepeatType default_defaultRecurPeriod;
+ static const KARecurrence::Feb29Type default_defaultFeb29Type;
+ static const TimePeriod::Units default_defaultReminderUnits;
+ static const QString default_defaultPreAction;
+ static const QString default_defaultPostAction;
+
+ signals:
+ void preferencesChanged();
+ void startOfDayChanged(const QTime& oldStartOfDay);
+
+ private:
+ Preferences() { } // only one instance allowed
+ void emitPreferencesChanged();
+ void emitStartOfDayChanged();
+
+ static void read();
+ static void convertOldPrefs();
+ static int startOfDayCheck();
+ static QString emailFrom(MailFrom, bool useAddress, bool bcc);
+ static MailFrom emailFrom(const QString&);
+ static void setNotify(const QString& messageID, bool notify);
+ static bool notifying(const QString& messageID);
+
+ static Preferences* mInstance;
+ static QFont mDefault_messageFont;
+ static QString mEmailAddress;
+ static QString mEmailBccAddress;
+
+ // All the following members are accessed by the Preferences dialog classes
+ friend class MiscPrefTab;
+ friend class EditPrefTab;
+ friend class ViewPrefTab;
+ friend class FontColourPrefTab;
+ friend class EmailPrefTab;
+ static void setEmailAddress(MailFrom, const QString& address);
+ static void setEmailBccAddress(bool useControlCentre, const QString& address);
+ static ColourList mMessageColours;
+ static QColor mDefaultBgColour;
+ static QFont mMessageFont;
+ static QTime mStartOfDay;
+ static bool mRunInSystemTray;
+ static bool mDisableAlarmsIfStopped;
+ static bool mAutostartTrayIcon;
+ static bool mModalMessages;
+ static int mMessageButtonDelay; // 0 = scatter; -1 = no delay, no scatter; >0 = delay, no scatter
+ static int mTooltipAlarmCount;
+ static bool mShowTooltipAlarmTime;
+ static bool mShowTooltipTimeToAlarm;
+ static QString mTooltipTimeToPrefix;
+ static int mDaemonTrayCheckInterval;
+ static MailClient mEmailClient;
+ static MailFrom mEmailFrom;
+ static MailFrom mEmailBccFrom;
+ static bool mEmailCopyToKMail;
+ static QString mCmdXTermCommand;
+ static QColor mDisabledColour;
+ static QColor mExpiredColour;
+ static int mExpiredKeepDays; // 0 = don't keep, -1 = keep indefinitely
+ // Default settings for Edit Alarm dialog
+ static QString mDefaultSoundFile;
+ static float mDefaultSoundVolume;
+ static int mDefaultLateCancel;
+ static bool mDefaultAutoClose;
+ static bool mDefaultCopyToKOrganizer;
+ static SoundPicker::Type mDefaultSoundType;
+ static bool mDefaultSoundRepeat;
+ static bool mDefaultConfirmAck;
+ static bool mDefaultEmailBcc;
+ static bool mDefaultCmdScript;
+ static EditAlarmDlg::CmdLogType mDefaultCmdLogType;
+ static QString mDefaultCmdLogFile;
+ static RecurrenceEdit::RepeatType mDefaultRecurPeriod;
+ static KARecurrence::Feb29Type mDefaultFeb29Type;
+ static TimePeriod::Units mDefaultReminderUnits;
+ static QString mDefaultPreAction;
+ static QString mDefaultPostAction;
+ // Change tracking
+ static QTime mOldStartOfDay; // previous start-of-day time
+ static bool mStartOfDayChanged; // start-of-day check value doesn't tally with mStartOfDay
+};
+
+#endif // PREFERENCES_H
diff --git a/kalarm/recurrenceedit.cpp b/kalarm/recurrenceedit.cpp
new file mode 100644
index 00000000..c7aef494
--- /dev/null
+++ b/kalarm/recurrenceedit.cpp
@@ -0,0 +1,1639 @@
+/*
+ * recurrenceedit.cpp - widget to edit the event's recurrence definition
+ * Program: kalarm
+ * Copyright © 2002-2008 by David Jarvie <djarvie@kde.org>
+ *
+ * Based originally on KOrganizer module koeditorrecurrence.cpp,
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@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 "kalarm.h"
+
+#include <qtooltip.h>
+#include <qlayout.h>
+#include <qvbox.h>
+#include <qwidgetstack.h>
+#include <qlistbox.h>
+#include <qframe.h>
+#include <qlabel.h>
+#include <qpushbutton.h>
+#include <qlineedit.h>
+#include <qwhatsthis.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kcalendarsystem.h>
+#include <kiconloader.h>
+#include <kdialog.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include <libkcal/event.h>
+
+#include "alarmevent.h"
+#include "alarmtimewidget.h"
+#include "checkbox.h"
+#include "combobox.h"
+#include "dateedit.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "karecurrence.h"
+#include "preferences.h"
+#include "radiobutton.h"
+#include "repetition.h"
+#include "spinbox.h"
+#include "timeedit.h"
+#include "timespinbox.h"
+#include "buttongroup.h"
+using namespace KCal;
+
+#include "recurrenceedit.moc"
+#include "recurrenceeditprivate.moc"
+
+// Collect these widget labels together to ensure consistent wording and
+// translations across different modules.
+QString RecurrenceEdit::i18n_Norecur() { return i18n("No recurrence"); }
+QString RecurrenceEdit::i18n_NoRecur() { return i18n("No Recurrence"); }
+QString RecurrenceEdit::i18n_AtLogin() { return i18n("At Login"); }
+QString RecurrenceEdit::i18n_l_Atlogin() { return i18n("At &login"); }
+QString RecurrenceEdit::i18n_HourlyMinutely() { return i18n("Hourly/Minutely"); }
+QString RecurrenceEdit::i18n_u_HourlyMinutely() { return i18n("Ho&urly/Minutely"); }
+QString RecurrenceEdit::i18n_Daily() { return i18n("Daily"); }
+QString RecurrenceEdit::i18n_d_Daily() { return i18n("&Daily"); }
+QString RecurrenceEdit::i18n_Weekly() { return i18n("Weekly"); }
+QString RecurrenceEdit::i18n_w_Weekly() { return i18n("&Weekly"); }
+QString RecurrenceEdit::i18n_Monthly() { return i18n("Monthly"); }
+QString RecurrenceEdit::i18n_m_Monthly() { return i18n("&Monthly"); }
+QString RecurrenceEdit::i18n_Yearly() { return i18n("Yearly"); }
+QString RecurrenceEdit::i18n_y_Yearly() { return i18n("&Yearly"); }
+
+
+RecurrenceEdit::RecurrenceEdit(bool readOnly, QWidget* parent, const char* name)
+ : QFrame(parent, name),
+ mRule(0),
+ mRuleButtonType(INVALID_RECUR),
+ mDailyShown(false),
+ mWeeklyShown(false),
+ mMonthlyShown(false),
+ mYearlyShown(false),
+ mNoEmitTypeChanged(true),
+ mReadOnly(readOnly)
+{
+ QBoxLayout* layout;
+ QVBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
+
+ /* Create the recurrence rule Group box which holds the recurrence period
+ * selection buttons, and the weekly, monthly and yearly recurrence rule
+ * frames which specify options individual to each of these distinct
+ * sections of the recurrence rule. Each frame is made visible by the
+ * selection of its corresponding radio button.
+ */
+
+ QGroupBox* recurGroup = new QGroupBox(1, Qt::Vertical, i18n("Recurrence Rule"), this, "recurGroup");
+ topLayout->addWidget(recurGroup);
+ QFrame* ruleFrame = new QFrame(recurGroup, "ruleFrame");
+ layout = new QVBoxLayout(ruleFrame, 0);
+ layout->addSpacing(KDialog::spacingHint()/2);
+
+ layout = new QHBoxLayout(layout, 0);
+ QBoxLayout* lay = new QVBoxLayout(layout, 0);
+ mRuleButtonGroup = new ButtonGroup(1, Qt::Horizontal, ruleFrame);
+ mRuleButtonGroup->setInsideMargin(0);
+ mRuleButtonGroup->setFrameStyle(QFrame::NoFrame);
+ lay->addWidget(mRuleButtonGroup);
+ lay->addStretch(); // top-adjust the interval radio buttons
+ connect(mRuleButtonGroup, SIGNAL(buttonSet(int)), SLOT(periodClicked(int)));
+
+ mNoneButton = new RadioButton(i18n_Norecur(), mRuleButtonGroup);
+ mNoneButton->setFixedSize(mNoneButton->sizeHint());
+ mNoneButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mNoneButton, i18n("Do not repeat the alarm"));
+
+ mAtLoginButton = new RadioButton(i18n_l_Atlogin(), mRuleButtonGroup);
+ mAtLoginButton->setFixedSize(mAtLoginButton->sizeHint());
+ mAtLoginButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mAtLoginButton,
+ i18n("Trigger the alarm at the specified date/time and at every login until then.\n"
+ "Note that it will also be triggered any time the alarm daemon is restarted."));
+
+ mSubDailyButton = new RadioButton(i18n_u_HourlyMinutely(), mRuleButtonGroup);
+ mSubDailyButton->setFixedSize(mSubDailyButton->sizeHint());
+ mSubDailyButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mSubDailyButton,
+ i18n("Repeat the alarm at hourly/minutely intervals"));
+
+ mDailyButton = new RadioButton(i18n_d_Daily(), mRuleButtonGroup);
+ mDailyButton->setFixedSize(mDailyButton->sizeHint());
+ mDailyButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mDailyButton,
+ i18n("Repeat the alarm at daily intervals"));
+
+ mWeeklyButton = new RadioButton(i18n_w_Weekly(), mRuleButtonGroup);
+ mWeeklyButton->setFixedSize(mWeeklyButton->sizeHint());
+ mWeeklyButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mWeeklyButton,
+ i18n("Repeat the alarm at weekly intervals"));
+
+ mMonthlyButton = new RadioButton(i18n_m_Monthly(), mRuleButtonGroup);
+ mMonthlyButton->setFixedSize(mMonthlyButton->sizeHint());
+ mMonthlyButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mMonthlyButton,
+ i18n("Repeat the alarm at monthly intervals"));
+
+ mYearlyButton = new RadioButton(i18n_y_Yearly(), mRuleButtonGroup);
+ mYearlyButton->setFixedSize(mYearlyButton->sizeHint());
+ mYearlyButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mYearlyButton,
+ i18n("Repeat the alarm at annual intervals"));
+
+ mNoneButtonId = mRuleButtonGroup->id(mNoneButton);
+ mAtLoginButtonId = mRuleButtonGroup->id(mAtLoginButton);
+ mSubDailyButtonId = mRuleButtonGroup->id(mSubDailyButton);
+ mDailyButtonId = mRuleButtonGroup->id(mDailyButton);
+ mWeeklyButtonId = mRuleButtonGroup->id(mWeeklyButton);
+ mMonthlyButtonId = mRuleButtonGroup->id(mMonthlyButton);
+ mYearlyButtonId = mRuleButtonGroup->id(mYearlyButton);
+
+ // Sub-repetition button
+ mSubRepetition = new RepetitionButton(i18n("Sub-Repetition"), true, ruleFrame);
+ mSubRepetition->setFixedSize(mSubRepetition->sizeHint());
+ mSubRepetition->setReadOnly(mReadOnly);
+ connect(mSubRepetition, SIGNAL(needsInitialisation()), SIGNAL(repeatNeedsInitialisation()));
+ connect(mSubRepetition, SIGNAL(changed()), SIGNAL(frequencyChanged()));
+ QWhatsThis::add(mSubRepetition, i18n("Set up a repetition within the recurrence, to trigger the alarm multiple times each time the recurrence is due."));
+ lay->addSpacing(KDialog::spacingHint());
+ lay->addWidget(mSubRepetition);
+
+ lay = new QVBoxLayout(layout);
+
+ lay->addStretch();
+ layout = new QHBoxLayout(lay);
+
+ layout->addSpacing(KDialog::marginHint());
+ QFrame* divider = new QFrame(ruleFrame);
+ divider->setFrameStyle(QFrame::VLine | QFrame::Sunken);
+ layout->addWidget(divider);
+ layout->addSpacing(KDialog::marginHint());
+
+ mNoRule = new NoRule(ruleFrame, "noFrame");
+ mSubDailyRule = new SubDailyRule(mReadOnly, ruleFrame, "subdayFrame");
+ mDailyRule = new DailyRule(mReadOnly, ruleFrame, "dayFrame");
+ mWeeklyRule = new WeeklyRule(mReadOnly, ruleFrame, "weekFrame");
+ mMonthlyRule = new MonthlyRule(mReadOnly, ruleFrame, "monthFrame");
+ mYearlyRule = new YearlyRule(mReadOnly, ruleFrame, "yearFrame");
+
+ connect(mSubDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
+ connect(mDailyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
+ connect(mWeeklyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
+ connect(mMonthlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
+ connect(mYearlyRule, SIGNAL(frequencyChanged()), this, SIGNAL(frequencyChanged()));
+
+ mRuleStack = new QWidgetStack(ruleFrame);
+ layout->addWidget(mRuleStack);
+ layout->addStretch(1);
+ mRuleStack->addWidget(mNoRule, 0);
+ mRuleStack->addWidget(mSubDailyRule, 1);
+ mRuleStack->addWidget(mDailyRule, 2);
+ mRuleStack->addWidget(mWeeklyRule, 3);
+ mRuleStack->addWidget(mMonthlyRule, 4);
+ mRuleStack->addWidget(mYearlyRule, 5);
+ layout->addSpacing(KDialog::marginHint());
+
+ // Create the recurrence range group which contains the controls
+ // which specify how long the recurrence is to last.
+
+ mRangeButtonGroup = new ButtonGroup(i18n("Recurrence End"), this, "mRangeButtonGroup");
+ connect(mRangeButtonGroup, SIGNAL(buttonSet(int)), SLOT(rangeTypeClicked()));
+ topLayout->addWidget(mRangeButtonGroup);
+
+ QVBoxLayout* vlayout = new QVBoxLayout(mRangeButtonGroup, KDialog::marginHint(), KDialog::spacingHint());
+ vlayout->addSpacing(fontMetrics().lineSpacing()/2);
+ mNoEndDateButton = new RadioButton(i18n("No &end"), mRangeButtonGroup);
+ mNoEndDateButton->setFixedSize(mNoEndDateButton->sizeHint());
+ mNoEndDateButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mNoEndDateButton, i18n("Repeat the alarm indefinitely"));
+ vlayout->addWidget(mNoEndDateButton, 1, Qt::AlignAuto);
+ QSize size = mNoEndDateButton->size();
+
+ layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
+ mRepeatCountButton = new RadioButton(i18n("End a&fter:"), mRangeButtonGroup);
+ mRepeatCountButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mRepeatCountButton,
+ i18n("Repeat the alarm for the number of times specified"));
+ mRepeatCountEntry = new SpinBox(1, 9999, 1, mRangeButtonGroup);
+ mRepeatCountEntry->setFixedSize(mRepeatCountEntry->sizeHint());
+ mRepeatCountEntry->setLineShiftStep(10);
+ mRepeatCountEntry->setSelectOnStep(false);
+ mRepeatCountEntry->setReadOnly(mReadOnly);
+ connect(mRepeatCountEntry, SIGNAL(valueChanged(int)), SLOT(repeatCountChanged(int)));
+ QWhatsThis::add(mRepeatCountEntry,
+ i18n("Enter the total number of times to trigger the alarm"));
+ mRepeatCountButton->setFocusWidget(mRepeatCountEntry);
+ mRepeatCountLabel = new QLabel(i18n("occurrence(s)"), mRangeButtonGroup);
+ mRepeatCountLabel->setFixedSize(mRepeatCountLabel->sizeHint());
+ layout->addWidget(mRepeatCountButton);
+ layout->addSpacing(KDialog::spacingHint());
+ layout->addWidget(mRepeatCountEntry);
+ layout->addWidget(mRepeatCountLabel);
+ layout->addStretch();
+ size = size.expandedTo(mRepeatCountButton->sizeHint());
+
+ layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
+ mEndDateButton = new RadioButton(i18n("End &by:"), mRangeButtonGroup);
+ mEndDateButton->setReadOnly(mReadOnly);
+ QWhatsThis::add(mEndDateButton,
+ i18n("Repeat the alarm until the date/time specified.\n\n"
+ "Note: This applies to the main recurrence only. It does not limit any sub-repetition which will occur regardless after the last main recurrence."));
+ mEndDateEdit = new DateEdit(mRangeButtonGroup);
+ mEndDateEdit->setFixedSize(mEndDateEdit->sizeHint());
+ mEndDateEdit->setReadOnly(mReadOnly);
+ QWhatsThis::add(mEndDateEdit,
+ i18n("Enter the last date to repeat the alarm"));
+ mEndDateButton->setFocusWidget(mEndDateEdit);
+ mEndTimeEdit = new TimeEdit(mRangeButtonGroup);
+ mEndTimeEdit->setFixedSize(mEndTimeEdit->sizeHint());
+ mEndTimeEdit->setReadOnly(mReadOnly);
+ static const QString lastTimeText = i18n("Enter the last time to repeat the alarm.");
+ QWhatsThis::add(mEndTimeEdit, QString("%1\n\n%2").arg(lastTimeText).arg(TimeSpinBox::shiftWhatsThis()));
+ mEndAnyTimeCheckBox = new CheckBox(i18n("Any time"), mRangeButtonGroup);
+ mEndAnyTimeCheckBox->setFixedSize(mEndAnyTimeCheckBox->sizeHint());
+ mEndAnyTimeCheckBox->setReadOnly(mReadOnly);
+ connect(mEndAnyTimeCheckBox, SIGNAL(toggled(bool)), SLOT(slotAnyTimeToggled(bool)));
+ QWhatsThis::add(mEndAnyTimeCheckBox,
+ i18n("Stop repeating the alarm after your first login on or after the specified end date"));
+ layout->addWidget(mEndDateButton);
+ layout->addSpacing(KDialog::spacingHint());
+ layout->addWidget(mEndDateEdit);
+ layout->addWidget(mEndTimeEdit);
+ layout->addWidget(mEndAnyTimeCheckBox);
+ layout->addStretch();
+ size = size.expandedTo(mEndDateButton->sizeHint());
+
+ // Line up the widgets to the right of the radio buttons
+ mRepeatCountButton->setFixedSize(size);
+ mEndDateButton->setFixedSize(size);
+
+ // Create the exceptions group which specifies dates to be excluded
+ // from the recurrence.
+
+ mExceptionGroup = new QGroupBox(i18n("E&xceptions"), this, "mExceptionGroup");
+ topLayout->addWidget(mExceptionGroup);
+ topLayout->setStretchFactor(mExceptionGroup, 2);
+ vlayout = new QVBoxLayout(mExceptionGroup, KDialog::marginHint(), KDialog::spacingHint());
+ vlayout->addSpacing(fontMetrics().lineSpacing()/2);
+ layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
+ vlayout = new QVBoxLayout(layout);
+
+ mExceptionDateList = new QListBox(mExceptionGroup);
+ mExceptionDateList->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
+ connect(mExceptionDateList, SIGNAL(selectionChanged()), SLOT(enableExceptionButtons()));
+ QWhatsThis::add(mExceptionDateList,
+ i18n("The list of exceptions, i.e. dates/times excluded from the recurrence"));
+ vlayout->addWidget(mExceptionDateList);
+
+ if (mReadOnly)
+ {
+ mExceptionDateEdit = 0;
+ mChangeExceptionButton = 0;
+ mDeleteExceptionButton = 0;
+ }
+ else
+ {
+ vlayout = new QVBoxLayout(layout);
+ mExceptionDateEdit = new DateEdit(mExceptionGroup);
+ mExceptionDateEdit->setFixedSize(mExceptionDateEdit->sizeHint());
+ mExceptionDateEdit->setDate(QDate::currentDate());
+ QWhatsThis::add(mExceptionDateEdit,
+ i18n("Enter a date to insert in the exceptions list. "
+ "Use in conjunction with the Add or Change button below."));
+ vlayout->addWidget(mExceptionDateEdit);
+
+ layout = new QHBoxLayout(vlayout, KDialog::spacingHint());
+ QPushButton* button = new QPushButton(i18n("Add"), mExceptionGroup);
+ button->setFixedSize(button->sizeHint());
+ connect(button, SIGNAL(clicked()), SLOT(addException()));
+ QWhatsThis::add(button,
+ i18n("Add the date entered above to the exceptions list"));
+ layout->addWidget(button);
+
+ mChangeExceptionButton = new QPushButton(i18n("Change"), mExceptionGroup);
+ mChangeExceptionButton->setFixedSize(mChangeExceptionButton->sizeHint());
+ connect(mChangeExceptionButton, SIGNAL(clicked()), SLOT(changeException()));
+ QWhatsThis::add(mChangeExceptionButton,
+ i18n("Replace the currently highlighted item in the exceptions list with the date entered above"));
+ layout->addWidget(mChangeExceptionButton);
+
+ mDeleteExceptionButton = new QPushButton(i18n("Delete"), mExceptionGroup);
+ mDeleteExceptionButton->setFixedSize(mDeleteExceptionButton->sizeHint());
+ connect(mDeleteExceptionButton, SIGNAL(clicked()), SLOT(deleteException()));
+ QWhatsThis::add(mDeleteExceptionButton,
+ i18n("Remove the currently highlighted item from the exceptions list"));
+ layout->addWidget(mDeleteExceptionButton);
+ }
+
+ mNoEmitTypeChanged = false;
+}
+
+/******************************************************************************
+ * Verify the consistency of the entered data.
+ * Reply = widget to receive focus on error, or 0 if no error.
+ */
+QWidget* RecurrenceEdit::checkData(const QDateTime& startDateTime, QString& errorMessage) const
+{
+ if (mAtLoginButton->isOn())
+ return 0;
+ const_cast<RecurrenceEdit*>(this)->mCurrStartDateTime = startDateTime;
+ if (mEndDateButton->isChecked())
+ {
+ QWidget* errWidget = 0;
+ bool noTime = !mEndTimeEdit->isEnabled();
+ QDate endDate = mEndDateEdit->date();
+ if (endDate < startDateTime.date())
+ errWidget = mEndDateEdit;
+ else if (!noTime && QDateTime(endDate, mEndTimeEdit->time()) < startDateTime)
+ errWidget = mEndTimeEdit;
+ if (errWidget)
+ {
+ errorMessage = noTime
+ ? i18n("End date is earlier than start date")
+ : i18n("End date/time is earlier than start date/time");
+ return errWidget;
+ }
+ }
+ if (!mRule)
+ return 0;
+ return mRule->validate(errorMessage);
+}
+
+/******************************************************************************
+ * Called when a recurrence period radio button is clicked.
+ */
+void RecurrenceEdit::periodClicked(int id)
+{
+ RepeatType oldType = mRuleButtonType;
+ bool none = (id == mNoneButtonId);
+ bool atLogin = (id == mAtLoginButtonId);
+ bool subdaily = (id == mSubDailyButtonId);
+ if (none)
+ {
+ mRule = 0;
+ mRuleButtonType = NO_RECUR;
+ }
+ else if (atLogin)
+ {
+ mRule = 0;
+ mRuleButtonType = AT_LOGIN;
+ mRangeButtonGroup->setButton(mRangeButtonGroup->id(mEndDateButton));
+ }
+ else if (subdaily)
+ {
+ mRule = mSubDailyRule;
+ mRuleButtonType = SUBDAILY;
+ }
+ else if (id == mDailyButtonId)
+ {
+ mRule = mDailyRule;
+ mRuleButtonType = DAILY;
+ mDailyShown = true;
+ }
+ else if (id == mWeeklyButtonId)
+ {
+ mRule = mWeeklyRule;
+ mRuleButtonType = WEEKLY;
+ mWeeklyShown = true;
+ }
+ else if (id == mMonthlyButtonId)
+ {
+ mRule = mMonthlyRule;
+ mRuleButtonType = MONTHLY;
+ mMonthlyShown = true;
+ }
+ else if (id == mYearlyButtonId)
+ {
+ mRule = mYearlyRule;
+ mRuleButtonType = ANNUAL;
+ mYearlyShown = true;
+ }
+ else
+ return;
+
+ if (mRuleButtonType != oldType)
+ {
+ mRuleStack->raiseWidget(mRule ? mRule : mNoRule);
+ if (oldType == NO_RECUR || none)
+ mRangeButtonGroup->setEnabled(!none);
+ mExceptionGroup->setEnabled(!(none || atLogin));
+ mEndAnyTimeCheckBox->setEnabled(atLogin);
+ if (!none)
+ {
+ mNoEndDateButton->setEnabled(!atLogin);
+ mRepeatCountButton->setEnabled(!atLogin);
+ }
+ rangeTypeClicked();
+ mSubRepetition->setEnabled(!(none || atLogin));
+ if (!mNoEmitTypeChanged)
+ emit typeChanged(mRuleButtonType);
+ }
+}
+
+void RecurrenceEdit::slotAnyTimeToggled(bool on)
+{
+ QButton* button = mRuleButtonGroup->selected();
+ mEndTimeEdit->setEnabled(button == mAtLoginButton && !on
+ || button == mSubDailyButton && mEndDateButton->isChecked());
+}
+
+/******************************************************************************
+ * Called when a recurrence range type radio button is clicked.
+ */
+void RecurrenceEdit::rangeTypeClicked()
+{
+ bool endDate = mEndDateButton->isOn();
+ mEndDateEdit->setEnabled(endDate);
+ mEndTimeEdit->setEnabled(endDate
+ && (mAtLoginButton->isOn() && !mEndAnyTimeCheckBox->isChecked()
+ || mSubDailyButton->isOn()));
+ bool repeatCount = mRepeatCountButton->isOn();
+ mRepeatCountEntry->setEnabled(repeatCount);
+ mRepeatCountLabel->setEnabled(repeatCount);
+}
+
+void RecurrenceEdit::showEvent(QShowEvent*)
+{
+ if (mRule)
+ mRule->setFrequencyFocus();
+ else
+ mRuleButtonGroup->selected()->setFocus();
+ emit shown();
+}
+
+ /******************************************************************************
+* Return the sub-repetition count within the recurrence, i.e. the number of
+* repetitions after the main recurrence.
+*/
+int RecurrenceEdit::subRepeatCount(int* subRepeatInterval) const
+{
+ int count = (mRuleButtonType >= SUBDAILY) ? mSubRepetition->count() : 0;
+ if (subRepeatInterval)
+ *subRepeatInterval = count ? mSubRepetition->interval() : 0;
+ return count;
+}
+
+/******************************************************************************
+* Called when the Sub-Repetition 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 RecurrenceEdit::setSubRepetition(int reminderMinutes, bool dateOnly)
+{
+ int maxDuration;
+ switch (mRuleButtonType)
+ {
+ case RecurrenceEdit::NO_RECUR:
+ case RecurrenceEdit::AT_LOGIN: // alarm repeat not allowed
+ maxDuration = 0;
+ break;
+ default: // repeat duration must be less than recurrence interval
+ {
+ KAEvent event;
+ updateEvent(event, false);
+ maxDuration = event.longestRecurrenceInterval() - reminderMinutes - 1;
+ break;
+ }
+ }
+ mSubRepetition->initialise(mSubRepetition->interval(), mSubRepetition->count(), dateOnly, maxDuration);
+ mSubRepetition->setEnabled(mRuleButtonType >= SUBDAILY && maxDuration);
+}
+
+/******************************************************************************
+* Activate the sub-repetition dialog.
+*/
+void RecurrenceEdit::activateSubRepetition()
+{
+ mSubRepetition->activate();
+}
+
+/******************************************************************************
+ * Called when the value of the repeat count field changes, to reset the
+ * minimum value to 1 if the value was 0.
+ */
+void RecurrenceEdit::repeatCountChanged(int value)
+{
+ if (value > 0 && mRepeatCountEntry->minValue() == 0)
+ mRepeatCountEntry->setMinValue(1);
+}
+
+/******************************************************************************
+ * Add the date entered in the exception date edit control to the list of
+ * exception dates.
+ */
+void RecurrenceEdit::addException()
+{
+ if (!mExceptionDateEdit || !mExceptionDateEdit->isValid())
+ return;
+ QDate date = mExceptionDateEdit->date();
+ QValueList<QDate>::Iterator it;
+ int index = 0;
+ bool insert = true;
+ for (it = mExceptionDates.begin(); it != mExceptionDates.end(); ++index, ++it)
+ {
+ if (date <= *it)
+ {
+ insert = (date != *it);
+ break;
+ }
+ }
+ if (insert)
+ {
+ mExceptionDates.insert(it, date);
+ mExceptionDateList->insertItem(KGlobal::locale()->formatDate(date), index);
+ }
+ mExceptionDateList->setCurrentItem(index);
+ enableExceptionButtons();
+}
+
+/******************************************************************************
+ * Change the currently highlighted exception date to that entered in the
+ * exception date edit control.
+ */
+void RecurrenceEdit::changeException()
+{
+ if (!mExceptionDateEdit || !mExceptionDateEdit->isValid())
+ return;
+ int index = mExceptionDateList->currentItem();
+ if (index >= 0 && mExceptionDateList->isSelected(index))
+ {
+ QDate olddate = mExceptionDates[index];
+ QDate newdate = mExceptionDateEdit->date();
+ if (newdate != olddate)
+ {
+ mExceptionDates.remove(mExceptionDates.at(index));
+ mExceptionDateList->removeItem(index);
+ addException();
+ }
+ }
+}
+
+/******************************************************************************
+ * Delete the currently highlighted exception date.
+ */
+void RecurrenceEdit::deleteException()
+{
+ int index = mExceptionDateList->currentItem();
+ if (index >= 0 && mExceptionDateList->isSelected(index))
+ {
+ mExceptionDates.remove(mExceptionDates.at(index));
+ mExceptionDateList->removeItem(index);
+ enableExceptionButtons();
+ }
+}
+
+/******************************************************************************
+ * Enable/disable the exception group buttons according to whether any item is
+ * selected in the exceptions listbox.
+ */
+void RecurrenceEdit::enableExceptionButtons()
+{
+ int index = mExceptionDateList->currentItem();
+ bool enable = (index >= 0 && mExceptionDateList->isSelected(index));
+ if (mDeleteExceptionButton)
+ mDeleteExceptionButton->setEnabled(enable);
+ if (mChangeExceptionButton)
+ mChangeExceptionButton->setEnabled(enable);
+
+ // Prevent the exceptions list box receiving keyboard focus is it's empty
+ mExceptionDateList->setFocusPolicy(mExceptionDateList->count() ? QWidget::WheelFocus : QWidget::NoFocus);
+}
+
+/******************************************************************************
+ * Notify this instance of a change in the alarm start date.
+ */
+void RecurrenceEdit::setStartDate(const QDate& start, const QDate& today)
+{
+ if (!mReadOnly)
+ {
+ setRuleDefaults(start);
+ if (start < today)
+ {
+ mEndDateEdit->setMinDate(today);
+ if (mExceptionDateEdit)
+ mExceptionDateEdit->setMinDate(today);
+ }
+ else
+ {
+ const QString startString = i18n("Date cannot be earlier than start date", "start date");
+ mEndDateEdit->setMinDate(start, startString);
+ if (mExceptionDateEdit)
+ mExceptionDateEdit->setMinDate(start, startString);
+ }
+ }
+}
+
+/******************************************************************************
+ * Specify the default recurrence end date.
+ */
+void RecurrenceEdit::setDefaultEndDate(const QDate& end)
+{
+ if (!mEndDateButton->isOn())
+ mEndDateEdit->setDate(end);
+}
+
+void RecurrenceEdit::setEndDateTime(const DateTime& end)
+{
+ mEndDateEdit->setDate(end.date());
+ mEndTimeEdit->setValue(end.time());
+ mEndTimeEdit->setEnabled(!end.isDateOnly());
+ mEndAnyTimeCheckBox->setChecked(end.isDateOnly());
+}
+
+DateTime RecurrenceEdit::endDateTime() const
+{
+ if (mRuleButtonGroup->selected() == mAtLoginButton && mEndAnyTimeCheckBox->isChecked())
+ return DateTime(mEndDateEdit->date());
+ return DateTime(mEndDateEdit->date(), mEndTimeEdit->time());
+}
+
+/******************************************************************************
+ * Set all controls to their default values.
+ */
+void RecurrenceEdit::setDefaults(const QDateTime& from)
+{
+ mCurrStartDateTime = from;
+ QDate fromDate = from.date();
+ mNoEndDateButton->setChecked(true);
+
+ mSubDailyRule->setFrequency(1);
+ mDailyRule->setFrequency(1);
+ mWeeklyRule->setFrequency(1);
+ mMonthlyRule->setFrequency(1);
+ mYearlyRule->setFrequency(1);
+
+ setRuleDefaults(fromDate);
+ mMonthlyRule->setType(MonthYearRule::DATE); // date in month
+ mYearlyRule->setType(MonthYearRule::DATE); // date in year
+
+ mEndDateEdit->setDate(fromDate);
+
+ mNoEmitTypeChanged = true;
+ int button;
+ switch (Preferences::defaultRecurPeriod())
+ {
+ case AT_LOGIN: button = mAtLoginButtonId; break;
+ case ANNUAL: button = mYearlyButtonId; break;
+ case MONTHLY: button = mMonthlyButtonId; break;
+ case WEEKLY: button = mWeeklyButtonId; break;
+ case DAILY: button = mDailyButtonId; break;
+ case SUBDAILY: button = mSubDailyButtonId; break;
+ case NO_RECUR:
+ default: button = mNoneButtonId; break;
+ }
+ mRuleButtonGroup->setButton(button);
+ mNoEmitTypeChanged = false;
+ rangeTypeClicked();
+ enableExceptionButtons();
+
+ saveState();
+}
+
+/******************************************************************************
+ * Set the controls for weekly, monthly and yearly rules which have not so far
+ * been shown, to their default values, depending on the recurrence start date.
+ */
+void RecurrenceEdit::setRuleDefaults(const QDate& fromDate)
+{
+ int day = fromDate.day();
+ int dayOfWeek = fromDate.dayOfWeek();
+ int month = fromDate.month();
+ if (!mDailyShown)
+ mDailyRule->setDays(true);
+ if (!mWeeklyShown)
+ mWeeklyRule->setDay(dayOfWeek);
+ if (!mMonthlyShown)
+ mMonthlyRule->setDefaultValues(day, dayOfWeek);
+ if (!mYearlyShown)
+ mYearlyRule->setDefaultValues(day, dayOfWeek, month);
+}
+
+/******************************************************************************
+* Set the state of all controls to reflect the data in the specified event.
+* Set 'keepDuration' true to prevent the recurrence count being adjusted to the
+* remaining number of recurrences.
+*/
+void RecurrenceEdit::set(const KAEvent& event, bool keepDuration)
+{
+ setDefaults(event.mainDateTime().dateTime());
+ if (event.repeatAtLogin())
+ {
+ mRuleButtonGroup->setButton(mAtLoginButtonId);
+ mEndDateButton->setChecked(true);
+ return;
+ }
+ mRuleButtonGroup->setButton(mNoneButtonId);
+ KARecurrence* recurrence = event.recurrence();
+ if (!recurrence)
+ return;
+ KARecurrence::Type rtype = recurrence->type();
+ switch (rtype)
+ {
+ case KARecurrence::MINUTELY:
+ mRuleButtonGroup->setButton(mSubDailyButtonId);
+ break;
+
+ case KARecurrence::DAILY:
+ {
+ mRuleButtonGroup->setButton(mDailyButtonId);
+ QBitArray rDays = recurrence->days();
+ bool set = false;
+ for (int i = 0; i < 7 && !set; ++i)
+ set = rDays.testBit(i);
+ if (set)
+ mDailyRule->setDays(rDays);
+ else
+ mDailyRule->setDays(true);
+ break;
+ }
+ case KARecurrence::WEEKLY:
+ {
+ mRuleButtonGroup->setButton(mWeeklyButtonId);
+ QBitArray rDays = recurrence->days();
+ mWeeklyRule->setDays(rDays);
+ break;
+ }
+ case KARecurrence::MONTHLY_POS: // on nth (Tuesday) of the month
+ {
+ QValueList<RecurrenceRule::WDayPos> posns = recurrence->monthPositions();
+ int i = posns.first().pos();
+ if (!i)
+ {
+ // It's every (Tuesday) of the month. Convert to a weekly recurrence
+ // (but ignoring any non-every xxxDay positions).
+ mRuleButtonGroup->setButton(mWeeklyButtonId);
+ mWeeklyRule->setFrequency(recurrence->frequency());
+ QBitArray rDays(7);
+ for (QValueList<RecurrenceRule::WDayPos>::ConstIterator it = posns.begin(); it != posns.end(); ++it)
+ {
+ if (!(*it).pos())
+ rDays.setBit((*it).day() - 1, 1);
+ }
+ mWeeklyRule->setDays(rDays);
+ break;
+ }
+ mRuleButtonGroup->setButton(mMonthlyButtonId);
+ mMonthlyRule->setPosition(i, posns.first().day());
+ break;
+ }
+ case KARecurrence::MONTHLY_DAY: // on nth day of the month
+ {
+ mRuleButtonGroup->setButton(mMonthlyButtonId);
+ QValueList<int> rmd = recurrence->monthDays();
+ int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
+ mMonthlyRule->setDate(day);
+ break;
+ }
+ case KARecurrence::ANNUAL_DATE: // on the nth day of (months...) in the year
+ case KARecurrence::ANNUAL_POS: // on the nth (Tuesday) of (months...) in the year
+ {
+ if (rtype == KARecurrence::ANNUAL_DATE)
+ {
+ mRuleButtonGroup->setButton(mYearlyButtonId);
+ const QValueList<int> rmd = recurrence->monthDays();
+ int day = (rmd.isEmpty()) ? event.mainDate().day() : rmd.first();
+ mYearlyRule->setDate(day);
+ mYearlyRule->setFeb29Type(recurrence->feb29Type());
+ }
+ else if (rtype == KARecurrence::ANNUAL_POS)
+ {
+ mRuleButtonGroup->setButton(mYearlyButtonId);
+ QValueList<RecurrenceRule::WDayPos> posns = recurrence->yearPositions();
+ mYearlyRule->setPosition(posns.first().pos(), posns.first().day());
+ }
+ mYearlyRule->setMonths(recurrence->yearMonths());
+ break;
+ }
+ default:
+ return;
+ }
+
+ mRule->setFrequency(recurrence->frequency());
+
+ // Get range information
+ QDateTime endtime = mCurrStartDateTime;
+ int duration = recurrence->duration();
+ if (duration == -1)
+ mNoEndDateButton->setChecked(true);
+ else if (duration)
+ {
+ mRepeatCountButton->setChecked(true);
+ mRepeatCountEntry->setValue(duration);
+ }
+ else
+ {
+ mEndDateButton->setChecked(true);
+ endtime = recurrence->endDateTime();
+ mEndTimeEdit->setValue(endtime.time());
+ }
+ mEndDateEdit->setDate(endtime.date());
+
+ // Get exception information
+ mExceptionDates = event.recurrence()->exDates();
+ qHeapSort(mExceptionDates);
+ mExceptionDateList->clear();
+ for (DateList::ConstIterator it = mExceptionDates.begin(); it != mExceptionDates.end(); ++it)
+ mExceptionDateList->insertItem(KGlobal::locale()->formatDate(*it));
+ enableExceptionButtons();
+
+ // Get repetition within recurrence
+ mSubRepetition->set(event.repeatInterval(), event.repeatCount());
+
+ rangeTypeClicked();
+
+ saveState();
+}
+
+/******************************************************************************
+ * Update the specified KAEvent with the entered recurrence data.
+ * If 'adjustStart' is true, the start date/time will be adjusted if necessary
+ * to be the first date/time which recurs on or after the original start.
+ */
+void RecurrenceEdit::updateEvent(KAEvent& event, bool adjustStart)
+{
+ // Get end date and repeat count, common to all types of recurring events
+ QDate endDate;
+ QTime endTime;
+ int repeatCount;
+ if (mNoEndDateButton->isChecked())
+ repeatCount = -1;
+ else if (mRepeatCountButton->isChecked())
+ repeatCount = mRepeatCountEntry->value();
+ else
+ {
+ repeatCount = 0;
+ endDate = mEndDateEdit->date();
+ endTime = mEndTimeEdit->time();
+ }
+
+ // Set up the recurrence according to the type selected
+ QButton* button = mRuleButtonGroup->selected();
+ event.setRepeatAtLogin(button == mAtLoginButton);
+ int frequency = mRule ? mRule->frequency() : 0;
+ if (button == mSubDailyButton)
+ {
+ QDateTime endDateTime(endDate, endTime);
+ event.setRecurMinutely(frequency, repeatCount, endDateTime);
+ }
+ else if (button == mDailyButton)
+ {
+ event.setRecurDaily(frequency, mDailyRule->days(), repeatCount, endDate);
+ }
+ else if (button == mWeeklyButton)
+ {
+ event.setRecurWeekly(frequency, mWeeklyRule->days(), repeatCount, endDate);
+ }
+ else if (button == mMonthlyButton)
+ {
+ if (mMonthlyRule->type() == MonthlyRule::POS)
+ {
+ // It's by position
+ KAEvent::MonthPos pos;
+ pos.days.fill(false);
+ pos.days.setBit(mMonthlyRule->dayOfWeek() - 1);
+ pos.weeknum = mMonthlyRule->week();
+ QValueList<KAEvent::MonthPos> poses;
+ poses.append(pos);
+ event.setRecurMonthlyByPos(frequency, poses, repeatCount, endDate);
+ }
+ else
+ {
+ // It's by day
+ int daynum = mMonthlyRule->date();
+ QValueList<int> daynums;
+ daynums.append(daynum);
+ event.setRecurMonthlyByDate(frequency, daynums, repeatCount, endDate);
+ }
+ }
+ else if (button == mYearlyButton)
+ {
+ QValueList<int> months = mYearlyRule->months();
+ if (mYearlyRule->type() == YearlyRule::POS)
+ {
+ // It's by position
+ KAEvent::MonthPos pos;
+ pos.days.fill(false);
+ pos.days.setBit(mYearlyRule->dayOfWeek() - 1);
+ pos.weeknum = mYearlyRule->week();
+ QValueList<KAEvent::MonthPos> poses;
+ poses.append(pos);
+ event.setRecurAnnualByPos(frequency, poses, months, repeatCount, endDate);
+ }
+ else
+ {
+ // It's by date in month
+ event.setRecurAnnualByDate(frequency, months, mYearlyRule->date(),
+ mYearlyRule->feb29Type(), repeatCount, endDate);
+ }
+ }
+ else
+ {
+ event.setNoRecur();
+ return;
+ }
+ if (!event.recurs())
+ return; // an error occurred setting up the recurrence
+ if (adjustStart)
+ event.setFirstRecurrence();
+
+ // Set up repetition within the recurrence.
+ // N.B. This requires the main recurrence to be set up first.
+ int count = mSubRepetition->count();
+ if (mRuleButtonType < SUBDAILY)
+ count = 0;
+ event.setRepetition(mSubRepetition->interval(), count);
+
+ // Set up exceptions
+ event.recurrence()->setExDates(mExceptionDates);
+
+ event.setUpdated();
+}
+
+/******************************************************************************
+ * Save the state of all controls.
+ */
+void RecurrenceEdit::saveState()
+{
+ mSavedRuleButton = mRuleButtonGroup->selected();
+ if (mRule)
+ mRule->saveState();
+ mSavedRangeButton = mRangeButtonGroup->selected();
+ if (mSavedRangeButton == mRepeatCountButton)
+ mSavedRecurCount = mRepeatCountEntry->value();
+ else if (mSavedRangeButton == mEndDateButton)
+ mSavedEndDateTime.set(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mEndAnyTimeCheckBox->isChecked());
+ mSavedExceptionDates = mExceptionDates;
+ mSavedRepeatInterval = mSubRepetition->interval();
+ mSavedRepeatCount = mSubRepetition->count();
+}
+
+/******************************************************************************
+ * Check whether any of the controls have changed state since initialisation.
+ */
+bool RecurrenceEdit::stateChanged() const
+{
+ if (mSavedRuleButton != mRuleButtonGroup->selected()
+ || mSavedRangeButton != mRangeButtonGroup->selected()
+ || mRule && mRule->stateChanged())
+ return true;
+ if (mSavedRangeButton == mRepeatCountButton
+ && mSavedRecurCount != mRepeatCountEntry->value())
+ return true;
+ if (mSavedRangeButton == mEndDateButton
+ && mSavedEndDateTime != DateTime(QDateTime(mEndDateEdit->date(), mEndTimeEdit->time()), mEndAnyTimeCheckBox->isChecked()))
+ return true;
+ if (mSavedExceptionDates != mExceptionDates
+ || mSavedRepeatInterval != mSubRepetition->interval()
+ || mSavedRepeatCount != mSubRepetition->count())
+ return true;
+ return false;
+}
+
+
+
+/*=============================================================================
+= Class Rule
+= Base class for rule widgets, including recurrence frequency.
+=============================================================================*/
+
+Rule::Rule(const QString& freqText, const QString& freqWhatsThis, bool time, bool readOnly, QWidget* parent, const char* name)
+ : NoRule(parent, name)
+{
+ mLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
+ QHBox* freqBox = new QHBox(this);
+ mLayout->addWidget(freqBox);
+ QHBox* box = new QHBox(freqBox); // this is to control the QWhatsThis text display area
+ box->setSpacing(KDialog::spacingHint());
+
+ QLabel* label = new QLabel(i18n("Recur e&very"), box);
+ label->setFixedSize(label->sizeHint());
+ if (time)
+ {
+ mIntSpinBox = 0;
+ mSpinBox = mTimeSpinBox = new TimeSpinBox(1, 5999, box);
+ mTimeSpinBox->setFixedSize(mTimeSpinBox->sizeHint());
+ mTimeSpinBox->setReadOnly(readOnly);
+ }
+ else
+ {
+ mTimeSpinBox = 0;
+ mSpinBox = mIntSpinBox = new SpinBox(1, 999, 1, box);
+ mIntSpinBox->setFixedSize(mIntSpinBox->sizeHint());
+ mIntSpinBox->setReadOnly(readOnly);
+ }
+ connect(mSpinBox, SIGNAL(valueChanged(int)), SIGNAL(frequencyChanged()));
+ label->setBuddy(mSpinBox);
+ label = new QLabel(freqText, box);
+ label->setFixedSize(label->sizeHint());
+ box->setFixedSize(sizeHint());
+ QWhatsThis::add(box, freqWhatsThis);
+
+ new QWidget(freqBox); // left adjust the visible widgets
+ freqBox->setFixedHeight(freqBox->sizeHint().height());
+ freqBox->setFocusProxy(mSpinBox);
+}
+
+int Rule::frequency() const
+{
+ if (mIntSpinBox)
+ return mIntSpinBox->value();
+ if (mTimeSpinBox)
+ return mTimeSpinBox->value();
+ return 0;
+}
+
+void Rule::setFrequency(int n)
+{
+ if (mIntSpinBox)
+ mIntSpinBox->setValue(n);
+ if (mTimeSpinBox)
+ mTimeSpinBox->setValue(n);
+}
+
+/******************************************************************************
+ * Save the state of all controls.
+ */
+void Rule::saveState()
+{
+ mSavedFrequency = frequency();
+}
+
+/******************************************************************************
+ * Check whether any of the controls have changed state since initialisation.
+ */
+bool Rule::stateChanged() const
+{
+ return (mSavedFrequency != frequency());
+}
+
+
+/*=============================================================================
+= Class SubDailyRule
+= Sub-daily rule widget.
+=============================================================================*/
+
+SubDailyRule::SubDailyRule(bool readOnly, QWidget* parent, const char* name)
+ : Rule(i18n("hours:minutes"),
+ i18n("Enter the number of hours and minutes between repetitions of the alarm"),
+ true, readOnly, parent, name)
+{ }
+
+
+/*=============================================================================
+= Class DayWeekRule
+= Daily/weekly rule widget base class.
+=============================================================================*/
+
+DayWeekRule::DayWeekRule(const QString& freqText, const QString& freqWhatsThis, const QString& daysWhatsThis,
+ bool readOnly, QWidget* parent, const char* name)
+ : Rule(freqText, freqWhatsThis, false, readOnly, parent, name),
+ mSavedDays(7)
+{
+ QGridLayout* grid = new QGridLayout(layout(), 1, 4, KDialog::spacingHint());
+ grid->setRowStretch(0, 1);
+
+ QLabel* label = new QLabel(i18n("On: Tuesday", "O&n:"), this);
+ label->setFixedSize(label->sizeHint());
+ grid->addWidget(label, 0, 0, Qt::AlignRight | Qt::AlignTop);
+ grid->addColSpacing(1, KDialog::spacingHint());
+
+ // List the days of the week starting at the user's start day of the week.
+ // Save the first day of the week, just in case it changes while the dialog is open.
+ QWidget* box = new QWidget(this); // this is to control the QWhatsThis text display area
+ QGridLayout* dgrid = new QGridLayout(box, 4, 2, 0, KDialog::spacingHint());
+ const KCalendarSystem* calendar = KGlobal::locale()->calendar();
+ for (int i = 0; i < 7; ++i)
+ {
+ int day = KAlarm::localeDayInWeek_to_weekDay(i);
+ mDayBox[i] = new CheckBox(calendar->weekDayName(day), box);
+ mDayBox[i]->setFixedSize(mDayBox[i]->sizeHint());
+ mDayBox[i]->setReadOnly(readOnly);
+ dgrid->addWidget(mDayBox[i], i%4, i/4, Qt::AlignAuto);
+ }
+ box->setFixedSize(box->sizeHint());
+ QWhatsThis::add(box, daysWhatsThis);
+ grid->addWidget(box, 0, 2, Qt::AlignAuto);
+ label->setBuddy(mDayBox[0]);
+ grid->setColStretch(3, 1);
+}
+
+/******************************************************************************
+ * Fetch which days of the week have been ticked.
+ */
+QBitArray DayWeekRule::days() const
+{
+ QBitArray ds(7);
+ ds.fill(false);
+ for (int i = 0; i < 7; ++i)
+ if (mDayBox[i]->isChecked())
+ ds.setBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1, 1);
+ return ds;
+}
+
+/******************************************************************************
+ * Tick/untick every day of the week.
+ */
+void DayWeekRule::setDays(bool tick)
+{
+ for (int i = 0; i < 7; ++i)
+ mDayBox[i]->setChecked(tick);
+}
+
+/******************************************************************************
+ * Tick/untick each day of the week according to the specified bits.
+ */
+void DayWeekRule::setDays(const QBitArray& days)
+{
+ for (int i = 0; i < 7; ++i)
+ {
+ bool x = days.testBit(KAlarm::localeDayInWeek_to_weekDay(i) - 1);
+ mDayBox[i]->setChecked(x);
+ }
+}
+
+/******************************************************************************
+ * Tick the specified day of the week, and untick all other days.
+ */
+void DayWeekRule::setDay(int dayOfWeek)
+{
+ for (int i = 0; i < 7; ++i)
+ mDayBox[i]->setChecked(false);
+ if (dayOfWeek > 0 && dayOfWeek <= 7)
+ mDayBox[KAlarm::weekDay_to_localeDayInWeek(dayOfWeek)]->setChecked(true);
+}
+
+/******************************************************************************
+ * Validate: check that at least one day is selected.
+ */
+QWidget* DayWeekRule::validate(QString& errorMessage)
+{
+ for (int i = 0; i < 7; ++i)
+ if (mDayBox[i]->isChecked())
+ return 0;
+ errorMessage = i18n("No day selected");
+ return mDayBox[0];
+}
+
+/******************************************************************************
+ * Save the state of all controls.
+ */
+void DayWeekRule::saveState()
+{
+ Rule::saveState();
+ mSavedDays = days();
+}
+
+/******************************************************************************
+ * Check whether any of the controls have changed state since initialisation.
+ */
+bool DayWeekRule::stateChanged() const
+{
+ return (Rule::stateChanged()
+ || mSavedDays != days());
+}
+
+
+/*=============================================================================
+= Class DailyRule
+= Daily rule widget.
+=============================================================================*/
+
+DailyRule::DailyRule(bool readOnly, QWidget* parent, const char* name)
+ : DayWeekRule(i18n("day(s)"),
+ i18n("Enter the number of days between repetitions of the alarm"),
+ i18n("Select the days of the week on which the alarm is allowed to occur"),
+ readOnly, parent, name)
+{ }
+
+
+/*=============================================================================
+= Class WeeklyRule
+= Weekly rule widget.
+=============================================================================*/
+
+WeeklyRule::WeeklyRule(bool readOnly, QWidget* parent, const char* name)
+ : DayWeekRule(i18n("week(s)"),
+ i18n("Enter the number of weeks between repetitions of the alarm"),
+ i18n("Select the days of the week on which to repeat the alarm"),
+ readOnly, parent, name)
+{ }
+
+
+/*=============================================================================
+= Class MonthYearRule
+= Monthly/yearly rule widget base class.
+=============================================================================*/
+
+MonthYearRule::MonthYearRule(const QString& freqText, const QString& freqWhatsThis, bool allowEveryWeek,
+ bool readOnly, QWidget* parent, const char* name)
+ : Rule(freqText, freqWhatsThis, false, readOnly, parent, name),
+ mEveryWeek(allowEveryWeek)
+{
+ mButtonGroup = new ButtonGroup(this);
+ mButtonGroup->hide();
+
+ // Month day selector
+ QHBox* box = new QHBox(this);
+ box->setSpacing(KDialog::spacingHint());
+ layout()->addWidget(box);
+
+ mDayButton = new RadioButton(i18n("On day number in the month", "O&n day"), box);
+ mDayButton->setFixedSize(mDayButton->sizeHint());
+ mDayButton->setReadOnly(readOnly);
+ mDayButtonId = mButtonGroup->insert(mDayButton);
+ QWhatsThis::add(mDayButton, i18n("Repeat the alarm on the selected day of the month"));
+
+ mDayCombo = new ComboBox(false, box);
+ mDayCombo->setSizeLimit(11);
+ for (int i = 0; i < 31; ++i)
+ mDayCombo->insertItem(QString::number(i + 1));
+ mDayCombo->insertItem(i18n("Last day of month", "Last"));
+ mDayCombo->setFixedSize(mDayCombo->sizeHint());
+ mDayCombo->setReadOnly(readOnly);
+ QWhatsThis::add(mDayCombo, i18n("Select the day of the month on which to repeat the alarm"));
+ mDayButton->setFocusWidget(mDayCombo);
+ connect(mDayCombo, SIGNAL(activated(int)), SLOT(slotDaySelected(int)));
+
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
+ box->setFixedHeight(box->sizeHint().height());
+
+ // Month position selector
+ box = new QHBox(this);
+ box->setSpacing(KDialog::spacingHint());
+ layout()->addWidget(box);
+
+ mPosButton = new RadioButton(i18n("On the 1st Tuesday", "On t&he"), box);
+ mPosButton->setFixedSize(mPosButton->sizeHint());
+ mPosButton->setReadOnly(readOnly);
+ mPosButtonId = mButtonGroup->insert(mPosButton);
+ QWhatsThis::add(mPosButton,
+ i18n("Repeat the alarm on one day of the week, in the selected week of the month"));
+
+ mWeekCombo = new ComboBox(false, box);
+ mWeekCombo->insertItem(i18n("1st"));
+ mWeekCombo->insertItem(i18n("2nd"));
+ mWeekCombo->insertItem(i18n("3rd"));
+ mWeekCombo->insertItem(i18n("4th"));
+ mWeekCombo->insertItem(i18n("5th"));
+ mWeekCombo->insertItem(i18n("Last Monday in March", "Last"));
+ mWeekCombo->insertItem(i18n("2nd Last"));
+ mWeekCombo->insertItem(i18n("3rd Last"));
+ mWeekCombo->insertItem(i18n("4th Last"));
+ mWeekCombo->insertItem(i18n("5th Last"));
+ if (mEveryWeek)
+ {
+ mWeekCombo->insertItem(i18n("Every (Monday...) in month", "Every"));
+ mWeekCombo->setSizeLimit(11);
+ }
+ QWhatsThis::add(mWeekCombo, i18n("Select the week of the month in which to repeat the alarm"));
+ mWeekCombo->setFixedSize(mWeekCombo->sizeHint());
+ mWeekCombo->setReadOnly(readOnly);
+ mPosButton->setFocusWidget(mWeekCombo);
+
+ mDayOfWeekCombo = new ComboBox(false, box);
+ const KCalendarSystem* calendar = KGlobal::locale()->calendar();
+ for (int i = 0; i < 7; ++i)
+ {
+ int day = KAlarm::localeDayInWeek_to_weekDay(i);
+ mDayOfWeekCombo->insertItem(calendar->weekDayName(day));
+ }
+ mDayOfWeekCombo->setReadOnly(readOnly);
+ QWhatsThis::add(mDayOfWeekCombo, i18n("Select the day of the week on which to repeat the alarm"));
+
+ box->setStretchFactor(new QWidget(box), 1); // left adjust the controls
+ box->setFixedHeight(box->sizeHint().height());
+ connect(mButtonGroup, SIGNAL(buttonSet(int)), SLOT(clicked(int)));
+}
+
+MonthYearRule::DayPosType MonthYearRule::type() const
+{
+ return (mButtonGroup->selectedId() == mDayButtonId) ? DATE : POS;
+}
+
+void MonthYearRule::setType(MonthYearRule::DayPosType type)
+{
+ mButtonGroup->setButton(type == DATE ? mDayButtonId : mPosButtonId);
+}
+
+void MonthYearRule::setDefaultValues(int dayOfMonth, int dayOfWeek)
+{
+ --dayOfMonth;
+ mDayCombo->setCurrentItem(dayOfMonth);
+ mWeekCombo->setCurrentItem(dayOfMonth / 7);
+ mDayOfWeekCombo->setCurrentItem(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
+}
+
+int MonthYearRule::date() const
+{
+ int daynum = mDayCombo->currentItem() + 1;
+ return (daynum <= 31) ? daynum : 31 - daynum;
+}
+
+int MonthYearRule::week() const
+{
+ int weeknum = mWeekCombo->currentItem() + 1;
+ return (weeknum <= 5) ? weeknum : (weeknum == 11) ? 0 : 5 - weeknum;
+}
+
+int MonthYearRule::dayOfWeek() const
+{
+ return KAlarm::localeDayInWeek_to_weekDay(mDayOfWeekCombo->currentItem());
+}
+
+void MonthYearRule::setDate(int dayOfMonth)
+{
+ mButtonGroup->setButton(mDayButtonId);
+ mDayCombo->setCurrentItem(dayOfMonth > 0 ? dayOfMonth - 1 : dayOfMonth < 0 ? 30 - dayOfMonth : 0); // day 0 shouldn't ever occur
+}
+
+void MonthYearRule::setPosition(int week, int dayOfWeek)
+{
+ mButtonGroup->setButton(mPosButtonId);
+ mWeekCombo->setCurrentItem((week > 0) ? week - 1 : (week < 0) ? 4 - week : mEveryWeek ? 10 : 0);
+ mDayOfWeekCombo->setCurrentItem(KAlarm::weekDay_to_localeDayInWeek(dayOfWeek));
+}
+
+void MonthYearRule::enableSelection(DayPosType type)
+{
+ bool date = (type == DATE);
+ mDayCombo->setEnabled(date);
+ mWeekCombo->setEnabled(!date);
+ mDayOfWeekCombo->setEnabled(!date);
+}
+
+void MonthYearRule::clicked(int id)
+{
+ enableSelection(id == mDayButtonId ? DATE : POS);
+}
+
+void MonthYearRule::slotDaySelected(int index)
+{
+ daySelected(index <= 30 ? index + 1 : 30 - index);
+}
+
+/******************************************************************************
+ * Save the state of all controls.
+ */
+void MonthYearRule::saveState()
+{
+ Rule::saveState();
+ mSavedType = type();
+ if (mSavedType == DATE)
+ mSavedDay = date();
+ else
+ {
+ mSavedWeek = week();
+ mSavedWeekDay = dayOfWeek();
+ }
+}
+
+/******************************************************************************
+ * Check whether any of the controls have changed state since initialisation.
+ */
+bool MonthYearRule::stateChanged() const
+{
+ if (Rule::stateChanged()
+ || mSavedType != type())
+ return true;
+ if (mSavedType == DATE)
+ {
+ if (mSavedDay != date())
+ return true;
+ }
+ else
+ {
+ if (mSavedWeek != week()
+ || mSavedWeekDay != dayOfWeek())
+ return true;
+ }
+ return false;
+}
+
+
+/*=============================================================================
+= Class MonthlyRule
+= Monthly rule widget.
+=============================================================================*/
+
+MonthlyRule::MonthlyRule(bool readOnly, QWidget* parent, const char* name)
+ : MonthYearRule(i18n("month(s)"),
+ i18n("Enter the number of months between repetitions of the alarm"),
+ false, readOnly, parent, name)
+{ }
+
+
+/*=============================================================================
+= Class YearlyRule
+= Yearly rule widget.
+=============================================================================*/
+
+YearlyRule::YearlyRule(bool readOnly, QWidget* parent, const char* name)
+ : MonthYearRule(i18n("year(s)"),
+ i18n("Enter the number of years between repetitions of the alarm"),
+ true, readOnly, parent, name)
+{
+ // Set up the month selection widgets
+ QBoxLayout* hlayout = new QHBoxLayout(layout(), KDialog::spacingHint());
+ QLabel* label = new QLabel(i18n("List of months to select", "Months:"), this);
+ label->setFixedSize(label->sizeHint());
+ hlayout->addWidget(label, 0, Qt::AlignAuto | Qt::AlignTop);
+
+ // List the months of the year.
+ QWidget* w = new QWidget(this); // this is to control the QWhatsThis text display area
+ hlayout->addWidget(w, 1, Qt::AlignAuto);
+ QGridLayout* grid = new QGridLayout(w, 4, 3, 0, KDialog::spacingHint());
+ const KCalendarSystem* calendar = KGlobal::locale()->calendar();
+ int year = QDate::currentDate().year();
+ for (int i = 0; i < 12; ++i)
+ {
+ mMonthBox[i] = new CheckBox(calendar->monthName(i + 1, year, true), w);
+ mMonthBox[i]->setFixedSize(mMonthBox[i]->sizeHint());
+ mMonthBox[i]->setReadOnly(readOnly);
+ grid->addWidget(mMonthBox[i], i%3, i/3, Qt::AlignAuto);
+ }
+ connect(mMonthBox[1], SIGNAL(toggled(bool)), SLOT(enableFeb29()));
+ w->setFixedHeight(w->sizeHint().height());
+ QWhatsThis::add(w, i18n("Select the months of the year in which to repeat the alarm"));
+
+ // February 29th handling option
+ QHBox* f29box = new QHBox(this);
+ layout()->addWidget(f29box);
+ QHBox* box = new QHBox(f29box); // this is to control the QWhatsThis text display area
+ box->setSpacing(KDialog::spacingHint());
+ mFeb29Label = new QLabel(i18n("February 2&9th alarm in non-leap years:"), box);
+ mFeb29Label->setFixedSize(mFeb29Label->sizeHint());
+ mFeb29Combo = new ComboBox(false, box);
+ mFeb29Combo->insertItem(i18n("No date", "None"));
+ mFeb29Combo->insertItem(i18n("1st March (short form)", "1 Mar"));
+ mFeb29Combo->insertItem(i18n("28th February (short form)", "28 Feb"));
+ mFeb29Combo->setFixedSize(mFeb29Combo->sizeHint());
+ mFeb29Combo->setReadOnly(readOnly);
+ mFeb29Label->setBuddy(mFeb29Combo);
+ box->setFixedSize(box->sizeHint());
+ QWhatsThis::add(box,
+ i18n("Select which date, if any, the February 29th alarm should trigger in non-leap years"));
+ new QWidget(f29box); // left adjust the visible widgets
+ f29box->setFixedHeight(f29box->sizeHint().height());
+}
+
+void YearlyRule::setDefaultValues(int dayOfMonth, int dayOfWeek, int month)
+{
+ MonthYearRule::setDefaultValues(dayOfMonth, dayOfWeek);
+ --month;
+ for (int i = 0; i < 12; ++i)
+ mMonthBox[i]->setChecked(i == month);
+ setFeb29Type(Preferences::defaultFeb29Type());
+ daySelected(dayOfMonth); // enable/disable month checkboxes as appropriate
+}
+
+/******************************************************************************
+ * Fetch which months have been checked (1 - 12).
+ * Reply = true if February has been checked.
+ */
+QValueList<int> YearlyRule::months() const
+{
+ QValueList<int> mnths;
+ for (int i = 0; i < 12; ++i)
+ if (mMonthBox[i]->isChecked() && mMonthBox[i]->isEnabled())
+ mnths.append(i + 1);
+ return mnths;
+}
+
+/******************************************************************************
+ * Check/uncheck each month of the year according to the specified list.
+ */
+void YearlyRule::setMonths(const QValueList<int>& mnths)
+{
+ bool checked[12];
+ for (int i = 0; i < 12; ++i)
+ checked[i] = false;
+ for (QValueListConstIterator<int> it = mnths.begin(); it != mnths.end(); ++it)
+ checked[(*it) - 1] = true;
+ for (int i = 0; i < 12; ++i)
+ mMonthBox[i]->setChecked(checked[i]);
+ enableFeb29();
+}
+
+/******************************************************************************
+ * Return the date for February 29th alarms in non-leap years.
+ */
+KARecurrence::Feb29Type YearlyRule::feb29Type() const
+{
+ if (mFeb29Combo->isEnabled())
+ {
+ switch (mFeb29Combo->currentItem())
+ {
+ case 1: return KARecurrence::FEB29_MAR1;
+ case 2: return KARecurrence::FEB29_FEB28;
+ default: break;
+ }
+ }
+ return KARecurrence::FEB29_FEB29;
+}
+
+/******************************************************************************
+ * Set the date for February 29th alarms to trigger in non-leap years.
+ */
+void YearlyRule::setFeb29Type(KARecurrence::Feb29Type type)
+{
+ int index;
+ switch (type)
+ {
+ default:
+ case KARecurrence::FEB29_FEB29: index = 0; break;
+ case KARecurrence::FEB29_MAR1: index = 1; break;
+ case KARecurrence::FEB29_FEB28: index = 2; break;
+ }
+ mFeb29Combo->setCurrentItem(index);
+}
+
+/******************************************************************************
+ * Validate: check that at least one month is selected.
+ */
+QWidget* YearlyRule::validate(QString& errorMessage)
+{
+ for (int i = 0; i < 12; ++i)
+ if (mMonthBox[i]->isChecked() && mMonthBox[i]->isEnabled())
+ return 0;
+ errorMessage = i18n("No month selected");
+ return mMonthBox[0];
+}
+
+/******************************************************************************
+ * Called when a yearly recurrence type radio button is clicked,
+ * to enable/disable month checkboxes as appropriate for the date selected.
+ */
+void YearlyRule::clicked(int id)
+{
+ MonthYearRule::clicked(id);
+ daySelected(buttonType(id) == DATE ? date() : 1);
+}
+
+/******************************************************************************
+ * Called when a day of the month is selected in a yearly recurrence, to
+ * disable months for which the day is out of range.
+ */
+void YearlyRule::daySelected(int day)
+{
+ mMonthBox[1]->setEnabled(day <= 29); // February
+ bool enable = (day != 31);
+ mMonthBox[3]->setEnabled(enable); // April
+ mMonthBox[5]->setEnabled(enable); // June
+ mMonthBox[8]->setEnabled(enable); // September
+ mMonthBox[10]->setEnabled(enable); // November
+ enableFeb29();
+}
+
+/******************************************************************************
+ * Enable/disable the February 29th combo box depending on whether February
+ * 29th is selected.
+ */
+void YearlyRule::enableFeb29()
+{
+ bool enable = (type() == DATE && date() == 29 && mMonthBox[1]->isChecked() && mMonthBox[1]->isEnabled());
+ mFeb29Label->setEnabled(enable);
+ mFeb29Combo->setEnabled(enable);
+}
+
+/******************************************************************************
+ * Save the state of all controls.
+ */
+void YearlyRule::saveState()
+{
+ MonthYearRule::saveState();
+ mSavedMonths = months();
+ mSavedFeb29Type = feb29Type();
+}
+
+/******************************************************************************
+ * Check whether any of the controls have changed state since initialisation.
+ */
+bool YearlyRule::stateChanged() const
+{
+ return (MonthYearRule::stateChanged()
+ || mSavedMonths != months()
+ || mSavedFeb29Type != feb29Type());
+}
diff --git a/kalarm/recurrenceedit.h b/kalarm/recurrenceedit.h
new file mode 100644
index 00000000..f366978d
--- /dev/null
+++ b/kalarm/recurrenceedit.h
@@ -0,0 +1,191 @@
+/*
+ * recurrenceedit.h - widget to edit the event's recurrence definition
+ * Program: kalarm
+ * Copyright © 2002-2005,2008 by David Jarvie <djarvie@kde.org>
+ *
+ * Based originally on KOrganizer module koeditorrecurrence.h,
+ * Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@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.
+ */
+
+#ifndef RECURRENCEEDIT_H
+#define RECURRENCEEDIT_H
+
+#include <qframe.h>
+#include <qdatetime.h>
+#include <qvaluelist.h>
+
+#include "datetime.h"
+class QWidgetStack;
+class QGroupBox;
+class QLabel;
+class QListBox;
+class QButton;
+class QPushButton;
+class QBoxLayout;
+class SpinBox;
+class CheckBox;
+class RadioButton;
+class DateEdit;
+class TimeEdit;
+class ButtonGroup;
+class RepetitionButton;
+class KAEvent;
+class Rule;
+class NoRule;
+class SubDailyRule;
+class DailyRule;
+class WeeklyRule;
+class MonthlyRule;
+class YearlyRule;
+
+
+class RecurrenceEdit : public QFrame
+{
+ Q_OBJECT
+ public:
+ // Don't alter the order of these recurrence types
+ enum RepeatType { INVALID_RECUR = -1, NO_RECUR, AT_LOGIN, SUBDAILY, DAILY, WEEKLY, MONTHLY, ANNUAL };
+
+ RecurrenceEdit(bool readOnly, QWidget* parent, const char* name = 0);
+ virtual ~RecurrenceEdit() { }
+
+ /** Set widgets to default values */
+ void setDefaults(const QDateTime& from);
+ /** Initialise according to a specified event */
+ void set(const KAEvent&, bool keepDuration);
+ /** Write recurrence settings into an event */
+ void updateEvent(KAEvent&, bool adjustStart);
+ QWidget* checkData(const QDateTime& startDateTime, QString& errorMessage) const;
+ RepeatType repeatType() const { return mRuleButtonType; }
+ bool isTimedRepeatType() const { return mRuleButtonType >= SUBDAILY; }
+ int subRepeatCount(int* subRepeatInterval = 0) const;
+ void setSubRepetition(int reminderMinutes, bool dateOnly);
+ void setStartDate(const QDate&, const QDate& today);
+ void setDefaultEndDate(const QDate&);
+ void setEndDateTime(const DateTime&);
+ DateTime endDateTime() const;
+ bool stateChanged() const;
+ void activateSubRepetition();
+
+ static QString i18n_Norecur(); // text of 'No recurrence' selection, lower case
+ static QString i18n_NoRecur(); // text of 'No Recurrence' selection, initial capitals
+ static QString i18n_AtLogin(); // text of 'At Login' selection
+ static QString i18n_l_Atlogin(); // text of 'At &login' selection, with 'L' shortcut
+ static QString i18n_HourlyMinutely(); // text of 'Hourly/Minutely'
+ static QString i18n_u_HourlyMinutely(); // text of 'Ho&urly/Minutely' selection, with 'U' shortcut
+ static QString i18n_Daily(); // text of 'Daily' selection
+ static QString i18n_d_Daily(); // text of '&Daily' selection, with 'D' shortcut
+ static QString i18n_Weekly(); // text of 'Weekly' selection
+ static QString i18n_w_Weekly(); // text of '&Weekly' selection, with 'W' shortcut
+ static QString i18n_Monthly(); // text of 'Monthly' selection
+ static QString i18n_m_Monthly(); // text of '&Monthly' selection, with 'M' shortcut
+ static QString i18n_Yearly(); // text of 'Yearly' selection
+ static QString i18n_y_Yearly(); // text of '&Yearly' selection, with 'Y' shortcut
+
+ public slots:
+ void setDateTime(const QDateTime& start) { mCurrStartDateTime = start; }
+
+ signals:
+ void shown();
+ void typeChanged(int recurType); // returns a RepeatType value
+ void frequencyChanged();
+ void repeatNeedsInitialisation();
+
+ protected:
+ virtual void showEvent(QShowEvent*);
+
+ private slots:
+ void periodClicked(int);
+ void rangeTypeClicked();
+ void repeatCountChanged(int value);
+ void slotAnyTimeToggled(bool);
+ void addException();
+ void changeException();
+ void deleteException();
+ void enableExceptionButtons();
+
+ private:
+ void setRuleDefaults(const QDate& start);
+ void saveState();
+
+ // Main rule box and choices
+ QWidgetStack* mRuleStack;
+ Rule* mRule; // current rule widget, or 0 if NoRule
+ NoRule* mNoRule;
+ SubDailyRule* mSubDailyRule;
+ DailyRule* mDailyRule;
+ WeeklyRule* mWeeklyRule;
+ MonthlyRule* mMonthlyRule;
+ YearlyRule* mYearlyRule;
+
+ ButtonGroup* mRuleButtonGroup;
+ RadioButton* mNoneButton;
+ RadioButton* mAtLoginButton;
+ RadioButton* mSubDailyButton;
+ RadioButton* mDailyButton;
+ RadioButton* mWeeklyButton;
+ RadioButton* mMonthlyButton;
+ RadioButton* mYearlyButton;
+ int mNoneButtonId;
+ int mAtLoginButtonId;
+ int mSubDailyButtonId;
+ int mDailyButtonId;
+ int mWeeklyButtonId;
+ int mMonthlyButtonId;
+ int mYearlyButtonId;
+ RepeatType mRuleButtonType;
+ bool mDailyShown; // daily rule has been displayed at some time or other
+ bool mWeeklyShown; // weekly rule has been displayed at some time or other
+ bool mMonthlyShown; // monthly rule has been displayed at some time or other
+ bool mYearlyShown; // yearly rule has been displayed at some time or other
+
+ // Range
+ ButtonGroup* mRangeButtonGroup;
+ RadioButton* mNoEndDateButton;
+ RadioButton* mRepeatCountButton;
+ SpinBox* mRepeatCountEntry;
+ QLabel* mRepeatCountLabel;
+ RadioButton* mEndDateButton;
+ DateEdit* mEndDateEdit;
+ TimeEdit* mEndTimeEdit;
+ CheckBox* mEndAnyTimeCheckBox;
+
+ // Exceptions
+ QGroupBox* mExceptionGroup;
+ QListBox* mExceptionDateList;
+ DateEdit* mExceptionDateEdit;
+ QPushButton* mChangeExceptionButton;
+ QPushButton* mDeleteExceptionButton;
+ QValueList<QDate> mExceptionDates;
+
+ // Current start date and time
+ QDateTime mCurrStartDateTime;
+ RepetitionButton* mSubRepetition;
+ bool mNoEmitTypeChanged; // suppress typeChanged() signal
+ bool mReadOnly;
+
+ // Initial state of non-rule controls
+ QButton* mSavedRuleButton; // which rule button was selected
+ QButton* mSavedRangeButton; // which range button was selected
+ int mSavedRecurCount; // recurrence repeat count
+ DateTime mSavedEndDateTime; // end date/time
+ QValueList<QDate> mSavedExceptionDates; // exception dates
+ int mSavedRepeatInterval; // sub-repetition interval (via mSubRepetition button)
+ int mSavedRepeatCount; // sub-repetition count (via mSubRepetition button)
+};
+
+#endif // RECURRENCEEDIT_H
diff --git a/kalarm/recurrenceeditprivate.h b/kalarm/recurrenceeditprivate.h
new file mode 100644
index 00000000..1210f95c
--- /dev/null
+++ b/kalarm/recurrenceeditprivate.h
@@ -0,0 +1,196 @@
+/*
+ * recurrenceeditprivate.h - private classes for recurrenceedit.cpp
+ * Program: kalarm
+ * Copyright © 2003,2005,2007 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.
+ */
+
+#ifndef RECURRENCEEDITPRIVATE_H
+#define RECURRENCEEDITPRIVATE_H
+
+#include <qframe.h>
+#include <qvaluelist.h>
+#include <qbitarray.h>
+
+class QWidget;
+class QVBoxLayout;
+class ButtonGroup;
+class RadioButton;
+class ComboBox;
+class CheckBox;
+class SpinBox;
+class TimeSpinBox;
+class QString;
+
+
+class NoRule : public QFrame
+{
+ public:
+ NoRule(QWidget* parent, const char* name = 0) : QFrame(parent, name)
+ { setFrameStyle(QFrame::NoFrame); }
+ virtual int frequency() const { return 0; }
+};
+
+class Rule : public NoRule
+{
+ Q_OBJECT
+ public:
+ Rule(const QString& freqText, const QString& freqWhatsThis, bool time, bool readOnly,
+ QWidget* parent, const char* name = 0);
+ int frequency() const;
+ void setFrequency(int);
+ virtual void setFrequencyFocus() { mSpinBox->setFocus(); }
+ QVBoxLayout* layout() const { return mLayout; }
+ virtual QWidget* validate(QString&) { return 0; }
+ virtual void saveState();
+ virtual bool stateChanged() const;
+ signals:
+ void frequencyChanged();
+ private:
+ QWidget* mSpinBox;
+ SpinBox* mIntSpinBox;
+ TimeSpinBox* mTimeSpinBox;
+ QVBoxLayout* mLayout;
+ // Saved state of all controls
+ int mSavedFrequency; // frequency for the selected rule
+};
+
+// Subdaily rule choices
+class SubDailyRule : public Rule
+{
+ Q_OBJECT
+ public:
+ SubDailyRule(bool readOnly, QWidget* parent, const char* name = 0);
+};
+
+// Daily/weekly rule choices base class
+class DayWeekRule : public Rule
+{
+ Q_OBJECT
+ public:
+ DayWeekRule(const QString& freqText, const QString& freqWhatsThis, const QString& daysWhatsThis,
+ bool readOnly, QWidget* parent, const char* name = 0);
+ QBitArray days() const;
+ void setDays(bool);
+ void setDays(const QBitArray& days);
+ void setDay(int dayOfWeek);
+ virtual QWidget* validate(QString& errorMessage);
+ virtual void saveState();
+ virtual bool stateChanged() const;
+ private:
+ CheckBox* mDayBox[7];
+ // Saved state of all controls
+ QBitArray mSavedDays; // ticked days for weekly rule
+};
+
+// Daily rule choices
+class DailyRule : public DayWeekRule
+{
+ public:
+ DailyRule(bool readOnly, QWidget* parent, const char* name = 0);
+};
+
+// Weekly rule choices
+class WeeklyRule : public DayWeekRule
+{
+ public:
+ WeeklyRule(bool readOnly, QWidget* parent, const char* name = 0);
+};
+
+// Monthly/yearly rule choices base class
+class MonthYearRule : public Rule
+{
+ Q_OBJECT
+ public:
+ enum DayPosType { DATE, POS };
+
+ MonthYearRule(const QString& freqText, const QString& freqWhatsThis, bool allowEveryWeek,
+ bool readOnly, QWidget* parent, const char* name = 0);
+ DayPosType type() const;
+ int date() const; // if date in month is selected
+ int week() const; // if position is selected
+ int dayOfWeek() const; // if position is selected
+ void setType(DayPosType);
+ void setDate(int dayOfMonth);
+ void setPosition(int week, int dayOfWeek);
+ void setDefaultValues(int dayOfMonth, int dayOfWeek);
+ virtual void saveState();
+ virtual bool stateChanged() const;
+ signals:
+ void typeChanged(DayPosType);
+ protected:
+ DayPosType buttonType(int id) const { return id == mDayButtonId ? DATE : POS; }
+ virtual void daySelected(int /*day*/) { }
+ protected slots:
+ virtual void clicked(int id);
+ private slots:
+ virtual void slotDaySelected(int index);
+ private:
+ void enableSelection(DayPosType);
+
+ ButtonGroup* mButtonGroup;
+ RadioButton* mDayButton;
+ RadioButton* mPosButton;
+ ComboBox* mDayCombo;
+ ComboBox* mWeekCombo;
+ ComboBox* mDayOfWeekCombo;
+ int mDayButtonId;
+ int mPosButtonId;
+ bool mEveryWeek; // "Every" week is allowed
+ // Saved state of all controls
+ int mSavedType; // whether day-of-month or month position radio button was selected
+ int mSavedDay; // chosen day of month selected item
+ int mSavedWeek; // chosen month position: selected week item
+ int mSavedWeekDay; // chosen month position: selected day of week
+};
+
+// Monthly rule choices
+class MonthlyRule : public MonthYearRule
+{
+ public:
+ MonthlyRule(bool readOnly, QWidget* parent, const char* name = 0);
+};
+
+// Yearly rule choices
+class YearlyRule : public MonthYearRule
+{
+ Q_OBJECT
+ public:
+ YearlyRule(bool readOnly, QWidget* parent, const char* name = 0);
+ QValueList<int> months() const;
+ void setMonths(const QValueList<int>& months);
+ void setDefaultValues(int dayOfMonth, int dayOfWeek, int month);
+ KARecurrence::Feb29Type feb29Type() const;
+ void setFeb29Type(KARecurrence::Feb29Type);
+ virtual QWidget* validate(QString& errorMessage);
+ virtual void saveState();
+ virtual bool stateChanged() const;
+ protected:
+ virtual void daySelected(int day);
+ protected slots:
+ virtual void clicked(int id);
+ private slots:
+ void enableFeb29();
+ private:
+ CheckBox* mMonthBox[12];
+ QLabel* mFeb29Label;
+ ComboBox* mFeb29Combo;
+ // Saved state of all controls
+ QValueList<int> mSavedMonths; // ticked months for yearly rule
+ int mSavedFeb29Type; // February 29th recurrence type
+};
+
+#endif // RECURRENCEEDITPRIVATE_H
diff --git a/kalarm/reminder.cpp b/kalarm/reminder.cpp
new file mode 100644
index 00000000..f31a2d0d
--- /dev/null
+++ b/kalarm/reminder.cpp
@@ -0,0 +1,159 @@
+/*
+ * reminder.cpp - reminder setting widget
+ * Program: kalarm
+ * Copyright (C) 2003 - 2005 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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kdialog.h>
+#include <kdebug.h>
+
+#include "preferences.h"
+#include "checkbox.h"
+#include "timeselector.h"
+#include "reminder.moc"
+
+
+// Collect these widget labels together to ensure consistent wording and
+// translations across different modules.
+QString Reminder::i18n_first_recurrence_only() { return i18n("Reminder for first recurrence only"); }
+QString Reminder::i18n_u_first_recurrence_only() { return i18n("Reminder for first rec&urrence only"); }
+
+
+Reminder::Reminder(const QString& caption, const QString& reminderWhatsThis, const QString& valueWhatsThis,
+ bool allowHourMinute, bool showOnceOnly, QWidget* parent, const char* name)
+ : QFrame(parent, name),
+ mReadOnly(false),
+ mOnceOnlyEnabled(showOnceOnly)
+{
+ setFrameStyle(QFrame::NoFrame);
+ QVBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
+
+ mTime = new TimeSelector(caption, i18n("in advance"), reminderWhatsThis,
+ valueWhatsThis, allowHourMinute, this, "timeOption");
+ mTime->setFixedSize(mTime->sizeHint());
+ connect(mTime, SIGNAL(toggled(bool)), SLOT(slotReminderToggled(bool)));
+ topLayout->addWidget(mTime);
+
+ if (showOnceOnly)
+ {
+ QBoxLayout* layout = new QHBoxLayout(topLayout, KDialog::spacingHint());
+ layout->addSpacing(3*KDialog::spacingHint());
+ mOnceOnly = new CheckBox(i18n_u_first_recurrence_only(), this);
+ mOnceOnly->setFixedSize(mOnceOnly->sizeHint());
+ QWhatsThis::add(mOnceOnly, i18n("Display the reminder only before the first time the alarm is scheduled"));
+ layout->addWidget(mOnceOnly);
+ layout->addStretch();
+ }
+ else
+ mOnceOnly = 0;
+}
+
+/******************************************************************************
+* Set the read-only status.
+*/
+void Reminder::setReadOnly(bool ro)
+{
+ if ((int)ro != (int)mReadOnly)
+ {
+ mReadOnly = ro;
+ mTime->setReadOnly(mReadOnly);
+ if (mOnceOnly)
+ mOnceOnly->setReadOnly(mReadOnly);
+ }
+}
+
+bool Reminder::isReminder() const
+{
+ return mTime->isChecked();
+}
+
+bool Reminder::isOnceOnly() const
+{
+ return mOnceOnly && mOnceOnly->isEnabled() && mOnceOnly->isChecked();
+}
+
+void Reminder::setOnceOnly(bool onceOnly)
+{
+ if (mOnceOnly)
+ mOnceOnly->setChecked(onceOnly);
+}
+
+/******************************************************************************
+* Specify whether the once-only checkbox is allowed to be enabled.
+*/
+void Reminder::enableOnceOnly(bool enable)
+{
+ if (mOnceOnly)
+ {
+ mOnceOnlyEnabled = enable;
+ mOnceOnly->setEnabled(enable && mTime->isChecked());
+ }
+}
+
+void Reminder::setMaximum(int hourmin, int days)
+{
+ mTime->setMaximum(hourmin, days);
+}
+
+/******************************************************************************
+ * Get the specified number of minutes in advance of the main alarm the
+ * reminder is to be.
+ */
+int Reminder::minutes() const
+{
+ return mTime->minutes();
+}
+
+/******************************************************************************
+* Initialise the controls with a specified reminder time.
+*/
+void Reminder::setMinutes(int minutes, bool dateOnly)
+{
+ mTime->setMinutes(minutes, dateOnly, Preferences::defaultReminderUnits());
+}
+
+/******************************************************************************
+* Set the advance reminder units to days if "Any time" is checked.
+*/
+void Reminder::setDateOnly(bool dateOnly)
+{
+ mTime->setDateOnly(dateOnly);
+}
+
+/******************************************************************************
+* Set the input focus on the count field.
+*/
+void Reminder::setFocusOnCount()
+{
+ mTime->setFocusOnCount();
+}
+
+/******************************************************************************
+* Called when the Reminder checkbox is toggled.
+*/
+void Reminder::slotReminderToggled(bool on)
+{
+ if (mOnceOnly)
+ mOnceOnly->setEnabled(on && mOnceOnlyEnabled);
+}
diff --git a/kalarm/reminder.h b/kalarm/reminder.h
new file mode 100644
index 00000000..1403621d
--- /dev/null
+++ b/kalarm/reminder.h
@@ -0,0 +1,60 @@
+/*
+ * reminder.h - reminder setting widget
+ * Program: kalarm
+ * Copyright (C) 2003, 2004 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.
+ */
+
+#ifndef REMINDER_H
+#define REMINDER_H
+
+#include <qframe.h>
+
+class TimeSelector;
+class CheckBox;
+
+
+class Reminder : public QFrame
+{
+ Q_OBJECT
+ public:
+ Reminder(const QString& caption, const QString& reminderWhatsThis, const QString& valueWhatsThis,
+ bool allowHourMinute, bool showOnceOnly, QWidget* parent, const char* name = 0);
+ bool isReminder() const;
+ bool isOnceOnly() const;
+ int minutes() const;
+ void setMinutes(int minutes, bool dateOnly);
+ void setReadOnly(bool);
+ void setDateOnly(bool dateOnly);
+ void setMaximum(int hourmin, int days);
+ void setFocusOnCount();
+ void setOnceOnly(bool);
+ void enableOnceOnly(bool enable);
+
+ static QString i18n_first_recurrence_only(); // plain text of 'Reminder for first recurrence only' checkbox
+ static QString i18n_u_first_recurrence_only(); // text of 'Reminder for first recurrence only' checkbox, with 'u' shortcut
+
+ protected slots:
+ void slotReminderToggled(bool);
+
+ private:
+ TimeSelector* mTime;
+ CheckBox* mOnceOnly;
+ bool mReadOnly; // the widget is read only
+ bool mOnceOnlyEnabled; // 'mOnceOnly' checkbox is allowed to be enabled
+};
+
+#endif // REMINDER_H
diff --git a/kalarm/repetition.cpp b/kalarm/repetition.cpp
new file mode 100644
index 00000000..dc6613ee
--- /dev/null
+++ b/kalarm/repetition.cpp
@@ -0,0 +1,364 @@
+/*
+ * repetition.cpp - pushbutton and dialogue to specify alarm sub-repetition
+ * Program: kalarm
+ * Copyright © 2004,2005,2007,2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <kdebug.h>
+#include <kdialog.h>
+#include <klocale.h>
+
+#include "buttongroup.h"
+#include "radiobutton.h"
+#include "spinbox.h"
+#include "timeperiod.h"
+#include "timeselector.h"
+#include "repetition.moc"
+
+
+/*=============================================================================
+= Class RepetitionButton
+= Button to display the Alarm Sub-Repetition dialogue.
+=============================================================================*/
+
+RepetitionButton::RepetitionButton(const QString& caption, bool waitForInitialisation, QWidget* parent, const char* name)
+ : QPushButton(caption, parent, name),
+ mDialog(0),
+ mInterval(0),
+ mCount(0),
+ mMaxDuration(-1),
+ mDateOnly(false),
+ mWaitForInit(waitForInitialisation),
+ mReadOnly(false)
+{
+ setToggleButton(true);
+ setOn(false);
+ connect(this, SIGNAL(clicked()), SLOT(slotPressed()));
+}
+
+void RepetitionButton::set(int interval, int count)
+{
+ mInterval = interval;
+ mCount = count;
+ setOn(mInterval && mCount);
+}
+
+/******************************************************************************
+* Set the data for the dialog.
+*/
+void RepetitionButton::set(int interval, int count, bool dateOnly, int maxDuration)
+{
+ mInterval = interval;
+ mCount = count;
+ mMaxDuration = maxDuration;
+ mDateOnly = dateOnly;
+ setOn(mInterval && mCount);
+}
+
+/******************************************************************************
+* Create the alarm repetition dialog.
+* If 'waitForInitialisation' is true, the dialog won't be displayed until set()
+* is called to initialise its data.
+*/
+void RepetitionButton::activate(bool waitForInitialisation)
+{
+ if (!mDialog)
+ mDialog = new RepetitionDlg(i18n("Alarm Sub-Repetition"), mReadOnly, this);
+ mDialog->set(mInterval, mCount, mDateOnly, mMaxDuration);
+ if (waitForInitialisation)
+ emit needsInitialisation(); // request dialog initialisation
+ else
+ displayDialog(); // display the dialog now
+}
+
+/******************************************************************************
+* Set the data for the dialog and display it.
+* To be called only after needsInitialisation() has been emitted.
+*/
+void RepetitionButton::initialise(int interval, int count, bool dateOnly, int maxDuration)
+{
+ if (maxDuration > 0 && interval > maxDuration)
+ count = 0;
+ mCount = count;
+ mInterval = interval;
+ mMaxDuration = maxDuration;
+ mDateOnly = dateOnly;
+ if (mDialog)
+ {
+ mDialog->set(interval, count, dateOnly, maxDuration);
+ displayDialog(); // display the dialog now
+ }
+ else
+ setOn(mInterval && mCount);
+}
+
+/******************************************************************************
+* Display the alarm 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 RepetitionButton::displayDialog()
+{
+ kdDebug(5950) << "RepetitionButton::displayDialog()" << endl;
+ bool change = false;
+ if (mReadOnly)
+ {
+ mDialog->setReadOnly(true);
+ mDialog->exec();
+ }
+ else if (mDialog->exec() == QDialog::Accepted)
+ {
+ mCount = mDialog->count();
+ mInterval = mDialog->interval();
+ change = true;
+ }
+ setOn(mInterval && mCount);
+ delete mDialog;
+ mDialog = 0;
+ if (change)
+ emit changed(); // delete dialog first, or initialise() will redisplay dialog
+}
+
+
+/*=============================================================================
+= Class RepetitionDlg
+= Alarm sub-repetition dialogue.
+=============================================================================*/
+
+static const int MAX_COUNT = 9999; // maximum range for count spinbox
+
+
+RepetitionDlg::RepetitionDlg(const QString& caption, bool readOnly, QWidget* parent, const char* name)
+ : KDialogBase(parent, name, true, caption, Ok|Cancel),
+ mMaxDuration(-1),
+ mDateOnly(false),
+ mReadOnly(readOnly)
+{
+ int spacing = spacingHint();
+ QWidget* page = new QWidget(this);
+ setMainWidget(page);
+ QVBoxLayout* topLayout = new QVBoxLayout(page, 0, spacing);
+
+ mTimeSelector = new TimeSelector(i18n("Repeat every 10 minutes", "&Repeat every"), QString::null,
+ i18n("Instead of the alarm triggering just once at each recurrence, "
+ "checking this option makes the alarm trigger multiple times at each recurrence."),
+ i18n("Enter the time between repetitions of the alarm"),
+ true, page);
+ mTimeSelector->setFixedSize(mTimeSelector->sizeHint());
+ connect(mTimeSelector, SIGNAL(valueChanged(int)), SLOT(intervalChanged(int)));
+ connect(mTimeSelector, SIGNAL(toggled(bool)), SLOT(repetitionToggled(bool)));
+ topLayout->addWidget(mTimeSelector, 0, Qt::AlignAuto);
+
+ mButtonGroup = new ButtonGroup(page, "buttonGroup");
+ connect(mButtonGroup, SIGNAL(buttonSet(int)), SLOT(typeClicked()));
+ topLayout->addWidget(mButtonGroup);
+
+ QBoxLayout* vlayout = new QVBoxLayout(mButtonGroup, marginHint(), spacing);
+ QBoxLayout* layout = new QHBoxLayout(vlayout, spacing);
+ mCountButton = new RadioButton(i18n("&Number of repetitions:"), mButtonGroup);
+ mCountButton->setFixedSize(mCountButton->sizeHint());
+ QWhatsThis::add(mCountButton,
+ i18n("Check to specify the number of times the alarm should repeat after each recurrence"));
+ layout->addWidget(mCountButton);
+ mCount = new SpinBox(1, MAX_COUNT, 1, mButtonGroup);
+ mCount->setFixedSize(mCount->sizeHint());
+ mCount->setLineShiftStep(10);
+ mCount->setSelectOnStep(false);
+ connect(mCount, SIGNAL(valueChanged(int)), SLOT(countChanged(int)));
+ QWhatsThis::add(mCount,
+ i18n("Enter the number of times to trigger the alarm after its initial occurrence"));
+ layout->addWidget(mCount);
+ mCountButton->setFocusWidget(mCount);
+ layout->addStretch();
+
+ layout = new QHBoxLayout(vlayout, spacing);
+ mDurationButton = new RadioButton(i18n("&Duration:"), mButtonGroup);
+ mDurationButton->setFixedSize(mDurationButton->sizeHint());
+ QWhatsThis::add(mDurationButton,
+ i18n("Check to specify how long the alarm is to be repeated"));
+ layout->addWidget(mDurationButton);
+ mDuration = new TimePeriod(true, mButtonGroup);
+ mDuration->setFixedSize(mDuration->sizeHint());
+ connect(mDuration, SIGNAL(valueChanged(int)), SLOT(durationChanged(int)));
+ QWhatsThis::add(mDuration,
+ i18n("Enter the length of time to repeat the alarm"));
+ layout->addWidget(mDuration);
+ mDurationButton->setFocusWidget(mDuration);
+ layout->addStretch();
+
+ mCountButton->setChecked(true);
+ repetitionToggled(false);
+ setReadOnly(mReadOnly);
+}
+
+/******************************************************************************
+* Set the state of all controls to reflect the data in the specified alarm.
+*/
+void RepetitionDlg::set(int interval, int count, bool dateOnly, int maxDuration)
+{
+ if (!interval)
+ count = 0;
+ else if (!count)
+ interval = 0;
+ if (dateOnly != mDateOnly)
+ {
+ mDateOnly = dateOnly;
+ mTimeSelector->setDateOnly(mDateOnly);
+ mDuration->setDateOnly(mDateOnly);
+ }
+ mMaxDuration = maxDuration;
+ if (mMaxDuration)
+ {
+ int maxhm = (mMaxDuration > 0) ? mMaxDuration : 9999;
+ int maxdw = (mMaxDuration > 0) ? mMaxDuration / 1440 : 9999;
+ mTimeSelector->setMaximum(maxhm, maxdw);
+ mDuration->setMaximum(maxhm, maxdw);
+ }
+ // Set the units - needed later if the control is unchecked initially.
+ TimePeriod::Units units = mDateOnly ? TimePeriod::DAYS : TimePeriod::HOURS_MINUTES;
+ mTimeSelector->setMinutes(interval, mDateOnly, units);
+ if (!mMaxDuration || !count)
+ mTimeSelector->setChecked(false);
+ else
+ {
+ bool on = mTimeSelector->isChecked();
+ repetitionToggled(on); // enable/disable controls
+ if (on)
+ intervalChanged(interval); // ensure mCount range is set
+ mCount->setValue(count);
+ mDuration->setMinutes(count * interval, mDateOnly, units);
+ mCountButton->setChecked(true);
+ }
+ mTimeSelector->setEnabled(mMaxDuration);
+}
+
+/******************************************************************************
+* Set the read-only status.
+*/
+void RepetitionDlg::setReadOnly(bool ro)
+{
+ ro = ro || mReadOnly;
+ mTimeSelector->setReadOnly(ro);
+ mCountButton->setReadOnly(ro);
+ mCount->setReadOnly(ro);
+ mDurationButton->setReadOnly(ro);
+ mDuration->setReadOnly(ro);
+}
+
+/******************************************************************************
+* Get the period between repetitions in minutes.
+*/
+int RepetitionDlg::interval() const
+{
+ return mTimeSelector->minutes();
+}
+
+/******************************************************************************
+* Set the entered repeat count.
+*/
+int RepetitionDlg::count() const
+{
+ int interval = mTimeSelector->minutes();
+ if (interval)
+ {
+ if (mCountButton->isOn())
+ return mCount->value();
+ if (mDurationButton->isOn())
+ return mDuration->minutes() / interval;
+ }
+ return 0; // no repetition
+}
+
+/******************************************************************************
+* Called when the time interval widget has changed value.
+* Adjust the maximum repetition count accordingly.
+*/
+void RepetitionDlg::intervalChanged(int minutes)
+{
+ if (mTimeSelector->isChecked() && minutes > 0)
+ {
+ mCount->setRange(1, (mMaxDuration >= 0 ? mMaxDuration / minutes : MAX_COUNT));
+ if (mCountButton->isOn())
+ countChanged(mCount->value());
+ else
+ durationChanged(mDuration->minutes());
+ }
+}
+
+/******************************************************************************
+* Called when the count spinbox has changed value.
+* Adjust the duration accordingly.
+*/
+void RepetitionDlg::countChanged(int count)
+{
+ int interval = mTimeSelector->minutes();
+ if (interval)
+ {
+ bool blocked = mDuration->signalsBlocked();
+ mDuration->blockSignals(true);
+ mDuration->setMinutes(count * interval, mDateOnly,
+ (mDateOnly ? TimePeriod::DAYS : TimePeriod::HOURS_MINUTES));
+ mDuration->blockSignals(blocked);
+ }
+}
+
+/******************************************************************************
+* Called when the duration widget has changed value.
+* Adjust the count accordingly.
+*/
+void RepetitionDlg::durationChanged(int minutes)
+{
+ int interval = mTimeSelector->minutes();
+ if (interval)
+ {
+ bool blocked = mCount->signalsBlocked();
+ mCount->blockSignals(true);
+ mCount->setValue(minutes / interval);
+ mCount->blockSignals(blocked);
+ }
+}
+
+/******************************************************************************
+* Called when the time period widget is toggled on or off.
+*/
+void RepetitionDlg::repetitionToggled(bool on)
+{
+ if (mMaxDuration == 0)
+ on = false;
+ mButtonGroup->setEnabled(on);
+ mCount->setEnabled(on && mCountButton->isOn());
+ mDuration->setEnabled(on && mDurationButton->isOn());
+}
+
+/******************************************************************************
+* Called when one of the count or duration radio buttons is toggled.
+*/
+void RepetitionDlg::typeClicked()
+{
+ if (mTimeSelector->isChecked())
+ {
+ mCount->setEnabled(mCountButton->isOn());
+ mDuration->setEnabled(mDurationButton->isOn());
+ }
+}
diff --git a/kalarm/repetition.h b/kalarm/repetition.h
new file mode 100644
index 00000000..64837b87
--- /dev/null
+++ b/kalarm/repetition.h
@@ -0,0 +1,99 @@
+/*
+ * repetition.h - pushbutton and dialogue to specify alarm repetition
+ * Program: kalarm
+ * Copyright © 2004,2007 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.
+ */
+
+#ifndef REPETITION_H
+#define REPETITION_H
+
+#include <qpushbutton.h>
+#include <kdialogbase.h>
+
+class ButtonGroup;
+class RadioButton;
+class SpinBox;
+class TimeSelector;
+class TimePeriod;
+class RepetitionDlg;
+
+
+class RepetitionButton : public QPushButton
+{
+ Q_OBJECT
+ public:
+ RepetitionButton(const QString& caption, bool waitForInitialisation, QWidget* parent, const char* name = 0);
+ void set(int interval, int count);
+ void set(int interval, int count, bool dateOnly, int maxDuration = -1);
+ void initialise(int interval, int count, bool dateOnly, int maxDuration = -1); // use only after needsInitialisation() signal
+ void activate() { activate(false); }
+ int interval() const { return mInterval; }
+ int count() const { return mCount; }
+ virtual void setReadOnly(bool ro) { mReadOnly = ro; }
+ virtual bool isReadOnly() const { return mReadOnly; }
+
+ signals:
+ void needsInitialisation(); // dialog has been created and needs set() to be called
+ void changed(); // the repetition dialog has been edited
+
+ private slots:
+ void slotPressed() { activate(mWaitForInit); }
+
+ private:
+ void activate(bool waitForInitialisation);
+ void displayDialog();
+
+ RepetitionDlg* mDialog;
+ int mInterval; // interval between repetitions, in minutes
+ int mCount; // total number of repetitions, including first occurrence
+ int mMaxDuration; // maximum allowed duration in minutes, or -1 for infinite
+ bool mDateOnly; // hours/minutes cannot be displayed
+ bool mWaitForInit; // emit needsInitialisation() when button pressed, display when initialise() called
+ bool mReadOnly;
+};
+
+
+class RepetitionDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ RepetitionDlg(const QString& caption, bool readOnly, QWidget* parent = 0, const char* name = 0);
+ void setReadOnly(bool);
+ void set(int interval, int count, bool dateOnly = false, int maxDuration = -1);
+ int interval() const; // get the interval between repetitions, in minutes
+ int count() const; // get the repeat count
+
+ private slots:
+ void typeClicked();
+ void intervalChanged(int);
+ void countChanged(int);
+ void durationChanged(int);
+ void repetitionToggled(bool);
+
+ private:
+ TimeSelector* mTimeSelector;
+ ButtonGroup* mButtonGroup;
+ RadioButton* mCountButton;
+ SpinBox* mCount;
+ RadioButton* mDurationButton;
+ TimePeriod* mDuration;
+ int mMaxDuration; // maximum allowed duration in minutes, or -1 for infinite
+ bool mDateOnly; // hours/minutes cannot be displayed
+ bool mReadOnly; // the widget is read only
+};
+
+#endif // REPETITION_H
diff --git a/kalarm/sounddlg.cpp b/kalarm/sounddlg.cpp
new file mode 100644
index 00000000..fe1df0ba
--- /dev/null
+++ b/kalarm/sounddlg.cpp
@@ -0,0 +1,472 @@
+/*
+ * sounddlg.cpp - sound file selection and configuration dialog
+ * Program: kalarm
+ * Copyright © 2005,2007,2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef WITHOUT_ARTS
+
+#include "kalarm.h"
+
+#include <qlabel.h>
+#include <qhbox.h>
+#include <qgroupbox.h>
+#include <qlayout.h>
+#include <qfile.h>
+#include <qdir.h>
+#include <qwhatsthis.h>
+#include <qtooltip.h>
+
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+#ifdef WITHOUT_ARTS
+#include <kaudioplayer.h>
+#else
+#include <qtimer.h>
+#include <arts/kartsdispatcher.h>
+#include <arts/kartsserver.h>
+#include <arts/kplayobjectfactory.h>
+#include <arts/kplayobject.h>
+#endif
+#include <kmessagebox.h>
+#include <kio/netaccess.h>
+#include <kdebug.h>
+
+#include "checkbox.h"
+#include "functions.h"
+#include "lineedit.h"
+#include "mainwindow.h"
+#include "pushbutton.h"
+#include "slider.h"
+#include "soundpicker.h"
+#include "spinbox.h"
+#include "sounddlg.moc"
+
+
+// Collect these widget labels together to ensure consistent wording and
+// translations across different modules.
+QString SoundDlg::i18n_SetVolume() { return i18n("Set volume"); }
+QString SoundDlg::i18n_v_SetVolume() { return i18n("Set &volume"); }
+QString SoundDlg::i18n_Repeat() { return i18n("Repeat"); }
+QString SoundDlg::i18n_p_Repeat() { return i18n("Re&peat"); }
+
+static const char SOUND_DIALOG_NAME[] = "SoundDialog";
+
+
+SoundDlg::SoundDlg(const QString& file, float volume, float fadeVolume, int fadeSeconds, bool repeat,
+ const QString& caption, QWidget* parent, const char* name)
+ : KDialogBase(parent, name, true, caption, Ok|Cancel, Ok, false),
+ mReadOnly(false),
+ mArtsDispatcher(0),
+ mPlayObject(0),
+ mPlayTimer(0)
+{
+ QWidget* page = new QWidget(this);
+ setMainWidget(page);
+ QVBoxLayout* layout = new QVBoxLayout(page, 0, spacingHint());
+
+ // File play button
+ QHBox* box = new QHBox(page);
+ layout->addWidget(box);
+ mFilePlay = new QPushButton(box);
+ mFilePlay->setPixmap(SmallIcon("player_play"));
+ mFilePlay->setFixedSize(mFilePlay->sizeHint());
+ connect(mFilePlay, SIGNAL(clicked()), SLOT(playSound()));
+ QToolTip::add(mFilePlay, i18n("Test the sound"));
+ QWhatsThis::add(mFilePlay, i18n("Play the selected sound file."));
+
+ // File name edit box
+ mFileEdit = new LineEdit(LineEdit::Url, box);
+ mFileEdit->setAcceptDrops(true);
+ QWhatsThis::add(mFileEdit, i18n("Enter the name or URL of a sound file to play."));
+
+ // File browse button
+ mFileBrowseButton = new PushButton(box);
+ mFileBrowseButton->setPixmap(SmallIcon("fileopen"));
+ mFileBrowseButton->setFixedSize(mFileBrowseButton->sizeHint());
+ connect(mFileBrowseButton, SIGNAL(clicked()), SLOT(slotPickFile()));
+ QToolTip::add(mFileBrowseButton, i18n("Choose a file"));
+ QWhatsThis::add(mFileBrowseButton, i18n("Select a sound file to play."));
+
+ // Sound repetition checkbox
+ mRepeatCheckbox = new CheckBox(i18n_p_Repeat(), page);
+ mRepeatCheckbox->setFixedSize(mRepeatCheckbox->sizeHint());
+ QWhatsThis::add(mRepeatCheckbox,
+ i18n("If checked, the sound file will be played repeatedly for as long as the message is displayed."));
+ layout->addWidget(mRepeatCheckbox);
+
+ // Volume
+ QGroupBox* group = new QGroupBox(i18n("Volume"), page);
+ layout->addWidget(group);
+ QGridLayout* grid = new QGridLayout(group, 4, 3, marginHint(), spacingHint());
+ grid->addRowSpacing(0, fontMetrics().height() - marginHint() + spacingHint());
+ grid->setColStretch(2, 1);
+ int indentWidth = 3 * KDialog::spacingHint();
+ grid->addColSpacing(0, indentWidth);
+ grid->addColSpacing(1, indentWidth);
+ // Get alignment to use in QGridLayout (AlignAuto doesn't work correctly there)
+ int alignment = QApplication::reverseLayout() ? Qt::AlignRight : Qt::AlignLeft;
+
+ // 'Set volume' checkbox
+ box = new QHBox(group);
+ box->setSpacing(spacingHint());
+ grid->addMultiCellWidget(box, 1, 1, 0, 2);
+ mVolumeCheckbox = new CheckBox(i18n_v_SetVolume(), box);
+ mVolumeCheckbox->setFixedSize(mVolumeCheckbox->sizeHint());
+ connect(mVolumeCheckbox, SIGNAL(toggled(bool)), SLOT(slotVolumeToggled(bool)));
+ QWhatsThis::add(mVolumeCheckbox,
+ i18n("Select to choose the volume for playing the sound file."));
+
+ // Volume slider
+ mVolumeSlider = new Slider(0, 100, 10, 0, Qt::Horizontal, box);
+ mVolumeSlider->setTickmarks(QSlider::Below);
+ mVolumeSlider->setTickInterval(10);
+ mVolumeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+ QWhatsThis::add(mVolumeSlider, i18n("Choose the volume for playing the sound file."));
+ mVolumeCheckbox->setFocusWidget(mVolumeSlider);
+
+ // Fade checkbox
+ mFadeCheckbox = new CheckBox(i18n("Fade"), group);
+ mFadeCheckbox->setFixedSize(mFadeCheckbox->sizeHint());
+ connect(mFadeCheckbox, SIGNAL(toggled(bool)), SLOT(slotFadeToggled(bool)));
+ QWhatsThis::add(mFadeCheckbox,
+ i18n("Select to fade the volume when the sound file first starts to play."));
+ grid->addMultiCellWidget(mFadeCheckbox, 2, 2, 1, 2, alignment);
+
+ // Fade time
+ mFadeBox = new QHBox(group);
+ mFadeBox->setSpacing(spacingHint());
+ grid->addWidget(mFadeBox, 3, 2, alignment);
+ QLabel* label = new QLabel(i18n("Time period over which to fade the sound", "Fade time:"), mFadeBox);
+ label->setFixedSize(label->sizeHint());
+ mFadeTime = new SpinBox(1, 999, 1, mFadeBox);
+ mFadeTime->setLineShiftStep(10);
+ mFadeTime->setFixedSize(mFadeTime->sizeHint());
+ label->setBuddy(mFadeTime);
+ label = new QLabel(i18n("seconds"), mFadeBox);
+ label->setFixedSize(label->sizeHint());
+ QWhatsThis::add(mFadeBox, i18n("Enter how many seconds to fade the sound before reaching the set volume."));
+
+ // Fade slider
+ mFadeVolumeBox = new QHBox(group);
+ mFadeVolumeBox->setSpacing(spacingHint());
+ grid->addWidget(mFadeVolumeBox, 4, 2);
+ label = new QLabel(i18n("Initial volume:"), mFadeVolumeBox);
+ label->setFixedSize(label->sizeHint());
+ mFadeSlider = new Slider(0, 100, 10, 0, Qt::Horizontal, mFadeVolumeBox);
+ mFadeSlider->setTickmarks(QSlider::Below);
+ mFadeSlider->setTickInterval(10);
+ mFadeSlider->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
+ label->setBuddy(mFadeSlider);
+ QWhatsThis::add(mFadeVolumeBox, i18n("Choose the initial volume for playing the sound file."));
+
+ // Restore the dialogue size from last time
+ QSize s;
+ if (KAlarm::readConfigWindowSize(SOUND_DIALOG_NAME, s))
+ resize(s);
+
+ // Initialise the control values
+ mFileEdit->setText(file);
+ mRepeatCheckbox->setChecked(repeat);
+ mVolumeCheckbox->setChecked(volume >= 0);
+ mVolumeSlider->setValue(volume >= 0 ? static_cast<int>(volume*100) : 100);
+ mFadeCheckbox->setChecked(fadeVolume >= 0);
+ mFadeSlider->setValue(fadeVolume >= 0 ? static_cast<int>(fadeVolume*100) : 100);
+ mFadeTime->setValue(fadeSeconds);
+ slotVolumeToggled(volume >= 0);
+}
+
+SoundDlg::~SoundDlg()
+{
+ stopPlay();
+}
+
+/******************************************************************************
+* Set the read-only status of the dialogue.
+*/
+void SoundDlg::setReadOnly(bool readOnly)
+{
+ if (readOnly != mReadOnly)
+ {
+ mFileEdit->setReadOnly(readOnly);
+ mFileBrowseButton->setReadOnly(readOnly);
+ mRepeatCheckbox->setReadOnly(readOnly);
+ mVolumeCheckbox->setReadOnly(readOnly);
+ mVolumeSlider->setReadOnly(readOnly);
+ mFadeCheckbox->setReadOnly(readOnly);
+ mFadeTime->setReadOnly(readOnly);
+ mFadeSlider->setReadOnly(readOnly);
+ mReadOnly = readOnly;
+ }
+}
+
+/******************************************************************************
+* Return the entered repetition and volume settings:
+* 'volume' is in range 0 - 1, or < 0 if volume is not to be set.
+* 'fadeVolume is similar, with 'fadeTime' set to the fade interval in seconds.
+* Reply = whether to repeat or not.
+*/
+bool SoundDlg::getSettings(float& volume, float& fadeVolume, int& fadeSeconds) const
+{
+ volume = mVolumeCheckbox->isChecked() ? (float)mVolumeSlider->value() / 100 : -1;
+ if (mFadeCheckbox->isChecked())
+ {
+ fadeVolume = (float)mFadeSlider->value() / 100;
+ fadeSeconds = mFadeTime->value();
+ }
+ else
+ {
+ fadeVolume = -1;
+ fadeSeconds = 0;
+ }
+ return mRepeatCheckbox->isChecked();
+}
+
+/******************************************************************************
+* Called when the dialog's size has changed.
+* Records the new size in the config file.
+*/
+void SoundDlg::resizeEvent(QResizeEvent* re)
+{
+ if (isVisible())
+ KAlarm::writeConfigWindowSize(SOUND_DIALOG_NAME, re->size());
+ mVolumeSlider->resize(mFadeSlider->size());
+ KDialog::resizeEvent(re);
+}
+
+void SoundDlg::showEvent(QShowEvent* se)
+{
+ mVolumeSlider->resize(mFadeSlider->size());
+ KDialog::showEvent(se);
+}
+
+/******************************************************************************
+* Called when the OK button is clicked.
+*/
+void SoundDlg::slotOk()
+{
+ if (mReadOnly)
+ reject();
+ if (!checkFile())
+ return;
+ accept();
+}
+
+/******************************************************************************
+* Called when the file browser button is clicked.
+*/
+void SoundDlg::slotPickFile()
+{
+ QString url = SoundPicker::browseFile(mDefaultDir, mFileEdit->text());
+ if (!url.isEmpty())
+ mFileEdit->setText(url);
+}
+
+/******************************************************************************
+* Called when the file play/stop button is clicked.
+*/
+void SoundDlg::playSound()
+{
+#ifdef WITHOUT_ARTS
+ if (checkFile())
+ KAudioPlayer::play(QFile::encodeName(mFileName));
+#else
+ if (mPlayObject)
+ {
+ stopPlay();
+ return;
+ }
+ if (!checkFile())
+ return;
+ KURL url(mFileName);
+ MainWindow* mmw = MainWindow::mainMainWindow();
+ if (!url.isValid() || !KIO::NetAccess::exists(url, true, mmw)
+ || !KIO::NetAccess::download(url, mLocalAudioFile, mmw))
+ {
+ kdError(5950) << "SoundDlg::playAudio(): Open failure: " << mFileName << endl;
+ KMessageBox::error(this, i18n("Cannot open audio file:\n%1").arg(mFileName));
+ return;
+ }
+ mPlayTimer = new QTimer(this);
+ connect(mPlayTimer, SIGNAL(timeout()), SLOT(checkAudioPlay()));
+ mArtsDispatcher = new KArtsDispatcher;
+ mPlayStarted = false;
+ KArtsServer aserver;
+ Arts::SoundServerV2 sserver = aserver.server();
+ KDE::PlayObjectFactory factory(sserver);
+ mPlayObject = factory.createPlayObject(mLocalAudioFile, true);
+ mFilePlay->setPixmap(SmallIcon("player_stop"));
+ QToolTip::add(mFilePlay, i18n("Stop sound"));
+ QWhatsThis::add(mFilePlay, i18n("Stop playing the sound"));
+ connect(mPlayObject, SIGNAL(playObjectCreated()), SLOT(checkAudioPlay()));
+ if (!mPlayObject->object().isNull())
+ checkAudioPlay();
+#endif
+}
+
+/******************************************************************************
+* Called when the audio file has loaded and is ready to play, or on a timer
+* when play is expected to have completed.
+* If it is ready to play, start playing it (for the first time or repeated).
+* If play has not yet completed, wait a bit longer.
+*/
+void SoundDlg::checkAudioPlay()
+{
+#ifndef WITHOUT_ARTS
+ if (!mPlayObject)
+ return;
+ if (mPlayObject->state() == Arts::posIdle)
+ {
+ // The file has loaded and is ready to play, or play has completed
+ if (mPlayStarted)
+ {
+ // Play has completed
+ stopPlay();
+ return;
+ }
+
+ // Start playing the file
+ kdDebug(5950) << "SoundDlg::checkAudioPlay(): start\n";
+ mPlayStarted = true;
+ mPlayObject->play();
+ }
+
+ // The sound file is still playing
+ Arts::poTime overall = mPlayObject->overallTime();
+ Arts::poTime current = mPlayObject->currentTime();
+ int time = 1000*(overall.seconds - current.seconds) + overall.ms - current.ms;
+ if (time < 0)
+ time = 0;
+ kdDebug(5950) << "SoundDlg::checkAudioPlay(): wait for " << (time+100) << "ms\n";
+ mPlayTimer->start(time + 100, true);
+#endif
+}
+
+/******************************************************************************
+* Called when play completes, the Silence button is clicked, or the window is
+* closed, to terminate audio access.
+*/
+void SoundDlg::stopPlay()
+{
+#ifndef WITHOUT_ARTS
+ delete mPlayObject; mPlayObject = 0;
+ delete mArtsDispatcher; mArtsDispatcher = 0;
+ delete mPlayTimer; mPlayTimer = 0;
+ if (!mLocalAudioFile.isEmpty())
+ {
+ KIO::NetAccess::removeTempFile(mLocalAudioFile); // removes it only if it IS a temporary file
+ mLocalAudioFile = QString::null;
+ }
+ mFilePlay->setPixmap(SmallIcon("player_play"));
+ QToolTip::add(mFilePlay, i18n("Test the sound"));
+ QWhatsThis::add(mFilePlay, i18n("Play the selected sound file."));
+#endif
+}
+
+/******************************************************************************
+* Check whether the specified sound file exists.
+* Note that KAudioPlayer::play() can only cope with local files.
+*/
+bool SoundDlg::checkFile()
+{
+ mFileName = mFileEdit->text();
+ KURL url;
+ if (KURL::isRelativeURL(mFileName))
+ {
+ // It's not an absolute URL, so check for an absolute path
+ QFileInfo f(mFileName);
+ if (!f.isRelative())
+ url.setPath(mFileName);
+ }
+ else
+ url = KURL::fromPathOrURL(mFileName); // it's an absolute URL
+#ifdef WITHOUT_ARTS
+ if (!url.isEmpty())
+ {
+ // It's an absolute path or URL.
+ // Only allow local files for KAudioPlayer.
+ if (url.isLocalFile() && KIO::NetAccess::exists(url, true, this))
+ {
+ mFileName = url.path();
+ return true;
+ }
+ }
+ else
+#else
+ if (url.isEmpty())
+#endif
+ {
+ // It's a relative path.
+ // Find the first sound resource that contains files.
+ QStringList soundDirs = KGlobal::dirs()->resourceDirs("sound");
+ if (!soundDirs.isEmpty())
+ {
+ QDir dir;
+ dir.setFilter(QDir::Files | QDir::Readable);
+ for (QStringList::ConstIterator it = soundDirs.begin(); it != soundDirs.end(); ++it)
+ {
+ dir = *it;
+ if (dir.isReadable() && dir.count() > 2)
+ {
+ url.setPath(*it);
+ url.addPath(mFileName);
+ if (KIO::NetAccess::exists(url, true, this))
+ {
+ mFileName = url.path();
+ return true;
+ }
+ }
+ }
+ }
+ url.setPath(QDir::homeDirPath());
+ url.addPath(mFileName);
+ if (KIO::NetAccess::exists(url, true, this))
+ {
+ mFileName = url.path();
+ return true;
+ }
+ }
+#ifdef WITHOUT_ARTS
+ KMessageBox::sorry(this, i18n("File not found"));
+ mFileName = QString::null;
+ return false;
+#else
+ return true;
+#endif
+}
+
+/******************************************************************************
+* Called when the Set Volume checkbox is toggled.
+*/
+void SoundDlg::slotVolumeToggled(bool on)
+{
+ mVolumeSlider->setEnabled(on);
+ mFadeCheckbox->setEnabled(on);
+ slotFadeToggled(on && mFadeCheckbox->isChecked());
+}
+
+/******************************************************************************
+* Called when the Fade checkbox is toggled.
+*/
+void SoundDlg::slotFadeToggled(bool on)
+{
+ mFadeBox->setEnabled(on);
+ mFadeVolumeBox->setEnabled(on);
+}
+
+#endif // #ifndef WITHOUT_ARTS
diff --git a/kalarm/sounddlg.h b/kalarm/sounddlg.h
new file mode 100644
index 00000000..ee4273b5
--- /dev/null
+++ b/kalarm/sounddlg.h
@@ -0,0 +1,97 @@
+/*
+ * sounddlg.h - sound file selection and configuration dialog
+ * Program: kalarm
+ * Copyright © 2005,2007,2008 by David Jarvie <djarvie@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.
+ */
+
+#ifndef SOUNDDLG_H
+#define SOUNDDLG_H
+
+#include <qframe.h>
+#include <qstring.h>
+#include <kdialogbase.h>
+
+class QHBox;
+class QPushButton;
+class KArtsDispatcher;
+namespace KDE { class PlayObject; }
+class LineEdit;
+class PushButton;
+class CheckBox;
+class SpinBox;
+class Slider;
+
+
+class SoundDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ SoundDlg(const QString& file, float volume, float fadeVolume, int fadeSeconds, bool repeat,
+ const QString& caption, QWidget* parent, const char* name = 0);
+ ~SoundDlg();
+ void setReadOnly(bool);
+ bool isReadOnly() const { return mReadOnly; }
+ QString getFile() const { return mFileName; }
+ bool getSettings(float& volume, float& fadeVolume, int& fadeSeconds) const;
+ QString defaultDir() const { return mDefaultDir; }
+
+ static QString i18n_SetVolume(); // plain text of Set volume checkbox
+ static QString i18n_v_SetVolume(); // text of Set volume checkbox, with 'V' shortcut
+ static QString i18n_Repeat(); // plain text of Repeat checkbox
+ static QString i18n_p_Repeat(); // text of Repeat checkbox, with 'P' shortcut
+
+ protected:
+ virtual void showEvent(QShowEvent*);
+ virtual void resizeEvent(QResizeEvent*);
+
+ protected slots:
+ virtual void slotOk();
+
+ private slots:
+ void slotPickFile();
+ void slotVolumeToggled(bool on);
+ void slotFadeToggled(bool on);
+ void playSound();
+ void checkAudioPlay();
+
+ private:
+ void stopPlay();
+ bool checkFile();
+
+ QPushButton* mFilePlay;
+ LineEdit* mFileEdit;
+ PushButton* mFileBrowseButton;
+ CheckBox* mRepeatCheckbox;
+ CheckBox* mVolumeCheckbox;
+ Slider* mVolumeSlider;
+ CheckBox* mFadeCheckbox;
+ QHBox* mFadeBox;
+ SpinBox* mFadeTime;
+ QHBox* mFadeVolumeBox;
+ Slider* mFadeSlider;
+ QString mDefaultDir; // current default directory for mFileEdit
+ QString mFileName;
+ bool mReadOnly;
+ // Sound file playing
+ KArtsDispatcher* mArtsDispatcher;
+ KDE::PlayObject* mPlayObject;
+ QTimer* mPlayTimer; // timer for playing the sound file
+ QString mLocalAudioFile; // local copy of audio file
+ bool mPlayStarted; // the sound file has started playing
+};
+
+#endif
diff --git a/kalarm/soundpicker.cpp b/kalarm/soundpicker.cpp
new file mode 100644
index 00000000..0e0626de
--- /dev/null
+++ b/kalarm/soundpicker.cpp
@@ -0,0 +1,292 @@
+/*
+ * soundpicker.cpp - widget to select a sound file or a beep
+ * Program: kalarm
+ * Copyright © 2002,2004-2007 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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qregexp.h>
+#include <qtooltip.h>
+#include <qtimer.h>
+#include <qlabel.h>
+#include <qhbox.h>
+#include <qwhatsthis.h>
+
+#include <kglobal.h>
+#include <klocale.h>
+#include <kfiledialog.h>
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+#ifndef WITHOUT_ARTS
+#include <arts/kplayobjectfactory.h>
+#endif
+#include <kdebug.h>
+
+#include "combobox.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "pushbutton.h"
+#include "sounddlg.h"
+#include "soundpicker.moc"
+
+
+// Collect these widget labels together to ensure consistent wording and
+// translations across different modules.
+QString SoundPicker::i18n_Sound() { return i18n("An audio sound", "Sound"); }
+QString SoundPicker::i18n_None() { return i18n("None"); }
+QString SoundPicker::i18n_Beep() { return i18n("Beep"); }
+QString SoundPicker::i18n_Speak() { return i18n("Speak"); }
+QString SoundPicker::i18n_File() { return i18n("Sound file"); }
+
+
+SoundPicker::SoundPicker(QWidget* parent, const char* name)
+ : QFrame(parent, name)
+{
+ setFrameStyle(QFrame::NoFrame);
+ QHBoxLayout* soundLayout = new QHBoxLayout(this, 0, KDialog::spacingHint());
+ mTypeBox = new QHBox(this); // this is to control the QWhatsThis text display area
+ mTypeBox->setSpacing(KDialog::spacingHint());
+
+ QLabel* label = new QLabel(i18n("An audio sound", "&Sound:"), mTypeBox);
+ label->setFixedSize(label->sizeHint());
+
+ // Sound type combo box
+ // The order of combo box entries must correspond with the 'Type' enum.
+ mTypeCombo = new ComboBox(false, mTypeBox);
+ mTypeCombo->insertItem(i18n_None()); // index NONE
+ mTypeCombo->insertItem(i18n_Beep()); // index BEEP
+ mTypeCombo->insertItem(i18n_File()); // index PLAY_FILE
+ mSpeakShowing = !theApp()->speechEnabled();
+ showSpeak(!mSpeakShowing); // index SPEAK (only displayed if appropriate)
+ connect(mTypeCombo, SIGNAL(activated(int)), SLOT(slotTypeSelected(int)));
+ label->setBuddy(mTypeCombo);
+ soundLayout->addWidget(mTypeBox);
+
+ // Sound file picker button
+ mFilePicker = new PushButton(this);
+ mFilePicker->setPixmap(SmallIcon("playsound"));
+ mFilePicker->setFixedSize(mFilePicker->sizeHint());
+ connect(mFilePicker, SIGNAL(clicked()), SLOT(slotPickFile()));
+ QToolTip::add(mFilePicker, i18n("Configure sound file"));
+ QWhatsThis::add(mFilePicker, i18n("Configure a sound file to play when the alarm is displayed."));
+ soundLayout->addWidget(mFilePicker);
+
+ // Initialise the file picker button state and tooltip
+ mTypeCombo->setCurrentItem(NONE);
+ mFilePicker->setEnabled(false);
+}
+
+/******************************************************************************
+* Set the read-only status of the widget.
+*/
+void SoundPicker::setReadOnly(bool readOnly)
+{
+ mTypeCombo->setReadOnly(readOnly);
+#ifdef WITHOUT_ARTS
+ mFilePicker->setReadOnly(readOnly);
+#endif
+ mReadOnly = readOnly;
+}
+
+/******************************************************************************
+* Show or hide the Speak option.
+*/
+void SoundPicker::showSpeak(bool show)
+{
+ if (!theApp()->speechEnabled())
+ show = false; // speech capability is not installed
+ if (show == mSpeakShowing)
+ return; // no change
+ QString whatsThis = "<p>" + i18n("Choose a sound to play when the message is displayed.")
+ + "<br>" + i18n("%1: the message is displayed silently.").arg("<b>" + i18n_None() + "</b>")
+ + "<br>" + i18n("%1: a simple beep is sounded.").arg("<b>" + i18n_Beep() + "</b>")
+ + "<br>" + i18n("%1: an audio file is played. You will be prompted to choose the file and set play options.").arg("<b>" + i18n_File() + "</b>");
+ if (!show && mTypeCombo->currentItem() == SPEAK)
+ mTypeCombo->setCurrentItem(NONE);
+ if (mTypeCombo->count() == SPEAK+1)
+ mTypeCombo->removeItem(SPEAK); // precaution in case of mix-ups
+ if (show)
+ {
+ mTypeCombo->insertItem(i18n_Speak());
+ whatsThis += "<br>" + i18n("%1: the message text is spoken.").arg("<b>" + i18n_Speak() + "</b>") + "</p>";
+ }
+ QWhatsThis::add(mTypeBox, whatsThis + "</p>");
+ mSpeakShowing = show;
+}
+
+/******************************************************************************
+* Return the currently selected option.
+*/
+SoundPicker::Type SoundPicker::sound() const
+{
+ return static_cast<SoundPicker::Type>(mTypeCombo->currentItem());
+}
+
+/******************************************************************************
+* Return the selected sound file, if the File option is selected.
+* Returns null string if File is not currently selected.
+*/
+QString SoundPicker::file() const
+{
+ return (mTypeCombo->currentItem() == PLAY_FILE) ? mFile : QString::null;
+}
+
+/******************************************************************************
+* Return the specified volumes (range 0 - 1).
+* Returns < 0 if beep is currently selected, or if 'set volume' is not selected.
+*/
+float SoundPicker::volume(float& fadeVolume, int& fadeSeconds) const
+{
+ if (mTypeCombo->currentItem() == PLAY_FILE && !mFile.isEmpty())
+ {
+ fadeVolume = mFadeVolume;
+ fadeSeconds = mFadeSeconds;
+ return mVolume;
+ }
+ else
+ {
+ fadeVolume = -1;
+ fadeSeconds = 0;
+ return -1;
+ }
+}
+
+/******************************************************************************
+* Return whether sound file repetition is selected, if the main checkbox is checked.
+* Returns false if beep is currently selected.
+*/
+bool SoundPicker::repeat() const
+{
+ return mTypeCombo->currentItem() == PLAY_FILE && !mFile.isEmpty() && mRepeat;
+}
+
+/******************************************************************************
+* Initialise the widget's state.
+*/
+void SoundPicker::set(SoundPicker::Type type, const QString& f, float volume, float fadeVolume, int fadeSeconds, bool repeat)
+{
+ if (type == PLAY_FILE && f.isEmpty())
+ type = BEEP;
+ mFile = f;
+ mVolume = volume;
+ mFadeVolume = fadeVolume;
+ mFadeSeconds = fadeSeconds;
+ mRepeat = repeat;
+ mTypeCombo->setCurrentItem(type); // this doesn't trigger slotTypeSelected()
+ mFilePicker->setEnabled(type == PLAY_FILE);
+ if (type == PLAY_FILE)
+ QToolTip::add(mTypeCombo, mFile);
+ else
+ QToolTip::remove(mTypeCombo);
+ mLastType = type;
+}
+
+/******************************************************************************
+* Called when the sound option is changed.
+*/
+void SoundPicker::slotTypeSelected(int id)
+{
+ Type newType = static_cast<Type>(id);
+ if (newType == mLastType)
+ return;
+ QString tooltip;
+ if (mLastType == PLAY_FILE)
+ {
+ mFilePicker->setEnabled(false);
+ QToolTip::remove(mTypeCombo);
+ }
+ else if (newType == PLAY_FILE)
+ {
+ if (mFile.isEmpty())
+ {
+ slotPickFile();
+ if (mFile.isEmpty())
+ return; // revert to previously selected type
+ }
+ mFilePicker->setEnabled(true);
+ QToolTip::add(mTypeCombo, mFile);
+ }
+ mLastType = newType;
+}
+
+/******************************************************************************
+* Called when the file picker button is clicked.
+*/
+void SoundPicker::slotPickFile()
+{
+#ifdef WITHOUT_ARTS
+ QString url = browseFile(mDefaultDir, mFile);
+ if (!url.isEmpty())
+ mFile = url;
+#else
+ QString file = mFile;
+ SoundDlg dlg(mFile, mVolume, mFadeVolume, mFadeSeconds, mRepeat, i18n("Sound File"), this, "soundDlg");
+ dlg.setReadOnly(mReadOnly);
+ bool accepted = (dlg.exec() == QDialog::Accepted);
+ if (mReadOnly)
+ return;
+ if (accepted)
+ {
+ float volume, fadeVolume;
+ int fadeTime;
+ file = dlg.getFile();
+ mRepeat = dlg.getSettings(volume, fadeVolume, fadeTime);
+ mVolume = volume;
+ mFadeVolume = fadeVolume;
+ mFadeSeconds = fadeTime;
+ }
+ if (!file.isEmpty())
+ {
+ mFile = file;
+ mDefaultDir = dlg.defaultDir();
+ }
+#endif
+ if (mFile.isEmpty())
+ {
+ // No audio file is selected, so revert to previously selected option
+ mTypeCombo->setCurrentItem(mLastType);
+ QToolTip::remove(mTypeCombo);
+ }
+ else
+ QToolTip::add(mTypeCombo, mFile);
+}
+
+/******************************************************************************
+* Display a dialogue to choose a sound file, initially highlighting any
+* specified file. 'initialFile' must be a full path name or URL.
+* 'defaultDir' is updated to the directory containing the chosen file.
+* Reply = URL selected. If none is selected, URL.isEmpty() is true.
+*/
+QString SoundPicker::browseFile(QString& defaultDir, const QString& initialFile)
+{
+ static QString kdeSoundDir; // directory containing KDE sound files
+ if (defaultDir.isEmpty())
+ {
+ if (kdeSoundDir.isNull())
+ kdeSoundDir = KGlobal::dirs()->findResourceDir("sound", "KDE_Notify.wav");
+ defaultDir = kdeSoundDir;
+ }
+#ifdef WITHOUT_ARTS
+ QString filter = QString::fromLatin1("*.wav *.mp3 *.ogg|%1\n*|%2").arg(i18n("Sound Files")).arg(i18n("All Files"));
+#else
+ QStringList filters = KDE::PlayObjectFactory::mimeTypes();
+ QString filter = filters.join(" ");
+#endif
+ return KAlarm::browseFile(i18n("Choose Sound File"), defaultDir, initialFile, filter, KFile::ExistingOnly, 0, "pickSoundFile");
+}
diff --git a/kalarm/soundpicker.h b/kalarm/soundpicker.h
new file mode 100644
index 00000000..7cad7f38
--- /dev/null
+++ b/kalarm/soundpicker.h
@@ -0,0 +1,135 @@
+/*
+ * soundpicker.h - widget to select a sound file or a beep
+ * Program: kalarm
+ * Copyright © 2002,2004-2006 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.
+ */
+
+#ifndef SOUNDPICKER_H
+#define SOUNDPICKER_H
+
+#include <qframe.h>
+#include <qstring.h>
+#include <kurl.h>
+
+class QHBox;
+class ComboBox;
+class PushButton;
+
+
+class SoundPicker : public QFrame
+{
+ Q_OBJECT
+ public:
+ /** Sound options which can be selected for when the alarm is displayed.
+ * @li NONE - silence.
+ * @li BEEP - a beep will be sounded.
+ * @li PLAY_FILE - a sound file will be played.
+ * @li SPEAK - the message text will be spoken.
+ */
+ enum Type { NONE = 0, BEEP, PLAY_FILE, SPEAK };
+ /** Constructor.
+ * @param parent The parent object of this widget.
+ * @param name The name of this widget.
+ */
+ explicit SoundPicker(QWidget* parent, const char* name = 0);
+ /** Initialises the widget's state.
+ * @param type The option to select.
+ * @param filename The full path or URL of the sound file to select. If the 'file' option is
+ * not initially selected, @p filename provides the default should 'file'
+ * later be selected by the user.
+ * @param volume The volume to play a sound file, or < 0 for no volume setting. If the
+ * 'file' option is not initially selected, @p volume provides the default
+ * should 'file' later be selected by the user.
+ * @param fadeVolume The initial volume to play a sound file if fading is to be used, or < 0
+ * for no fading. If the 'file' option is not initially selected, @p fadeVolume
+ * provides the default should 'file' later be selected by the user.
+ * @param fadeSeconds The number of seconds over which the sound file volume should be faded,
+ * or 0 for no fading. If the 'file' option is not initially selected,
+ * @p fadeSeconds provides the default should 'file' later be selected by the user.
+ * @param repeat True to play the sound file repeatedly. If the 'file' option is not initially
+ * selected, @p repeat provides the default should 'file' later be selected by
+ * the user.
+ */
+ void set(Type type, const QString& filename, float volume, float fadeVolume, int fadeSeconds, bool repeat);
+ /** Returns true if the widget is read only for the user. */
+ bool isReadOnly() const { return mReadOnly; }
+ /** Sets whether the widget can be changed the user.
+ * @param readOnly True to set the widget read-only, false to set it read-write.
+ */
+ void setReadOnly(bool readOnly);
+ /** Show or hide the 'speak' option.
+ * If it is to be hidden and it is currently selected, sound is turned off.
+ */
+ void showSpeak(bool show);
+ /** Returns the selected option. */
+ Type sound() const;
+ /** If the 'file' option is selected, returns the URL of the chosen file.
+ * Otherwise returns a null string.
+ */
+ QString file() const;
+ /** Returns the volume and fade characteristics for playing a sound file.
+ * @param fadeVolume Receives the initial volume if the volume is to be faded, else -1.
+ * @param fadeSeconds Receives the number of seconds over which the volume is to be faded, else 0.
+ * @return Volume to play the sound file, or < 0 if the 'file' option is not selected.
+ */
+ float volume(float& fadeVolume, int& fadeSeconds) const;
+ /** Returns true if a sound file is to be played repeatedly.
+ * If the 'file' option is not selected, returns false.
+ */
+ bool repeat() const;
+ /** Returns the current file URL regardless of whether the 'file' option is selected. */
+ QString fileSetting() const { return mFile; }
+ /** Returns the current file repetition setting regardless of whether the 'file' option is selected. */
+ bool repeatSetting() const { return mRepeat; }
+ /** Display a dialogue to choose a sound file, initially highlighting
+ * @p initialFile if non-null.
+ * @param initialDir Initial directory to display if @p initialFile is null. If a file is
+ * chosen, this is updated to the directory containing the chosen file.
+ * @param initialFile Full path name or URL of file to be highlighted initially.
+ * If null, no file will be highlighted.
+ * @return URL selected, in human readable format. If none is selected, URL.isEmpty() is true.
+ */
+ static QString browseFile(QString& initialDir, const QString& initialFile = QString::null);
+
+ static QString i18n_Sound(); // plain text of Sound label
+ static QString i18n_None(); // plain text of None combo box item
+ static QString i18n_Beep(); // plain text of Beep combo box item
+ static QString i18n_Speak(); // plain text of Speak combo box item
+ static QString i18n_File(); // plain text of File combo box item
+
+
+ private slots:
+ void slotTypeSelected(int id);
+ void slotPickFile();
+
+ private:
+
+ ComboBox* mTypeCombo;
+ QHBox* mTypeBox;
+ PushButton* mFilePicker;
+ QString mDefaultDir;
+ QString mFile; // sound file to play when alarm is triggered
+ float mVolume; // volume for file, or < 0 to not set volume
+ float mFadeVolume; // initial volume for file, or < 0 for no fading
+ int mFadeSeconds; // fade interval in seconds
+ Type mLastType; // last selected sound option
+ bool mSpeakShowing; // Speak option is shown in combo box
+ bool mRepeat; // repeat the sound file
+ bool mReadOnly;
+};
+
+#endif // SOUNDPICKER_H
diff --git a/kalarm/specialactions.cpp b/kalarm/specialactions.cpp
new file mode 100644
index 00000000..a92166ee
--- /dev/null
+++ b/kalarm/specialactions.cpp
@@ -0,0 +1,192 @@
+/*
+ * specialactions.cpp - widget to specify special alarm actions
+ * Program: kalarm
+ * Copyright © 2004,2005,2007 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 "kalarm.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <klineedit.h>
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "functions.h"
+#include "shellprocess.h"
+#include "specialactions.moc"
+
+
+/*=============================================================================
+= Class SpecialActionsButton
+= Button to display the Special Alarm Actions dialogue.
+=============================================================================*/
+
+SpecialActionsButton::SpecialActionsButton(const QString& caption, QWidget* parent, const char* name)
+ : QPushButton(caption, parent, name),
+ mReadOnly(false)
+{
+ setToggleButton(true);
+ setOn(false);
+ connect(this, SIGNAL(clicked()), SLOT(slotButtonPressed()));
+ QWhatsThis::add(this,
+ i18n("Specify actions to execute before and after the alarm is displayed."));
+}
+
+/******************************************************************************
+* Set the pre- and post-alarm actions.
+* The button's pressed state is set to reflect whether any actions are set.
+*/
+void SpecialActionsButton::setActions(const QString& pre, const QString& post)
+{
+ mPreAction = pre;
+ mPostAction = post;
+ setOn(!mPreAction.isEmpty() || !mPostAction.isEmpty());
+}
+
+/******************************************************************************
+* Called when the OK button is clicked.
+* Display a font and colour selection dialog and get the selections.
+*/
+void SpecialActionsButton::slotButtonPressed()
+{
+ SpecialActionsDlg dlg(mPreAction, mPostAction,
+ i18n("Special Alarm Actions"), this, "actionsDlg");
+ dlg.setReadOnly(mReadOnly);
+ if (dlg.exec() == QDialog::Accepted)
+ {
+ mPreAction = dlg.preAction();
+ mPostAction = dlg.postAction();
+ emit selected();
+ }
+ setOn(!mPreAction.isEmpty() || !mPostAction.isEmpty());
+}
+
+
+/*=============================================================================
+= Class SpecialActionsDlg
+= Pre- and post-alarm actions dialogue.
+=============================================================================*/
+
+static const char SPEC_ACT_DIALOG_NAME[] = "SpecialActionsDialog";
+
+
+SpecialActionsDlg::SpecialActionsDlg(const QString& preAction, const QString& postAction,
+ const QString& caption, QWidget* parent, const char* name)
+ : KDialogBase(parent, name, true, caption, Ok|Cancel, Ok, false)
+{
+ QWidget* page = new QWidget(this);
+ setMainWidget(page);
+ QVBoxLayout* layout = new QVBoxLayout(page, 0, spacingHint());
+
+ mActions = new SpecialActions(page);
+ mActions->setActions(preAction, postAction);
+ layout->addWidget(mActions);
+ layout->addSpacing(KDialog::spacingHint());
+
+ QSize s;
+ if (KAlarm::readConfigWindowSize(SPEC_ACT_DIALOG_NAME, s))
+ resize(s);
+}
+
+/******************************************************************************
+* Called when the OK button is clicked.
+*/
+void SpecialActionsDlg::slotOk()
+{
+ if (mActions->isReadOnly())
+ reject();
+ accept();
+}
+
+/******************************************************************************
+* Called when the dialog's size has changed.
+* Records the new size in the config file.
+*/
+void SpecialActionsDlg::resizeEvent(QResizeEvent* re)
+{
+ if (isVisible())
+ KAlarm::writeConfigWindowSize(SPEC_ACT_DIALOG_NAME, re->size());
+ KDialog::resizeEvent(re);
+}
+
+
+/*=============================================================================
+= Class SpecialActions
+= Pre- and post-alarm actions widget.
+=============================================================================*/
+
+SpecialActions::SpecialActions(QWidget* parent, const char* name)
+ : QWidget(parent, name),
+ mReadOnly(false)
+{
+ QBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
+
+ // Pre-alarm action
+ QLabel* label = new QLabel(i18n("Pre-a&larm action:"), this);
+ label->setFixedSize(label->sizeHint());
+ topLayout->addWidget(label, 0, Qt::AlignAuto);
+
+ mPreAction = new KLineEdit(this);
+ label->setBuddy(mPreAction);
+ QWhatsThis::add(mPreAction,
+ i18n("Enter a shell command to execute before the alarm is displayed.\n"
+ "Note that it is executed only when the alarm proper is displayed, not when a reminder or deferred alarm is displayed.\n"
+ "N.B. KAlarm will wait for the command to complete before displaying the alarm."));
+ topLayout->addWidget(mPreAction);
+ topLayout->addSpacing(KDialog::spacingHint());
+
+ // Post-alarm action
+ label = new QLabel(i18n("Post-alar&m action:"), this);
+ label->setFixedSize(label->sizeHint());
+ topLayout->addWidget(label, 0, Qt::AlignAuto);
+
+ mPostAction = new KLineEdit(this);
+ label->setBuddy(mPostAction);
+ QWhatsThis::add(mPostAction,
+ i18n("Enter a shell command to execute after the alarm window is closed.\n"
+ "Note that it is not executed after closing a reminder window. If you defer "
+ "the alarm, it is not executed until the alarm is finally acknowledged or closed."));
+ topLayout->addWidget(mPostAction);
+}
+
+void SpecialActions::setActions(const QString& pre, const QString& post)
+{
+ mPreAction->setText(pre);
+ mPostAction->setText(post);
+}
+
+QString SpecialActions::preAction() const
+{
+ return mPreAction->text();
+}
+
+QString SpecialActions::postAction() const
+{
+ return mPostAction->text();
+}
+
+void SpecialActions::setReadOnly(bool ro)
+{
+ mReadOnly = ro;
+ mPreAction->setReadOnly(mReadOnly);
+ mPostAction->setReadOnly(mReadOnly);
+}
diff --git a/kalarm/specialactions.h b/kalarm/specialactions.h
new file mode 100644
index 00000000..ba1106d0
--- /dev/null
+++ b/kalarm/specialactions.h
@@ -0,0 +1,96 @@
+/*
+ * specialactions.h - widget to specify special alarm actions
+ * Program: kalarm
+ * Copyright © 2004,2005 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.
+ */
+
+#ifndef SPECIALACTIONS_H
+#define SPECIALACTIONS_H
+
+#include <kdialogbase.h>
+#include <qwidget.h>
+#include <qpushbutton.h>
+
+class KLineEdit;
+
+
+class SpecialActionsButton : public QPushButton
+{
+ Q_OBJECT
+ public:
+ SpecialActionsButton(const QString& caption, QWidget* parent = 0, const char* name = 0);
+ void setActions(const QString& pre, const QString& post);
+ const QString& preAction() const { return mPreAction; }
+ const QString& postAction() const { return mPostAction; }
+ virtual void setReadOnly(bool ro) { mReadOnly = ro; }
+ virtual bool isReadOnly() const { return mReadOnly; }
+
+ signals:
+ void selected();
+
+ protected slots:
+ void slotButtonPressed();
+
+ private:
+ QString mPreAction;
+ QString mPostAction;
+ bool mReadOnly;
+};
+
+
+// Pre- and post-alarm actions widget
+class SpecialActions : public QWidget
+{
+ Q_OBJECT
+ public:
+ SpecialActions(QWidget* parent = 0, const char* name = 0);
+ void setActions(const QString& pre, const QString& post);
+ QString preAction() const;
+ QString postAction() const;
+ void setReadOnly(bool);
+ bool isReadOnly() const { return mReadOnly; }
+
+ private:
+ KLineEdit* mPreAction;
+ KLineEdit* mPostAction;
+ bool mReadOnly;
+};
+
+
+// Pre- and post-alarm actions dialogue displayed by the push button
+class SpecialActionsDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ SpecialActionsDlg(const QString& preAction, const QString& postAction,
+ const QString& caption, QWidget* parent = 0, const char* name = 0);
+ QString preAction() const { return mActions->preAction(); }
+ QString postAction() const { return mActions->postAction(); }
+ void setReadOnly(bool ro) { mActions->setReadOnly(ro); }
+ bool isReadOnly() const { return mActions->isReadOnly(); }
+
+ protected:
+ virtual void resizeEvent(QResizeEvent*);
+
+ protected slots:
+ virtual void slotOk();
+
+ private:
+ SpecialActions* mActions;
+};
+
+#endif // SPECIALACTIONS_H
diff --git a/kalarm/startdaytimer.cpp b/kalarm/startdaytimer.cpp
new file mode 100644
index 00000000..41ee3bc8
--- /dev/null
+++ b/kalarm/startdaytimer.cpp
@@ -0,0 +1,49 @@
+/*
+ * startdaytimer.cpp - timer triggered at the user-defined start-of-day time
+ * Program: kalarm
+ * Copyright (C) 2004, 2005 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 "kalarm.h"
+
+#include "preferences.h"
+#include "startdaytimer.moc"
+
+
+StartOfDayTimer* StartOfDayTimer::mInstance = 0;
+
+StartOfDayTimer::StartOfDayTimer()
+ : DailyTimer(Preferences::startOfDay(), false)
+{
+ Preferences::connect(SIGNAL(startOfDayChanged(const QTime&)), this, SLOT(startOfDayChanged(const QTime&)));
+}
+
+StartOfDayTimer* StartOfDayTimer::instance()
+{
+ if (!mInstance)
+ mInstance = new StartOfDayTimer; // receive notifications of change of start-of-day time
+ return mInstance;
+}
+
+/******************************************************************************
+* Called when the start-of-day time has changed.
+* The timer is adjusted and if appropriate timer events are triggered now.
+*/
+void StartOfDayTimer::startOfDayChanged(const QTime&)
+{
+ changeTime(Preferences::startOfDay(), true);
+}
diff --git a/kalarm/startdaytimer.h b/kalarm/startdaytimer.h
new file mode 100644
index 00000000..52f50d8c
--- /dev/null
+++ b/kalarm/startdaytimer.h
@@ -0,0 +1,66 @@
+/*
+ * startdaytimer.h - timer triggered at the user-defined start-of-day time
+ * Program: kalarm
+ * Copyright © 2004,2005 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.
+ */
+
+#ifndef STARTDAYTIMER_H
+#define STARTDAYTIMER_H
+
+/* @file startdaytimer.h - timer triggered at the user-defined start-of-day time */
+
+#include "synchtimer.h"
+
+
+/** StartOfDayTimer is an application-wide timer synchronised to the user-defined
+ * start-of-day time (set in KAlarm's Preferences dialog).
+ * It automatically adjusts to any changes in the start-of-day time.
+ *
+ * @author David Jarvie <software@astrojar.org.uk>
+ */
+class StartOfDayTimer : public DailyTimer
+{
+ Q_OBJECT
+ public:
+ virtual ~StartOfDayTimer() { }
+ /** 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:
+ StartOfDayTimer();
+ static StartOfDayTimer* instance();
+
+ private slots:
+ void startOfDayChanged(const QTime& oldTime);
+
+ private:
+ static StartOfDayTimer* mInstance; // exists solely to receive signals
+};
+
+#endif // STARTDAYTIMER_H
+
diff --git a/kalarm/templatedlg.cpp b/kalarm/templatedlg.cpp
new file mode 100644
index 00000000..917c1ea9
--- /dev/null
+++ b/kalarm/templatedlg.cpp
@@ -0,0 +1,241 @@
+/*
+ * templatedlg.cpp - dialogue to create, edit and delete alarm templates
+ * Program: kalarm
+ * Copyright © 2004-2006 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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qpushbutton.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kguiitem.h>
+#include <kmessagebox.h>
+#include <kaccel.h>
+#include <kdebug.h>
+
+#include "editdlg.h"
+#include "alarmcalendar.h"
+#include "functions.h"
+#include "templatelistview.h"
+#include "undo.h"
+#include "templatedlg.moc"
+
+static const char TMPL_DIALOG_NAME[] = "TemplateDialog";
+
+
+TemplateDlg* TemplateDlg::mInstance = 0;
+
+
+TemplateDlg::TemplateDlg(QWidget* parent, const char* name)
+ : KDialogBase(KDialogBase::Plain, i18n("Alarm Templates"), Close, Ok, parent, name, false, true)
+{
+ QWidget* topWidget = plainPage();
+ QBoxLayout* topLayout = new QHBoxLayout(topWidget);
+ topLayout->setSpacing(spacingHint());
+
+ QBoxLayout* layout = new QVBoxLayout(topLayout);
+ mTemplateList = new TemplateListView(true, i18n("The list of alarm templates"), topWidget);
+ mTemplateList->setSelectionMode(QListView::Extended);
+ mTemplateList->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
+ connect(mTemplateList, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
+ layout->addWidget(mTemplateList);
+
+ layout = new QVBoxLayout(topLayout);
+ QPushButton* button = new QPushButton(i18n("&New..."), topWidget);
+ connect(button, SIGNAL(clicked()), SLOT(slotNew()));
+ QWhatsThis::add(button, i18n("Create a new alarm template"));
+ layout->addWidget(button);
+
+ mEditButton = new QPushButton(i18n("&Edit..."), topWidget);
+ connect(mEditButton, SIGNAL(clicked()), SLOT(slotEdit()));
+ QWhatsThis::add(mEditButton, i18n("Edit the currently highlighted alarm template"));
+ layout->addWidget(mEditButton);
+
+ mCopyButton = new QPushButton(i18n("Co&py"), topWidget);
+ connect(mCopyButton, SIGNAL(clicked()), SLOT(slotCopy()));
+ QWhatsThis::add(mCopyButton,
+ i18n("Create a new alarm template based on a copy of the currently highlighted template"));
+ layout->addWidget(mCopyButton);
+
+ mDeleteButton = new QPushButton(i18n("&Delete"), topWidget);
+ connect(mDeleteButton, SIGNAL(clicked()), SLOT(slotDelete()));
+ QWhatsThis::add(mDeleteButton, i18n("Delete the currently highlighted alarm template"));
+ layout->addWidget(mDeleteButton);
+
+ KAccel* accel = new KAccel(this);
+ accel->insert(KStdAccel::SelectAll, mTemplateList, SLOT(slotSelectAll()));
+ accel->insert(KStdAccel::Deselect, mTemplateList, SLOT(slotDeselect()));
+ accel->readSettings();
+
+ mTemplateList->refresh();
+ slotSelectionChanged(); // enable/disable buttons as appropriate
+
+ QSize s;
+ if (KAlarm::readConfigWindowSize(TMPL_DIALOG_NAME, s))
+ resize(s);
+}
+
+/******************************************************************************
+* Destructor.
+*/
+TemplateDlg::~TemplateDlg()
+{
+ mInstance = 0;
+}
+
+/******************************************************************************
+* Create an instance, if none already exists.
+*/
+TemplateDlg* TemplateDlg::create(QWidget* parent, const char* name)
+{
+ if (mInstance)
+ return 0;
+ mInstance = new TemplateDlg(parent, name);
+ return mInstance;
+}
+
+/******************************************************************************
+* Called when the New Template button is clicked to create a new template
+* based on the currently selected alarm.
+*/
+void TemplateDlg::slotNew()
+{
+ createTemplate(0, this, mTemplateList);
+}
+
+/******************************************************************************
+* Called when the Copy button is clicked to edit a copy of an existing alarm,
+* to add to the list.
+*/
+void TemplateDlg::slotCopy()
+{
+ TemplateListViewItem* item = mTemplateList->selectedItem();
+ if (item)
+ {
+ KAEvent event = item->event();
+ createTemplate(&event, mTemplateList);
+ }
+}
+
+/******************************************************************************
+* Create a new template.
+* If 'event' is non-zero, base the new template on an existing event or template.
+*/
+void TemplateDlg::createTemplate(const KAEvent* event, QWidget* parent, TemplateListView* view)
+{
+ EditAlarmDlg editDlg(true, i18n("New Alarm Template"), parent, 0, event);
+ if (editDlg.exec() == QDialog::Accepted)
+ {
+ KAEvent event;
+ editDlg.getEvent(event);
+
+ // Add the template to the displayed lists and to the calendar file
+ KAlarm::addTemplate(event, view, &editDlg);
+ Undo::saveAdd(event);
+ }
+}
+
+/******************************************************************************
+* Called when the Modify button is clicked to edit the currently highlighted
+* alarm in the list.
+*/
+void TemplateDlg::slotEdit()
+{
+ TemplateListViewItem* item = mTemplateList->selectedItem();
+ if (item)
+ {
+ KAEvent event = item->event();
+ EditAlarmDlg editDlg(true, i18n("Edit Alarm Template"), this, 0, &event);
+ if (editDlg.exec() == QDialog::Accepted)
+ {
+ KAEvent newEvent;
+ editDlg.getEvent(newEvent);
+ QString id = event.id();
+ newEvent.setEventID(id);
+
+ // Update the event in the displays and in the calendar file
+ KAlarm::updateTemplate(newEvent, mTemplateList, &editDlg);
+ Undo::saveEdit(event, newEvent);
+ }
+ }
+}
+
+/******************************************************************************
+* Called when the Delete button is clicked to delete the currently highlighted
+* alarms in the list.
+*/
+void TemplateDlg::slotDelete()
+{
+ QValueList<EventListViewItemBase*> items = mTemplateList->selectedItems();
+ int n = items.count();
+ if (KMessageBox::warningContinueCancel(this, i18n("Do you really want to delete the selected alarm template?",
+ "Do you really want to delete the %n selected alarm templates?", n),
+ i18n("Delete Alarm Template", "Delete Alarm Templates", n), KGuiItem(i18n("&Delete"), "editdelete"))
+ != KMessageBox::Continue)
+ return;
+
+ int warnErr = 0;
+ KAlarm::UpdateStatus status = KAlarm::UPDATE_OK;
+ QValueList<KAEvent> events;
+ AlarmCalendar::templateCalendar()->startUpdate(); // prevent multiple saves of the calendar until we're finished
+ for (QValueList<EventListViewItemBase*>::Iterator it = items.begin(); it != items.end(); ++it)
+ {
+ TemplateListViewItem* item = (TemplateListViewItem*)(*it);
+ events.append(item->event());
+ KAlarm::UpdateStatus st = KAlarm::deleteTemplate(item->event());
+ if (st != KAlarm::UPDATE_OK)
+ {
+ status = st;
+ ++warnErr;
+ }
+ }
+ if (!AlarmCalendar::templateCalendar()->endUpdate()) // save the calendar now
+ {
+ status = KAlarm::SAVE_FAILED;
+ warnErr = items.count();
+ }
+ Undo::saveDeletes(events);
+ if (warnErr)
+ displayUpdateError(this, status, KAlarm::ERR_TEMPLATE, warnErr);
+}
+
+/******************************************************************************
+* Called when the group of items selected changes.
+* Enable/disable the buttons depending on whether/how many templates are
+* currently highlighted.
+*/
+void TemplateDlg::slotSelectionChanged()
+{
+ int count = mTemplateList->selectedCount();
+ mEditButton->setEnabled(count == 1);
+ mCopyButton->setEnabled(count == 1);
+ mDeleteButton->setEnabled(count);
+}
+
+/******************************************************************************
+* Called when the dialog's size has changed.
+* Records the new size in the config file.
+*/
+void TemplateDlg::resizeEvent(QResizeEvent* re)
+{
+ if (isVisible())
+ KAlarm::writeConfigWindowSize(TMPL_DIALOG_NAME, re->size());
+ KDialog::resizeEvent(re);
+}
diff --git a/kalarm/templatedlg.h b/kalarm/templatedlg.h
new file mode 100644
index 00000000..c9335986
--- /dev/null
+++ b/kalarm/templatedlg.h
@@ -0,0 +1,62 @@
+/*
+ * templatedlg.h - dialogue to create, edit and delete alarm templates
+ * Program: kalarm
+ * Copyright (C) 2004 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.
+ */
+#ifndef TEMPLATEDLG_H
+#define TEMPLATEDLG_H
+
+#include <kdialogbase.h>
+
+class QPushButton;
+class TemplateListView;
+class KAEvent;
+
+
+class TemplateDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ static TemplateDlg* create(QWidget* parent = 0, const char* name = 0);
+ ~TemplateDlg();
+ static void createTemplate(const KAEvent* = 0, QWidget* parent = 0, TemplateListView* = 0);
+
+ signals:
+ void emptyToggled(bool notEmpty);
+
+ protected:
+ virtual void resizeEvent(QResizeEvent*);
+
+ private slots:
+ void slotNew();
+ void slotCopy();
+ void slotEdit();
+ void slotDelete();
+ void slotSelectionChanged();
+
+ private:
+ TemplateDlg(QWidget* parent, const char* name);
+
+ static TemplateDlg* mInstance; // the current instance, to prevent multiple dialogues
+
+ TemplateListView* mTemplateList;
+ QPushButton* mEditButton;
+ QPushButton* mCopyButton;
+ QPushButton* mDeleteButton;
+};
+
+#endif // TEMPLATEDLG_H
diff --git a/kalarm/templatelistview.cpp b/kalarm/templatelistview.cpp
new file mode 100644
index 00000000..bf1e0085
--- /dev/null
+++ b/kalarm/templatelistview.cpp
@@ -0,0 +1,131 @@
+/*
+ * templatelistview.cpp - widget showing list of alarm templates
+ * Program: kalarm
+ * Copyright (C) 2004, 2005 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 "kalarm.h"
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "alarmcalendar.h"
+#include "functions.h"
+#include "templatelistview.moc"
+
+
+/*=============================================================================
+= Class: TemplateListView
+= Displays the list of outstanding alarms.
+=============================================================================*/
+QValueList<EventListViewBase*> TemplateListView::mInstanceList;
+
+
+TemplateListView::TemplateListView(bool includeCmdAlarms, const QString& whatsThisText, QWidget* parent, const char* name)
+ : EventListViewBase(parent, name),
+ mWhatsThisText(whatsThisText),
+ mIconColumn(0),
+ mNameColumn(1),
+ mExcludeCmdAlarms(!includeCmdAlarms)
+{
+ addColumn(QString::null); // icon column
+ addLastColumn(i18n("Name"));
+ setSorting(mNameColumn); // sort initially by name
+ setColumnAlignment(mIconColumn, Qt::AlignHCenter);
+ setColumnWidthMode(mIconColumn, QListView::Maximum);
+
+ mInstanceList.append(this);
+}
+
+TemplateListView::~TemplateListView()
+{
+ mInstanceList.remove(this);
+}
+
+/******************************************************************************
+* Add all the templates to the list.
+*/
+void TemplateListView::populate()
+{
+ QValueList<KAEvent> templates = KAlarm::templateList();
+ for (QValueList<KAEvent>::Iterator it = templates.begin(); it != templates.end(); ++it)
+ addEntry(*it);
+}
+
+/******************************************************************************
+* Create a new list item for addEntry().
+*/
+EventListViewItemBase* TemplateListView::createItem(const KAEvent& event)
+{
+ return new TemplateListViewItem(this, event);
+}
+
+/******************************************************************************
+* Returns the QWhatsThis text for a specified column.
+*/
+QString TemplateListView::whatsThisText(int column) const
+{
+ if (column == mIconColumn)
+ return i18n("Alarm type");
+ if (column == mNameColumn)
+ return i18n("Name of the alarm template");
+ return mWhatsThisText;
+}
+
+
+/*=============================================================================
+= Class: TemplateListViewItem
+= Contains the details of one alarm for display in the TemplateListView.
+=============================================================================*/
+
+TemplateListViewItem::TemplateListViewItem(TemplateListView* parent, const KAEvent& event)
+ : EventListViewItemBase(parent, event)
+{
+ setLastColumnText(); // set the template name column text
+
+ int index;
+ switch (event.action())
+ {
+ case KAAlarm::FILE: index = 2; break;
+ case KAAlarm::COMMAND: index = 3; break;
+ case KAAlarm::EMAIL: index = 4; break;
+ case KAAlarm::MESSAGE:
+ default: index = 1; break;
+ }
+ mIconOrder.sprintf("%01u", index);
+ setPixmap(templateListView()->iconColumn(), *eventIcon());
+}
+
+/******************************************************************************
+* Return the alarm summary text.
+*/
+QString TemplateListViewItem::lastColumnText() const
+{
+ return event().templateName();
+}
+
+/******************************************************************************
+* Return the column sort order for one item in the list.
+*/
+QString TemplateListViewItem::key(int column, bool) const
+{
+ TemplateListView* listView = templateListView();
+ if (column == listView->iconColumn())
+ return mIconOrder;
+ return text(column).lower();
+}
+
diff --git a/kalarm/templatelistview.h b/kalarm/templatelistview.h
new file mode 100644
index 00000000..f420641b
--- /dev/null
+++ b/kalarm/templatelistview.h
@@ -0,0 +1,88 @@
+/*
+ * templatelistview.h - widget showing list of alarm templates
+ * Program: kalarm
+ * Copyright (C) 2004, 2005 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.
+ */
+
+#ifndef TEMPLATELISTVIEW_H
+#define TEMPLATELISTVIEW_H
+
+#include "kalarm.h"
+
+#include <qmap.h>
+#include <klistview.h>
+
+#include "eventlistviewbase.h"
+
+class TemplateListView;
+
+
+class TemplateListViewItem : public EventListViewItemBase
+{
+ public:
+ TemplateListViewItem(TemplateListView* parent, const KAEvent&);
+ TemplateListView* templateListView() const { return (TemplateListView*)listView(); }
+ // Overridden base class methods
+ TemplateListViewItem* nextSibling() const { return (TemplateListViewItem*)QListViewItem::nextSibling(); }
+ virtual QString key(int column, bool ascending) const;
+ protected:
+ virtual QString lastColumnText() const;
+ private:
+ QString mIconOrder; // controls ordering of icon column
+};
+
+
+class TemplateListView : public EventListViewBase
+{
+ Q_OBJECT
+ public:
+ explicit TemplateListView(bool includeCmdAlarms, const QString& whatsThisText, QWidget* parent = 0, const char* name = 0);
+ ~TemplateListView();
+ int iconColumn() const { return mIconColumn; }
+ int nameColumn() const { return mNameColumn; }
+ // Overridden base class methods
+ static void addEvent(const KAEvent& e, EventListViewBase* v)
+ { EventListViewBase::addEvent(e, mInstanceList, v); }
+ static void modifyEvent(const KAEvent& e, EventListViewBase* v)
+ { EventListViewBase::modifyEvent(e.id(), e, mInstanceList, v); }
+ static void modifyEvent(const QString& oldEventID, const KAEvent& newEvent, EventListViewBase* v)
+ { EventListViewBase::modifyEvent(oldEventID, newEvent, mInstanceList, v); }
+ static void deleteEvent(const QString& eventID)
+ { EventListViewBase::deleteEvent(eventID, mInstanceList); }
+ TemplateListViewItem* getEntry(const QString& eventID) { return (TemplateListViewItem*)EventListViewBase::getEntry(eventID); }
+ TemplateListViewItem* selectedItem() const { return (TemplateListViewItem*)EventListViewBase::selectedItem(); }
+ TemplateListViewItem* currentItem() const { return (TemplateListViewItem*)EventListViewBase::currentItem(); }
+ TemplateListViewItem* firstChild() const { return (TemplateListViewItem*)EventListViewBase::firstChild(); }
+ virtual void setSelected(QListViewItem* item, bool selected) { EventListViewBase::setSelected(item, selected); }
+ virtual void setSelected(TemplateListViewItem* item, bool selected) { EventListViewBase::setSelected(item, selected); }
+ virtual QValueList<EventListViewBase*> instances() { return mInstanceList; }
+
+ protected:
+ virtual void populate();
+ EventListViewItemBase* createItem(const KAEvent&);
+ virtual QString whatsThisText(int column) const;
+
+ private:
+ static QValueList<EventListViewBase*> mInstanceList;
+ QString mWhatsThisText; // default QWhatsThis text
+ int mIconColumn; // index to icon column
+ int mNameColumn; // index to template name column
+ bool mExcludeCmdAlarms; // omit command alarms from the list
+};
+
+#endif // TEMPLATELISTVIEW_H
+
diff --git a/kalarm/templatemenuaction.cpp b/kalarm/templatemenuaction.cpp
new file mode 100644
index 00000000..dae3bc12
--- /dev/null
+++ b/kalarm/templatemenuaction.cpp
@@ -0,0 +1,84 @@
+/*
+ * templatemenuaction.cpp - menu action to select a template
+ * Program: kalarm
+ * Copyright © 2005,2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <kactionclasses.h>
+#include <kpopupmenu.h>
+#include <kdebug.h>
+
+#include "alarmcalendar.h"
+#include "alarmevent.h"
+#include "functions.h"
+#include "templatemenuaction.moc"
+
+
+TemplateMenuAction::TemplateMenuAction(const QString& label, const QString& icon, QObject* receiver,
+ const char* slot, KActionCollection* actions, const char* name)
+ : KActionMenu(label, icon, actions, name)
+{
+ setDelayed(false);
+ connect(popupMenu(), SIGNAL(aboutToShow()), SLOT(slotInitMenu()));
+ connect(popupMenu(), SIGNAL(activated(int)), SLOT(slotSelected(int)));
+ connect(this, SIGNAL(selected(const KAEvent&)), receiver, slot);
+}
+
+/******************************************************************************
+* Called when the New From Template action is clicked.
+* Creates a popup menu listing all alarm templates, in sorted name order.
+*/
+void TemplateMenuAction::slotInitMenu()
+{
+ KPopupMenu* menu = popupMenu();
+ menu->clear();
+ mOriginalTexts.clear();
+ QValueList<KAEvent> templates = KAlarm::templateList();
+ for (QValueList<KAEvent>::ConstIterator it = templates.constBegin(); it != templates.constEnd(); ++it)
+ {
+ QString name = (*it).templateName();
+ // Insert the template in sorted order
+ QStringList::Iterator tit;
+ for (tit = mOriginalTexts.begin();
+ tit != mOriginalTexts.end() && QString::localeAwareCompare(name, *tit) > 0;
+ ++tit);
+ mOriginalTexts.insert(tit, name);
+ }
+ for (QStringList::ConstIterator tit = mOriginalTexts.constBegin(); tit != mOriginalTexts.constEnd(); ++tit)
+ menu->insertItem(*tit);
+}
+
+/******************************************************************************
+* Called when a template is selected from the New From Template popup menu.
+* Executes a New Alarm dialog, preset from the selected template.
+*/
+void TemplateMenuAction::slotSelected(int id)
+{
+ KPopupMenu* menu = popupMenu();
+ QString item = mOriginalTexts[menu->indexOf(id)];
+ if (!item.isEmpty())
+ {
+ AlarmCalendar* cal = AlarmCalendar::templateCalendarOpen();
+ if (cal)
+ {
+ KAEvent templ = KAEvent::findTemplateName(*cal, item);
+ emit selected(templ);
+ }
+ }
+}
diff --git a/kalarm/templatemenuaction.h b/kalarm/templatemenuaction.h
new file mode 100644
index 00000000..20d9f30a
--- /dev/null
+++ b/kalarm/templatemenuaction.h
@@ -0,0 +1,46 @@
+/*
+ * templatemenuaction.h - menu action to select a template
+ * Program: kalarm
+ * Copyright (C) 2005 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.
+ */
+
+#ifndef TEMPLATEMENUACTION_H
+#define TEMPLATEMENUACTION_H
+
+#include <kactionclasses.h>
+class KAEvent;
+
+
+class TemplateMenuAction : public KActionMenu
+{
+ Q_OBJECT
+ public:
+ TemplateMenuAction(const QString& label, const QString& icon, QObject* receiver,
+ const char* slot, KActionCollection* parent, const char* name = 0);
+
+ signals:
+ void selected(const KAEvent&);
+
+ private slots:
+ void slotInitMenu();
+ void slotSelected(int id);
+
+ private:
+ QStringList mOriginalTexts; // menu item texts without added ampersands
+};
+
+#endif // TEMPLATEMENUACTION_H
diff --git a/kalarm/templatepickdlg.cpp b/kalarm/templatepickdlg.cpp
new file mode 100644
index 00000000..50ec2b6e
--- /dev/null
+++ b/kalarm/templatepickdlg.cpp
@@ -0,0 +1,87 @@
+/*
+ * templatepickdlg.cpp - dialogue to choose an alarm template
+ * Program: kalarm
+ * Copyright © 2004,2008 by David Jarvie <djarvie@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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kdebug.h>
+
+#include "functions.h"
+#include "shellprocess.h"
+#include "templatelistview.h"
+#include "templatepickdlg.moc"
+
+static const char TMPL_PICK_DIALOG_NAME[] = "TemplatePickDialog";
+
+
+TemplatePickDlg::TemplatePickDlg(QWidget* parent, const char* name)
+ : KDialogBase(KDialogBase::Plain, i18n("Choose Alarm Template"), Ok|Cancel, Ok, parent, name)
+{
+ QWidget* topWidget = plainPage();
+ QBoxLayout* topLayout = new QVBoxLayout(topWidget);
+ topLayout->setSpacing(spacingHint());
+
+ // Display the list of templates, but exclude command alarms if in kiosk mode.
+ bool includeCmdAlarms = ShellProcess::authorised();
+ mTemplateList = new TemplateListView(includeCmdAlarms, i18n("Select a template to base the new alarm on."), topWidget, "list");
+ mTemplateList->setSelectionMode(QListView::Single);
+ mTemplateList->refresh(); // populate the template list
+ connect(mTemplateList, SIGNAL(selectionChanged()), SLOT(slotSelectionChanged()));
+ // Require a real double click (even if KDE is in single-click mode) to accept the selection
+ connect(mTemplateList, SIGNAL(doubleClicked(QListViewItem*, const QPoint&, int)), SLOT(slotOk()));
+ topLayout->addWidget(mTemplateList);
+
+ slotSelectionChanged(); // enable or disable the OK button
+
+ QSize s;
+ if (KAlarm::readConfigWindowSize(TMPL_PICK_DIALOG_NAME, s))
+ resize(s);
+}
+
+/******************************************************************************
+* Return the currently selected alarm template, or 0 if none.
+*/
+const KAEvent* TemplatePickDlg::selectedTemplate() const
+{
+ return mTemplateList->selectedEvent();
+}
+
+/******************************************************************************
+* Called when the template selection changes.
+* Enable/disable the OK button depending on whether anything is selected.
+*/
+void TemplatePickDlg::slotSelectionChanged()
+{
+ enableButtonOK(mTemplateList->selectedItem());
+}
+
+/******************************************************************************
+* Called when the dialog's size has changed.
+* Records the new size in the config file.
+*/
+void TemplatePickDlg::resizeEvent(QResizeEvent* re)
+{
+ if (isVisible())
+ KAlarm::writeConfigWindowSize(TMPL_PICK_DIALOG_NAME, re->size());
+ KDialog::resizeEvent(re);
+}
diff --git a/kalarm/templatepickdlg.h b/kalarm/templatepickdlg.h
new file mode 100644
index 00000000..9bc97bfa
--- /dev/null
+++ b/kalarm/templatepickdlg.h
@@ -0,0 +1,43 @@
+/*
+ * templatepickdlg.h - dialogue to choose an alarm template
+ * Program: kalarm
+ * Copyright (C) 2004 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.
+ */
+#ifndef TEMPLATEPICKDLG_H
+#define TEMPLATEPICKDLG_H
+
+#include <kdialogbase.h>
+
+class TemplateListView;
+class KAEvent;
+
+
+class TemplatePickDlg : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ TemplatePickDlg(QWidget* parent = 0, const char* name = 0);
+ const KAEvent* selectedTemplate() const;
+ protected:
+ virtual void resizeEvent(QResizeEvent*);
+ private slots:
+ void slotSelectionChanged();
+ private:
+ TemplateListView* mTemplateList;
+};
+
+#endif // TEMPLATEPICKDLG_H
diff --git a/kalarm/timeselector.cpp b/kalarm/timeselector.cpp
new file mode 100644
index 00000000..0bfc142d
--- /dev/null
+++ b/kalarm/timeselector.cpp
@@ -0,0 +1,159 @@
+/*
+ * timeselector.cpp - widget to optionally set a time period
+ * Program: kalarm
+ * Copyright (C) 2004 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 "kalarm.h"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qhbox.h>
+#include <qwhatsthis.h>
+
+#include <klocale.h>
+#include <kdialog.h>
+#include <kdebug.h>
+
+#include "checkbox.h"
+#include "timeselector.moc"
+
+
+TimeSelector::TimeSelector(const QString& selectText, const QString& postfix, const QString& selectWhatsThis,
+ const QString& valueWhatsThis, bool allowHourMinute, QWidget* parent, const char* name)
+ : QFrame(parent, name),
+ mLabel(0),
+ mReadOnly(false)
+{
+ setFrameStyle(QFrame::NoFrame);
+ QVBoxLayout* topLayout = new QVBoxLayout(this, 0, KDialog::spacingHint());
+ QHBoxLayout* layout = new QHBoxLayout(topLayout, KDialog::spacingHint());
+ mSelect = new CheckBox(selectText, this);
+ mSelect->setFixedSize(mSelect->sizeHint());
+ connect(mSelect, SIGNAL(toggled(bool)), SLOT(selectToggled(bool)));
+ QWhatsThis::add(mSelect, selectWhatsThis);
+ layout->addWidget(mSelect);
+
+ QHBox* box = new QHBox(this); // to group widgets for QWhatsThis text
+ box->setSpacing(KDialog::spacingHint());
+ layout->addWidget(box);
+ mPeriod = new TimePeriod(allowHourMinute, box);
+ mPeriod->setFixedSize(mPeriod->sizeHint());
+ mPeriod->setSelectOnStep(false);
+ connect(mPeriod, SIGNAL(valueChanged(int)), SLOT(periodChanged(int)));
+ mSelect->setFocusWidget(mPeriod);
+ mPeriod->setEnabled(false);
+
+ if (!postfix.isEmpty())
+ {
+ mLabel = new QLabel(postfix, box);
+ QWhatsThis::add(box, valueWhatsThis);
+ mLabel->setEnabled(false);
+ }
+}
+
+/******************************************************************************
+* Set the read-only status.
+*/
+void TimeSelector::setReadOnly(bool ro)
+{
+ if ((int)ro != (int)mReadOnly)
+ {
+ mReadOnly = ro;
+ mSelect->setReadOnly(mReadOnly);
+ mPeriod->setReadOnly(mReadOnly);
+ }
+}
+
+bool TimeSelector::isChecked() const
+{
+ return mSelect->isChecked();
+}
+
+void TimeSelector::setChecked(bool on)
+{
+ if (on != mSelect->isChecked())
+ {
+ mSelect->setChecked(on);
+ emit valueChanged(minutes());
+ }
+}
+
+void TimeSelector::setMaximum(int hourmin, int days)
+{
+ mPeriod->setMaximum(hourmin, days);
+}
+
+void TimeSelector::setDateOnly(bool dateOnly)
+{
+ mPeriod->setDateOnly(dateOnly);
+}
+
+/******************************************************************************
+ * Get the specified number of minutes.
+ * Reply = 0 if unselected.
+ */
+int TimeSelector::minutes() const
+{
+ return mSelect->isChecked() ? mPeriod->minutes() : 0;
+}
+
+/******************************************************************************
+* Initialise the controls with a specified time period.
+* If minutes = 0, it will be deselected.
+* The time unit combo-box is initialised to 'defaultUnits', but if 'dateOnly'
+* is true, it will never be initialised to hours/minutes.
+*/
+void TimeSelector::setMinutes(int minutes, bool dateOnly, TimePeriod::Units defaultUnits)
+{
+ mSelect->setChecked(minutes);
+ mPeriod->setEnabled(minutes);
+ if (mLabel)
+ mLabel->setEnabled(minutes);
+ mPeriod->setMinutes(minutes, dateOnly, defaultUnits);
+}
+
+/******************************************************************************
+* Set the input focus on the count field.
+*/
+void TimeSelector::setFocusOnCount()
+{
+ mPeriod->setFocusOnCount();
+}
+
+/******************************************************************************
+* Called when the TimeSelector checkbox is toggled.
+*/
+void TimeSelector::selectToggled(bool on)
+{
+ mPeriod->setEnabled(on);
+ if (mLabel)
+ mLabel->setEnabled(on);
+ if (on)
+ mPeriod->setFocus();
+ emit toggled(on);
+ emit valueChanged(minutes());
+}
+
+/******************************************************************************
+* Called when the period value changes.
+*/
+void TimeSelector::periodChanged(int minutes)
+{
+ if (mSelect->isChecked())
+ emit valueChanged(minutes);
+}
diff --git a/kalarm/timeselector.h b/kalarm/timeselector.h
new file mode 100644
index 00000000..9ba66c09
--- /dev/null
+++ b/kalarm/timeselector.h
@@ -0,0 +1,61 @@
+/*
+ * timeselector.h - widget to optionally set a time period
+ * Program: kalarm
+ * Copyright (C) 2004 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.
+ */
+
+#ifndef TIMESELECTOR_H
+#define TIMESELECTOR_H
+
+#include <qframe.h>
+#include "timeperiod.h"
+
+class QLabel;
+class CheckBox;
+
+
+class TimeSelector : public QFrame
+{
+ Q_OBJECT
+ public:
+ TimeSelector(const QString& selectText, const QString& postfix, const QString& selectWhatsThis,
+ const QString& valueWhatsThis, bool allowHourMinute, QWidget* parent, const char* name = 0);
+ bool isChecked() const;
+ void setChecked(bool on);
+ int minutes() const;
+ void setMinutes(int minutes, bool dateOnly, TimePeriod::Units defaultUnits);
+ void setReadOnly(bool);
+ void setDateOnly(bool dateOnly = true);
+ void setMaximum(int hourmin, int days);
+ void setFocusOnCount();
+
+ signals:
+ void toggled(bool); // selection checkbox has been toggled
+ void valueChanged(int minutes); // value has changed
+
+ protected slots:
+ void selectToggled(bool);
+ void periodChanged(int minutes);
+
+ private:
+ CheckBox* mSelect;
+ TimePeriod* mPeriod;
+ QLabel* mLabel;
+ bool mReadOnly; // the widget is read only
+};
+
+#endif // TIMESELECTOR_H
diff --git a/kalarm/traywindow.cpp b/kalarm/traywindow.cpp
new file mode 100644
index 00000000..e968e02b
--- /dev/null
+++ b/kalarm/traywindow.cpp
@@ -0,0 +1,361 @@
+/*
+ * traywindow.cpp - the KDE system tray applet
+ * Program: kalarm
+ * Copyright © 2002-2005,2007 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 "kalarm.h"
+
+#include <stdlib.h>
+
+#include <qtooltip.h>
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kpopupmenu.h>
+#include <kmessagebox.h>
+#include <kstandarddirs.h>
+#include <kstdaction.h>
+#include <kstdguiitem.h>
+#include <kaccel.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include "alarmcalendar.h"
+#include "alarmlistview.h"
+#include "alarmtext.h"
+#include "daemon.h"
+#include "functions.h"
+#include "kalarmapp.h"
+#include "mainwindow.h"
+#include "messagewin.h"
+#include "prefdlg.h"
+#include "preferences.h"
+#include "templatemenuaction.h"
+#include "traywindow.moc"
+
+
+class TrayTooltip : public QToolTip
+{
+ public:
+ TrayTooltip(QWidget* parent) : QToolTip(parent) { }
+ virtual ~TrayTooltip() {}
+ protected:
+ virtual void maybeTip(const QPoint&);
+};
+
+struct TipItem
+{
+ QDateTime dateTime;
+ QString text;
+};
+
+
+/*=============================================================================
+= Class: TrayWindow
+= The KDE system tray window.
+=============================================================================*/
+
+TrayWindow::TrayWindow(MainWindow* parent, const char* name)
+ : KSystemTray((theApp()->wantRunInSystemTray() ? parent : 0), name),
+ mAssocMainWindow(parent)
+{
+ kdDebug(5950) << "TrayWindow::TrayWindow()\n";
+ // Set up GUI icons
+ mPixmapEnabled = loadIcon("kalarm");
+ mPixmapDisabled = loadIcon("kalarm_disabled");
+ if (mPixmapEnabled.isNull() || mPixmapDisabled.isNull())
+ KMessageBox::sorry(this, i18n("Cannot load system tray icon."));
+ setAcceptDrops(true); // allow drag-and-drop onto this window
+
+ // Set up the context menu
+ KActionCollection* actcol = actionCollection();
+ AlarmEnableAction* a = Daemon::createAlarmEnableAction(actcol, "tAlarmEnable");
+ a->plug(contextMenu());
+ connect(a, SIGNAL(switched(bool)), SLOT(setEnabledStatus(bool)));
+ KAlarm::createNewAlarmAction(i18n("&New Alarm..."), this, SLOT(slotNewAlarm()), actcol, "tNew")->plug(contextMenu());
+ KAlarm::createNewFromTemplateAction(i18n("New Alarm From &Template"), this, SLOT(slotNewFromTemplate(const KAEvent&)), actcol, "tNewFromTempl")->plug(contextMenu());
+ KStdAction::preferences(this, SLOT(slotPreferences()), actcol)->plug(contextMenu());
+
+ // Replace the default handler for the Quit context menu item
+ const char* quitName = KStdAction::name(KStdAction::Quit);
+ actcol->remove(actcol->action(quitName));
+ actcol->accel()->remove(quitName);
+ KStdAction::quit(this, SLOT(slotQuit()), actcol);
+
+ // Set icon to correspond with the alarms enabled menu status
+ Daemon::checkStatus();
+ setEnabledStatus(Daemon::monitoringAlarms());
+
+ mTooltip = new TrayTooltip(this);
+}
+
+TrayWindow::~TrayWindow()
+{
+ kdDebug(5950) << "TrayWindow::~TrayWindow()\n";
+ delete mTooltip;
+ mTooltip = 0;
+ theApp()->removeWindow(this);
+ emit deleted();
+}
+
+/******************************************************************************
+* Called just before the context menu is displayed.
+* Update the Alarms Enabled item status.
+*/
+void TrayWindow::contextMenuAboutToShow(KPopupMenu* menu)
+{
+ KSystemTray::contextMenuAboutToShow(menu); // needed for KDE <= 3.1 compatibility
+ Daemon::checkStatus();
+}
+
+/******************************************************************************
+* Called when the "New Alarm" menu item is selected to edit a new alarm.
+*/
+void TrayWindow::slotNewAlarm()
+{
+ MainWindow::executeNew();
+}
+
+/******************************************************************************
+* Called when the "New Alarm" menu item is selected to edit a new alarm.
+*/
+void TrayWindow::slotNewFromTemplate(const KAEvent& event)
+{
+ MainWindow::executeNew(event);
+}
+
+/******************************************************************************
+* Called when the "Configure KAlarm" menu item is selected.
+*/
+void TrayWindow::slotPreferences()
+{
+ KAlarmPrefDlg::display();
+}
+
+/******************************************************************************
+* Called when the Quit context menu item is selected.
+*/
+void TrayWindow::slotQuit()
+{
+ theApp()->doQuit(this);
+}
+
+/******************************************************************************
+* Called when the Alarms Enabled action status has changed.
+* Updates the alarms enabled menu item check state, and the icon pixmap.
+*/
+void TrayWindow::setEnabledStatus(bool status)
+{
+ kdDebug(5950) << "TrayWindow::setEnabledStatus(" << (int)status << ")\n";
+ setPixmap(status ? mPixmapEnabled : mPixmapDisabled);
+}
+
+/******************************************************************************
+* Called when the mouse is clicked over the panel icon.
+* A left click displays the KAlarm main window.
+* A middle button click displays the New Alarm window.
+*/
+void TrayWindow::mousePressEvent(QMouseEvent* e)
+{
+ if (e->button() == LeftButton && !theApp()->wantRunInSystemTray())
+ {
+ // Left click: display/hide the first main window
+ mAssocMainWindow = MainWindow::toggleWindow(mAssocMainWindow);
+ }
+ else if (e->button() == MidButton)
+ MainWindow::executeNew(); // display a New Alarm dialog
+ else
+ KSystemTray::mousePressEvent(e);
+}
+
+/******************************************************************************
+* Called when the mouse is released over the panel icon.
+* The main window (if not hidden) is raised and made the active window.
+* If this is done in mousePressEvent(), it doesn't work.
+*/
+void TrayWindow::mouseReleaseEvent(QMouseEvent* e)
+{
+ if (e->button() == LeftButton && mAssocMainWindow && mAssocMainWindow->isVisible())
+ {
+ mAssocMainWindow->raise();
+ mAssocMainWindow->setActiveWindow();
+ }
+ else
+ KSystemTray::mouseReleaseEvent(e);
+}
+
+/******************************************************************************
+* Called when the drag cursor enters the panel icon.
+*/
+void TrayWindow::dragEnterEvent(QDragEnterEvent* e)
+{
+ MainWindow::executeDragEnterEvent(e);
+}
+
+/******************************************************************************
+* Called when an object is dropped on the panel icon.
+* If the object is recognised, the edit alarm dialog is opened appropriately.
+*/
+void TrayWindow::dropEvent(QDropEvent* e)
+{
+ MainWindow::executeDropEvent(0, e);
+}
+
+/******************************************************************************
+* Return the tooltip text showing alarms due in the next 24 hours.
+* The limit of 24 hours is because only times, not dates, are displayed.
+*/
+void TrayWindow::tooltipAlarmText(QString& text) const
+{
+ KAEvent event;
+ const QString& prefix = Preferences::tooltipTimeToPrefix();
+ int maxCount = Preferences::tooltipAlarmCount();
+ QDateTime now = QDateTime::currentDateTime();
+
+ // Get today's and tomorrow's alarms, sorted in time order
+ QValueList<TipItem> items;
+ QValueList<TipItem>::Iterator iit;
+ KCal::Event::List events = AlarmCalendar::activeCalendar()->eventsWithAlarms(now.date(), now.addDays(1));
+ for (KCal::Event::List::ConstIterator it = events.begin(); it != events.end(); ++it)
+ {
+ KCal::Event* kcalEvent = *it;
+ event.set(*kcalEvent);
+ if (event.enabled() && !event.expired() && event.action() == KAEvent::MESSAGE)
+ {
+ TipItem item;
+ DateTime dateTime = event.displayDateTime();
+ if (dateTime.date() > now.date())
+ {
+ // Ignore alarms after tomorrow at the current clock time
+ if (dateTime.date() != now.date().addDays(1)
+ || dateTime.time() >= now.time())
+ continue;
+ }
+ item.dateTime = dateTime.dateTime();
+
+ // The alarm is due today, or early tomorrow
+ bool space = false;
+ if (Preferences::showTooltipAlarmTime())
+ {
+ item.text += KGlobal::locale()->formatTime(item.dateTime.time());
+ item.text += ' ';
+ space = true;
+ }
+ if (Preferences::showTooltipTimeToAlarm())
+ {
+ int mins = (now.secsTo(item.dateTime) + 59) / 60;
+ if (mins < 0)
+ mins = 0;
+ char minutes[3] = "00";
+ minutes[0] = (mins%60) / 10 + '0';
+ minutes[1] = (mins%60) % 10 + '0';
+ if (Preferences::showTooltipAlarmTime())
+ item.text += i18n("prefix + hours:minutes", "(%1%2:%3)").arg(prefix).arg(mins/60).arg(minutes);
+ else
+ item.text += i18n("prefix + hours:minutes", "%1%2:%3").arg(prefix).arg(mins/60).arg(minutes);
+ item.text += ' ';
+ space = true;
+ }
+ if (space)
+ item.text += ' ';
+ item.text += AlarmText::summary(event);
+
+ // Insert the item into the list in time-sorted order
+ for (iit = items.begin(); iit != items.end(); ++iit)
+ {
+ if (item.dateTime <= (*iit).dateTime)
+ break;
+ }
+ items.insert(iit, item);
+ }
+ }
+ kdDebug(5950) << "TrayWindow::tooltipAlarmText():\n";
+ int count = 0;
+ for (iit = items.begin(); iit != items.end(); ++iit)
+ {
+ kdDebug(5950) << "-- " << (count+1) << ") " << (*iit).text << endl;
+ text += '\n';
+ text += (*iit).text;
+ if (++count == maxCount)
+ break;
+ }
+}
+
+/******************************************************************************
+* Called when the associated main window is closed.
+*/
+void TrayWindow::removeWindow(MainWindow* win)
+{
+ if (win == mAssocMainWindow)
+ mAssocMainWindow = 0;
+}
+
+
+#ifdef HAVE_X11_HEADERS
+ #include <X11/X.h>
+ #include <X11/Xlib.h>
+ #include <X11/Xutil.h>
+#endif
+
+/******************************************************************************
+* Check whether the widget is in the system tray.
+* Note that it is only sometime AFTER the show event that the system tray
+* becomes the widget's parent. So for a definitive status, call this method
+* only after waiting a bit...
+* Reply = true if the widget is in the system tray, or its status can't be determined.
+* = false if it is not currently in the system tray.
+*/
+bool TrayWindow::inSystemTray() const
+{
+#ifdef HAVE_X11_HEADERS
+ Window xParent; // receives parent window
+ Window root;
+ Window* children = 0;
+ unsigned int nchildren;
+ // Find the X parent window of the widget. This is not the same as the Qt parent widget.
+ if (!XQueryTree(qt_xdisplay(), winId(), &root, &xParent, &children, &nchildren))
+ return true; // error determining its parent X window
+ if (children)
+ XFree(children);
+
+ // If it is in the system tray, the system tray window will be its X parent.
+ // Otherwise, the root window will be its X parent.
+ return xParent != root;
+#else
+ return true;
+#endif // HAVE_X11_HEADERS
+}
+
+
+/******************************************************************************
+* Displays the appropriate tooltip depending on preference settings.
+*/
+void TrayTooltip::maybeTip(const QPoint&)
+{
+ TrayWindow* parent = (TrayWindow*)parentWidget();
+ QString text;
+ if (Daemon::monitoringAlarms())
+ text = kapp->aboutData()->programName();
+ else
+ text = i18n("%1 - disabled").arg(kapp->aboutData()->programName());
+ kdDebug(5950) << "TrayTooltip::maybeTip(): " << text << endl;
+ if (Preferences::tooltipAlarmCount())
+ parent->tooltipAlarmText(text);
+ tip(parent->rect(), text);
+}
diff --git a/kalarm/traywindow.h b/kalarm/traywindow.h
new file mode 100644
index 00000000..e2e2d5d9
--- /dev/null
+++ b/kalarm/traywindow.h
@@ -0,0 +1,71 @@
+/*
+ * traywindow.h - the KDE system tray applet
+ * Program: kalarm
+ * Copyright © 2002-2004,2007 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.
+ */
+
+#ifndef TRAYWINDOW_H
+#define TRAYWINDOW_H
+
+#include <qpixmap.h>
+#include <ksystemtray.h>
+class KPopupMenu;
+
+class KAEvent;
+class MainWindow;
+class TrayTooltip;
+
+class TrayWindow : public KSystemTray
+{
+ Q_OBJECT
+ public:
+ TrayWindow(MainWindow* parent, const char* name = 0);
+ ~TrayWindow();
+ void removeWindow(MainWindow*);
+ MainWindow* assocMainWindow() const { return mAssocMainWindow; }
+ void setAssocMainWindow(MainWindow* win) { mAssocMainWindow = win; }
+ bool inSystemTray() const;
+ void tooltipAlarmText(QString& text) const;
+
+ public slots:
+ void slotQuit();
+
+ signals:
+ void deleted();
+
+ protected:
+ virtual void contextMenuAboutToShow(KPopupMenu*);
+ virtual void mousePressEvent(QMouseEvent*);
+ virtual void mouseReleaseEvent(QMouseEvent*);
+ virtual void dragEnterEvent(QDragEnterEvent*);
+ virtual void dropEvent(QDropEvent*);
+
+ private slots:
+ void slotNewAlarm();
+ void slotNewFromTemplate(const KAEvent&);
+ void slotPreferences();
+ void setEnabledStatus(bool status);
+
+ private:
+ friend class TrayTooltip;
+
+ MainWindow* mAssocMainWindow; // main window associated with this, or null
+ QPixmap mPixmapEnabled, mPixmapDisabled;
+ TrayTooltip* mTooltip;
+};
+
+#endif // TRAYWINDOW_H
diff --git a/kalarm/undo.cpp b/kalarm/undo.cpp
new file mode 100644
index 00000000..bab085ae
--- /dev/null
+++ b/kalarm/undo.cpp
@@ -0,0 +1,1127 @@
+/*
+ * undo.cpp - undo/redo facility
+ * Program: kalarm
+ * Copyright © 2005,2006 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 "kalarm.h"
+
+#include <qobject.h>
+#include <qstringlist.h>
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include "alarmcalendar.h"
+#include "alarmevent.h"
+#include "alarmtext.h"
+#include "functions.h"
+#include "undo.moc"
+
+static int maxCount = 12;
+
+
+class UndoItem
+{
+ public:
+ enum Operation { ADD, EDIT, DELETE, REACTIVATE, DEACTIVATE, MULTI };
+ UndoItem(); // needed by QValueList
+ virtual ~UndoItem();
+ virtual Operation operation() const = 0;
+ virtual QString actionText() const = 0;
+ virtual QString description() const { return QString::null; }
+ virtual QString eventID() const { return QString::null; }
+ virtual QString oldEventID() const { return QString::null; }
+ virtual QString newEventID() const { return QString::null; }
+ int id() const { return mId; }
+ Undo::Type type() const { return mType; }
+ void setType(Undo::Type t) { mType = t; }
+ KAEvent::Status calendar() const { return mCalendar; }
+ virtual void setCalendar(KAEvent::Status s) { mCalendar = s; }
+ virtual UndoItem* restore() = 0;
+ virtual bool deleteID(const QString& /*id*/) { return false; }
+
+ enum Error { ERR_NONE, ERR_PROG, ERR_NOT_FOUND, ERR_CREATE, ERR_TEMPLATE, ERR_EXPIRED };
+ enum Warning { WARN_NONE, WARN_KORG_ADD, WARN_KORG_MODIFY, WARN_KORG_DELETE };
+ static int mLastId;
+ static Error mRestoreError; // error code valid only if restore() returns 0
+ static Warning mRestoreWarning; // warning code set by restore()
+ static int mRestoreWarningCount; // item count for mRestoreWarning (to allow i18n messages to work correctly)
+
+ protected:
+ UndoItem(Undo::Type);
+ static QString addDeleteActionText(KAEvent::Status, bool add);
+ QString description(const KAEvent&) const;
+ void replaceWith(UndoItem* item) { Undo::replace(this, item); }
+
+ int mId; // unique identifier (only for mType = UNDO, REDO)
+ Undo::Type mType; // which list (if any) the object is in
+ KAEvent::Status mCalendar;
+};
+
+class UndoMultiBase : public UndoItem
+{
+ public:
+ UndoMultiBase(Undo::Type t) : UndoItem(t) { }
+ UndoMultiBase(Undo::Type t, Undo::List& undos) : UndoItem(t), mUndos(undos) { }
+ ~UndoMultiBase();
+ const Undo::List& undos() const { return mUndos; }
+ protected:
+ Undo::List mUndos; // this list must always have >= 2 entries
+};
+
+template <class T> class UndoMulti : public UndoMultiBase
+{
+ public:
+ UndoMulti(Undo::Type, const QValueList<KAEvent>&);
+ UndoMulti(Undo::Type t, Undo::List& undos) : UndoMultiBase(t, undos) { }
+ virtual Operation operation() const { return MULTI; }
+ virtual UndoItem* restore();
+ virtual bool deleteID(const QString& id);
+ virtual UndoItem* createRedo(Undo::List&) = 0;
+};
+
+class UndoAdd : public UndoItem
+{
+ public:
+ UndoAdd(Undo::Type, const KAEvent&);
+ UndoAdd(Undo::Type, const KAEvent&, KAEvent::Status);
+ virtual Operation operation() const { return ADD; }
+ virtual QString actionText() const;
+ virtual QString description() const { return mDescription; }
+ virtual QString eventID() const { return mEventID; }
+ virtual QString newEventID() const { return mEventID; }
+ virtual UndoItem* restore() { return doRestore(); }
+ protected:
+ UndoItem* doRestore(bool setArchive = false);
+ virtual UndoItem* createRedo(const KAEvent&);
+ private:
+ QString mEventID;
+ QString mDescription;
+};
+
+class UndoEdit : public UndoItem
+{
+ public:
+ UndoEdit(Undo::Type, const KAEvent& oldEvent, const QString& newEventID, const QString& description);
+ ~UndoEdit();
+ virtual Operation operation() const { return EDIT; }
+ virtual QString actionText() const;
+ virtual QString description() const { return mDescription; }
+ virtual QString eventID() const { return mNewEventID; }
+ virtual QString oldEventID() const { return mOldEvent->id(); }
+ virtual QString newEventID() const { return mNewEventID; }
+ virtual UndoItem* restore();
+ private:
+ KAEvent* mOldEvent;
+ QString mNewEventID;
+ QString mDescription;
+};
+
+class UndoDelete : public UndoItem
+{
+ public:
+ UndoDelete(Undo::Type, const KAEvent&);
+ ~UndoDelete();
+ virtual Operation operation() const { return DELETE; }
+ virtual QString actionText() const;
+ virtual QString description() const { return UndoItem::description(*mEvent); }
+ virtual QString eventID() const { return mEvent->id(); }
+ virtual QString oldEventID() const { return mEvent->id(); }
+ virtual UndoItem* restore();
+ KAEvent* event() const { return mEvent; }
+ protected:
+ virtual UndoItem* createRedo(const KAEvent&);
+ private:
+ KAEvent* mEvent;
+};
+
+class UndoReactivate : public UndoAdd
+{
+ public:
+ UndoReactivate(Undo::Type t, const KAEvent& e) : UndoAdd(t, e, KAEvent::ACTIVE) { }
+ virtual Operation operation() const { return REACTIVATE; }
+ virtual QString actionText() const;
+ virtual UndoItem* restore();
+ protected:
+ virtual UndoItem* createRedo(const KAEvent&);
+};
+
+class UndoDeactivate : public UndoDelete
+{
+ public:
+ UndoDeactivate(Undo::Type t, const KAEvent& e) : UndoDelete(t, e) { }
+ virtual Operation operation() const { return DEACTIVATE; }
+ virtual QString actionText() const;
+ virtual UndoItem* restore();
+ protected:
+ virtual UndoItem* createRedo(const KAEvent&);
+};
+
+class UndoDeletes : public UndoMulti<UndoDelete>
+{
+ public:
+ UndoDeletes(Undo::Type t, const QValueList<KAEvent>& events)
+ : UndoMulti<UndoDelete>(t, events) { } // UNDO only
+ UndoDeletes(Undo::Type t, Undo::List& undos)
+ : UndoMulti<UndoDelete>(t, undos) { }
+ virtual QString actionText() const;
+ virtual UndoItem* createRedo(Undo::List&);
+};
+
+class UndoReactivates : public UndoMulti<UndoReactivate>
+{
+ public:
+ UndoReactivates(Undo::Type t, const QValueList<KAEvent>& events)
+ : UndoMulti<UndoReactivate>(t, events) { } // UNDO only
+ UndoReactivates(Undo::Type t, Undo::List& undos)
+ : UndoMulti<UndoReactivate>(t, undos) { }
+ virtual QString actionText() const;
+ virtual UndoItem* createRedo(Undo::List&);
+};
+
+Undo* Undo::mInstance = 0;
+Undo::List Undo::mUndoList;
+Undo::List Undo::mRedoList;
+
+
+/******************************************************************************
+* Create the one and only instance of the Undo class.
+*/
+Undo* Undo::instance()
+{
+ if (!mInstance)
+ mInstance = new Undo(kapp);
+ return mInstance;
+}
+
+/******************************************************************************
+* Clear the lists of undo and redo items.
+*/
+void Undo::clear()
+{
+ if (!mUndoList.isEmpty() || !mRedoList.isEmpty())
+ {
+ mInstance->blockSignals(true);
+ while (mUndoList.count())
+ delete mUndoList.first(); // N.B. 'delete' removes the object from the list
+ while (mRedoList.count())
+ delete mRedoList.first(); // N.B. 'delete' removes the object from the list
+ mInstance->blockSignals(false);
+ emitChanged();
+ }
+}
+
+/******************************************************************************
+* Create an undo item and add it to the list of undos.
+* N.B. The base class constructor adds the object to the undo list.
+*/
+void Undo::saveAdd(const KAEvent& event)
+{
+ new UndoAdd(UNDO, event);
+ emitChanged();
+}
+
+void Undo::saveEdit(const KAEvent& oldEvent, const KAEvent& newEvent)
+{
+ new UndoEdit(UNDO, oldEvent, newEvent.id(), AlarmText::summary(newEvent));
+ removeRedos(oldEvent.id()); // remove any redos which are made invalid by this edit
+ emitChanged();
+}
+
+void Undo::saveDelete(const KAEvent& event)
+{
+ new UndoDelete(UNDO, event);
+ removeRedos(event.id()); // remove any redos which are made invalid by this deletion
+ emitChanged();
+}
+
+void Undo::saveDeletes(const QValueList<KAEvent>& events)
+{
+ int count = events.count();
+ if (count == 1)
+ saveDelete(events.first());
+ else if (count > 1)
+ {
+ new UndoDeletes(UNDO, events);
+ for (QValueList<KAEvent>::ConstIterator it = events.begin(); it != events.end(); ++it)
+ removeRedos((*it).id()); // remove any redos which are made invalid by these deletions
+ emitChanged();
+ }
+}
+
+void Undo::saveReactivate(const KAEvent& event)
+{
+ new UndoReactivate(UNDO, event);
+ emitChanged();
+}
+
+void Undo::saveReactivates(const QValueList<KAEvent>& events)
+{
+ int count = events.count();
+ if (count == 1)
+ saveReactivate(events.first());
+ else if (count > 1)
+ {
+ new UndoReactivates(UNDO, events);
+ emitChanged();
+ }
+}
+
+/******************************************************************************
+* Remove any redos which are made invalid by a new undo.
+*/
+void Undo::removeRedos(const QString& eventID)
+{
+ QString id = eventID;
+ for (Iterator it = mRedoList.begin(); it != mRedoList.end(); )
+ {
+ UndoItem* item = *it;
+//kdDebug(5950)<<"removeRedos(): "<<item->eventID()<<" (looking for "<<id<<")"<<endl;
+ if (item->operation() == UndoItem::MULTI)
+ {
+ if (item->deleteID(id))
+ {
+ // The old multi-redo was replaced with a new single redo
+ delete item;
+ }
+ ++it;
+ }
+ else if (item->eventID() == id)
+ {
+ if (item->operation() == UndoItem::EDIT)
+ id = item->oldEventID(); // continue looking for its post-edit ID
+ item->setType(NONE); // prevent the destructor removing it from the list
+ delete item;
+ it = mRedoList.remove(it);
+ }
+ else
+ ++it;
+ }
+}
+
+/******************************************************************************
+* Undo or redo a specified item.
+* Reply = true if success, or if the item no longer exists.
+*/
+bool Undo::undo(Undo::Iterator it, Undo::Type type, QWidget* parent, const QString& action)
+{
+ UndoItem::mRestoreError = UndoItem::ERR_NONE;
+ UndoItem::mRestoreWarning = UndoItem::WARN_NONE;
+ UndoItem::mRestoreWarningCount = 0;
+ if (it != mUndoList.end() && it != mRedoList.end() && (*it)->type() == type)
+ {
+ (*it)->restore();
+ delete *it; // N.B. 'delete' removes the object from its list
+ emitChanged();
+ }
+
+ QString err;
+ switch (UndoItem::mRestoreError)
+ {
+ case UndoItem::ERR_NONE:
+ {
+ KAlarm::KOrgUpdateError errcode;
+ switch (UndoItem::mRestoreWarning)
+ {
+ case UndoItem::WARN_KORG_ADD: errcode = KAlarm::KORG_ERR_ADD; break;
+ case UndoItem::WARN_KORG_MODIFY: errcode = KAlarm::KORG_ERR_MODIFY; break;
+ case UndoItem::WARN_KORG_DELETE: errcode = KAlarm::KORG_ERR_DELETE; break;
+ case UndoItem::WARN_NONE:
+ default:
+ return true;
+ }
+ KAlarm::displayKOrgUpdateError(parent, errcode, UndoItem::mRestoreWarningCount);
+ return true;
+ }
+ case UndoItem::ERR_NOT_FOUND: err = i18n("Alarm not found"); break;
+ case UndoItem::ERR_CREATE: err = i18n("Error recreating alarm"); break;
+ case UndoItem::ERR_TEMPLATE: err = i18n("Error recreating alarm template"); break;
+ case UndoItem::ERR_EXPIRED: err = i18n("Cannot reactivate expired alarm"); break;
+ case UndoItem::ERR_PROG: err = i18n("Program error"); break;
+ default: err = i18n("Unknown error"); break;
+ }
+ KMessageBox::sorry(parent, i18n("Undo-action: message", "%1: %2").arg(action).arg(err));
+ return false;
+}
+
+/******************************************************************************
+* Add an undo item to the start of one of the lists.
+*/
+void Undo::add(UndoItem* item, bool undo)
+{
+ if (item)
+ {
+ // Limit the number of items stored
+ int undoCount = mUndoList.count();
+ int redoCount = mRedoList.count();
+ if (undoCount + redoCount >= maxCount - 1)
+ {
+ if (undoCount)
+ mUndoList.pop_back();
+ else
+ mRedoList.pop_back();
+ }
+
+ // Append the new item
+ List* list = undo ? &mUndoList : &mRedoList;
+ list->prepend(item);
+ }
+}
+
+/******************************************************************************
+* Remove an undo item from one of the lists.
+*/
+void Undo::remove(UndoItem* item, bool undo)
+{
+ List* list = undo ? &mUndoList : &mRedoList;
+ if (!list->isEmpty())
+ list->remove(item);
+}
+
+/******************************************************************************
+* Replace an undo item in one of the lists.
+*/
+void Undo::replace(UndoItem* old, UndoItem* New)
+{
+ Type type = old->type();
+ List* list = (type == UNDO) ? &mUndoList : (type == REDO) ? &mRedoList : 0;
+ if (!list)
+ return;
+ Iterator it = list->find(old);
+ if (it != list->end())
+ {
+ New->setType(type); // ensure the item points to the correct list
+ *it = New;
+ old->setType(NONE); // mark the old item as no longer being in a list
+ }
+}
+
+/******************************************************************************
+* Return the action description of the latest undo/redo item.
+*/
+QString Undo::actionText(Undo::Type type)
+{
+ List* list = (type == UNDO) ? &mUndoList : (type == REDO) ? &mRedoList : 0;
+ return (list && !list->isEmpty()) ? list->first()->actionText() : QString::null;
+}
+
+/******************************************************************************
+* Return the action description of the undo/redo item with the specified ID.
+*/
+QString Undo::actionText(Undo::Type type, int id)
+{
+ UndoItem* undo = getItem(id, type);
+ return undo ? undo->actionText() : QString::null;
+}
+
+/******************************************************************************
+* Return the alarm description of the undo/redo item with the specified ID.
+*/
+QString Undo::description(Undo::Type type, int id)
+{
+ UndoItem* undo = getItem(id, type);
+ return undo ? undo->description() : QString::null;
+}
+
+/******************************************************************************
+* Return the descriptions of all undo or redo items, in order latest first.
+* For alarms which have undergone more than one change, only the first one is
+* listed, to force dependent undos to be executed in their correct order.
+* If 'ids' is non-null, also returns a list of their corresponding IDs.
+*/
+QValueList<int> Undo::ids(Undo::Type type)
+{
+ QValueList<int> ids;
+ QStringList ignoreIDs;
+//int n=0;
+ List* list = (type == UNDO) ? &mUndoList : (type == REDO) ? &mRedoList : 0;
+ if (!list)
+ return ids;
+ for (Iterator it = list->begin(); it != list->end(); ++it)
+ {
+ // Check whether this item should be ignored because it is a
+ // deendent undo. If not, add this item's ID to the ignore list.
+ UndoItem* item = *it;
+ bool omit = false;
+ if (item->operation() == UndoItem::MULTI)
+ {
+ // If any item in a multi-undo is disqualified, omit the whole multi-undo
+ QStringList newIDs;
+ const Undo::List& undos = ((UndoMultiBase*)item)->undos();
+ for (Undo::List::ConstIterator u = undos.begin(); u != undos.end(); ++u)
+ {
+ QString evid = (*u)->eventID();
+ if (ignoreIDs.find(evid) != ignoreIDs.end())
+ omit = true;
+ else if (omit)
+ ignoreIDs.append(evid);
+ else
+ newIDs.append(evid);
+ }
+ if (omit)
+ {
+ for (QStringList::ConstIterator i = newIDs.begin(); i != newIDs.end(); ++i)
+ ignoreIDs.append(*i);
+ }
+ }
+ else
+ {
+ omit = (ignoreIDs.find(item->eventID()) != ignoreIDs.end());
+ if (!omit)
+ ignoreIDs.append(item->eventID());
+ if (item->operation() == UndoItem::EDIT)
+ ignoreIDs.append(item->oldEventID()); // continue looking for its post-edit ID
+ }
+ if (!omit)
+ ids.append(item->id());
+//else kdDebug(5950)<<"Undo::ids(): omit "<<item->actionText()<<": "<<item->description()<<endl;
+ }
+//kdDebug(5950)<<"Undo::ids(): "<<n<<" -> "<<ids.count()<<endl;
+ return ids;
+}
+
+/******************************************************************************
+* Emit the appropriate 'changed' signal.
+*/
+void Undo::emitChanged()
+{
+ if (mInstance)
+ mInstance->emitChanged(actionText(UNDO), actionText(REDO));
+}
+
+/******************************************************************************
+* Return the item with the specified ID.
+*/
+UndoItem* Undo::getItem(int id, Undo::Type type)
+{
+ List* list = (type == UNDO) ? &mUndoList : (type == REDO) ? &mRedoList : 0;
+ if (list)
+ {
+ for (Iterator it = list->begin(); it != list->end(); ++it)
+ {
+ if ((*it)->id() == id)
+ return *it;
+ }
+ }
+ return 0;
+}
+
+/******************************************************************************
+* Find an item with the specified ID.
+*/
+Undo::Iterator Undo::findItem(int id, Undo::Type type)
+{
+ List* list = (type == UNDO) ? &mUndoList : &mRedoList;
+ Iterator it;
+ for (it = list->begin(); it != list->end(); ++it)
+ {
+ if ((*it)->id() == id)
+ break;
+ }
+ return it;
+}
+
+
+/*=============================================================================
+= Class: UndoItem
+= A single undo action.
+=============================================================================*/
+int UndoItem::mLastId = 0;
+UndoItem::Error UndoItem::mRestoreError;
+UndoItem::Warning UndoItem::mRestoreWarning;
+int UndoItem::mRestoreWarningCount;
+
+/******************************************************************************
+* Constructor.
+* Optionally appends the undo to the list of undos.
+*/
+UndoItem::UndoItem(Undo::Type type)
+ : mId(0),
+ mType(type)
+{
+ if (type != Undo::NONE)
+ {
+ mId = ++mLastId;
+ if (mId < 0)
+ mId = mLastId = 1; // wrap round if we reach a negative number
+ Undo::add(this, (mType == Undo::UNDO));
+ }
+}
+
+/******************************************************************************
+* Destructor.
+* Removes the undo from the list (if it's in the list).
+*/
+UndoItem::~UndoItem()
+{
+ if (mType != Undo::NONE)
+ Undo::remove(this, (mType == Undo::UNDO));
+}
+
+/******************************************************************************
+* Return the description of an event.
+*/
+QString UndoItem::description(const KAEvent& event) const
+{
+ return (mCalendar == KAEvent::TEMPLATE) ? event.templateName() : AlarmText::summary(event);
+}
+
+/******************************************************************************
+* Return the action description of an add or delete Undo/Redo item for displaying.
+*/
+QString UndoItem::addDeleteActionText(KAEvent::Status calendar, bool add)
+{
+ switch (calendar)
+ {
+ case KAEvent::ACTIVE:
+ if (add)
+ return i18n("Action to create a new alarm", "New alarm");
+ else
+ return i18n("Action to delete an alarm", "Delete alarm");
+ case KAEvent::TEMPLATE:
+ if (add)
+ return i18n("Action to create a new alarm template", "New template");
+ else
+ return i18n("Action to delete an alarm template", "Delete template");
+ case KAEvent::EXPIRED:
+ return i18n("Delete expired alarm");
+ default:
+ break;
+ }
+ return QString::null;
+}
+
+
+/*=============================================================================
+= Class: UndoMultiBase
+= Undo item for multiple alarms.
+=============================================================================*/
+
+template <class T>
+UndoMulti<T>::UndoMulti(Undo::Type type, const QValueList<KAEvent>& events)
+ : UndoMultiBase(type) // UNDO only
+{
+ for (QValueList<KAEvent>::ConstIterator it = events.begin(); it != events.end(); ++it)
+ mUndos.append(new T(Undo::NONE, *it));
+}
+
+UndoMultiBase::~UndoMultiBase()
+{
+ for (Undo::List::Iterator it = mUndos.begin(); it != mUndos.end(); ++it)
+ delete *it;
+}
+
+/******************************************************************************
+* Undo the item, i.e. restore multiple alarms which were deleted (or delete
+* alarms which were restored).
+* Create a redo item to delete (or restore) the alarms again.
+* Reply = redo item.
+*/
+template <class T>
+UndoItem* UndoMulti<T>::restore()
+{
+ Undo::List newUndos;
+ for (Undo::List::Iterator it = mUndos.begin(); it != mUndos.end(); ++it)
+ {
+ UndoItem* undo = (*it)->restore();
+ if (undo)
+ newUndos.append(undo);
+ }
+ if (newUndos.isEmpty())
+ return 0;
+
+ // Create a redo item to delete the alarm again
+ return createRedo(newUndos);
+}
+
+/******************************************************************************
+* If one of the multiple items has the specified ID, delete it.
+* If an item is deleted and there is only one item left, the UndoMulti
+* instance is removed from its list and replaced by the remaining UndoItem instead.
+* Reply = true if this instance was replaced. The caller must delete it.
+* = false otherwise.
+*/
+template <class T>
+bool UndoMulti<T>::deleteID(const QString& id)
+{
+ for (Undo::List::Iterator it = mUndos.begin(); it != mUndos.end(); ++it)
+ {
+ UndoItem* item = *it;
+ if (item->eventID() == id)
+ {
+ // Found a matching entry - remove it
+ mUndos.remove(it);
+ if (mUndos.count() == 1)
+ {
+ // There is only one entry left after removal.
+ // Replace 'this' multi instance with the remaining single entry.
+ replaceWith(item);
+ return true;
+ }
+ else
+ {
+ delete item;
+ return false;
+ }
+ }
+ }
+ return false;
+}
+
+
+/*=============================================================================
+= Class: UndoAdd
+= Undo item for alarm creation.
+=============================================================================*/
+
+UndoAdd::UndoAdd(Undo::Type type, const KAEvent& event)
+ : UndoItem(type),
+ mEventID(event.id())
+{
+ setCalendar(KAEvent::uidStatus(mEventID));
+ mDescription = UndoItem::description(event); // calendar must be set before calling this
+}
+
+UndoAdd::UndoAdd(Undo::Type type, const KAEvent& event, KAEvent::Status cal)
+ : UndoItem(type),
+ mEventID(KAEvent::uid(event.id(), cal))
+{
+ setCalendar(cal);
+ mDescription = UndoItem::description(event); // calendar must be set before calling this
+}
+
+/******************************************************************************
+* Undo the item, i.e. delete the alarm which was added.
+* Create a redo item to add the alarm back again.
+* Reply = redo item.
+*/
+UndoItem* UndoAdd::doRestore(bool setArchive)
+{
+ // Retrieve the current state of the alarm
+ kdDebug(5950) << "UndoAdd::doRestore(" << mEventID << ")\n";
+ const KCal::Event* kcalEvent = AlarmCalendar::getEvent(mEventID);
+ if (!kcalEvent)
+ {
+ mRestoreError = ERR_NOT_FOUND; // alarm is no longer in calendar
+ return 0;
+ }
+ KAEvent event(*kcalEvent);
+
+ // Create a redo item to recreate the alarm.
+ // Do it now, since 'event' gets modified by KAlarm::deleteEvent()
+ UndoItem* undo = createRedo(event);
+
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ if (setArchive)
+ event.setArchive();
+ // Archive it if it has already triggered
+ switch (KAlarm::deleteEvent(event, true))
+ {
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ mRestoreError = ERR_CREATE;
+ break;
+ case KAlarm::UPDATE_KORG_ERR:
+ mRestoreWarning = WARN_KORG_DELETE;
+ ++mRestoreWarningCount;
+ break;
+ default:
+ break;
+ }
+ break;
+ case KAEvent::TEMPLATE:
+ if (KAlarm::deleteTemplate(event) != KAlarm::UPDATE_OK)
+ mRestoreError = ERR_TEMPLATE;
+ break;
+ case KAEvent::EXPIRED: // redoing the deletion of an expired alarm
+ KAlarm::deleteEvent(event);
+ break;
+ default:
+ delete undo;
+ mRestoreError = ERR_PROG;
+ return 0;
+ }
+ return undo;
+}
+
+/******************************************************************************
+* Create a redo item to add the alarm back again.
+*/
+UndoItem* UndoAdd::createRedo(const KAEvent& event)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoDelete(t, event);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoAdd::actionText() const
+{
+ return addDeleteActionText(calendar(), (type() == Undo::UNDO));
+}
+
+
+/*=============================================================================
+= Class: UndoEdit
+= Undo item for alarm edit.
+=============================================================================*/
+
+UndoEdit::UndoEdit(Undo::Type type, const KAEvent& oldEvent, const QString& newEventID, const QString& description)
+ : UndoItem(type),
+ mOldEvent(new KAEvent(oldEvent)),
+ mNewEventID(newEventID),
+ mDescription(description)
+{
+ setCalendar(KAEvent::uidStatus(mNewEventID));
+}
+
+UndoEdit::~UndoEdit()
+{
+ delete mOldEvent;
+}
+
+/******************************************************************************
+* Undo the item, i.e. undo an edit to a previously existing alarm.
+* Create a redo item to reapply the edit.
+* Reply = redo item.
+*/
+UndoItem* UndoEdit::restore()
+{
+ kdDebug(5950) << "UndoEdit::restore(" << mNewEventID << ")\n";
+ // Retrieve the current state of the alarm
+ const KCal::Event* kcalEvent = AlarmCalendar::getEvent(mNewEventID);
+ if (!kcalEvent)
+ {
+ mRestoreError = ERR_NOT_FOUND; // alarm is no longer in calendar
+ return 0;
+ }
+ KAEvent newEvent(*kcalEvent);
+
+ // Create a redo item to restore the edit
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ UndoItem* undo = new UndoEdit(t, newEvent, mOldEvent->id(), mDescription);
+
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ switch (KAlarm::modifyEvent(newEvent, *mOldEvent, 0))
+ {
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ mRestoreError = ERR_CREATE;
+ break;
+ case KAlarm::UPDATE_KORG_ERR:
+ mRestoreWarning = WARN_KORG_MODIFY;
+ ++mRestoreWarningCount;
+ break;
+ default:
+ break;
+ }
+ break;
+ case KAEvent::TEMPLATE:
+ if (KAlarm::updateTemplate(*mOldEvent, 0) != KAlarm::UPDATE_OK)
+ mRestoreError = ERR_TEMPLATE;
+ break;
+ case KAEvent::EXPIRED: // editing of expired events is not allowed
+ default:
+ delete undo;
+ mRestoreError = ERR_PROG;
+ return 0;
+ }
+ return undo;
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoEdit::actionText() const
+{
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ return i18n("Action to edit an alarm", "Edit alarm");
+ case KAEvent::TEMPLATE:
+ return i18n("Action to edit an alarm template", "Edit template");
+ default:
+ break;
+ }
+ return QString::null;
+}
+
+
+/*=============================================================================
+= Class: UndoDelete
+= Undo item for alarm deletion.
+=============================================================================*/
+
+UndoDelete::UndoDelete(Undo::Type type, const KAEvent& event)
+ : UndoItem(type),
+ mEvent(new KAEvent(event))
+{
+ setCalendar(KAEvent::uidStatus(mEvent->id()));
+}
+
+UndoDelete::~UndoDelete()
+{
+ delete mEvent;
+}
+
+/******************************************************************************
+* Undo the item, i.e. restore an alarm which was deleted.
+* Create a redo item to delete the alarm again.
+* Reply = redo item.
+*/
+UndoItem* UndoDelete::restore()
+{
+ kdDebug(5950) << "UndoDelete::restore(" << mEvent->id() << ")\n";
+ // Restore the original event
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ if (mEvent->toBeArchived())
+ {
+ // It was archived when it was deleted
+ mEvent->setUid(KAEvent::EXPIRED);
+ switch (KAlarm::reactivateEvent(*mEvent, 0, true))
+ {
+ case KAlarm::UPDATE_KORG_ERR:
+ mRestoreWarning = WARN_KORG_ADD;
+ ++mRestoreWarningCount;
+ break;
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ mRestoreError = ERR_EXPIRED;
+ return 0;
+ case KAlarm::UPDATE_OK:
+ break;
+ }
+ }
+ else
+ {
+ switch (KAlarm::addEvent(*mEvent, 0, 0, true))
+ {
+ case KAlarm::UPDATE_KORG_ERR:
+ mRestoreWarning = WARN_KORG_ADD;
+ ++mRestoreWarningCount;
+ break;
+ case KAlarm::UPDATE_ERROR:
+ case KAlarm::UPDATE_FAILED:
+ case KAlarm::SAVE_FAILED:
+ mRestoreError = ERR_CREATE;
+ return 0;
+ case KAlarm::UPDATE_OK:
+ break;
+ }
+ }
+ break;
+ case KAEvent::TEMPLATE:
+ if (KAlarm::addTemplate(*mEvent, 0) != KAlarm::UPDATE_OK)
+ {
+ mRestoreError = ERR_CREATE;
+ return 0;
+ }
+ break;
+ case KAEvent::EXPIRED:
+ if (!KAlarm::addExpiredEvent(*mEvent))
+ {
+ mRestoreError = ERR_CREATE;
+ return 0;
+ }
+ break;
+ default:
+ mRestoreError = ERR_PROG;
+ return 0;
+ }
+
+ // Create a redo item to delete the alarm again
+ return createRedo(*mEvent);
+}
+
+/******************************************************************************
+* Create a redo item to archive the alarm again.
+*/
+UndoItem* UndoDelete::createRedo(const KAEvent& event)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoAdd(t, event);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoDelete::actionText() const
+{
+ return addDeleteActionText(calendar(), (type() == Undo::REDO));
+}
+
+
+/*=============================================================================
+= Class: UndoDeletes
+= Undo item for multiple alarm deletion.
+=============================================================================*/
+
+/******************************************************************************
+* Create a redo item to delete the alarms again.
+*/
+UndoItem* UndoDeletes::createRedo(Undo::List& undos)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoDeletes(t, undos);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoDeletes::actionText() const
+{
+ if (mUndos.isEmpty())
+ return QString::null;
+ for (Undo::List::ConstIterator it = mUndos.begin(); it != mUndos.end(); ++it)
+ {
+ switch ((*it)->calendar())
+ {
+ case KAEvent::ACTIVE:
+ return i18n("Delete multiple alarms");
+ case KAEvent::TEMPLATE:
+ return i18n("Delete multiple templates");
+ case KAEvent::EXPIRED:
+ break; // check if they are ALL expired
+ default:
+ return QString::null;
+ }
+ }
+ return i18n("Delete multiple expired alarms");
+}
+
+
+/*=============================================================================
+= Class: UndoReactivate
+= Undo item for alarm reactivation.
+=============================================================================*/
+
+/******************************************************************************
+* Undo the item, i.e. re-archive the alarm which was reactivated.
+* Create a redo item to reactivate the alarm back again.
+* Reply = redo item.
+*/
+UndoItem* UndoReactivate::restore()
+{
+ kdDebug(5950) << "UndoReactivate::restore()\n";
+ // Validate the alarm's calendar
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ break;
+ default:
+ mRestoreError = ERR_PROG;
+ return 0;
+ }
+ return UndoAdd::doRestore(true); // restore alarm, ensuring that it is re-archived
+}
+
+/******************************************************************************
+* Create a redo item to add the alarm back again.
+*/
+UndoItem* UndoReactivate::createRedo(const KAEvent& event)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoDeactivate(t, event);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoReactivate::actionText() const
+{
+ return i18n("Reactivate alarm");
+}
+
+
+/*=============================================================================
+= Class: UndoDeactivate
+= Redo item for alarm reactivation.
+=============================================================================*/
+
+/******************************************************************************
+* Undo the item, i.e. reactivate an alarm which was archived.
+* Create a redo item to archive the alarm again.
+* Reply = redo item.
+*/
+UndoItem* UndoDeactivate::restore()
+{
+ kdDebug(5950) << "UndoDeactivate::restore()\n";
+ // Validate the alarm's calendar
+ switch (calendar())
+ {
+ case KAEvent::ACTIVE:
+ break;
+ default:
+ mRestoreError = ERR_PROG;
+ return 0;
+ }
+
+ return UndoDelete::restore();
+}
+
+/******************************************************************************
+* Create a redo item to archive the alarm again.
+*/
+UndoItem* UndoDeactivate::createRedo(const KAEvent& event)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoReactivate(t, event);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoDeactivate::actionText() const
+{
+ return i18n("Reactivate alarm");
+}
+
+
+/*=============================================================================
+= Class: UndoReactivates
+= Undo item for multiple alarm reactivation.
+=============================================================================*/
+
+/******************************************************************************
+* Create a redo item to reactivate the alarms again.
+*/
+UndoItem* UndoReactivates::createRedo(Undo::List& undos)
+{
+ Undo::Type t = (type() == Undo::UNDO) ? Undo::REDO : (type() == Undo::REDO) ? Undo::UNDO : Undo::NONE;
+ return new UndoReactivates(t, undos);
+}
+
+/******************************************************************************
+* Return the action description of the Undo item for displaying.
+*/
+QString UndoReactivates::actionText() const
+{
+ return i18n("Reactivate multiple alarms");
+}
diff --git a/kalarm/undo.h b/kalarm/undo.h
new file mode 100644
index 00000000..99e06f97
--- /dev/null
+++ b/kalarm/undo.h
@@ -0,0 +1,93 @@
+/*
+ * undo.h - undo/redo facility
+ * Program: kalarm
+ * Copyright (C) 2005 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.
+ */
+
+#ifndef UNDO_H
+#define UNDO_H
+
+/** @file undo.h - undo/redo facility */
+
+#include <qvaluelist.h>
+#include <qstringlist.h>
+
+class KAEvent;
+class UndoItem;
+
+
+class Undo : public QObject
+{
+ Q_OBJECT
+ public:
+ enum Type { NONE, UNDO, REDO };
+
+ static Undo* instance();
+ static void saveAdd(const KAEvent&);
+ static void saveEdit(const KAEvent& oldEvent, const KAEvent& newEvent);
+ static void saveDelete(const KAEvent&);
+ static void saveDeletes(const QValueList<KAEvent>&);
+ static void saveReactivate(const KAEvent&);
+ static void saveReactivates(const QValueList<KAEvent>&);
+ static bool undo(QWidget* parent, const QString& action)
+ { return undo(mUndoList.begin(), UNDO, parent, action); }
+ static bool undo(int id, QWidget* parent, const QString& action)
+ { return undo(findItem(id, UNDO), UNDO, parent, action); }
+ static bool redo(QWidget* parent, const QString& action)
+ { return undo(mRedoList.begin(), REDO, parent, action); }
+ static bool redo(int id, QWidget* parent, const QString& action)
+ { return undo(findItem(id, REDO), REDO, parent, action); }
+ static void clear();
+ static bool haveUndo() { return !mUndoList.isEmpty(); }
+ static bool haveRedo() { return !mRedoList.isEmpty(); }
+ static QString actionText(Type);
+ static QString actionText(Type, int id);
+ static QString description(Type, int id);
+ static QValueList<int> ids(Type);
+ static void emitChanged();
+
+ // Types for use by UndoItem class and its descendants
+ typedef QValueList<UndoItem*> List;
+
+ signals:
+ void changed(const QString& undo, const QString& redo);
+
+ protected:
+ // Methods for use by UndoItem class
+ static void add(UndoItem*, bool undo);
+ static void remove(UndoItem*, bool undo);
+ static void replace(UndoItem* old, UndoItem* New);
+
+ private:
+ typedef QValueList<UndoItem*>::Iterator Iterator;
+
+ Undo(QObject* parent) : QObject(parent) { }
+ static void removeRedos(const QString& eventID);
+ static bool undo(Iterator, Type, QWidget* parent, const QString& action);
+ static UndoItem* getItem(int id, Type);
+ static Iterator findItem(int id, Type);
+ void emitChanged(const QString& undo, const QString& redo)
+ { emit changed(undo, redo); }
+
+ static Undo* mInstance; // the one and only Undo instance
+ static List mUndoList; // edit history for undo, latest undo first
+ static List mRedoList; // edit history for redo, latest redo first
+
+ friend class UndoItem;
+};
+
+#endif // UNDO_H
diff --git a/kalarm/uninstall.desktop b/kalarm/uninstall.desktop
new file mode 100644
index 00000000..e1e3e173
--- /dev/null
+++ b/kalarm/uninstall.desktop
@@ -0,0 +1,2 @@
+[Desktop Entry]
+Hidden=true