summaryrefslogtreecommitdiff
path: root/kcalutils
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-27 07:49:37 (GMT)
committerChristian Mollekopf <chrigi_1@fastmail.fm>2012-06-27 07:49:37 (GMT)
commitf4a1668ab4aba34ab39c271527cdbaff8557b52c (patch)
treec7a4d190013958758c10170717002fd5b0f6e7aa /kcalutils
parent9fc556b238e0fbf12f7299439ee6b8953b975ffb (diff)
downloadlibcalendaring-f4a1668ab4aba34ab39c271527cdbaff8557b52c.tar.gz
initial import of kcalutils
commit 4fddf6f044ea74e348f1b4bf664c17c4d5236a29 Author: Allen Winter <allen.winter@kdab.com> Date: Sun Jun 24 18:33:17 2012 -0400 set version for 4.9rc1 release
Diffstat (limited to 'kcalutils')
-rw-r--r--kcalutils/.krazy2
-rw-r--r--kcalutils/CMakeLists.txt68
-rw-r--r--kcalutils/COPYING487
-rw-r--r--kcalutils/Mainpage.dox28
-rw-r--r--kcalutils/Messages.sh3
-rw-r--r--kcalutils/dndfactory.cpp369
-rw-r--r--kcalutils/dndfactory.h188
-rw-r--r--kcalutils/htmlexport.cpp773
-rw-r--r--kcalutils/htmlexport.h107
-rw-r--r--kcalutils/htmlexportsettings.kcfg196
-rw-r--r--kcalutils/htmlexportsettings.kcfgc11
-rw-r--r--kcalutils/icaldrag.cpp71
-rw-r--r--kcalutils/icaldrag.h63
-rw-r--r--kcalutils/incidenceformatter.cpp4512
-rw-r--r--kcalutils/incidenceformatter.h255
-rw-r--r--kcalutils/kcalutils_export.h69
-rw-r--r--kcalutils/recurrenceactions.cpp246
-rw-r--r--kcalutils/recurrenceactions.h176
-rw-r--r--kcalutils/recurrenceactionsscopewidget.ui139
-rw-r--r--kcalutils/scheduler.cpp569
-rw-r--r--kcalutils/scheduler.h161
-rw-r--r--kcalutils/stringify.cpp384
-rw-r--r--kcalutils/stringify.h129
-rw-r--r--kcalutils/tests/CMakeLists.txt14
-rw-r--r--kcalutils/tests/testdndfactory.cpp162
-rw-r--r--kcalutils/tests/testdndfactory.h54
-rw-r--r--kcalutils/tests/testincidenceformatter.cpp122
-rw-r--r--kcalutils/tests/testincidenceformatter.h34
-rw-r--r--kcalutils/tests/teststringify.cpp75
-rw-r--r--kcalutils/tests/teststringify.h36
-rw-r--r--kcalutils/vcaldrag.cpp72
-rw-r--r--kcalutils/vcaldrag.h62
32 files changed, 9637 insertions, 0 deletions
diff --git a/kcalutils/.krazy b/kcalutils/.krazy
new file mode 100644
index 0000000..738e092
--- /dev/null
+++ b/kcalutils/.krazy
@@ -0,0 +1,2 @@
+SKIP /tests/
+EXTRA style,kdebug,tipsandthis,defines,qenums,null
diff --git a/kcalutils/CMakeLists.txt b/kcalutils/CMakeLists.txt
new file mode 100644
index 0000000..c2ec917
--- /dev/null
+++ b/kcalutils/CMakeLists.txt
@@ -0,0 +1,68 @@
+project(kcalutils)
+
+add_definitions(-DKDE_DEFAULT_DEBUG_AREA=5820)
+
+if(KDE4_BUILD_TESTS)
+ add_definitions(-DCOMPILING_TESTS)
+endif()
+
+include_directories(
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${KDE4_INCLUDE_DIR}
+)
+
+########### next target ###############
+
+set(kcalutils_LIB_SRCS
+ htmlexport.cpp
+ icaldrag.cpp
+ incidenceformatter.cpp
+ recurrenceactions.cpp
+ stringify.cpp
+ scheduler.cpp
+ vcaldrag.cpp
+)
+
+if(NOT WINCE)
+ set(kcalutils_LIB_SRCS ${kcalutils_LIB_SRCS} dndfactory.cpp)
+endif()
+
+kde4_add_kcfg_files(kcalutils_LIB_SRCS htmlexportsettings.kcfgc)
+
+kde4_add_ui_files(kcalutils_LIB_SRCS recurrenceactionsscopewidget.ui)
+
+kde4_add_library(kcalutils ${LIBRARY_TYPE} ${kcalutils_LIB_SRCS})
+
+target_link_libraries(kcalutils
+ ${KDE4_KDECORE_LIBRARY}
+ ${KDE4_KDEUI_LIBRARY}
+ ${KDE4_KIO_LIBS}
+ kcalcore
+ kpimutils
+ ${QT_QTGUI_LIBRARY}
+)
+set_target_properties(kcalutils PROPERTIES
+ VERSION ${GENERIC_LIB_VERSION}
+ SOVERSION ${GENERIC_LIB_SOVERSION}
+)
+
+install(TARGETS kcalutils EXPORT kdepimlibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
+
+########### next target ###############
+
+add_subdirectory(tests)
+
+########### install files ###############
+
+install(FILES
+ dndfactory.h
+ htmlexport.h
+ icaldrag.h
+ incidenceformatter.h
+ kcalutils_export.h
+ vcaldrag.h
+ recurrenceactions.h
+ scheduler.h
+ stringify.h
+ ${CMAKE_CURRENT_BINARY_DIR}/htmlexportsettings.h
+ DESTINATION ${INCLUDE_INSTALL_DIR}/kcalutils COMPONENT Devel)
diff --git a/kcalutils/COPYING b/kcalutils/COPYING
new file mode 100644
index 0000000..b05ce71
--- /dev/null
+++ b/kcalutils/COPYING
@@ -0,0 +1,487 @@
+NOTE! The LGPL below is copyrighted by the Free Software Foundation, but
+the instance of code that it refers to are copyrighted
+by the authors who actually wrote it.
+
+---------------------------------------------------------------------------
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor
+ Boston, MA 02110-1301, USA.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the library GPL. It is
+ numbered 2 because it goes with version 2 of the ordinary GPL.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Library General Public License, applies to some
+specially designated Free Software Foundation software, and to any
+other libraries whose authors decide to use it. You can use it for
+your libraries, 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 library, or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link a program with the library, you must provide
+complete object files to the recipients so that they can relink them
+with the library, after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ Our method of protecting your rights has two steps: (1) copyright
+the library, and (2) offer you this license which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ Also, for each distributor's protection, we want to make certain
+that everyone understands that there is no warranty for this free
+library. If the library is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original
+version, 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 companies distributing free
+software will individually obtain patent licenses, thus in effect
+transforming the program into proprietary software. To prevent this,
+we have made it clear that any patent must be licensed for everyone's
+free use or not licensed at all.
+
+ Most GNU software, including some libraries, is covered by the ordinary
+GNU General Public License, which was designed for utility programs. This
+license, the GNU Library General Public License, applies to certain
+designated libraries. This license is quite different from the ordinary
+one; be sure to read it in full, and don't assume that anything in it is
+the same as in the ordinary license.
+
+ The reason we have a separate public license for some libraries is that
+they blur the distinction we usually make between modifying or adding to a
+program and simply using it. Linking a program with a library, without
+changing the library, is in some sense simply using the library, and is
+analogous to running a utility program or application program. However, in
+a textual and legal sense, the linked executable is a combined work, a
+derivative of the original library, and the ordinary General Public License
+treats it as such.
+
+ Because of this blurred distinction, using the ordinary General
+Public License for libraries did not effectively promote software
+sharing, because most developers did not use the libraries. We
+concluded that weaker conditions might promote sharing better.
+
+ However, unrestricted linking of non-free programs would deprive the
+users of those programs of all benefit from the free status of the
+libraries themselves. This Library General Public License is intended to
+permit developers of non-free programs to use free libraries, while
+preserving your freedom as a user of such programs to change the free
+libraries that are incorporated in them. (We have not seen how to achieve
+this as regards changes in header files, but we have achieved it as regards
+changes in the actual functions of the Library.) The hope is that this
+will lead to faster development of free libraries.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, while the latter only
+works together with the library.
+
+ Note that it is possible for a library to be covered by the ordinary
+General Public License rather than by this special one.
+
+ GNU LIBRARY GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library which
+contains a notice placed by the copyright holder or other authorized
+party saying it may be distributed under the terms of this Library
+General Public License (also called "this License"). Each licensee is
+addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, 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 library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete 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 distribute a copy of this License along with the
+Library.
+
+ 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 Library or any portion
+of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+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 Library, 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 Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you 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.
+
+ If distribution of 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 satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also compile or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ c) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ d) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. 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.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library 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.
+
+ 9. 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 Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+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.
+
+ 11. 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 Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library 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 Library.
+
+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.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library 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.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Library 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 Library
+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 Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+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
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "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
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. 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 LIBRARY 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
+LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. 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 library's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+ <signature of Ty Coon>, 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/kcalutils/Mainpage.dox b/kcalutils/Mainpage.dox
new file mode 100644
index 0000000..d74f6b2
--- /dev/null
+++ b/kcalutils/Mainpage.dox
@@ -0,0 +1,28 @@
+/*!
+ * @mainpage KCalUtils - the KDE calendar utility library.
+ *
+ * @section purpose Purpose
+ *
+ * This library provides utility functions for the handling of calendar data.
+ *
+ * @section desc Description
+ *
+ * This library provides a set of utility functions that help applications
+ * access and use calendar data via the KCalCore library.
+ *
+ * @authors
+ * The major authors of this library are (in alphabetical order):\n
+ * Preston Brown \<pbrown@kde.org\>,
+ * Reinhold Kainhofer \<reinhold@kainhofer.com\>,
+ * Cornelius Schumacher \<schumacher@kde.org\>
+ * Allen Winter \<winter@kde.org\>
+ *
+ * @maintainers
+ * Allen Winter \<winter@kde.org\>
+ *
+ * @licenses
+ * @lgpl
+ */
+
+// DOXYGEN_PROJECTNAME=KCalUtils Library
+// DOXYGEN_REFERENCES=kcalcore
diff --git a/kcalutils/Messages.sh b/kcalutils/Messages.sh
new file mode 100644
index 0000000..a4e77c3
--- /dev/null
+++ b/kcalutils/Messages.sh
@@ -0,0 +1,3 @@
+#! /bin/sh
+$EXTRACTRC *.kcfg >> rc.cpp
+$XGETTEXT *.cpp -o $podir/libkcalutils.pot
diff --git a/kcalutils/dndfactory.cpp b/kcalutils/dndfactory.cpp
new file mode 100644
index 0000000..745f49f
--- /dev/null
+++ b/kcalutils/dndfactory.cpp
@@ -0,0 +1,369 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001,2002 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
+ Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and
+ defines the DndFactory class.
+
+ @brief
+ vCalendar/iCalendar Drag-and-Drop object factory.
+
+ @author Preston Brown \<pbrown@kde.org\>
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+*/
+#include "dndfactory.h"
+#include "icaldrag.h"
+#include "vcaldrag.h"
+
+#include <KDebug>
+#include <KIconLoader> // for BarIcon
+#include <KUrl>
+
+#include <QtCore/QMimeData>
+#include <QtGui/QApplication>
+#include <QtGui/QClipboard>
+#include <QtGui/QDrag>
+#include <QtGui/QDropEvent>
+#include <QtGui/QPixmap>
+
+using namespace KCalCore;
+using namespace KCalUtils;
+
+/**
+ Private class that helps to provide binary compatibility between releases.
+ @internal
+*/
+//@cond PRIVATE
+class KCalUtils::DndFactory::Private
+{
+ public:
+ Private( const MemoryCalendar::Ptr &calendar )
+ : mCalendar ( calendar )
+ {}
+
+ Incidence::Ptr pasteIncidence( const Incidence::Ptr &incidence,
+ const KDateTime &newDateTime,
+ const QFlags<PasteFlag> &pasteOptions )
+ {
+
+ Incidence::Ptr inc( incidence );
+
+ if ( inc ) {
+ inc = Incidence::Ptr( inc->clone() );
+ inc->recreate();
+ }
+
+ if ( inc && newDateTime.isValid() ) {
+ if ( inc->type() == Incidence::TypeEvent ) {
+
+ Event::Ptr event = inc.staticCast<Event>();
+
+ // in seconds
+ const int durationInSeconds = event->dtStart().secsTo( event->dtEnd() );
+ const int durationInDays = event->dtStart().daysTo( event->dtEnd() );
+
+ event->setDtStart( newDateTime );
+
+ if ( newDateTime.isDateOnly() ) {
+ event->setDtEnd( newDateTime.addDays( durationInDays ) );
+ } else {
+ event->setDtEnd( newDateTime.addSecs( durationInSeconds ) );
+ }
+
+ } else if ( inc->type() == Incidence::TypeTodo ) {
+ Todo::Ptr aTodo = inc.staticCast<Todo>();
+
+ if ( pasteOptions & FlagTodosPasteAtDtStart ) {
+ aTodo->setDtStart( newDateTime );
+ } else {
+ aTodo->setDtDue( newDateTime );
+ }
+
+ } else if ( inc->type() == Incidence::TypeJournal ) {
+ inc->setDtStart( newDateTime );
+ } else {
+ kDebug() << "Trying to paste unknown incidence of type" << int( inc->type() );
+ }
+ }
+
+ return inc;
+ }
+
+ MemoryCalendar::Ptr mCalendar;
+};
+//@endcond
+
+DndFactory::DndFactory( const MemoryCalendar::Ptr &calendar )
+ : d( new KCalUtils::DndFactory::Private ( calendar ) )
+{
+}
+
+DndFactory::~DndFactory()
+{
+ delete d;
+}
+
+QMimeData *DndFactory::createMimeData()
+{
+ QMimeData *mimeData = new QMimeData;
+
+ ICalDrag::populateMimeData( mimeData, d->mCalendar );
+ VCalDrag::populateMimeData( mimeData, d->mCalendar );
+
+ return mimeData;
+}
+
+QDrag *DndFactory::createDrag( QWidget *owner )
+{
+ QDrag *drag = new QDrag( owner );
+ drag->setMimeData( createMimeData() );
+
+ return drag;
+}
+
+QMimeData *DndFactory::createMimeData( const Incidence::Ptr &incidence )
+{
+ MemoryCalendar::Ptr cal( new MemoryCalendar( d->mCalendar->timeSpec() ) );
+ Incidence::Ptr i( incidence->clone() );
+ cal->addIncidence( i );
+
+ QMimeData *mimeData = new QMimeData;
+
+ ICalDrag::populateMimeData( mimeData, cal );
+ VCalDrag::populateMimeData( mimeData, cal );
+
+ KUrl uri = i->uri();
+ if ( uri.isValid() ) {
+ QMap<QString, QString> metadata;
+ metadata["labels"] = KUrl::toPercentEncoding( i->summary() );
+ uri.populateMimeData( mimeData, metadata );
+ }
+
+ return mimeData;
+}
+
+QDrag *DndFactory::createDrag( const Incidence::Ptr &incidence, QWidget *owner )
+{
+ QDrag *drag = new QDrag( owner );
+ drag->setMimeData( createMimeData( incidence ) );
+ drag->setPixmap( BarIcon( incidence->iconName() ) );
+
+ return drag;
+}
+
+MemoryCalendar::Ptr DndFactory::createDropCalendar( const QMimeData *mimeData )
+{
+ return createDropCalendar( mimeData, d->mCalendar->timeSpec() );
+}
+
+MemoryCalendar::Ptr DndFactory::createDropCalendar( const QMimeData *mimeData,
+ const KDateTime::Spec &timeSpec )
+{
+ MemoryCalendar::Ptr calendar( new MemoryCalendar( timeSpec ) );
+
+ if ( ICalDrag::fromMimeData( mimeData, calendar ) ||
+ VCalDrag::fromMimeData( mimeData, calendar ) ){
+ return calendar;
+ }
+
+ return MemoryCalendar::Ptr();
+}
+
+MemoryCalendar::Ptr DndFactory::createDropCalendar( QDropEvent *dropEvent )
+{
+ MemoryCalendar::Ptr calendar( createDropCalendar( dropEvent->mimeData() ) );
+ if ( calendar ) {
+ dropEvent->accept();
+ return calendar;
+ }
+ return MemoryCalendar::Ptr();
+}
+
+Event::Ptr DndFactory::createDropEvent( const QMimeData *mimeData )
+{
+ kDebug();
+ Event::Ptr event;
+ MemoryCalendar::Ptr calendar( createDropCalendar( mimeData ) );
+
+ if ( calendar ) {
+ Event::List events = calendar->events();
+ if ( !events.isEmpty() ) {
+ event = Event::Ptr( new Event( *events.first() ) );
+ }
+ }
+ return event;
+}
+
+Event::Ptr DndFactory::createDropEvent( QDropEvent *dropEvent )
+{
+ Event::Ptr event = createDropEvent( dropEvent->mimeData() );
+
+ if ( event ) {
+ dropEvent->accept();
+ }
+
+ return event;
+}
+
+Todo::Ptr DndFactory::createDropTodo( const QMimeData *mimeData )
+{
+ kDebug();
+ Todo::Ptr todo;
+ MemoryCalendar::Ptr calendar( createDropCalendar( mimeData ) );
+
+ if ( calendar ) {
+ Todo::List todos = calendar->todos();
+ if ( !todos.isEmpty() ) {
+ todo = Todo::Ptr( new Todo( *todos.first() ) );
+ }
+ }
+
+ return todo;
+}
+
+Todo::Ptr DndFactory::createDropTodo( QDropEvent *dropEvent )
+{
+ Todo::Ptr todo = createDropTodo( dropEvent->mimeData() );
+
+ if ( todo ) {
+ dropEvent->accept();
+ }
+
+ return todo;
+}
+
+void DndFactory::cutIncidence( const Incidence::Ptr &selectedIncidence )
+{
+ Incidence::List list;
+ list.append( selectedIncidence );
+ cutIncidences( list );
+}
+
+bool DndFactory::cutIncidences( const Incidence::List &incidences )
+{
+ if ( copyIncidences( incidences ) ) {
+ Incidence::List::ConstIterator it;
+ for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) {
+ d->mCalendar->deleteIncidence( *it );
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool DndFactory::copyIncidences( const Incidence::List &incidences )
+{
+ QClipboard *clipboard = QApplication::clipboard();
+ Q_ASSERT( clipboard );
+ MemoryCalendar::Ptr calendar( new MemoryCalendar( d->mCalendar->timeSpec() ) );
+
+ Incidence::List::ConstIterator it;
+ for ( it = incidences.constBegin(); it != incidences.constEnd(); ++it ) {
+ if ( *it ) {
+ calendar->addIncidence( Incidence::Ptr( ( *it )->clone() ) );
+ }
+ }
+
+ QMimeData *mimeData = new QMimeData;
+
+ ICalDrag::populateMimeData( mimeData, calendar );
+ VCalDrag::populateMimeData( mimeData, calendar );
+
+ if ( calendar->incidences().isEmpty() ) {
+ return false;
+ } else {
+ clipboard->setMimeData( mimeData );
+ return true;
+ }
+}
+
+bool DndFactory::copyIncidence( const Incidence::Ptr &selectedInc )
+{
+ Incidence::List list;
+ list.append( selectedInc );
+ return copyIncidences( list );
+}
+
+Incidence::List DndFactory::pasteIncidences( const KDateTime &newDateTime,
+ const QFlags<PasteFlag> &pasteOptions )
+{
+ QClipboard *clipboard = QApplication::clipboard();
+ Q_ASSERT( clipboard );
+ MemoryCalendar::Ptr calendar( createDropCalendar( clipboard->mimeData() ) );
+ Incidence::List list;
+
+ if ( !calendar ) {
+ kDebug() << "Can't parse clipboard";
+ return list;
+ }
+
+ // All pasted incidences get new uids, must keep track of old uids,
+ // so we can update child's parents
+ QHash<QString, Incidence::Ptr> oldUidToNewInc;
+
+ Incidence::List::ConstIterator it;
+ const Incidence::List incidences = calendar->incidences();
+ for ( it = incidences.constBegin();
+ it != incidences.constEnd(); ++it ) {
+ Incidence::Ptr incidence = d->pasteIncidence( *it, newDateTime, pasteOptions );
+ if ( incidence ) {
+ list.append( incidence );
+ oldUidToNewInc[(*it)->uid()] = *it;
+ }
+ }
+
+ // update relations
+ for ( it = list.constBegin(); it != list.constEnd(); ++it ) {
+ Incidence::Ptr incidence = *it;
+ if ( oldUidToNewInc.contains( incidence->relatedTo() ) ) {
+ Incidence::Ptr parentInc = oldUidToNewInc[incidence->relatedTo()];
+ incidence->setRelatedTo( parentInc->uid() );
+ } else {
+ // not related to anything in the clipboard
+ incidence->setRelatedTo( QString() );
+ }
+ }
+
+ return list;
+}
+
+Incidence::Ptr DndFactory::pasteIncidence( const KDateTime &newDateTime,
+ const QFlags<PasteFlag> &pasteOptions )
+{
+ QClipboard *clipboard = QApplication::clipboard();
+ MemoryCalendar::Ptr calendar( createDropCalendar( clipboard->mimeData() ) );
+
+ if ( !calendar ) {
+ kDebug() << "Can't parse clipboard";
+ return Incidence::Ptr();
+ }
+
+ Incidence::List incidenceList = calendar->incidences();
+ Incidence::Ptr incidence = incidenceList.isEmpty() ? Incidence::Ptr() : incidenceList.first();
+
+ return d->pasteIncidence( incidence, newDateTime, pasteOptions );
+}
diff --git a/kcalutils/dndfactory.h b/kcalutils/dndfactory.h
new file mode 100644
index 0000000..86cad96
--- /dev/null
+++ b/kcalutils/dndfactory.h
@@ -0,0 +1,188 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001,2002,2003 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (C) 2003-2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2008 Thomas Thrainer <tom_t@gmx.at>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and
+ defines the DndFactory class.
+
+ @author Preston Brown \<pbrown@kde.org\>
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+*/
+#ifndef KCALUTILS_DNDFACTORY_H
+#define KCALUTILS_DNDFACTORY_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/event.h>
+#include <kcalcore/journal.h>
+#include <kcalcore/todo.h>
+#include <kcalcore/memorycalendar.h>
+
+#include <KDE/KDateTime>
+
+class QDrag;
+class QDropEvent;
+class QMimeData;
+
+namespace KCalUtils {
+
+/**
+ @brief
+ vCalendar/iCalendar Drag-and-Drop object factory.
+
+ This class implements functions to create Drag and Drop objects used for
+ Drag-and-Drop and Copy-and-Paste.
+*/
+class KCALUTILS_EXPORT DndFactory
+{
+ public:
+
+ enum PasteFlag {
+ FlagTodosPasteAtDtStart = 1 /**< If the cloned incidence is a to-do, the date/time passed
+ to DndFactory::pasteIncidence() will change dtStart if this
+ flag is on, changes dtDue otherwise. */
+ };
+
+ Q_DECLARE_FLAGS( PasteFlags, PasteFlag )
+
+ explicit DndFactory( const KCalCore::MemoryCalendar::Ptr &cal );
+
+ ~DndFactory();
+
+ /**
+ Create the calendar that is contained in the drop event's data.
+ */
+ KCalCore::MemoryCalendar::Ptr createDropCalendar( QDropEvent *de );
+
+ /**
+ Create the calendar that is contained in the mime data.
+ */
+ KCalCore::MemoryCalendar::Ptr createDropCalendar( const QMimeData *md );
+
+ /**
+ Create the calendar that is contained in the mime data.
+ */
+ static KCalCore::MemoryCalendar::Ptr createDropCalendar( const QMimeData *md,
+ const KDateTime::Spec &timeSpec );
+
+ /**
+ Create the mime data for the whole calendar.
+ */
+ QMimeData *createMimeData();
+
+ /**
+ Create a drag object for the whole calendar.
+ */
+ QDrag *createDrag( QWidget *owner );
+
+ /**
+ Create the mime data for a single incidence.
+ */
+ QMimeData *createMimeData( const KCalCore::Incidence::Ptr &incidence );
+
+ /**
+ Create a drag object for a single incidence.
+ */
+ QDrag *createDrag( const KCalCore::Incidence::Ptr &incidence, QWidget *owner );
+
+ /**
+ Create Todo object from mime data.
+ */
+ KCalCore::Todo::Ptr createDropTodo( const QMimeData *md );
+
+ /**
+ Create Todo object from drop event.
+ */
+ KCalCore::Todo::Ptr createDropTodo( QDropEvent *de );
+
+ /**
+ Create Event object from mime data.
+ */
+ KCalCore::Event::Ptr createDropEvent( const QMimeData *md );
+
+ /**
+ Create Event object from drop event.
+ */
+ KCalCore::Event::Ptr createDropEvent( QDropEvent *de );
+
+ /**
+ Cut the incidence to the clipboard.
+ */
+ void cutIncidence( const KCalCore::Incidence::Ptr & );
+
+ /**
+ Copy the incidence to clipboard/
+ */
+ bool copyIncidence( const KCalCore::Incidence::Ptr & );
+
+ /**
+ Cuts a list of @p incidences to the clipboard.
+ */
+ bool cutIncidences( const KCalCore::Incidence::List &incidences );
+
+ /**
+ Copies a list of @p incidences to the clipboard.
+ */
+ bool copyIncidences( const KCalCore::Incidence::List &incidences );
+
+ /**
+ This function clones the incidences that are in the clipboard and sets the clone's
+ date/time to the specified @p newDateTime.
+
+ @see pasteIncidence()
+ */
+ KCalCore::Incidence::List pasteIncidences(
+ const KDateTime &newDateTime = KDateTime(),
+ const QFlags<PasteFlag> &pasteOptions = QFlags<PasteFlag>() );
+
+ /**
+ This function clones the incidence that's in the clipboard and sets the clone's
+ date/time to the specified @p newDateTime.
+
+ @param newDateTime The new date/time that the incidence will have. If it's an event
+ or journal, DTSTART will be set. If it's a to-do, DTDUE is set.
+ If you wish another behaviour, like changing DTSTART on to-dos, specify
+ @p pasteOptions. If newDateTime is invalid the original incidence's dateTime
+ will be used, regardless of @p pasteOptions.
+
+ @param pasteOptions Control how @p newDateTime changes the incidence's dates. @see PasteFlag.
+
+ @return A pointer to the cloned incidence.
+ */
+ KCalCore::Incidence::Ptr pasteIncidence(
+ const KDateTime &newDateTime = KDateTime(),
+ const QFlags<PasteFlag> &pasteOptions = QFlags<PasteFlag>() );
+
+ private:
+ //@cond PRIVATE
+ Q_DISABLE_COPY( DndFactory )
+ class Private;
+ Private *const d;
+ //@endcond
+};
+
+}
+
+#endif
diff --git a/kcalutils/htmlexport.cpp b/kcalutils/htmlexport.cpp
new file mode 100644
index 0000000..4a50435
--- /dev/null
+++ b/kcalutils/htmlexport.cpp
@@ -0,0 +1,773 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2000,2001 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "htmlexport.h"
+#include "htmlexportsettings.h"
+#include "stringify.h"
+
+#include <kcalcore/memorycalendar.h>
+using namespace KCalCore;
+
+#include <KDebug>
+#include <KCalendarSystem>
+#include <KGlobal>
+#include <KLocale>
+
+#include <QtCore/QFile>
+#include <QtCore/QMap>
+#include <QtCore/QTextStream>
+#include <QtGui/QApplication>
+
+using namespace KCalUtils;
+
+static QString cleanChars( const QString &txt );
+
+//@cond PRIVATE
+class KCalUtils::HtmlExport::Private
+{
+ public:
+ Private( MemoryCalendar *calendar, HTMLExportSettings *settings )
+ : mCalendar( calendar ), mSettings( settings )
+ {}
+
+ MemoryCalendar *mCalendar;
+ HTMLExportSettings *mSettings;
+ QMap<QDate,QString> mHolidayMap;
+};
+//@endcond
+
+HtmlExport::HtmlExport( MemoryCalendar *calendar, HTMLExportSettings *settings )
+ : d( new Private( calendar, settings ) )
+{
+}
+
+HtmlExport::~HtmlExport()
+{
+ delete d;
+}
+
+bool HtmlExport::save( const QString &fileName )
+{
+ QString fn( fileName );
+ if ( fn.isEmpty() && d->mSettings ) {
+ fn = d->mSettings->outputFile();
+ }
+ if ( !d->mSettings || fn.isEmpty() ) {
+ return false;
+ }
+ QFile f( fileName );
+ if ( !f.open( QIODevice::WriteOnly ) ) {
+ return false;
+ }
+ QTextStream ts( &f );
+ bool success = save( &ts );
+ f.close();
+ return success;
+}
+
+bool HtmlExport::save( QTextStream *ts )
+{
+ if ( !d->mSettings ) {
+ return false;
+ }
+ ts->setCodec( "UTF-8" );
+ // Write HTML header
+ *ts << "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ";
+ *ts << "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">" << endl;
+
+ *ts << "<html><head>" << endl;
+ *ts << " <meta http-equiv=\"Content-Type\" content=\"text/html; charset=";
+ *ts << "UTF-8\" />" << endl;
+ if ( !d->mSettings->pageTitle().isEmpty() ) {
+ *ts << " <title>" << d->mSettings->pageTitle() << "</title>" << endl;
+ }
+ *ts << " <style type=\"text/css\">" << endl;
+ *ts << styleSheet();
+ *ts << " </style>" << endl;
+ *ts << "</head><body>" << endl;
+
+ // FIXME: Write header
+ // (Heading, Calendar-Owner, Calendar-Date, ...)
+
+ if ( d->mSettings->eventView() || d->mSettings->monthView() || d->mSettings->weekView() ) {
+ if ( !d->mSettings->eventTitle().isEmpty() ) {
+ *ts << "<h1>" << d->mSettings->eventTitle() << "</h1>" << endl;
+ }
+
+ // Write Week View
+ if ( d->mSettings->weekView() ) {
+ createWeekView( ts );
+ }
+ // Write Month View
+ if ( d->mSettings->monthView() ) {
+ createMonthView( ts );
+ }
+ // Write Event List
+ if ( d->mSettings->eventView() ) {
+ createEventList( ts );
+ }
+ }
+
+ // Write Todo List
+ if ( d->mSettings->todoView() ) {
+ if ( !d->mSettings->todoListTitle().isEmpty() ) {
+ *ts << "<h1>" << d->mSettings->todoListTitle() << "</h1>" << endl;
+ }
+ createTodoList( ts );
+ }
+
+ // Write Journals
+ if ( d->mSettings->journalView() ) {
+ if ( !d->mSettings->journalTitle().isEmpty() ) {
+ *ts << "<h1>" << d->mSettings->journalTitle() << "</h1>" << endl;
+ }
+ createJournalView( ts );
+ }
+
+ // Write Free/Busy
+ if ( d->mSettings->freeBusyView() ) {
+ if ( !d->mSettings->freeBusyTitle().isEmpty() ) {
+ *ts << "<h1>" << d->mSettings->freeBusyTitle() << "</h1>" << endl;
+ }
+ createFreeBusyView( ts );
+ }
+
+ createFooter( ts );
+
+ // Write HTML trailer
+ *ts << "</body></html>" << endl;
+
+ return true;
+}
+
+void HtmlExport::createMonthView( QTextStream *ts )
+{
+ QDate start = fromDate();
+ start.setYMD( start.year(), start.month(), 1 ); // go back to first day in month
+
+ QDate end( start.year(), start.month(), start.daysInMonth() );
+
+ int startmonth = start.month();
+ int startyear = start.year();
+
+ while ( start < toDate() ) {
+ // Write header
+ QDate hDate( start.year(), start.month(), 1 );
+ QString hMon = hDate.toString( "MMMM" );
+ QString hYear = hDate.toString( "yyyy" );
+ *ts << "<h2>"
+ << i18nc( "@title month and year", "%1 %2", hMon, hYear )
+ << "</h2>" << endl;
+ if ( KGlobal::locale()->weekStartDay() == 1 ) {
+ start = start.addDays( 1 - start.dayOfWeek() );
+ } else {
+ if ( start.dayOfWeek() != 7 ) {
+ start = start.addDays( -start.dayOfWeek() );
+ }
+ }
+ *ts << "<table border=\"1\">" << endl;
+
+ // Write table header
+ *ts << " <tr>";
+ for ( int i=0; i < 7; ++i ) {
+ *ts << "<th>" << KGlobal::locale()->calendar()->weekDayName( start.addDays(i) ) << "</th>";
+ }
+ *ts << "</tr>" << endl;
+
+ // Write days
+ while ( start <= end ) {
+ *ts << " <tr>" << endl;
+ for ( int i=0; i < 7; ++i ) {
+ *ts << " <td valign=\"top\"><table border=\"0\">";
+
+ *ts << "<tr><td ";
+ if ( d->mHolidayMap.contains( start ) || start.dayOfWeek() == 7 ) {
+ *ts << "class=\"dateholiday\"";
+ } else {
+ *ts << "class=\"date\"";
+ }
+ *ts << ">" << QString::number( start.day() );
+
+ if ( d->mHolidayMap.contains( start ) ) {
+ *ts << " <em>" << d->mHolidayMap[start] << "</em>";
+ }
+
+ *ts << "</td></tr><tr><td valign=\"top\">";
+
+ // Only print events within the from-to range
+ if ( start >= fromDate() && start <= toDate() ) {
+ Event::List events = d->mCalendar->events( start, d->mCalendar->timeSpec(),
+ EventSortStartDate,
+ SortDirectionAscending );
+ if ( events.count() ) {
+ *ts << "<table>";
+ Event::List::ConstIterator it;
+ for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
+ if ( checkSecrecy( *it ) ) {
+ createEvent( ts, *it, start, false );
+ }
+ }
+ *ts << "</table>";
+ } else {
+ *ts << "&nbsp;";
+ }
+ }
+
+ *ts << "</td></tr></table></td>" << endl;
+ start = start.addDays( 1 );
+ }
+ *ts << " </tr>" << endl;
+ }
+ *ts << "</table>" << endl;
+ startmonth += 1;
+ if ( startmonth > 12 ) {
+ startyear += 1;
+ startmonth = 1;
+ }
+ start.setYMD( startyear, startmonth, 1 );
+ end.setYMD( start.year(), start.month(), start.daysInMonth() );
+ }
+}
+
+void HtmlExport::createEventList( QTextStream *ts )
+{
+ int columns = 3;
+ *ts << "<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
+ *ts << " <tr>" << endl;
+ *ts << " <th class=\"sum\">" << i18nc( "@title:column event start time",
+ "Start Time" ) << "</th>" << endl;
+ *ts << " <th>" << i18nc( "@title:column event end time",
+ "End Time" ) << "</th>" << endl;
+ *ts << " <th>" << i18nc( "@title:column event description",
+ "Event" ) << "</th>" << endl;
+ if ( d->mSettings->eventLocation() ) {
+ *ts << " <th>" << i18nc( "@title:column event locatin",
+ "Location" ) << "</th>" << endl;
+ ++columns;
+ }
+ if ( d->mSettings->eventCategories() ) {
+ *ts << " <th>" << i18nc( "@title:column event categories",
+ "Categories" ) << "</th>" << endl;
+ ++columns;
+ }
+ if ( d->mSettings->eventAttendees() ) {
+ *ts << " <th>" << i18nc( "@title:column event attendees",
+ "Attendees" ) << "</th>" << endl;
+ ++columns;
+ }
+
+ *ts << " </tr>" << endl;
+
+ for ( QDate dt = fromDate(); dt <= toDate(); dt = dt.addDays(1) ) {
+ kDebug() << "Getting events for" << dt.toString();
+ Event::List events = d->mCalendar->events( dt, d->mCalendar->timeSpec(),
+ EventSortStartDate,
+ SortDirectionAscending );
+ if ( events.count() ) {
+ *ts << " <tr><td colspan=\"" << QString::number( columns )
+ << "\" class=\"datehead\"><i>"
+ << KGlobal::locale()->formatDate( dt )
+ << "</i></td></tr>" << endl;
+
+ Event::List::ConstIterator it;
+ for ( it = events.constBegin(); it != events.constEnd(); ++it ) {
+ if ( checkSecrecy( *it ) ) {
+ createEvent( ts, *it, dt );
+ }
+ }
+ }
+ }
+
+ *ts << "</table>" << endl;
+}
+
+void HtmlExport::createEvent ( QTextStream *ts,
+ const Event::Ptr &event,
+ const QDate &date,
+ bool withDescription )
+{
+ kDebug() << event->summary();
+ *ts << " <tr>" << endl;
+
+ if ( !event->allDay() ) {
+ if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtStart().date() != date ) ) {
+ *ts << " <td>&nbsp;</td>" << endl;
+ } else {
+ *ts << " <td valign=\"top\">"
+ << Stringify::formatTime( event->dtStart(), true, d->mCalendar->timeSpec() )
+ << "</td>" << endl;
+ }
+ if ( event->isMultiDay( d->mCalendar->timeSpec() ) && ( event->dtEnd().date() != date ) ) {
+ *ts << " <td>&nbsp;</td>" << endl;
+ } else {
+ *ts << " <td valign=\"top\">"
+ << Stringify::formatTime( event->dtEnd(), true, d->mCalendar->timeSpec() )
+ << "</td>" << endl;
+ }
+ } else {
+ *ts << " <td>&nbsp;</td><td>&nbsp;</td>" << endl;
+ }
+
+ *ts << " <td class=\"sum\">" << endl;
+ *ts << " <b>" << cleanChars( event->summary() ) << "</b>" << endl;
+ if ( withDescription && !event->description().isEmpty() ) {
+ *ts << " <p>" << breakString( cleanChars( event->description() ) ) << "</p>" << endl;
+ }
+ *ts << " </td>" << endl;
+
+ if ( d->mSettings->eventLocation() ) {
+ *ts << " <td>" << endl;
+ formatLocation( ts, event );
+ *ts << " </td>" << endl;
+ }
+
+ if ( d->mSettings->eventCategories() ) {
+ *ts << " <td>" << endl;
+ formatCategories( ts, event );
+ *ts << " </td>" << endl;
+ }
+
+ if ( d->mSettings->eventAttendees() ) {
+ *ts << " <td>" << endl;
+ formatAttendees( ts, event );
+ *ts << " </td>" << endl;
+ }
+
+ *ts << " </tr>" << endl;
+}
+
+void HtmlExport::createTodoList ( QTextStream *ts )
+{
+ Todo::List rawTodoList = d->mCalendar->todos();
+
+ int index = 0;
+ while ( index < rawTodoList.count() ) {
+ Todo::Ptr ev = rawTodoList[ index ];
+ Todo::Ptr subev = ev;
+ const QString uid = ev->relatedTo();
+ if ( !uid.isEmpty() ) {
+ Incidence::Ptr inc = d->mCalendar->incidence( uid );
+ if ( inc && inc->type() == Incidence::TypeTodo ) {
+ Todo::Ptr todo = inc.staticCast<Todo>();
+ if ( !rawTodoList.contains( todo ) ) {
+ rawTodoList.append( todo );
+ }
+ }
+ }
+ index = rawTodoList.indexOf( subev );
+ ++index;
+ }
+
+ // FIXME: Sort list by priorities. This is brute force and should be
+ // replaced by a real sorting algorithm.
+ Todo::List todoList;
+ Todo::List::ConstIterator it;
+ for ( int i = 1; i <= 9; ++i ) {
+ for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
+ if ( (*it)->priority() == i && checkSecrecy( *it ) ) {
+ todoList.append( *it );
+ }
+ }
+ }
+ for ( it = rawTodoList.constBegin(); it != rawTodoList.constEnd(); ++it ) {
+ if ( (*it)->priority() == 0 && checkSecrecy( *it ) ) {
+ todoList.append( *it );
+ }
+ }
+
+ int columns = 3;
+ *ts << "<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\">" << endl;
+ *ts << " <tr>" << endl;
+ *ts << " <th class=\"sum\">" << i18nc( "@title:column", "To-do" ) << "</th>" << endl;
+ *ts << " <th>" << i18nc( "@title:column to-do priority", "Priority" ) << "</th>" << endl;
+ *ts << " <th>" << i18nc( "@title:column to-do percent completed",
+ "Completed" ) << "</th>" << endl;
+ if ( d->mSettings->taskDueDate() ) {
+ *ts << " <th>" << i18nc( "@title:column to-do due date", "Due Date" ) << "</th>" << endl;
+ ++columns;
+ }
+ if ( d->mSettings->taskLocation() ) {
+ *ts << " <th>" << i18nc( "@title:column to-do location", "Location" ) << "</th>" << endl;
+ ++columns;
+ }
+ if ( d->mSettings->taskCategories() ) {
+ *ts << " <th>" << i18nc( "@title:column to-do categories", "Categories" ) << "</th>" << endl;
+ ++columns;
+ }
+ if ( d->mSettings->taskAttendees() ) {
+ *ts << " <th>" << i18nc( "@title:column to-do attendees", "Attendees" ) << "</th>" << endl;
+ ++columns;
+ }
+ *ts << " </tr>" << endl;
+
+ // Create top-level list.
+ for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
+ if ( (*it)->relatedTo().isEmpty() ) {
+ createTodo( ts, *it );
+ }
+ }
+
+ // Create sub-level lists
+ for ( it = todoList.constBegin(); it != todoList.constEnd(); ++it ) {
+ Incidence::List relations = d->mCalendar->relations( ( *it )->uid() );
+
+ if ( relations.count() ) {
+ // Generate sub-to-do list
+ *ts << " <tr>" << endl;
+ *ts << " <td class=\"subhead\" colspan=";
+ *ts << "\"" << QString::number(columns) << "\"";
+ *ts << "><a name=\"sub" << (*it)->uid() << "\"></a>"
+ << i18nc( "@title:column sub-to-dos of the parent to-do",
+ "Sub-To-dos of: " ) << "<a href=\"#"
+ << (*it)->uid() << "\"><b>" << cleanChars( (*it)->summary() )
+ << "</b></a></td>" << endl;
+ *ts << " </tr>" << endl;
+
+ Todo::List sortedList;
+ // FIXME: Sort list by priorities. This is brute force and should be
+ // replaced by a real sorting algorithm.
+ for ( int i = 1; i <= 9; ++i ) {
+ Incidence::List::ConstIterator it2;
+ for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
+ Todo::Ptr ev3 = (*it2).staticCast<Todo>();
+ if ( ev3 && ev3->priority() == i ) {
+ sortedList.append( ev3 );
+ }
+ }
+ }
+ Incidence::List::ConstIterator it2;
+ for ( it2 = relations.constBegin(); it2 != relations.constEnd(); ++it2 ) {
+ Todo::Ptr ev3 = (*it2).staticCast<Todo>();
+ if ( ev3 && ev3->priority() == 0 ) {
+ sortedList.append( ev3 );
+ }
+ }
+
+ Todo::List::ConstIterator it3;
+ for ( it3 = sortedList.constBegin(); it3 != sortedList.constEnd(); ++it3 ) {
+ createTodo( ts, *it3 );
+ }
+ }
+ }
+
+ *ts << "</table>" << endl;
+}
+
+void HtmlExport::createTodo( QTextStream *ts, const Todo::Ptr &todo )
+{
+ kDebug();
+
+ const bool completed = todo->isCompleted();
+
+ Incidence::List relations = d->mCalendar->relations( todo->uid() );
+
+ *ts << "<tr>" << endl;
+
+ *ts << " <td class=\"sum";
+ if (completed) *ts << "done";
+ *ts << "\">" << endl;
+ *ts << " <a name=\"" << todo->uid() << "\"></a>" << endl;
+ *ts << " <b>" << cleanChars( todo->summary() ) << "</b>" << endl;
+ if ( !todo->description().isEmpty() ) {
+ *ts << " <p>" << breakString( cleanChars( todo->description() ) ) << "</p>" << endl;
+ }
+ if ( relations.count() ) {
+ *ts << " <div align=\"right\"><a href=\"#sub" << todo->uid()
+ << "\">" << i18nc( "@title:column sub-to-dos of the parent to-do",
+ "Sub-To-dos" ) << "</a></div>" << endl;
+ }
+ *ts << " </td>" << endl;
+
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ *ts << " " << todo->priority() << endl;
+ *ts << " </td>" << endl;
+
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ *ts << " " << i18nc( "@info/plain to-do percent complete",
+ "%1 %", todo->percentComplete() ) << endl;
+ *ts << " </td>" << endl;
+
+ if ( d->mSettings->taskDueDate() ) {
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ if ( todo->hasDueDate() ) {
+ *ts << " " << Stringify::formatDate( todo->dtDue( true ) ) << endl;
+ } else {
+ *ts << " &nbsp;" << endl;
+ }
+ *ts << " </td>" << endl;
+ }
+
+ if ( d->mSettings->taskLocation() ) {
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ formatLocation( ts, todo );
+ *ts << " </td>" << endl;
+ }
+
+ if ( d->mSettings->taskCategories() ) {
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ formatCategories( ts, todo );
+ *ts << " </td>" << endl;
+ }
+
+ if ( d->mSettings->taskAttendees() ) {
+ *ts << " <td";
+ if ( completed ) {
+ *ts << " class=\"done\"";
+ }
+ *ts << ">" << endl;
+ formatAttendees( ts, todo );
+ *ts << " </td>" << endl;
+ }
+
+ *ts << "</tr>" << endl;
+}
+
+void HtmlExport::createWeekView( QTextStream *ts )
+{
+ Q_UNUSED( ts );
+ // FIXME: Implement this!
+}
+
+void HtmlExport::createJournalView( QTextStream *ts )
+{
+ Q_UNUSED( ts );
+// Journal::List rawJournalList = d->mCalendar->journals();
+ // FIXME: Implement this!
+}
+
+void HtmlExport::createFreeBusyView( QTextStream *ts )
+{
+ Q_UNUSED( ts );
+ // FIXME: Implement this!
+}
+
+bool HtmlExport::checkSecrecy( const Incidence::Ptr &incidence )
+{
+ int secrecy = incidence->secrecy();
+ if ( secrecy == Incidence::SecrecyPublic ) {
+ return true;
+ }
+ if ( secrecy == Incidence::SecrecyPrivate && !d->mSettings->excludePrivate() ) {
+ return true;
+ }
+ if ( secrecy == Incidence::SecrecyConfidential &&
+ !d->mSettings->excludeConfidential() ) {
+ return true;
+ }
+ return false;
+}
+
+void HtmlExport::formatLocation( QTextStream *ts,
+ const Incidence::Ptr &incidence )
+{
+ if ( !incidence->location().isEmpty() ) {
+ *ts << " " << cleanChars( incidence->location() ) << endl;
+ } else {
+ *ts << " &nbsp;" << endl;
+ }
+}
+
+void HtmlExport::formatCategories( QTextStream *ts,
+ const Incidence::Ptr &incidence )
+{
+ if ( !incidence->categoriesStr().isEmpty() ) {
+ *ts << " " << cleanChars( incidence->categoriesStr() ) << endl;
+ } else {
+ *ts << " &nbsp;" << endl;
+ }
+}
+
+void HtmlExport::formatAttendees( QTextStream *ts,
+ const Incidence::Ptr &incidence )
+{
+ Attendee::List attendees = incidence->attendees();
+ if ( attendees.count() ) {
+ *ts << "<em>";
+ *ts << incidence->organizer()->fullName();
+ *ts << "</em><br />";
+ Attendee::List::ConstIterator it;
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a( *it );
+ if ( !a->email().isEmpty() ) {
+ *ts << "<a href=\"mailto:" << a->email();
+ *ts << "\">" << cleanChars( a->name() ) << "</a>";
+ } else {
+ *ts << " " << cleanChars( a->name() );
+ }
+ *ts << "<br />" << endl;
+ }
+ } else {
+ *ts << " &nbsp;" << endl;
+ }
+}
+
+QString HtmlExport::breakString( const QString &text )
+{
+ int number = text.count( "\n" );
+ if ( number <= 0 ) {
+ return text;
+ } else {
+ QString out;
+ QString tmpText = text;
+ int pos = 0;
+ QString tmp;
+ for ( int i = 0; i <= number; ++i ) {
+ pos = tmpText.indexOf( "\n" );
+ tmp = tmpText.left( pos );
+ tmpText = tmpText.right( tmpText.length() - pos - 1 );
+ out += tmp + "<br />";
+ }
+ return out;
+ }
+}
+
+void HtmlExport::createFooter( QTextStream *ts )
+{
+ // FIXME: Implement this in a translatable way!
+ QString trailer = i18nc( "@info/plain", "This page was created " );
+
+/* bool hasPerson = false;
+ bool hasCredit = false;
+ bool hasCreditURL = false;
+ QString mail, name, credit, creditURL;*/
+ if ( !d->mSettings->eMail().isEmpty() ) {
+ if ( !d->mSettings->name().isEmpty() ) {
+ trailer += i18nc( "@info/plain page creator email link with name",
+ "by <link url='mailto:%1'>%2</link> ",
+ d->mSettings->eMail(), d->mSettings->name() );
+ } else {
+ trailer += i18nc( "@info/plain page creator email link",
+ "by <link url='mailto:%1'>%2</link> ",
+ d->mSettings->eMail(), d->mSettings->eMail() );
+ }
+ } else {
+ if ( !d->mSettings->name().isEmpty() ) {
+ trailer += i18nc( "@info/plain page creator name only",
+ "by %1 ", d->mSettings->name() );
+ }
+ }
+ if ( !d->mSettings->creditName().isEmpty() ) {
+ if ( !d->mSettings->creditURL().isEmpty() ) {
+ trailer += i18nc( "@info/plain page credit with name and link",
+ "with <link url='%1'>%2</link>",
+ d->mSettings->creditURL(), d->mSettings->creditName() );
+ } else {
+ trailer += i18nc( "@info/plain page credit name only",
+ "with %1", d->mSettings->creditName() );
+ }
+ }
+ *ts << "<p>" << trailer << "</p>" << endl;
+}
+
+QString cleanChars( const QString &text )
+{
+ QString txt = text;
+ txt = txt.replace( '&', "&amp;" );
+ txt = txt.replace( '<', "&lt;" );
+ txt = txt.replace( '>', "&gt;" );
+ txt = txt.replace( '\"', "&quot;" );
+ txt = txt.replace( QString::fromUtf8( "ä" ), "&auml;" );
+ txt = txt.replace( QString::fromUtf8( "Ä" ), "&Auml;" );
+ txt = txt.replace( QString::fromUtf8( "ö" ), "&ouml;" );
+ txt = txt.replace( QString::fromUtf8( "Ö" ), "&Ouml;" );
+ txt = txt.replace( QString::fromUtf8( "ü" ), "&uuml;" );
+ txt = txt.replace( QString::fromUtf8( "Ü" ), "&Uuml;" );
+ txt = txt.replace( QString::fromUtf8( "ß" ), "&szlig;" );
+ txt = txt.replace( QString::fromUtf8( "€" ), "&euro;" );
+ txt = txt.replace( QString::fromUtf8( "é" ), "&eacute;" );
+
+ return txt;
+}
+
+QString HtmlExport::styleSheet() const
+{
+ if ( !d->mSettings->styleSheet().isEmpty() ) {
+ return d->mSettings->styleSheet();
+ }
+
+ QString css;
+
+ if ( QApplication::isRightToLeft() ) {
+ css += " body { background-color:white; color:black; direction: rtl }\n";
+ css += " td { text-align:center; background-color:#eee }\n";
+ css += " th { text-align:center; background-color:#228; color:white }\n";
+ css += " td.sumdone { background-color:#ccc }\n";
+ css += " td.done { background-color:#ccc }\n";
+ css += " td.subhead { text-align:center; background-color:#ccf }\n";
+ css += " td.datehead { text-align:center; background-color:#ccf }\n";
+ css += " td.space { background-color:white }\n";
+ css += " td.dateholiday { color:red }\n";
+ } else {
+ css += " body { background-color:white; color:black }\n";
+ css += " td { text-align:center; background-color:#eee }\n";
+ css += " th { text-align:center; background-color:#228; color:white }\n";
+ css += " td.sum { text-align:left }\n";
+ css += " td.sumdone { text-align:left; background-color:#ccc }\n";
+ css += " td.done { background-color:#ccc }\n";
+ css += " td.subhead { text-align:center; background-color:#ccf }\n";
+ css += " td.datehead { text-align:center; background-color:#ccf }\n";
+ css += " td.space { background-color:white }\n";
+ css += " td.date { text-align:left }\n";
+ css += " td.dateholiday { text-align:left; color:red }\n";
+ }
+
+ return css;
+}
+
+void HtmlExport::addHoliday( const QDate &date, const QString &name )
+{
+ if ( d->mHolidayMap[date].isEmpty() ) {
+ d->mHolidayMap[date] = name;
+ } else {
+ d->mHolidayMap[date] = i18nc( "@info/plain holiday by date and name",
+ "%1, %2", d->mHolidayMap[date], name );
+ }
+}
+
+QDate HtmlExport::fromDate() const
+{
+ return d->mSettings->dateStart().date();
+}
+
+QDate HtmlExport::toDate() const
+{
+ return d->mSettings->dateEnd().date();
+}
diff --git a/kcalutils/htmlexport.h b/kcalutils/htmlexport.h
new file mode 100644
index 0000000..bc1faff
--- /dev/null
+++ b/kcalutils/htmlexport.h
@@ -0,0 +1,107 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2000-2003 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KCALUTILS_HTMLEXPORT_H
+#define KCALUTILS_HTMLEXPORT_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/event.h>
+#include <kcalcore/incidence.h>
+#include <kcalcore/todo.h>
+
+#include <QtCore/QDateTime>
+#include <QtCore/QString>
+
+namespace KCalCore {
+ class MemoryCalendar;
+}
+
+class QTextStream;
+
+namespace KCalUtils {
+
+class HTMLExportSettings;
+
+/**
+ This class provides the functions to export a calendar as a HTML page.
+*/
+class KCALUTILS_EXPORT HtmlExport
+{
+ public:
+ /**
+ Create new HTML exporter for calendar.
+ */
+ HtmlExport( KCalCore::MemoryCalendar *calendar, HTMLExportSettings *settings );
+ virtual ~HtmlExport();
+
+ /**
+ Writes out the calendar in HTML format.
+ */
+ bool save( const QString &fileName = QString() );
+
+ /**
+ Writes out calendar to text stream.
+ */
+ bool save( QTextStream *ts );
+
+ void addHoliday( const QDate &date, const QString &name );
+
+ protected:
+ void createWeekView( QTextStream *ts );
+ void createMonthView( QTextStream *ts );
+ void createEventList( QTextStream *ts );
+ void createTodoList( QTextStream *ts );
+ void createJournalView( QTextStream *ts );
+ void createFreeBusyView( QTextStream *ts );
+
+ void createTodo( QTextStream *ts, const KCalCore::Todo::Ptr &todo );
+
+ void createEvent( QTextStream *ts, const KCalCore::Event::Ptr &event,
+ const QDate &date, bool withDescription = true );
+
+ void createFooter( QTextStream *ts );
+
+ bool checkSecrecy( const KCalCore::Incidence::Ptr &incidence );
+
+ void formatLocation( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence );
+
+ void formatCategories( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence );
+
+ void formatAttendees( QTextStream *ts, const KCalCore::Incidence::Ptr &incidence );
+
+ QString breakString( const QString &text );
+
+ QDate fromDate() const;
+ QDate toDate() const;
+ QString styleSheet() const;
+
+ private:
+ //@cond PRIVATE
+ Q_DISABLE_COPY( HtmlExport )
+ class Private;
+ Private *const d;
+ //@endcond
+};
+
+}
+
+#endif
diff --git a/kcalutils/htmlexportsettings.kcfg b/kcalutils/htmlexportsettings.kcfg
new file mode 100644
index 0000000..bb5beb9
--- /dev/null
+++ b/kcalutils/htmlexportsettings.kcfg
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <include>kcalutils_export.h</include>
+ <kcfgfile name="libkcalutils_htmlexportrc">
+ <parameter name="application" />
+ </kcfgfile>
+
+ <group name="$(application)-General">
+ <entry type="String" key="Name">
+ <label>Full name of the calendar owner</label>
+ <tooltip>The full name of the calendar owner for the export</tooltip>
+ <whatsthis>Enter the full name to print for the owner of the calendar.</whatsthis>
+ </entry>
+ <entry type="String" key="EMail">
+ <label>Email of the calendar owner</label>
+ <tooltip>The email address of the calendar owner for the export</tooltip>
+ <whatsthis>Enter the email address to print for the owner of the calendar.</whatsthis>
+ </entry>
+ <entry type="String" key="Credit Name">
+ <label>Creator application</label>
+ <tooltip>Creator application of the calendar</tooltip>
+ <whatsthis>Enter a creator application of the calendar, for example KOrganizer.</whatsthis>
+ </entry>
+ <entry type="String" key="Credit URL">
+ <label>Creator URL</label>
+ <tooltip>URL of the creator application of the calendar.</tooltip>
+ <whatsthis>Enter the URL of the creator application of the calendar, for example http://userbase.kde.org/KOrganizer</whatsthis>
+ <default></default>
+ </entry>
+ <entry type="String" key="Page Title">
+ <label>Page Title</label>
+ <default code="true">i18n("Calendar")</default>
+ <tooltip>The title of the exported page</tooltip>
+ <whatsthis>Enter a title for the HTML page.</whatsthis>
+ </entry>
+
+ <entry type="DateTime" key="Date Start">
+ <label>Date start</label>
+ <tooltip>First day of the range to export</tooltip>
+ <whatsthis>First day of the range that shall be exported to HTML.</whatsthis>
+ </entry>
+ <entry type="DateTime" key="Date End">
+ <label>Date end</label>
+ <tooltip>Last day of the range to export</tooltip>
+ <whatsthis>Last day of the range that shall be exported to HTML.</whatsthis>
+ </entry>
+
+ <entry type="Path" key="Output File">
+ <label>Output filename</label>
+ <tooltip>The file name for the export</tooltip>
+ <whatsthis>The output file name for the HTML export.</whatsthis>
+ <default>$HOME/calendar.html</default>
+ </entry>
+ <entry type="String" key="Style Sheet">
+ <label>Style sheet</label>
+ <tooltip>CSS style sheet to use by the final HTML page</tooltip>
+ <whatsthis>CSS style sheet to be used by the final HTML page. This string contains the actual contents of the CSS, not a path to the style sheet.</whatsthis>
+ </entry>
+
+ <entry type="Bool" key="Exclude Private">
+ <label>Exclude private incidences from the export</label>
+ <default>true</default>
+ <tooltip>Exclude private items from the export</tooltip>
+ <whatsthis>Check this box if you do NOT want to export your private items.</whatsthis>
+ </entry>
+ <entry type="Bool" key="Exclude Confidential">
+ <label>Exclude confidential incidences from the export</label>
+ <default>true</default>
+ <tooltip>Exclude confidential items from the export</tooltip>
+ <whatsthis>Check this box if you do NOT want to export your confidential items.</whatsthis>
+ </entry>
+
+ </group>
+
+ <group name="$(application)-Events">
+ <entry type="Bool" key="Event View">
+ <label>Export events as list</label>
+ <default>false</default>
+ <tooltip>Export events as a list</tooltip>
+ <whatsthis>Check this box if you want your events shown as a list.</whatsthis>
+ </entry>
+ <entry type="Bool" key="Month View">
+ <label>Export in month view</label>
+ <default>true</default>
+ <tooltip>Export events in a month view</tooltip>
+ <whatsthis>Check this box if you want your events shown in a month view.</whatsthis>
+ </entry>
+ <entry type="Bool" key="Week View">
+ <label>Export in week view</label>
+ <default>false</default>
+ <tooltip>Export events in a week view</tooltip>
+ <whatsthis>Check this box if you want your events shown in a week view.</whatsthis>
+ </entry>
+ <entry type="String" name="EventTitle" key="Title">
+ <label>Title of the calendar</label>
+ <default code="true">i18n("Calendar")</default>
+ <tooltip>Title for the event calendar</tooltip>
+ <whatsthis>Enter a string to use for the title of the event calendar.</whatsthis>
+ </entry>
+
+ <entry type="Bool" name="EventLocation" key="Export Location">
+ <label>Export location of the events</label>
+ <default>true</default>
+ <tooltip>Include the event locations</tooltip>
+ <whatsthis>Check this box if you want the event locations to be exported, only if the event has a location.</whatsthis>
+ </entry>
+ <entry type="Bool" name="EventCategories" key="Export Categories">
+ <label>Export categories of the events</label>
+ <default>true</default>
+ <tooltip>Include the event categories</tooltip>
+ <whatsthis>Check this box if you want the event categories to be exported.</whatsthis>
+ </entry>
+ <entry type="Bool" name="EventAttendees" key="Export Attendees">
+ <label>Export attendees of the events</label>
+ <tooltip>Include the event attendees</tooltip>
+ <whatsthis>Check this box if you want the event attendees to be exported.</whatsthis>
+ <default>false</default>
+ </entry>
+
+ </group>
+
+ <group name="$(application)-Todos">
+ <entry type="Bool" key="Todo View">
+ <label>Export to-do list</label>
+ <default>true</default>
+ <tooltip>Export the to-do list</tooltip>
+ <whatsthis>Check this box if you want your to-do list to also be exported to the HTML.</whatsthis>
+ </entry>
+ <entry type="String" key="TodoList Title">
+ <label>Title of the to-do list</label>
+ <default code="true">i18n("To-do List")</default>
+ <tooltip>Title for the to-do list</tooltip>
+ <whatsthis>Enter a string to use for the title of the to-do list.</whatsthis>
+ </entry>
+
+ <entry type="Bool" name="TaskDueDate" key="Export Due Date">
+ <label>Export due dates of the to-dos</label>
+ <default>true</default>
+ <tooltip>Include to-do due dates</tooltip>
+ <whatsthis>Check this box if you want the to-do list due dates to be exported, if the to-do does have a due date.</whatsthis>
+ </entry>
+ <entry type="Bool" name="TaskLocation" key="Export Location">
+ <label>Export location of the to-dos</label>
+ <default>true</default>
+ <tooltip>Include the to-do locations</tooltip>
+ <whatsthis>Check this box if you want the to-do locations to be exported, only if the to-do has a location.</whatsthis>
+ </entry>
+ <entry type="Bool" name="TaskCategories" key="Export Categories">
+ <label>Export categories of the to-dos</label>
+ <default>true</default>
+ <tooltip>Include the to-do categories</tooltip>
+ <whatsthis>Check this box if you want the to-do categories to be exported.</whatsthis>
+ </entry>
+ <entry type="Bool" name="TaskAttendees" key="Export Attendees">
+ <label>Export attendees of the to-dos</label>
+ <default>false</default>
+ <tooltip>Include the to-do attendees</tooltip>
+ <whatsthis>Check this box if you want the to-do attendees to be exported.</whatsthis>
+ </entry>
+
+ </group>
+
+ <group name="$(application)-Journals">
+ <entry type="Bool" key="Journal View">
+ <label>Export journals</label>
+ <default>false</default>
+ <tooltip>Export journals</tooltip>
+ <whatsthis>Check this box if you want to export journals as well.</whatsthis>
+ </entry>
+ <entry type="String" key="Journal Title">
+ <label>Title of the journal list</label>
+ <default code="true">i18n("Journals")</default>
+ <tooltip>Title for the journal list</tooltip>
+ <whatsthis>Enter a title for the Journal list, if Journals are exported.</whatsthis>
+ </entry>
+ </group>
+
+ <group name="$(application)-FreeBusy">
+ <entry type="Bool" key="FreeBusy View">
+ <label>Export free/busy list</label>
+ <default>false</default>
+ <tooltip>Export free/busy list</tooltip>
+ <whatsthis>Check this box if you want to export free/busy information as well.</whatsthis>
+ </entry>
+ <entry type="String" name="FreeBusyTitle" key="Free/Busy Title">
+ <label>Title of the free/busy list</label>
+ <default code="true">i18n("Busy times")</default>
+ <tooltip>Title for the free/busy list</tooltip>
+ <whatsthis>Enter a title for the free/busy list, if free/busy times are exported.</whatsthis>
+ </entry>
+ </group>
+
+</kcfg>
diff --git a/kcalutils/htmlexportsettings.kcfgc b/kcalutils/htmlexportsettings.kcfgc
new file mode 100644
index 0000000..13a7eec
--- /dev/null
+++ b/kcalutils/htmlexportsettings.kcfgc
@@ -0,0 +1,11 @@
+# Code generation options for kconfig_compiler
+File=htmlexportsettings.kcfg
+ClassName=HTMLExportSettings
+NameSpace=KCalUtils
+Singleton=false
+Mutators=true
+MemberVariables=public
+GlobalEnums=true
+ItemAccessors=true
+SetUserTexts=true
+Visibility=KCALUTILS_EXPORT
diff --git a/kcalutils/icaldrag.cpp b/kcalutils/icaldrag.cpp
new file mode 100644
index 0000000..ffded7f
--- /dev/null
+++ b/kcalutils/icaldrag.cpp
@@ -0,0 +1,71 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "icaldrag.h"
+
+#include <kcalcore/icalformat.h>
+using namespace KCalCore;
+
+#include <QtCore/QMimeData>
+#include <QtCore/QString>
+
+using namespace KCalUtils;
+using namespace ICalDrag;
+
+QString ICalDrag::mimeType()
+{
+ return "text/calendar";
+}
+
+bool ICalDrag::populateMimeData( QMimeData *me, const MemoryCalendar::Ptr &cal )
+{
+ ICalFormat icf;
+ QString scal = icf.toString( cal, QString(), false );
+
+ if ( scal.length()>0 ){
+ me->setData( mimeType(), scal.toUtf8() );
+ }
+ return canDecode( me );
+}
+
+bool ICalDrag::canDecode( const QMimeData *me )
+{
+ return me->hasFormat( mimeType() );
+}
+
+bool ICalDrag::fromMimeData( const QMimeData *de, const MemoryCalendar::Ptr &cal )
+{
+ if ( !canDecode( de ) ) {
+ return false;
+ }
+ bool success = false;
+
+ QByteArray payload = de->data( mimeType() );
+ if ( payload.size() ) {
+ QString txt = QString::fromUtf8( payload.data() );
+
+ ICalFormat icf;
+ success = icf.fromString( cal, txt );
+ }
+
+ return success;
+}
+
diff --git a/kcalutils/icaldrag.h b/kcalutils/icaldrag.h
new file mode 100644
index 0000000..fa94115
--- /dev/null
+++ b/kcalutils/icaldrag.h
@@ -0,0 +1,63 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KCALUTILS_ICALDRAG_H
+#define KCALUTILS_ICALDRAG_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/memorycalendar.h>
+
+class QMimeData;
+
+namespace KCalUtils {
+
+/**
+ iCalendar drag&drop class.
+*/
+namespace ICalDrag
+{
+ /**
+ Mime-type of iCalendar
+ */
+ KCALUTILS_EXPORT QString mimeType();
+
+ /**
+ Sets the iCalendar representation as data of the drag object
+ */
+ KCALUTILS_EXPORT bool populateMimeData( QMimeData *e,
+ const KCalCore::MemoryCalendar::Ptr &cal );
+
+ /**
+ Return, if drag&drop object can be decode to iCalendar.
+ */
+ KCALUTILS_EXPORT bool canDecode( const QMimeData * );
+
+ /**
+ Decode drag&drop object to iCalendar component \a cal.
+ */
+ KCALUTILS_EXPORT bool fromMimeData( const QMimeData *e,
+ const KCalCore::MemoryCalendar::Ptr &cal );
+}
+
+}
+
+#endif
diff --git a/kcalutils/incidenceformatter.cpp b/kcalutils/incidenceformatter.cpp
new file mode 100644
index 0000000..d5a634a
--- /dev/null
+++ b/kcalutils/incidenceformatter.cpp
@@ -0,0 +1,4512 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
+ Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and provides
+ static functions for formatting Incidences for various purposes.
+
+ @brief
+ Provides methods to format Incidences in various ways for display purposes.
+
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+ @author Allen Winter \<allen@kdab.com\>
+*/
+#include "incidenceformatter.h"
+#include "stringify.h"
+
+#include <kcalcore/event.h>
+#include <kcalcore/freebusy.h>
+#include <kcalcore/icalformat.h>
+#include <kcalcore/journal.h>
+#include <kcalcore/memorycalendar.h>
+#include <kcalcore/todo.h>
+#include <kcalcore/visitor.h>
+using namespace KCalCore;
+
+#include <kpimutils/email.h>
+
+#include <KCalendarSystem>
+#include <KDebug>
+#include <KEMailSettings>
+#include <KIconLoader>
+#include <KLocale>
+#include <KMimeType>
+#include <KSystemTimeZone>
+
+#include <QtCore/QBitArray>
+#include <QtGui/QApplication>
+#include <QtGui/QPalette>
+#include <QtGui/QTextDocument>
+
+using namespace KCalUtils;
+using namespace IncidenceFormatter;
+
+/*******************
+ * General helpers
+ *******************/
+
+//@cond PRIVATE
+static QString htmlAddLink( const QString &ref, const QString &text,
+ bool newline = true )
+{
+ QString tmpStr( "<a href=\"" + ref + "\">" + text + "</a>" );
+ if ( newline ) {
+ tmpStr += '\n';
+ }
+ return tmpStr;
+}
+
+static QString htmlAddMailtoLink( const QString &email, const QString &name )
+{
+ QString str;
+
+ if ( !email.isEmpty() ) {
+ Person person( name, email );
+ QString path = person.fullName().simplified();
+ if ( path.isEmpty() || path.startsWith( '"' ) ) {
+ path = email;
+ }
+ KUrl mailto;
+ mailto.setProtocol( "mailto" );
+ mailto.setPath( path );
+ const QString iconPath =
+ KIconLoader::global()->iconPath( "mail-message-new", KIconLoader::Small );
+ str = htmlAddLink( mailto.url(), "<img valign=\"top\" src=\"" + iconPath + "\">" );
+ }
+ return str;
+}
+
+static QString htmlAddUidLink( const QString &email, const QString &name, const QString &uid )
+{
+ QString str;
+
+ if ( !uid.isEmpty() ) {
+ // There is a UID, so make a link to the addressbook
+ if ( name.isEmpty() ) {
+ // Use the email address for text
+ str += htmlAddLink( "uid:" + uid, email );
+ } else {
+ str += htmlAddLink( "uid:" + uid, name );
+ }
+ }
+ return str;
+}
+
+static QString htmlAddTag( const QString &tag, const QString &text )
+{
+ int numLineBreaks = text.count( "\n" );
+ QString str = '<' + tag + '>';
+ QString tmpText = text;
+ QString tmpStr = str;
+ if( numLineBreaks >= 0 ) {
+ if ( numLineBreaks > 0 ) {
+ int pos = 0;
+ QString tmp;
+ for ( int i = 0; i <= numLineBreaks; ++i ) {
+ pos = tmpText.indexOf( "\n" );
+ tmp = tmpText.left( pos );
+ tmpText = tmpText.right( tmpText.length() - pos - 1 );
+ tmpStr += tmp + "<br>";
+ }
+ } else {
+ tmpStr += tmpText;
+ }
+ }
+ tmpStr += "</" + tag + '>';
+ return tmpStr;
+}
+
+static QPair<QString, QString> searchNameAndUid( const QString &email, const QString &name,
+ const QString &uid )
+{
+ // Yes, this is a silly method now, but it's predecessor was quite useful in e35.
+ // For now, please keep this sillyness until e35 is frozen to ease forward porting.
+ // -Allen
+ QPair<QString, QString>s;
+ s.first = name;
+ s.second = uid;
+ if ( !email.isEmpty() && ( name.isEmpty() || uid.isEmpty() ) ) {
+ s.second.clear();
+ }
+ return s;
+}
+
+static QString searchName( const QString &email, const QString &name )
+{
+ const QString printName = name.isEmpty() ? email : name;
+ return printName;
+}
+
+static bool iamAttendee( Attendee::Ptr attendee )
+{
+ // Check if I'm this attendee
+
+ bool iam = false;
+ KEMailSettings settings;
+ QStringList profiles = settings.profiles();
+ for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
+ settings.setProfile( *it );
+ if ( settings.getSetting( KEMailSettings::EmailAddress ) == attendee->email() ) {
+ iam = true;
+ break;
+ }
+ }
+ return iam;
+}
+
+static bool iamOrganizer( Incidence::Ptr incidence )
+{
+ // Check if I'm the organizer for this incidence
+
+ if ( !incidence ) {
+ return false;
+ }
+
+ bool iam = false;
+ KEMailSettings settings;
+ QStringList profiles = settings.profiles();
+ for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
+ settings.setProfile( *it );
+ if ( settings.getSetting( KEMailSettings::EmailAddress ) == incidence->organizer()->email() ) {
+ iam = true;
+ break;
+ }
+ }
+ return iam;
+}
+
+static bool senderIsOrganizer( Incidence::Ptr incidence, const QString &sender )
+{
+ // Check if the specified sender is the organizer
+
+ if ( !incidence || sender.isEmpty() ) {
+ return true;
+ }
+
+ bool isorg = true;
+ QString senderName, senderEmail;
+ if ( KPIMUtils::extractEmailAddressAndName( sender, senderEmail, senderName ) ) {
+ // for this heuristic, we say the sender is the organizer if either the name or the email match.
+ if ( incidence->organizer()->email() != senderEmail &&
+ incidence->organizer()->name() != senderName ) {
+ isorg = false;
+ }
+ }
+ return isorg;
+}
+
+static bool attendeeIsOrganizer( const Incidence::Ptr &incidence, const Attendee::Ptr &attendee )
+{
+ if ( incidence && attendee &&
+ ( incidence->organizer()->email() == attendee->email() ) ) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static QString organizerName( const Incidence::Ptr incidence, const QString &defName )
+{
+ QString tName;
+ if ( !defName.isEmpty() ) {
+ tName = defName;
+ } else {
+ tName = i18n( "Organizer Unknown" );
+ }
+
+ QString name;
+ if ( incidence ) {
+ name = incidence->organizer()->name();
+ if ( name.isEmpty() ) {
+ name = incidence->organizer()->email();
+ }
+ }
+ if ( name.isEmpty() ) {
+ name = tName;
+ }
+ return name;
+}
+
+static QString firstAttendeeName( const Incidence::Ptr &incidence, const QString &defName )
+{
+ QString tName;
+ if ( !defName.isEmpty() ) {
+ tName = defName;
+ } else {
+ tName = i18n( "Sender" );
+ }
+
+ QString name;
+ if ( incidence ) {
+ Attendee::List attendees = incidence->attendees();
+ if( attendees.count() > 0 ) {
+ Attendee::Ptr attendee = *attendees.begin();
+ name = attendee->name();
+ if ( name.isEmpty() ) {
+ name = attendee->email();
+ }
+ }
+ }
+ if ( name.isEmpty() ) {
+ name = tName;
+ }
+ return name;
+}
+
+static QString rsvpStatusIconPath( Attendee::PartStat status )
+{
+ QString iconPath;
+ switch ( status ) {
+ case Attendee::Accepted:
+ iconPath = KIconLoader::global()->iconPath( "dialog-ok-apply", KIconLoader::Small );
+ break;
+ case Attendee::Declined:
+ iconPath = KIconLoader::global()->iconPath( "dialog-cancel", KIconLoader::Small );
+ break;
+ case Attendee::NeedsAction:
+ iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
+ break;
+ case Attendee::InProcess:
+ iconPath = KIconLoader::global()->iconPath( "help-about", KIconLoader::Small );
+ break;
+ case Attendee::Tentative:
+ iconPath = KIconLoader::global()->iconPath( "dialog-ok", KIconLoader::Small );
+ break;
+ case Attendee::Delegated:
+ iconPath = KIconLoader::global()->iconPath( "mail-forward", KIconLoader::Small );
+ break;
+ case Attendee::Completed:
+ iconPath = KIconLoader::global()->iconPath( "mail-mark-read", KIconLoader::Small );
+ default:
+ break;
+ }
+ return iconPath;
+}
+
+//@endcond
+
+/*******************************************************************
+ * Helper functions for the extensive display (display viewer)
+ *******************************************************************/
+
+//@cond PRIVATE
+static QString displayViewFormatPerson( const QString &email, const QString &name,
+ const QString &uid, const QString &iconPath )
+{
+ // Search for new print name or uid, if needed.
+ QPair<QString, QString> s = searchNameAndUid( email, name, uid );
+ const QString printName = s.first;
+ const QString printUid = s.second;
+
+ QString personString;
+ if ( !iconPath.isEmpty() ) {
+ personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
+ }
+
+ // Make the uid link
+ if ( !printUid.isEmpty() ) {
+ personString += htmlAddUidLink( email, printName, printUid );
+ } else {
+ // No UID, just show some text
+ personString += ( printName.isEmpty() ? email : printName );
+ }
+
+#ifndef KDEPIM_MOBILE_UI
+ // Make the mailto link
+ if ( !email.isEmpty() ) {
+ personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
+ }
+#endif
+
+ return personString;
+}
+
+static QString displayViewFormatPerson( const QString &email, const QString &name,
+ const QString &uid, Attendee::PartStat status )
+{
+ return displayViewFormatPerson( email, name, uid, rsvpStatusIconPath( status ) );
+}
+
+static bool incOrganizerOwnsCalendar( const Calendar::Ptr &calendar,
+ const Incidence::Ptr &incidence )
+{
+ //PORTME! Look at e35's CalHelper::incOrganizerOwnsCalendar
+
+ // For now, use iamOrganizer() which is only part of the check
+ Q_UNUSED( calendar );
+ return iamOrganizer( incidence );
+}
+
+static QString displayViewFormatAttendeeRoleList( Incidence::Ptr incidence, Attendee::Role role,
+ bool showStatus )
+{
+ QString tmpStr;
+ Attendee::List::ConstIterator it;
+ Attendee::List attendees = incidence->attendees();
+
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a = *it;
+ if ( a->role() != role ) {
+ // skip this role
+ continue;
+ }
+ if ( attendeeIsOrganizer( incidence, a ) ) {
+ // skip attendee that is also the organizer
+ continue;
+ }
+ tmpStr += displayViewFormatPerson( a->email(), a->name(), a->uid(),
+ showStatus ? a->status() : Attendee::None );
+ if ( !a->delegator().isEmpty() ) {
+ tmpStr += i18n( " (delegated by %1)", a->delegator() );
+ }
+ if ( !a->delegate().isEmpty() ) {
+ tmpStr += i18n( " (delegated to %1)", a->delegate() );
+ }
+ tmpStr += "<br>";
+ }
+ if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
+ tmpStr.chop( 4 );
+ }
+ return tmpStr;
+}
+
+static QString displayViewFormatAttendees( Calendar::Ptr calendar, Incidence::Ptr incidence )
+{
+ QString tmpStr, str;
+
+ // Add organizer link
+ int attendeeCount = incidence->attendees().count();
+ if ( attendeeCount > 1 ||
+ ( attendeeCount == 1 &&
+ !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
+
+ QPair<QString, QString> s = searchNameAndUid( incidence->organizer()->email(),
+ incidence->organizer()->name(),
+ QString() );
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Organizer:" ) + "</b></td>";
+ const QString iconPath =
+ KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
+ tmpStr += "<td>" + displayViewFormatPerson( incidence->organizer()->email(),
+ s.first, s.second, iconPath ) +
+ "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // Show the attendee status if the incidence's organizer owns the resource calendar,
+ // which means they are running the show and have all the up-to-date response info.
+ bool showStatus = incOrganizerOwnsCalendar( calendar, incidence );
+
+ // Add "chair"
+ str = displayViewFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Chair:" ) + "</b></td>";
+ tmpStr += "<td>" + str + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // Add required participants
+ str = displayViewFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Required Participants:" ) + "</b></td>";
+ tmpStr += "<td>" + str + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // Add optional participants
+ str = displayViewFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Optional Participants:" ) + "</b></td>";
+ tmpStr += "<td>" + str + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // Add observers
+ str = displayViewFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Observers:" ) + "</b></td>";
+ tmpStr += "<td>" + str + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ return tmpStr;
+}
+
+static QString displayViewFormatAttachments( Incidence::Ptr incidence )
+{
+ QString tmpStr;
+ Attachment::List as = incidence->attachments();
+ Attachment::List::ConstIterator it;
+ int count = 0;
+ for ( it = as.constBegin(); it != as.constEnd(); ++it ) {
+ count++;
+ if ( (*it)->isUri() ) {
+ QString name;
+ if ( (*it)->uri().startsWith( QLatin1String( "kmail:" ) ) ) {
+ name = i18n( "Show mail" );
+ } else {
+ if ( (*it)->label().isEmpty() ) {
+ name = (*it)->uri();
+ } else {
+ name = (*it)->label();
+ }
+ }
+ tmpStr += htmlAddLink( (*it)->uri(), name );
+ } else {
+ tmpStr += htmlAddLink( QString::fromLatin1( "ATTACH:%1" ).
+ arg( QString::fromUtf8( (*it)->label().toUtf8().toBase64() ) ),
+ (*it)->label() );
+ }
+ if ( count < as.count() ) {
+ tmpStr += "<br>";
+ }
+ }
+ return tmpStr;
+}
+
+static QString displayViewFormatCategories( Incidence::Ptr incidence )
+{
+ // We do not use Incidence::categoriesStr() since it does not have whitespace
+ return incidence->categories().join( ", " );
+}
+
+static QString displayViewFormatCreationDate( Incidence::Ptr incidence, KDateTime::Spec spec )
+{
+ KDateTime kdt = incidence->created().toTimeSpec( spec );
+ return i18n( "Creation date: %1", dateTimeToString( incidence->created(), false, true, spec ) );
+}
+
+static QString displayViewFormatBirthday( Event::Ptr event )
+{
+ if ( !event ) {
+ return QString();
+ }
+ if ( event->customProperty( "KABC", "BIRTHDAY" ) != "YES" &&
+ event->customProperty( "KABC", "ANNIVERSARY" ) != "YES" ) {
+ return QString();
+ }
+
+ QString uid_1 = event->customProperty( "KABC", "UID-1" );
+ QString name_1 = event->customProperty( "KABC", "NAME-1" );
+ QString email_1= event->customProperty( "KABC", "EMAIL-1" );
+
+ QString tmpStr = displayViewFormatPerson( email_1, name_1, uid_1, QString() );
+ return tmpStr;
+}
+
+static QString displayViewFormatHeader( Incidence::Ptr incidence )
+{
+ QString tmpStr = "<table><tr>";
+
+ // show icons
+ KIconLoader *iconLoader = KIconLoader::global();
+ tmpStr += "<td>";
+
+ QString iconPath;
+ if ( incidence->customProperty( "KABC", "BIRTHDAY" ) == "YES" ) {
+ iconPath = iconLoader->iconPath( "view-calendar-birthday", KIconLoader::Small );
+ } else if ( incidence->customProperty( "KABC", "ANNIVERSARY" ) == "YES" ) {
+ iconPath = iconLoader->iconPath( "view-calendar-wedding-anniversary", KIconLoader::Small );
+ } else {
+ iconPath = iconLoader->iconPath( incidence->iconName(), KIconLoader::Small );
+ }
+ tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
+
+ if ( incidence->hasEnabledAlarms() ) {
+ tmpStr += "<img valign=\"top\" src=\"" +
+ iconLoader->iconPath( "preferences-desktop-notification-bell", KIconLoader::Small ) +
+ "\">";
+ }
+ if ( incidence->recurs() ) {
+ tmpStr += "<img valign=\"top\" src=\"" +
+ iconLoader->iconPath( "edit-redo", KIconLoader::Small ) +
+ "\">";
+ }
+ if ( incidence->isReadOnly() ) {
+ tmpStr += "<img valign=\"top\" src=\"" +
+ iconLoader->iconPath( "object-locked", KIconLoader::Small ) +
+ "\">";
+ }
+ tmpStr += "</td>";
+
+ tmpStr += "<td>";
+ tmpStr += "<b><u>" + incidence->richSummary() + "</u></b>";
+ tmpStr += "</td>";
+
+ tmpStr += "</tr></table>";
+
+ return tmpStr;
+}
+
+static QString displayViewFormatEvent( const Calendar::Ptr calendar, const QString &sourceName,
+ const Event::Ptr &event,
+ const QDate &date, KDateTime::Spec spec )
+{
+ if ( !event ) {
+ return QString();
+ }
+
+ QString tmpStr = displayViewFormatHeader( event );
+
+ tmpStr += "<table>";
+ tmpStr += "<col width=\"25%\"/>";
+ tmpStr += "<col width=\"75%\"/>";
+
+ const QString calStr = calendar ? resourceString( calendar, event ) : sourceName;
+ if ( !calStr.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
+ tmpStr += "<td>" + calStr + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( !event->location().isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
+ tmpStr += "<td>" + event->richLocation() + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ KDateTime startDt = event->dtStart();
+ KDateTime endDt = event->dtEnd();
+ if ( event->recurs() ) {
+ if ( date.isValid() ) {
+ KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
+ int diffDays = startDt.daysTo( kdt );
+ kdt = kdt.addSecs( -1 );
+ startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
+ if ( event->hasEndDate() ) {
+ endDt = endDt.addDays( diffDays );
+ if ( startDt > endDt ) {
+ startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
+ endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
+ }
+ }
+ }
+ }
+
+ tmpStr += "<tr>";
+ if ( event->allDay() ) {
+ if ( event->isMultiDay() ) {
+ tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ i18nc( "<beginTime> - <endTime>","%1 - %2",
+ dateToString( startDt, false, spec ),
+ dateToString( endDt, false, spec ) ) +
+ "</td>";
+ } else {
+ tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ i18nc( "date as string","%1",
+ dateToString( startDt, false, spec ) ) +
+ "</td>";
+ }
+ } else {
+ if ( event->isMultiDay() ) {
+ tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ i18nc( "<beginTime> - <endTime>","%1 - %2",
+ dateToString( startDt, false, spec ),
+ dateToString( endDt, false, spec ) ) +
+ "</td>";
+ } else {
+ tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ i18nc( "date as string", "%1",
+ dateToString( startDt, false, spec ) ) +
+ "</td>";
+
+ tmpStr += "</tr><tr>";
+ tmpStr += "<td><b>" + i18n( "Time:" ) + "</b></td>";
+ if ( event->hasEndDate() && startDt != endDt ) {
+ tmpStr += "<td>" +
+ i18nc( "<beginTime> - <endTime>","%1 - %2",
+ timeToString( startDt, true, spec ),
+ timeToString( endDt, true, spec ) ) +
+ "</td>";
+ } else {
+ tmpStr += "<td>" +
+ timeToString( startDt, true, spec ) +
+ "</td>";
+ }
+ }
+ }
+ tmpStr += "</tr>";
+
+ QString durStr = durationString( event );
+ if ( !durStr.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
+ tmpStr += "<td>" + durStr + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( event->recurs() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ recurrenceString( event ) +
+ "</td>";
+ tmpStr += "</tr>";
+ }
+
+ const bool isBirthday = event->customProperty( "KABC", "BIRTHDAY" ) == "YES";
+ const bool isAnniversary = event->customProperty( "KABC", "ANNIVERSARY" ) == "YES";
+
+ if ( isBirthday || isAnniversary ) {
+ tmpStr += "<tr>";
+ if ( isAnniversary ) {
+ tmpStr += "<td><b>" + i18n( "Anniversary:" ) + "</b></td>";
+ } else {
+ tmpStr += "<td><b>" + i18n( "Birthday:" ) + "</b></td>";
+ }
+ tmpStr += "<td>" + displayViewFormatBirthday( event ) + "</td>";
+ tmpStr += "</tr>";
+ tmpStr += "</table>";
+ return tmpStr;
+ }
+
+ if ( !event->description().isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
+ tmpStr += "<td>" + event->richDescription() + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // TODO: print comments?
+
+ int reminderCount = event->alarms().count();
+ if ( reminderCount > 0 && event->hasEnabledAlarms() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Reminder:", "Reminders:", reminderCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + reminderStringList( event ).join( "<br>" ) + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ tmpStr += displayViewFormatAttendees( calendar, event );
+
+ int categoryCount = event->categories().count();
+ if ( categoryCount > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>";
+ tmpStr += i18np( "Category:", "Categories:", categoryCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + displayViewFormatCategories( event ) + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ int attachmentCount = event->attachments().count();
+ if ( attachmentCount > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Attachment:", "Attachments:", attachmentCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + displayViewFormatAttachments( event ) + "</td>";
+ tmpStr += "</tr>";
+ }
+ tmpStr += "</table>";
+
+ tmpStr += "<p><em>" + displayViewFormatCreationDate( event, spec ) + "</em>";
+
+ return tmpStr;
+}
+
+static QString displayViewFormatTodo( const Calendar::Ptr &calendar, const QString &sourceName,
+ const Todo::Ptr &todo,
+ const QDate &date, KDateTime::Spec spec )
+{
+ if ( !todo ) {
+ kDebug() << "IncidenceFormatter::displayViewFormatTodo was called without to-do, quitting";
+ return QString();
+ }
+
+ QString tmpStr = displayViewFormatHeader( todo );
+
+ tmpStr += "<table>";
+ tmpStr += "<col width=\"25%\"/>";
+ tmpStr += "<col width=\"75%\"/>";
+
+ const QString calStr = calendar ? resourceString( calendar, todo ) : sourceName;
+ if ( !calStr.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
+ tmpStr += "<td>" + calStr + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( !todo->location().isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Location:" ) + "</b></td>";
+ tmpStr += "<td>" + todo->richLocation() + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ const bool hastStartDate = todo->hasStartDate() && todo->dtStart().isValid();
+ const bool hasDueDate = todo->hasDueDate() && todo->dtDue().isValid();
+
+ if ( hastStartDate ) {
+ KDateTime startDt = todo->dtStart( true /**first*/);
+ if ( todo->recurs() ) {
+ if ( date.isValid() ) {
+ if ( hasDueDate ) {
+ // In kdepim all recuring to-dos have due date.
+ const int length = startDt.daysTo( todo->dtDue( true /**first*/) );
+ if ( length >= 0 ) {
+ startDt.setDate( date.addDays( -length ) );
+ } else {
+ kError() << "DTSTART is bigger than DTDUE, todo->uid() is " << todo->uid();
+ startDt.setDate( date );
+ }
+ } else {
+ kError() << "To-do is recurring but has no DTDUE set, todo->uid() is " << todo->uid();
+ startDt.setDate( date );
+ }
+ }
+ }
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18nc( "to-do start date/time", "Start:" ) +
+ "</b></td>";
+ tmpStr += "<td>" +
+ dateTimeToString( startDt, todo->allDay(), false, spec ) +
+ "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( hasDueDate ) {
+ KDateTime dueDt = todo->dtDue();
+ if ( todo->recurs() ) {
+ if ( date.isValid() ) {
+ KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
+ kdt = kdt.addSecs( -1 );
+ dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
+ }
+ }
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18nc( "to-do due date/time", "Due:" ) +
+ "</b></td>";
+ tmpStr += "<td>" +
+ dateTimeToString( dueDt, todo->allDay(), false, spec ) +
+ "</td>";
+ tmpStr += "</tr>";
+ }
+
+ QString durStr = durationString( todo );
+ if ( !durStr.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Duration:" ) + "</b></td>";
+ tmpStr += "<td>" + durStr + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( todo->recurs() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Recurrence:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ recurrenceString( todo ) +
+ "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( !todo->description().isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
+ tmpStr += "<td>" + todo->richDescription() + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ // TODO: print comments?
+
+ int reminderCount = todo->alarms().count();
+ if ( reminderCount > 0 && todo->hasEnabledAlarms() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Reminder:", "Reminders:", reminderCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + reminderStringList( todo ).join( "<br>" ) + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ tmpStr += displayViewFormatAttendees( calendar, todo );
+
+ int categoryCount = todo->categories().count();
+ if ( categoryCount > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Category:", "Categories:", categoryCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + displayViewFormatCategories( todo ) + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ if ( todo->priority() > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Priority:" ) + "</b></td>";
+ tmpStr += "<td>";
+ tmpStr += QString::number( todo->priority() );
+ tmpStr += "</td>";
+ tmpStr += "</tr>";
+ }
+
+ tmpStr += "<tr>";
+ if ( todo->isCompleted() ) {
+ tmpStr += "<td><b>" + i18nc( "Completed: date", "Completed:" ) + "</b></td>";
+ tmpStr += "<td>";
+ tmpStr += Stringify::todoCompletedDateTime( todo );
+ } else {
+ tmpStr += "<td><b>" + i18n( "Percent Done:" ) + "</b></td>";
+ tmpStr += "<td>";
+ tmpStr += i18n( "%1%", todo->percentComplete() );
+ }
+ tmpStr += "</td>";
+ tmpStr += "</tr>";
+
+ int attachmentCount = todo->attachments().count();
+ if ( attachmentCount > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Attachment:", "Attachments:", attachmentCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + displayViewFormatAttachments( todo ) + "</td>";
+ tmpStr += "</tr>";
+ }
+ tmpStr += "</table>";
+
+ tmpStr += "<p><em>" + displayViewFormatCreationDate( todo, spec ) + "</em>";
+
+ return tmpStr;
+}
+
+static QString displayViewFormatJournal( const Calendar::Ptr &calendar, const QString &sourceName,
+ const Journal::Ptr &journal, KDateTime::Spec spec )
+{
+ if ( !journal ) {
+ return QString();
+ }
+
+ QString tmpStr = displayViewFormatHeader( journal );
+
+ tmpStr += "<table>";
+ tmpStr += "<col width=\"25%\"/>";
+ tmpStr += "<col width=\"75%\"/>";
+
+ const QString calStr = calendar ? resourceString( calendar, journal ) : sourceName;
+ if ( !calStr.isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Calendar:" ) + "</b></td>";
+ tmpStr += "<td>" + calStr + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Date:" ) + "</b></td>";
+ tmpStr += "<td>" +
+ dateToString( journal->dtStart(), false, spec ) +
+ "</td>";
+ tmpStr += "</tr>";
+
+ if ( !journal->description().isEmpty() ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" + i18n( "Description:" ) + "</b></td>";
+ tmpStr += "<td>" + journal->richDescription() + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ int categoryCount = journal->categories().count();
+ if ( categoryCount > 0 ) {
+ tmpStr += "<tr>";
+ tmpStr += "<td><b>" +
+ i18np( "Category:", "Categories:", categoryCount ) +
+ "</b></td>";
+ tmpStr += "<td>" + displayViewFormatCategories( journal ) + "</td>";
+ tmpStr += "</tr>";
+ }
+
+ tmpStr += "</table>";
+
+ tmpStr += "<p><em>" + displayViewFormatCreationDate( journal, spec ) + "</em>";
+
+ return tmpStr;
+}
+
+static QString displayViewFormatFreeBusy( const Calendar::Ptr &calendar, const QString &sourceName,
+ const FreeBusy::Ptr &fb, KDateTime::Spec spec )
+{
+ Q_UNUSED( calendar );
+ Q_UNUSED( sourceName );
+ if ( !fb ) {
+ return QString();
+ }
+
+ QString tmpStr(
+ htmlAddTag(
+ "h2", i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) ) );
+
+ tmpStr += htmlAddTag( "h4",
+ i18n( "Busy times in date range %1 - %2:",
+ dateToString( fb->dtStart(), true, spec ),
+ dateToString( fb->dtEnd(), true, spec ) ) );
+
+ QString text =
+ htmlAddTag( "em",
+ htmlAddTag( "b", i18nc( "tag for busy periods list", "Busy:" ) ) );
+
+ Period::List periods = fb->busyPeriods();
+ Period::List::iterator it;
+ for ( it = periods.begin(); it != periods.end(); ++it ) {
+ Period per = *it;
+ if ( per.hasDuration() ) {
+ int dur = per.duration().asSeconds();
+ QString cont;
+ if ( dur >= 3600 ) {
+ cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
+ dur %= 3600;
+ }
+ if ( dur >= 60 ) {
+ cont += i18ncp( "minutes part duration", "1 minute ", "%1 minutes ", dur / 60 );
+ dur %= 60;
+ }
+ if ( dur > 0 ) {
+ cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
+ }
+ text += i18nc( "startDate for duration", "%1 for %2",
+ dateTimeToString( per.start(), false, true, spec ),
+ cont );
+ text += "<br>";
+ } else {
+ if ( per.start().date() == per.end().date() ) {
+ text += i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
+ dateToString( per.start(), true, spec ),
+ timeToString( per.start(), true, spec ),
+ timeToString( per.end(), true, spec ) );
+ } else {
+ text += i18nc( "fromDateTime - toDateTime", "%1 - %2",
+ dateTimeToString( per.start(), false, true, spec ),
+ dateTimeToString( per.end(), false, true, spec ) );
+ }
+ text += "<br>";
+ }
+ }
+ tmpStr += htmlAddTag( "p", text );
+ return tmpStr;
+}
+//@endcond
+
+//@cond PRIVATE
+class KCalUtils::IncidenceFormatter::EventViewerVisitor : public Visitor
+{
+ public:
+ EventViewerVisitor()
+ : mCalendar( 0 ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
+
+ bool act( const Calendar::Ptr &calendar, IncidenceBase::Ptr incidence, const QDate &date,
+ KDateTime::Spec spec=KDateTime::Spec() )
+ {
+ mCalendar = calendar;
+ mSourceName.clear();
+ mDate = date;
+ mSpec = spec;
+ mResult = "";
+ return incidence->accept( *this, incidence );
+ }
+
+ bool act( const QString &sourceName, IncidenceBase::Ptr incidence, const QDate &date,
+ KDateTime::Spec spec=KDateTime::Spec() )
+ {
+ mSourceName = sourceName;
+ mDate = date;
+ mSpec = spec;
+ mResult = "";
+ return incidence->accept( *this, incidence );
+ }
+
+ QString result() const { return mResult; }
+
+ protected:
+ bool visit( Event::Ptr event )
+ {
+ mResult = displayViewFormatEvent( mCalendar, mSourceName, event, mDate, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( Todo::Ptr todo )
+ {
+ mResult = displayViewFormatTodo( mCalendar, mSourceName, todo, mDate, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( Journal::Ptr journal )
+ {
+ mResult = displayViewFormatJournal( mCalendar, mSourceName, journal, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( FreeBusy::Ptr fb )
+ {
+ mResult = displayViewFormatFreeBusy( mCalendar, mSourceName, fb, mSpec );
+ return !mResult.isEmpty();
+ }
+
+ protected:
+ Calendar::Ptr mCalendar;
+ QString mSourceName;
+ QDate mDate;
+ KDateTime::Spec mSpec;
+ QString mResult;
+};
+//@endcond
+
+QString IncidenceFormatter::extensiveDisplayStr( const Calendar::Ptr &calendar,
+ const IncidenceBase::Ptr &incidence,
+ const QDate &date,
+ KDateTime::Spec spec )
+{
+ if ( !incidence ) {
+ return QString();
+ }
+
+ EventViewerVisitor v;
+ if ( v.act( calendar, incidence, date, spec ) ) {
+ return v.result();
+ } else {
+ return QString();
+ }
+}
+
+QString IncidenceFormatter::extensiveDisplayStr( const QString &sourceName,
+ const IncidenceBase::Ptr &incidence,
+ const QDate &date,
+ KDateTime::Spec spec )
+{
+ if ( !incidence ) {
+ return QString();
+ }
+
+ EventViewerVisitor v;
+ if ( v.act( sourceName, incidence, date, spec ) ) {
+ return v.result();
+ } else {
+ return QString();
+ }
+}
+/***********************************************************************
+ * Helper functions for the body part formatter of kmail (Invitations)
+ ***********************************************************************/
+
+//@cond PRIVATE
+static QString string2HTML( const QString &str )
+{
+ return Qt::convertFromPlainText( str, Qt::WhiteSpaceNormal );
+}
+
+static QString cleanHtml( const QString &html )
+{
+ QRegExp rx( "<body[^>]*>(.*)</body>", Qt::CaseInsensitive );
+ rx.indexIn( html );
+ QString body = rx.cap( 1 );
+
+ return Qt::escape( body.remove( QRegExp( "<[^>]*>" ) ).trimmed() );
+}
+
+static QString invitationSummary( const Incidence::Ptr &incidence, bool noHtmlMode )
+{
+ QString summaryStr = i18n( "Summary unspecified" );
+ if ( !incidence->summary().isEmpty() ) {
+ if ( !incidence->summaryIsRich() ) {
+ summaryStr = Qt::escape( incidence->summary() );
+ } else {
+ summaryStr = incidence->richSummary();
+ if ( noHtmlMode ) {
+ summaryStr = cleanHtml( summaryStr );
+ }
+ }
+ }
+ return summaryStr;
+}
+
+static QString invitationLocation( const Incidence::Ptr &incidence, bool noHtmlMode )
+{
+ QString locationStr = i18n( "Location unspecified" );
+ if ( !incidence->location().isEmpty() ) {
+ if ( !incidence->locationIsRich() ) {
+ locationStr = Qt::escape( incidence->location() );
+ } else {
+ locationStr = incidence->richLocation();
+ if ( noHtmlMode ) {
+ locationStr = cleanHtml( locationStr );
+ }
+ }
+ }
+ return locationStr;
+}
+
+static QString eventStartTimeStr( const Event::Ptr &event )
+{
+ QString tmp;
+ if ( !event->allDay() ) {
+ tmp = i18nc( "%1: Start Date, %2: Start Time", "%1 %2",
+ dateToString( event->dtStart(), true, KSystemTimeZones::local() ),
+ timeToString( event->dtStart(), true, KSystemTimeZones::local() ) );
+ } else {
+ tmp = i18nc( "%1: Start Date", "%1 (all day)",
+ dateToString( event->dtStart(), true, KSystemTimeZones::local() ) );
+ }
+ return tmp;
+}
+
+static QString eventEndTimeStr( const Event::Ptr &event )
+{
+ QString tmp;
+ if ( event->hasEndDate() && event->dtEnd().isValid() ) {
+ if ( !event->allDay() ) {
+ tmp = i18nc( "%1: End Date, %2: End Time", "%1 %2",
+ dateToString( event->dtEnd(), true, KSystemTimeZones::local() ),
+ timeToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
+ } else {
+ tmp = i18nc( "%1: End Date", "%1 (all day)",
+ dateToString( event->dtEnd(), true, KSystemTimeZones::local() ) );
+ }
+ }
+ return tmp;
+}
+
+static QString htmlInvitationDetailsBegin()
+{
+ QString dir = ( QApplication::isRightToLeft() ? "rtl" : "ltr" );
+ return QString( "<div dir=\"%1\">\n" ).arg( dir );
+}
+
+static QString htmlInvitationDetailsEnd()
+{
+ return "</div>\n";
+}
+
+static QString htmlInvitationDetailsTableBegin()
+{
+ return "<table cellspacing=\"4\" style=\"border-width:4px; border-style:groove\">";
+}
+
+static QString htmlInvitationDetailsTableEnd()
+{
+ return "</table>\n";
+}
+
+static QString diffColor()
+{
+ // Color for printing comparison differences inside invitations.
+
+// return "#DE8519"; // hard-coded color from Outlook2007
+ return QColor( Qt::red ).name(); //krazy:exclude=qenums TODO make configurable
+}
+
+static QString noteColor()
+{
+ // Color for printing notes inside invitations.
+ return qApp->palette().color( QPalette::Active, QPalette::Highlight ).name();
+}
+
+static QString htmlRow( const QString &title, const QString &value )
+{
+ if ( !value.isEmpty() ) {
+ return "<tr><td>" + title + "</td><td>" + value + "</td></tr>\n";
+ } else {
+ return QString();
+ }
+}
+
+static QString htmlRow( const QString &title, const QString &value, const QString &oldvalue )
+{
+ // if 'value' is empty, then print nothing
+ if ( value.isEmpty() ) {
+ return QString();
+ }
+
+ // if 'value' is new or unchanged, then print normally
+ if ( oldvalue.isEmpty() || value == oldvalue ) {
+ return htmlRow( title, value );
+ }
+
+ // if 'value' has changed, then make a special print
+ QString color = diffColor();
+ QString newtitle = "<font color=\"" + color + "\">" + title + "</font>";
+ QString newvalue = "<font color=\"" + color + "\">" + value + "</font>" +
+ "&nbsp;" +
+ "(<strike>" + oldvalue + "</strike>)";
+ return htmlRow( newtitle, newvalue );
+
+}
+
+static Attendee::Ptr findDelegatedFromMyAttendee( const Incidence::Ptr &incidence )
+{
+ // Return the first attendee that was delegated-from me
+
+ Attendee::Ptr attendee;
+ if ( !incidence ) {
+ return attendee;
+ }
+
+ KEMailSettings settings;
+ QStringList profiles = settings.profiles();
+ for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
+ settings.setProfile( *it );
+
+ QString delegatorName, delegatorEmail;
+ Attendee::List attendees = incidence->attendees();
+ Attendee::List::ConstIterator it2;
+ for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
+ Attendee::Ptr a = *it2;
+ KPIMUtils::extractEmailAddressAndName( a->delegator(), delegatorEmail, delegatorName );
+ if ( settings.getSetting( KEMailSettings::EmailAddress ) == delegatorEmail ) {
+ attendee = a;
+ break;
+ }
+ }
+ }
+ return attendee;
+}
+
+static Attendee::Ptr findMyAttendee( const Incidence::Ptr &incidence )
+{
+ // Return the attendee for the incidence that is probably me
+
+ Attendee::Ptr attendee;
+ if ( !incidence ) {
+ return attendee;
+ }
+
+ KEMailSettings settings;
+ QStringList profiles = settings.profiles();
+ for ( QStringList::Iterator it=profiles.begin(); it != profiles.end(); ++it ) {
+ settings.setProfile( *it );
+
+ Attendee::List attendees = incidence->attendees();
+ Attendee::List::ConstIterator it2;
+ for ( it2 = attendees.constBegin(); it2 != attendees.constEnd(); ++it2 ) {
+ Attendee::Ptr a = *it2;
+ if ( settings.getSetting( KEMailSettings::EmailAddress ) == a->email() ) {
+ attendee = a;
+ break;
+ }
+ }
+ }
+ return attendee;
+}
+
+static Attendee::Ptr findAttendee( const Incidence::Ptr &incidence,
+ const QString &email )
+{
+ // Search for an attendee by email address
+
+ Attendee::Ptr attendee;
+ if ( !incidence ) {
+ return attendee;
+ }
+
+ Attendee::List attendees = incidence->attendees();
+ Attendee::List::ConstIterator it;
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a = *it;
+ if ( email == a->email() ) {
+ attendee = a;
+ break;
+ }
+ }
+ return attendee;
+}
+
+static bool rsvpRequested( const Incidence::Ptr &incidence )
+{
+ if ( !incidence ) {
+ return false;
+ }
+
+ //use a heuristic to determine if a response is requested.
+
+ bool rsvp = true; // better send superfluously than not at all
+ Attendee::List attendees = incidence->attendees();
+ Attendee::List::ConstIterator it;
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ if ( it == attendees.constBegin() ) {
+ rsvp = (*it)->RSVP(); // use what the first one has
+ } else {
+ if ( (*it)->RSVP() != rsvp ) {
+ rsvp = true; // they differ, default
+ break;
+ }
+ }
+ }
+ return rsvp;
+}
+
+static QString rsvpRequestedStr( bool rsvpRequested, const QString &role )
+{
+ if ( rsvpRequested ) {
+ if ( role.isEmpty() ) {
+ return i18n( "Your response is requested" );
+ } else {
+ return i18n( "Your response as <b>%1</b> is requested", role );
+ }
+ } else {
+ if ( role.isEmpty() ) {
+ return i18n( "No response is necessary" );
+ } else {
+ return i18n( "No response as <b>%1</b> is necessary", role );
+ }
+ }
+}
+
+static QString myStatusStr( Incidence::Ptr incidence )
+{
+ QString ret;
+ Attendee::Ptr a = findMyAttendee( incidence );
+ if ( a &&
+ a->status() != Attendee::NeedsAction && a->status() != Attendee::Delegated ) {
+ ret = i18n( "(<b>Note</b>: the Organizer preset your response to <b>%1</b>)",
+ Stringify::attendeeStatus( a->status() ) );
+ }
+ return ret;
+}
+
+static QString invitationNote( const QString &title, const QString &note,
+ const QString &tag, const QString &color )
+{
+ QString noteStr;
+ if ( !note.isEmpty() ) {
+ noteStr += "<table border=\"0\" style=\"margin-top:4px;\">";
+ noteStr += "<tr><center><td>";
+ if ( !color.isEmpty() ) {
+ noteStr += "<font color=\"" + color + "\">";
+ }
+ if ( !title.isEmpty() ) {
+ if ( !tag.isEmpty() ) {
+ noteStr += htmlAddTag( tag, title );
+ } else {
+ noteStr += title;
+ }
+ }
+ noteStr += "&nbsp;" + note;
+ if ( !color.isEmpty() ) {
+ noteStr += "</font>";
+ }
+ noteStr += "</td></center></tr>";
+ noteStr += "</table>";
+ }
+ return noteStr;
+}
+
+static QString invitationPerson( const QString &email, const QString &name, const QString &uid,
+ const QString &comment )
+{
+ QPair<QString, QString> s = searchNameAndUid( email, name, uid );
+ const QString printName = s.first;
+ const QString printUid = s.second;
+
+ QString personString;
+ // Make the uid link
+ if ( !printUid.isEmpty() ) {
+ personString = htmlAddUidLink( email, printName, printUid );
+ } else {
+ // No UID, just show some text
+ personString = ( printName.isEmpty() ? email : printName );
+ }
+ if ( !comment.isEmpty() ) {
+ personString = i18nc( "name (comment)", "%1 (%2)", personString, comment );
+ }
+ personString += '\n';
+
+ // Make the mailto link
+ if ( !email.isEmpty() ) {
+ personString += "&nbsp;" + htmlAddMailtoLink( email, printName );
+ }
+ personString += '\n';
+
+ return personString;
+}
+
+static QString invitationDetailsIncidence( const Incidence::Ptr &incidence, bool noHtmlMode )
+{
+ // if description and comment -> use both
+ // if description, but no comment -> use the desc as the comment (and no desc)
+ // if comment, but no description -> use the comment and no description
+
+ QString html;
+ QString descr;
+ QStringList comments;
+
+ if ( incidence->comments().isEmpty() ) {
+ if ( !incidence->description().isEmpty() ) {
+ // use description as comments
+ if ( !incidence->descriptionIsRich() &&
+ !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
+ comments << string2HTML( incidence->description() );
+ } else {
+ if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
+ comments << incidence->richDescription();
+ } else {
+ comments << incidence->description();
+ }
+ if ( noHtmlMode ) {
+ comments[0] = cleanHtml( comments[0] );
+ }
+ comments[0] = htmlAddTag( "p", comments[0] );
+ }
+ }
+ //else desc and comments are empty
+ } else {
+ // non-empty comments
+ foreach ( const QString &c, incidence->comments() ) {
+ if ( !c.isEmpty() ) {
+ // kcalutils doesn't know about richtext comments, so we need to guess
+ if ( !Qt::mightBeRichText( c ) ) {
+ comments << string2HTML( c );
+ } else {
+ if ( noHtmlMode ) {
+ comments << cleanHtml( cleanHtml( "<body>" + c + "</body>" ) );
+ } else {
+ comments << c;
+ }
+ }
+ }
+ }
+ if ( !incidence->description().isEmpty() ) {
+ // use description too
+ if ( !incidence->descriptionIsRich() &&
+ !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
+ descr = string2HTML( incidence->description() );
+ } else {
+ if ( !incidence->description().startsWith( QLatin1String( "<!DOCTYPE HTML" ) ) ) {
+ descr = incidence->richDescription();
+ } else {
+ descr = incidence->description();
+ }
+ if ( noHtmlMode ) {
+ descr = cleanHtml( descr );
+ }
+ descr = htmlAddTag( "p", descr );
+ }
+ }
+ }
+
+ if( !descr.isEmpty() ) {
+ html += "<p>";
+ html += "<table border=\"0\" style=\"margin-top:4px;\">";
+ html += "<tr><td><center>" +
+ htmlAddTag( "u", i18n( "Description:" ) ) +
+ "</center></td></tr>";
+ html += "<tr><td>" + descr + "</td></tr>";
+ html += "</table>";
+ }
+
+ if ( !comments.isEmpty() ) {
+ html += "<p>";
+ html += "<table border=\"0\" style=\"margin-top:4px;\">";
+ html += "<tr><td><center>" +
+ htmlAddTag( "u", i18n( "Comments:" ) ) +
+ "</center></td></tr>";
+ html += "<tr><td>";
+ if ( comments.count() > 1 ) {
+ html += "<ul>";
+ for ( int i=0; i < comments.count(); ++i ) {
+ html += "<li>" + comments[i] + "</li>";
+ }
+ html += "</ul>";
+ } else {
+ html += comments[0];
+ }
+ html += "</td></tr>";
+ html += "</table>";
+ }
+ return html;
+}
+
+static QString invitationDetailsEvent( const Event::Ptr &event, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ // Invitation details are formatted into an HTML table
+ if ( !event ) {
+ return QString();
+ }
+
+ QString html = htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ // Invitation summary & location rows
+ html += htmlRow( i18n( "What:" ), invitationSummary( event, noHtmlMode ) );
+ html += htmlRow( i18n( "Where:" ), invitationLocation( event, noHtmlMode ) );
+
+ // If a 1 day event
+ if ( event->dtStart().date() == event->dtEnd().date() ) {
+ html += htmlRow( i18n( "Date:" ), dateToString( event->dtStart(), false, spec ) );
+ if ( !event->allDay() ) {
+ html += htmlRow( i18n( "Time:" ),
+ timeToString( event->dtStart(), true, spec ) +
+ " - " +
+ timeToString( event->dtEnd(), true, spec ) );
+ }
+ } else {
+ html += htmlRow( i18nc( "starting date", "From:" ),
+ dateToString( event->dtStart(), false, spec ) );
+ if ( !event->allDay() ) {
+ html += htmlRow( i18nc( "starting time", "At:" ),
+ timeToString( event->dtStart(), true, spec ) );
+ }
+ if ( event->hasEndDate() ) {
+ html += htmlRow( i18nc( "ending date", "To:" ),
+ dateToString( event->dtEnd(), false, spec ) );
+ if ( !event->allDay() ) {
+ html += htmlRow( i18nc( "ending time", "At:" ),
+ timeToString( event->dtEnd(), true, spec ) );
+ }
+ } else {
+ html += htmlRow( i18nc( "ending date", "To:" ), i18n( "no end date specified" ) );
+ }
+ }
+
+ // Invitation Duration Row
+ html += htmlRow( i18n( "Duration:" ), durationString( event ) );
+
+ // Invitation Recurrence Row
+ if ( event->recurs() ) {
+ html += htmlRow( i18n( "Recurrence:" ), recurrenceString( event ) );
+ }
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( event, noHtmlMode );
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsEvent( const Event::Ptr &event, const Event::Ptr &oldevent,
+ const ScheduleMessage::Ptr message, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ if ( !oldevent ) {
+ return invitationDetailsEvent( event, noHtmlMode, spec );
+ }
+
+ QString html;
+
+ // Print extra info typically dependent on the iTIP
+ if ( message->method() == iTIPDeclineCounter ) {
+ html += "<br>";
+ html += invitationNote( QString(),
+ i18n( "Please respond again to the original proposal." ),
+ QString(), noteColor() );
+ }
+
+ html += htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ html += htmlRow( i18n( "What:" ),
+ invitationSummary( event, noHtmlMode ),
+ invitationSummary( oldevent, noHtmlMode ) );
+
+ html += htmlRow( i18n( "Where:" ),
+ invitationLocation( event, noHtmlMode ),
+ invitationLocation( oldevent, noHtmlMode ) );
+
+ // If a 1 day event
+ if ( event->dtStart().date() == event->dtEnd().date() ) {
+ html += htmlRow( i18n( "Date:" ),
+ dateToString( event->dtStart(), false ),
+ dateToString( oldevent->dtStart(), false ) );
+ QString spanStr, oldspanStr;
+ if ( !event->allDay() ) {
+ spanStr = timeToString( event->dtStart(), true ) +
+ " - " +
+ timeToString( event->dtEnd(), true );
+ }
+ if ( !oldevent->allDay() ) {
+ oldspanStr = timeToString( oldevent->dtStart(), true ) +
+ " - " +
+ timeToString( oldevent->dtEnd(), true );
+ }
+ html += htmlRow( i18n( "Time:" ), spanStr, oldspanStr );
+ } else {
+ html += htmlRow( i18nc( "Starting date of an event", "From:" ),
+ dateToString( event->dtStart(), false ),
+ dateToString( oldevent->dtStart(), false ) );
+ QString startStr, oldstartStr;
+ if ( !event->allDay() ) {
+ startStr = timeToString( event->dtStart(), true );
+ }
+ if ( !oldevent->allDay() ) {
+ oldstartStr = timeToString( oldevent->dtStart(), true );
+ }
+ html += htmlRow( i18nc( "Starting time of an event", "At:" ), startStr, oldstartStr );
+ if ( event->hasEndDate() ) {
+ html += htmlRow( i18nc( "Ending date of an event", "To:" ),
+ dateToString( event->dtEnd(), false ),
+ dateToString( oldevent->dtEnd(), false ) );
+ QString endStr, oldendStr;
+ if ( !event->allDay() ) {
+ endStr = timeToString( event->dtEnd(), true );
+ }
+ if ( !oldevent->allDay() ) {
+ oldendStr = timeToString( oldevent->dtEnd(), true );
+ }
+ html += htmlRow( i18nc( "Starting time of an event", "At:" ), endStr, oldendStr );
+ } else {
+ QString endStr = i18n( "no end date specified" );
+ QString oldendStr;
+ if ( !oldevent->hasEndDate() ) {
+ oldendStr = i18n( "no end date specified" );
+ } else {
+ oldendStr = dateTimeToString( oldevent->dtEnd(), oldevent->allDay(), false );
+ }
+ html += htmlRow( i18nc( "Ending date of an event", "To:" ), endStr, oldendStr );
+ }
+ }
+
+ html += htmlRow( i18n( "Duration:" ), durationString( event ), durationString( oldevent ) );
+
+ QString recurStr, oldrecurStr;
+ if ( event->recurs() || oldevent->recurs() ) {
+ recurStr = recurrenceString( event );
+ oldrecurStr = recurrenceString( oldevent );
+ }
+ html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( event, noHtmlMode );
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsTodo( const Todo::Ptr &todo, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ // To-do details are formatted into an HTML table
+ if ( !todo ) {
+ return QString();
+ }
+
+ QString html = htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ // Invitation summary & location rows
+ html += htmlRow( i18n( "What:" ), invitationSummary( todo, noHtmlMode ) );
+ html += htmlRow( i18n( "Where:" ), invitationLocation( todo, noHtmlMode ) );
+
+ if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
+ html += htmlRow( i18n( "Start Date:" ), dateToString( todo->dtStart(), false, spec ) );
+ if ( !todo->allDay() ) {
+ html += htmlRow( i18n( "Start Time:" ), timeToString( todo->dtStart(), false, spec ) );
+ }
+ }
+ if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
+ html += htmlRow( i18n( "Due Date:" ), dateToString( todo->dtDue(), false, spec ) );
+ if ( !todo->allDay() ) {
+ html += htmlRow( i18n( "Due Time:" ), timeToString( todo->dtDue(), false, spec ) );
+ }
+ } else {
+ html += htmlRow( i18n( "Due Date:" ), i18nc( "Due Date: None", "None" ) );
+ }
+
+ // Invitation Duration Row
+ html += htmlRow( i18n( "Duration:" ), durationString( todo ) );
+
+ // Completeness
+ if ( todo->percentComplete() > 0 ) {
+ html += htmlRow( i18n( "Percent Done:" ), i18n( "%1%", todo->percentComplete() ) );
+ }
+
+ // Invitation Recurrence Row
+ if ( todo->recurs() ) {
+ html += htmlRow( i18n( "Recurrence:" ), recurrenceString( todo ) );
+ }
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( todo, noHtmlMode );
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsTodo( const Todo::Ptr &todo, const Todo::Ptr &oldtodo,
+ const ScheduleMessage::Ptr message, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ if ( !oldtodo ) {
+ return invitationDetailsTodo( todo, noHtmlMode, spec );
+ }
+
+ QString html;
+
+ // Print extra info typically dependent on the iTIP
+ if ( message->method() == iTIPDeclineCounter ) {
+ html += "<br>";
+ html += invitationNote( QString(),
+ i18n( "Please respond again to the original proposal." ),
+ QString(), noteColor() );
+ }
+
+ html += htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ html += htmlRow( i18n( "What:" ),
+ invitationSummary( todo, noHtmlMode ),
+ invitationSummary( todo, noHtmlMode ) );
+
+ html += htmlRow( i18n( "Where:" ),
+ invitationLocation( todo, noHtmlMode ),
+ invitationLocation( oldtodo, noHtmlMode ) );
+
+ if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
+ html += htmlRow( i18n( "Start Date:" ),
+ dateToString( todo->dtStart(), false ),
+ dateToString( oldtodo->dtStart(), false ) );
+ QString startTimeStr, oldstartTimeStr;
+ if ( !todo->allDay() || !oldtodo->allDay() ) {
+ startTimeStr = todo->allDay() ?
+ i18n( "All day" ) : timeToString( todo->dtStart(), false );
+ oldstartTimeStr = oldtodo->allDay() ?
+ i18n( "All day" ) : timeToString( oldtodo->dtStart(), false );
+ }
+ html += htmlRow( i18n( "Start Time:" ), startTimeStr, oldstartTimeStr );
+ }
+ if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
+ html += htmlRow( i18n( "Due Date:" ),
+ dateToString( todo->dtDue(), false ),
+ dateToString( oldtodo->dtDue(), false ) );
+ QString endTimeStr, oldendTimeStr;
+ if ( !todo->allDay() || !oldtodo->allDay() ) {
+ endTimeStr = todo->allDay() ?
+ i18n( "All day" ) : timeToString( todo->dtDue(), false );
+ oldendTimeStr = oldtodo->allDay() ?
+ i18n( "All day" ) : timeToString( oldtodo->dtDue(), false );
+ }
+ html += htmlRow( i18n( "Due Time:" ), endTimeStr, oldendTimeStr );
+ } else {
+ QString dueStr = i18nc( "Due Date: None", "None" );
+ QString olddueStr;
+ if ( !oldtodo->hasDueDate() || !oldtodo->dtDue().isValid() ) {
+ olddueStr = i18nc( "Due Date: None", "None" );
+ } else {
+ olddueStr = dateTimeToString( oldtodo->dtDue(), oldtodo->allDay(), false );
+ }
+ html += htmlRow( i18n( "Due Date:" ), dueStr, olddueStr );
+ }
+
+ html += htmlRow( i18n( "Duration:" ), durationString( todo ), durationString( oldtodo ) );
+
+ QString completionStr, oldcompletionStr;
+ if ( todo->percentComplete() > 0 || oldtodo->percentComplete() > 0 ) {
+ completionStr = i18n( "%1%", todo->percentComplete() );
+ oldcompletionStr = i18n( "%1%", oldtodo->percentComplete() );
+ }
+ html += htmlRow( i18n( "Percent Done:" ), completionStr, oldcompletionStr );
+
+ QString recurStr, oldrecurStr;
+ if ( todo->recurs() || oldtodo->recurs() ) {
+ recurStr = recurrenceString( todo );
+ oldrecurStr = recurrenceString( oldtodo );
+ }
+ html += htmlRow( i18n( "Recurrence:" ), recurStr, oldrecurStr );
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( todo, noHtmlMode );
+
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsJournal( const Journal::Ptr &journal, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ if ( !journal ) {
+ return QString();
+ }
+
+ QString html = htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ html += htmlRow( i18n( "Summary:" ), invitationSummary( journal, noHtmlMode ) );
+ html += htmlRow( i18n( "Date:" ), dateToString( journal->dtStart(), false, spec ) );
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( journal, noHtmlMode );
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsJournal( const Journal::Ptr &journal,
+ const Journal::Ptr &oldjournal,
+ bool noHtmlMode, KDateTime::Spec spec )
+{
+ if ( !oldjournal ) {
+ return invitationDetailsJournal( journal, noHtmlMode, spec );
+ }
+
+ QString html = htmlInvitationDetailsBegin();
+ html += htmlInvitationDetailsTableBegin();
+
+ html += htmlRow( i18n( "What:" ),
+ invitationSummary( journal, noHtmlMode ),
+ invitationSummary( oldjournal, noHtmlMode ) );
+
+ html += htmlRow( i18n( "Date:" ),
+ dateToString( journal->dtStart(), false, spec ),
+ dateToString( oldjournal->dtStart(), false, spec ) );
+
+ html += htmlInvitationDetailsTableEnd();
+ html += invitationDetailsIncidence( journal, noHtmlMode );
+ html += htmlInvitationDetailsEnd();
+
+ return html;
+}
+
+static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, bool noHtmlMode,
+ KDateTime::Spec spec )
+{
+ Q_UNUSED( noHtmlMode );
+
+ if ( !fb ) {
+ return QString();
+ }
+
+ QString html = htmlInvitationDetailsTableBegin();
+
+ html += htmlRow( i18n( "Person:" ), fb->organizer()->fullName() );
+ html += htmlRow( i18n( "Start date:" ), dateToString( fb->dtStart(), true, spec ) );
+ html += htmlRow( i18n( "End date:" ), dateToString( fb->dtEnd(), true, spec ) );
+
+ html += "<tr><td colspan=2><hr></td></tr>\n";
+ html += "<tr><td colspan=2>Busy periods given in this free/busy object:</td></tr>\n";
+
+ Period::List periods = fb->busyPeriods();
+ Period::List::iterator it;
+ for ( it = periods.begin(); it != periods.end(); ++it ) {
+ Period per = *it;
+ if ( per.hasDuration() ) {
+ int dur = per.duration().asSeconds();
+ QString cont;
+ if ( dur >= 3600 ) {
+ cont += i18ncp( "hours part of duration", "1 hour ", "%1 hours ", dur / 3600 );
+ dur %= 3600;
+ }
+ if ( dur >= 60 ) {
+ cont += i18ncp( "minutes part of duration", "1 minute", "%1 minutes ", dur / 60 );
+ dur %= 60;
+ }
+ if ( dur > 0 ) {
+ cont += i18ncp( "seconds part of duration", "1 second", "%1 seconds", dur );
+ }
+ html += htmlRow( QString(),
+ i18nc( "startDate for duration", "%1 for %2",
+ KGlobal::locale()->formatDateTime(
+ per.start().dateTime(), KLocale::LongDate ),
+ cont ) );
+ } else {
+ QString cont;
+ if ( per.start().date() == per.end().date() ) {
+ cont = i18nc( "date, fromTime - toTime ", "%1, %2 - %3",
+ KGlobal::locale()->formatDate( per.start().date() ),
+ KGlobal::locale()->formatTime( per.start().time() ),
+ KGlobal::locale()->formatTime( per.end().time() ) );
+ } else {
+ cont = i18nc( "fromDateTime - toDateTime", "%1 - %2",
+ KGlobal::locale()->formatDateTime(
+ per.start().dateTime(), KLocale::LongDate ),
+ KGlobal::locale()->formatDateTime(
+ per.end().dateTime(), KLocale::LongDate ) );
+ }
+
+ html += htmlRow( QString(), cont );
+ }
+ }
+
+ html += htmlInvitationDetailsTableEnd();
+ return html;
+}
+
+static QString invitationDetailsFreeBusy( const FreeBusy::Ptr &fb, const FreeBusy::Ptr &oldfb,
+ bool noHtmlMode, KDateTime::Spec spec )
+{
+ Q_UNUSED( oldfb );
+ return invitationDetailsFreeBusy( fb, noHtmlMode, spec );
+}
+
+static bool replyMeansCounter( const Incidence::Ptr &incidence )
+{
+ Q_UNUSED( incidence );
+ return false;
+/**
+ see kolab/issue 3665 for an example of when we might use this for something
+
+ bool status = false;
+ if ( incidence ) {
+ // put code here that looks at the incidence and determines that
+ // the reply is meant to be a counter proposal. We think this happens
+ // with Outlook counter proposals, but we aren't sure how yet.
+ if ( condition ) {
+ status = true;
+ }
+ }
+ return status;
+*/
+}
+
+static QString invitationHeaderEvent( const Event::Ptr &event,
+ const Incidence::Ptr &existingIncidence,
+ ScheduleMessage::Ptr msg, const QString &sender )
+{
+ if ( !msg || !event ) {
+ return QString();
+ }
+
+ switch ( msg->method() ) {
+ case iTIPPublish:
+ return i18n( "This invitation has been published" );
+ case iTIPRequest:
+ if ( existingIncidence && event->revision() > 0 ) {
+ QString orgStr = organizerName( event, sender );
+ if ( senderIsOrganizer( event, sender ) ) {
+ return i18n( "This invitation has been updated by the organizer %1", orgStr );
+ } else {
+ return i18n( "This invitation has been updated by %1 as a representative of %2",
+ sender, orgStr );
+ }
+ }
+ if ( iamOrganizer( event ) ) {
+ return i18n( "I created this invitation" );
+ } else {
+ QString orgStr = organizerName( event, sender );
+ if ( senderIsOrganizer( event, sender ) ) {
+ return i18n( "You received an invitation from %1", orgStr );
+ } else {
+ return i18n( "You received an invitation from %1 as a representative of %2",
+ sender, orgStr );
+ }
+ }
+ case iTIPRefresh:
+ return i18n( "This invitation was refreshed" );
+ case iTIPCancel:
+ if ( iamOrganizer( event ) ) {
+ return i18n( "This invitation has been canceled" );
+ } else {
+ return i18n( "The organizer has revoked the invitation" );
+ }
+ case iTIPAdd:
+ return i18n( "Addition to the invitation" );
+ case iTIPReply:
+ {
+ if ( replyMeansCounter( event ) ) {
+ return i18n( "%1 makes this counter proposal", firstAttendeeName( event, sender ) );
+ }
+
+ Attendee::List attendees = event->attendees();
+ if( attendees.count() == 0 ) {
+ kDebug() << "No attendees in the iCal reply!";
+ return QString();
+ }
+ if ( attendees.count() != 1 ) {
+ kDebug() << "Warning: attendeecount in the reply should be 1"
+ << "but is" << attendees.count();
+ }
+ QString attendeeName = firstAttendeeName( event, sender );
+
+ QString delegatorName, dummy;
+ Attendee::Ptr attendee = *attendees.begin();
+ KPIMUtils::extractEmailAddressAndName( attendee->delegator(), dummy, delegatorName );
+ if ( delegatorName.isEmpty() ) {
+ delegatorName = attendee->delegator();
+ }
+
+ switch( attendee->status() ) {
+ case Attendee::NeedsAction:
+ return i18n( "%1 indicates this invitation still needs some action", attendeeName );
+ case Attendee::Accepted:
+ if ( event->revision() > 0 ) {
+ if ( !sender.isEmpty() ) {
+ return i18n( "This invitation has been updated by attendee %1", sender );
+ } else {
+ return i18n( "This invitation has been updated by an attendee" );
+ }
+ } else {
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 accepts this invitation", attendeeName );
+ } else {
+ return i18n( "%1 accepts this invitation on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ }
+ case Attendee::Tentative:
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 tentatively accepts this invitation", attendeeName );
+ } else {
+ return i18n( "%1 tentatively accepts this invitation on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ case Attendee::Declined:
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 declines this invitation", attendeeName );
+ } else {
+ return i18n( "%1 declines this invitation on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ case Attendee::Delegated:
+ {
+ QString delegate, dummy;
+ KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
+ if ( delegate.isEmpty() ) {
+ delegate = attendee->delegate();
+ }
+ if ( !delegate.isEmpty() ) {
+ return i18n( "%1 has delegated this invitation to %2", attendeeName, delegate );
+ } else {
+ return i18n( "%1 has delegated this invitation", attendeeName );
+ }
+ }
+ case Attendee::Completed:
+ return i18n( "This invitation is now completed" );
+ case Attendee::InProcess:
+ return i18n( "%1 is still processing the invitation", attendeeName );
+ case Attendee::None:
+ return i18n( "Unknown response to this invitation" );
+ }
+ break;
+ }
+ case iTIPCounter:
+ return i18n( "%1 makes this counter proposal",
+ firstAttendeeName( event, i18n( "Sender" ) ) );
+
+ case iTIPDeclineCounter:
+ {
+ QString orgStr = organizerName( event, sender );
+ if ( senderIsOrganizer( event, sender ) ) {
+ return i18n( "%1 declines your counter proposal", orgStr );
+ } else {
+ return i18n( "%1 declines your counter proposal on behalf of %2", sender, orgStr );
+ }
+ }
+
+ case iTIPNoMethod:
+ return i18n( "Error: Event iTIP message with unknown method" );
+ }
+ kError() << "encountered an iTIP method that we do not support";
+ return QString();
+}
+
+static QString invitationHeaderTodo( const Todo::Ptr &todo,
+ const Incidence::Ptr &existingIncidence,
+ ScheduleMessage::Ptr msg, const QString &sender )
+{
+ if ( !msg || !todo ) {
+ return QString();
+ }
+
+ switch ( msg->method() ) {
+ case iTIPPublish:
+ return i18n( "This to-do has been published" );
+ case iTIPRequest:
+ if ( existingIncidence && todo->revision() > 0 ) {
+ QString orgStr = organizerName( todo, sender );
+ if ( senderIsOrganizer( todo, sender ) ) {
+ return i18n( "This to-do has been updated by the organizer %1", orgStr );
+ } else {
+ return i18n( "This to-do has been updated by %1 as a representative of %2",
+ sender, orgStr );
+ }
+ } else {
+ if ( iamOrganizer( todo ) ) {
+ return i18n( "I created this to-do" );
+ } else {
+ QString orgStr = organizerName( todo, sender );
+ if ( senderIsOrganizer( todo, sender ) ) {
+ return i18n( "You have been assigned this to-do by %1", orgStr );
+ } else {
+ return i18n( "You have been assigned this to-do by %1 as a representative of %2",
+ sender, orgStr );
+ }
+ }
+ }
+ case iTIPRefresh:
+ return i18n( "This to-do was refreshed" );
+ case iTIPCancel:
+ if ( iamOrganizer( todo ) ) {
+ return i18n( "This to-do was canceled" );
+ } else {
+ return i18n( "The organizer has revoked this to-do" );
+ }
+ case iTIPAdd:
+ return i18n( "Addition to the to-do" );
+ case iTIPReply:
+ {
+ if ( replyMeansCounter( todo ) ) {
+ return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
+ }
+
+ Attendee::List attendees = todo->attendees();
+ if ( attendees.count() == 0 ) {
+ kDebug() << "No attendees in the iCal reply!";
+ return QString();
+ }
+ if ( attendees.count() != 1 ) {
+ kDebug() << "Warning: attendeecount in the reply should be 1"
+ << "but is" << attendees.count();
+ }
+ QString attendeeName = firstAttendeeName( todo, sender );
+
+ QString delegatorName, dummy;
+ Attendee::Ptr attendee = *attendees.begin();
+ KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegatorName );
+ if ( delegatorName.isEmpty() ) {
+ delegatorName = attendee->delegator();
+ }
+
+ switch( attendee->status() ) {
+ case Attendee::NeedsAction:
+ return i18n( "%1 indicates this to-do assignment still needs some action",
+ attendeeName );
+ case Attendee::Accepted:
+ if ( todo->revision() > 0 ) {
+ if ( !sender.isEmpty() ) {
+ if ( todo->isCompleted() ) {
+ return i18n( "This to-do has been completed by assignee %1", sender );
+ } else {
+ return i18n( "This to-do has been updated by assignee %1", sender );
+ }
+ } else {
+ if ( todo->isCompleted() ) {
+ return i18n( "This to-do has been completed by an assignee" );
+ } else {
+ return i18n( "This to-do has been updated by an assignee" );
+ }
+ }
+ } else {
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 accepts this to-do", attendeeName );
+ } else {
+ return i18n( "%1 accepts this to-do on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ }
+ case Attendee::Tentative:
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 tentatively accepts this to-do", attendeeName );
+ } else {
+ return i18n( "%1 tentatively accepts this to-do on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ case Attendee::Declined:
+ if ( delegatorName.isEmpty() ) {
+ return i18n( "%1 declines this to-do", attendeeName );
+ } else {
+ return i18n( "%1 declines this to-do on behalf of %2",
+ attendeeName, delegatorName );
+ }
+ case Attendee::Delegated:
+ {
+ QString delegate, dummy;
+ KPIMUtils::extractEmailAddressAndName( attendee->delegate(), dummy, delegate );
+ if ( delegate.isEmpty() ) {
+ delegate = attendee->delegate();
+ }
+ if ( !delegate.isEmpty() ) {
+ return i18n( "%1 has delegated this to-do to %2", attendeeName, delegate );
+ } else {
+ return i18n( "%1 has delegated this to-do", attendeeName );
+ }
+ }
+ case Attendee::Completed:
+ return i18n( "The request for this to-do is now completed" );
+ case Attendee::InProcess:
+ return i18n( "%1 is still processing the to-do", attendeeName );
+ case Attendee::None:
+ return i18n( "Unknown response to this to-do" );
+ }
+ break;
+ }
+ case iTIPCounter:
+ return i18n( "%1 makes this counter proposal", firstAttendeeName( todo, sender ) );
+
+ case iTIPDeclineCounter:
+ {
+ QString orgStr = organizerName( todo, sender );
+ if ( senderIsOrganizer( todo, sender ) ) {
+ return i18n( "%1 declines the counter proposal", orgStr );
+ } else {
+ return i18n( "%1 declines the counter proposal on behalf of %2", sender, orgStr );
+ }
+ }
+
+ case iTIPNoMethod:
+ return i18n( "Error: To-do iTIP message with unknown method" );
+ }
+ kError() << "encountered an iTIP method that we do not support";
+ return QString();
+}
+
+static QString invitationHeaderJournal( const Journal::Ptr &journal,
+ ScheduleMessage::Ptr msg )
+{
+ if ( !msg || !journal ) {
+ return QString();
+ }
+
+ switch ( msg->method() ) {
+ case iTIPPublish:
+ return i18n( "This journal has been published" );
+ case iTIPRequest:
+ return i18n( "You have been assigned this journal" );
+ case iTIPRefresh:
+ return i18n( "This journal was refreshed" );
+ case iTIPCancel:
+ return i18n( "This journal was canceled" );
+ case iTIPAdd:
+ return i18n( "Addition to the journal" );
+ case iTIPReply:
+ {
+ if ( replyMeansCounter( journal ) ) {
+ return i18n( "Sender makes this counter proposal" );
+ }
+
+ Attendee::List attendees = journal->attendees();
+ if ( attendees.count() == 0 ) {
+ kDebug() << "No attendees in the iCal reply!";
+ return QString();
+ }
+ if( attendees.count() != 1 ) {
+ kDebug() << "Warning: attendeecount in the reply should be 1 "
+ << "but is " << attendees.count();
+ }
+ Attendee::Ptr attendee = *attendees.begin();
+
+ switch( attendee->status() ) {
+ case Attendee::NeedsAction:
+ return i18n( "Sender indicates this journal assignment still needs some action" );
+ case Attendee::Accepted:
+ return i18n( "Sender accepts this journal" );
+ case Attendee::Tentative:
+ return i18n( "Sender tentatively accepts this journal" );
+ case Attendee::Declined:
+ return i18n( "Sender declines this journal" );
+ case Attendee::Delegated:
+ return i18n( "Sender has delegated this request for the journal" );
+ case Attendee::Completed:
+ return i18n( "The request for this journal is now completed" );
+ case Attendee::InProcess:
+ return i18n( "Sender is still processing the invitation" );
+ case Attendee::None:
+ return i18n( "Unknown response to this journal" );
+ }
+ break;
+ }
+ case iTIPCounter:
+ return i18n( "Sender makes this counter proposal" );
+ case iTIPDeclineCounter:
+ return i18n( "Sender declines the counter proposal" );
+ case iTIPNoMethod:
+ return i18n( "Error: Journal iTIP message with unknown method" );
+ }
+ kError() << "encountered an iTIP method that we do not support";
+ return QString();
+}
+
+static QString invitationHeaderFreeBusy( const FreeBusy::Ptr &fb,
+ ScheduleMessage::Ptr msg )
+{
+ if ( !msg || !fb ) {
+ return QString();
+ }
+
+ switch ( msg->method() ) {
+ case iTIPPublish:
+ return i18n( "This free/busy list has been published" );
+ case iTIPRequest:
+ return i18n( "The free/busy list has been requested" );
+ case iTIPRefresh:
+ return i18n( "This free/busy list was refreshed" );
+ case iTIPCancel:
+ return i18n( "This free/busy list was canceled" );
+ case iTIPAdd:
+ return i18n( "Addition to the free/busy list" );
+ case iTIPReply:
+ return i18n( "Reply to the free/busy list" );
+ case iTIPCounter:
+ return i18n( "Sender makes this counter proposal" );
+ case iTIPDeclineCounter:
+ return i18n( "Sender declines the counter proposal" );
+ case iTIPNoMethod:
+ return i18n( "Error: Free/Busy iTIP message with unknown method" );
+ }
+ kError() << "encountered an iTIP method that we do not support";
+ return QString();
+}
+//@endcond
+
+static QString invitationAttendeeList( const Incidence::Ptr &incidence )
+{
+ QString tmpStr;
+ if ( !incidence ) {
+ return tmpStr;
+ }
+ if ( incidence->type() == Incidence::TypeTodo ) {
+ tmpStr += i18n( "Assignees" );
+ } else {
+ tmpStr += i18n( "Invitation List" );
+ }
+
+ int count=0;
+ Attendee::List attendees = incidence->attendees();
+ if ( !attendees.isEmpty() ) {
+ QStringList comments;
+ Attendee::List::ConstIterator it;
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a = *it;
+ if ( !iamAttendee( a ) ) {
+ count++;
+ if ( count == 1 ) {
+ tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
+ }
+ tmpStr += "<tr>";
+ tmpStr += "<td>";
+ comments.clear();
+ if ( attendeeIsOrganizer( incidence, a ) ) {
+ comments << i18n( "organizer" );
+ }
+ if ( !a->delegator().isEmpty() ) {
+ comments << i18n( " (delegated by %1)", a->delegator() );
+ }
+ if ( !a->delegate().isEmpty() ) {
+ comments << i18n( " (delegated to %1)", a->delegate() );
+ }
+ tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
+ tmpStr += "</td>";
+ tmpStr += "</tr>";
+ }
+ }
+ }
+ if ( count ) {
+ tmpStr += "</table>";
+ } else {
+ tmpStr.clear();
+ }
+
+ return tmpStr;
+}
+
+static QString invitationRsvpList( const Incidence::Ptr &incidence, const Attendee::Ptr &sender )
+{
+ QString tmpStr;
+ if ( !incidence ) {
+ return tmpStr;
+ }
+ if ( incidence->type() == Incidence::TypeTodo ) {
+ tmpStr += i18n( "Assignees" );
+ } else {
+ tmpStr += i18n( "Invitation List" );
+ }
+
+ int count=0;
+ Attendee::List attendees = incidence->attendees();
+ if ( !attendees.isEmpty() ) {
+ QStringList comments;
+ Attendee::List::ConstIterator it;
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a = *it;
+ if ( !attendeeIsOrganizer( incidence, a ) ) {
+ QString statusStr = Stringify::attendeeStatus( a->status () );
+ if ( sender && ( a->email() == sender->email() ) ) {
+ // use the attendee taken from the response incidence,
+ // rather than the attendee from the calendar incidence.
+ if ( a->status() != sender->status() ) {
+ statusStr = i18n( "%1 (<i>unrecorded</i>)",
+ Stringify::attendeeStatus( sender->status() ) );
+ }
+ a = sender;
+ }
+ count++;
+ if ( count == 1 ) {
+ tmpStr += "<table border=\"1\" cellpadding=\"1\" cellspacing=\"0\">";
+ }
+ tmpStr += "<tr>";
+ tmpStr += "<td>";
+ comments.clear();
+ if ( iamAttendee( a ) ) {
+ comments << i18n( "myself" );
+ }
+ if ( !a->delegator().isEmpty() ) {
+ comments << i18n( " (delegated by %1)", a->delegator() );
+ }
+ if ( !a->delegate().isEmpty() ) {
+ comments << i18n( " (delegated to %1)", a->delegate() );
+ }
+ tmpStr += invitationPerson( a->email(), a->name(), QString(), comments.join( "," ) );
+ tmpStr += "</td>";
+ tmpStr += "<td>" + statusStr + "</td>";
+ tmpStr += "</tr>";
+ }
+ }
+ }
+ if ( count ) {
+ tmpStr += "</table>";
+ } else {
+ tmpStr += "<i>" + i18nc( "no attendees", "None" ) + "</i>";
+ }
+
+ return tmpStr;
+}
+
+static QString invitationAttachments( InvitationFormatterHelper *helper,
+ const Incidence::Ptr &incidence )
+{
+ QString tmpStr;
+ if ( !incidence ) {
+ return tmpStr;
+ }
+
+ Attachment::List attachments = incidence->attachments();
+ if ( !attachments.isEmpty() ) {
+ tmpStr += i18n( "Attached Documents:" ) + "<ol>";
+
+ Attachment::List::ConstIterator it;
+ for ( it = attachments.constBegin(); it != attachments.constEnd(); ++it ) {
+ Attachment::Ptr a = *it;
+ tmpStr += "<li>";
+ // Attachment icon
+ KMimeType::Ptr mimeType = KMimeType::mimeType( a->mimeType() );
+ const QString iconStr = ( mimeType ?
+ mimeType->iconName( a->uri() ) :
+ QString( "application-octet-stream" ) );
+ const QString iconPath = KIconLoader::global()->iconPath( iconStr, KIconLoader::Small );
+ if ( !iconPath.isEmpty() ) {
+ tmpStr += "<img valign=\"top\" src=\"" + iconPath + "\">";
+ }
+ tmpStr += helper->makeLink( "ATTACH:" + a->label().toUtf8().toBase64(), a->label() );
+ tmpStr += "</li>";
+ }
+ tmpStr += "</ol>";
+ }
+
+ return tmpStr;
+}
+
+//@cond PRIVATE
+class KCalUtils::IncidenceFormatter::ScheduleMessageVisitor : public Visitor
+{
+ public:
+ ScheduleMessageVisitor() : mMessage( 0 ) { mResult = ""; }
+ bool act( const IncidenceBase::Ptr &incidence,
+ const Incidence::Ptr &existingIncidence,
+ ScheduleMessage::Ptr msg, const QString &sender )
+ {
+ mExistingIncidence = existingIncidence;
+ mMessage = msg;
+ mSender = sender;
+ return incidence->accept( *this, incidence );
+ }
+ QString result() const { return mResult; }
+
+ protected:
+ QString mResult;
+ Incidence::Ptr mExistingIncidence;
+ ScheduleMessage::Ptr mMessage;
+ QString mSender;
+};
+
+class KCalUtils::IncidenceFormatter::InvitationHeaderVisitor :
+ public IncidenceFormatter::ScheduleMessageVisitor
+{
+ protected:
+ bool visit( Event::Ptr event )
+ {
+ mResult = invitationHeaderEvent( event, mExistingIncidence, mMessage, mSender );
+ return !mResult.isEmpty();
+ }
+ bool visit( Todo::Ptr todo )
+ {
+ mResult = invitationHeaderTodo( todo, mExistingIncidence, mMessage, mSender );
+ return !mResult.isEmpty();
+ }
+ bool visit( Journal::Ptr journal )
+ {
+ mResult = invitationHeaderJournal( journal, mMessage );
+ return !mResult.isEmpty();
+ }
+ bool visit( FreeBusy::Ptr fb )
+ {
+ mResult = invitationHeaderFreeBusy( fb, mMessage );
+ return !mResult.isEmpty();
+ }
+};
+
+class KCalUtils::IncidenceFormatter::InvitationBodyVisitor
+ : public IncidenceFormatter::ScheduleMessageVisitor
+{
+ public:
+ InvitationBodyVisitor( bool noHtmlMode, KDateTime::Spec spec )
+ : ScheduleMessageVisitor(), mNoHtmlMode( noHtmlMode ), mSpec( spec ) {}
+
+ protected:
+ bool visit( Event::Ptr event )
+ {
+ Event::Ptr oldevent = mExistingIncidence.dynamicCast<Event>();
+ mResult = invitationDetailsEvent( event, oldevent, mMessage, mNoHtmlMode, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( Todo::Ptr todo )
+ {
+ Todo::Ptr oldtodo = mExistingIncidence.dynamicCast<Todo>();
+ mResult = invitationDetailsTodo( todo, oldtodo, mMessage, mNoHtmlMode, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( Journal::Ptr journal )
+ {
+ Journal::Ptr oldjournal = mExistingIncidence.dynamicCast<Journal>();
+ mResult = invitationDetailsJournal( journal, oldjournal, mNoHtmlMode, mSpec );
+ return !mResult.isEmpty();
+ }
+ bool visit( FreeBusy::Ptr fb )
+ {
+ mResult = invitationDetailsFreeBusy( fb, FreeBusy::Ptr(), mNoHtmlMode, mSpec );
+ return !mResult.isEmpty();
+ }
+
+ private:
+ bool mNoHtmlMode;
+ KDateTime::Spec mSpec;
+};
+//@endcond
+
+InvitationFormatterHelper::InvitationFormatterHelper()
+ : d( 0 )
+{
+}
+
+InvitationFormatterHelper::~InvitationFormatterHelper()
+{
+}
+
+QString InvitationFormatterHelper::generateLinkURL( const QString &id )
+{
+ return id;
+}
+
+//@cond PRIVATE
+class IncidenceFormatter::IncidenceCompareVisitor : public Visitor
+{
+ public:
+ IncidenceCompareVisitor() {}
+ bool act( const IncidenceBase::Ptr &incidence,
+ const Incidence::Ptr &existingIncidence )
+ {
+ if ( !existingIncidence ) {
+ return false;
+ }
+ Incidence::Ptr inc = incidence.staticCast<Incidence>();
+ if ( !inc || !existingIncidence ||
+ inc->revision() <= existingIncidence->revision() ) {
+ return false;
+ }
+ mExistingIncidence = existingIncidence;
+ return incidence->accept( *this, incidence );
+ }
+
+ QString result() const
+ {
+ if ( mChanges.isEmpty() ) {
+ return QString();
+ }
+ QString html = "<div align=\"left\"><ul><li>";
+ html += mChanges.join( "</li><li>" );
+ html += "</li><ul></div>";
+ return html;
+ }
+
+ protected:
+ bool visit( Event::Ptr event )
+ {
+ compareEvents( event, mExistingIncidence.dynamicCast<Event>() );
+ compareIncidences( event, mExistingIncidence );
+ return !mChanges.isEmpty();
+ }
+ bool visit( Todo::Ptr todo )
+ {
+ compareTodos( todo, mExistingIncidence.dynamicCast<Todo>() );
+ compareIncidences( todo, mExistingIncidence );
+ return !mChanges.isEmpty();
+ }
+ bool visit( Journal::Ptr journal )
+ {
+ compareIncidences( journal, mExistingIncidence );
+ return !mChanges.isEmpty();
+ }
+ bool visit( FreeBusy::Ptr fb )
+ {
+ Q_UNUSED( fb );
+ return !mChanges.isEmpty();
+ }
+
+ private:
+ void compareEvents( const Event::Ptr &newEvent,
+ const Event::Ptr &oldEvent )
+ {
+ if ( !oldEvent || !newEvent ) {
+ return;
+ }
+ if ( oldEvent->dtStart() != newEvent->dtStart() ||
+ oldEvent->allDay() != newEvent->allDay() ) {
+ mChanges += i18n( "The invitation starting time has been changed from %1 to %2",
+ eventStartTimeStr( oldEvent ), eventStartTimeStr( newEvent ) );
+ }
+ if ( oldEvent->dtEnd() != newEvent->dtEnd() ||
+ oldEvent->allDay() != newEvent->allDay() ) {
+ mChanges += i18n( "The invitation ending time has been changed from %1 to %2",
+ eventEndTimeStr( oldEvent ), eventEndTimeStr( newEvent ) );
+ }
+ }
+
+ void compareTodos( const Todo::Ptr &newTodo,
+ const Todo::Ptr &oldTodo )
+ {
+ if ( !oldTodo || !newTodo ) {
+ return;
+ }
+
+ if ( !oldTodo->isCompleted() && newTodo->isCompleted() ) {
+ mChanges += i18n( "The to-do has been completed" );
+ }
+ if ( oldTodo->isCompleted() && !newTodo->isCompleted() ) {
+ mChanges += i18n( "The to-do is no longer completed" );
+ }
+ if ( oldTodo->percentComplete() != newTodo->percentComplete() ) {
+ const QString oldPer = i18n( "%1%", oldTodo->percentComplete() );
+ const QString newPer = i18n( "%1%", newTodo->percentComplete() );
+ mChanges += i18n( "The task completed percentage has changed from %1 to %2",
+ oldPer, newPer );
+ }
+
+ if ( !oldTodo->hasStartDate() && newTodo->hasStartDate() ) {
+ mChanges += i18n( "A to-do starting time has been added" );
+ }
+ if ( oldTodo->hasStartDate() && !newTodo->hasStartDate() ) {
+ mChanges += i18n( "The to-do starting time has been removed" );
+ }
+ if ( oldTodo->hasStartDate() && newTodo->hasStartDate() &&
+ oldTodo->dtStart() != newTodo->dtStart() ) {
+ mChanges += i18n( "The to-do starting time has been changed from %1 to %2",
+ dateTimeToString( oldTodo->dtStart(), oldTodo->allDay(), false ),
+ dateTimeToString( newTodo->dtStart(), newTodo->allDay(), false ) );
+ }
+
+ if ( !oldTodo->hasDueDate() && newTodo->hasDueDate() ) {
+ mChanges += i18n( "A to-do due time has been added" );
+ }
+ if ( oldTodo->hasDueDate() && !newTodo->hasDueDate() ) {
+ mChanges += i18n( "The to-do due time has been removed" );
+ }
+ if ( oldTodo->hasDueDate() && newTodo->hasDueDate() &&
+ oldTodo->dtDue() != newTodo->dtDue() ) {
+ mChanges += i18n( "The to-do due time has been changed from %1 to %2",
+ dateTimeToString( oldTodo->dtDue(), oldTodo->allDay(), false ),
+ dateTimeToString( newTodo->dtDue(), newTodo->allDay(), false ) );
+ }
+ }
+
+ void compareIncidences( const Incidence::Ptr &newInc,
+ const Incidence::Ptr &oldInc )
+ {
+ if ( !oldInc || !newInc ) {
+ return;
+ }
+
+ if ( oldInc->summary() != newInc->summary() ) {
+ mChanges += i18n( "The summary has been changed to: \"%1\"",
+ newInc->richSummary() );
+ }
+
+ if ( oldInc->location() != newInc->location() ) {
+ mChanges += i18n( "The location has been changed to: \"%1\"",
+ newInc->richLocation() );
+ }
+
+ if ( oldInc->description() != newInc->description() ) {
+ mChanges += i18n( "The description has been changed to: \"%1\"",
+ newInc->richDescription() );
+ }
+
+ Attendee::List oldAttendees = oldInc->attendees();
+ Attendee::List newAttendees = newInc->attendees();
+ for ( Attendee::List::ConstIterator it = newAttendees.constBegin();
+ it != newAttendees.constEnd(); ++it ) {
+ Attendee::Ptr oldAtt = oldInc->attendeeByMail( (*it)->email() );
+ if ( !oldAtt ) {
+ mChanges += i18n( "Attendee %1 has been added", (*it)->fullName() );
+ } else {
+ if ( oldAtt->status() != (*it)->status() ) {
+ mChanges += i18n( "The status of attendee %1 has been changed to: %2",
+ (*it)->fullName(), Stringify::attendeeStatus( (*it)->status() ) );
+ }
+ }
+ }
+
+ for ( Attendee::List::ConstIterator it = oldAttendees.constBegin();
+ it != oldAttendees.constEnd(); ++it ) {
+ if ( !attendeeIsOrganizer( oldInc, (*it) ) ) {
+ Attendee::Ptr newAtt = newInc->attendeeByMail( (*it)->email() );
+ if ( !newAtt ) {
+ mChanges += i18n( "Attendee %1 has been removed", (*it)->fullName() );
+ }
+ }
+ }
+ }
+
+ private:
+ Incidence::Ptr mExistingIncidence;
+ QStringList mChanges;
+};
+//@endcond
+
+QString InvitationFormatterHelper::makeLink( const QString &id, const QString &text )
+{
+ if ( !id.startsWith( QLatin1String( "ATTACH:" ) ) ) {
+ QString res = QString( "<a href=\"%1\"><b>%2</b></a>" ).
+ arg( generateLinkURL( id ), text );
+ return res;
+ } else {
+ // draw the attachment links in non-bold face
+ QString res = QString( "<a href=\"%1\">%2</a>" ).
+ arg( generateLinkURL( id ), text );
+ return res;
+ }
+}
+
+// Check if the given incidence is likely one that we own instead one from
+// a shared calendar (Kolab-specific)
+static bool incidenceOwnedByMe( const Calendar::Ptr &calendar,
+ const Incidence::Ptr &incidence )
+{
+ Q_UNUSED( calendar );
+ Q_UNUSED( incidence );
+ return true;
+}
+
+// The open & close table cell tags for the invitation buttons
+static QString tdOpen = "<td style=\"border-width:2px;border-style:outset\">";
+static QString tdClose = "</td>";
+
+static QString responseButtons( const Incidence::Ptr &inc,
+ bool rsvpReq, bool rsvpRec,
+ InvitationFormatterHelper *helper )
+{
+ QString html;
+ if ( !helper ) {
+ return html;
+ }
+
+ if ( !rsvpReq && ( inc && inc->revision() == 0 ) ) {
+ // Record only
+ html += tdOpen;
+ html += helper->makeLink( "record", i18n( "[Record]" ) );
+ html += tdClose;
+
+ // Move to trash
+ html += tdOpen;
+ html += helper->makeLink( "delete", i18n( "[Move to Trash]" ) );
+ html += tdClose;
+
+ } else {
+
+ // Accept
+ html += tdOpen;
+ html += helper->makeLink( "accept", i18nc( "accept invitation", "Accept" ) );
+ html += tdClose;
+
+ // Tentative
+ html += tdOpen;
+ html += helper->makeLink( "accept_conditionally",
+ i18nc( "Accept invitation conditionally", "Accept cond." ) );
+ html += tdClose;
+
+ // Counter proposal
+ html += tdOpen;
+ html += helper->makeLink( "counter",
+ i18nc( "invitation counter proposal", "Counter proposal" ) );
+ html += tdClose;
+
+ // Decline
+ html += tdOpen;
+ html += helper->makeLink( "decline",
+ i18nc( "decline invitation", "Decline" ) );
+ html += tdClose;
+ }
+
+ if ( !rsvpRec || ( inc && inc->revision() > 0 ) ) {
+ // Delegate
+ html += tdOpen;
+ html += helper->makeLink( "delegate",
+ i18nc( "delegate inviation to another", "Delegate" ) );
+ html += tdClose;
+
+ // Forward
+ html += tdOpen;
+ html += helper->makeLink( "forward",
+ i18nc( "forward request to another", "Forward" ) );
+ html += tdClose;
+
+ // Check calendar
+ if ( inc && inc->type() == Incidence::TypeEvent ) {
+ html += tdOpen;
+ html += helper->makeLink( "check_calendar",
+ i18nc( "look for scheduling conflicts", "Check my calendar" ) );
+ html += tdClose;
+ }
+ }
+ return html;
+}
+
+static QString counterButtons( const Incidence::Ptr &incidence,
+ InvitationFormatterHelper *helper )
+{
+ QString html;
+ if ( !helper ) {
+ return html;
+ }
+
+ // Accept proposal
+ html += tdOpen;
+ html += helper->makeLink( "accept_counter", i18n( "[Accept]" ) );
+ html += tdClose;
+
+ // Decline proposal
+ html += tdOpen;
+ html += helper->makeLink( "decline_counter", i18n( "[Decline]" ) );
+ html += tdClose;
+
+ // Check calendar
+ if ( incidence && incidence->type() == Incidence::TypeEvent ) {
+ html += tdOpen;
+ html += helper->makeLink( "check_calendar", i18n( "[Check my calendar] " ) );
+ html += tdClose;
+ }
+ return html;
+}
+
+Calendar::Ptr InvitationFormatterHelper::calendar() const
+{
+ return Calendar::Ptr();
+}
+
+static QString formatICalInvitationHelper( QString invitation,
+ const MemoryCalendar::Ptr &mCalendar,
+ InvitationFormatterHelper *helper,
+ bool noHtmlMode,
+ KDateTime::Spec spec,
+ const QString &sender,
+ bool outlookCompareStyle )
+{
+ if ( invitation.isEmpty() ) {
+ return QString();
+ }
+
+ ICalFormat format;
+ // parseScheduleMessage takes the tz from the calendar,
+ // no need to set it manually here for the format!
+ ScheduleMessage::Ptr msg = format.parseScheduleMessage( mCalendar, invitation );
+
+ if( !msg ) {
+ kDebug() << "Failed to parse the scheduling message";
+ Q_ASSERT( format.exception() );
+ kDebug() << Stringify::errorMessage( *format.exception() ); //krazy:exclude=kdebug
+ return QString();
+ }
+
+ IncidenceBase::Ptr incBase = msg->event();
+
+ incBase->shiftTimes( mCalendar->timeSpec(), KDateTime::Spec::LocalZone() );
+
+ // Determine if this incidence is in my calendar (and owned by me)
+ Incidence::Ptr existingIncidence;
+ if ( incBase && helper->calendar() ) {
+ existingIncidence = helper->calendar()->incidence( incBase->uid() );
+
+ if ( !incidenceOwnedByMe( helper->calendar(), existingIncidence ) ) {
+ existingIncidence.clear();
+ }
+ if ( !existingIncidence ) {
+ const Incidence::List list = helper->calendar()->incidences();
+ for ( Incidence::List::ConstIterator it = list.begin(), end = list.end(); it != end; ++it ) {
+ if ( (*it)->schedulingID() == incBase->uid() &&
+ incidenceOwnedByMe( helper->calendar(), *it ) ) {
+ existingIncidence = *it;
+ break;
+ }
+ }
+ }
+ }
+
+ Incidence::Ptr inc = incBase.staticCast<Incidence>(); // the incidence in the invitation email
+
+ // First make the text of the message
+ QString html;
+ html += "<div align=\"center\" style=\"border:solid 1px;\">";
+
+ IncidenceFormatter::InvitationHeaderVisitor headerVisitor;
+ // The InvitationHeaderVisitor returns false if the incidence is somehow invalid, or not handled
+ if ( !headerVisitor.act( inc, existingIncidence, msg, sender ) ) {
+ return QString();
+ }
+ html += htmlAddTag( "h3", headerVisitor.result() );
+
+ if ( outlookCompareStyle ||
+ msg->method() == iTIPDeclineCounter ) { //use Outlook style for decline
+ // use the Outlook 2007 Comparison Style
+ IncidenceFormatter::InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
+ bool bodyOk;
+ if ( msg->method() == iTIPRequest || msg->method() == iTIPReply ||
+ msg->method() == iTIPDeclineCounter ) {
+ if ( inc && existingIncidence &&
+ inc->revision() < existingIncidence->revision() ) {
+ bodyOk = bodyVisitor.act( existingIncidence, inc, msg, sender );
+ } else {
+ bodyOk = bodyVisitor.act( inc, existingIncidence, msg, sender );
+ }
+ } else {
+ bodyOk = bodyVisitor.act( inc, Incidence::Ptr(), msg, sender );
+ }
+ if ( bodyOk ) {
+ html += bodyVisitor.result();
+ } else {
+ return QString();
+ }
+ } else {
+ // use our "Classic" Comparison Style
+ InvitationBodyVisitor bodyVisitor( noHtmlMode, spec );
+ if ( !bodyVisitor.act( inc, Incidence::Ptr(), msg, sender ) ) {
+ return QString();
+ }
+ html += bodyVisitor.result();
+
+ if ( msg->method() == iTIPRequest ) {
+ IncidenceFormatter::IncidenceCompareVisitor compareVisitor;
+ if ( compareVisitor.act( inc, existingIncidence ) ) {
+ html += "<p align=\"left\">";
+ if ( senderIsOrganizer( inc, sender ) ) {
+ html += i18n( "The following changes have been made by the organizer:" );
+ } else if ( !sender.isEmpty() ) {
+ html += i18n( "The following changes have been made by %1:", sender );
+ } else {
+ html += i18n( "The following changes have been made:" );
+ }
+ html += "</p>";
+ html += compareVisitor.result();
+ }
+ }
+ if ( msg->method() == iTIPReply ) {
+ IncidenceCompareVisitor compareVisitor;
+ if ( compareVisitor.act( inc, existingIncidence ) ) {
+ html += "<p align=\"left\">";
+ if ( !sender.isEmpty() ) {
+ html += i18n( "The following changes have been made by %1:", sender );
+ } else {
+ html += i18n( "The following changes have been made by an attendee:" );
+ }
+ html += "</p>";
+ html += compareVisitor.result();
+ }
+ }
+ }
+
+ // determine if I am the organizer for this invitation
+ bool myInc = iamOrganizer( inc );
+
+ // determine if the invitation response has already been recorded
+ bool rsvpRec = false;
+ Attendee::Ptr ea;
+ if ( !myInc ) {
+ Incidence::Ptr rsvpIncidence = existingIncidence;
+ if ( !rsvpIncidence && inc && inc->revision() > 0 ) {
+ rsvpIncidence = inc;
+ }
+ if ( rsvpIncidence ) {
+ ea = findMyAttendee( rsvpIncidence );
+ }
+ if ( ea &&
+ ( ea->status() == Attendee::Accepted ||
+ ea->status() == Attendee::Declined ||
+ ea->status() == Attendee::Tentative ) ) {
+ rsvpRec = true;
+ }
+ }
+
+ // determine invitation role
+ QString role;
+ bool isDelegated = false;
+ Attendee::Ptr a = findMyAttendee( inc );
+ if ( !a && inc ) {
+ if ( !inc->attendees().isEmpty() ) {
+ a = inc->attendees().first();
+ }
+ }
+ if ( a ) {
+ isDelegated = ( a->status() == Attendee::Delegated );
+ role = Stringify::attendeeRole( a->role() );
+ }
+
+ // determine if RSVP needed, not-needed, or response already recorded
+ bool rsvpReq = rsvpRequested( inc );
+ if ( !myInc && a ) {
+ html += "<br/>";
+ html += "<i><u>";
+ if ( rsvpRec && inc ) {
+ if ( inc->revision() == 0 ) {
+ html += i18n( "Your <b>%1</b> response has been recorded",
+ Stringify::attendeeStatus( ea->status() ) );
+ } else {
+ html += i18n( "Your status for this invitation is <b>%1</b>",
+ Stringify::attendeeStatus( ea->status() ) );
+ }
+ rsvpReq = false;
+ } else if ( msg->method() == iTIPCancel ) {
+ html += i18n( "This invitation was canceled" );
+ } else if ( msg->method() == iTIPAdd ) {
+ html += i18n( "This invitation was accepted" );
+ } else if ( msg->method() == iTIPDeclineCounter ) {
+ rsvpReq = true;
+ html += rsvpRequestedStr( rsvpReq, role );
+ } else {
+ if ( !isDelegated ) {
+ html += rsvpRequestedStr( rsvpReq, role );
+ } else {
+ html += i18n( "Awaiting delegation response" );
+ }
+ }
+ html += "</u></i>";
+ }
+
+ // Print if the organizer gave you a preset status
+ if ( !myInc ) {
+ if ( inc && inc->revision() == 0 ) {
+ QString statStr = myStatusStr( inc );
+ if ( !statStr.isEmpty() ) {
+ html += "<br/>";
+ html += "<i>";
+ html += statStr;
+ html += "</i>";
+ }
+ }
+ }
+
+ // Add groupware links
+
+ html += "<p>";
+ html += "<table border=\"0\" align=\"center\" cellspacing=\"4\"><tr>";
+
+ switch ( msg->method() ) {
+ case iTIPPublish:
+ case iTIPRequest:
+ case iTIPRefresh:
+ case iTIPAdd:
+ {
+ if ( inc && inc->revision() > 0 && ( existingIncidence || !helper->calendar() ) ) {
+ if ( inc->type() == Incidence::TypeTodo ) {
+ html += helper->makeLink( "reply", i18n( "[Record invitation in my to-do list]" ) );
+ } else {
+ html += helper->makeLink( "reply", i18n( "[Record invitation in my calendar]" ) );
+ }
+ }
+
+ if ( !myInc && a ) {
+ html += responseButtons( inc, rsvpReq, rsvpRec, helper );
+ }
+ break;
+ }
+
+ case iTIPCancel:
+ // Remove invitation
+ if ( inc ) {
+ html += tdOpen;
+ if ( inc->type() == Incidence::TypeTodo ) {
+ html += helper->makeLink( "cancel",
+ i18n( "Remove invitation from my to-do list" ) );
+ } else {
+ html += helper->makeLink( "cancel",
+ i18n( "Remove invitation from my calendar" ) );
+ }
+ html += tdClose;
+ }
+ break;
+
+ case iTIPReply:
+ {
+ // Record invitation response
+ Attendee::Ptr a;
+ Attendee::Ptr ea;
+ if ( inc ) {
+ // First, determine if this reply is really a counter in disguise.
+ if ( replyMeansCounter( inc ) ) {
+ html += "<tr>" + counterButtons( inc, helper ) + "</tr>";
+ break;
+ }
+
+ // Next, maybe this is a declined reply that was delegated from me?
+ // find first attendee who is delegated-from me
+ // look a their PARTSTAT response, if the response is declined,
+ // then we need to start over which means putting all the action
+ // buttons and NOT putting on the [Record response..] button
+ a = findDelegatedFromMyAttendee( inc );
+ if ( a ) {
+ if ( a->status() != Attendee::Accepted ||
+ a->status() != Attendee::Tentative ) {
+ html += responseButtons( inc, rsvpReq, rsvpRec, helper );
+ break;
+ }
+ }
+
+ // Finally, simply allow a Record of the reply
+ if ( !inc->attendees().isEmpty() ) {
+ a = inc->attendees().first();
+ }
+ if ( a && helper->calendar() ) {
+ ea = findAttendee( existingIncidence, a->email() );
+ }
+ }
+ if ( ea && ( ea->status() != Attendee::NeedsAction ) && ( ea->status() == a->status() ) ) {
+ html += tdOpen;
+ html += htmlAddTag( "i", i18n( "The <b>%1</b> response has been recorded",
+ Stringify::attendeeStatus( ea->status() ) ) );
+ html += tdClose;
+ } else {
+ if ( inc ) {
+ if ( inc->type() == Incidence::TypeTodo ) {
+ html += helper->makeLink( "reply", i18n( "[Record response in my to-do list]" ) );
+ } else {
+ html += helper->makeLink( "reply", i18n( "[Record response in my calendar]" ) );
+ }
+ }
+ }
+ break;
+ }
+
+ case iTIPCounter:
+ // Counter proposal
+ html += counterButtons( inc, helper );
+ break;
+
+ case iTIPDeclineCounter:
+ html += responseButtons( inc, rsvpReq, rsvpRec, helper );
+ break;
+
+ case iTIPNoMethod:
+ break;
+ }
+
+ // close the groupware table
+ html += "</tr></table>";
+
+ // Add the attendee list
+ if ( myInc ) {
+ html += invitationRsvpList( existingIncidence, a );
+ } else {
+ html += invitationAttendeeList( inc );
+ }
+
+ // close the top-level table
+ html += "</div>";
+
+ // Add the attachment list
+ html += invitationAttachments( helper, inc );
+
+ return html;
+}
+//@endcond
+
+QString IncidenceFormatter::formatICalInvitation( QString invitation,
+ const MemoryCalendar::Ptr &calendar,
+ InvitationFormatterHelper *helper,
+ bool outlookCompareStyle )
+{
+ return formatICalInvitationHelper( invitation, calendar, helper, false,
+ KSystemTimeZones::local(), QString(),
+ outlookCompareStyle );
+}
+
+QString IncidenceFormatter::formatICalInvitationNoHtml( const QString &invitation,
+ const MemoryCalendar::Ptr &calendar,
+ InvitationFormatterHelper *helper,
+ const QString &sender,
+ bool outlookCompareStyle )
+{
+ return formatICalInvitationHelper( invitation, calendar, helper, true,
+ KSystemTimeZones::local(), sender,
+ outlookCompareStyle );
+}
+
+/*******************************************************************
+ * Helper functions for the Incidence tooltips
+ *******************************************************************/
+
+//@cond PRIVATE
+class KCalUtils::IncidenceFormatter::ToolTipVisitor : public Visitor
+{
+ public:
+ ToolTipVisitor()
+ : mRichText( true ), mSpec( KDateTime::Spec() ), mResult( "" ) {}
+
+ bool act( const MemoryCalendar::Ptr &calendar,
+ const IncidenceBase::Ptr &incidence,
+ const QDate &date=QDate(), bool richText=true,
+ KDateTime::Spec spec=KDateTime::Spec() )
+ {
+ mCalendar = calendar;
+ mLocation.clear();
+ mDate = date;
+ mRichText = richText;
+ mSpec = spec;
+ mResult = "";
+ return incidence ? incidence->accept( *this, incidence ) : false;
+ }
+
+ bool act( const QString &location, const IncidenceBase::Ptr &incidence,
+ const QDate &date=QDate(), bool richText=true,
+ KDateTime::Spec spec=KDateTime::Spec() )
+ {
+ mLocation = location;
+ mDate = date;
+ mRichText = richText;
+ mSpec = spec;
+ mResult = "";
+ return incidence ? incidence->accept( *this, incidence ) : false;
+ }
+
+ QString result() const { return mResult; }
+
+ protected:
+ bool visit( Event::Ptr event );
+ bool visit( Todo::Ptr todo );
+ bool visit( Journal::Ptr journal );
+ bool visit( FreeBusy::Ptr fb );
+
+ QString dateRangeText( const Event::Ptr &event, const QDate &date );
+ QString dateRangeText( const Todo::Ptr &todo, const QDate &date );
+ QString dateRangeText( const Journal::Ptr &journal );
+ QString dateRangeText( const FreeBusy::Ptr &fb );
+
+ QString generateToolTip( const Incidence::Ptr &incidence, QString dtRangeText );
+
+ protected:
+ MemoryCalendar::Ptr mCalendar;
+ QString mLocation;
+ QDate mDate;
+ bool mRichText;
+ KDateTime::Spec mSpec;
+ QString mResult;
+};
+
+QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Event::Ptr &event,
+ const QDate &date )
+{
+ //FIXME: support mRichText==false
+ QString ret;
+ QString tmp;
+
+ KDateTime startDt = event->dtStart();
+ KDateTime endDt = event->dtEnd();
+ if ( event->recurs() ) {
+ if ( date.isValid() ) {
+ KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
+ int diffDays = startDt.daysTo( kdt );
+ kdt = kdt.addSecs( -1 );
+ startDt.setDate( event->recurrence()->getNextDateTime( kdt ).date() );
+ if ( event->hasEndDate() ) {
+ endDt = endDt.addDays( diffDays );
+ if ( startDt > endDt ) {
+ startDt.setDate( event->recurrence()->getPreviousDateTime( kdt ).date() );
+ endDt = startDt.addDays( event->dtStart().daysTo( event->dtEnd() ) );
+ }
+ }
+ }
+ }
+
+ if ( event->isMultiDay() ) {
+ tmp = dateToString( startDt, true, mSpec );
+ ret += "<br>" + i18nc( "Event start", "<i>From:</i> %1", tmp );
+
+ tmp = dateToString( endDt, true, mSpec );
+ ret += "<br>" + i18nc( "Event end","<i>To:</i> %1", tmp );
+
+ } else {
+
+ ret += "<br>" +
+ i18n( "<i>Date:</i> %1", dateToString( startDt, false, mSpec ) );
+ if ( !event->allDay() ) {
+ const QString dtStartTime = timeToString( startDt, true, mSpec );
+ const QString dtEndTime = timeToString( endDt, true, mSpec );
+ if ( dtStartTime == dtEndTime ) {
+ // to prevent 'Time: 17:00 - 17:00'
+ tmp = "<br>" +
+ i18nc( "time for event", "<i>Time:</i> %1",
+ dtStartTime );
+ } else {
+ tmp = "<br>" +
+ i18nc( "time range for event",
+ "<i>Time:</i> %1 - %2",
+ dtStartTime, dtEndTime );
+ }
+ ret += tmp;
+ }
+ }
+ return ret.replace( ' ', "&nbsp;" );
+}
+
+QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Todo::Ptr &todo,
+ const QDate &date )
+{
+ //FIXME: support mRichText==false
+ QString ret;
+ if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
+ KDateTime startDt = todo->dtStart();
+ if ( todo->recurs() ) {
+ if ( date.isValid() ) {
+ startDt.setDate( date );
+ }
+ }
+ ret += "<br>" +
+ i18n( "<i>Start:</i> %1", dateToString( startDt, false, mSpec ) );
+ }
+
+ if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
+ KDateTime dueDt = todo->dtDue();
+ if ( todo->recurs() ) {
+ if ( date.isValid() ) {
+ KDateTime kdt( date, QTime( 0, 0, 0 ), KSystemTimeZones::local() );
+ kdt = kdt.addSecs( -1 );
+ dueDt.setDate( todo->recurrence()->getNextDateTime( kdt ).date() );
+ }
+ }
+ ret += "<br>" +
+ i18n( "<i>Due:</i> %1",
+ dateTimeToString( dueDt, todo->allDay(), false, mSpec ) );
+ }
+
+ // Print priority and completed info here, for lack of a better place
+
+ if ( todo->priority() > 0 ) {
+ ret += "<br>";
+ ret += "<i>" + i18n( "Priority:" ) + "</i>" + "&nbsp;";
+ ret += QString::number( todo->priority() );
+ }
+
+ ret += "<br>";
+ if ( todo->isCompleted() ) {
+ ret += "<i>" + i18nc( "Completed: date", "Completed:" ) + "</i>" + "&nbsp;";
+ ret += Stringify::todoCompletedDateTime( todo ).replace( ' ', "&nbsp;" );
+ } else {
+ ret += "<i>" + i18n( "Percent Done:" ) + "</i>" + "&nbsp;";
+ ret += i18n( "%1%", todo->percentComplete() );
+ }
+
+ return ret.replace( ' ', "&nbsp;" );
+}
+
+QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const Journal::Ptr &journal )
+{
+ //FIXME: support mRichText==false
+ QString ret;
+ if ( journal->dtStart().isValid() ) {
+ ret += "<br>" +
+ i18n( "<i>Date:</i> %1", dateToString( journal->dtStart(), false, mSpec ) );
+ }
+ return ret.replace( ' ', "&nbsp;" );
+}
+
+QString IncidenceFormatter::ToolTipVisitor::dateRangeText( const FreeBusy::Ptr &fb )
+{
+ //FIXME: support mRichText==false
+ QString ret;
+ ret = "<br>" +
+ i18n( "<i>Period start:</i> %1",
+ KGlobal::locale()->formatDateTime( fb->dtStart().dateTime() ) );
+ ret += "<br>" +
+ i18n( "<i>Period start:</i> %1",
+ KGlobal::locale()->formatDateTime( fb->dtEnd().dateTime() ) );
+ return ret.replace( ' ', "&nbsp;" );
+}
+
+bool IncidenceFormatter::ToolTipVisitor::visit( Event::Ptr event )
+{
+ mResult = generateToolTip( event, dateRangeText( event, mDate ) );
+ return !mResult.isEmpty();
+}
+
+bool IncidenceFormatter::ToolTipVisitor::visit( Todo::Ptr todo )
+{
+ mResult = generateToolTip( todo, dateRangeText( todo, mDate ) );
+ return !mResult.isEmpty();
+}
+
+bool IncidenceFormatter::ToolTipVisitor::visit( Journal::Ptr journal )
+{
+ mResult = generateToolTip( journal, dateRangeText( journal ) );
+ return !mResult.isEmpty();
+}
+
+bool IncidenceFormatter::ToolTipVisitor::visit( FreeBusy::Ptr fb )
+{
+ //FIXME: support mRichText==false
+ mResult = "<qt><b>" +
+ i18n( "Free/Busy information for %1", fb->organizer()->fullName() ) +
+ "</b>";
+ mResult += dateRangeText( fb );
+ mResult += "</qt>";
+ return !mResult.isEmpty();
+}
+
+static QString tooltipPerson( const QString &email, const QString &name, Attendee::PartStat status )
+{
+ // Search for a new print name, if needed.
+ const QString printName = searchName( email, name );
+
+ // Get the icon corresponding to the attendee participation status.
+ const QString iconPath = rsvpStatusIconPath( status );
+
+ // Make the return string.
+ QString personString;
+ if ( !iconPath.isEmpty() ) {
+ personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
+ }
+ if ( status != Attendee::None ) {
+ personString += i18nc( "attendee name (attendee status)", "%1 (%2)",
+ printName.isEmpty() ? email : printName,
+ Stringify::attendeeStatus( status ) );
+ } else {
+ personString += i18n( "%1", printName.isEmpty() ? email : printName );
+ }
+ return personString;
+}
+
+static QString tooltipFormatOrganizer( const QString &email, const QString &name )
+{
+ // Search for a new print name, if needed
+ const QString printName = searchName( email, name );
+
+ // Get the icon for organizer
+ const QString iconPath =
+ KIconLoader::global()->iconPath( "meeting-organizer", KIconLoader::Small );
+
+ // Make the return string.
+ QString personString;
+ personString += "<img valign=\"top\" src=\"" + iconPath + "\">" + "&nbsp;";
+ personString += ( printName.isEmpty() ? email : printName );
+ return personString;
+}
+
+static QString tooltipFormatAttendeeRoleList( const Incidence::Ptr &incidence,
+ Attendee::Role role, bool showStatus )
+{
+ int maxNumAtts = 8; // maximum number of people to print per attendee role
+ const QString etc = i18nc( "elipsis", "..." );
+
+ int i = 0;
+ QString tmpStr;
+ Attendee::List::ConstIterator it;
+ Attendee::List attendees = incidence->attendees();
+
+ for ( it = attendees.constBegin(); it != attendees.constEnd(); ++it ) {
+ Attendee::Ptr a = *it;
+ if ( a->role() != role ) {
+ // skip not this role
+ continue;
+ }
+ if ( attendeeIsOrganizer( incidence, a ) ) {
+ // skip attendee that is also the organizer
+ continue;
+ }
+ if ( i == maxNumAtts ) {
+ tmpStr += "&nbsp;&nbsp;" + etc;
+ break;
+ }
+ tmpStr += "&nbsp;&nbsp;" + tooltipPerson( a->email(), a->name(),
+ showStatus ? a->status() : Attendee::None );
+ if ( !a->delegator().isEmpty() ) {
+ tmpStr += i18n( " (delegated by %1)", a->delegator() );
+ }
+ if ( !a->delegate().isEmpty() ) {
+ tmpStr += i18n( " (delegated to %1)", a->delegate() );
+ }
+ tmpStr += "<br>";
+ i++;
+ }
+ if ( tmpStr.endsWith( QLatin1String( "<br>" ) ) ) {
+ tmpStr.chop( 4 );
+ }
+ return tmpStr;
+}
+
+static QString tooltipFormatAttendees( const Calendar::Ptr &calendar,
+ const Incidence::Ptr &incidence )
+{
+ QString tmpStr, str;
+
+ // Add organizer link
+ int attendeeCount = incidence->attendees().count();
+ if ( attendeeCount > 1 ||
+ ( attendeeCount == 1 &&
+ !attendeeIsOrganizer( incidence, incidence->attendees().first() ) ) ) {
+ tmpStr += "<i>" + i18n( "Organizer:" ) + "</i>" + "<br>";
+ tmpStr += "&nbsp;&nbsp;" + tooltipFormatOrganizer( incidence->organizer()->email(),
+ incidence->organizer()->name() );
+ }
+
+ // Show the attendee status if the incidence's organizer owns the resource calendar,
+ // which means they are running the show and have all the up-to-date response info.
+ const bool showStatus = attendeeCount > 0 && incOrganizerOwnsCalendar( calendar, incidence );
+
+ // Add "chair"
+ str = tooltipFormatAttendeeRoleList( incidence, Attendee::Chair, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<br><i>" + i18n( "Chair:" ) + "</i>" + "<br>";
+ tmpStr += str;
+ }
+
+ // Add required participants
+ str = tooltipFormatAttendeeRoleList( incidence, Attendee::ReqParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<br><i>" + i18n( "Required Participants:" ) + "</i>" + "<br>";
+ tmpStr += str;
+ }
+
+ // Add optional participants
+ str = tooltipFormatAttendeeRoleList( incidence, Attendee::OptParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<br><i>" + i18n( "Optional Participants:" ) + "</i>" + "<br>";
+ tmpStr += str;
+ }
+
+ // Add observers
+ str = tooltipFormatAttendeeRoleList( incidence, Attendee::NonParticipant, showStatus );
+ if ( !str.isEmpty() ) {
+ tmpStr += "<br><i>" + i18n( "Observers:" ) + "</i>" + "<br>";
+ tmpStr += str;
+ }
+
+ return tmpStr;
+}
+
+QString IncidenceFormatter::ToolTipVisitor::generateToolTip( const Incidence::Ptr &incidence,
+ QString dtRangeText )
+{
+ int maxDescLen = 120; // maximum description chars to print (before elipsis)
+
+ //FIXME: support mRichText==false
+ if ( !incidence ) {
+ return QString();
+ }
+
+ QString tmp = "<qt>";
+
+ // header
+ tmp += "<b>" + incidence->richSummary() + "</b>";
+ tmp += "<hr>";
+
+ QString calStr = mLocation;
+ if ( mCalendar ) {
+ calStr = resourceString( mCalendar, incidence );
+ }
+ if ( !calStr.isEmpty() ) {
+ tmp += "<i>" + i18n( "Calendar:" ) + "</i>" + "&nbsp;";
+ tmp += calStr;
+ }
+
+ tmp += dtRangeText;
+
+ if ( !incidence->location().isEmpty() ) {
+ tmp += "<br>";
+ tmp += "<i>" + i18n( "Location:" ) + "</i>" + "&nbsp;";
+ tmp += incidence->richLocation();
+ }
+
+ QString durStr = durationString( incidence );
+ if ( !durStr.isEmpty() ) {
+ tmp += "<br>";
+ tmp += "<i>" + i18n( "Duration:" ) + "</i>" + "&nbsp;";
+ tmp += durStr;
+ }
+
+ if ( incidence->recurs() ) {
+ tmp += "<br>";
+ tmp += "<i>" + i18n( "Recurrence:" ) + "</i>" + "&nbsp;";
+ tmp += recurrenceString( incidence );
+ }
+
+ if ( !incidence->description().isEmpty() ) {
+ QString desc( incidence->description() );
+ if ( !incidence->descriptionIsRich() ) {
+ if ( desc.length() > maxDescLen ) {
+ desc = desc.left( maxDescLen ) + i18nc( "elipsis", "..." );
+ }
+ desc = Qt::escape( desc ).replace( '\n', "<br>" );
+ } else {
+ // TODO: truncate the description when it's rich text
+ }
+ tmp += "<hr>";
+ tmp += "<i>" + i18n( "Description:" ) + "</i>" + "<br>";
+ tmp += desc;
+ tmp += "<hr>";
+ }
+
+ int reminderCount = incidence->alarms().count();
+ if ( reminderCount > 0 && incidence->hasEnabledAlarms() ) {
+ tmp += "<br>";
+ tmp += "<i>" + i18np( "Reminder:", "Reminders:", reminderCount ) + "</i>" + "&nbsp;";
+ tmp += reminderStringList( incidence ).join( ", " );
+ }
+
+ tmp += "<br>";
+ tmp += tooltipFormatAttendees( mCalendar, incidence );
+
+ int categoryCount = incidence->categories().count();
+ if ( categoryCount > 0 ) {
+ tmp += "<br>";
+ tmp += "<i>" + i18np( "Category:", "Categories:", categoryCount ) + "</i>" + "&nbsp;";
+ tmp += incidence->categories().join( ", " );
+ }
+
+ tmp += "</qt>";
+ return tmp;
+}
+//@endcond
+
+QString IncidenceFormatter::toolTipStr( const QString &sourceName,
+ const IncidenceBase::Ptr &incidence,
+ const QDate &date,
+ bool richText,
+ KDateTime::Spec spec )
+{
+ ToolTipVisitor v;
+ if ( v.act( sourceName, incidence, date, richText, spec ) ) {
+ return v.result();
+ } else {
+ return QString();
+ }
+}
+
+/*******************************************************************
+ * Helper functions for the Incidence tooltips
+ *******************************************************************/
+
+//@cond PRIVATE
+static QString mailBodyIncidence( const Incidence::Ptr &incidence )
+{
+ QString body;
+ if ( !incidence->summary().isEmpty() ) {
+ body += i18n( "Summary: %1\n", incidence->richSummary() );
+ }
+ if ( !incidence->organizer()->isEmpty() ) {
+ body += i18n( "Organizer: %1\n", incidence->organizer()->fullName() );
+ }
+ if ( !incidence->location().isEmpty() ) {
+ body += i18n( "Location: %1\n", incidence->richLocation() );
+ }
+ return body;
+}
+//@endcond
+
+//@cond PRIVATE
+class KCalUtils::IncidenceFormatter::MailBodyVisitor : public Visitor
+{
+ public:
+ MailBodyVisitor()
+ : mSpec( KDateTime::Spec() ), mResult( "" ) {}
+
+ bool act( IncidenceBase::Ptr incidence, KDateTime::Spec spec=KDateTime::Spec() )
+ {
+ mSpec = spec;
+ mResult = "";
+ return incidence ? incidence->accept( *this, incidence ) : false;
+ }
+ QString result() const
+ {
+ return mResult;
+ }
+
+ protected:
+ bool visit( Event::Ptr event );
+ bool visit( Todo::Ptr todo );
+ bool visit( Journal::Ptr journal );
+ bool visit( FreeBusy::Ptr )
+ {
+ mResult = i18n( "This is a Free Busy Object" );
+ return !mResult.isEmpty();
+ }
+ protected:
+ KDateTime::Spec mSpec;
+ QString mResult;
+};
+
+bool IncidenceFormatter::MailBodyVisitor::visit( Event::Ptr event )
+{
+ QString recurrence[]= {
+ i18nc( "no recurrence", "None" ),
+ i18nc( "event recurs by minutes", "Minutely" ),
+ i18nc( "event recurs by hours", "Hourly" ),
+ i18nc( "event recurs by days", "Daily" ),
+ i18nc( "event recurs by weeks", "Weekly" ),
+ i18nc( "event recurs same position (e.g. first monday) each month", "Monthly Same Position" ),
+ i18nc( "event recurs same day each month", "Monthly Same Day" ),
+ i18nc( "event recurs same month each year", "Yearly Same Month" ),
+ i18nc( "event recurs same day each year", "Yearly Same Day" ),
+ i18nc( "event recurs same position (e.g. first monday) each year", "Yearly Same Position" )
+ };
+
+ mResult = mailBodyIncidence( event );
+ mResult += i18n( "Start Date: %1\n", dateToString( event->dtStart(), true, mSpec ) );
+ if ( !event->allDay() ) {
+ mResult += i18n( "Start Time: %1\n", timeToString( event->dtStart(), true, mSpec ) );
+ }
+ if ( event->dtStart() != event->dtEnd() ) {
+ mResult += i18n( "End Date: %1\n", dateToString( event->dtEnd(), true, mSpec ) );
+ }
+ if ( !event->allDay() ) {
+ mResult += i18n( "End Time: %1\n", timeToString( event->dtEnd(), true, mSpec ) );
+ }
+ if ( event->recurs() ) {
+ Recurrence *recur = event->recurrence();
+ // TODO: Merge these two to one of the form "Recurs every 3 days"
+ mResult += i18n( "Recurs: %1\n", recurrence[ recur->recurrenceType() ] );
+ mResult += i18n( "Frequency: %1\n", event->recurrence()->frequency() );
+
+ if ( recur->duration() > 0 ) {
+ mResult += i18np( "Repeats once", "Repeats %1 times", recur->duration() );
+ mResult += '\n';
+ } else {
+ if ( recur->duration() != -1 ) {
+// TODO_Recurrence: What to do with all-day
+ QString endstr;
+ if ( event->allDay() ) {
+ endstr = KGlobal::locale()->formatDate( recur->endDate() );
+ } else {
+ endstr = KGlobal::locale()->formatDateTime( recur->endDateTime().dateTime() );
+ }
+ mResult += i18n( "Repeat until: %1\n", endstr );
+ } else {
+ mResult += i18n( "Repeats forever\n" );
+ }
+ }
+ }
+
+ QString details = event->richDescription();
+ if ( !details.isEmpty() ) {
+ mResult += i18n( "Details:\n%1\n", details );
+ }
+ return !mResult.isEmpty();
+}
+
+bool IncidenceFormatter::MailBodyVisitor::visit( Todo::Ptr todo )
+{
+ mResult = mailBodyIncidence( todo );
+
+ if ( todo->hasStartDate() && todo->dtStart().isValid() ) {
+ mResult += i18n( "Start Date: %1\n", dateToString( todo->dtStart( false ), true, mSpec ) );
+ if ( !todo->allDay() ) {
+ mResult += i18n( "Start Time: %1\n", timeToString( todo->dtStart( false ), true, mSpec ) );
+ }
+ }
+ if ( todo->hasDueDate() && todo->dtDue().isValid() ) {
+ mResult += i18n( "Due Date: %1\n", dateToString( todo->dtDue(), true, mSpec ) );
+ if ( !todo->allDay() ) {
+ mResult += i18n( "Due Time: %1\n", timeToString( todo->dtDue(), true, mSpec ) );
+ }
+ }
+ QString details = todo->richDescription();
+ if ( !details.isEmpty() ) {
+ mResult += i18n( "Details:\n%1\n", details );
+ }
+ return !mResult.isEmpty();
+}
+
+bool IncidenceFormatter::MailBodyVisitor::visit( Journal::Ptr journal )
+{
+ mResult = mailBodyIncidence( journal );
+ mResult += i18n( "Date: %1\n", dateToString( journal->dtStart(), true, mSpec ) );
+ if ( !journal->allDay() ) {
+ mResult += i18n( "Time: %1\n", timeToString( journal->dtStart(), true, mSpec ) );
+ }
+ if ( !journal->description().isEmpty() ) {
+ mResult += i18n( "Text of the journal:\n%1\n", journal->richDescription() );
+ }
+ return !mResult.isEmpty();
+}
+//@endcond
+
+QString IncidenceFormatter::mailBodyStr( const IncidenceBase::Ptr &incidence,
+ KDateTime::Spec spec )
+{
+ if ( !incidence ) {
+ return QString();
+ }
+
+ MailBodyVisitor v;
+ if ( v.act( incidence, spec ) ) {
+ return v.result();
+ }
+ return QString();
+}
+
+//@cond PRIVATE
+static QString recurEnd( const Incidence::Ptr &incidence )
+{
+ QString endstr;
+ if ( incidence->allDay() ) {
+ endstr = KGlobal::locale()->formatDate( incidence->recurrence()->endDate() );
+ } else {
+ endstr = KGlobal::locale()->formatDateTime( incidence->recurrence()->endDateTime() );
+ }
+ return endstr;
+}
+//@endcond
+
+/************************************
+ * More static formatting functions
+ ************************************/
+
+QString IncidenceFormatter::recurrenceString( const Incidence::Ptr &incidence )
+{
+ if ( !incidence->recurs() ) {
+ return i18n( "No recurrence" );
+ }
+ static QStringList dayList;
+ if ( dayList.isEmpty() ) {
+ dayList.append( i18n( "31st Last" ) );
+ dayList.append( i18n( "30th Last" ) );
+ dayList.append( i18n( "29th Last" ) );
+ dayList.append( i18n( "28th Last" ) );
+ dayList.append( i18n( "27th Last" ) );
+ dayList.append( i18n( "26th Last" ) );
+ dayList.append( i18n( "25th Last" ) );
+ dayList.append( i18n( "24th Last" ) );
+ dayList.append( i18n( "23rd Last" ) );
+ dayList.append( i18n( "22nd Last" ) );
+ dayList.append( i18n( "21st Last" ) );
+ dayList.append( i18n( "20th Last" ) );
+ dayList.append( i18n( "19th Last" ) );
+ dayList.append( i18n( "18th Last" ) );
+ dayList.append( i18n( "17th Last" ) );
+ dayList.append( i18n( "16th Last" ) );
+ dayList.append( i18n( "15th Last" ) );
+ dayList.append( i18n( "14th Last" ) );
+ dayList.append( i18n( "13th Last" ) );
+ dayList.append( i18n( "12th Last" ) );
+ dayList.append( i18n( "11th Last" ) );
+ dayList.append( i18n( "10th Last" ) );
+ dayList.append( i18n( "9th Last" ) );
+ dayList.append( i18n( "8th Last" ) );
+ dayList.append( i18n( "7th Last" ) );
+ dayList.append( i18n( "6th Last" ) );
+ dayList.append( i18n( "5th Last" ) );
+ dayList.append( i18n( "4th Last" ) );
+ dayList.append( i18n( "3rd Last" ) );
+ dayList.append( i18n( "2nd Last" ) );
+ dayList.append( i18nc( "last day of the month", "Last" ) );
+ dayList.append( i18nc( "unknown day of the month", "unknown" ) ); //#31 - zero offset from UI
+ dayList.append( i18n( "1st" ) );
+ dayList.append( i18n( "2nd" ) );
+ dayList.append( i18n( "3rd" ) );
+ dayList.append( i18n( "4th" ) );
+ dayList.append( i18n( "5th" ) );
+ dayList.append( i18n( "6th" ) );
+ dayList.append( i18n( "7th" ) );
+ dayList.append( i18n( "8th" ) );
+ dayList.append( i18n( "9th" ) );
+ dayList.append( i18n( "10th" ) );
+ dayList.append( i18n( "11th" ) );
+ dayList.append( i18n( "12th" ) );
+ dayList.append( i18n( "13th" ) );
+ dayList.append( i18n( "14th" ) );
+ dayList.append( i18n( "15th" ) );
+ dayList.append( i18n( "16th" ) );
+ dayList.append( i18n( "17th" ) );
+ dayList.append( i18n( "18th" ) );
+ dayList.append( i18n( "19th" ) );
+ dayList.append( i18n( "20th" ) );
+ dayList.append( i18n( "21st" ) );
+ dayList.append( i18n( "22nd" ) );
+ dayList.append( i18n( "23rd" ) );
+ dayList.append( i18n( "24th" ) );
+ dayList.append( i18n( "25th" ) );
+ dayList.append( i18n( "26th" ) );
+ dayList.append( i18n( "27th" ) );
+ dayList.append( i18n( "28th" ) );
+ dayList.append( i18n( "29th" ) );
+ dayList.append( i18n( "30th" ) );
+ dayList.append( i18n( "31st" ) );
+ }
+
+ const int weekStart = KGlobal::locale()->weekStartDay();
+ QString dayNames;
+ const KCalendarSystem *calSys = KGlobal::locale()->calendar();
+
+ Recurrence *recur = incidence->recurrence();
+
+ QString txt, recurStr;
+ static QString noRecurrence = i18n( "No recurrence" );
+ switch ( recur->recurrenceType() ) {
+ case Recurrence::rNone:
+ return noRecurrence;
+
+ case Recurrence::rMinutely:
+ if ( recur->duration() != -1 ) {
+ recurStr = i18np( "Recurs every minute until %2",
+ "Recurs every %1 minutes until %2",
+ recur->frequency(), recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18np( "Recurs every minute",
+ "Recurs every %1 minutes", recur->frequency() );
+ }
+ break;
+
+ case Recurrence::rHourly:
+ if ( recur->duration() != -1 ) {
+ recurStr = i18np( "Recurs hourly until %2",
+ "Recurs every %1 hours until %2",
+ recur->frequency(), recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18np( "Recurs hourly", "Recurs every %1 hours", recur->frequency() );
+ }
+ break;
+
+ case Recurrence::rDaily:
+ if ( recur->duration() != -1 ) {
+ recurStr = i18np( "Recurs daily until %2",
+ "Recurs every %1 days until %2",
+ recur->frequency(), recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18np( "Recurs daily", "Recurs every %1 days", recur->frequency() );
+ }
+ break;
+
+ case Recurrence::rWeekly:
+ {
+ bool addSpace = false;
+ for ( int i = 0; i < 7; ++i ) {
+ if ( recur->days().testBit( ( i + weekStart + 6 ) % 7 ) ) {
+ if ( addSpace ) {
+ dayNames.append( i18nc( "separator for list of days", ", " ) );
+ }
+ dayNames.append( calSys->weekDayName( ( ( i + weekStart + 6 ) % 7 ) + 1,
+ KCalendarSystem::ShortDayName ) );
+ addSpace = true;
+ }
+ }
+ if ( dayNames.isEmpty() ) {
+ dayNames = i18nc( "Recurs weekly on no days", "no days" );
+ }
+ if ( recur->duration() != -1 ) {
+ recurStr = i18ncp( "Recurs weekly on [list of days] until end-date",
+ "Recurs weekly on %2 until %3",
+ "Recurs every <numid>%1</numid> weeks on %2 until %3",
+ recur->frequency(), dayNames, recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18ncp( "Recurs weekly on [list of days]",
+ "Recurs weekly on %2",
+ "Recurs every <numid>%1</numid> weeks on %2",
+ recur->frequency(), dayNames );
+ }
+ break;
+ }
+ case Recurrence::rMonthlyPos:
+ {
+ if ( !recur->monthPositions().isEmpty() ) {
+ RecurrenceRule::WDayPos rule = recur->monthPositions()[0];
+ if ( recur->duration() != -1 ) {
+ recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...]"
+ " weekdayname until end-date",
+ "Recurs every month on the %2 %3 until %4",
+ "Recurs every <numid>%1</numid> months on the %2 %3 until %4",
+ recur->frequency(),
+ dayList[rule.pos() + 31],
+ calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
+ recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18ncp( "Recurs every N months on the [2nd|3rd|...] weekdayname",
+ "Recurs every month on the %2 %3",
+ "Recurs every %1 months on the %2 %3",
+ recur->frequency(),
+ dayList[rule.pos() + 31],
+ calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ) );
+ }
+ }
+ break;
+ }
+ case Recurrence::rMonthlyDay:
+ {
+ if ( !recur->monthDays().isEmpty() ) {
+ int days = recur->monthDays()[0];
+ if ( recur->duration() != -1 ) {
+ recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day until end-date",
+ "Recurs monthly on the %2 day until %3",
+ "Recurs every %1 months on the %2 day until %3",
+ recur->frequency(),
+ dayList[days + 31],
+ recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18ncp( "Recurs monthly on the [1st|2nd|...] day",
+ "Recurs monthly on the %2 day",
+ "Recurs every <numid>%1</numid> month on the %2 day",
+ recur->frequency(),
+ dayList[days + 31] );
+ }
+ }
+ break;
+ }
+ case Recurrence::rYearlyMonth:
+ {
+ if ( recur->duration() != -1 ) {
+ if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
+ recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]"
+ " until end-date",
+ "Recurs yearly on %2 %3 until %4",
+ "Recurs every %1 years on %2 %3 until %4",
+ recur->frequency(),
+ calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
+ dayList[ recur->yearDates()[0] + 31 ],
+ recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ }
+ } else {
+ if ( !recur->yearDates().isEmpty() && !recur->yearMonths().isEmpty() ) {
+ recurStr = i18ncp( "Recurs Every N years on month-name [1st|2nd|...]",
+ "Recurs yearly on %2 %3",
+ "Recurs every %1 years on %2 %3",
+ recur->frequency(),
+ calSys->monthName( recur->yearMonths()[0],
+ recur->startDate().year() ),
+ dayList[ recur->yearDates()[0] + 31 ] );
+ } else {
+ if (!recur->yearMonths().isEmpty() ) {
+ recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
+ "Recurs yearly on %1 %2",
+ calSys->monthName( recur->yearMonths()[0],
+ recur->startDate().year() ),
+ dayList[ recur->startDate().day() + 31 ] );
+ } else {
+ recurStr = i18nc( "Recurs Every year on month-name [1st|2nd|...]",
+ "Recurs yearly on %1 %2",
+ calSys->monthName( recur->startDate().month(),
+ recur->startDate().year() ),
+ dayList[ recur->startDate().day() + 31 ] );
+ }
+ }
+ }
+ break;
+ }
+ case Recurrence::rYearlyDay:
+ if ( !recur->yearDays().isEmpty() ) {
+ if ( recur->duration() != -1 ) {
+ recurStr = i18ncp( "Recurs every N years on day N until end-date",
+ "Recurs every year on day <numid>%2</numid> until %3",
+ "Recurs every <numid>%1</numid> years"
+ " on day <numid>%2</numid> until %3",
+ recur->frequency(),
+ recur->yearDays()[0],
+ recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18ncp( "Recurs every N YEAR[S] on day N",
+ "Recurs every year on day <numid>%2</numid>",
+ "Recurs every <numid>%1</numid> years"
+ " on day <numid>%2</numid>",
+ recur->frequency(), recur->yearDays()[0] );
+ }
+ }
+ break;
+ case Recurrence::rYearlyPos:
+ {
+ if ( !recur->yearMonths().isEmpty() && !recur->yearPositions().isEmpty() ) {
+ RecurrenceRule::WDayPos rule = recur->yearPositions()[0];
+ if ( recur->duration() != -1 ) {
+ recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
+ "of monthname until end-date",
+ "Every year on the %2 %3 of %4 until %5",
+ "Every <numid>%1</numid> years on the %2 %3 of %4"
+ " until %5",
+ recur->frequency(),
+ dayList[rule.pos() + 31],
+ calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
+ calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ),
+ recurEnd( incidence ) );
+ if ( recur->duration() > 0 ) {
+ recurStr += i18nc( "number of occurrences",
+ " (<numid>%1</numid> occurrences)",
+ recur->duration() );
+ }
+ } else {
+ recurStr = i18ncp( "Every N years on the [2nd|3rd|...] weekdayname "
+ "of monthname",
+ "Every year on the %2 %3 of %4",
+ "Every <numid>%1</numid> years on the %2 %3 of %4",
+ recur->frequency(),
+ dayList[rule.pos() + 31],
+ calSys->weekDayName( rule.day(), KCalendarSystem::LongDayName ),
+ calSys->monthName( recur->yearMonths()[0], recur->startDate().year() ) );
+ }
+ }
+ }
+ break;
+ }
+
+ if ( recurStr.isEmpty() ) {
+ recurStr = i18n( "Incidence recurs" );
+ }
+
+ // Now, append the EXDATEs
+ DateTimeList l = recur->exDateTimes();
+ DateTimeList::ConstIterator il;
+ QStringList exStr;
+ for ( il = l.constBegin(); il != l.constEnd(); ++il ) {
+ switch ( recur->recurrenceType() ) {
+ case Recurrence::rMinutely:
+ exStr << i18n( "minute %1", (*il).time().minute() );
+ break;
+ case Recurrence::rHourly:
+ exStr << KGlobal::locale()->formatTime( (*il).time() );
+ break;
+ case Recurrence::rDaily:
+ exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
+ break;
+ case Recurrence::rWeekly:
+ exStr << calSys->weekDayName( (*il).date(), KCalendarSystem::ShortDayName );
+ break;
+ case Recurrence::rMonthlyPos:
+ exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
+ break;
+ case Recurrence::rMonthlyDay:
+ exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
+ break;
+ case Recurrence::rYearlyMonth:
+ exStr << calSys->monthName( (*il).date(), KCalendarSystem::LongName );
+ break;
+ case Recurrence::rYearlyDay:
+ exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
+ break;
+ case Recurrence::rYearlyPos:
+ exStr << KGlobal::locale()->formatDate( (*il).date(), KLocale::ShortDate );
+ break;
+ }
+ }
+
+ DateList d = recur->exDates();
+ DateList::ConstIterator dl;
+ for ( dl = d.constBegin(); dl != d.constEnd(); ++dl ) {
+ switch ( recur->recurrenceType() ) {
+ case Recurrence::rDaily:
+ exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
+ break;
+ case Recurrence::rWeekly:
+ // exStr << calSys->weekDayName( (*dl), KCalendarSystem::ShortDayName );
+ // kolab/issue4735, should be ( excluding 3 days ), instead of excluding( Fr,Fr,Fr )
+ if ( exStr.isEmpty() ) {
+ exStr << i18np( "1 day", "%1 days", recur->exDates().count() );
+ }
+ break;
+ case Recurrence::rMonthlyPos:
+ exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
+ break;
+ case Recurrence::rMonthlyDay:
+ exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
+ break;
+ case Recurrence::rYearlyMonth:
+ exStr << calSys->monthName( (*dl), KCalendarSystem::LongName );
+ break;
+ case Recurrence::rYearlyDay:
+ exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
+ break;
+ case Recurrence::rYearlyPos:
+ exStr << KGlobal::locale()->formatDate( (*dl), KLocale::ShortDate );
+ break;
+ }
+ }
+
+ if ( !exStr.isEmpty() ) {
+ recurStr = i18n( "%1 (excluding %2)", recurStr, exStr.join( "," ) );
+ }
+
+ return recurStr;
+}
+
+QString IncidenceFormatter::timeToString( const KDateTime &date,
+ bool shortfmt,
+ const KDateTime::Spec &spec )
+{
+ if ( spec.isValid() ) {
+
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return KGlobal::locale()->formatTime( date.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
+ } else {
+ return KGlobal::locale()->formatTime( date.time(), !shortfmt );
+ }
+}
+
+QString IncidenceFormatter::dateToString( const KDateTime &date,
+ bool shortfmt,
+ const KDateTime::Spec &spec )
+{
+ if ( spec.isValid() ) {
+
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return
+ KGlobal::locale()->formatDate( date.toTimeSpec( spec ).date(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
+ timeZone;
+ } else {
+ return
+ KGlobal::locale()->formatDate( date.date(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
+ }
+}
+
+QString IncidenceFormatter::dateTimeToString( const KDateTime &date,
+ bool allDay,
+ bool shortfmt,
+ const KDateTime::Spec &spec )
+{
+ if ( allDay ) {
+ return dateToString( date, shortfmt, spec );
+ }
+
+ if ( spec.isValid() ) {
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return KGlobal::locale()->formatDateTime(
+ date.toTimeSpec( spec ).dateTime(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
+ } else {
+ return KGlobal::locale()->formatDateTime(
+ date.dateTime(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
+ }
+}
+
+QString IncidenceFormatter::resourceString( const Calendar::Ptr &calendar,
+ const Incidence::Ptr &incidence )
+{
+ Q_UNUSED( calendar );
+ Q_UNUSED( incidence );
+ return QString();
+}
+
+static QString secs2Duration( int secs )
+{
+ QString tmp;
+ int days = secs / 86400;
+ if ( days > 0 ) {
+ tmp += i18np( "1 day", "%1 days", days );
+ tmp += ' ';
+ secs -= ( days * 86400 );
+ }
+ int hours = secs / 3600;
+ if ( hours > 0 ) {
+ tmp += i18np( "1 hour", "%1 hours", hours );
+ tmp += ' ';
+ secs -= ( hours * 3600 );
+ }
+ int mins = secs / 60;
+ if ( mins > 0 ) {
+ tmp += i18np( "1 minute", "%1 minutes", mins );
+ }
+ return tmp;
+}
+
+QString IncidenceFormatter::durationString( const Incidence::Ptr &incidence )
+{
+ QString tmp;
+ if ( incidence->type() == Incidence::TypeEvent ) {
+ Event::Ptr event = incidence.staticCast<Event>();
+ if ( event->hasEndDate() ) {
+ if ( !event->allDay() ) {
+ tmp = secs2Duration( event->dtStart().secsTo( event->dtEnd() ) );
+ } else {
+ tmp = i18np( "1 day", "%1 days",
+ event->dtStart().date().daysTo( event->dtEnd().date() ) + 1 );
+ }
+ } else {
+ tmp = i18n( "forever" );
+ }
+ } else if ( incidence->type() == Incidence::TypeTodo ) {
+ Todo::Ptr todo = incidence.staticCast<Todo>();
+ if ( todo->hasDueDate() ) {
+ if ( todo->hasStartDate() ) {
+ if ( !todo->allDay() ) {
+ tmp = secs2Duration( todo->dtStart().secsTo( todo->dtDue() ) );
+ } else {
+ tmp = i18np( "1 day", "%1 days",
+ todo->dtStart().date().daysTo( todo->dtDue().date() ) + 1 );
+ }
+ }
+ }
+ }
+ return tmp;
+}
+
+QStringList IncidenceFormatter::reminderStringList( const Incidence::Ptr &incidence,
+ bool shortfmt )
+{
+ //TODO: implement shortfmt=false
+ Q_UNUSED( shortfmt );
+
+ QStringList reminderStringList;
+
+ if ( incidence ) {
+ Alarm::List alarms = incidence->alarms();
+ Alarm::List::ConstIterator it;
+ for ( it = alarms.constBegin(); it != alarms.constEnd(); ++it ) {
+ Alarm::Ptr alarm = *it;
+ int offset = 0;
+ QString remStr, atStr, offsetStr;
+ if ( alarm->hasTime() ) {
+ offset = 0;
+ if ( alarm->time().isValid() ) {
+ atStr = KGlobal::locale()->formatDateTime( alarm->time() );
+ }
+ } else if ( alarm->hasStartOffset() ) {
+ offset = alarm->startOffset().asSeconds();
+ if ( offset < 0 ) {
+ offset = -offset;
+ offsetStr = i18nc( "N days/hours/minutes before the start datetime",
+ "%1 before the start", secs2Duration( offset ) );
+ } else if ( offset > 0 ) {
+ offsetStr = i18nc( "N days/hours/minutes after the start datetime",
+ "%1 after the start", secs2Duration( offset ) );
+ } else { //offset is 0
+ if ( incidence->dtStart().isValid() ) {
+ atStr = KGlobal::locale()->formatDateTime( incidence->dtStart() );
+ }
+ }
+ } else if ( alarm->hasEndOffset() ) {
+ offset = alarm->endOffset().asSeconds();
+ if ( offset < 0 ) {
+ offset = -offset;
+ if ( incidence->type() == Incidence::TypeTodo ) {
+ offsetStr = i18nc( "N days/hours/minutes before the due datetime",
+ "%1 before the to-do is due", secs2Duration( offset ) );
+ } else {
+ offsetStr = i18nc( "N days/hours/minutes before the end datetime",
+ "%1 before the end", secs2Duration( offset ) );
+ }
+ } else if ( offset > 0 ) {
+ if ( incidence->type() == Incidence::TypeTodo ) {
+ offsetStr = i18nc( "N days/hours/minutes after the due datetime",
+ "%1 after the to-do is due", secs2Duration( offset ) );
+ } else {
+ offsetStr = i18nc( "N days/hours/minutes after the end datetime",
+ "%1 after the end", secs2Duration( offset ) );
+ }
+ } else { //offset is 0
+ if ( incidence->type() == Incidence::TypeTodo ) {
+ Todo::Ptr t = incidence.staticCast<Todo>();
+ if ( t->dtDue().isValid() ) {
+ atStr = KGlobal::locale()->formatDateTime( t->dtDue() );
+ }
+ } else {
+ Event::Ptr e = incidence.staticCast<Event>();
+ if ( e->dtEnd().isValid() ) {
+ atStr = KGlobal::locale()->formatDateTime( e->dtEnd() );
+ }
+ }
+ }
+ }
+ if ( offset == 0 ) {
+ if ( !atStr.isEmpty() ) {
+ remStr = i18nc( "reminder occurs at datetime", "at %1", atStr );
+ }
+ } else {
+ remStr = offsetStr;
+ }
+
+ if ( alarm->repeatCount() > 0 ) {
+ QString countStr = i18np( "repeats once", "repeats %1 times", alarm->repeatCount() );
+ QString intervalStr = i18nc( "interval is N days/hours/minutes",
+ "interval is %1",
+ secs2Duration( alarm->snoozeTime().asSeconds() ) );
+ QString repeatStr = i18nc( "(repeat string, interval string)",
+ "(%1, %2)", countStr, intervalStr );
+ remStr = remStr + ' ' + repeatStr;
+
+ }
+ reminderStringList << remStr;
+ }
+ }
+
+ return reminderStringList;
+}
diff --git a/kcalutils/incidenceformatter.h b/kcalutils/incidenceformatter.h
new file mode 100644
index 0000000..87ec681
--- /dev/null
+++ b/kcalutils/incidenceformatter.h
@@ -0,0 +1,255 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and provides
+ static functions for formatting Incidences for various purposes.
+
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+ @author Allen Winter \<allen@kdab.com\>
+*/
+#ifndef KCALUTILS_INCIDENCEFORMATTER_H
+#define KCALUTILS_INCIDENCEFORMATTER_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/incidence.h>
+#include <kcalcore/memorycalendar.h>
+
+namespace KCalUtils {
+
+class KCALUTILS_EXPORT InvitationFormatterHelper
+{
+ public:
+ InvitationFormatterHelper();
+ virtual ~InvitationFormatterHelper();
+ virtual QString generateLinkURL( const QString &id );
+ virtual QString makeLink( const QString &id, const QString &text );
+ virtual KCalCore::Calendar::Ptr calendar() const;
+
+ private:
+ //@cond PRIVATE
+ Q_DISABLE_COPY( InvitationFormatterHelper )
+ class Private;
+ Private *const d;
+ //@endcond
+};
+
+/**
+ @brief
+ Provides methods to format Incidences in various ways for display purposes.
+
+ Helpers that provides several static methods to format an Incidence in
+ different ways: like an HTML representation for KMail, a representation
+ for tool tips, or a representation for a viewer widget.
+
+*/
+namespace IncidenceFormatter
+{
+ /**
+ Create a QString representation of an Incidence in a nice format
+ suitable for using in a tooltip.
+ @param sourceName where the incidence is from (e.g. resource name)
+ @param incidence is a pointer to the Incidence to be formatted.
+ @param date is the QDate for which the toolTip should be computed; used
+ mainly for recurring incidences. Note: For to-dos, this is the due date of
+ the occurrence, not the start date.
+ @param richText if yes, the QString will be created as RichText.
+ @param spec is an optional time specification which, when specified,
+ will shift the Incidence times to different timezones.
+ */
+ KCALUTILS_EXPORT QString toolTipStr( const QString &sourceName,
+ const KCalCore::IncidenceBase::Ptr &incidence,
+ const QDate &date = QDate(),
+ bool richText = true,
+ KDateTime::Spec spec = KDateTime::Spec() );
+
+ /**
+ Create a RichText QString representation of an Incidence in a nice format
+ suitable for using in a viewer widget.
+ @param calendar is a pointer to the Calendar that owns the specified Incidence.
+ @param incidence is a pointer to the Incidence to be formatted.
+ @param date is the QDate for which the string representation should be computed;
+ used mainly for recurring incidences.
+ @param spec is an optional time specification which, when specified,
+ will shift the Incidence times to different timezones.
+ */
+ KCALUTILS_EXPORT QString extensiveDisplayStr( const KCalCore::Calendar::Ptr &calendar,
+ const KCalCore::IncidenceBase::Ptr &incidence,
+ const QDate &date=QDate(),
+ KDateTime::Spec spec=KDateTime::Spec() );
+
+ /**
+ Create a RichText QString representation of an Incidence in a nice format
+ suitable for using in a viewer widget.
+ @param sourceName where the incidence is from (e.g. resource name)
+ @param incidence is a pointer to the Incidence to be formatted.
+ @param date is the QDate for which the string representation should be computed;
+ used mainly for recurring incidences.
+ @param spec is an optional time specification which, when specified,
+ will shift the Incidence times to different timezones.
+ */
+ KCALUTILS_EXPORT QString extensiveDisplayStr( const QString &sourceName,
+ const KCalCore::IncidenceBase::Ptr &incidence,
+ const QDate &date=QDate(),
+ KDateTime::Spec spec=KDateTime::Spec() );
+
+ /**
+ Create a QString representation of an Incidence in format suitable for
+ including inside a mail message.
+ @param incidence is a pointer to the Incidence to be formatted.
+ @param spec is an optional time specification which, when specified,
+ will shift the Incidence times to different timezones.
+ */
+ KCALUTILS_EXPORT QString mailBodyStr( const KCalCore::IncidenceBase::Ptr &incidence,
+ KDateTime::Spec spec=KDateTime::Spec() );
+
+ /**
+ Deliver an HTML formatted string displaying an invitation.
+ Use the time zone from mCalendar.
+
+ @param invitation a QString containing a string representation of a calendar Incidence
+ which will be intrepreted as an invitation.
+ @param calendar is a pointer to the Calendar that owns the invitation.
+ @param helper is a pointer to an InvitationFormatterHelper.
+ @param outlookCompareStyle if true, display updated invitation comparisons in the style
+ of Microsoft Outlook (tm); else use our own "classic" style.
+ */
+ KCALUTILS_EXPORT QString formatICalInvitation(
+ QString invitation,
+ const KCalCore::MemoryCalendar::Ptr &calendar,
+ InvitationFormatterHelper *helper,
+ bool outlookCompareStyle );
+
+ /**
+ Deliver an HTML formatted string displaying an invitation.
+ Differs from formatICalInvitation() in that invitation details (summary, location, etc)
+ have HTML formatting cleaned.
+ Use the time zone from calendar.
+
+ @param invitation a QString containing a string representation of a calendar Incidence
+ which will be intrepreted as an invitation.
+ @param calendar is a pointer to the Calendar that owns the invitation.
+ @param helper is a pointer to an InvitationFormatterHelper.
+ @param sender is a QString containing the email address of the person sending the invitation.
+ @param outlookCompareStyle if true, display updated invitation comparisons in the style
+ of Microsoft Outlook (tm); else use our own "classic" style.
+ */
+ KCALUTILS_EXPORT QString formatICalInvitationNoHtml(
+ const QString &invitation,
+ const KCalCore::MemoryCalendar::Ptr &calendar,
+ InvitationFormatterHelper *helper,
+ const QString &sender,
+ bool outlookCompareStyle );
+
+ /**
+ Build a pretty QString representation of an Incidence's recurrence info.
+ @param incidence is a pointer to the Incidence whose recurrence info
+ is to be formatted.
+ */
+ KCALUTILS_EXPORT QString recurrenceString( const KCalCore::Incidence::Ptr &incidence );
+
+ /**
+ Returns a reminder string computed for the specified Incidence.
+ Each item of the returning QStringList corresponds to a string
+ representation of an reminder belonging to this incidence.
+ @param incidence is a pointer to the Incidence.
+ @param shortfmt if false, a short version of each reminder is printed;
+ else a longer version of each reminder is printed.
+ */
+ KCALUTILS_EXPORT QStringList reminderStringList( const KCalCore::Incidence::Ptr &incidence,
+ bool shortfmt = true );
+
+ /**
+ Build a QString time representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see dateToString(), dateTimeToString().
+ */
+ KCALUTILS_EXPORT QString timeToString( const KDateTime &date, bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Build a QString date representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see dateToString(), dateTimeToString().
+ */
+ KCALUTILS_EXPORT QString dateToString( const KDateTime &date, bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Build a QString date/time representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param dateOnly If true, don't print the time fields; print the date fields only.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see dateToString(), timeToString().
+ */
+ KCALUTILS_EXPORT QString dateTimeToString( const KDateTime &date,
+ bool dateOnly = false,
+ bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Returns a Calendar Resource label name for the specified Incidence.
+ @param calendar is a pointer to the Calendar.
+ @param incidence is a pointer to the Incidence.
+ */
+ KCALUTILS_EXPORT QString resourceString( const KCalCore::Calendar::Ptr &calendar,
+ const KCalCore::Incidence::Ptr &incidence );
+
+ /**
+ Returns a duration string computed for the specified Incidence.
+ Only makes sense for Events and Todos.
+ @param incidence is a pointer to the Incidence.
+ */
+ KCALUTILS_EXPORT QString durationString( const KCalCore::Incidence::Ptr &incidence );
+
+ /**
+ Returns the translated string form of a specified #Status.
+ @param status is a #Status type.
+ */
+ KCALUTILS_EXPORT QString incidenceStatusName( KCalCore::Incidence::Status status );
+
+ /**
+ Returns a translatedstatus string for this incidence
+ */
+ KCALUTILS_EXPORT QString incidenceStatusStr( const KCalCore::Incidence::Ptr &incidence );
+
+ class EventViewerVisitor;
+ class ScheduleMessageVisitor;
+ class InvitationHeaderVisitor;
+ class InvitationBodyVisitor;
+ class IncidenceCompareVisitor;
+ class ToolTipVisitor;
+ class MailBodyVisitor;
+}
+
+}
+
+#endif
+
diff --git a/kcalutils/kcalutils_export.h b/kcalutils/kcalutils_export.h
new file mode 100644
index 0000000..a8bebf3
--- /dev/null
+++ b/kcalutils/kcalutils_export.h
@@ -0,0 +1,69 @@
+/*
+ This file is part of the kcal library.
+
+ Copyright (c) 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KCALUTILS_EXPORT_H
+#define KCALUTILS_EXPORT_H
+
+#include <kdemacros.h>
+
+#ifndef KCALUTILS_EXPORT
+# if defined(KDEPIM_STATIC_LIBS)
+ /* No export/import for static libraries */
+# define KCALUTILS_EXPORT
+# elif defined(MAKE_KCALUTILS_LIB)
+ /* We are building this library */
+# define KCALUTILS_EXPORT KDE_EXPORT
+# else
+ /* We are using this library */
+# define KCALUTILS_EXPORT KDE_IMPORT
+# endif
+#endif
+
+# ifndef KCALUTILS_EXPORT_DEPRECATED
+# if !defined( WANT_DEPRECATED_KCALUTILS_API )
+# define KCALUTILS_EXPORT_DEPRECATED KDE_DEPRECATED KCALUTILS_EXPORT
+# else
+# define KCALUTILS_EXPORT_DEPRECATED KCALUTILS_EXPORT
+# endif
+# endif
+
+#ifdef COMPILING_TESTS
+#ifndef KCALUTILS_TEST_EXPORT
+# if defined(KDEPIM_STATIC_LIBS)
+ /* No export/import for static libraries */
+# define KCALUTILS_TEST_EXPORT
+# elif defined(MAKE_KCALUTILS_TEST_LIB)
+ /* We are building this library */
+# define KCALUTILS_TEST_EXPORT KDE_EXPORT
+# else
+ /* We are using this library */
+# define KCALUTILS_TEST_EXPORT KDE_IMPORT
+# endif
+#endif
+#endif /* COMPILING_TESTS */
+
+/**
+ @namespace KCalUtils
+
+ @brief
+ Contains all the KCalUtils library global classes, objects, and functions.
+*/
+
+#endif
diff --git a/kcalutils/recurrenceactions.cpp b/kcalutils/recurrenceactions.cpp
new file mode 100644
index 0000000..66a2a2c
--- /dev/null
+++ b/kcalutils/recurrenceactions.cpp
@@ -0,0 +1,246 @@
+/*
+ This file is part of the kcal library.
+
+ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
+ Author: Kevin Krammer, krake@kdab.com
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "recurrenceactions.h"
+
+#include "ui_recurrenceactionsscopewidget.h"
+
+#include <KDialog>
+#include <KLocale>
+#include <KMessageBox>
+
+#include <QPointer>
+
+#include <boost/shared_ptr.hpp>
+
+using namespace KCalUtils;
+using namespace KCalUtils::RecurrenceActions;
+using namespace KCalCore;
+
+class ScopeWidget : public QWidget
+{
+ public:
+ ScopeWidget( int availableChoices, const KDateTime &dateTime, QWidget *parent )
+ : QWidget( parent ), mAvailableChoices( availableChoices )
+ {
+ mUi.setupUi( this );
+
+ if ( ( mAvailableChoices & PastOccurrences ) == 0 ) {
+ mUi.checkBoxPast->hide();
+ } else {
+ mUi.checkBoxPast->setText( i18nc( "@option:check calendar items before a certain date",
+ "Items before %1",
+ KGlobal::locale()->formatDateTime( dateTime ) ) );
+ }
+ if ( ( mAvailableChoices & SelectedOccurrence ) == 0 ) {
+ mUi.checkBoxSelected->hide();
+ } else {
+ mUi.checkBoxSelected->setText( i18nc( "@option:check currently selected calendar item",
+ "Selected item" ) );
+ }
+ if ( ( mAvailableChoices & FutureOccurrences ) == 0 ) {
+ mUi.checkBoxFuture->hide();
+ } else {
+ mUi.checkBoxFuture->setText( i18nc( "@option:check calendar items after a certain date",
+ "Items after %1",
+ KGlobal::locale()->formatDateTime( dateTime ) ) );
+ }
+ }
+
+ void setMessage( const QString &message );
+ void setIcon( const QIcon &icon );
+
+ void setCheckedChoices( int choices );
+ int checkedChoices() const;
+
+ private:
+ const int mAvailableChoices;
+ Ui_RecurrenceActionsScopeWidget mUi;
+};
+
+void ScopeWidget::setMessage( const QString &message )
+{
+ mUi.messageLabel->setText( message );
+}
+
+void ScopeWidget::setIcon( const QIcon &icon )
+{
+ QStyleOption option;
+ option.initFrom( this );
+ mUi.iconLabel->setPixmap(
+ icon.pixmap( style()->pixelMetric( QStyle::PM_MessageBoxIconSize, &option, this ) ) );
+}
+
+void ScopeWidget::setCheckedChoices( int choices )
+{
+ // mask with available ones
+ choices &= mAvailableChoices;
+
+ mUi.checkBoxPast->setChecked( ( choices & PastOccurrences ) != 0 );
+ mUi.checkBoxSelected->setChecked( ( choices & SelectedOccurrence ) != 0 );
+ mUi.checkBoxFuture->setChecked( ( choices & FutureOccurrences ) != 0 );
+}
+
+int ScopeWidget::checkedChoices() const
+{
+ int result = NoOccurrence;
+
+ if ( mUi.checkBoxPast->isChecked() ) {
+ result |= PastOccurrences;
+ }
+ if ( mUi.checkBoxSelected->isChecked() ) {
+ result |= SelectedOccurrence;
+ }
+ if ( mUi.checkBoxFuture->isChecked() ) {
+ result |= FutureOccurrences;
+ }
+
+ return result;
+}
+
+int RecurrenceActions::availableOccurrences( const Incidence::Ptr &incidence,
+ const KDateTime &selectedOccurrence )
+{
+ int result = NoOccurrence;
+
+ if ( incidence->recurrence()->recursOn( selectedOccurrence.date(),
+ selectedOccurrence.timeSpec() ) ) {
+ result |= SelectedOccurrence;
+ }
+
+ if ( incidence->recurrence()->getPreviousDateTime( selectedOccurrence ).isValid() ) {
+ result |= PastOccurrences;
+ }
+
+ if ( incidence->recurrence()->getNextDateTime( selectedOccurrence ).isValid() ) {
+ result |= FutureOccurrences;
+ }
+
+ return result;
+}
+
+int RecurrenceActions::questionMultipleChoice( const KDateTime &selectedOccurrence,
+ const QString &message, const QString &caption,
+ const KGuiItem &action, int availableChoices,
+ int preselectedChoices, QWidget *parent )
+{
+ QPointer<KDialog> dialog = new KDialog( parent );
+ dialog->setCaption( caption );
+ dialog->setButtons( KDialog::Ok | KDialog::Cancel );
+ dialog->setDefaultButton( KDialog::Ok );
+ dialog->setButtonGuiItem( KDialog::Ok, action );
+
+ ScopeWidget *widget = new ScopeWidget( availableChoices, selectedOccurrence, dialog );
+ dialog->setMainWidget( widget );
+
+ widget->setMessage( message );
+ widget->setIcon( widget->style()->standardIcon( QStyle::SP_MessageBoxQuestion ) );
+ widget->setCheckedChoices( preselectedChoices );
+
+ const int result = dialog->exec();
+ if ( dialog ) {
+ dialog->deleteLater();
+ }
+
+ if ( result == QDialog::Rejected ) {
+ return NoOccurrence;
+ }
+
+ return widget->checkedChoices();
+}
+
+int RecurrenceActions::questionSelectedAllCancel( const QString &message, const QString &caption,
+ const KGuiItem &actionSelected,
+ const KGuiItem &actionAll, QWidget *parent )
+{
+ KDialog *dialog = new KDialog( parent );
+ dialog->setCaption( caption );
+ dialog->setButtons( KDialog::Yes | KDialog::Ok | KDialog::Cancel );
+ dialog->setObjectName( "RecurrenceActions::questionSelectedAllCancel" );
+ dialog->setDefaultButton( KDialog::Yes );
+ dialog->setButtonGuiItem( KDialog::Yes, actionSelected );
+ dialog->setButtonGuiItem( KDialog::Ok, actionAll );
+
+ bool checkboxResult = false;
+ int result = KMessageBox::createKMessageBox(
+ dialog,
+ QMessageBox::Question,
+ message,
+ QStringList(),
+ QString(),
+ &checkboxResult,
+ KMessageBox::Notify );
+
+ switch (result) {
+ case KDialog::Yes:
+ return SelectedOccurrence;
+ case QDialog::Accepted:
+ // See kdialog.h, 'Ok' doesn't return KDialog:Ok
+ return AllOccurrences;
+ default:
+ return NoOccurrence;
+ }
+
+ return NoOccurrence;
+}
+
+int RecurrenceActions::questionSelectedFutureAllCancel( const QString &message,
+ const QString &caption,
+ const KGuiItem &actionSelected,
+ const KGuiItem &actionFuture,
+ const KGuiItem &actionAll,
+ QWidget *parent )
+{
+ KDialog *dialog = new KDialog( parent );
+ dialog->setCaption( caption );
+ dialog->setButtons( KDialog::Yes | KDialog::No | KDialog::Ok | KDialog::Cancel );
+ dialog->setObjectName( "RecurrenceActions::questionSelectedFutureAllCancel" );
+ dialog->setDefaultButton( KDialog::Yes );
+ dialog->setButtonGuiItem( KDialog::Yes, actionSelected );
+ dialog->setButtonGuiItem( KDialog::No, actionFuture );
+ dialog->setButtonGuiItem( KDialog::Ok, actionAll );
+
+ bool checkboxResult = false;
+ int result = KMessageBox::createKMessageBox(
+ dialog,
+ QMessageBox::Question,
+ message,
+ QStringList(),
+ QString(),
+ &checkboxResult,
+ KMessageBox::Notify );
+
+ switch (result) {
+ case KDialog::Yes:
+ return SelectedOccurrence;
+ case KDialog::No:
+ return FutureOccurrences;
+ case QDialog::Accepted:
+ return AllOccurrences;
+ default:
+ return NoOccurrence;
+ }
+
+ return NoOccurrence;
+}
+
+// kate: space-indent on; indent-width 2; replace-tabs on;
diff --git a/kcalutils/recurrenceactions.h b/kcalutils/recurrenceactions.h
new file mode 100644
index 0000000..b718458
--- /dev/null
+++ b/kcalutils/recurrenceactions.h
@@ -0,0 +1,176 @@
+/*
+ This file is part of the kcal library.
+
+ Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.net
+ Author: Kevin Krammer, krake@kdab.com
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef RECURRENCEACTIONS_H
+#define RECURRENCEACTIONS_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/incidence.h>
+
+class KDateTime;
+class KGuiItem;
+class QWidget;
+
+namespace KCalUtils
+{
+
+/**
+ @short Utility functions for dealing with recurrences
+
+ Incidences with recurrencies need to be treated differently than single independent ones.
+ For example the user might be given the choice to not only modify a selected occurrence
+ of an incidence but also all that follow that one, etc.
+
+ @author Kevin Krammer, krake@kdab.com
+ @since 4.6
+*/
+namespace RecurrenceActions
+{
+ /**
+ @short Flags for indicating on which occurrences to work on
+
+ Flags can be OR'ed together to get a combined scope.
+ */
+ enum Scope {
+ /**
+ Scope does not apply to any occurrence
+ */
+ NoOccurrence = 0,
+
+ /**
+ Scope does include the given/selected occurrence
+ */
+ SelectedOccurrence = 1,
+
+ /**
+ Scope does include occurrences before the given/selected occurrence
+ */
+ PastOccurrences = 2,
+
+ /**
+ Scope does include occurrences after the given/selected occurrence
+ */
+ FutureOccurrences = 4,
+
+ /**
+ Scope does include all occurrences (past, present and future)
+ */
+ AllOccurrences = PastOccurrences | SelectedOccurrence | FutureOccurrences
+ };
+
+ /**
+ @short Checks what scope an action could be applied on for a given incidence
+
+ Checks whether the incidence is occurring on the given date and whether there
+ are occurrences in the past and future.
+
+ @param incidence the incidence of which to check recurrences.
+ @param selectedOccurrence the date (including timespec) to use as the base occurrence,
+ i.e., from which to check for past and future occurrences.
+
+ @return the #Scope to which actions on the given @incidence can be applied to
+ */
+ KCALUTILS_EXPORT
+ int availableOccurrences( const KCalCore::Incidence::Ptr &incidence,
+ const KDateTime &selectedOccurrence );
+
+ /**
+ @short Presents a multiple choice scope selection dialog to the user
+
+ Shows a message box style question dialog with checkboxes for occurrence scope flags
+ so the user can be asked specifically which occurrences to apply actions to.
+
+ @param selectedOccurrence the date to use for telling the user which occurrence
+ is the selected one.
+ @param message the message which explains the change and selection options.
+ @param caption the dialog's caption.
+ @param action the GUI item to use for the "OK" button.
+ @param availableChoices combined #Scope values to select which options should be present.
+ @param preselectedChoices combined #Scope values to optionally preselect some of the options
+ specified with @p availableChoices.
+ @param parent QWidget parent for the dialog.
+
+ @return the chosen #Scope options, OR'ed together
+ */
+ KCALUTILS_EXPORT int questionMultipleChoice( const KDateTime &selectedOccurrence,
+ const QString &message, const QString &caption,
+ const KGuiItem &action, int availableChoices,
+ int preselectedChoices, QWidget *parent );
+
+ /**
+ @short Presents a message box with two action choices and cancel to the user
+
+ Shows a message box style question dialog with two action scope buttons and cancel.
+ This is for quick decisions like whether to only modify a single occurrence or all occurrences.
+
+ @param message the message which explains the change and available options.
+ @param caption the dialog's caption.
+ @param actionSelected the GUI item to use for the button representing the
+ #SelectedOccurrence scope.
+ @param actionAll the GUI item to use for the button representing the #AllOccurrences scope.
+ @param parent QWidget parent for the dialog.
+
+ @param #NoOccurrence on cancel, #SelectedOccurrence or #AllOccurrences on the respective action.
+ */
+ KCALUTILS_EXPORT
+ int questionSelectedAllCancel( const QString &message, const QString &caption,
+ const KGuiItem &actionSelected, const KGuiItem &actionAll,
+ QWidget *parent );
+
+ /**
+ @short Presents a message box with three action choices and cancel to the user
+
+ Shows a message box style question dialog with three action scope buttons and cancel.
+ This is for quick decisions like whether to only modify a single occurrence, to include
+ future or all occurrences.
+
+ @note The calling application code can of course decide to word the future action text
+ in a way that it includes the selected occurrence, e.g. "Also Future Items".
+ The returned value will still just be #FutureOccurrences so the calling code
+ has to include #SelectedOccurrence itself if it passes the value further on
+
+ @param message the message which explains the change and available options.
+ @param caption the dialog's caption.
+ @param actionSelected the GUI item to use for the button representing the
+ #SelectedOccurrence scope.
+ @param actionSelected the GUI item to use for the button representing the
+ #FutureOccurrences scope.
+ @param actionAll the GUI item to use for the button representing the #AllOccurrences scope.
+ @param parent QWidget parent for the dialog.
+
+ @param #NoOccurrence on cancel, #SelectedOccurrence, #FutureOccurrences or #AllOccurrences
+ on the respective action.
+ */
+ KCALUTILS_EXPORT
+ int questionSelectedFutureAllCancel( const QString &message, const QString &caption,
+ const KGuiItem &actionSelected,
+ const KGuiItem &actionFuture,
+ const KGuiItem &actionAll,
+ QWidget *parent );
+}
+
+}
+
+#endif
+
+// kate: space-indent on; indent-width 2; replace-tabs on;
diff --git a/kcalutils/recurrenceactionsscopewidget.ui b/kcalutils/recurrenceactionsscopewidget.ui
new file mode 100644
index 0000000..2248feb
--- /dev/null
+++ b/kcalutils/recurrenceactionsscopewidget.ui
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RecurrenceActionsScopeWidget</class>
+ <widget class="QWidget" name="RecurrenceActionsScopeWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>359</width>
+ <height>174</height>
+ </rect>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="iconLabel">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="0" column="1" rowspan="2">
+ <widget class="QLabel" name="messageLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QCheckBox" name="checkBoxPast">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Select calendar items before the specified date</string>
+ </property>
+ <property name="whatsThis">
+ <string>Check this box if you want to select calendar items that occur before the specified date.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBoxSelected">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Choose the currently selected calendar item</string>
+ </property>
+ <property name="whatsThis">
+ <string>Check this box if you want to select calendar items that occur on the specified date.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="checkBoxFuture">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="toolTip">
+ <string>Select calendar items after the specified date</string>
+ </property>
+ <property name="whatsThis">
+ <string>Check this box if you want to select calendar items that occur after the specified date.</string>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/kcalutils/scheduler.cpp b/kcalutils/scheduler.cpp
new file mode 100644
index 0000000..9fec592
--- /dev/null
+++ b/kcalutils/scheduler.cpp
@@ -0,0 +1,569 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001,2004 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (C) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "scheduler.h"
+#include "stringify.h"
+
+#include <kcalcore/icalformat.h>
+#include <kcalcore/freebusycache.h>
+using namespace KCalCore;
+
+#include <KDebug>
+#include <KLocale>
+#include <KMessageBox>
+
+using namespace KCalUtils;
+
+//@cond PRIVATE
+struct KCalUtils::Scheduler::Private
+{
+ public:
+ Private() : mFreeBusyCache( 0 )
+ {
+ }
+ FreeBusyCache *mFreeBusyCache;
+};
+//@endcond
+
+Scheduler::Scheduler( const Calendar::Ptr &calendar ) : d( new KCalUtils::Scheduler::Private )
+{
+ mCalendar = calendar;
+ mFormat = new ICalFormat();
+ mFormat->setTimeSpec( calendar->timeSpec() );
+}
+
+Scheduler::~Scheduler()
+{
+ delete mFormat;
+ delete d;
+}
+
+void Scheduler::setFreeBusyCache( FreeBusyCache *c )
+{
+ d->mFreeBusyCache = c;
+}
+
+FreeBusyCache *Scheduler::freeBusyCache() const
+{
+ return d->mFreeBusyCache;
+}
+
+bool Scheduler::acceptTransaction( const IncidenceBase::Ptr &incidence, iTIPMethod method,
+ ScheduleMessage::Status status, const QString &email )
+{
+ kDebug() << "method=" << ScheduleMessage::methodName( method ); //krazy:exclude=kdebug
+
+ switch ( method ) {
+ case iTIPPublish:
+ return acceptPublish( incidence, status, method );
+ case iTIPRequest:
+ return acceptRequest( incidence, status, email );
+ case iTIPAdd:
+ return acceptAdd( incidence, status );
+ case iTIPCancel:
+ return acceptCancel( incidence, status, email );
+ case iTIPDeclineCounter:
+ return acceptDeclineCounter( incidence, status );
+ case iTIPReply:
+ return acceptReply( incidence, status, method );
+ case iTIPRefresh:
+ return acceptRefresh( incidence, status );
+ case iTIPCounter:
+ return acceptCounter( incidence, status );
+ default:
+ break;
+ }
+ deleteTransaction( incidence );
+ return false;
+}
+
+bool Scheduler::deleteTransaction( const IncidenceBase::Ptr & )
+{
+ return true;
+}
+
+bool Scheduler::acceptPublish( const IncidenceBase::Ptr &newIncBase, ScheduleMessage::Status status,
+ iTIPMethod method )
+{
+ if ( newIncBase->type() == IncidenceBase::TypeFreeBusy ) {
+ return acceptFreeBusy( newIncBase, method );
+ }
+
+ bool res = false;
+
+ kDebug() << "status=" << Stringify::scheduleMessageStatus( status ); //krazy:exclude=kdebug
+
+ Incidence::Ptr newInc = newIncBase.staticCast<Incidence>() ;
+ Incidence::Ptr calInc = mCalendar->incidence( newIncBase->uid() );
+ switch ( status ) {
+ case ScheduleMessage::Unknown:
+ case ScheduleMessage::PublishNew:
+ case ScheduleMessage::PublishUpdate:
+ if ( calInc && newInc ) {
+ if ( ( newInc->revision() > calInc->revision() ) ||
+ ( newInc->revision() == calInc->revision() &&
+ newInc->lastModified() > calInc->lastModified() ) ) {
+ const QString oldUid = calInc->uid();
+
+ if ( calInc->type() != newInc->type() ) {
+ kError() << "assigning different incidence types";
+ } else {
+ IncidenceBase *ci = calInc.data();
+ IncidenceBase *ni = newInc.data();
+ *ci = *ni;
+ calInc->setSchedulingID( newInc->uid(), oldUid );
+ res = true;
+ }
+ }
+ }
+ break;
+ case ScheduleMessage::Obsolete:
+ res = true;
+ break;
+ default:
+ break;
+ }
+ deleteTransaction( newIncBase );
+ return res;
+}
+
+bool Scheduler::acceptRequest( const IncidenceBase::Ptr &incidence,
+ ScheduleMessage::Status status,
+ const QString &email )
+{
+ Incidence::Ptr inc = incidence.staticCast<Incidence>() ;
+ if ( !inc ) {
+ kWarning() << "Accept what?";
+ return false;
+ }
+ if ( inc->type() == IncidenceBase::TypeFreeBusy ) {
+ // reply to this request is handled in korganizer's incomingdialog
+ return true;
+ }
+
+ const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
+ kDebug() << "status=" << Stringify::scheduleMessageStatus( status ) //krazy:exclude=kdebug
+ << ": found " << existingIncidences.count()
+ << " incidences with schedulingID " << inc->schedulingID()
+ << "; uid was = " << inc->uid();
+
+ if ( existingIncidences.isEmpty() ) {
+ // Perfectly normal if the incidence doesn't exist. This is probably
+ // a new invitation.
+ kDebug() << "incidence not found; calendar = " << mCalendar.data()
+ << "; incidence count = " << mCalendar->incidences().count();
+ }
+ Incidence::List::ConstIterator incit = existingIncidences.begin();
+ for ( ; incit != existingIncidences.end() ; ++incit ) {
+ Incidence::Ptr existingIncidence = *incit;
+ kDebug() << "Considering this found event ("
+ << ( existingIncidence->isReadOnly() ? "readonly" : "readwrite" )
+ << ") :" << mFormat->toString( existingIncidence );
+ // If it's readonly, we can't possible update it.
+ if ( existingIncidence->isReadOnly() ) {
+ continue;
+ }
+ if ( existingIncidence->revision() <= inc->revision() ) {
+ // The new incidence might be an update for the found one
+ bool isUpdate = true;
+ // Code for new invitations:
+ // If you think we could check the value of "status" to be RequestNew: we can't.
+ // It comes from a similar check inside libical, where the event is compared to
+ // other events in the calendar. But if we have another version of the event around
+ // (e.g. shared folder for a group), the status could be RequestNew, Obsolete or Updated.
+ kDebug() << "looking in " << existingIncidence->uid() << "'s attendees";
+ // This is supposed to be a new request, not an update - however we want to update
+ // the existing one to handle the "clicking more than once on the invitation" case.
+ // So check the attendee status of the attendee.
+ const Attendee::List attendees = existingIncidence->attendees();
+ Attendee::List::ConstIterator ait;
+ for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
+ if( (*ait)->email() == email && (*ait)->status() == Attendee::NeedsAction ) {
+ // This incidence wasn't created by me - it's probably in a shared folder
+ // and meant for someone else, ignore it.
+ kDebug() << "ignoring " << existingIncidence->uid()
+ << " since I'm still NeedsAction there";
+ isUpdate = false;
+ break;
+ }
+ }
+ if ( isUpdate ) {
+ if ( existingIncidence->revision() == inc->revision() &&
+ existingIncidence->lastModified() > inc->lastModified() ) {
+ // This isn't an update - the found incidence was modified more recently
+ kDebug() << "This isn't an update - the found incidence was modified more recently";
+ deleteTransaction( existingIncidence );
+ return false;
+ }
+ kDebug() << "replacing existing incidence " << existingIncidence->uid();
+ bool res = true;
+ const QString oldUid = existingIncidence->uid();
+ if ( existingIncidence->type() != inc->type() ) {
+ kError() << "assigning different incidence types";
+ res = false;
+ } else {
+ IncidenceBase *existingIncidenceBase = existingIncidence.data();
+ IncidenceBase *incBase = inc.data();
+ *existingIncidenceBase = *incBase;
+ existingIncidence->setSchedulingID( inc->uid(), oldUid );
+ }
+ deleteTransaction( incidence );
+ return res;
+ }
+ } else {
+ // This isn't an update - the found incidence has a bigger revision number
+ kDebug() << "This isn't an update - the found incidence has a bigger revision number";
+ deleteTransaction( incidence );
+ return false;
+ }
+ }
+
+ // Move the uid to be the schedulingID and make a unique UID
+ inc->setSchedulingID( inc->uid(), CalFormat::createUniqueId() );
+ // notify the user in case this is an update and we didn't find the to-be-updated incidence
+ if ( existingIncidences.count() == 0 && inc->revision() > 0 ) {
+ KMessageBox::information(
+ 0,
+ i18nc( "@info",
+ "<para>You accepted an invitation update, but an earlier version of the "
+ "item could not be found in your calendar.</para>"
+ "<para>This may have occurred because:<list>"
+ "<item>the organizer did not include you in the original invitation</item>"
+ "<item>you did not accept the original invitation yet</item>"
+ "<item>you deleted the original invitation from your calendar</item>"
+ "<item>you no longer have access to the calendar containing the invitation</item>"
+ "</list></para>"
+ "<para>This is not a problem, but we thought you should know.</para>" ),
+ i18nc( "@title", "Cannot find invitation to be updated" ), "AcceptCantFindIncidence" );
+ }
+ kDebug() << "Storing new incidence with scheduling uid=" << inc->schedulingID()
+ << " and uid=" << inc->uid();
+ mCalendar->addIncidence( inc );
+
+ deleteTransaction( incidence );
+ return true;
+}
+
+bool Scheduler::acceptAdd( const IncidenceBase::Ptr &incidence,
+ ScheduleMessage::Status /* status */)
+{
+ deleteTransaction( incidence );
+ return false;
+}
+
+bool Scheduler::acceptCancel( const IncidenceBase::Ptr &incidence,
+ ScheduleMessage::Status status,
+ const QString &attendee )
+{
+ Incidence::Ptr inc = incidence.staticCast<Incidence>();
+ if ( !inc ) {
+ return false;
+ }
+
+ if ( inc->type() == IncidenceBase::TypeFreeBusy ) {
+ // reply to this request is handled in korganizer's incomingdialog
+ return true;
+ }
+
+ const Incidence::List existingIncidences = mCalendar->incidencesFromSchedulingID( inc->uid() );
+ kDebug() << "Scheduler::acceptCancel="
+ << Stringify::scheduleMessageStatus( status ) //krazy2:exclude=kdebug
+ << ": found " << existingIncidences.count()
+ << " incidences with schedulingID " << inc->schedulingID();
+
+ bool ret = false;
+ Incidence::List::ConstIterator incit = existingIncidences.begin();
+ for ( ; incit != existingIncidences.end() ; ++incit ) {
+ Incidence::Ptr i = *incit;
+ kDebug() << "Considering this found event ("
+ << ( i->isReadOnly() ? "readonly" : "readwrite" )
+ << ") :" << mFormat->toString( i );
+
+ // If it's readonly, we can't possible remove it.
+ if ( i->isReadOnly() ) {
+ continue;
+ }
+
+ // Code for new invitations:
+ // We cannot check the value of "status" to be RequestNew because
+ // "status" comes from a similar check inside libical, where the event
+ // is compared to other events in the calendar. But if we have another
+ // version of the event around (e.g. shared folder for a group), the
+ // status could be RequestNew, Obsolete or Updated.
+ kDebug() << "looking in " << i->uid() << "'s attendees";
+
+ // This is supposed to be a new request, not an update - however we want
+ // to update the existing one to handle the "clicking more than once
+ // on the invitation" case. So check the attendee status of the attendee.
+ bool isMine = true;
+ const Attendee::List attendees = i->attendees();
+ Attendee::List::ConstIterator ait;
+ for ( ait = attendees.begin(); ait != attendees.end(); ++ait ) {
+ if ( (*ait)->email() == attendee &&
+ (*ait)->status() == Attendee::NeedsAction ) {
+ // This incidence wasn't created by me - it's probably in a shared
+ // folder and meant for someone else, ignore it.
+ kDebug() << "ignoring " << i->uid()
+ << " since I'm still NeedsAction there";
+ isMine = false;
+ break;
+ }
+ }
+
+ if ( isMine ) {
+ kDebug() << "removing existing incidence " << i->uid();
+ if ( i->type() == IncidenceBase::TypeEvent ) {
+ Event::Ptr event = mCalendar->event( i->uid() );
+ ret = ( event && mCalendar->deleteEvent( event ) );
+ } else if ( i->type() == IncidenceBase::TypeTodo ) {
+ Todo::Ptr todo = mCalendar->todo( i->uid() );
+ ret = ( todo && mCalendar->deleteTodo( todo ) );
+ }
+ deleteTransaction( incidence );
+ return ret;
+ }
+ }
+
+ // in case we didn't find the to-be-removed incidence
+ if ( existingIncidences.count() > 0 && inc->revision() > 0 ) {
+ KMessageBox::error(
+ 0,
+ i18nc( "@info",
+ "The event or task could not be removed from your calendar. "
+ "Maybe it has already been deleted or is not owned by you. "
+ "Or it might belong to a read-only or disabled calendar." ) );
+ }
+ deleteTransaction( incidence );
+ return ret;
+}
+
+bool Scheduler::acceptDeclineCounter( const IncidenceBase::Ptr &incidence,
+ ScheduleMessage::Status status )
+{
+ Q_UNUSED( status );
+ deleteTransaction( incidence );
+ return false;
+}
+
+bool Scheduler::acceptReply( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status,
+ iTIPMethod method )
+{
+ Q_UNUSED( status );
+ if ( incidence->type() == IncidenceBase::TypeFreeBusy ) {
+ return acceptFreeBusy( incidence, method );
+ }
+ bool ret = false;
+ Event::Ptr ev = mCalendar->event( incidence->uid() );
+ Todo::Ptr to = mCalendar->todo( incidence->uid() );
+
+ // try harder to find the correct incidence
+ if ( !ev && !to ) {
+ const Incidence::List list = mCalendar->incidences();
+ for ( Incidence::List::ConstIterator it=list.constBegin(), end=list.constEnd();
+ it != end; ++it ) {
+ if ( (*it)->schedulingID() == incidence->uid() ) {
+ ev = ( *it ).dynamicCast<Event>();
+ to = ( *it ).dynamicCast<Todo>();
+ break;
+ }
+ }
+ }
+
+ if ( ev || to ) {
+ //get matching attendee in calendar
+ kDebug() << "match found!";
+ Attendee::List attendeesIn = incidence->attendees();
+ Attendee::List attendeesEv;
+ Attendee::List attendeesNew;
+ if ( ev ) {
+ attendeesEv = ev->attendees();
+ }
+ if ( to ) {
+ attendeesEv = to->attendees();
+ }
+ Attendee::List::ConstIterator inIt;
+ Attendee::List::ConstIterator evIt;
+ for ( inIt = attendeesIn.constBegin(); inIt != attendeesIn.constEnd(); ++inIt ) {
+ Attendee::Ptr attIn = *inIt;
+ bool found = false;
+ for ( evIt = attendeesEv.constBegin(); evIt != attendeesEv.constEnd(); ++evIt ) {
+ Attendee::Ptr attEv = *evIt;
+ if ( attIn->email().toLower() == attEv->email().toLower() ) {
+ //update attendee-info
+ kDebug() << "update attendee";
+ attEv->setStatus( attIn->status() );
+ attEv->setDelegate( attIn->delegate() );
+ attEv->setDelegator( attIn->delegator() );
+ ret = true;
+ found = true;
+ }
+ }
+ if ( !found && attIn->status() != Attendee::Declined ) {
+ attendeesNew.append( attIn );
+ }
+ }
+
+ bool attendeeAdded = false;
+ for ( Attendee::List::ConstIterator it = attendeesNew.constBegin();
+ it != attendeesNew.constEnd(); ++it ) {
+ Attendee::Ptr attNew = *it;
+ QString msg =
+ i18nc( "@info", "%1 wants to attend %2 but was not invited.",
+ attNew->fullName(),
+ ( ev ? ev->summary() : to->summary() ) );
+ if ( !attNew->delegator().isEmpty() ) {
+ msg = i18nc( "@info", "%1 wants to attend %2 on behalf of %3.",
+ attNew->fullName(),
+ ( ev ? ev->summary() : to->summary() ), attNew->delegator() );
+ }
+ if ( KMessageBox::questionYesNo(
+ 0, msg, i18nc( "@title", "Uninvited attendee" ),
+ KGuiItem( i18nc( "@option", "Accept Attendance" ) ),
+ KGuiItem( i18nc( "@option", "Reject Attendance" ) ) ) != KMessageBox::Yes ) {
+ Incidence::Ptr cancel = incidence.dynamicCast<Incidence>();
+ if ( cancel ) {
+ cancel->addComment(
+ i18nc( "@info",
+ "The organizer rejected your attendance at this meeting." ) );
+ }
+ performTransaction( incidence, iTIPCancel, attNew->fullName() );
+ // ### can't delete cancel here because it is aliased to incidence which
+ // is accessed in the next loop iteration (CID 4232)
+ // delete cancel;
+ continue;
+ }
+
+ Attendee::Ptr a( new Attendee( attNew->name(), attNew->email(), attNew->RSVP(),
+ attNew->status(), attNew->role(), attNew->uid() ) );
+
+ a->setDelegate( attNew->delegate() );
+ a->setDelegator( attNew->delegator() );
+ if ( ev ) {
+ ev->addAttendee( a );
+ } else if ( to ) {
+ to->addAttendee( a );
+ }
+ ret = true;
+ attendeeAdded = true;
+ }
+
+ // send update about new participants
+ if ( attendeeAdded ) {
+ bool sendMail = false;
+ if ( ev || to ) {
+ if ( KMessageBox::questionYesNo(
+ 0,
+ i18nc( "@info",
+ "An attendee was added to the incidence. "
+ "Do you want to email the attendees an update message?" ),
+ i18nc( "@title", "Attendee Added" ),
+ KGuiItem( i18nc( "@option", "Send Messages" ) ),
+ KGuiItem( i18nc( "@option", "Do Not Send" ) ) ) == KMessageBox::Yes ) {
+ sendMail = true;
+ }
+ }
+
+ if ( ev ) {
+ ev->setRevision( ev->revision() + 1 );
+ if ( sendMail ) {
+ performTransaction( ev, iTIPRequest );
+ }
+ }
+ if ( to ) {
+ to->setRevision( to->revision() + 1 );
+ if ( sendMail ) {
+ performTransaction( to, iTIPRequest );
+ }
+ }
+ }
+
+ if ( ret ) {
+ // We set at least one of the attendees, so the incidence changed
+ // Note: This should not result in a sequence number bump
+ if ( ev ) {
+ ev->updated();
+ } else if ( to ) {
+ to->updated();
+ }
+ }
+ if ( to ) {
+ // for VTODO a REPLY can be used to update the completion status of
+ // a to-do. see RFC2446 3.4.3
+ Todo::Ptr update = incidence.dynamicCast<Todo>();
+ Q_ASSERT( update );
+ if ( update && ( to->percentComplete() != update->percentComplete() ) ) {
+ to->setPercentComplete( update->percentComplete() );
+ to->updated();
+ }
+ }
+ } else {
+ kError() << "No incidence for scheduling.";
+ }
+
+ if ( ret ) {
+ deleteTransaction( incidence );
+ }
+ return ret;
+}
+
+bool Scheduler::acceptRefresh( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
+{
+ Q_UNUSED( status );
+ // handled in korganizer's IncomingDialog
+ deleteTransaction( incidence );
+ return false;
+}
+
+bool Scheduler::acceptCounter( const IncidenceBase::Ptr &incidence, ScheduleMessage::Status status )
+{
+ Q_UNUSED( status );
+ deleteTransaction( incidence );
+ return false;
+}
+
+bool Scheduler::acceptFreeBusy( const IncidenceBase::Ptr &incidence, iTIPMethod method )
+{
+ if ( !d->mFreeBusyCache ) {
+ kError() << "Scheduler: no FreeBusyCache.";
+ return false;
+ }
+
+ FreeBusy::Ptr freebusy = incidence.staticCast<FreeBusy>();
+
+ kDebug() << "freeBusyDirName:" << freeBusyDir();
+
+ Person::Ptr from;
+ if( method == iTIPPublish ) {
+ from = freebusy->organizer();
+ }
+ if ( ( method == iTIPReply ) && ( freebusy->attendeeCount() == 1 ) ) {
+ Attendee::Ptr attendee = freebusy->attendees().first();
+ from->setName( attendee->name() );
+ from->setEmail( attendee->email() );
+ }
+
+ if ( !d->mFreeBusyCache->saveFreeBusy( freebusy, from ) ) {
+ return false;
+ }
+
+ deleteTransaction( incidence );
+ return true;
+}
diff --git a/kcalutils/scheduler.h b/kcalutils/scheduler.h
new file mode 100644
index 0000000..50220ed
--- /dev/null
+++ b/kcalutils/scheduler.h
@@ -0,0 +1,161 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KCALUTILS_SCHEDULER_H
+#define KCALUTILS_SCHEDULER_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/schedulemessage.h>
+#include <kcalcore/incidencebase.h>
+#include <kcalcore/calendar.h>
+
+#include <QtCore/QString>
+#include <QtCore/QList>
+
+namespace KCalCore {
+ class ICalFormat;
+ class FreeBusyCache;
+}
+
+namespace KCalUtils {
+/**
+ This class provides an encapsulation of iTIP transactions (RFC 2446).
+ It is an abstract base class for inheritance by implementations of the
+ iTIP scheme like iMIP or iRIP.
+*/
+class KCALUTILS_EXPORT Scheduler
+{
+ public:
+ /**
+ Creates a scheduler for calendar specified as argument.
+ */
+ explicit Scheduler( const KCalCore::Calendar::Ptr &calendar );
+ virtual ~Scheduler();
+
+ /**
+ iTIP publish action
+ */
+ virtual bool publish( const KCalCore::IncidenceBase::Ptr &incidence,
+ const QString &recipients ) = 0;
+ /**
+ Performs iTIP transaction on incidence. The method is specified as the
+ method argument and can be any valid iTIP method.
+
+ @param incidence the incidence for the transaction.
+ @param method the iTIP transaction method to use.
+ */
+ virtual bool performTransaction( const KCalCore::IncidenceBase::Ptr &incidence,
+ KCalCore::iTIPMethod method ) = 0;
+
+ /**
+ Performs iTIP transaction on incidence to specified recipient(s).
+ The method is specified as the method argumanet and can be any valid iTIP method.
+
+ @param incidence the incidence for the transaction.
+ @param method the iTIP transaction method to use.
+ @param recipients the receipients of the transaction.
+ */
+ virtual bool performTransaction( const KCalCore::IncidenceBase::Ptr &incidence,
+ KCalCore::iTIPMethod method, const QString &recipients ) = 0;
+
+ /**
+ Retrieves incoming iTIP transactions.
+ */
+ //KDAB_TODO PTR
+ virtual QList<KCalCore::ScheduleMessage*> retrieveTransactions() = 0;
+
+ /**
+ Accepts the transaction. The incidence argument specifies the iCal
+ component on which the transaction acts. The status is the result of
+ processing a iTIP message with the current calendar and specifies the
+ action to be taken for this incidence.
+
+ @param incidence the incidence for the transaction.
+ @param method iTIP transaction method to check.
+ @param status scheduling status.
+ @param email the email address of the person for whom this
+ transaction is to be performed.
+ */
+ bool acceptTransaction( const KCalCore::IncidenceBase::Ptr &incidence,
+ KCalCore::iTIPMethod method,
+ KCalCore::ScheduleMessage::Status status,
+ const QString &email = QString() );
+
+ virtual bool deleteTransaction( const KCalCore::IncidenceBase::Ptr &incidence );
+
+ /**
+ Returns the directory where the free-busy information is stored.
+ */
+ virtual QString freeBusyDir() = 0;
+
+ /**
+ Sets the free/busy cache used to store free/busy information.
+ */
+ void setFreeBusyCache( KCalCore::FreeBusyCache * );
+
+ /**
+ Returns the free/busy cache.
+ */
+ KCalCore::FreeBusyCache *freeBusyCache() const;
+
+ protected:
+ bool acceptPublish( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status,
+ KCalCore::iTIPMethod method );
+
+ bool acceptRequest( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status,
+ const QString &email );
+
+ bool acceptAdd( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status );
+
+ bool acceptCancel( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status,
+ const QString &attendee );
+
+ bool acceptDeclineCounter( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status );
+
+ bool acceptReply( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status,
+ KCalCore::iTIPMethod method );
+
+ bool acceptRefresh( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status );
+
+ bool acceptCounter( const KCalCore::IncidenceBase::Ptr &,
+ KCalCore::ScheduleMessage::Status status );
+
+ bool acceptFreeBusy( const KCalCore::IncidenceBase::Ptr &, KCalCore::iTIPMethod method );
+
+ KCalCore::Calendar::Ptr mCalendar;
+ KCalCore::ICalFormat *mFormat;
+
+ private:
+ Q_DISABLE_COPY( Scheduler )
+ struct Private;
+ Private *const d;
+};
+
+}
+
+#endif
diff --git a/kcalutils/stringify.cpp b/kcalutils/stringify.cpp
new file mode 100644
index 0000000..d0a75fa
--- /dev/null
+++ b/kcalutils/stringify.cpp
@@ -0,0 +1,384 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2005 Rafal Rzepecki <divide@users.sourceforge.net>
+ Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and provides
+ static functions for formatting Incidence properties for various purposes.
+
+ @brief
+ Provides methods to format Incidence properties in various ways for display purposes.
+
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+ @author Allen Winter \<allen@kdab.com\>
+*/
+#include "stringify.h"
+
+#include <kcalcore/exceptions.h>
+using namespace KCalCore;
+
+#include <KLocale>
+#include <KGlobal>
+#include <KSystemTimeZone>
+
+using namespace KCalUtils;
+using namespace Stringify;
+
+QString Stringify::incidenceType( Incidence::IncidenceType type )
+{
+ switch( type ) {
+ case Incidence::TypeEvent:
+ return i18nc( "@item incidence type is event", "event" );
+ case Incidence::TypeTodo:
+ return i18nc( "@item incidence type is to-do/task", "to-do" );
+ case Incidence::TypeJournal:
+ return i18nc( "@item incidence type is journal", "journal" );
+ case Incidence::TypeFreeBusy:
+ return i18nc( "@item incidence type is freebusy", "free/busy" );
+ default:
+ return QString();
+ }
+}
+
+QString Stringify::todoCompletedDateTime( const Todo::Ptr &todo,
+ bool shortfmt )
+{
+ return KGlobal::locale()->formatDateTime( todo->completed().dateTime(),
+ ( shortfmt ? KLocale::ShortDate :
+ KLocale::LongDate ) );
+}
+
+QString Stringify::incidenceSecrecy( Incidence::Secrecy secrecy )
+{
+ switch ( secrecy ) {
+ case Incidence::SecrecyPublic:
+ return i18nc( "@item incidence access if for everyone", "Public" );
+ case Incidence::SecrecyPrivate:
+ return i18nc( "@item incidence access is by owner only", "Private" );
+ case Incidence::SecrecyConfidential:
+ return i18nc( "@item incidence access is by owner and a controlled group", "Confidential" );
+ default: // to make compiler happy
+ return QString();
+ }
+}
+
+QStringList Stringify::incidenceSecrecyList()
+{
+ QStringList list;
+ list << incidenceSecrecy( Incidence::SecrecyPublic );
+ list << incidenceSecrecy( Incidence::SecrecyPrivate );
+ list << incidenceSecrecy( Incidence::SecrecyConfidential );
+
+ return list;
+}
+
+QString Stringify::incidenceStatus( Incidence::Status status )
+{
+ switch ( status ) {
+ case Incidence::StatusTentative:
+ return i18nc( "@item event is tentative", "Tentative" );
+ case Incidence::StatusConfirmed:
+ return i18nc( "@item event is definite", "Confirmed" );
+ case Incidence::StatusCompleted:
+ return i18nc( "@item to-do is complete", "Completed" );
+ case Incidence::StatusNeedsAction:
+ return i18nc( "@item to-do needs action", "Needs-Action" );
+ case Incidence::StatusCanceled:
+ return i18nc( "@item event orto-do is canceled; journal is removed", "Canceled" );
+ case Incidence::StatusInProcess:
+ return i18nc( "@item to-do is in process", "In-Process" );
+ case Incidence::StatusDraft:
+ return i18nc( "@item journal is in draft form", "Draft" );
+ case Incidence::StatusFinal:
+ return i18nc( "@item journal is in final form", "Final" );
+ case Incidence::StatusX:
+ case Incidence::StatusNone:
+ default:
+ return QString();
+ }
+}
+
+QString Stringify::incidenceStatus( const Incidence::Ptr &incidence )
+{
+ if ( incidence->status() == Incidence::StatusX ) {
+ return incidence->customStatus();
+ } else {
+ return incidenceStatus( incidence->status() );
+ }
+}
+
+QString Stringify::attendeeRole( Attendee::Role role )
+{
+ switch ( role ) {
+ case Attendee::Chair:
+ return i18nc( "@item chairperson", "Chair" );
+ break;
+ default:
+ case Attendee::ReqParticipant:
+ return i18nc( "@item participation is required", "Participant" );
+ break;
+ case Attendee::OptParticipant:
+ return i18nc( "@item participation is optional", "Optional Participant" );
+ break;
+ case Attendee::NonParticipant:
+ return i18nc( "@item non-participant copied for information", "Observer" );
+ break;
+ }
+}
+
+QStringList Stringify::attendeeRoleList()
+{
+ QStringList list;
+ list << attendeeRole( Attendee::ReqParticipant );
+ list << attendeeRole( Attendee::OptParticipant );
+ list << attendeeRole( Attendee::NonParticipant );
+ list << attendeeRole( Attendee::Chair );
+
+ return list;
+}
+
+QString Stringify::attendeeStatus( Attendee::PartStat status )
+{
+ switch ( status ) {
+ default:
+ case Attendee::NeedsAction:
+ return i18nc( "@item event, to-do or journal needs action", "Needs Action" );
+ break;
+ case Attendee::Accepted:
+ return i18nc( "@item event, to-do or journal accepted", "Accepted" );
+ break;
+ case Attendee::Declined:
+ return i18nc( "@item event, to-do or journal declined", "Declined" );
+ break;
+ case Attendee::Tentative:
+ return i18nc( "@item event or to-do tentatively accepted", "Tentative" );
+ break;
+ case Attendee::Delegated:
+ return i18nc( "@item event or to-do delegated", "Delegated" );
+ break;
+ case Attendee::Completed:
+ return i18nc( "@item to-do completed", "Completed" );
+ break;
+ case Attendee::InProcess:
+ return i18nc( "@item to-do in process of being completed", "In Process" );
+ break;
+ case Attendee::None:
+ return i18nc( "@item event or to-do status unknown", "Unknown" );
+ break;
+ }
+}
+
+QStringList Stringify::attendeeStatusList()
+{
+ QStringList list;
+ list << attendeeStatus( Attendee::NeedsAction );
+ list << attendeeStatus( Attendee::Accepted );
+ list << attendeeStatus( Attendee::Declined );
+ list << attendeeStatus( Attendee::Tentative );
+ list << attendeeStatus( Attendee::Delegated );
+ list << attendeeStatus( Attendee::Completed );
+ list << attendeeStatus( Attendee::InProcess );
+
+ return list;
+}
+
+QString Stringify::formatTime( const KDateTime &dt, bool shortfmt, const KDateTime::Spec &spec )
+{
+ if ( spec.isValid() ) {
+
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return KGlobal::locale()->formatTime( dt.toTimeSpec( spec ).time(), !shortfmt ) + timeZone;
+ } else {
+ return KGlobal::locale()->formatTime( dt.time(), !shortfmt );
+ }
+}
+
+QString Stringify::formatDate( const KDateTime &dt, bool shortfmt, const KDateTime::Spec &spec )
+{
+ if ( spec.isValid() ) {
+
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return
+ KGlobal::locale()->formatDate( dt.toTimeSpec( spec ).date(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) +
+ timeZone;
+ } else {
+ return
+ KGlobal::locale()->formatDate( dt.date(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
+ }
+}
+
+QString Stringify::formatDateTime( const KDateTime &dt, bool allDay,
+ bool shortfmt, const KDateTime::Spec &spec )
+{
+ if ( allDay ) {
+ return formatDate( dt, shortfmt, spec );
+ }
+
+ if ( spec.isValid() ) {
+ QString timeZone;
+ if ( spec.timeZone() != KSystemTimeZones::local() ) {
+ timeZone = ' ' + spec.timeZone().name();
+ }
+
+ return KGlobal::locale()->formatDateTime(
+ dt.toTimeSpec( spec ).dateTime(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) ) + timeZone;
+ } else {
+ return KGlobal::locale()->formatDateTime(
+ dt.dateTime(),
+ ( shortfmt ? KLocale::ShortDate : KLocale::LongDate ) );
+ }
+}
+
+QString Stringify::errorMessage( const Exception &exception )
+{
+ QString message = "";
+
+ switch ( exception.code() ) {
+ case Exception::LoadError:
+ message = i18nc( "@item", "Load Error" );
+ break;
+ case Exception::SaveError:
+ message = i18nc( "@item", "Save Error" );
+ break;
+ case Exception::ParseErrorIcal:
+ message = i18nc( "@item", "Parse Error in libical" );
+ break;
+ case Exception::ParseErrorKcal:
+ message = i18nc( "@item", "Parse Error in the kcalcore library" );
+ break;
+ case Exception::NoCalendar:
+ message = i18nc( "@item", "No calendar component found." );
+ break;
+ case Exception::CalVersion1:
+ message = i18nc( "@item", "Expected iCalendar, got vCalendar format" );
+ break;
+ case Exception::CalVersion2:
+ message = i18nc( "@item", "iCalendar Version 2.0 detected." );
+ break;
+ case Exception::CalVersionUnknown:
+ message = i18nc( "@item", "Expected iCalendar, got unknown format" );
+ break;
+ case Exception::Restriction:
+ message = i18nc( "@item", "Restriction violation" );
+ break;
+ case Exception::NoWritableFound:
+ message = i18nc( "@item", "No writable resource found" );
+ break;
+ case Exception::SaveErrorOpenFile:
+ Q_ASSERT( exception.arguments().count() == 1 );
+ message = i18nc( "@item", "Error saving to '%1'.", exception.arguments()[0] );
+ break;
+ case Exception::SaveErrorSaveFile:
+ Q_ASSERT( exception.arguments().count() == 1 );
+ message = i18nc( "@item", "Could not save '%1'", exception.arguments()[0] );
+ break;
+ case Exception::LibICalError:
+ message = i18nc( "@item", "libical error" );
+ break;
+ case Exception::VersionPropertyMissing:
+ message = i18nc( "@item", "No VERSION property found" );
+ break;
+ case Exception::ExpectedCalVersion2:
+ message = i18nc( "@item", "Expected iCalendar, got vCalendar format" );
+ break;
+ case Exception::ExpectedCalVersion2Unknown:
+ message = i18nc( "@item", "Expected iCalendar, got unknown format" );
+ break;
+ case Exception::ParseErrorNotIncidence:
+ message = i18nc( "@item", "object is not a freebusy, event, todo or journal" );
+ break;
+ case Exception::ParseErrorEmptyMessage:
+ message = i18nc( "@item", "messageText is empty, unable to parse into a ScheduleMessage" );
+ break;
+ case Exception::ParseErrorUnableToParse:
+ message = i18nc( "@item", "icalparser is unable to parse messageText into a ScheduleMessage" );
+ break;
+ case Exception::ParseErrorMethodProperty:
+ message = i18nc( "@item", "message does not contain ICAL_METHOD_PROPERTY" );
+ break;
+ case Exception::UserCancel:
+ // no real error; the user canceled the operation
+ break;
+
+ }
+
+ return message;
+}
+
+QString Stringify::scheduleMessageStatus( ScheduleMessage::Status status )
+{
+ switch( status ) {
+ case ScheduleMessage::PublishNew:
+ return i18nc( "@item this is a new scheduling message",
+ "New Scheduling Message" );
+ case ScheduleMessage::PublishUpdate:
+ return i18nc( "@item this is an update to an existing scheduling message",
+ "Updated Scheduling Message" );
+ case ScheduleMessage::Obsolete:
+ return i18nc( "@item obsolete status", "Obsolete" );
+ case ScheduleMessage::RequestNew:
+ return i18nc( "@item this is a request for a new scheduling message",
+ "New Scheduling Message Request" );
+ case ScheduleMessage::RequestUpdate:
+ return i18nc( "@item this is a request for an update to an existing scheduling message",
+ "Updated Scheduling Message Request" );
+ default:
+ return i18nc( "@item unknown status", "Unknown Status: %1", int( status ) );
+ }
+}
+
+QString Stringify::secrecyName( Incidence::Secrecy secrecy )
+{
+ switch ( secrecy ) {
+ case Incidence::SecrecyPublic:
+ return i18nc( "@item incidence access if for everyone", "Public" );
+ case Incidence::SecrecyPrivate:
+ return i18nc( "@item incidence access is by owner only", "Private" );
+ case Incidence::SecrecyConfidential:
+ return i18nc( "@item incidence access is by owner and a controlled group", "Confidential" );
+ default:
+ return QString(); // to make compilers happy
+ }
+}
+
+QStringList Stringify::secrecyList()
+{
+ QStringList list;
+ list << secrecyName( Incidence::SecrecyPublic );
+ list << secrecyName( Incidence::SecrecyPrivate );
+ list << secrecyName( Incidence::SecrecyConfidential );
+
+ return list;
+}
diff --git a/kcalutils/stringify.h b/kcalutils/stringify.h
new file mode 100644
index 0000000..307e86c
--- /dev/null
+++ b/kcalutils/stringify.h
@@ -0,0 +1,129 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
+ Copyright (c) 2004 Reinhold Kainhofer <reinhold@kainhofer.com>
+ Copyright (c) 2009-2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/**
+ @file
+ This file is part of the API for handling calendar data and provides
+ static functions for formatting Incidence properties for various purposes.
+
+ @author Cornelius Schumacher \<schumacher@kde.org\>
+ @author Reinhold Kainhofer \<reinhold@kainhofer.com\>
+ @author Allen Winter \<allen@kdab.com\>
+*/
+#ifndef KCALUTILS_STRINGIFY_H
+#define KCALUTILS_STRINGIFY_H
+
+#include "kcalutils_export.h"
+
+#include <kcalcore/schedulemessage.h>
+#include <kcalcore/todo.h>
+
+namespace KCalCore {
+ class Exception;
+}
+
+namespace KCalUtils {
+
+/**
+ @brief
+ Provides methods to format Incidence properties in various ways for display purposes.
+*/
+namespace Stringify
+{
+ KCALUTILS_EXPORT QString incidenceType( KCalCore::Incidence::IncidenceType type );
+
+ /**
+ Returns the incidence Secrecy as translated string.
+ @see incidenceSecrecyList().
+ */
+ KCALUTILS_EXPORT QString incidenceSecrecy( KCalCore::Incidence::Secrecy secrecy );
+
+ /**
+ Returns a list of all available Secrecy types as a list of translated strings.
+ @see incidenceSecrecy().
+ */
+ KCALUTILS_EXPORT QStringList incidenceSecrecyList();
+
+ KCALUTILS_EXPORT QString incidenceStatus( KCalCore::Incidence::Status status );
+ KCALUTILS_EXPORT QString incidenceStatus( const KCalCore::Incidence::Ptr &incidence );
+ KCALUTILS_EXPORT QString scheduleMessageStatus( KCalCore::ScheduleMessage::Status status );
+
+ /**
+ Returns string containing the date/time when the to-do was completed,
+ formatted according to the user's locale settings.
+ @param shortfmt If true, use a short date format; else use a long format.
+ */
+ KCALUTILS_EXPORT QString todoCompletedDateTime( const KCalCore::Todo::Ptr &todo,
+ bool shortfmt = false );
+
+ KCALUTILS_EXPORT QString attendeeRole( KCalCore::Attendee::Role role );
+ KCALUTILS_EXPORT QStringList attendeeRoleList();
+ KCALUTILS_EXPORT QString attendeeStatus( KCalCore::Attendee::PartStat status );
+ KCALUTILS_EXPORT QStringList attendeeStatusList();
+
+ /**
+ Build a QString time representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see formatDate(), formatDateTime().
+ */
+ KCALUTILS_EXPORT QString formatTime( const KDateTime &dt, bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Build a QString date representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see formatDate(), formatDateTime().
+ */
+ KCALUTILS_EXPORT QString formatDate( const KDateTime &dt, bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Build a QString date/time representation of a KDateTime object.
+ @param date The date to be formatted.
+ @param dateOnly If true, don't print the time fields; print the date fields only.
+ @param shortfmt If true, display info in short format.
+ @param spec Time spec to use.
+ @see formatDate(), formatTime().
+ */
+ KCALUTILS_EXPORT QString formatDateTime( const KDateTime &dt,
+ bool dateOnly = false,
+ bool shortfmt = true,
+ const KDateTime::Spec &spec = KDateTime::Spec() );
+
+ /**
+ Build a translated message representing an exception
+ */
+ KCALUTILS_EXPORT QString errorMessage( const KCalCore::Exception &exception );
+
+ KCALUTILS_EXPORT QString secrecyName( KCalCore::Incidence::Secrecy secrecy );
+
+ KCALUTILS_EXPORT QStringList secrecyList();
+
+} // namespace Stringify
+
+} //namespace KCalUtils
+
+#endif
diff --git a/kcalutils/tests/CMakeLists.txt b/kcalutils/tests/CMakeLists.txt
new file mode 100644
index 0000000..69c20b2
--- /dev/null
+++ b/kcalutils/tests/CMakeLists.txt
@@ -0,0 +1,14 @@
+set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
+
+MACRO(KCALUTILS_UNIT_TESTS)
+ FOREACH(_testname ${ARGN})
+ kde4_add_unit_test(${_testname} NOGUI ${_testname}.cpp)
+ target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} kcalutils kcalcore ${QT_QTTEST_LIBRARY} ${QT_QTGUI_LIBRARY})
+ ENDFOREACH(_testname)
+ENDMACRO(KCALUTILS_UNIT_TESTS)
+
+KCALUTILS_UNIT_TESTS(
+ testdndfactory
+ testincidenceformatter
+ teststringify
+)
diff --git a/kcalutils/tests/testdndfactory.cpp b/kcalutils/tests/testdndfactory.cpp
new file mode 100644
index 0000000..234bfc5
--- /dev/null
+++ b/kcalutils/tests/testdndfactory.cpp
@@ -0,0 +1,162 @@
+/*
+ This file is part of the kcalcore library.
+
+ Copyright (c) 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+ Author: Srgio Martins <sergio.martins@kdab.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "teststringify.h"
+#include "testdndfactory.moc"
+
+#include "../dndfactory.h"
+
+#include <kcalcore/memorycalendar.h>
+
+#include <KDebug>
+#include <qtest_kde.h>
+
+
+QTEST_KDEMAIN( DndFactoryTest, GUI ) // clipboard() needs GUI
+
+using namespace KCalCore;
+using namespace KCalUtils;
+
+void DndFactoryTest::testPasteAllDayEvent()
+{
+
+ MemoryCalendar::Ptr calendar( new MemoryCalendar( QString() ) );
+
+ DndFactory factory( calendar );
+
+ Event::Ptr allDayEvent( new Event() );
+ allDayEvent->setSummary( QLatin1String( "Summary 1" ) );
+ allDayEvent->setDtStart( KDateTime( QDate( 2010, 8, 8 ) ) );
+ allDayEvent->setDtEnd( KDateTime( QDate( 2010, 8, 9 ) ) );
+ const QString originalUid = allDayEvent->uid();
+ const bool originalIsAllDay = allDayEvent->allDay();
+
+ Incidence::List incidencesToPaste;
+ incidencesToPaste.append( allDayEvent );
+
+ QVERIFY( factory.copyIncidences( incidencesToPaste ) );
+
+ Incidence::List pastedIncidences = factory.pasteIncidences();
+ QVERIFY( pastedIncidences.size() == 1 );
+
+ Incidence::Ptr incidence = pastedIncidences.first();
+
+ QVERIFY( incidence->type() == Incidence::TypeEvent );
+
+ // check if a new uid was generated.
+ QVERIFY( incidence->uid() != originalUid );
+
+ // we passed an invalid KDateTime to pasteIncidences() so dates don't change.
+ QVERIFY( incidence->allDay() == originalIsAllDay );
+
+ Event::Ptr pastedEvent = incidence.staticCast<Event>();
+
+ QVERIFY( pastedEvent->dtStart() == allDayEvent->dtStart() );
+ QVERIFY( pastedEvent->dtEnd() == allDayEvent->dtEnd() );
+ QVERIFY( pastedEvent->summary() == allDayEvent->summary() );
+}
+
+void DndFactoryTest::testPasteAllDayEvent2()
+{
+
+ MemoryCalendar::Ptr calendar( new MemoryCalendar( QString() ) );
+
+ DndFactory factory( calendar );
+
+ Event::Ptr allDayEvent( new Event() );
+ allDayEvent->setSummary( QLatin1String( "Summary 2" ) );
+ allDayEvent->setDtStart( KDateTime( QDate( 2010, 8, 8 ) ) );
+ allDayEvent->setDtEnd( KDateTime( QDate( 2010, 8, 9 ) ) );
+ const QString originalUid = allDayEvent->uid();
+
+ Incidence::List incidencesToPaste;
+ incidencesToPaste.append( allDayEvent );
+
+ QVERIFY( factory.copyIncidences( incidencesToPaste ) );
+
+ const KDateTime newDateTime( QDate( 2011, 1, 1 ) );
+ const uint originalLength = allDayEvent->dtStart().secsTo( allDayEvent->dtEnd() );
+
+ // paste at the new time
+ Incidence::List pastedIncidences = factory.pasteIncidences( newDateTime );
+
+ // we only copied one incidence
+ QVERIFY( pastedIncidences.size() == 1 );
+
+ Incidence::Ptr incidence = pastedIncidences.first();
+
+ QVERIFY( incidence->type() == Incidence::TypeEvent );
+
+ // check if a new uid was generated.
+ QVERIFY( incidence->uid() != originalUid );
+
+ // the new dateTime didn't have time component
+ QVERIFY( incidence->allDay() );
+
+ Event::Ptr pastedEvent = incidence.staticCast<Event>();
+ const uint newLength = pastedEvent->dtStart().secsTo( pastedEvent->dtEnd() );
+
+ kDebug() << "originalLength was " << originalLength << "; and newLength is "
+ << newLength << "; old dtStart was " << allDayEvent->dtStart()
+ << " and old dtEnd was " << allDayEvent->dtEnd() << endl
+ << "; new dtStart is " << pastedEvent->dtStart()
+ << " and new dtEnd is " << pastedEvent->dtEnd();
+
+ QVERIFY( originalLength == newLength );
+ QVERIFY( pastedEvent->dtStart() == newDateTime );
+ QVERIFY( pastedEvent->summary() == allDayEvent->summary() );
+}
+
+void DndFactoryTest::testPasteTodo()
+{
+ MemoryCalendar::Ptr calendar( new MemoryCalendar( QString() ) );
+
+ DndFactory factory( calendar );
+
+ Todo::Ptr todo( new Todo() );
+ todo->setSummary( QLatin1String( "Summary 1" ) );
+ todo->setDtDue( KDateTime( QDate( 2010, 8, 9 ) ) );
+
+ Incidence::List incidencesToPaste;
+ incidencesToPaste.append( todo );
+
+ QVERIFY( factory.copyIncidences( incidencesToPaste ) );
+
+ const KDateTime newDateTime( QDate( 2011, 1, 1 ), QTime( 10, 10 ) );
+
+ Incidence::List pastedIncidences = factory.pasteIncidences( newDateTime );
+ QVERIFY( pastedIncidences.size() == 1 );
+
+ Incidence::Ptr incidence = pastedIncidences.first();
+
+ QVERIFY( incidence->type() == Incidence::TypeTodo );
+
+ // check if a new uid was generated.
+ QVERIFY( incidence->uid() != todo->uid() );
+
+ Todo::Ptr pastedTodo = incidence.staticCast<Todo>();
+
+ QVERIFY( pastedTodo->dtDue() == newDateTime );
+ QVERIFY( pastedTodo->summary() == todo->summary() );
+
+}
+
diff --git a/kcalutils/tests/testdndfactory.h b/kcalutils/tests/testdndfactory.h
new file mode 100644
index 0000000..c5d6d5c
--- /dev/null
+++ b/kcalutils/tests/testdndfactory.h
@@ -0,0 +1,54 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+ Author: Srgio Martins <sergio.martins@kdab.com>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TESTDNDFACTORY_H
+#define TESTDNDFACTORY_H
+
+#include <QtCore/QObject>
+
+class DndFactoryTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+
+ /** Pastes an event without time component (all day). We don't specify a new date/time to
+ DndFactory::pasteIncidences(), so dates of the pasted incidence should be the same as
+ the copied incidence */
+ void testPasteAllDayEvent();
+
+
+ /** Pastes an event without time component (all day). We specify a new date/time to
+ DndFactory::pasteIncidences(), so dates of the pasted incidence should be different than
+ the copied incidence */
+ void testPasteAllDayEvent2();
+
+ /** Pastes to-do at a given date/time, should change due-date.
+ */
+ void testPasteTodo();
+
+ /** Things that need testing:
+ - Paste to-do, changing dtStart instead of dtDue.
+ - ...
+ */
+};
+
+#endif
diff --git a/kcalutils/tests/testincidenceformatter.cpp b/kcalutils/tests/testincidenceformatter.cpp
new file mode 100644
index 0000000..ce1fbff
--- /dev/null
+++ b/kcalutils/tests/testincidenceformatter.cpp
@@ -0,0 +1,122 @@
+/*
+ This file is part of the kcalcore library.
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "testincidenceformatter.h"
+#include "testincidenceformatter.moc"
+#include "../incidenceformatter.h"
+
+#include <kcalcore/event.h>
+
+#include <KDateTime>
+#include <KLocale>
+
+#include <QDebug>
+#include <qtest_kde.h>
+QTEST_KDEMAIN( IncidenceFormatterTest, NoGUI )
+
+using namespace KCalCore;
+using namespace KCalUtils;
+
+void IncidenceFormatterTest::testRecurrenceString()
+{
+ // TEST: A daily recurrence with date exclusions //
+ Event::Ptr e1 = Event::Ptr( new Event() );
+
+ QDate day( 2010, 10, 3 );
+ QTime tim( 12, 0, 0 );
+ KDateTime kdt( day, tim, KDateTime::UTC );
+ e1->setDtStart( kdt );
+ e1->setDtEnd( kdt.addSecs( 60 * 60 ) ); // 1hr event
+
+ QVERIFY( IncidenceFormatter::recurrenceString( e1 ) == i18n( "No recurrence" ) );
+
+ Recurrence *r1 = e1->recurrence();
+
+ r1->setDaily( 1 );
+ r1->setEndDateTime( kdt.addDays( 5 ) ); // ends 5 days from now
+ QVERIFY( IncidenceFormatter::recurrenceString( e1 ) ==
+ i18n( "Recurs daily until 2010-10-08 12:00" ) );
+
+ r1->setFrequency( 2 );
+
+ QVERIFY( IncidenceFormatter::recurrenceString( e1 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00" ) );
+
+ r1->addExDate( kdt.addDays( 1 ).date() );
+ QVERIFY( IncidenceFormatter::recurrenceString( e1 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00 (excluding 2010-10-04)" ) );
+
+ r1->addExDate( kdt.addDays( 3 ).date() );
+ QVERIFY( IncidenceFormatter::recurrenceString( e1 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00 (excluding 2010-10-04,2010-10-06)" ) );
+
+ // TEST: An daily recurrence, with datetime exclusions //
+ Event::Ptr e2 = Event::Ptr( new Event() );
+ e2->setDtStart( kdt );
+ e2->setDtEnd( kdt.addSecs( 60 * 60 ) ); // 1hr event
+
+ Recurrence *r2 = e2->recurrence();
+
+ r2->setDaily( 1 );
+ r2->setEndDate( kdt.addDays( 5 ).date() ); // ends 5 days from now
+ QVERIFY( IncidenceFormatter::recurrenceString( e2 ) ==
+ i18n( "Recurs daily until 2010-10-08 12:00" ) );
+
+ r2->setFrequency( 2 );
+
+ QVERIFY( IncidenceFormatter::recurrenceString( e2 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00" ) );
+
+ r2->addExDateTime( kdt.addDays( 1 ) );
+ QVERIFY( IncidenceFormatter::recurrenceString( e2 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00 (excluding 2010-10-04)" ) );
+
+ r2->addExDate( kdt.addDays( 3 ).date() );
+ QVERIFY( IncidenceFormatter::recurrenceString( e2 ) ==
+ i18n( "Recurs every 2 days until 2010-10-08 12:00 (excluding 2010-10-04,2010-10-06)" ) );
+
+ // TEST: An hourly recurrence, with exclusions //
+ Event::Ptr e3 = Event::Ptr( new Event() );
+ e3->setDtStart( kdt );
+ e3->setDtEnd( kdt.addSecs( 60 * 60 ) ); // 1hr event
+
+ Recurrence *r3 = e3->recurrence();
+
+ r3->setHourly( 1 );
+ r3->setEndDateTime( kdt.addSecs( 5 * 60 * 60 ) ); // ends 5 hrs from now
+ QVERIFY( IncidenceFormatter::recurrenceString( e3 ) ==
+ i18n( "Recurs hourly until 2010-10-03 17:00" ) );
+
+ r3->setFrequency( 2 );
+
+ QVERIFY( IncidenceFormatter::recurrenceString( e3 ) ==
+ i18n( "Recurs every 2 hours until 2010-10-03 17:00" ) );
+
+ r3->addExDateTime( kdt.addSecs( 1 * 60 * 60 ) );
+ QVERIFY( IncidenceFormatter::recurrenceString( e3 ) ==
+ i18n( "Recurs every 2 hours until 2010-10-03 17:00 (excluding 13:00)" ) );
+
+ r3->addExDateTime( kdt.addSecs( 3 * 60 * 60 ) );
+ QVERIFY( IncidenceFormatter::recurrenceString( e3 ) ==
+ i18n( "Recurs every 2 hours until 2010-10-03 17:00 (excluding 13:00,15:00)" ) );
+
+// qDebug() << "recurrenceString=" << IncidenceFormatter::recurrenceString( e3 );
+}
diff --git a/kcalutils/tests/testincidenceformatter.h b/kcalutils/tests/testincidenceformatter.h
new file mode 100644
index 0000000..8b8619d
--- /dev/null
+++ b/kcalutils/tests/testincidenceformatter.h
@@ -0,0 +1,34 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TESTINCIDENCEFORMATTER_H
+#define TESTINCIDENCEFORMATTER_H
+
+#include <QtCore/QObject>
+
+class IncidenceFormatterTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testRecurrenceString();
+};
+
+#endif
diff --git a/kcalutils/tests/teststringify.cpp b/kcalutils/tests/teststringify.cpp
new file mode 100644
index 0000000..c918ad6
--- /dev/null
+++ b/kcalutils/tests/teststringify.cpp
@@ -0,0 +1,75 @@
+/*
+ This file is part of the kcalcore library.
+
+ Copyright (c) 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "teststringify.h"
+#include "teststringify.moc"
+#include "../stringify.h"
+
+#include <qtest_kde.h>
+QTEST_KDEMAIN( StringifyTest, NoGUI )
+
+using namespace KCalCore;
+using namespace KCalUtils;
+
+void StringifyTest::testIncidenceStrings()
+{
+ QVERIFY( Stringify::incidenceType( Incidence::TypeEvent ) == i18n( "event" ) );
+ QVERIFY( Stringify::incidenceType( Incidence::TypeTodo ) == i18n( "to-do" ) );
+ QVERIFY( Stringify::incidenceType( Incidence::TypeJournal ) == i18n( "journal" ) );
+ QVERIFY( Stringify::incidenceType( Incidence::TypeFreeBusy ) == i18n( "free/busy" ) );
+
+ QVERIFY( Stringify::incidenceSecrecy( Incidence::SecrecyPublic ) == i18n( "Public" ) );
+ QVERIFY( Stringify::incidenceSecrecy( Incidence::SecrecyPrivate ) == i18n( "Private" ) );
+ QVERIFY(
+ Stringify::incidenceSecrecy( Incidence::SecrecyConfidential ) == i18n( "Confidential" ) );
+
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusTentative ) == i18n( "Tentative" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusConfirmed ) == i18n( "Confirmed" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusCompleted ) == i18n( "Completed" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusNeedsAction ) == i18n( "Needs-Action" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusCanceled ) == i18n( "Canceled" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusInProcess ) == i18n( "In-Process" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusDraft ) == i18n( "Draft" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusFinal ) == i18n( "Final" ) );
+ QVERIFY( Stringify::incidenceStatus( Incidence::StatusX ).isEmpty() );
+}
+
+void StringifyTest::testAttendeeStrings()
+{
+ QVERIFY( Stringify::attendeeRole( Attendee::Chair ) == i18n( "Chair" ) );
+ QVERIFY( Stringify::attendeeRole( Attendee::ReqParticipant ) == i18n( "Participant" ) );
+ QVERIFY( Stringify::attendeeRole( Attendee::OptParticipant ) == i18n( "Optional Participant" ) );
+ QVERIFY( Stringify::attendeeRole( Attendee::NonParticipant ) == i18n( "Observer" ) );
+
+ QVERIFY( Stringify::attendeeStatus( Attendee::NeedsAction ) == i18n( "Needs Action" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::Accepted ) == i18n( "Accepted" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::Declined ) == i18n( "Declined" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::Tentative ) == i18n( "Tentative" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::Delegated ) == i18n( "Delegated" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::Completed ) == i18n( "Completed" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::InProcess ) == i18n( "In Process" ) );
+ QVERIFY( Stringify::attendeeStatus( Attendee::None ) == i18n( "Unknown" ) );
+}
+
+void StringifyTest::testDateTimeStrings()
+{
+ //TODO
+}
diff --git a/kcalutils/tests/teststringify.h b/kcalutils/tests/teststringify.h
new file mode 100644
index 0000000..775af38
--- /dev/null
+++ b/kcalutils/tests/teststringify.h
@@ -0,0 +1,36 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 2010 Klarlvdalens Datakonsult AB, a KDAB Group company <info@kdab.net>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef TESTSTRINGIFY_H
+#define TESTSTRINGIFY_H
+
+#include <QtCore/QObject>
+
+class StringifyTest : public QObject
+{
+ Q_OBJECT
+ private Q_SLOTS:
+ void testIncidenceStrings();
+ void testAttendeeStrings();
+ void testDateTimeStrings();
+};
+
+#endif
diff --git a/kcalutils/vcaldrag.cpp b/kcalutils/vcaldrag.cpp
new file mode 100644
index 0000000..da71808
--- /dev/null
+++ b/kcalutils/vcaldrag.cpp
@@ -0,0 +1,72 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#include "vcaldrag.h"
+
+#include <kcalcore/vcalformat.h>
+using namespace KCalCore;
+
+#include <QtCore/QMimeData>
+#include <QtCore/QString>
+
+using namespace KCalUtils;
+using namespace VCalDrag;
+
+QString VCalDrag::mimeType()
+{
+ return "text/x-vCalendar";
+}
+
+bool VCalDrag::populateMimeData( QMimeData *e,
+ const MemoryCalendar::Ptr &cal )
+{
+ VCalFormat format;
+ QString calstr( format.toString( cal ) );
+ if ( calstr.length() > 0 ) {
+ e->setData( mimeType(), calstr.toUtf8() );
+ }
+ return canDecode( e );
+}
+
+bool VCalDrag::canDecode( const QMimeData *me )
+{
+ return me->hasFormat( mimeType() );
+}
+
+bool VCalDrag::fromMimeData( const QMimeData *de,
+ const MemoryCalendar::Ptr &cal )
+{
+ if ( !canDecode( de ) ) {
+ return false;
+ }
+
+ bool success = false;
+ QByteArray payload = de->data( mimeType() );
+ if ( payload.size() ) {
+ QString txt = QString::fromUtf8( payload.data() );
+
+ VCalFormat format;
+ success = format.fromString( cal, txt );
+ }
+
+ return success;
+}
+
diff --git a/kcalutils/vcaldrag.h b/kcalutils/vcaldrag.h
new file mode 100644
index 0000000..4cce926
--- /dev/null
+++ b/kcalutils/vcaldrag.h
@@ -0,0 +1,62 @@
+/*
+ This file is part of the kcalutils library.
+
+ Copyright (c) 1998 Preston Brown <pbrown@kde.org>
+ Copyright (c) 2001-2003 Cornelius Schumacher <schumacher@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KCALUTILS_VCALDRAG_H
+#define KCALUTILS_VCALDRAG_H
+
+#include "kcalutils_export.h"
+#include <kcalcore/memorycalendar.h>
+
+class QMimeData;
+
+namespace KCalUtils {
+
+/**
+ vCalendar drag&drop class.
+*/
+namespace VCalDrag
+{
+ /**
+ Mime-type of iCalendar
+ */
+ KCALUTILS_EXPORT QString mimeType();
+
+ /**
+ Sets the vCalendar representation as data of the drag object
+ */
+ KCALUTILS_EXPORT bool populateMimeData( QMimeData *e,
+ const KCalCore::MemoryCalendar::Ptr &cal );
+
+ /**
+ Return, if drag&drop object can be decode to vCalendar.
+ */
+ KCALUTILS_EXPORT bool canDecode( const QMimeData * );
+
+ /**
+ Decode drag&drop object to vCalendar component \a vcal.
+ */
+ KCALUTILS_EXPORT bool fromMimeData( const QMimeData *e,
+ const KCalCore::MemoryCalendar::Ptr &cal );
+}
+
+}
+
+#endif