From 9ee999fe4a7de48abc3b4f950699cd4564d16562 Mon Sep 17 00:00:00 2001 From: Ossi Jormakka Date: Tue, 19 May 2009 09:25:11 +0300 Subject: [PATCH] qtmeetings sources to Maemo garage --- QtMeetings.conf | 57 + QtMeetings.desktop | 12 + QtMeetings.doxygen | 1503 ++++++++++++++++++++ QtMeetings.pro | 132 ++ debian/README.Debian | 6 + debian/changelog | 44 + debian/changelog.Debian | 1 + debian/compat | 1 + debian/control | 13 + debian/copyright | 40 + debian/postinst | 7 + debian/postrm | 28 + debian/preinst | 19 + debian/qtmeetings/DEBIAN/conffiles | 2 + debian/qtmeetings/DEBIAN/control | 11 + debian/qtmeetings/DEBIAN/md5sums | 7 + debian/qtmeetings/DEBIAN/postinst | 7 + debian/qtmeetings/DEBIAN/postrm | 28 + debian/qtmeetings/DEBIAN/preinst | 19 + debian/qtmeetings/etc/QtMeetings.conf | 57 + debian/qtmeetings/etc/init.d/qtmeetings-launcher | 15 + debian/qtmeetings/usr/bin/qtmeetings | Bin 0 -> 577324 bytes debian/qtmeetings/usr/bin/qtmeetings-devstopper | 13 + debian/qtmeetings/usr/bin/qtmeetings-rename | 6 + debian/qtmeetings/usr/bin/qtmeetings-updatercd | 14 + .../share/applications/hildon/QtMeetings.desktop | 12 + .../usr/share/doc/qtmeetings/README.Debian | 6 + .../qtmeetings/usr/share/doc/qtmeetings/copyright | 40 + debian/rules | 78 + resources/BusinessLogic.qrc | 5 + resources/UserInterface.qrc | 16 + resources/icons/arrow_left.png | Bin 0 -> 875 bytes resources/icons/arrow_right.png | Bin 0 -> 895 bytes resources/icons/button_current.png | Bin 0 -> 594 bytes resources/icons/button_settings.png | Bin 0 -> 2397 bytes resources/icons/popup_cancel.png | Bin 0 -> 2143 bytes resources/icons/popup_error.png | Bin 0 -> 2068 bytes resources/icons/popup_information.png | Bin 0 -> 1198 bytes resources/icons/popup_ok.png | Bin 0 -> 1790 bytes resources/icons/popup_question.png | Bin 0 -> 1646 bytes resources/icons/popup_warning.png | Bin 0 -> 1375 bytes resources/icons/roomstatus_busy.png | Bin 0 -> 43255 bytes resources/icons/roomstatus_free.png | Bin 0 -> 43508 bytes resources/xml/errortable.xml | 30 + scripts/qtmeetings-devstopper | 13 + scripts/qtmeetings-launcher | 15 + scripts/qtmeetings-rename | 6 + scripts/qtmeetings-updatercd | 14 + src/BusinessLogic/Engine.cpp | 241 ++++ src/BusinessLogic/Engine.h | 195 +++ src/BusinessLogic/Utils/Clock.cpp | 57 + src/BusinessLogic/Utils/Clock.h | 75 + src/BusinessLogic/Utils/ErrorMapper.cpp | 83 ++ src/BusinessLogic/Utils/ErrorMapper.h | 54 + src/Domain/Configuration/Configuration.cpp | 601 ++++++++ src/Domain/Configuration/Configuration.h | 217 +++ src/Domain/Configuration/ConnectionSettings.cpp | 61 + src/Domain/Configuration/ConnectionSettings.h | 92 ++ src/Domain/Configuration/DisplaySettings.cpp | 107 ++ src/Domain/Configuration/DisplaySettings.h | 145 ++ src/Domain/Configuration/StartupSettings.cpp | 40 + src/Domain/Configuration/StartupSettings.h | 70 + src/Domain/Meeting.cpp | 150 ++ src/Domain/Meeting.h | 169 +++ src/Domain/Room.cpp | 45 + src/Domain/Room.h | 76 + src/IO/Communication/Communication.cpp | 114 ++ src/IO/Communication/Communication.h | 101 ++ src/IO/Communication/CommunicationManager.cpp | 192 +++ src/IO/Communication/CommunicationManager.h | 163 +++ src/IO/Communication/MessagingUtils.cpp | 1125 +++++++++++++++ src/IO/Communication/MessagingUtils.h | 611 ++++++++ src/IO/DeviceControl/AlarmSender.cpp | 200 +++ src/IO/DeviceControl/AlarmSender.h | 107 ++ src/IO/DeviceControl/DeviceConfigurator.cpp | 258 ++++ src/IO/DeviceControl/DeviceConfigurator.h | 89 ++ src/IO/DeviceControl/DeviceConstants.h | 11 + src/IO/DeviceControl/DeviceDataStorage.cpp | 210 +++ src/IO/DeviceControl/DeviceDataStorage.h | 123 ++ src/IO/DeviceControl/DeviceManager.cpp | 241 ++++ src/IO/DeviceControl/DeviceManager.h | 171 +++ src/IO/DeviceControl/HWKeyListener.cpp | 39 + src/IO/DeviceControl/HWKeyListener.h | 53 + .../Components/DigitalTimeDisplayWidget.cpp | 55 + .../Components/DigitalTimeDisplayWidget.h | 64 + src/UserInterface/Components/MeetingRoomCombo.cpp | 106 ++ src/UserInterface/Components/MeetingRoomCombo.h | 103 ++ src/UserInterface/Components/ObservedWidget.cpp | 61 + src/UserInterface/Components/ObservedWidget.h | 75 + src/UserInterface/Components/ScheduleWidget.cpp | 557 ++++++++ src/UserInterface/Components/ScheduleWidget.h | 346 +++++ src/UserInterface/Components/TimeDisplayWidget.cpp | 40 + src/UserInterface/Components/TimeDisplayWidget.h | 86 ++ src/UserInterface/Utils/PasswordDialog.cpp | 112 ++ src/UserInterface/Utils/PasswordDialog.h | 69 + src/UserInterface/Utils/PopUpMessageBox.cpp | 196 +++ src/UserInterface/Utils/PopUpMessageBox.h | 184 +++ src/UserInterface/Utils/ToolBox.cpp | 10 + src/UserInterface/Utils/ToolBox.h | 25 + src/UserInterface/Views/MeetingInfoDialog.cpp | 85 ++ src/UserInterface/Views/MeetingInfoDialog.h | 33 + .../Views/RoomStatusIndicatorWidget.cpp | 120 ++ .../Views/RoomStatusIndicatorWidget.h | 86 ++ src/UserInterface/Views/SettingsView.cpp | 420 ++++++ src/UserInterface/Views/SettingsView.h | 84 ++ src/UserInterface/Views/WeeklyViewWidget.cpp | 202 +++ src/UserInterface/Views/WeeklyViewWidget.h | 169 +++ src/UserInterface/WindowManager.cpp | 272 ++++ src/UserInterface/WindowManager.h | 125 ++ src/main.cpp | 41 + tests/BusinessLogic/Engine/QtMeetings.conf | 58 + tests/BusinessLogic/Engine/TestEngine.cpp | 91 ++ tests/BusinessLogic/Engine/TestEngine.h | 27 + tests/BusinessLogic/Engine/TestEngineOnly.cpp | 11 + tests/BusinessLogic/Engine/TestEngineOnly.pro | 49 + tests/BusinessLogic/Utils/Clock/TestClock.cpp | 42 + tests/BusinessLogic/Utils/Clock/TestClock.h | 18 + tests/BusinessLogic/Utils/Clock/TestClockOnly.cpp | 11 + tests/BusinessLogic/Utils/Clock/TestClockOnly.pro | 9 + .../Utils/ErrorMapper/TestErrorMapper.cpp | 18 + .../Utils/ErrorMapper/TestErrorMapper.h | 12 + .../Utils/ErrorMapper/TestErrorMapperOnly.cpp | 11 + .../Utils/ErrorMapper/TestErrorMapperOnly.pro | 11 + .../Configuration/Configuration/QtMeetings.conf | 45 + .../Configuration/TestConfiguration.cpp | 165 +++ .../Configuration/TestConfiguration.h | 29 + .../Configuration/TestConfigurationOnly.cpp | 11 + .../Configuration/TestConfigurationOnly.pro | 18 + .../ConnectionSettings/TestConnectionSettings.cpp | 50 + .../ConnectionSettings/TestConnectionSettings.h | 50 + .../TestConnectionSettingsOnly.cpp | 11 + .../TestConnectionSettingsOnly.pro | 9 + .../DisplaySettings/TestDisplaySettings.cpp | 72 + .../DisplaySettings/TestDisplaySettings.h | 25 + .../DisplaySettings/TestDisplaySettingsOnly.cpp | 11 + .../DisplaySettings/TestDisplaySettingsOnly.pro | 9 + .../StartupSettings/TestStartupSettings.cpp | 55 + .../StartupSettings/TestStartupSettings.h | 23 + .../StartupSettings/TestStartupSettingsOnly.cpp | 11 + .../StartupSettings/TestStartupSettingsOnly.pro | 9 + tests/Domain/Meeting/TestMeeting.cpp | 184 +++ tests/Domain/Meeting/TestMeeting.h | 45 + tests/Domain/Meeting/TestMeetingOnly.cpp | 11 + tests/Domain/Meeting/TestMeetingOnly.pro | 11 + tests/Domain/Room/TestRoom.cpp | 71 + tests/Domain/Room/TestRoom.h | 50 + tests/Domain/Room/TestRoomOnly.cpp | 11 + tests/Domain/Room/TestRoomOnly.pro | 10 + tests/IO/Communication/TestCommunication.cpp | 100 ++ tests/IO/Communication/TestCommunication.h | 21 + tests/IO/Communication/TestCommunicationOnly.cpp | 11 + tests/IO/Communication/TestCommunicationOnly.pro | 13 + tests/IO/Communication/input.xml | 40 + .../TestCommunicationManager.cpp | 151 ++ .../TestCommunicationManager.h | 26 + .../TestCommunicationManagerOnly.cpp | 11 + .../TestCommunicationManagerOnly.pro | 21 + tests/QtMeetings.conf | 45 + tests/TestQtMeetings.cpp | 69 + tests/TestQtMeetings.pro | 107 ++ .../MeetingRoomCombo/TestMeetingRoomCombo.cpp | 101 ++ .../MeetingRoomCombo/TestMeetingRoomCombo.h | 27 + .../MeetingRoomCombo/TestMeetingRoomComboOnly.cpp | 12 + .../MeetingRoomCombo/TestMeetingRoomComboOnly.pro | 14 + welcome | 1 - 165 files changed, 14422 insertions(+), 1 deletion(-) create mode 100644 QtMeetings.conf create mode 100644 QtMeetings.desktop create mode 100644 QtMeetings.doxygen create mode 100644 QtMeetings.pro create mode 100644 debian/README.Debian create mode 100644 debian/changelog create mode 100644 debian/changelog.Debian create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/postinst create mode 100644 debian/postrm create mode 100644 debian/preinst create mode 100644 debian/qtmeetings.install create mode 100644 debian/qtmeetings/DEBIAN/conffiles create mode 100644 debian/qtmeetings/DEBIAN/control create mode 100644 debian/qtmeetings/DEBIAN/md5sums create mode 100755 debian/qtmeetings/DEBIAN/postinst create mode 100755 debian/qtmeetings/DEBIAN/postrm create mode 100755 debian/qtmeetings/DEBIAN/preinst create mode 100644 debian/qtmeetings/etc/QtMeetings.conf create mode 100755 debian/qtmeetings/etc/init.d/qtmeetings-launcher create mode 100755 debian/qtmeetings/usr/bin/qtmeetings create mode 100755 debian/qtmeetings/usr/bin/qtmeetings-devstopper create mode 100755 debian/qtmeetings/usr/bin/qtmeetings-rename create mode 100755 debian/qtmeetings/usr/bin/qtmeetings-updatercd create mode 100644 debian/qtmeetings/usr/share/applications/hildon/QtMeetings.desktop create mode 100644 debian/qtmeetings/usr/share/doc/qtmeetings/README.Debian create mode 100644 debian/qtmeetings/usr/share/doc/qtmeetings/copyright create mode 100644 debian/rules create mode 100644 resources/BusinessLogic.qrc create mode 100644 resources/UserInterface.qrc create mode 100644 resources/icons/arrow_left.png create mode 100644 resources/icons/arrow_right.png create mode 100644 resources/icons/button_current.png create mode 100644 resources/icons/button_settings.png create mode 100644 resources/icons/popup_cancel.png create mode 100644 resources/icons/popup_error.png create mode 100644 resources/icons/popup_information.png create mode 100644 resources/icons/popup_ok.png create mode 100644 resources/icons/popup_question.png create mode 100644 resources/icons/popup_warning.png create mode 100644 resources/icons/roomstatus_busy.png create mode 100644 resources/icons/roomstatus_free.png create mode 100644 resources/xml/errortable.xml create mode 100755 scripts/qtmeetings-devstopper create mode 100755 scripts/qtmeetings-launcher create mode 100755 scripts/qtmeetings-rename create mode 100755 scripts/qtmeetings-updatercd create mode 100644 src/BusinessLogic/Engine.cpp create mode 100644 src/BusinessLogic/Engine.h create mode 100644 src/BusinessLogic/Utils/Clock.cpp create mode 100644 src/BusinessLogic/Utils/Clock.h create mode 100644 src/BusinessLogic/Utils/ErrorMapper.cpp create mode 100644 src/BusinessLogic/Utils/ErrorMapper.h create mode 100644 src/Domain/Configuration/Configuration.cpp create mode 100644 src/Domain/Configuration/Configuration.h create mode 100644 src/Domain/Configuration/ConnectionSettings.cpp create mode 100644 src/Domain/Configuration/ConnectionSettings.h create mode 100644 src/Domain/Configuration/DisplaySettings.cpp create mode 100644 src/Domain/Configuration/DisplaySettings.h create mode 100644 src/Domain/Configuration/StartupSettings.cpp create mode 100644 src/Domain/Configuration/StartupSettings.h create mode 100644 src/Domain/Meeting.cpp create mode 100644 src/Domain/Meeting.h create mode 100644 src/Domain/Room.cpp create mode 100644 src/Domain/Room.h create mode 100644 src/IO/Communication/Communication.cpp create mode 100644 src/IO/Communication/Communication.h create mode 100644 src/IO/Communication/CommunicationManager.cpp create mode 100644 src/IO/Communication/CommunicationManager.h create mode 100644 src/IO/Communication/MessagingUtils.cpp create mode 100644 src/IO/Communication/MessagingUtils.h create mode 100644 src/IO/DeviceControl/AlarmSender.cpp create mode 100644 src/IO/DeviceControl/AlarmSender.h create mode 100644 src/IO/DeviceControl/DeviceConfigurator.cpp create mode 100644 src/IO/DeviceControl/DeviceConfigurator.h create mode 100644 src/IO/DeviceControl/DeviceConstants.h create mode 100644 src/IO/DeviceControl/DeviceDataStorage.cpp create mode 100644 src/IO/DeviceControl/DeviceDataStorage.h create mode 100644 src/IO/DeviceControl/DeviceManager.cpp create mode 100644 src/IO/DeviceControl/DeviceManager.h create mode 100644 src/IO/DeviceControl/HWKeyListener.cpp create mode 100644 src/IO/DeviceControl/HWKeyListener.h create mode 100644 src/UserInterface/Components/DigitalTimeDisplayWidget.cpp create mode 100644 src/UserInterface/Components/DigitalTimeDisplayWidget.h create mode 100644 src/UserInterface/Components/MeetingRoomCombo.cpp create mode 100644 src/UserInterface/Components/MeetingRoomCombo.h create mode 100644 src/UserInterface/Components/ObservedWidget.cpp create mode 100644 src/UserInterface/Components/ObservedWidget.h create mode 100644 src/UserInterface/Components/ScheduleWidget.cpp create mode 100644 src/UserInterface/Components/ScheduleWidget.h create mode 100644 src/UserInterface/Components/TimeDisplayWidget.cpp create mode 100644 src/UserInterface/Components/TimeDisplayWidget.h create mode 100644 src/UserInterface/Utils/PasswordDialog.cpp create mode 100644 src/UserInterface/Utils/PasswordDialog.h create mode 100644 src/UserInterface/Utils/PopUpMessageBox.cpp create mode 100644 src/UserInterface/Utils/PopUpMessageBox.h create mode 100644 src/UserInterface/Utils/ToolBox.cpp create mode 100644 src/UserInterface/Utils/ToolBox.h create mode 100644 src/UserInterface/Views/MeetingInfoDialog.cpp create mode 100644 src/UserInterface/Views/MeetingInfoDialog.h create mode 100644 src/UserInterface/Views/RoomStatusIndicatorWidget.cpp create mode 100644 src/UserInterface/Views/RoomStatusIndicatorWidget.h create mode 100644 src/UserInterface/Views/SettingsView.cpp create mode 100644 src/UserInterface/Views/SettingsView.h create mode 100644 src/UserInterface/Views/WeeklyViewWidget.cpp create mode 100644 src/UserInterface/Views/WeeklyViewWidget.h create mode 100644 src/UserInterface/WindowManager.cpp create mode 100644 src/UserInterface/WindowManager.h create mode 100644 src/main.cpp create mode 100644 tests/BusinessLogic/Engine/QtMeetings.conf create mode 100644 tests/BusinessLogic/Engine/TestEngine.cpp create mode 100644 tests/BusinessLogic/Engine/TestEngine.h create mode 100644 tests/BusinessLogic/Engine/TestEngineOnly.cpp create mode 100644 tests/BusinessLogic/Engine/TestEngineOnly.pro create mode 100644 tests/BusinessLogic/Utils/Clock/TestClock.cpp create mode 100644 tests/BusinessLogic/Utils/Clock/TestClock.h create mode 100644 tests/BusinessLogic/Utils/Clock/TestClockOnly.cpp create mode 100644 tests/BusinessLogic/Utils/Clock/TestClockOnly.pro create mode 100644 tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.cpp create mode 100644 tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.h create mode 100644 tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.cpp create mode 100644 tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.pro create mode 100644 tests/Domain/Configuration/Configuration/QtMeetings.conf create mode 100644 tests/Domain/Configuration/Configuration/TestConfiguration.cpp create mode 100644 tests/Domain/Configuration/Configuration/TestConfiguration.h create mode 100644 tests/Domain/Configuration/Configuration/TestConfigurationOnly.cpp create mode 100644 tests/Domain/Configuration/Configuration/TestConfigurationOnly.pro create mode 100644 tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.cpp create mode 100644 tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.h create mode 100644 tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.cpp create mode 100644 tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.pro create mode 100644 tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.cpp create mode 100644 tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.h create mode 100644 tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.cpp create mode 100644 tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.pro create mode 100644 tests/Domain/Configuration/StartupSettings/TestStartupSettings.cpp create mode 100644 tests/Domain/Configuration/StartupSettings/TestStartupSettings.h create mode 100644 tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.cpp create mode 100644 tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.pro create mode 100644 tests/Domain/Meeting/TestMeeting.cpp create mode 100644 tests/Domain/Meeting/TestMeeting.h create mode 100644 tests/Domain/Meeting/TestMeetingOnly.cpp create mode 100644 tests/Domain/Meeting/TestMeetingOnly.pro create mode 100644 tests/Domain/Room/TestRoom.cpp create mode 100644 tests/Domain/Room/TestRoom.h create mode 100644 tests/Domain/Room/TestRoomOnly.cpp create mode 100644 tests/Domain/Room/TestRoomOnly.pro create mode 100644 tests/IO/Communication/TestCommunication.cpp create mode 100644 tests/IO/Communication/TestCommunication.h create mode 100644 tests/IO/Communication/TestCommunicationOnly.cpp create mode 100644 tests/IO/Communication/TestCommunicationOnly.pro create mode 100644 tests/IO/Communication/input.xml create mode 100644 tests/IO/CommunicationManager/TestCommunicationManager.cpp create mode 100644 tests/IO/CommunicationManager/TestCommunicationManager.h create mode 100644 tests/IO/CommunicationManager/TestCommunicationManagerOnly.cpp create mode 100644 tests/IO/CommunicationManager/TestCommunicationManagerOnly.pro create mode 100644 tests/QtMeetings.conf create mode 100644 tests/TestQtMeetings.cpp create mode 100644 tests/TestQtMeetings.pro create mode 100644 tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.cpp create mode 100644 tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.h create mode 100644 tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.cpp create mode 100644 tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.pro delete mode 100644 welcome diff --git a/QtMeetings.conf b/QtMeetings.conf new file mode 100644 index 0000000..7066087 --- /dev/null +++ b/QtMeetings.conf @@ -0,0 +1,57 @@ + + + + + + + jklexch01.ixonos.com + + + + 60 + + + + + + + + + + + + + + ddd dd MMM + hh:mm + + + + + + TestRoom +
meetingroomtest@test.local
+
+ + + Pegasus +
meetingroom.pegasus_jyv@ixonos.com
+
+ + Taurus +
meetingroom.taurus_jyv@ixonos.com
+
+ + Hercules +
meetingroom.hercules@ixonos.com
+
+
+ + + + +
diff --git a/QtMeetings.desktop b/QtMeetings.desktop new file mode 100644 index 0000000..c5c0587 --- /dev/null +++ b/QtMeetings.desktop @@ -0,0 +1,12 @@ +[Desktop Entry] +Encoding=UTF-8 +Version=1.0.3 +Type=Application +Name=Qt Meetings +Comment=Qt application to access meeting room's shared calendar. +Exec=/usr/bin/qtmeetings +Icon=qtmeetings +X-HildonDesk-ShowInToolbar=true +#X-Osso-Service=qtmeetings_app +X-Osso-Type=application/x-executable +Teminal=false \ No newline at end of file diff --git a/QtMeetings.doxygen b/QtMeetings.doxygen new file mode 100644 index 0000000..a23465f --- /dev/null +++ b/QtMeetings.doxygen @@ -0,0 +1,1503 @@ +# Doxyfile 1.5.8 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project +# +# All text after a hash (#) is considered a comment and will be ignored +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" ") + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = QtMeetings + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = docs + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, +# Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, +# Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = YES + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = */test*/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# is applied to all files. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to FRAME, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NONE + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. This is useful +# if you want to understand what is going on. On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# in the INCLUDE_PATH (see below) will be search if a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse +# the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more +# powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO diff --git a/QtMeetings.pro b/QtMeetings.pro new file mode 100644 index 0000000..38adacb --- /dev/null +++ b/QtMeetings.pro @@ -0,0 +1,132 @@ +TEMPLATE = app + +TARGET = qtmeetings + +QT += xml \ + network + +INCLUDEPATH += src/Domain/ \ + src/Domain/Configuration/ \ + src/IO/ \ + src/IO/Communication/ \ + src/IO/DeviceControl/ \ + src/BusinessLogic/ \ + src/BusinessLogic/Utils/ \ + src/UserInterface/ \ + src/UserInterface/Components/ \ + src/UserInterface/Utils/ \ + src/UserInterface/Views/ + +HEADERS += src/Domain/Room.h \ + src/Domain/Meeting.h \ + src/Domain/Configuration/ConnectionSettings.h \ + src/Domain/Configuration/StartupSettings.h \ + src/Domain/Configuration/DisplaySettings.h \ + src/Domain/Configuration/Configuration.h \ + src/IO/Communication/MessagingUtils.h \ + src/IO/Communication/Communication.h \ + src/IO/Communication/CommunicationManager.h \ + src/IO/DeviceControl/AlarmSender.h \ + src/IO/DeviceControl/HWKeyListener.h \ + src/IO/DeviceControl/DeviceDataStorage.h \ + src/IO/DeviceControl/DeviceConfigurator.h \ + src/IO/DeviceControl/DeviceManager.h \ + src/BusinessLogic/Utils/ErrorMapper.h \ + src/BusinessLogic/Utils/Clock.h \ + src/BusinessLogic/Engine.h \ + src/UserInterface/Utils/ToolBox.h \ + src/UserInterface/Utils/PopUpMessageBox.h \ + src/UserInterface/Utils/PasswordDialog.h \ + src/UserInterface/Components/ObservedWidget.h \ + src/UserInterface/Components/TimeDisplayWidget.h \ + src/UserInterface/Components/DigitalTimeDisplayWidget.h \ + src/UserInterface/Components/MeetingRoomCombo.h \ + src/UserInterface/Components/ScheduleWidget.h \ + src/UserInterface/Views/RoomStatusIndicatorWidget.h \ + src/UserInterface/Views/WeeklyViewWidget.h \ + src/UserInterface/Views/MeetingInfoDialog.h \ + src/UserInterface/Views/SettingsView.h \ + src/UserInterface/WindowManager.h + +SOURCES += src/Domain/Room.cpp \ + src/Domain/Meeting.cpp \ + src/Domain/Configuration/ConnectionSettings.cpp \ + src/Domain/Configuration/StartupSettings.cpp \ + src/Domain/Configuration/DisplaySettings.cpp \ + src/Domain/Configuration/Configuration.cpp \ + src/IO/Communication/MessagingUtils.cpp \ + src/IO/Communication/Communication.cpp \ + src/IO/Communication/CommunicationManager.cpp \ + src/IO/DeviceControl/AlarmSender.cpp \ + src/IO/DeviceControl/HWKeyListener.cpp \ + src/IO/DeviceControl/DeviceDataStorage.cpp \ + src/IO/DeviceControl/DeviceConfigurator.cpp \ + src/IO/DeviceControl/DeviceManager.cpp \ + src/BusinessLogic/Utils/ErrorMapper.cpp \ + src/BusinessLogic/Utils/Clock.cpp \ + src/BusinessLogic/Engine.cpp \ + src/UserInterface/Utils/ToolBox.cpp \ + src/UserInterface/Utils/PopUpMessageBox.cpp \ + src/UserInterface/Utils/PasswordDialog.cpp \ + src/UserInterface/Components/ObservedWidget.cpp \ + src/UserInterface/Components/TimeDisplayWidget.cpp \ + src/UserInterface/Components/DigitalTimeDisplayWidget.cpp \ + src/UserInterface/Components/MeetingRoomCombo.cpp \ + src/UserInterface/Components/ScheduleWidget.cpp \ + src/UserInterface/Views/RoomStatusIndicatorWidget.cpp \ + src/UserInterface/Views/WeeklyViewWidget.cpp \ + src/UserInterface/Views/MeetingInfoDialog.cpp \ + src/UserInterface/Views/SettingsView.cpp \ + src/UserInterface/WindowManager.cpp \ + src/main.cpp + +RESOURCES += resources/BusinessLogic.qrc \ + resources/UserInterface.qrc + +CONFIG += link_pkgconfig +PKGCONFIG += libalarm + +DEFINES += QT_NO_DEBUG_OUTPUT + +executable.files = qtmeetings +executable.path = /usr/bin/ +executable.hint = executable +INSTALLS += executable + +appconfig.files = QtMeetings.conf +appconfig.path = /etc/ +appconfig.hint = appconfig +INSTALLS += appconfig + +desktop.files = QtMeetings.desktop +desktop.path = /usr/share/applications/hildon/ +desktop.hint = desktop +INSTALLS += desktop + +devstopperscript.files = scripts/qtmeetings-devstopper +devstopperscript.path = /usr/bin/ +devstopperscript.hint = devstopperscript +INSTALLS += devstopperscript + +renamescript.files = scripts/qtmeetings-rename +renamescript.path = /usr/bin/ +renamescript.hint = renamescript +INSTALLS += renamescript + +updatercdscript.files = scripts/qtmeetings-updatercd +updatercdscript.path = /usr/bin/ +updatercdscript.hint = updatercdscript +INSTALLS += updatercdscript + +launcherscript.files = scripts/qtmeetings-launcher +launcherscript.path = /etc/init.d/ +launcherscript.hint = launcherscript +INSTALLS += launcherscript + +unix:exists( $$system(which doxygen) ) { + message( "Doxygen is present in your system." ) + BUILD_NOW = $$prompt( "Do you want to build Doxygen documentation now? [YES/no]" ) + contains( BUILD_NOW, "YES" ):system( "doxygen QtMeetings.doxygen" )::message( "Documentation is built successfully." ) +} +else:message( "You must install Doxygen to build the project documentation." ) +message( "Now run 'make' to compile the source code." ) diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..2e5379e --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +qtmeetings for Debian +--------------------- + +Qt application to access meeting room's shared calendar on Microsoft Exchange Server 2007, to present availability in customizable weekly or current status view, developed for Nokia N800/810 Internet Tablet. + + -- Ossi Jormakka Thu, 14 May 2009 11:11:00 +0300 diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..08cf32a --- /dev/null +++ b/debian/changelog @@ -0,0 +1,44 @@ +qtmeetings (1.0.5-1) unstable; urgency=low + + * Improved communication module for getting a meeting secondary id and detailed information + * Improved device control module + * Traffic light was showing green light in room status view even if the room was busy + * Switched places between the OK and Cancel buttons in PasswordDialog. + * Fix for #4061 Meeting info pop up doesn't close with one OK click + * Fix for #4068 Room Status View just flashes + * Fix for #4062 Error note is not displayed + * Fix for #4060 Description and Organizer not shown + * Fix for #4063 Traffic lights are truncated + + -- Ossi Jormakka Thu, 14 May 2009 12:30:00 +0300 + +qtmeetings (1.0.4-1) unstable; urgency=low + + * Improved communication module for handling multiple meeting rooms (one per time) + * Improved device control module + * Kiosk mode in use + * Details of meetings in dialog box + * Redesigned Weekly View and Room Status View + * Source is fully documented with Doxygen comments + * Unit tests are launchable indivudually per classes or all together + + -- Zoltan Papp Mon, 4 May 2009 17:15:00 +0000 + +qtmeetings (1.0.3-3) unstable; urgency=low + + * Configuration path-fix for the previous revision + + -- Zoltan Papp Wed, 29 Apr 2009 12:20:00 +0000 + +qtmeetings (1.0.3-2) unstable; urgency=low + + * Dependency to libalarm-dev added due to failure of compilation in Extras repository + + -- Zoltan Papp Tue, 28 Apr 2009 16:00:00 +0000 + +qtmeetings (1.0.3-1) unstable; urgency=low + + * Initial release + + -- Zoltan Papp Thu, 16 Apr 2009 17:30:00 +0000 + diff --git a/debian/changelog.Debian b/debian/changelog.Debian new file mode 100644 index 0000000..0853ac3 --- /dev/null +++ b/debian/changelog.Debian @@ -0,0 +1 @@ +for more information see changelog \ No newline at end of file diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7ed6ff8 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +5 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..5f699e8 --- /dev/null +++ b/debian/control @@ -0,0 +1,13 @@ +Source: qtmeetings +Section: user/other +Priority: optional +Maintainer: Zoltan Papp +Build-Depends: debhelper (>= 5), libqt4-dev, libqt4-core, libqt4-gui, libqt4-xml, libqt4-network, libalarm-dev +Standards-Version: 3.7.2 + +Package: qtmeetings +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Qt application to access meeting room's shared calendar + on Microsoft Exchange Server 2007 developed for Nokia + N800/810 Internet Tablet. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..e66d2fd --- /dev/null +++ b/debian/copyright @@ -0,0 +1,40 @@ +Upstream Author(s): + + Hagman, Jaana + Holopainen, Tanja + Jokitalo, Janne + Jormakka, Ossi + Kasari, Olli + Kolehmainen, Mika + Kulmala, Miika + Kupari, Teemu + Lapinkataja, Jan + Maslova, Ugne + Ollenberg, Aki + Papp, Zoltan + Riihola, Juha + Sihvo, Jouni + Sirén, Mikko + +Copyright: + + Copyright (C) 2009 Ixonos Plc. + +License: + + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..a729807 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +maemo-select-menu-location QtMeetings.desktop + +exit 0 \ No newline at end of file diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..00ece1c --- /dev/null +++ b/debian/postrm @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +# Check that /etc/sudoers file exists +if [ -e /etc/sudoers ]; then + grep -v 'qtmeetings' /etc/sudoers > /tmp/sudoers.new + cp /tmp/sudoers.new /etc/sudoers + rm /tmp/sudoers.new +fi + +if [ -e /usr/bin/qtmeetings-rename ]; then + rm /usr/bin/qtmeetings-rename +fi + +if [ -e /usr/bin/qtmeetings-devstopper ]; then + rm /usr/bin/qtmeetings-devstopper +fi + +if [ -e /usr/bin/qtmeetings-updatercd ]; then + rm /usr/bin/qtmeetings-updatercd +fi + +if [ -e /usr/var/qtmeetings.txt ]; then + rm /usr/var/qtmeetings.txt +fi + +exit 0 diff --git a/debian/preinst b/debian/preinst new file mode 100644 index 0000000..06b7504 --- /dev/null +++ b/debian/preinst @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +# Check that /etc/sudoers file exists +if [ ! -e /etc/sudoers ]; then + echo "Error: No /etc/sudoers file. Is sudo installed ?" + exit 1 +fi + +if ( ! grep qtmeetings-updatercd /etc/sudoers 2>/dev/null >/dev/null ); then + echo 'user ALL = NOPASSWD: /usr/bin/qtmeetings-updatercd' >> /etc/sudoers +fi + +if ( ! grep qtmeetings-rename /etc/sudoers 2>/dev/null >/dev/null ); then + echo 'user ALL = NOPASSWD: /usr/bin/qtmeetings-rename' >> /etc/sudoers +fi + +exit 0 \ No newline at end of file diff --git a/debian/qtmeetings.install b/debian/qtmeetings.install new file mode 100644 index 0000000..e69de29 diff --git a/debian/qtmeetings/DEBIAN/conffiles b/debian/qtmeetings/DEBIAN/conffiles new file mode 100644 index 0000000..d97a05d --- /dev/null +++ b/debian/qtmeetings/DEBIAN/conffiles @@ -0,0 +1,2 @@ +/etc/init.d/qtmeetings-launcher +/etc/QtMeetings.conf diff --git a/debian/qtmeetings/DEBIAN/control b/debian/qtmeetings/DEBIAN/control new file mode 100644 index 0000000..72b4cfb --- /dev/null +++ b/debian/qtmeetings/DEBIAN/control @@ -0,0 +1,11 @@ +Package: qtmeetings +Version: 1.0.5-1 +Section: user/other +Priority: optional +Architecture: armel +Depends: libalarm0 (>= 0.5.20), libc6 (>= 2.5.0-1), libgcc1 (>= 1:3.4.4), libqt4-network (>= 4.4.0), libqt4-xml (>= 4.4.0), libqtcore4 (>= 4.4.0), libqtgui4 (>= 4.4.0), libstdc++6 (>= 3.4.4) +Installed-Size: 660 +Maintainer: Zoltan Papp +Description: Qt application to access meeting room's shared calendar + on Microsoft Exchange Server 2007 developed for Nokia + N800/810 Internet Tablet. diff --git a/debian/qtmeetings/DEBIAN/md5sums b/debian/qtmeetings/DEBIAN/md5sums new file mode 100644 index 0000000..479426b --- /dev/null +++ b/debian/qtmeetings/DEBIAN/md5sums @@ -0,0 +1,7 @@ +24d4fabbf8f62ef25a57769182b6417e usr/share/doc/qtmeetings/README.Debian +7895482ea907cf53dab85a4543e838f5 usr/share/doc/qtmeetings/copyright +62db04cd3c8d12c924158af250a0d2df usr/share/applications/hildon/QtMeetings.desktop +3491e49963a41b9b27e27ead4e6d559b usr/bin/qtmeetings-devstopper +bbfd6c68e82075825bf439c6834e20c4 usr/bin/qtmeetings +2d402ba0e905291c6c5ac0d4efe4d519 usr/bin/qtmeetings-updatercd +ad0a015d12de590f84f7d955255b9c1e usr/bin/qtmeetings-rename diff --git a/debian/qtmeetings/DEBIAN/postinst b/debian/qtmeetings/DEBIAN/postinst new file mode 100755 index 0000000..a729807 --- /dev/null +++ b/debian/qtmeetings/DEBIAN/postinst @@ -0,0 +1,7 @@ +#!/bin/sh + +set -e + +maemo-select-menu-location QtMeetings.desktop + +exit 0 \ No newline at end of file diff --git a/debian/qtmeetings/DEBIAN/postrm b/debian/qtmeetings/DEBIAN/postrm new file mode 100755 index 0000000..00ece1c --- /dev/null +++ b/debian/qtmeetings/DEBIAN/postrm @@ -0,0 +1,28 @@ +#!/bin/sh + +set -e + +# Check that /etc/sudoers file exists +if [ -e /etc/sudoers ]; then + grep -v 'qtmeetings' /etc/sudoers > /tmp/sudoers.new + cp /tmp/sudoers.new /etc/sudoers + rm /tmp/sudoers.new +fi + +if [ -e /usr/bin/qtmeetings-rename ]; then + rm /usr/bin/qtmeetings-rename +fi + +if [ -e /usr/bin/qtmeetings-devstopper ]; then + rm /usr/bin/qtmeetings-devstopper +fi + +if [ -e /usr/bin/qtmeetings-updatercd ]; then + rm /usr/bin/qtmeetings-updatercd +fi + +if [ -e /usr/var/qtmeetings.txt ]; then + rm /usr/var/qtmeetings.txt +fi + +exit 0 diff --git a/debian/qtmeetings/DEBIAN/preinst b/debian/qtmeetings/DEBIAN/preinst new file mode 100755 index 0000000..06b7504 --- /dev/null +++ b/debian/qtmeetings/DEBIAN/preinst @@ -0,0 +1,19 @@ +#!/bin/sh + +set -e + +# Check that /etc/sudoers file exists +if [ ! -e /etc/sudoers ]; then + echo "Error: No /etc/sudoers file. Is sudo installed ?" + exit 1 +fi + +if ( ! grep qtmeetings-updatercd /etc/sudoers 2>/dev/null >/dev/null ); then + echo 'user ALL = NOPASSWD: /usr/bin/qtmeetings-updatercd' >> /etc/sudoers +fi + +if ( ! grep qtmeetings-rename /etc/sudoers 2>/dev/null >/dev/null ); then + echo 'user ALL = NOPASSWD: /usr/bin/qtmeetings-rename' >> /etc/sudoers +fi + +exit 0 \ No newline at end of file diff --git a/debian/qtmeetings/etc/QtMeetings.conf b/debian/qtmeetings/etc/QtMeetings.conf new file mode 100644 index 0000000..7066087 --- /dev/null +++ b/debian/qtmeetings/etc/QtMeetings.conf @@ -0,0 +1,57 @@ + + + + + + + jklexch01.ixonos.com + + + + 60 + + + + + + + + + + + + + + ddd dd MMM + hh:mm + + + + + + TestRoom +
meetingroomtest@test.local
+
+ + + Pegasus +
meetingroom.pegasus_jyv@ixonos.com
+
+ + Taurus +
meetingroom.taurus_jyv@ixonos.com
+
+ + Hercules +
meetingroom.hercules@ixonos.com
+
+
+ + + + +
diff --git a/debian/qtmeetings/etc/init.d/qtmeetings-launcher b/debian/qtmeetings/etc/init.d/qtmeetings-launcher new file mode 100755 index 0000000..7645f9b --- /dev/null +++ b/debian/qtmeetings/etc/init.d/qtmeetings-launcher @@ -0,0 +1,15 @@ +#!/bin/sh +# must be in /etc/init.d/ + +case "$1" in + start) + test -x $PROG || exit 0 + export DISPLAY=':0.0' + cd /usr/bin/ + run-standalone.sh /usr/bin/qtmeetings & + ;; + *) + exit 1 + ;; +esac + diff --git a/debian/qtmeetings/usr/bin/qtmeetings b/debian/qtmeetings/usr/bin/qtmeetings new file mode 100755 index 0000000000000000000000000000000000000000..a7ebed13ebb257e820fd087c1d41856f24e4bf3e GIT binary patch literal 577324 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

AZq&;pFqZ)VoD*wk@cWg`3 zhZLhyJk`XgCI#`F!{C1tdg?mx1$qk_H8#8po(<#Q=eg)t|Mz&8&d2vfrvpA+L3iAq zRYq^o-{Ix0{sh||TDMLecIs%?Hx5{l7zp)+gri57drBGKC?) z8To7=FSq*5(Dz5KTj2K={2uj_#niKfeuDjDt|*vO>VNK4!z80woNsh$vkl`XdDrT6JC_;?Lzm@BMj&)UXTWV;@}G{ZH*`os{fXX?3RQ~Q{4 zk8e4uh4cAD+yDI4)J1z|xo0@QLH(L|HWZrl-G!1l>O72RNy@(dcjjJxugZ_T6v5Blx;M9r*etox0!GvqtWJBN?I0;is4A(S2I? z@KM*Jxz^ayOA{N+sE;i3_kUb{F3lN}sp6TmQGgfNC)m2uZaeF`No`KO$XgDcN&kM+ z)?aHaM2nssV(ahf`?%G`YouGKukfm_pL|UH)$sOU@VUDF3_R}n65#)wx~YH59xg?$ zw0~IA_Vb|r)aUQ7KpS=P=yyr<;l^CNF5j<%F$Q#w15UY3tgaJfv!xUN)n?j2yuS;4 zb3_+7rCYo_V;+35_9}SYy6}T4@b1!A2=e7C!}+eKqbaX0nV_IW1mrarToYwf8k*`4({6L*sgJzeQvIKS1NRqcm#YLE3l={&8IvUekVk?Z#Jr z8hMnxmi>Jcd90)DH)02MuYCSq=n4FaY-(VC>l^&xNYj329kpZI3-YnFcNuU481~(1 zypzv&C*}1{{+{ApYIp5JnMkWm3;jRmd-)=13;ROKF`qoZTBZKuDBHue)(-UYUHdmy z`OIGr^l;zByJ>G^ucX%@ABWW(asLW zX!lTtG5NsZe4}<%9ZrIV^#Q%`3V!6^M{k!KbAS1lxtP)nz85a>`kt>l zy;07u8C~S4!Q=ZY>J{g=8GfzA-&_l>Yr%D^>O(AgNZO5M=M(4Acjpw}34P+LV#Phs zV8)0c{LnVh;{QMJFD+}Y>iB=4oPF=YVmCRD-PB+5I09O9{T#9)`TaWY)^Oj=yLSOw z*H7Db(qCW4_mnKJ;r)S>m7GeB|H!wCj$Gw;X)j7E$BV$Hwvik!lk9Tc%eNZKt8LU) z6Y}M4!yf026ZozJcxoHP!%93-EX?p|f#v0i&~TM)!xL1OUGR0jDsV~W zD%u-7<1C(|Yrvy%g5>SFBfvvF6&H_{C*Y5@@JG}DpMKcz8+axeSwWjhj-J8yIbauU zP;b|bC~g`{3%pRgN`32jU{%OA+Ffni-EU6$rs;R@uL`U&?+?!O!GDPF$6tNPGpE%0f1oJ;^m`yz`^UjtT@ zPp{k1+u zJMGRw`o8+lTcP>tfQKd*OFBCi5Ar?6;<*4kwf56P!4vMMigCxb33ys9o@@P)|c@y=Kj3$w*zj2>LCpTwS9+=E{2eS8X@eXowRmW5}dvxSIt1Heb+XFgfX&7CF zrzSoOKj}NmEdRv2rfTg(y_zh{#v1DNBKhcQC&eK3|InBn+s@)z_Pdws5nM|@{etU% zD_tB7IK6;&Ur6~JH-U;@1fHzU!lk+g0 zxP1XXJpIR^-fZIcn;AbS?%sFYD!=bA=BG7gND-$==b6vI?sv$a%lqT|w6H zTrWPg+xX;N5jBs*(ELPZ>Av`YLD71^zkoWT59Kl5N`Gjd9i}{>m7l z^UPKLBDFjIm;6WRJn^MI5ADz>~u&V8O>SyQz6Fvxe}0XrK8fuJY%~Z(~iP+Kjz?8f91H zx3wX!;;-aYJP~g3MdvUYJ7;o!PsFavZ>#4U*}3uA+x`71<9XAt$^5t9_&wsZKit0^ z9^V)6d22E~93GJy%vwYE&QewRL5s-G)j4!iwa>|@y6m)pzwOv@K^_gq+cxNlz;=5bmilVx3`)(R7r}q^8_b-e^q+7` z|GfbJ#qYYz5yZqt7T<3A%9^g&6M~KHi;$MW1xi9r_^RJ2@ zoPfR}2N|8j{5jto&F_4hQ=|8D>08R9c_ZJ62Xj-{K=~!qF-@7d)H|+TF8`r5u3qr5 zB#)(i)!e?9@;y7Mz-{R;ahIVrz`auZf?mxN&#D49W#Mk8ZsESUt9<5@0=c=xzx(hV z+umyXK?WQ_KdpktMdj>(rjhs~{8PDKj3dWk{%xOHpSOb4E(SAcPHz6OU zuB*_Gk!zgKysX!O-(O%XfqqZq5AwHRyXV|2_hB<*5| zrE^(j*Y*J)c?hHA%V|HV%cw&z51=?V{qIuhtQcxAN1+_vK5}Z?@jLL{>N-@vX~p_n zp0w&#DmMmYEn&LRxXVJ;N<*GOK|%){jIH*HLD{6(u8j-_rBGaJaO zz8ZhHhyKj@*&WLFQC$9<{${>8oNt;gA~(3(#DHWwHKslkoue^zGIQ|GF}2Q1NaPL< z{Dnl$3vyfIY-ys)#9Ph^+F;kddZGi_CS*I=u>hU8 z0Ge5&&NDY2{5x`^XPWP5fq$A?xR>`W$p3tBNMsSjU4;*$?DIP)8^?$9?eii0iw}tyKIC{8=0kH8J_PgA z@Jc!L9khYw0i^f5%zXN%*Z5>w^y_@mTv2E4>AdZ?S-eHw1^8*FaE{bdJQv-6q#jAe z$da>q?%zZ?&5e4QF8_c&ViDVy_~c6kZL>Q%?v0`2U`xk+Rp|JUrDI)$j(rA6hh#O% zkDEhuD3471_=m;6%KqPrti5X6WuXY!& zg0U2SpK^*G%eirNd7Ns$cGKVLfLU&%;&hgYQ<1M(I;RtzC?CtqLmPNzSHa`rVb4&v z!Q!a{k9?4mpiQ|;YepM;uj{XIt+S*0&2JS3U*}$!wKTfdnwn<&pW^?# zwTy$wA4uoA{Bi1&-g)TKLjRjppLP?6Os1Clp9H^h$wZg*^nS!{uA|*r^5h`XuByAf z`zYV3Z53nQAA5L}?LQ3}LwjSIId%I@7Ft#A6kE>;{7dJZ#Q!+yW#uGhjb46>eRl%? zs$U;%qjyd(3(i>HNgt789mF(##kIZ>JxB0PzKH7lryuOtddPe)sJC(?m76Nr7mWR1 zHu5j})9^Vgmm8|&csRMd5Sj+-A1c>=XWpfg%K|hYm%Gc)IJE{m$|X_#HGb6DvGt>O zm|T0Vcf7u2vs3UPoyz+u_%0dgknKu$`E~8+?a+SQ@Us}42`zr%GvTv65sW>KyQ(j7 z9Q*1MTT>hU{W#$xA3ZxC*&tqtKNg1L)>q@VJt#l71HDpb|AYN1!K*c3b=|^GzO*-d z3+rb3CPJt76%S&n$e`8;(Vy|Ry8H>EC2iI`*B_VfOR_FMp}qd0N&dxMtSg|M$(bw^ zhh-k|8wzGEk#Z=W!ge*_SDpf``t}*w;?z_A4sW<}z8jNia@W0&2WthCN1IG(pY0{S z^0Diwcb%=fczrH)*Egvg{eo}QFZGS1C0J9)GsR~#E|84sn|ZX2%72Fc9bR*hv3Db~ z>9;CpRC}zY^PzJMy3na+pS2|8-ihijf!}>W(0?1mbNccyTc5kFy%>QUXuj|waOgcg zTVosd@8|vn`iJP&{HW-bUmvFXQ0i5eTM*0%UXSc(-lPfK*Xf!)@RSoQSZ4$4F5CB1 zuQRw;y(aQ6J*fKr!Tu|jE1c;nIJM4OaXGb>a7~9_%H187d*ndX<5*y-9yd^rKUtVs zW3ek48XM@jbBl?P?2z9d$d2MpakBGzCp&4)49nORfWM?|O9zv{8 z@zm3JwzK_0a-5`Zsr`t{BL98Gu{+!E{Mhz8-L~IXs6V!J;I9GCP5Ti`7$(O!+B=X&Jukl@;x_Va>k<*cki-+jjuZo@mI>0->*w~GoRQqI>`T-p3 zD1HsIefd)Ar@id;?nLV5@ZJavN7FuOWFW&B=PJ&Wa&4G3^mW?r-U6TNyt$lDv8sXd z>KYg;kc+?6mn~I46V9Z?IZ-&f3*?+*=PgcMe^PXFUJ7l}81y0H!s{MGe2_CMe=G;U8pE8jkg-+ylaf0@5Y&n*2Ld44y~1z+c~i7#WI z;b$QlI=8cCBtrLUV2JJl<-bb#X38JO9$lTplANqw$8*_c+4${O8sAFyUp&;v^L4@j zzE*GHz`K5KeFN^r_m$LJx>_+T$yVP#OXoEwHnG;4-!y)+`7P(Sp5GRJO;0g5&d-gD zCvctP*TF9;lXpW$R3^Xo!9ADBe^K8(mC4qSOkNz~{kwM~ld^GfGI@%99+k=Q!Sh1# zY>VTs_@BFy$+3JdnUv4$WO7nSCjY{I>Duo(P9}c>e<=?|I^}p5d!NsZqo5{IWvTxyi zj_L{~PNqEvW}xR=QjDkZ(ObJaA0=Ns@YA^-&^bCSY0e7+Zhgo{u6q?a@!5rYJoL9< z(-r&FUXy|q#?^xVARk^iS;SW!Y~`E~%9WknLv9BC!}0)6P;YelPJbl3#D5=kl0P6_ z|C>BxE%>H*kn$#^vkvIv9q}8%md_H#-vD0W-$cDspN3F9!YX(wif#?TeQSi z?&}lSN!9y~J9ljRz4%NFbsYUk^m&)C4s)aWP!wX{fz6BG={fizY^4@-3K~CqJSY9_G@t5o7Ni>8l2kIdY$w_mkh~=8awc z#`+3)mH8Vwr(z=XrE<&%WUlq60GpUO<=xzd+=$;(V ze`P>_l=jKC@7xpzGwb$+V7@yIFObKU%(d)=#yJ7-uZ1z^-^6>KxAI(Z8$EC3`Lni7 zve?XYZb?v|bPVsZ7QcL_^0tWK9d(*OUB=k97!w1}?V)+mVBo=@@xW`NE!4JcG4RG) z`rb7dc*x*5;EfIHd)2_$76<7Q9DE4htAGWwO7oG%HvI?#-7S~^~ zf93K}9T#Jv0M|X>dJtSX155SNd{Bb5KNGV~l{d%b`tU4aaxozl;Ib z7;@6$_~3G1mQ>&}{cs5Kr$#K_)79DAzlJu;s6Tif&X+8=;_<}_AdXU;7i$+#@Z zwMUsD$1Un>#$V8y7R9TY>BBH`RFZe{ed>nuUh((TV*kkM ztS#(W$~kP%0c_={;VX<1jEj1VzjQNoNO1ohbfo^}>npaVZ=Sn@xD@3zuGjsa?fqkF z%bet>(0>i(g|8F->6yMe8`v60%IA0dc%epqjM)u8G!BpB$Mt*2k3%g#&b9mq^WmGe z-tx0lzpvZ-$3uLWWchGLh!4sCk9?T2hkSVQr$!Ej$ME4J5k7o4h7Ut59}*!x+z(7A z5AsE4etDHIpE=ciHha}x(0?v+o64R|Uobg*i30Yai#Tw8sXt+=iz9^QcG~e{J4_tm zi}?LIPhLI{W6hoUG$kET%5OO#=pX1}^iQG#pD{nbEU&@&P;Nfw6Tlwqe2&q>%l#AK zd!UD@i+mI5yZ~1r9+#UlnO*}f#pq=}zeyS8cfAgry^=+2+(BH6*Dkiy_dC1pIK=nd ze}L~}|BHM-um)Z@zHe;VGrqrs9UN>MJHEfm^8H8PbbMdSb(rrPV)%aWPxfBke+9k& zEqVVz4ZLuC|I8lpeMJqp9N(X(jO6|OA--P_;d?%Y?;ZaEdGGiy^8Nf8c;Wcox`%u} zr3PG%?>AV!?>M+5@2_wjmiN?Sm3k)gAK?3l|03UC##Rls?>oN#gnI9Ze($RRm*e{h zmhWE<@%{D)-wVI3^^bp^(5x#eX z_`dEvoExfnk{o9x=N9{Q1@4Tm9o*No2j{B$>)IFl z-S{&HcsFl3z?)AlBxjS3W(~}-t96_X`FM5Gw^{Y@-F)P z4?Nd7iqYr$H?U7%%=5qS{FPHGzkii8$~MP5CoN^vdd3NwKkm66+PXBJzp($Sfgd+H z@Z;{3kA?4(#P3PsYiXRff_mSW$p*d}^URIP<-D`q7eiwD_p{;wsOB++q;u_9nY)G*#+P1TBEu!4vk#Z^{+33zQPS0L9Fy59E-y~PUH=T0Lk#Z_Cly(rV z4COpqt}#?@4CRJ$FF6#gB{~=Osa%$Fy?i4aufRWtZ#!oazQ}zYzHU%Q+QF6G*vdJc zW8o9BJQNw8CjXT0vV0T7Mnmm2|B=e}%G&n25%@E~y}S0xzP;35_EAgtX{fvP?9(d-?dHhPK>{8&}&V968J$34)PLc<~ z7y}OBS`sQdgR)-=l}$dpYG)e_q5M5P=qt+Z=rgdEQV+?=_J_b3>O=eI$O+1uysb9_ zZv${|3iYAxL%>blJe_Nixf&gCrGMwie3L%Cf0I4C@MDyVla)EZ5-lU(-HskZM;E*h z?^d5pP9U&XSiK;<(F84BmX=0fPJEMSIc=xj7z6EwmKA;*unj$npe5Ts-O_S)6 zzYp(ducNBaG6P(qB^9EjQ?#U6?|s%Pe+sZyg!HNS(rIZ~4=rQC7uKguz%#V4hCXPc ztNi3irj4dpS`t-g*}!|z63B{eqxsMxxm7#$uwP8Hjpkcgz6>peH%aY53Alxj1>52(BnCc}ENFG!9(RHp*LCJ`c=haEIII1>v)8^kR#n#kA1`OUqBH z(6Y9zl9qXn7TReI<)dvh&(d-sFgw8)ZlhVii?-3s@urRLv1@*Bt3u0Ia1DvH(OgFh zZPW^`XdBJ7w2TMlD@T>L(WYNIT2}ad;10LZSWC;?Dzp?1uH?%cM+}VTxSX%yu-?y2y$l*3x z&%QFzHtJ!`vUFX-(sFziS~__jrDe9Gg*MW<=V%+vwzT{jTBd_Ltn0?#?`T=!PXKnf zjpng;y7+QH6)6#M+Ftgwbw^15+(Kc#ju2XF^+tTtt6)IVHw9#YDm2KqqcDV$Y_lzlTqowPiB`w>y!e0(-*G4y{vi)1^`rQ8$E%2_IjhCJk zJz2`DO>Y$*Xwmf=U@YT4Ty{vlP?YbdGJ$WAN6u~8>q2EaD0@w$tjZ-)*kNSA;qB$S zhSaG8^Fn3E^K5QhS?a2@(p}j_d?z|T7AouU>?1K{!}LzG8@>7R={JOqjjBRFfK2j=sW-zuX zWZ-3o9orD2*4U;8_|dUVq1}va`tcPD;0Vj7TlYC6<>svG|e zubekyo9RER^p%%efAyE(84n#{e^vWam(PnX^qWI?%-DuK-bb3T%|V$<%IbYgsNUL# zEL!j0`y%wh)~k9FTj>dP&6-XFWR9o4{`(zbX9Y@SFXkgr}I-IKJ=w z4Q2S{xPOfQcK$o~ZQ^=6zgPGrDAU5PlV6T9Gx@b~J&xZBe(U&cUdKL?3wZ(P5~^*p}0lmB)6Z{S&m?|Ql3&UG8tWBJYaNdj2= z1ohj%yN&$j@Lc=IPv`%!pPKq=y@~QayKxz3iA4miMDuw_S|8K0

c5gwO3o8cSTfjU#4GbvZ!Qz)+M36Fml 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%0ADxNbQ-`U`}6~e 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%0DN?EmjP&U^ioC`!hKtUeBdV%#<+hIK1gqUbM|_ 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 gRr|;g&Mx9t Thu, 14 May 2009 11:11:00 +0300 diff --git a/debian/qtmeetings/usr/share/doc/qtmeetings/copyright b/debian/qtmeetings/usr/share/doc/qtmeetings/copyright new file mode 100644 index 0000000..e66d2fd --- /dev/null +++ b/debian/qtmeetings/usr/share/doc/qtmeetings/copyright @@ -0,0 +1,40 @@ +Upstream Author(s): + + Hagman, Jaana + Holopainen, Tanja + Jokitalo, Janne + Jormakka, Ossi + Kasari, Olli + Kolehmainen, Mika + Kulmala, Miika + Kupari, Teemu + Lapinkataja, Jan + Maslova, Ugne + Ollenberg, Aki + Papp, Zoltan + Riihola, Juha + Sihvo, Jouni + Sirén, Mikko + +Copyright: + + Copyright (C) 2009 Ixonos Plc. + +License: + + This package is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this package; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + On Debian systems, the complete text of the GNU General + Public License can be found in `/usr/share/common-licenses/GPL'. diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..5a34552 --- /dev/null +++ b/debian/rules @@ -0,0 +1,78 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 +APPNAME := QtMeetings +SCND_NAME := qtmeetings +builddir: + mkdir -p builddir + +builddir/Makefile: builddir + cd builddir && qmake-qt4 PREFIX=/usr ../$(APPNAME).pro + +build: build-stamp + +build-stamp: builddir/Makefile + dh_testdir + # Add here commands to compile the package. + cd builddir && $(MAKE) + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-stamp + # Add here commands to clean up after the build process. + rm -rf builddir + dh_clean +install: build + dh_testdir + dh_testroot + dh_clean -k + dh_installdirs + + # Add here commands to install the package into debian/ixonosmeetings. + cd builddir && $(MAKE) INSTALL_ROOT=$(CURDIR)/debian/$(SCND_NAME) install + +# Build architecture-independent files here. +binary-indep: build install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir + dh_testroot +# dh_installchangelogs + dh_installdocs + dh_installexamples + dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/resources/BusinessLogic.qrc b/resources/BusinessLogic.qrc new file mode 100644 index 0000000..0f7a535 --- /dev/null +++ b/resources/BusinessLogic.qrc @@ -0,0 +1,5 @@ + + + xml/errortable.xml + + diff --git a/resources/UserInterface.qrc b/resources/UserInterface.qrc new file mode 100644 index 0000000..dc3301b --- /dev/null +++ b/resources/UserInterface.qrc @@ -0,0 +1,16 @@ + + + icons/arrow_left.png + icons/arrow_right.png + icons/button_current.png + icons/button_settings.png + icons/roomstatus_busy.png + icons/roomstatus_free.png + icons/popup_cancel.png + icons/popup_error.png + icons/popup_information.png + icons/popup_ok.png + icons/popup_question.png + icons/popup_warning.png + + diff --git a/resources/icons/arrow_left.png b/resources/icons/arrow_left.png new file mode 100644 index 0000000000000000000000000000000000000000..0b7273cc973ba983a75de5b9b307a849b3af82d4 GIT binary patch literal 875 zcmV-x1C;!UP)^M=` zNY-Q)6Pw0!jP2)j27ujq7;v;!+@sVFV%qt-Ag*!tkE*I1Byi|%&NmkPg)NWuswI)`_TK;kn->z#zLN)~~ zer0VMEP~UkwK0WrVp47-s}rbh1Z?YA-yrwq>YoRjdcuHr%FudHW{Djdz5z?&m3|gT zMwNxh|NbCAH%T{+t`_j*wo+q%gvBBEBiDi57v-%-E9J95YC(R!RBYv0=jE1JEW@dX z2BDUiDn7+T$AE*k*%l3!mlC z{0GqTR!{?%fa!h%6ctJyJ?fIpfqPgI<@4jC27x7o9sq+q27x8LWLwe93xmMsSmpAN z_<wv~$-~;KY?u!9!Bx8pMvM88FscGANKU7O{0^gnaxfS3apF?ywiy<5 zIWR3z0kOz6m?2`3%QUm%4pYDi)&>Eh!>ZW;JpR(%OSFrErcb-!D6OZu(oL?msZ|E- zJPnk&OaRMauLS%j>5VU#0G7ku2-F`@{Wi*plcx!AcvmYLOWQREfcOY7-n$N9H}3!4e?l)<-?6fM5xTeNJQijqF_X9%~dVE7J24W(R=Dw}7(-*szs)Zd=H^+Vm$_ z?7{!nPG(cU!gp$8pbSngd1FpzHsaj!!r%eQtI6DNqTVa_)^crOc~VB7t^9o^0W3}d ziOE7g0VHROg^ILtA;1Ua4;(1ufKyh+e4__pa~JvwQc6*M(DXbF<*qy_QWi+f$>U4K z3S*s-TV_Fls)sMaEk63k6`b;Wu=XHTJt~1R;OlcVt3iT}%i#ZjQm@1-J_H_sTJi2W z@Zq)z-m_z`fWE6Ha8N8XPuk@*YIxY-iN7_%kg)w-K=iQ*N<3bty;n?7yeEGEPmck) zlnG#i9b^Zi>bOl^frf7aLyuIuyhXa-lpxRPSo4)C>d8KCT-`Z`dgOwGV}dHLhx=7Y z--0(@RPIOexk36ET-`ZEF!s0nK2-};S84p>OyOa{;IQv+0g(r#DTg%$o$9A*=0fRe ze@$VZpHh9D{thcT(wZ9mQiW*G3UJj$VTN#R8xr7Y?lc7~aayEq+VHi8Fx+F@f4TWu z!^9Vm1bw;;x?p3;?8w V^JlRL%EJHv002ovPDHLkV1g97npXe- literal 0 HcmV?d00001 diff --git a/resources/icons/button_current.png b/resources/icons/button_current.png new file mode 100644 index 0000000000000000000000000000000000000000..6df40877b01626724c6037ddb78370d212e98d68 GIT binary patch literal 594 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 P00000NkvXXu0mjfO$4Yu literal 0 HcmV?d00001 diff --git a/resources/icons/popup_cancel.png b/resources/icons/popup_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..2c9d6df999bd3b7da8cd5c7d681a7cf5ed789618 GIT binary patch literal 2143 zcmV-l2%z_gP)P000>X1^@s6#OZ}&00009a7bBm000XT z000XT0n*)m`~Uz55lKWrR7i;?i?}MR3d>%EU3T_1 zvomvU@B9AyL2RGTPoCe$=SQXSaZE-LhzMVV0f0!jQVKFg)x!`;oB(W2HwYvpQYiIBH6tguJ6Mb0|Ou}goHNc<7$-$(~du9=FCOs7iV3N zZ)$24mL*iJmJAIK4<796?>atG>OPw&IX{yj1yIOr1OST&r7*&9{KdsgF&1Ob_yYDL_EffBP}hk zzzhJ$3nAZ35?#1*$>w`EY}}|wno}oEC}E5=T9YwM7QrG060KQ9kx@xvBCVmd<|P*{ zJm=ij);;UDZ(mm{js5LgQp!F+gBijA@Bn!*NpxoM#rJI5uxVqZR!^qJMj?{K(osYy zBJ$%vmInuTsP||Ro*JgSR?-(j>L>z*p;ao4=v1~~Z(Y0g=|j2fiv3#aEFd%h%O;ZJ zfIcy!eZ>Q}+;-o1xuSI#Sj0?ml9-HTi8o)|_0jKMf8)WRRzDuaNjaTz3TIAhTey1R zf_2Nke8Kq@ZL~;~tkoy9mv-gmuV4N6-Ouj0cs1iQ8xZocVv&3L`s_77UjJNi#;mql zc@ofCm^g-sW3W;rg1UUB>)l7b5=QT=Cn^<{>ZfvL|6dLt+4kP?W6|>2#iam&;#dUr zn(8Pt6vnC*|J9MvmvgPHLe6ZPhK!EatXsS8CzF$94Xva}5~w&ah+`240XMg_y5E>L z_r@na{P0~TijOUl!Z`!Lav`~EVsg*!fxgmpbLXy%wKgJ2L>w!c=ewO-5BKaqQ`4x- zPoIWmXU+Z9{Q2j8sa`HKD=j#vb2^ShY}lAM7GbSsJTI48)-mgb-}fBpLKGjJO+t;Rj5{r=R-vPaWfb9HZ|{4%22b97&r?tR+z)C3r$M!eaZ~UM z{u9?<_rgDXciG#_Do;cOA<$e44t{)UxX0H96o#zB5VONOl0Egky-0DZiF^PM}M-INPzQZO4^2ny;oQ%HOM_QjXHu-6t>?PXTYLO6|pDvgi# zIdKe2YfzFPFGOQnih{+=&|w6f3ZcUg+L9n`bRED=QaTR}oY->b-(TW~7cJT{dSY0c z(NQr~shBL_J>Js1>xZLb-^?ZEwKgfCMow`YDJUrc--iezKu96#VK^2|RT2|L&_Mtl zMmW>=Iw}ArjL|m>VX48BzrF3Hmo}w8cwh3+kg>I**tSi<&AWf@DDK>wl2<*iHRk!* zIc!-FVF-+rFj*Rz3_%q!R1f;c#>R#zj$wj;YV`n@wzpr_2jpWwp9V>e^_E|<-qe!w zrIXE>bT&)Mb4^=AtNVv@&Uw1C!QVKixpgLTc{mLXXa@HepYO?Xx$skXaTHK%;shysp|2TyQa#Un!Jp(J>R5!pJF$|(_7DI{Nu%! zZcY~((zfrx@qE}`23=9~4uc?CrNqCE^**PAI+9uqrMKP^!-sotOUn6mmqm;A0ZAJm ztAc2~ZQDUL+;Z!Q!C$m^X_?98kn(*JzDIGs(KxvrY~N=umy;tf!P{Z-iYEocS(b%r z81MXN&qv+${Rd=FDlt0_`cE<-GoWwQF>A;F7TZT`er*2#_Ix0;Ib# zllo50mJ9w(P@k~jqeNa)CRK!Ew_h}U+E*J}+S1a^z;Qh?Qkn+WmG2E6ANkpT4&A&8 zD4l3(5>_413jlFUGA<#V)_L0&<$+#vJfB}Ov?Mic3DJ}<{hLu$#6owpQf!C${E1XTbd0EjQlVhZ4! z05K8iyEf?LN0Sz+&4MiFbB~eC2C>mPR7%H685k`DGkHRirOIue7F|yLK#Dr4D69O(X2mq~GvIOCtJ@AD1pAr=SF%0l-fDNJb zJWE_Y-?kUDI8M8ja%?4J_*s(lADpUo9gUI$X<*`NA*h8wlLiKCU=v}48AF|&_%Df= VZLSF;|M~y`002ovPDHLkV1n9V18M*O literal 0 HcmV?d00001 diff --git a/resources/icons/popup_error.png b/resources/icons/popup_error.png new file mode 100644 index 0000000000000000000000000000000000000000..900f9c7a5c45147d6dfc66e8ce484c1215947677 GIT binary patch literal 2068 zcmV+v2P000>X1^@s6#OZ}&00009a7bBm000XT z000XT0n*)m`~Uz4#z{m$R7i8R$Iq9#oH)tXY6#G=~bw;vAJX{Y;TKkUr&-<{|G?EgnAR;)mwP=F8uT5Avyan9M))MOk5A*Pab*n>F=!HaPO%d zZ(*eKHB6rvQaT7VV?@Llh%u01Xi;C!>;;R!S6xX*!}!#5Tc6zcVb_5_3n92tDppD% zl}dq2DdGD*X|1(wnhh_#w(X6tEW7xtUEkjr2AvDJA*7uK}1Z{QcF=OOo|hzWeQO zhDN1g2+w0gM9eS%0H42t2*i197lN2&Gu3}Yo0Elj>DRxpHaC_F29Ec?4}eQ4HRGH! z0O)w=XTMz8+Pdbwd+rWBDGdk#?l=(XG~#L%+%#*283Qv6AdVqi7qX=VDyV`rG%|Jc z2)A}^SKqz!{>R$d=3N6IW{hzTt@Sx`<}80=%M;HZ>i8kdQi+3eFx$qmXP?Eq4I7Zz zw-3?S7!211I0uvhml92P-;LBwHz6DxL^YEEPbLYO&y+WF2CH9u(W3Wu?fAWvGU8e( zZ2HlUx14kGS7dzOh`cm(6W!o3;E47ha#;{yNNB4*H(gy38{-Fod7WA@k1L8k3VenM$7}w1yD}A(#*#B5)}YmP#1!?L~Qf9Lz9K%I6VH zPC`X-&BB5qUDtqo7$O3|iNJ`!83Q8%0jNe1v{vAP8?0O^6_jNIjDb52D%mV@d-tL| zJ`UkHnDfjtP|KE~S}1^7Cd`Bj&otn)&cxd7+u)vi9*iJB!nWaZ4p#`cjKPr(k(8LO zRHoR8p-=x6)zyQ09+d|NG1k?EC=6jZ4qCTt!SsR!sANW9)Fr?xKKSS;X3U=phY0Dp z^Kiqici=8u2%}PgXIXHBfD35ZJOhEWi({ji6YP^i-T$c6CF7~FJj-`?0|2-XXxXv_ z)rAY;XGdXp9!#x~ee4$)yz3rBeMivRz6fRvBwAXqcGoVrOO`;!FR#TYG3 z9X~mm>1W42Ieg&L{P;(eWC{vU1OSGCz;zLn%P<@V77>A6k`mr?_+fH zX7F4Nh7cI)co5Xv3rh$vA>b5?*tc#SPVV22Y7}AL%{Rk4b_{NNJ3Patlhw6{Yh5|~mdiZ(we9{KHWw!ZkMKW<8=(~LWg%Y45Q_4oDkT(fEO`e zh=)E^hGCE{m%%3|YyQ_7bb1ynBG^*GsaBDwRN&2-3n!64l_+BOZ4z%CKX&l(M<4kf zfQoI~;8NP!_kBJwF>zvO;Ml;`9S`3e=f`Ne|7a|TYTpb1RtxT$h!8LtgK68alSvp# z>5A*d?gJfSf2A^b&mHS;i{f|?0FzScGs&nq=X_-3vqOEo-5;;q^uTpZ3l}-%z8)2o zN;NRh8l*LGtzjyqMG%0M%PMB1+0|b}+5=6w6 zQgMAP000>X1^@s6#OZ}&00009a7bBm000XT z000XT0n*)m`~Uz1U`a$lR7i=HmdlS7MHGj>Q&nAk?=Y8{0mKI?Mj$bf7#C=C0ddsu z2)fbe#x2H2)VT4HxX^`h<<5mh{{h{YprRQe7*`=mYS0)* zMn-UdpF77hTQ)N{GZn*z?b`+(*feTUON^ijF+yx#HTK5+c8n0CqL{2%pJ`n?ygSX# zA1l<$uX}z`7h{y`*RHCLe*WF|9q;dYyhgM}3}^{aQ0>{E?J@0pWmi=Mbat+g{ePYO z?D*b~-)8#g*F}iNN#mx*jW4~tZRdx(o^D>qr>?4mf{2vy<+L5ugb=`(G6CrMbnHqh zy7P(0k}Uu9%hS7GJzz9H<%$p(T)%nis_E;TnVOZtI4A9yyYUJ|P+|-WRD{`jB=N52 z)AdzCP^!LOthxKafzyNQw`j;tImBS2R_o0M6>^l=RiV>Dgfvy&c;#L;JXB@uk1PDR zZvvkVpsL-)s48k8D^yz1fK8UEDh@zHA&pjp@$m^J>#;8dFjknH%6V7l94L}Set8%ag0B4NQY-HrENu<&O6L0ZXz*L13XD8Y5&VJV3 zZ#j1S2Dw{_spe?Z{x_!!DKrC0NckHhq!vvKn#e63FT|)k{KycaPp#&z;R-vquA@5a ziNXT^Zd_`6a&^d5JQ7n>>^!+3gvi=^Ja28UP|udpXa)8jo1me~5GRYur9SQcTR23@ zOp*lD!#oJ4EUz5H#7x0lE0X0AwHCYwC$|(zchTPWK`1A|CqN2f7J(>|2+l^VF*qmS z5^x^u|Alli!QTd=c(16lB!SXjQm`xyF=pPLBncQ#rxxZzYzjj|2`wDWRzwn4`ri(+ zFcS}U2}lvK6~pr3W#l+qv%M&LCy>y$@k>Ec%ka<;Ej%{KOMiDK5NjY!Ar^~31d4!7 z9m|)mASVTBl#t|hd(ti}gZ4LGl?7k77-49& zXpfKf$6n7S7D z;q^}<*>S8FhenMy5srWRUOe^lZe8){D3DMTP!v5#r(3(YZ45XkWc69b4~|hxA8GH8 z%GA^pV`F24s768~Qe-`;`sM1LD*8?r%)h5)N|iyd715CEv(G)xzsbQ;SgnCRGXMYp M07*qoM6N<$g5R_>e*gdg literal 0 HcmV?d00001 diff --git a/resources/icons/popup_ok.png b/resources/icons/popup_ok.png new file mode 100644 index 0000000000000000000000000000000000000000..37b1d3a04ccc42774ce72e62885ce81fe09c4e5e GIT binary patch literal 1790 zcmVP000>X1^@s6#OZ}&00009a7bBm000XT z000XT0n*)m`~Uz3ut`KgR7i<+mTPcTR~5(qd++nO&xAb43z9&RCWa7<#kQ!OMp`Kq zoT*ePI~ha6#*x zUHkrYeCdWSKkByUT9^Fb_J{ZE*?X+Halu!WQk+pnG)>berIZjdUGHXb07ObDIp>^G zsz3I~x~KN+d$q@^PnMs_oa8Alfy3j6`1!g2F8a+&&mUa-tw*;JO2V9TE~Ql0vznr- z<^M}5r365#DynLiw%@aA^|v3~IJa%?`~m-h@TDuOLUXjZJr2$k{V3fpFYqC?eB;U| zm#w&W%^Uj<>^^ez$o}zUd;ox$2|^vmkqr$EtxrAu%xl$6)eUNvGUFI`F^sfGD@vHA6et%0oC|O+!F>ha0i?qa zyXIhVaT|7i?Wfqj{3o>1t_kU1R= zB^V`X5UwGa%;8Erj--{w!!7qr6rq-HA>pJD zaMH;53Tbd?UJ9v{3oaBcTf;Sq|nSNqU)>2*YEXCaqOK z$YrxhaDtR&;aVbrR5FV@gKcP!EQFn~aO&zA)ECW0OQ;4yNo0HrFP`5EvyOs99f**H za7c%3JDL1%pM)sJG+Z7Yx=`R1B4fF9U~CkATB1mV08d~=-C~qzCYF|c4n!j;D=oz< z=l3CAeho&cfCyPY#71Sf6eADICA4m9M6jU4VbqhR@RpBUOb5Jh(KJGgkFay&d($EcZ>GH+P z$^P+U3nB}V&AAf7!0$ge2upKOH@^mD<)x^vs{_{+cAxtlqO}1KB4BD7II-2Ov)bSf zx#w+ep9uzn0455P?=e&efw#N6JC~Q<4afGOD-#*&;F+G6P*7Bamc~}hsjS7$qq}e= zJdA>H2w`1ANhpXAg?g}jHQw%i>kR;7WWp(@L1Z)%MbCRZe=Zy||I=2|%Eqm<42Mjd z%k*QzfnOl&+Bk6hujuUWL|Iia3)Ph~;rX8Oq>F-R1l_}jP}i{t-+6N_v}y)M2|}8NvS<($K~sLC;CtBn z^4>qVu6GU-POPRu06fCeEQ0f}tSXSswH`28cl*6$d2% zWv=|9{s6n!6Fd9np@T0NhJl-7rbCMWz;in~Hr-d=7H^(ir^T~L>H8dlOL(3SU-+O( zgJ}fSJaGrD7`n~b_S;|n48Rqo6wS}#|3RdtX)KXQe6nNvbL*d2{9Vk8RD%&+78%7T zH!D#exdn~I&8k&jBHNuc`1P;0Y>CCj{zfS!N-24>pf&>pQcA%XV;`P5xxZ`oiyNNZ zyy^LWWdEhoW}HW(CJ9C~RB4rDQTjHvbL;k9Cr%yT#u$STBL99cGh}i~8Do%A3PU&8 z;dlSJ%d#@*$2V@?ZB&FJZXl;3GEAI)$J@H&>219yk8Lpw!xKUf#+YJ^fpd<@vz`z# z6*Ad&LI_AHAtn~siPOEE>(@Q<{_Tsuu)cQQ{6#~9gM-ITy!S_+dxrri&+~3{)1>&M z)hnf-TL1t607*qoM6N<$f|XiRT>t<8 literal 0 HcmV?d00001 diff --git a/resources/icons/popup_question.png b/resources/icons/popup_question.png new file mode 100644 index 0000000000000000000000000000000000000000..b91d1393cda86e47c8fbd49dc8acc482f5226205 GIT binary patch literal 1646 zcmV-!29f!RP)P000>X1^@s6#OZ}&00009a7bBm004C` z004C`0o^ll%>V!fAW1|)R7i=1R$FLY*AZPa``m}_y^^jJNq*b1{FD)Pl{$4*8xkm` z;6B`98k*oXkU}Y;wD_m=H_)Gc1Wf;0D1;VD11Yp_9B4^tshz}e5}U-9g|THdv7Ok~ zm2{uyu^%)2xRRW>6&X03k3HwCy=G?346v3F5s`@qpf!B}5I{sG0@0d&|M!51jjAf3 z0`4p3s{0-rShx33e|2y)A4$o2ZZ{fBvkP}Foezzfi$LZG#2|vI$Y+9d0VE)*mWOtI z=eemTjz4$g(7w^VV}m+0P@rO-ky=4*DZ|Wc4QJnc|Jqw;|8lB!?T^2OblmFqYl09P zsD#4M)S;=PKmXl{}mi;)h*6$o0soZXDK5%R1)fb^w z0U|@h5uh@>_nDJVe)s98N2=;=KxDX^F6$H%JIbi`Mu;LpE+Q0ijNX#L*7ZfqFF7nW zJcxlLQzkgyH?U><<(dD?C5zMV5t)cp#Z#`DAOHL}ez1PfpxFuRgTlJ*lQ}`X$)Lrb^@;-%46TbZ~X{hK!c9}pl5L6(DvK`LA=)@M%69FIuAw+?|OfZH}Z(4kDe;*DX=!b}c8Te>E!@u6G z!!QshVVQ{_PP~Hhs0?f!U0eZ?;7hHJL(wQW6F`?iOgGVSTd-xMfWrs+5u_``^Ou`= z?X4x0i;Tbo>lDs+*;@}ZI}WO-tTGYR*468Gv0Efy!_fcHVzjcs_<6ef+EcXOHGg4_d2*+OCiBES3%UrH>|aoyW>%BD~1Ph80d>o z=`rXn88kZnQw`|O{S3x*LzLKnG!wMqfTgC#{UwJ+qfJZ!S^M_QRgm_=)eGw+gWd|3d{^J5r_%xIN*hoJMhiNhcLCjANzMzaOUg+ z@;P1=Vw?&(slcTzB?8~S_R`Nfwdt!w#;~de0Jt~vhuk0R2SGEYFii zM!$Mw^W=~J%27`hWPFq>QK67CB0!vYFcW&q5kP=-0`FY#Hdc^EKC9oJd*k_|?fLgF z6EUxh;!jP+m5Ejvn%KSN;K>(z`o^XdP=H`e6rw2VhD!t?1nNdYm7vsKxbnAK@1A-# zso(r)<-)7~#Y46NWbzd=wEgH)1KYm*(_-(oM>#4nh(I7Hx;uvW*e8v-^NSz+{e{}> znLn#IB4T=YnOJLEVO0g7LY$-W;GR7_)g1>5DC5&!@I07*qoM6N<$g3VMGT>t<8 literal 0 HcmV?d00001 diff --git a/resources/icons/popup_warning.png b/resources/icons/popup_warning.png new file mode 100644 index 0000000000000000000000000000000000000000..46edc41cde48b61cfe457035cd13d85213ad20e1 GIT binary patch literal 1375 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 z


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 OzgsHW%5@0Ku>S$U2bMqp literal 0 HcmV?d00001 diff --git a/resources/icons/roomstatus_free.png b/resources/icons/roomstatus_free.png new file mode 100644 index 0000000000000000000000000000000000000000..14aa52f9aa5f72bcdfeaaf88538cbe4b48878a28 GIT binary patch literal 43508 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 V0t;ta_K3ibilUlA5%!+nzW}$0WxW6Z literal 0 HcmV?d00001 diff --git a/resources/xml/errortable.xml b/resources/xml/errortable.xml new file mode 100644 index 0000000..d611fda --- /dev/null +++ b/resources/xml/errortable.xml @@ -0,0 +1,30 @@ + + + Not really an error. Everything went just fine. + For unit test purposes. + Communication: No Error + Communication: Unknown error + Communication: Host name lookup failed + Communication: Server refused the connection + Communication: Server closed the connection unexpectedly + Communication: Server sent an invalid HTTP header + Communication: Server sent an invalid content length + Communication: Invalid username or password + Communication: SOAP error when fetching meetings + Communication: SOAP error when fetching meeting details + Communication error + Failed to change the operation mode. Error creating internal file storage. + Failed to change the operation mode. Error removing old alarm events. + Failed to change the operation mode. Error sending alarm events. %1 + Failed to change the operation mode. Error storing data about sent alarm events. Removing the already sent alarm events. + Failed to change the operation mode. Error fetching data of original hardware key settings. + Failed to change the operation mode. Error storing data of original hardware key settings. + Failed to change the operation mode. Error changing hardware key settings. + Failed to change the operation mode. Init script to auto launch the application was not installed/removed. + Failed to change the operation mode. The application failed to know the current mode of itself. + Failed to change the operation mode. The application failed to store the current mode of itself. + Error storing data of original automatic screen switching-off and dimming parameter values. Using the default values instead. + Error fetching data of original automatic screen switching-off and dimming parameter values. Using the default values instead. + Failed to change the operation mode. Error changing automatic screen switching-off and dimming parameter values. + Failed to change the operation mode. Error restarting the device. + diff --git a/scripts/qtmeetings-devstopper b/scripts/qtmeetings-devstopper new file mode 100755 index 0000000..e692f6c --- /dev/null +++ b/scripts/qtmeetings-devstopper @@ -0,0 +1,13 @@ +#!/bin/sh +# must be in /usr/bin/ + +case "$1" in + restart) + SUFFIX=req_reboot + ;; + *) + SUFFIX=req_shutdown + ;; +esac + +run-standalone.sh dbus-send --system --type=method_call --dest='com.nokia.mce' --print-reply '/com/nokia/mce/request' com.nokia.mce.request.$SUFFIX diff --git a/scripts/qtmeetings-launcher b/scripts/qtmeetings-launcher new file mode 100755 index 0000000..7645f9b --- /dev/null +++ b/scripts/qtmeetings-launcher @@ -0,0 +1,15 @@ +#!/bin/sh +# must be in /etc/init.d/ + +case "$1" in + start) + test -x $PROG || exit 0 + export DISPLAY=':0.0' + cd /usr/bin/ + run-standalone.sh /usr/bin/qtmeetings & + ;; + *) + exit 1 + ;; +esac + diff --git a/scripts/qtmeetings-rename b/scripts/qtmeetings-rename new file mode 100755 index 0000000..19b525c --- /dev/null +++ b/scripts/qtmeetings-rename @@ -0,0 +1,6 @@ +#!/bin/sh +# must be in /usr/bin/ + +if [ -e $1 ]; then + mv $1 $2 +fi diff --git a/scripts/qtmeetings-updatercd b/scripts/qtmeetings-updatercd new file mode 100755 index 0000000..50a2d4f --- /dev/null +++ b/scripts/qtmeetings-updatercd @@ -0,0 +1,14 @@ +#!/bin/sh +# must be in /usr/bin/ + +case "$1" in + install) + update-rc.d qtmeetings-launcher start 99 2 + ;; + remove) + update-rc.d -f qtmeetings-launcher remove + ;; + *) + exit 0 + ;; +esac diff --git a/src/BusinessLogic/Engine.cpp b/src/BusinessLogic/Engine.cpp new file mode 100644 index 0000000..2f943a7 --- /dev/null +++ b/src/BusinessLogic/Engine.cpp @@ -0,0 +1,241 @@ +#include "Engine.h" + +#include +#include +#include "Room.h" +#include "Meeting.h" +#include "ConnectionSettings.h" +#include "Configuration.h" +#include "CommunicationManager.h" +#include "DeviceManager.h" +#include "Clock.h" +#include "ErrorMapper.h" + +#include + +QTime Engine::endOfTheDay = QTime( 23, 59, 0, 0 ); // end of the day is 11:59pm + +Engine::Engine() : + iClock( 0 ), iConfiguration( Configuration::instance() ), iCommunication( 0 ), iCurrentRoom( 0 ) +{ + // if reading of configuration fails, signal that initialization failed + if ( iConfiguration == 0 ) + { + QTimer::singleShot( 0, this, SIGNAL( initializationFailed() ) ); + return; + } + // initialize communication + iCommunication = new CommunicationManager( *(iConfiguration->connectionSettings()) ); + connect( iCommunication, + SIGNAL( error( int, CommunicationManager::CommunicationType ) ), + this, + SLOT( errorHandler( int ) ) ); + connect( iCommunication, + SIGNAL( meetingsFetched( const QList& ) ), + this, + SLOT( meetingsFetched( const QList& ) ) + ); + connect( iCommunication, + SIGNAL( meetingDetailsFetched( Meeting& ) ), + this, + SLOT( meetingDetailsFetched( Meeting& ) ) + ); + + // create application clock + iClock = new Clock; + connect( iClock, SIGNAL( tick( QDateTime ) ), this, SLOT( checkStatusOfAllRooms() ) ); + + iAutoRefresh = new QTimer; + iAutoRefresh->setInterval( iConfiguration->connectionSettings()->refreshInterval() * 1000 ); + iAutoRefresh->start(); + connect( iAutoRefresh, SIGNAL( timeout() ), iAutoRefresh, SLOT( start() ) ); + connect( iAutoRefresh, SIGNAL( timeout() ), this, SLOT( fetchMeetings() ) ); + + iDevice = new DeviceManager( iConfiguration->startupSettings() ); + connect( iDevice, SIGNAL( error( int, const QString& ) ), this, SLOT( errorHandler( int, const QString& ) ) ); + iDevice->initDeviceManager(); + + QTimer::singleShot( 0, this, SLOT( fetchMeetings() ) ); + + // TODO: continue implementation +} + +Engine::~Engine() +{ + while ( !iMeetings.isEmpty() ) + delete iMeetings.takeFirst(); +} + +Room* Engine::defaultRoom() +{ + return iConfiguration->defaultRoom(); +} + +Clock* Engine::clock() +{ + return iClock; +} + +Configuration* Engine::configuration() +{ + return iConfiguration; +} + +DeviceManager* Engine::deviceManager() +{ + return iDevice; +} + +void Engine::checkStatusOfAllRooms() +{ + // iterate trough on the rooms + for ( int i = 0; i < iConfiguration->rooms().count(); i++ ) + { + // and check the status + roomStatusInfoNeeded( iConfiguration->rooms().at( i ) ); + } +} + +int Engine::indexOfMeetingAt( Room *aRoom, QDateTime aAt ) +{ + for ( int i = 0; i < iMeetings.count(); i++ ) + { + // exchange server ensures that there is only one meeting in a room at a specified time + if ( aRoom->equals( iMeetings.at( i )->room() ) + && iMeetings.at( i )->startsAt() <= aAt + && iMeetings.at( i )->endsAt() >= aAt ) + { + return i; + } + } + return -1; +} + +int Engine::indexOfMeetingAfter( Room *aRoom, QDateTime aAfter ) +{ + // seeks for the next meeting on the SAME DAY + int min = -1; + for ( int i = 0; i < iMeetings.count(); i++ ) + { + // if the meeting is in the same room, on the same day but after the specified time + if ( aRoom->equals( iMeetings.at( i )->room() ) + && iMeetings.at( i )->startsAt().date() == aAfter.date() + && iMeetings.at( i )->startsAt() > aAfter ) + { + // if there was not any meeting find yet or the previously found is a later one then the (i)th + if ( min == -1 + || iMeetings.at( min )->startsAt() > iMeetings.at( i )->startsAt() ) + { + min = i; + } + } + } + return min; +} + +void Engine::roomStatusInfoNeeded( Room *aRoom ) +{ + if ( aRoom == 0 ) + { + return; + } + + int indexOfCurrentMeeting = indexOfMeetingAt( aRoom, iClock->datetime() ); + int indexOfNextMeeting = indexOfMeetingAfter( aRoom, iClock->datetime() ); + +// qDebug() << QString( "Engine::roomStatusInfoNeeded\troom:%1current:%2 next:%3" ).arg( aRoom->toString() ).arg( indexOfCurrentMeeting ).arg( indexOfNextMeeting ); + + // if there is no meeting, then status is Free; otherwise Busy + Room::Status status = ( indexOfCurrentMeeting == -1 ) ? Room::FreeStatus : Room::BusyStatus; + // if room is Busy, then check end time, otherwise... + QTime until = ( status == Room::BusyStatus ) ? iMeetings.at( indexOfCurrentMeeting )->endsAt().time() : + // ...if there is meeting following on the same day then check end time, otherwise end is the of the working day + (( indexOfNextMeeting != -1 ) ? iMeetings.at( indexOfNextMeeting )->startsAt().time() : Engine::endOfTheDay ); + + emit roomStatusChanged( aRoom, status, until ); +} + +void Engine::fetchMeetings() +{ + // TODO : define interval correctly. at the moment it's +/- 14 days + Room *room = iCurrentRoom; + if ( room == 0 ) room = defaultRoom(); + qDebug() << "Engine::fetchMeetings for " << room->name(); + fetchMeetings( iClock->datetime().addDays( -14 ), iClock->datetime().addDays( 14 ), room ); +} + +void Engine::fetchMeetings( const QDateTime &aFrom, const QDateTime &aUntil, Room *aIn ) +{ + iCommunication->fetchMeetings( aFrom, aUntil, *aIn ); +} + +void Engine::fetchMeetingDetails( Meeting *aMeeting ) +{ + iCommunication->fetchMeetingDetails( *aMeeting ); +} + +bool Engine::isMeetingInList( const QList &aList, const Meeting *aMeeting ) +{ + for ( int i = 0; i < aList.count(); i++ ) + { + if ( aMeeting->equals( *(aList.at( i )) ) ) + { + return true; + } + } + return false; +} + +void Engine::meetingsFetched( const QList &aMeetings ) +{ + // check if there is any new meeting in the list came from the server -> added + for ( int i = 0; i < aMeetings.count(); i++ ) + { + // if the (i)th meeting is not in the local meeting list + if ( !isMeetingInList( iMeetings, aMeetings.at( i ) ) ) + { + // add to the local database =) + Meeting* m = new Meeting( *(aMeetings.at( i )) ); + iMeetings.append( m ); + // and signal the changes + emit meetingAdded( m ); + } + } + + // check if there is any meeting NOT in the list came from the server -> deleted + for ( int i = 0; i < iMeetings.count(); i++ ) + { + // if the (i)th meeting is in the local but NOT in the server's meeting list + if ( !isMeetingInList( aMeetings, iMeetings.at( i ) ) ) + { + Meeting* m = iMeetings.takeAt( i ); + // signal the changes + emit meetingDeleted( m ); + // delete the meeting from the local list + delete m; + } + } + + // refresh room status info + roomStatusInfoNeeded( defaultRoom() ); +} + +void Engine::meetingDetailsFetched( Meeting &aDetailedMeeting ) +{ + emit meetingDetailsFetched( &aDetailedMeeting ); +} + +void Engine::errorHandler( int aCode, const QString &aAddInfo ) +{ + qDebug() << "Engine::ErrorHandler, aCode: " << aCode; + // inform UI about the problem + if( aCode >= 100 && aCode <= 110 ) + qDebug() << "CommunicationManager signaled an error:" << aCode; + emit error( ErrorMapper::codeToString( aCode, aAddInfo ) ); +} + +void Engine::currentRoomChanged( Room *aCurrentRoom ) +{ + iCurrentRoom = aCurrentRoom; + qDebug() << "Engine::currentRoomChanged to " << iCurrentRoom->name(); +} diff --git a/src/BusinessLogic/Engine.h b/src/BusinessLogic/Engine.h new file mode 100644 index 0000000..47b2a4d --- /dev/null +++ b/src/BusinessLogic/Engine.h @@ -0,0 +1,195 @@ +#ifndef ENGINE_H_ +#define ENGINE_H_ + +#include +#include +#include "Room.h" + +class QTimer; +class Clock; +class Configuration; +class CommunicationManager; +class Meeting; +class DeviceManager; + +//! BusinessLogic class. Contains all the business logic of the application. +/*! + * BusinessLogic class. Contains all the business logic of the application. It is responsible + * for connecting user interface to lower application layers (IO). + */ +class Engine : public QObject +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize an Engine instance. + */ + Engine(); + //! Destructor. + virtual ~Engine(); + + //! Gets the clock instance used by the object to get up-to-date date and time info. + /*! + * Gets the clock instance used by the object to get up-to-date date and time info. + * \return Pointer to the Clock instance. + */ + Clock* clock(); + //! Gets the application's configuration. + /*! + * Gets the application's configuration. + * \return Pointer to the Configuration instance. + */ + Configuration* configuration(); + //! Gets default room of the application. + /*! + * Gets default room of the application. + * \return Pointer to the default Room instance. + */ + Room* defaultRoom(); + //! Gets the deviceManager instance + /*! + * Gets the deviceManager instance. + * \return Pointer to the deviceManager instance. + */ + DeviceManager* deviceManager(); + +signals: + //! Signal. Emitted if initialization of the current instance failed. + /*! + * Signal. Emitted if initialization of the current instance failed, if reading of the configuration + * was not successful. It's purpose to inform the userinterface that the Engine is not ready for + * controlling the application, application must be shut down. + */ + void initializationFailed(); + //! Signal. Emitted if the availability information of the specified room changed. + /*! + * Signal. Emitted if the availability information of the specified room changed. + * \param aRoom The Room instance which availability changed. + * \param aStatus The status of the room. + * \param aUntil Time until the spacified status is valid. + */ + void roomStatusChanged( Room *aRoom, Room::Status aStatus, QTime aUntil ); + //! Signal. Emitted if new meeting was found on the server. + /*! + * Signal. Emitted if new meeting was found on the server. + * \param aMeeting The new meeting which was added. + */ + void meetingAdded( Meeting *aMeeting ); + //! Signal. Emitted if meeting was deleted on the server. + /*! + * Signal. Emitted if meeting was deleted on the server. + * \param aMeeting The meeting which was deleted. + */ + void meetingDeleted( Meeting *aMeeting ); + //! Signal. Emitted error occured and error message must be shown on UI. + /*! + * Signal. Emitted error occured and error message must be shown on UI. + * \param aErrorMessage The message. + */ + void error( const QString &aErrorMessage ); + + void meetingDetailsFetched( Meeting *aDetailedMeeting ); + +public slots: + //! Slot. Checks actual availability information of the specified room. + /*! + * Slot. Checks actual availability information of the specified room. + * \param aRoom The room which availability information is needed. + */ + void roomStatusInfoNeeded( Room *aRoom ); + //! Slot. Fetches meetings from the server. + /*! + * Slot. Fetches meetings from the server, exact parameters are specified in the parameter list. + * \param aFrom Time from when the meetings need to be fetched. + * \param aUntil Time until when the meetings need to be fetched. + * \param aIn The room which meetings need to be fetched. + */ + void fetchMeetings( const QDateTime &aFrom, const QDateTime &aUntil, Room *aIn ); + + void fetchMeetingDetails( Meeting * ); + + /*! + * Slot. Sets the current meeting room iCurrentRoom. + * \param aCurrentRoom + */ + void currentRoomChanged( Room *aCurrentRoom ); + +private slots: + //! Slot. Handles errors. + /*! + * Slot. Handles errors and informs the UI by emitting the error() signal with the message in + * parameter. + * \param aCode The error code. + * \param aAddInfo Possible addition info. + */ + void errorHandler( int aCode, const QString &aAddInfo = "" ); + //! Slot. Fetches meetings from the server. + /*! + * Slot. Fetches meetings from the server. Parameters are hard coded: the meetings of the default + * room from current and +/- 2 weeks are fetched. + */ + void fetchMeetings(); + //! Slot. Saves fetched meetings to the current instance's local storage. + /*! + * Slot. Saves fetched meetings to the current instance's local storage. Meetings are soted in a + * private QList, it is iterated through and signals are emitted if there is any new, updated or + * deleted meeting. + * \param aMeetings The list of freshly fetched meetings. + */ + void meetingsFetched( const QList& ); + + void meetingDetailsFetched( Meeting &aDetailedMeeting ); + + //! Slot. Checks the availability of all the rooms. + /*! + * Slot. Checks the availability of all the rooms by iterating through the current object's local + * room storage and calling the roomStatusInfoNeeded() separately on each of them. + */ + void checkStatusOfAllRooms(); + +private: + //! Provides the index of the Meeting instance which is at the specified time. + /*! + * Provides the index of the Meeting instance which is at the specified time. If there are + * overlapping meetings then returns one of then undetetministically. + * \param aRoom The room which meetings are looked through. + * \param aAt Date and time when the meeting is already going. + * \return Index of the meeting if found; otherwise, -1. + */ + int indexOfMeetingAt( Room *aRoom, QDateTime aAt ); + //! Provides the index of the Meeting instance which is starts the closest to the specified time. + /*! + * Provides the index of the Meeting instance which is starts the closest to the specified time. + * If there are overlapping meetings then returns one of then undetetministically. + * \param aRoom The room which meetings are looked through. + * \param aAt Date and time when the meeting is not yet started. + * \return Index of the meeting if found; otherwise, -1. + */ + int indexOfMeetingAfter( Room *aRoom, QDateTime aAfter ); + //! Indicates if the QList contains the Meeting or not. + /*! + * Indicates if the QList contains the Meeting or not. + * \param aList List of meetings. + * \param aMeeting The meeting which is seeked in the list for. + * \return True if contains; otherwise, false. + */ + static bool isMeetingInList( const QList &aList, const Meeting *aMeeting ); + +private: + static QTime endOfTheDay; + + Clock *iClock; + Configuration *iConfiguration; + CommunicationManager *iCommunication; + DeviceManager *iDevice; + + QTimer *iAutoRefresh; + + QList iMeetings; + + Room *iCurrentRoom; /*! Not owned */ +}; + +#endif /*ENGINE_H_*/ diff --git a/src/BusinessLogic/Utils/Clock.cpp b/src/BusinessLogic/Utils/Clock.cpp new file mode 100644 index 0000000..93b3163 --- /dev/null +++ b/src/BusinessLogic/Utils/Clock.cpp @@ -0,0 +1,57 @@ +#include "Clock.h" +#include +#include + +const int TICK_TIME = 1000; // milliseconds + +Clock::Clock() : + QObject() +{ + iCurrentDateTime = QDateTime::currentDateTime(); + + iTimer = new QTimer; + iTimer->setInterval( TICK_TIME ); + connect( iTimer, SIGNAL( timeout() ), iTimer, SLOT( start() ) ); + connect( iTimer, SIGNAL( timeout() ), this, SLOT( update() ) ); + iTimer->start(); +} + +Clock::~Clock() +{ + if ( iTimer != 0 ) + { + iTimer->stop(); + delete iTimer; + } +} + +QDateTime Clock::datetime() +{ + return iCurrentDateTime; +} + +QDate Clock::today() +{ + return iCurrentDateTime.date(); +} + +QTime Clock::time() +{ + return iCurrentDateTime.time(); +} + +void Clock::syncronizeDateTime() +{ + const QString systemCmd = "sudo /etc/init.d/openntpd restart"; + + iTimer->stop(); + QProcess::execute( systemCmd ); + iTimer->start(); +} + +void Clock::update() +{ + iCurrentDateTime = QDateTime::currentDateTime(); + + emit tick( datetime() ); +} diff --git a/src/BusinessLogic/Utils/Clock.h b/src/BusinessLogic/Utils/Clock.h new file mode 100644 index 0000000..13cffdc --- /dev/null +++ b/src/BusinessLogic/Utils/Clock.h @@ -0,0 +1,75 @@ +#ifndef CLOCK_H_ +#define CLOCK_H_ + +#include +#include + +class QTimer; + +//! BusinessLogic class. Provide up-to-date date and time for the whole application. +/*! + * BusinessLogic class. Provide up-to-date date and time for the whole application. The class uses + * openNTPD to syncronize local datetime with a remote NTP server. The NTP client must be configured. + * The class uses ticks in every minute and signals the actual datetime information. + */ +class Clock : public QObject +{ + Q_OBJECT + +public: + //! Contructor. + /*! + * Contructor to initialize a new Clock instance. + */ + Clock(); + //! Destructor. + virtual ~Clock(); + + //! Gets the current date and time. + /*! + * Gets the current date and time. + * \return The current date and time. + */ + QDateTime datetime(); + //! Gets the current day. + /*! + * Gets the current day. + * \return The current day. + */ + QDate today(); + //! Gets the current time. + /*! + * Gets the current time. + * \return The current time. + */ + QTime time(); + +signals: + //! Signal. Emitted in every second, it signals the actual date and time info. + /*! + * Signal. Emitted in every second, it signals the actual date and time info. + * \param aCurrentDateTime The current date and time. + */ + void tick( QDateTime aCurrentDateTime ); + +public slots: + //! Slot. Syncronizes the devices local date and time with a remote NTP server. + /*! + * Slot. Syncronizes the devices local date and time with a remote NTP server by using + * openNTPD client. It launches the installed and configured klient with a system command. + */ + void syncronizeDateTime(); + +private slots: + //! Slot. Updates the date and time info stored in the current instance. + /*! + * Slot. Updates the date and time info stored in the current instance by reading the device's clock. + */ + void update(); + +private: + QDateTime iCurrentDateTime; + QTimer* iTimer; +}; + +#endif /*CLOCK_H_*/ diff --git a/src/BusinessLogic/Utils/ErrorMapper.cpp b/src/BusinessLogic/Utils/ErrorMapper.cpp new file mode 100644 index 0000000..58dfc8c --- /dev/null +++ b/src/BusinessLogic/Utils/ErrorMapper.cpp @@ -0,0 +1,83 @@ +#include "ErrorMapper.h" + +#include +#include +#include +#include + +#include + +ErrorMapper * ErrorMapper::sInstance = 0; +QString ErrorMapper::sNotValidErrorCode = QString( "ABC" ); + +ErrorMapper::ErrorMapper() +{ + Q_INIT_RESOURCE( BusinessLogic ); + mapFromXML( ":/errortable" ); +} + +ErrorMapper::~ErrorMapper() +{ +} + +void ErrorMapper::mapFromXML( const QString &aXml ) +{ + // create and xml reader + QDomDocument doc; + QFile file( aXml ); + + // open the file + if ( !file.open( QIODevice::ReadOnly ) ) + { + return; + } + + // associate the content to the xml reader + if ( !doc.setContent( &file ) ) + { + file.close(); + return; + } + file.close(); + + // start readin the xml file + QDomElement root = doc.documentElement(); + // check if the file is the one we need + if ( root.tagName().toLower() != "errors" ) + { + return; + } + + // loop through the nodes + for ( QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + // read an element in the xml file + QDomElement e = node.toElement(); + if ( !e.isNull() && e.tagName().toLower() == "error" ) + { + bool success = false; + int code = QString( e.attribute( "code", sNotValidErrorCode ) ).toInt( &success ); + QString text = e.text(); + + // if element is correct, then store it + if ( success ) + { + iErrorTable.insert( code, text ); + } + } + } +} + +QString ErrorMapper::codeToString( int code, const QString &aAddInfo ) +{ + if ( sInstance == 0 ) + { + sInstance = new ErrorMapper; + } + + QString text = sInstance->iErrorTable.value( code, "" ); + if( aAddInfo != "" && text.contains( "%1" ) ) + text = text.arg( "%1" ).arg( aAddInfo ); + + return text; +} diff --git a/src/BusinessLogic/Utils/ErrorMapper.h b/src/BusinessLogic/Utils/ErrorMapper.h new file mode 100644 index 0000000..01e8e9b --- /dev/null +++ b/src/BusinessLogic/Utils/ErrorMapper.h @@ -0,0 +1,54 @@ +#ifndef ERRORMAPPER_H_ +#define ERRORMAPPER_H_ + +#include +#include + +//! BusinessLogic class. Map error codes to error strings. +/*! + * BusinessLogic class. Map error codes to error strings. The code-string pairs are defined in XML-file + * which is read by the mapper at initialization and stored in a QMap. The class has only one public + * static method which provides access to the code-string table, there is a static instance used but this + * is transparent for the caller. + */ +class ErrorMapper +{ + +public: + //! Gets the error string of the specified error code. + /*! + * Gets the error string of the specified error code. + * \param aCode The code to which the string must be got. + * \param aAddInfo Possible additional info. + * \return The string pair of the specified code, if found; otherwise, empty string. + */ + static QString codeToString( int aCode, const QString &aAddInfo = "" ); + +private: + //! Constructor. + /*! + * Constructor to initialize an ErrorMapper instance. + */ + ErrorMapper(); + //! Destructor. + virtual ~ErrorMapper(); + + //! Loads to code-string pairs. + /*! + * Loads to code-string pairs from XML-file to a QMap object. + * \param aXml The XML-file which contains the error table. + */ + void mapFromXML( const QString &aXml ); + +private: + /*! + * The static instance which is in use to load and store the code-string pairs. + */ + static ErrorMapper *sInstance; + static QString sNotValidErrorCode; + + QMap iErrorTable; + +}; + +#endif /*ERRORMAPPER_H_*/ diff --git a/src/Domain/Configuration/Configuration.cpp b/src/Domain/Configuration/Configuration.cpp new file mode 100644 index 0000000..9286846 --- /dev/null +++ b/src/Domain/Configuration/Configuration.cpp @@ -0,0 +1,601 @@ +#include "Configuration.h" +#include "ConnectionSettings.h" +#include "StartupSettings.h" +#include "DisplaySettings.h" +#include "Room.h" +#include +#include +#include +#include +#include + +#include + +Configuration * Configuration::sInstance = 0; +QString Configuration::sConfigurationPath = "/etc/QtMeetings.conf"; + +Configuration::Configuration() : + iConnectionSettings( 0 ), + iStartupSettings( 0 ), + iDisplaySettings( 0 ) +{ +} + +Configuration::~Configuration() +{ + delete iConnectionSettings; + delete iStartupSettings; + delete iDisplaySettings; + while ( !iRooms.isEmpty() ) + { + delete iRooms.takeFirst(); + } +} + +Configuration* Configuration::instance() +{ + if ( sInstance == 0 ) + { + sInstance = readFromXML( sConfigurationPath ); + if( !sInstance ) + { + qDebug() << "FATAL: Configuration cannot be read from:" << Configuration::sConfigurationPath; + } + } + return sInstance; +} + +ConnectionSettings* Configuration::connectionSettings() +{ + return iConnectionSettings; +} + +StartupSettings * Configuration::startupSettings() +{ + return iStartupSettings; +} + +DisplaySettings * Configuration::displaySettings() +{ + return iDisplaySettings; +} + +Room* Configuration::defaultRoom() +{ + // default room is stored as the first element of the list + return ( iRooms.count() == 0 ) ? 0 : iRooms.at( 0 ); +} + +QString Configuration::languageCode() +{ + return iLanguageCode; +} + +QList Configuration::rooms() +{ + return iRooms; +} + +QByteArray Configuration::adminPassword() +{ + return iAdminPassword; +} + +void Configuration::save() +{ + QDomDocument doc; + QFile file( sConfigurationPath ); + + if ( !file.open( QIODevice::ReadWrite ) ) + { + return; + } + + if ( !doc.setContent( &file ) ) + { + file.close(); + return; + } + + QDomElement root = doc.documentElement(); + // Save all attributes to document + + saveAdminPassword( root ); + + for ( QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "connection" ) ) + { + saveConnectionSettings( node ); + } + else if ( tagName == QString( "rooms" ) ) + { + saveRooms( node ); + } + else if ( tagName == QString( "language" ) ) + { + saveLanguageCode( node ); + } + else if ( tagName == QString( "startup" ) ) + { + saveStartupSettings( node ); + } + else if ( tagName == QString( "display" ) ) + { + saveDisplaySettings( node ); + } + } + + //! Empty the file from previous content and write again with new one + file.resize( 0 ); + file.write( doc.toByteArray( 4 ) ); //! 4 as intent + + file.close(); + +} + +void Configuration::saveConnectionSettings( const QDomNode &aXML ) +{ + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "serverurl" ) ) + { + QDomText t = node.ownerDocument().createTextNode( iConnectionSettings->serverUrl().toString() ); + e.replaceChild( t, e.firstChild() ); + } + else if ( tagName == QString( "username" ) ) + { + QDomText t = node.ownerDocument().createTextNode( iConnectionSettings->username() ); + e.replaceChild( t, e.firstChild() ); + } + else if ( tagName == QString( "password" ) ) + { + QDomText t = node.ownerDocument().createTextNode( iConnectionSettings->password() ); + e.replaceChild( t, e.firstChild() ); + } + else if ( tagName == QString( "refreshinterval" ) ) + { + QString s = QString( "%1" ).arg( iConnectionSettings->refreshInterval() ); + QDomText t = node.ownerDocument().createTextNode( s ); + e.replaceChild( t, e.firstChild() ); + } + } +} + +void Configuration::saveRooms( const QDomNode &aXML ) +{ + //! List of rooms must be cleared and rewritten again + QDomDocument doc = aXML.ownerDocument(); + QDomNode root = aXML; + + // Remove child nodes... + int count = root.childNodes().count(); + QDomNode node = root.firstChild(); + QDomNode next; + for (int i=0; i::iterator i; + for ( i = iRooms.begin(); i != iRooms.end(); ++i ) + { + QDomElement tag = doc.createElement( "room" ); + node.appendChild( tag ); + + // First room in the list is a dafault room + if ( i == iRooms.begin() ) + { + tag.setAttribute( "default", "true" ); + } + + QDomElement ename = doc.createElement( "name" ); + QDomText tname = node.ownerDocument().createTextNode(( *i )->name() ); + ename.appendChild( tname ); + tag.appendChild( ename ); + + QDomElement eaddress = doc.createElement( "address" ); + QDomText taddress = node.ownerDocument().createTextNode(( *i )->address() ); + eaddress.appendChild( taddress ); + tag.appendChild( eaddress ); + } +} + +void Configuration::saveLanguageCode( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + e.setAttribute( "code", languageCode() ); +} + +void Configuration::saveStartupSettings( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "powersaving" ) ) + { + ( iStartupSettings->isPowersavingEnabled() ? + e.setAttribute( "enabled", "true" ) : + e.setAttribute( "enabled", "false" ) ); + + e.setAttribute( "on", iStartupSettings->turnOnAt().toString( "hh:mm" ) ); + e.setAttribute( "off", iStartupSettings->turnOffAt().toString( "hh:mm" ) ); + } + } +} + +void Configuration::saveDisplaySettings( const QDomNode &aXML ) +{ + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "schedule" ) ) + { + for ( QDomNode scheduleNode = node.firstChild(); !scheduleNode.isNull(); scheduleNode = scheduleNode.nextSibling() ) + { + QDomElement scheduleElem = scheduleNode.toElement(); + tagName = scheduleElem.tagName().toLower(); + + if ( tagName == QString( "week" ) ) + { + scheduleElem.setAttribute( "days", iDisplaySettings->daysInSchedule() ); + } + else if ( tagName == QString( "day" ) ) + { + scheduleElem.setAttribute( "startsat", iDisplaySettings->dayStartsAt().toString( "hh:mm" ) ); + scheduleElem.setAttribute( "endsat", iDisplaySettings->dayEndsAt().toString( "hh:mm" ) ); + } + } // end of for + } // end of schedule + else if ( tagName == QString( "dateformat" ) ) + { + QDomText t = node.ownerDocument().createTextNode( iDisplaySettings->dateFormat() ); + e.replaceChild( t, e.firstChild() ); + } + else if ( tagName == QString( "timeformat" ) ) + { + QDomText t = node.ownerDocument().createTextNode( iDisplaySettings->timeFormat() ); + e.replaceChild( t, e.firstChild() ); + } + else if ( tagName == QString( "screensaver" ) ) + { + QString s = QString( "%1" ).arg( iDisplaySettings->screensaver() ); + QDomText t = node.ownerDocument().createTextNode( s ); + e.replaceChild( t, e.firstChild() ); + } + } +} + +void Configuration::saveAdminPassword( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + e.setAttribute( "password", QString( adminPassword() ) ); +} + + +Configuration* Configuration::readFromXML( const QString &aPath ) +{ + QDomDocument doc; + QFile file( aPath ); + + if ( !file.open( QIODevice::ReadOnly ) ) + { + return 0; + } + if ( !doc.setContent( &file ) ) + { + file.close(); + return 0; + } + file.close(); + + QDomElement root = doc.documentElement(); + // check if the file is the one we need + if ( root.tagName().toLower() != QString( "configuration" ) ) + { + return 0; + } + + Configuration* conf = new Configuration(); + + conf->iAdminPassword = Configuration::readAdminPassword( root ); + + for ( QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "connection" ) ) + { + conf->iConnectionSettings = Configuration::readConnectionSettings( node ); + } + else if ( tagName == QString( "rooms" ) ) + { + conf->iRooms = Configuration::readRooms( node ); + } + else if ( tagName == QString( "language" ) ) + { + conf->iLanguageCode = Configuration::readLanguageCode( node ); + } + else if ( tagName == QString( "startup" ) ) + { + conf->iStartupSettings = Configuration::readStartupSettings( node ); + } + else if ( tagName == QString( "display" ) ) + { + conf->iDisplaySettings = Configuration::readDisplaySettings( node ); + } + } + + return conf; +} + +ConnectionSettings* Configuration::readConnectionSettings( const QDomNode &aXML ) +{ + QString server, username, password; + unsigned int interval = Configuration::sDefaultInterval; + + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "serverurl" ) ) + { + server = e.text(); + } + else if ( tagName == QString( "username" ) ) + { + username = e.text(); + } + else if ( tagName == QString( "password" ) ) + { + password = e.text(); + } + else if ( tagName == QString( "refreshinterval" ) ) + { + bool success = false; + unsigned int intervalTMP = e.text().toUInt( &success ); + if ( success ) + { + interval = intervalTMP; + } + } + } + + return new ConnectionSettings( server, username, password, interval ); +} + +QList Configuration::readRooms( const QDomNode &aXML ) +{ + QList rooms; + + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "room" ) ) + { + QString name, address; + + for ( QDomNode roomNode = node.firstChild(); !roomNode.isNull(); roomNode = roomNode.nextSibling() ) + { + QDomElement roomElem = roomNode.toElement(); + tagName = roomElem.tagName().toLower(); + if ( tagName == QString( "name" ) ) + { + name = roomElem.text(); + } + else if ( tagName == QString( "address" ) ) + { + address = roomElem.text(); + } + } + Room* room = new Room( name, address ); + QString defaultAttr = e.attribute( "default" ).toLower(); + if ( defaultAttr == QString( "true" ) ) + { + rooms.insert( 0, room ); + } + else + { + rooms.append( room ); + } + } + } + + return rooms; +} + +QString Configuration::readLanguageCode( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + QString tagName = e.tagName().toLower(); + + if ( e.hasAttribute( "code" ) ) + { + return e.attribute( "code" ); + } + else + { + // default language is English + return "EN"; + } +} + +StartupSettings * Configuration::readStartupSettings( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + + bool isPowersavingEnabled = false; + QTime turnOnAt, turnOffAt; + + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "powersaving" ) ) + { + if ( e.hasAttribute( "enabled" ) && + e.attribute( "enabled" ) == QString( "true" ) ) + { + isPowersavingEnabled = true; + } + + if ( e.hasAttribute( "on" ) ) + { + QString on = e.attribute( "on" ); + turnOnAt = QTime::fromString( on, "hh:mm" ); + + } + + if ( e.hasAttribute( "off" ) ) + { + QString off = e.attribute( "off" ); + turnOffAt = QTime::fromString( off, "hh:mm" ); + } + } + } + + return new StartupSettings( isPowersavingEnabled, turnOnAt, turnOffAt ); +} + +DisplaySettings * Configuration::readDisplaySettings( const QDomNode &aXML ) +{ + DisplaySettings::DaysInSchedule daysInSchedule; + QTime dayStartsAt, dayEndsAt; + DisplaySettings::DateFormat dateformat; + DisplaySettings::TimeFormat timeformat; + int screensaver = 1; //! Default value for screensaver wait time + + for ( QDomNode node = aXML.firstChild(); !node.isNull(); node = node.nextSibling() ) + { + QDomElement e = node.toElement(); + QString tagName = e.tagName().toLower(); + + if ( tagName == QString( "schedule" ) ) + { + for ( QDomNode scheduleNode = node.firstChild(); !scheduleNode.isNull(); scheduleNode = scheduleNode.nextSibling() ) + { + QDomElement scheduleElem = scheduleNode.toElement(); + tagName = scheduleElem.tagName().toLower(); + + if ( tagName == QString( "week" ) ) + { + if ( scheduleElem.hasAttribute( "days" ) ) + { + // default value is 5, only other supported possibility is 7 + bool success = false; + unsigned int days = scheduleElem.attribute( "days" ).toUInt( &success ); + daysInSchedule = ( success && days == 7 ) ? DisplaySettings::WholeWeekInSchedule : DisplaySettings::WeekdaysInSchedule; + } + + } + else if ( tagName == QString( "day" ) ) + { + if ( scheduleElem.hasAttribute( "startsat" ) ) + { + QString time = scheduleElem.attribute( "startsat" ); + dayStartsAt = QTime::fromString( time, "hh:mm" ); + } + if ( scheduleElem.hasAttribute( "endsat" ) ) + { + QString time = scheduleElem.attribute( "endsat" ); + dayEndsAt = QTime::fromString( time, "hh:mm" ); + } + + } + } // end of for + } // end of schedule + else if ( tagName == QString( "dateformat" ) ) + { + //! Not able to display long format anyway at the moment + /* + if ( e.text() == QObject::tr( "dddd d MMMM yyyy" ) ) + dateformat = DisplaySettings::LongDateFormat; + else + dateformat = DisplaySettings::ShortDateFormat; + */ + dateformat = DisplaySettings::ShortDateFormat; + } + else if ( tagName == QString( "timeformat" ) ) + { + //! Not able to display "TwelveHoursTimeFormat" anyway at the moment + /* + if ( e.text() == QObject::tr( "hh:mm ap" ) ) + timeformat = DisplaySettings::TwelveHoursTimeFormat; + else + timeformat = DisplaySettings::TwentyFourHoursTimeFormat; + */ + timeformat = DisplaySettings::TwentyFourHoursTimeFormat; + } + else if ( tagName == QString( "screensaver" ) ) + { + bool success = false; + unsigned int screensaverTMP = e.text().toUInt( &success ); + if ( success ) + { + screensaver = screensaverTMP; + } + } + } + + return new DisplaySettings( dateformat, timeformat, daysInSchedule, dayStartsAt, dayEndsAt, screensaver ); +} + +QByteArray Configuration::readAdminPassword( const QDomNode &aXML ) +{ + QDomElement e = aXML.toElement(); + QString tagName = e.tagName().toLower(); + if ( e.hasAttribute( "password" ) ) + { + QString pw = e.attribute( "password" ); + // Check if the password is default uncrypted "admin" + if ( pw == QString( "admin" ) ) + { + // uncrypted password needs to be encoded + QCryptographicHash *hash = new QCryptographicHash( QCryptographicHash::Md5 ); + hash->addData( pw.toUtf8() ); + pw = QString( hash->result() ); + delete hash; + } + return ( pw.toAscii() ).toHex(); + + } + else + { + return 0; + } +} + +void Configuration::setRooms( const QList aRooms ) +{ + iRooms = aRooms; +} + +QString Configuration::hashPassword( const QString aPassword ) +{ + QCryptographicHash *hash = new QCryptographicHash( QCryptographicHash::Md5 ); + hash->addData( aPassword.toUtf8() ); + QByteArray password = hash->result(); + delete hash; + + return QString( password ); +} diff --git a/src/Domain/Configuration/Configuration.h b/src/Domain/Configuration/Configuration.h new file mode 100644 index 0000000..c08002d --- /dev/null +++ b/src/Domain/Configuration/Configuration.h @@ -0,0 +1,217 @@ +#ifndef CONFIGURATION_H_ +#define CONFIGURATION_H_ + +#include +#include +#include +#include + +class ConnectionSettings; +class StartupSettings; +class DisplaySettings; +class Room; +class QDomNode; + +//! Domain class. Store application wide configuration values. +/*! + * Domain class. Store application wide configuration values. The values are read from a configuration + * file at initialization time. Since there is one appliation per device normally running, therefore + * there is only one instance of this class, which is accessible by using a statis getter method. + */ +class Configuration : public QObject +{ + Q_OBJECT + +private: + //! Constructor. + /*! + * Constructor to initialize an Configuration instance. The method populates the object by reading + * through the application's configuration file. + */ + Configuration(); + +public: + //! Destructor + virtual ~Configuration(); + + //! Static. Gets the application wide configuration instance. + /*! + * Static Gets the static instance of this class. It is used to read the configuration information. + * \return Pointer to the Configuration instalce. + */ + static Configuration* instance(); + //! Gets the connection settings. + /*! + * Gets the connection settings. + * \return Pointer to ConnectionSettings instance. + */ + ConnectionSettings* connectionSettings(); + //! Gets the detault room. + /*! + * Gets the default meeting room. + * \return Pointer to the default room. + */ + Room* defaultRoom(); + //! Gets the language code. + /*! + * Gets the language code. + * \return Language code in ISO 3166-1 alpha-2 format. + */ + QString languageCode(); + //! Gets the list of meeting rooms. + /*! + * Gets the list of meeting rooms. + * \return List of rooms. + */ + QList rooms(); + //! Gets startup settings. + /*! + * Gets the startup settings. + * \return Pointer to StartupSettings instance. + */ + StartupSettings* startupSettings(); + //! Gets display settings. + /*! + * Gets the display settings. + * \return Pointer to DisplaySettings instance. + */ + DisplaySettings* displaySettings(); + //! Gets the administrator's password. + /*! + * Gets the administrator's password + * \return Administration password. + */ + QByteArray adminPassword(); + //! Sets room list. + /*! + * Sets room list. + * \param aRooms List of rooms + */ + void setRooms( const QList aRooms ); + +public slots: + + //! Saves setting values to file. + /*! + * Writes setting values to configuration file. + */ + void save(); + +private: + //! Static. Reads the configuration instance from XML file. + /*! + * Static. Reads the configuration information from configuration file. + * \param aPath path and name of configuration file + * \return Configuration object. + */ + static Configuration* readFromXML( const QString &aPath ); + //! Static. Reads settings of connection from and XML node. + /*! + * Static. Reads settings of connection from an XML node. + * \param aXml QDomNode containing connection parameters. + * \return Pointer to ConnectionSettings object. + */ + static ConnectionSettings* readConnectionSettings( const QDomNode &aXML ); + //! Static. Reads rooms from an XML node. + /*! + * Static. Reads rooms from an XML node. + * \param aXml QDomNode containing meeting room parameters + * \return List of meetingrooms. + */ + static QList readRooms( const QDomNode &aXML ); + //! Static. Reads language code from an XML node. + /*! + * Static. Reads rooms from an XML node. + * \param aXml QDomNode containing language code + * \return Language code. + */ + static QString readLanguageCode( const QDomNode &aXML ); + //! Static. Reads settings of startup from an XML node. + /*! + * Static. Reads settings of startup from an XML node. + * \param aXml QDomNode containing startup parameters + * \return Pointer to the read StartupSettings object. + */ + static StartupSettings* readStartupSettings( const QDomNode &aXML ); + /*! + * Static function to load and store display settings from xml node. + * \param aXml QDomNode containing display parameters + * \return Pointer to the read DisplaySettings object. + */ + static DisplaySettings* readDisplaySettings( const QDomNode &aXML ); + //! Static. Reads adminstrator's password from an XML node. + /*! + * Static. Reads adminstrator's password from an XML node. + * \param aXml QDomNode containing admin password + * \return Admin password. + */ + static QByteArray readAdminPassword( const QDomNode &aXML ); + + //! Saves connection data to the document. + /*! + * Reads data from iConnectionSettings and saves it to the aXML document. + * \param aXml QDomNode containing connection parameters. + */ + void saveConnectionSettings( const QDomNode &aXML ); + //! Saves meeting rooms to the document. + /*! + * Reads data from iRooms list and saves it to the aXML document. + * \param aXml QDomNode containing meeting room parameters + */ + void saveRooms( const QDomNode &aXML ); + //! Saves the language code. + /*! + * Reads data from iLanguageCode and saves it to the aXML document. + * \param aXml QDomNode containing language code + */ + void saveLanguageCode( const QDomNode &aXML ); + //! Saves startup setting data to the document. + /*! + * Reads data from iStartupSettings and saves it to the aXML document. + * \param aXml QDomNode containing startup parameters + */ + void saveStartupSettings( const QDomNode &aXML ); + //! Saves display setting data to the document. + /*! + * Reads data from iDisplaySettings and saves it to the aXML document. + * \param aXml QDomNode containing display parameters + */ + void saveDisplaySettings( const QDomNode &aXML ); + //! Saves admin password to the document. + /*! + * Reads data from iAdminPassword and saves it to the aXML document. + * \param aXml QDomNode containing admin password + */ + void saveAdminPassword( const QDomNode &aXML ); + + //! Hash password with md5 method. + /*! + * Hash password with md5 method. + * \param aPassword password to be encoded + * \return Encoded password. + */ + QString hashPassword( const QString aPassword ); + +private: + //! Path and name of configuration file + static QString sConfigurationPath; + //! The static instance which is in use to read the configuration. + static Configuration *sInstance; + //! Static constant to store the default interval used for connection refresh. + static const unsigned int sDefaultInterval = 60; + //! Pointer to the ConnectionSettings object + ConnectionSettings *iConnectionSettings; + //! Stores startup settings. + StartupSettings *iStartupSettings; + //! Stores display settings. + DisplaySettings *iDisplaySettings; + //! List of meeting rooms. + QList iRooms; + //! Stores administrator password. + QByteArray iAdminPassword; + //! Stores language code + QString iLanguageCode; + +}; + +#endif /*CONFIGURATION_H_*/ diff --git a/src/Domain/Configuration/ConnectionSettings.cpp b/src/Domain/Configuration/ConnectionSettings.cpp new file mode 100644 index 0000000..9c70168 --- /dev/null +++ b/src/Domain/Configuration/ConnectionSettings.cpp @@ -0,0 +1,61 @@ +#include "ConnectionSettings.h" + +ConnectionSettings::ConnectionSettings( const QUrl &aServerUrl, const QString &aUsername, const QString &aPassword, unsigned int aRefreshInterval ) +{ + iServerUrl = aServerUrl; + iUsername = aUsername; + iPassword = aPassword; + iRefreshInterval = aRefreshInterval; +} + +ConnectionSettings::ConnectionSettings( const ConnectionSettings &aOther ) +{ + this->iServerUrl = aOther.iServerUrl; + this->iUsername = aOther.iUsername; + this->iPassword = aOther.iPassword; + this->iRefreshInterval = aOther.iRefreshInterval; +} + +ConnectionSettings::~ConnectionSettings() +{ +} + +QUrl ConnectionSettings::serverUrl() +{ + return iServerUrl; +} + +QString ConnectionSettings::username() +{ + return iUsername; +} + +QString ConnectionSettings::password() +{ + return iPassword; +} + +unsigned int ConnectionSettings::refreshInterval() +{ + return iRefreshInterval; +} + +void ConnectionSettings::setServerUrl( const QUrl &aServerUrl ) +{ + iServerUrl = aServerUrl; +} + +void ConnectionSettings::setUsername( const QString &aUsername ) +{ + iUsername = aUsername; +} + +void ConnectionSettings::setPassword( const QString &aPassword ) +{ + iPassword = aPassword; +} + +void ConnectionSettings::setRefreshInterval( unsigned int aRefreshInterval ) +{ + iRefreshInterval = aRefreshInterval; +} diff --git a/src/Domain/Configuration/ConnectionSettings.h b/src/Domain/Configuration/ConnectionSettings.h new file mode 100644 index 0000000..27b399b --- /dev/null +++ b/src/Domain/Configuration/ConnectionSettings.h @@ -0,0 +1,92 @@ +#ifndef CONNECTION_H_ +#define CONNECTION_H_ + +#include +#include + +//! Domain class. Stores connection information. +/*! + * Domain class. Stores connection information like server url, username, password and refresh + * interval. + */ +class ConnectionSettings +{ +public: + //! Constructor. + /*! + * Overloaded. Constructor to initialize a ConnectionSettings instance. + * \param aServerUrl The URL of the Exchange server. + * \param aUsername The username used to log in to the server. + * \param aPassword The password used to log in to the server. + * \param aRefreshInterval The interval defines how often the server's data must be fetched. (Interpreted in seconds.) + */ + ConnectionSettings( const QUrl &aServerUrl, const QString &aUsername, const QString &aPassword, unsigned int aRefreshInterval ); + //! Copy constructor. + /*! + * Overloaded. Constructor to initialize a ConnectionSettings instance. + * \param aOther ConnectionSettings class to be copied. + */ + ConnectionSettings( const ConnectionSettings &aOther ); + //! Destructor + virtual ~ConnectionSettings(); + + //! Gets the server address + /*! + * Gets the server url address. + * \return Url address of server. + */ + QUrl serverUrl(); + //! Gets the username. + /*! + * Gets username used to connect. + * \return Username. + */ + QString username(); + //! Gets the password. + /*! + * Gets the password used to connect. + * \return Password. + */ + QString password(); + //! Gets the refresh interval. + /*! + * Gets refresh interval in seconds to read the data from server. + * \return Refresh interval. + */ + unsigned int refreshInterval(); + + //! Sets the server address + /*! + * Sets the server url address. + * \param aServerUrl Url address of server. + */ + void setServerUrl( const QUrl &aServerUrl ); + //! Sets the username. + /*! + * Sets username used to connect. + * \param aUsername Username. + */ + void setUsername( const QString &aUsername ); + //! Sets the password. + /*! + * Sets the password used to connect. + * \param aPassword Password. + */ + void setPassword( const QString &aPassword ); + //! Sets the refresh interval. + /*! + * Sets refresh interval in seconds to read the data from server. + * \param aRefreshInterval Refresh interval. + */ + void setRefreshInterval( unsigned int aRefreshInterval ); + + +private: + QUrl iServerUrl; + QString iUsername; + QString iPassword; + unsigned int iRefreshInterval; // in seconds + +}; + +#endif /*CONNECTION_H_*/ diff --git a/src/Domain/Configuration/DisplaySettings.cpp b/src/Domain/Configuration/DisplaySettings.cpp new file mode 100644 index 0000000..debe583 --- /dev/null +++ b/src/Domain/Configuration/DisplaySettings.cpp @@ -0,0 +1,107 @@ +#include "DisplaySettings.h" +#include + +DisplaySettings::DisplaySettings( DateFormat aDateFormat, TimeFormat aTimeFormat, DaysInSchedule aDaysInSchedule, QTime aDayStartsAt, QTime aDayEndsAt, int aScreensaver ) : + iDateFormat( aDateFormat ), + iTimeFormat( aTimeFormat ), + iDaysInSchedule( aDaysInSchedule ), + iDayStartsAt( aDayStartsAt ), + iDayEndsAt( aDayEndsAt ), + iScreensaver( aScreensaver ) +{ +} + +DisplaySettings::~DisplaySettings() +{ +} + +QString DisplaySettings::dateFormat() +{ + + QString format; + switch ( iDateFormat ) + { + case DisplaySettings::LongDateFormat : + { + format = QObject::tr( "dddd d MMMM yyyy" ); + break; + } + case DisplaySettings::ShortDateFormat : + default: + { + format = QObject::tr( "ddd d MMM" ); + break; + } + } + return format; +} + +QString DisplaySettings::timeFormat() +{ + QString format; + switch ( iTimeFormat ) + { + case DisplaySettings::TwelveHoursTimeFormat : + { + format = QObject::tr( "hh:mm ap" ); + break; + } + case DisplaySettings::TwentyFourHoursTimeFormat : + default: + { + format = QObject::tr( "hh:mm" ); + break; + } + } + return format; +} + +DisplaySettings::DaysInSchedule DisplaySettings::daysInSchedule() +{ + return iDaysInSchedule; +} + +QTime DisplaySettings::dayStartsAt() +{ + return iDayStartsAt; +} + +QTime DisplaySettings::dayEndsAt() +{ + return iDayEndsAt; +} + +int DisplaySettings::screensaver() +{ + return iScreensaver; +} + +void DisplaySettings::setDateFormat( DateFormat aDateFormat ) +{ + iDateFormat = aDateFormat; +} + +void DisplaySettings::setTimeFormat( TimeFormat aTimeFormat ) +{ + iTimeFormat = aTimeFormat; +} + +void DisplaySettings::setDaysInSchedule( DaysInSchedule aDaysInSchedule ) +{ + iDaysInSchedule = aDaysInSchedule; +} + +void DisplaySettings::setDayStartsAt( QTime aDayStartsAt ) +{ + iDayStartsAt = aDayStartsAt; +} + +void DisplaySettings::setDayEndsAt( QTime aDayEndsAt ) +{ + iDayEndsAt = aDayEndsAt; +} + +void DisplaySettings::setScreensaver( int aWaitTime ) +{ + iScreensaver = aWaitTime; +} diff --git a/src/Domain/Configuration/DisplaySettings.h b/src/Domain/Configuration/DisplaySettings.h new file mode 100644 index 0000000..8230297 --- /dev/null +++ b/src/Domain/Configuration/DisplaySettings.h @@ -0,0 +1,145 @@ +#ifndef DISPLAYSETTINGS_H_ +#define DISPLAYSETTINGS_H_ + +#include + +//! Domain class. Stores display settings. +/*! + * Domain class. Stores display settings such as date and time format, number of days shown + * in week calendar. + */ +class DisplaySettings +{ +public: + //! Enumeration of days shown in week calendar. + /*! + * Enumeration of days shown in week calendar. + */ + enum DaysInSchedule + { + WeekdaysInSchedule = 5, /*!< Display days from Monday till Friday. */ + WholeWeekInSchedule = 7 /*!< Display days from Monday till Sunday. */ + }; + + //! Enumeration of used date format. + /*! + * Enumeration of used date format. + */ + enum DateFormat + { + LongDateFormat, /*!< Date displayed like "Monday 6 April 2009". */ + ShortDateFormat /*!< Date displayed like "Mon 6 Apr". */ + }; + + //! Enumeration of used time format. + /*! + * Enumeration of used time format. + */ + enum TimeFormat + { + TwelveHoursTimeFormat, /*!< Time displayed like "01:34 pm". */ + TwentyFourHoursTimeFormat /*!< Time displayed like "13:34". */ + }; + +public: + //! Constructor + /*! + * Constuctor to initialize DisplaySettings instance. + * \param aDateFormat DateFormat type variable to eliminate the date format in use. + * \param aTimeFormat TimeFormat type variable to eliminate the time in use. + * \param aDaysInSchedule Indicates how many days are shown in the schedule. + * \param aDayStartsAt Time which the schedule starts with. + * \param aDayEndsAt Time which the schedule ends with. + * \param aScreensaver Minutes for timer to launch screensaver. + */ + DisplaySettings( DateFormat aDateFormat, TimeFormat aTimeFormat, DaysInSchedule aDaysInSchedule, QTime aDayStartsAt, QTime aDayEndsAt, int aScreensaver ); + //! Destructor. + virtual ~DisplaySettings(); + + + //! Gets date format. + /*! + * Gets date format. + * \return Format string to display date. + */ + QString dateFormat(); + //! Gets time format. + /*! + * Gets time format. + * \return Format string to display time. + */ + QString timeFormat(); + //! Gets number of days in week calendar. + /*! + * Gets number of days in week calendar. + * \return Number of days to be shown. + */ + DaysInSchedule daysInSchedule(); + //! Gets first hour to be shown in calendar. + /*! + * Gets first hour to be shown in calendar. + * \return First visible hour in calendar. + */ + QTime dayStartsAt(); + //! Gets last hour to be shown in calendar. + /*! + * Gets last hour to be shown in calendar. + * \return Last visible hour in calendar. + */ + QTime dayEndsAt(); + + //! Gets minutes to wait before screensaver launches. + /*! + * Gets minutes to wait before screensaver launches. + * \return Waiting time in minutes. + */ + int screensaver(); + + //! Sets date format. + /*! + * Sets date format. + * \param aDateFormat Enumeration of date format. + */ + void setDateFormat( DateFormat aDateFormat ); + //! Sets time format. + /*! + * Sets time format. + * \param aTimeFormat Enumeration of time format. + */ + void setTimeFormat( TimeFormat aTimeFormat ); + //! Sets number of days in week calendar. + /*! + * Sets number of days in week calendar. + * \param aDaysInSchedule Number of days to be shown. + */ + void setDaysInSchedule( DaysInSchedule aDaysInSchedule ); + //! Sets first hour to be shown in calendar. + /*! + * Sets first hour to be shown in calendar. + * \param aDayStartsAt First visible hour in calendar. + */ + void setDayStartsAt( QTime aDayStartsAt ); + //! Sets last hour to be shown in calendar. + /*! + * Sets last hour to be shown in calendar. + * \param aDayEndsAt Last visible hour in calendar. + */ + void setDayEndsAt( QTime aDayEndsAt ); + //! Sets minutes to wait before screensaver launches. + /*! + * Sets minutes to wait before screensaver launches. + * \param aWaitTime Waiting time as minutes. + */ + void setScreensaver( int aWaitTime = 1 ); + +private: + DateFormat iDateFormat; + TimeFormat iTimeFormat; + DaysInSchedule iDaysInSchedule; + QTime iDayStartsAt; + QTime iDayEndsAt; + int iScreensaver; + +}; + +#endif /*DISPLAYSETTINGS_H_*/ diff --git a/src/Domain/Configuration/StartupSettings.cpp b/src/Domain/Configuration/StartupSettings.cpp new file mode 100644 index 0000000..6ea4588 --- /dev/null +++ b/src/Domain/Configuration/StartupSettings.cpp @@ -0,0 +1,40 @@ +#include "StartupSettings.h" + +StartupSettings::StartupSettings( bool aIsPowersavingEnabled, QTime aTurnOnAt, QTime aTurnOffAt ) : + iIsPowersavingEnabled( aIsPowersavingEnabled ), + iTurnOnAt( aTurnOnAt ), + iTurnOffAt( aTurnOffAt ) +{ +} + +StartupSettings::~StartupSettings() +{ +} + +bool StartupSettings::isPowersavingEnabled() +{ + return iIsPowersavingEnabled; +} + +QTime StartupSettings::turnOnAt() +{ + return iTurnOnAt; +} + +QTime StartupSettings::turnOffAt() +{ + return iTurnOffAt; +} + +void StartupSettings::setPowersavingEnabled( bool aEnabled ) +{ + iIsPowersavingEnabled = aEnabled; +} +void StartupSettings::setTurnOnAt( QTime aTurnOn ) +{ + iTurnOnAt = aTurnOn; +} +void StartupSettings::setTurnOffAt( QTime aTurnOff ) +{ + iTurnOffAt = aTurnOff; +} diff --git a/src/Domain/Configuration/StartupSettings.h b/src/Domain/Configuration/StartupSettings.h new file mode 100644 index 0000000..3aa626b --- /dev/null +++ b/src/Domain/Configuration/StartupSettings.h @@ -0,0 +1,70 @@ +#ifndef STARTUPSETTINGS_H_ +#define STARTUPSETTINGS_H_ + +#include + +//! Domain class. Stores starup settings. +/*! + * Domain class. Stores starup settings. + */ +class StartupSettings +{ +public: + //! Constructor. + /*! + * Constructor to initiazile StartupSettings instance. + * \param aIsPowersavingEnabled Flag which is true is powersaving enabled; otherwise, false. + * \param aTurnOnAt Time when the device must be turned on if flag is true. + * \param aTurnOffAt Time when the device must be turned off if flag is true. + */ + StartupSettings( bool aIsPowersavingEnabled, QTime aTurnOnAt, QTime aTurnOffAt ); + //! Destructor + virtual ~StartupSettings(); + + //! Gets powersaving flag. + /*! + * Gets powersaving flag. + * \return True if power saving option is used, otherwise false. + */ + bool isPowersavingEnabled(); + //! Gets time to turn on device. + /*! + * Gets time to turn on device. + * \return Time to automatically turn on device. + */ + QTime turnOnAt(); + //! Gets time to turn off device. + /*! + * Gets time to turn off device. + * \return Time to automatically turn off device. + */ + QTime turnOffAt(); + + //! Sets powersaving flag. + /*! + * Sets powersaving flag. + * \param aEnabled If true, power saving option is used. + */ + void setPowersavingEnabled( bool aEnabled ); + //! Sets time to turn on device. + /*! + * Sets time to turn on device. + * \param aTurnOn Time to automatically turn on device. + */ + void setTurnOnAt( QTime aTurnOn ); + //! Sets time to turn off device. + /*! + * Sets time to turn off device. + * \param aTurnOff Time to automatically turn off device. + */ + void setTurnOffAt( QTime aTurnOff ); + + +private: + bool iIsPowersavingEnabled; + QTime iTurnOnAt; + QTime iTurnOffAt; + +}; + +#endif /*STARTUPSETTINGS_H_*/ diff --git a/src/Domain/Meeting.cpp b/src/Domain/Meeting.cpp new file mode 100644 index 0000000..8880043 --- /dev/null +++ b/src/Domain/Meeting.cpp @@ -0,0 +1,150 @@ +#include "Meeting.h" +#include "Room.h" + +Meeting::Meeting( const QString &aPrimaryId, + const Room &aRoom, + const QDateTime &aStartsAt, + const QDateTime &aEndsAt, + const QString &aSubject, + const QString &aOrganizerName, + const QString &aOrganizerEMail, + const QString &aDescription ) : + iPrimaryId( aPrimaryId ), + iSecondaryId( QString::null ), + iRoom( aRoom ), + iStartsAt( aStartsAt ), + iEndsAt( aEndsAt ), + iSubject( aSubject ), + iOrganizerName( aOrganizerName ), + iOrganizerEMail( aOrganizerEMail ), + iDescription( aDescription ), + iDetailsAvailable( false ) +{ +} + +Meeting::~Meeting() +{ +} + +Room Meeting::room() const +{ + return iRoom; +} + +QString Meeting::primaryId() const +{ + return iPrimaryId; +} + +QString Meeting::secondaryId() const +{ + return iSecondaryId; +} + +QString Meeting::organizer() const +{ + if ( iOrganizerName == "" && iOrganizerEMail != "" ) + { + return iOrganizerEMail; + } + if ( iOrganizerName != "" && iOrganizerEMail == "" ) + { + return iOrganizerName; + } + if ( iOrganizerEMail == "" && iOrganizerName == "" ) + { + return ""; + } + return QString( "%1 <%2>" ).arg( iOrganizerName ).arg( iOrganizerEMail ); + +} + +QDateTime Meeting::startsAt() const +{ + return iStartsAt; +} + +QDateTime Meeting::endsAt() const +{ + return iEndsAt; +} + +QString Meeting::subject() const +{ + return iSubject; +} + +QString Meeting::description() const +{ + return iDescription; +} + +bool Meeting::detailsAvailable() const +{ + return iDetailsAvailable; +} + +void Meeting::setSecondaryId( const QString& aSecondaryId ) +{ + iSecondaryId = aSecondaryId; + iDetailsAvailable = true; +} + +void Meeting::setOrganizer( const QString &aOrganizerName, const QString &aOrganizerEMail ) +{ + iOrganizerName = aOrganizerName; + iOrganizerEMail = aOrganizerEMail; +} + +void Meeting::setStartsAt( QDateTime aNewStart ) +{ + iStartsAt = aNewStart; +} + +void Meeting::setEndsAt( QDateTime aNewEnd ) +{ + iEndsAt = aNewEnd; +} + +void Meeting::setSubject( const QString &aSubject ) +{ + iSubject = aSubject; +} + +void Meeting::setDescription( const QString &aDescription ) +{ + iDescription = aDescription; +} + +bool Meeting::equals( const Meeting &aOther ) const +{ + if ( iRoom.equals( aOther.room() ) + && iStartsAt == aOther.iStartsAt + && iEndsAt == aOther.iEndsAt + && iPrimaryId == aOther.iPrimaryId ) + { + return true; + } + return false; +} + +bool Meeting::overlaps( const Meeting &aOther ) const +{ + return (( iStartsAt >= aOther.iStartsAt && iStartsAt < aOther.iEndsAt ) || + ( iStartsAt <= aOther.iStartsAt && iEndsAt > aOther.iStartsAt ) ); +} + +QString Meeting::toString() const +{ + QString meetingToString = QString( "[MEETING: id1:%1 id2:%2 in:%3 from:%4 until:%5 by:%6 subject:%7 description:%8]" ) + .arg( iPrimaryId ) + .arg( iSecondaryId ) + .arg( iRoom.toString() ) + .arg( iStartsAt.toString() ) + .arg( iEndsAt.toString() ) + .arg( organizer() ) + .arg( iSubject ) + .arg( iDescription ); + + return meetingToString; +} diff --git a/src/Domain/Meeting.h b/src/Domain/Meeting.h new file mode 100644 index 0000000..78850eb --- /dev/null +++ b/src/Domain/Meeting.h @@ -0,0 +1,169 @@ +#ifndef MEETING_H_ +#define MEETING_H_ + +#include +#include +#include "Room.h" + +//! Domain class. Describe appointments on Microsoft Exchange Server 2007. +/*! + * Domain class. Describe appointments on Microsoft Exchange Server 2007. + */ +class Meeting +{ +public: + //! Constructor. + /*! + * Constructor to initialize a Meeting instance. + * \param aRoom Pointer to the Room instance where the meeting will be held. + * \param aStartsAt The date and time when the meeting starts. + * \param aEndsAt The date and time when the meeting ends. + * \param aOrganizer The e-mail address of the organizer. + * \param aSubject The subject of the meeting. + */ + Meeting( const QString &aPrimaryId, + const Room &aRoom, + const QDateTime &aStartsAt, + const QDateTime &aEndsAt, + const QString &aSubject = "", + const QString &aOrganizerName = "", + const QString &aOrganizerEMail = "", + const QString &aDescription = "" ); + //! Destructor. + virtual ~Meeting(); + //! Gets the primary identifier of the meeting. + /*! + * Gets the primary identifier of the meeting. The primary ID comes from the availability service + * which is used in Communication module to fetch the meetings at first. Secondary ID is needed to + * get more details of the meeting when user wants to open MeetingInfoDialog by clicking on cerain + * meeting in the schedule. + * \return Primary ID might be provided by the Microsoft Exchange Availability Service. + */ + QString primaryId() const; + //! Gets the secondary identifier of the meeting. + /*! + * Gets the secondary identifier of the meeting. The primary ID comes from the availability service + * which is used in Communication module to fetch the meetings at first. Secondary ID is needed to + * get more details of the meeting when user wants to open MeetingInfoDialog by clicking on cerain + * meeting in the schedule. + * \return Secondary ID is provided by the translating the Primary one to Secondaty. + */ + QString secondaryId() const; + //! Gets the room where the meeting is held. + /*! + * Gets the Room instance which identified the location of the meeting. + * \return Pointer to the Room instance. + */ + Room room() const; + //! Gets the organizer. + /*! + * Gets formatted string which contains the organizer's name and mail address. + * \return The organizer's name and mail address. + */ + QString organizer() const; + //! Gets the date and time when the meeting starts. + /*! + * Gets the date and time when the meeting starts. + * \return The date and time. + */ + QDateTime startsAt() const; + //! Gets the date and time when the meeting ends. + /*! + * Gets the date and time when the meeting ends. + * \return The date and time. + */ + QDateTime endsAt() const; + //! Gets the subject of the meeting. + /*! + * Gets the subject of the meeting. + * \return The description of the meeting's subject. + */ + QString subject() const; + //! Gets the description of the meeting. + /*! + * Gets the description of the meeting. This field is a detailed description of the topic of the + * meeting provided by the meeting organizer. Note it can contain confidential information. + * \return The description of the meeting. + */ + QString description() const; + //! Indicates if all the details of the current Meeting instance are available or not. + /*! + * Indicates if all the details of the current Meeting instance are available or not. If yes, then + * Secondary ID is valid, otherwise not. + * \return TRUE if yes; otherwise, false. + */ + bool detailsAvailable() const; + + //! Sets the secondary Id of the current meeting. + /*! + * Sets the secondary Id of the current meeting. + * \param aSecondaryId The secondary ID provided by translating to Primary into Secondary. + */ + void setSecondaryId( const QString& aSecondaryId ); + //! Sets the organizer of the meeting. + /*! + * Sets the organizer of the meeting. + * \param aOrganizerName The name of the organizer. + * \param aOrganizerEMail The e-mail address of the organizer. + */ + void setOrganizer( const QString &aOrganizerName, const QString &aOrganizerEMail ); + //! Sets the date and time when the meeting starts. + /*! + * Sets the date and time when the meeting starts. + * \param aNewStart The new date and time. + */ + void setStartsAt( QDateTime aNewStart ); + //! Sets the date and time when the meeting endss. + /*! + * Sets the date and time when the meeting endss. + * \param aNewEnd The new date and time. + */ + void setEndsAt( QDateTime aNewEnd ); + //! Sets new subject to the meeting. + /*! + * Sets new subject to the meeting. + * \param aSubject The description of the new subject. + */ + void setSubject( const QString &aSubject ); + //! Sets new description to the meeting. + /*! + * Sets new description to the meeting. + * \param aDescription The description of the meeting. + */ + void setDescription( const QString &aDescription ); + //! Checks if two objects are equal. + /*! + * Checks if the another same type object is equal to the current instance. + * \param *aOther The pointer to another Meeting class instance. + * \return TRUE if equals; otherwise, FALSE. + */ + bool equals( const Meeting &aOther ) const; + //! Checks if two Meetings are overlaping. + /*! + * Checks if the current Meeting instance overlaps the parameter one. + * \param *aOther The pointer to another Meeting class instance. + * \return TRUE if overlaps; otherwise, FALSE. + */ + bool overlaps( const Meeting &aOther ) const; + //! Makes a string to identify a meeting. + /*! + * Makes string representation of the current Meeting instance. + * \return The string. + */ + QString toString() const; + +private: + QString iPrimaryId; + QString iSecondaryId; + Room iRoom; + QDateTime iStartsAt; + QDateTime iEndsAt; + QString iSubject; + QString iOrganizerName; + QString iOrganizerEMail; + QString iDescription; + bool iDetailsAvailable; + +}; + +#endif /*MEETING_H_*/ diff --git a/src/Domain/Room.cpp b/src/Domain/Room.cpp new file mode 100644 index 0000000..749152e --- /dev/null +++ b/src/Domain/Room.cpp @@ -0,0 +1,45 @@ +#include "Room.h" + +Room::Room( const QString &aName, const QString &aAddress ) +{ + this->iName = aName; + this->iAddress = aAddress; +} + +Room::~Room() +{ +} + +QString Room::address() const +{ + return iAddress; +} + +QString Room::name() const +{ + return iName; +} + +bool Room::equals( const Room &aOther ) const +{ + if ( iName == aOther.iName + && iAddress == aOther.iAddress ) + { + return true; + } + return false; +} + +QString Room::toString() const +{ + QString roomToString = QString( "[Room: name:%1 address:%2 ]" ) + .arg( iName ) + .arg( iAddress ); + + return roomToString; +} + +bool Room::caseInsensitiveLessThan( const Room *aRoom1, const Room *aRoom2 ) +{ + return aRoom1->iName.toLower() < aRoom2->iName.toLower(); +} diff --git a/src/Domain/Room.h b/src/Domain/Room.h new file mode 100644 index 0000000..2f6d8f4 --- /dev/null +++ b/src/Domain/Room.h @@ -0,0 +1,76 @@ +#ifndef ROOM_H_ +#define ROOM_H_ + +#include + +//! Domain class. Describe a meeting room resource on Microsoft Exchange Server 2007. +/*! + * Domain class. Describe a meeting room resource on Microsoft Exchange Server 2007. + */ +class Room +{ +public: + //! Enumeration of the status of the room. + /*! + * Enumeration of the status of the room. The room instance can have one of the two states. + */ + enum Status + { + BusyStatus, /*!< The room is reserved. */ + FreeStatus /*!< The room is free. */ + }; + +public: + //! Constructor. + /*! + * Constructor to initialize a Room instance. + * \param aName The name of the meeting room. + * \param aAddress The mail address of the meeting room. + */ + Room( const QString &aName, const QString &aAddress ); + //! Destructor. + virtual ~Room(); + + //! Gets the name of the room. + /*! + * Gets the name of the room. + * \return The string containing the name. + */ + QString name() const; + //! Gets the address of the room. + /*! + * Gets the address of the room. + * \return The string containing the address. + */ + QString address() const; + //! Checks if two objects are equal. + /*! + * Checks if the another same type object is equal to the current instance. + * \param *aOther The pointer to another Room class instance. + * \return TRUE if equals; otherwise, FALSE. + */ + bool equals( const Room &aOther ) const; + //! Makes a string to identify a room. + /*! + * Makes one string of the name and the address of the room. + * \return The string containing the name and address of the room. + */ + QString toString() const; + //! Method to compare equalness of two rooms. + /*! + * Compares if two rooms are equal. Is not case sensitive. + * \param *aRoom1 A Pointer to the first room class instance. + * \param *aRoom2 A Pointer to the second room class instance. + * \return The string containing the name and address of the room. + */ + static bool caseInsensitiveLessThan( const Room *aRoom1, const Room *aRoom2 ); + +private: + //! The name for the room in this instance. + QString iName; + //! The address for the room in this instance. + QString iAddress; + +}; + +#endif /*ROOM_H_*/ diff --git a/src/IO/Communication/Communication.cpp b/src/IO/Communication/Communication.cpp new file mode 100644 index 0000000..82b847e --- /dev/null +++ b/src/IO/Communication/Communication.cpp @@ -0,0 +1,114 @@ +#include "Communication.h" +#include "ConnectionSettings.h" +#include + +Communication::Communication( const ConnectionSettings &aConnection ) : + iCurrentRequest(0), + iAuthFailCount(0) +{ + iConnectionSettings = new ConnectionSettings( aConnection ); + + iHttp = new QHttp( iConnectionSettings->serverUrl().toString(), QHttp::ConnectionModeHttps ); + iResponse = new QByteArray(); + + connect( iHttp, + SIGNAL( readyRead( const QHttpResponseHeader& ) ), + this, + SLOT( processResponse( const QHttpResponseHeader& ) ) + ); + connect( iHttp, + SIGNAL( requestStarted( int ) ), + this, + SLOT( handleRequestStarted( int ) ) + ); + connect( iHttp, + SIGNAL( requestFinished( int, bool ) ), + this, + SLOT( handleResults( int, bool ) ) + ); + connect( iHttp, + SIGNAL( dataReadProgress( int, int ) ), + this, + SLOT( handleReadProgress( int, int ) ) + ); + connect( iHttp, + SIGNAL( authenticationRequired( const QString&, quint16, QAuthenticator* ) ), + this, + SLOT( handleAuthentication( const QString&, quint16, QAuthenticator* ) ) ); + connect( iHttp, + SIGNAL( sslErrors( const QList& ) ), + iHttp, + SLOT( ignoreSslErrors() )/*this, SLOT( notifySsl( const QList& ) )*/ + ); +} + +Communication::~Communication() +{ + delete iConnectionSettings; + delete iHttp; + delete iResponse; +} + +void Communication::processResponse( const QHttpResponseHeader& aHeader ) +{ + if ( aHeader.statusCode() == 200 ) + { + iResponse->append( iHttp->readAll() ); + } +} + +void Communication::handleRequestStarted( int aRequestId ) +{ + if( aRequestId != 0 && iCurrentRequest != 0 && + aRequestId == iCurrentRequest ) + { + emit requestStarted( iCurrentRequest ); + } +} + +void Communication::handleResults( int aId, bool /*aError*/ ) +{ + if( aId == iCurrentRequest ) + { + iCurrentRequest = 0; + emit requestFinished( aId, iHttp->error() ); + } +} + +void Communication::handleAuthentication( const QString& /*aHost*/, quint16 /*aPort*/, QAuthenticator* aAuthenticator ) +{ + aAuthenticator->setPassword( iConnectionSettings->password() ); + aAuthenticator->setUser( iConnectionSettings->username() ); + iAuthFailCount++; + if( iAuthFailCount > 1 ) + { + iHttp->abort(); + } +} + +int Communication::request( const QString &aCommand, const QByteArray &aContent ) +{ + //TODO: This is just temporarily here to implement multiple request support + if( iCurrentRequest ) + return 0; + QHttpRequestHeader header( QString( "POST" ), QString( "/ews/exchange.asmx" ) ); + header.setValue( "Host", iConnectionSettings->serverUrl().toString() ); + QString command = aCommand; + header.setContentType( command ); + + iAuthFailCount = 0; + iResponse->clear(); + iCurrentRequest = iHttp->request( header, aContent ); + + return iCurrentRequest; +} + +const QByteArray& Communication::response( int /*aRequestId*/ ) +{ + return *iResponse; +} + +void Communication::handleReadProgress( int aDone, int aTotal ) +{ + emit readProgress( iCurrentRequest, aDone, aTotal ); +} diff --git a/src/IO/Communication/Communication.h b/src/IO/Communication/Communication.h new file mode 100644 index 0000000..9fd8523 --- /dev/null +++ b/src/IO/Communication/Communication.h @@ -0,0 +1,101 @@ +#ifndef COMMUNICATION_H_ +#define COMMUNICATION_H_ + +#include +#include + +class QByteArray; +class ConnectionSettings; + +//! Class for handling HTTP requests and responses. +/*! + * This class uses the QHttp class to make HTTP requests. HTTP responses are + * returned as QByteArray. + * + * NOTE! Currently this class does NOT support multiple simultaneuos requests. + */ +class Communication : public QObject +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * \param aConnection Reference to ConnectionSettings which holds + * the server to connect to and authentication information. + */ + Communication( const ConnectionSettings &aConnection ); + + virtual ~Communication(); + //! Returns the response of a request identified by aRequestId. + /*! + * NOTE! Currently the last request response is returned as the multiple + * request support is not implemented. + * \param aRequestId Request id number. + */ + const QByteArray& response( int aRequestId ); + + //! Makes a HTTP request to the server defined in the constructor. + /*! + * Returns a request id number if successful, otherwise zero. + * \param aCommand Content type string of the HTTP header. + * \param aContent Content body of the HTTP POST request. + */ + int request( const QString &aCommand, const QByteArray &aContent ); + +signals: + //! Emitted when a request ongoing. Reports the bytes read from the server. + /*! + * \param aRequestId Request id number. + * \param aDone Bytes read from the server. + * \param aTotal Bytes total of the response. + */ + void readProgress( int aRequestId, int aDone, int aTotal ); + //! Emitted when a request is started. + /*! + * \param aRequestId Request id number. + */ + void requestStarted( int aRequestId ); + //! Emitted when a request is finished. + /*! + * \param aRequestId Request id number. + * \param aError Error code of the request finished. + */ + void requestFinished( int aRequestId, QHttp::Error aError ); + +protected slots: + //! Connected to QHttp::readyRead to read the response buffer. + void processResponse( const QHttpResponseHeader& aHeader ); + //! Connected to QHttp::authenticationRequired to provide the user name and password defined in ConnectionSettings + void handleAuthentication( const QString& aHost, quint16 aPort, QAuthenticator* aAuthenticator ); + //! Connected to QHttp::requestFinished + void handleResults( int aId, bool aError ); + //! Connected to QHttp::requestStarted + void handleRequestStarted( int aRequestId ); + //! Connected to QHttp::dataReadProgress + void handleReadProgress( int aDone, int aTotal ); + +private: + /*! + * Instance of Connection settings of the communication + */ + ConnectionSettings *iConnectionSettings; + /*! + * Instance of QHttp + */ + QHttp* iHttp; + /*! + * Response buffer + */ + QByteArray* iResponse; + /*! + * ID number of a request currently ongoing + */ + int iCurrentRequest; + /*! + * Counter how many times auhentication was failed to the server + */ + int iAuthFailCount; +}; + +#endif /*COMMUNICATION_H_*/ diff --git a/src/IO/Communication/CommunicationManager.cpp b/src/IO/Communication/CommunicationManager.cpp new file mode 100644 index 0000000..4212f68 --- /dev/null +++ b/src/IO/Communication/CommunicationManager.cpp @@ -0,0 +1,192 @@ +#include "CommunicationManager.h" +#include "Communication.h" +#include "ConnectionSettings.h" +#include "Meeting.h" +#include "Room.h" +#include +#include +#include + +static const int ERROR_BASE=100; + +CommunicationManager::CommunicationManager( const ConnectionSettings &aConnection ) +{ + iConnectionSettings = new ConnectionSettings( aConnection ); + iModifyingCommunication = NULL; + iFetchingCommunication = new Communication( aConnection ); + + if ( iFetchingCommunication ) + { + connect( iFetchingCommunication, + SIGNAL( readProgress( int, int, int ) ), + this, + SLOT( readProgress( int, int, int ) ) + ); + connect( iFetchingCommunication, + SIGNAL( requestFinished( int, QHttp::Error ) ), + this, + SLOT( requestFinished( int, QHttp::Error ) ) + ); + } +} + +CommunicationManager::~CommunicationManager() +{ + delete iConnectionSettings; + delete iModifyingCommunication; + delete iFetchingCommunication; + while ( !iMeetings.isEmpty() ) + delete iMeetings.takeFirst(); +} + +void CommunicationManager::fetchMeetings( const QDateTime &aFrom, const QDateTime &aUntil, const Room &aIn ) +{ + ReqMsgGetUserAvailability msg; + msg.setTimeZone(); + msg.setTimeWindow( aFrom, aUntil ); + msg.addUser( aIn.address() ); + + int id = iFetchingCommunication->request( msg.getContentTypeForHeader(), msg.getMessage() ); + qDebug() << "CommunicationManager::fetchMeetings: id: " << id; + if( id ) + { + addRequest( GetUserAvailability, id ); + iRequestInfos.first()->room = new Room( aIn ); + } +} + +void CommunicationManager::getSecondaryIdForMeeting( Meeting &aMeeting ) +{ + ReqMsgConvertMeetingId msg( aMeeting.primaryId(), aMeeting.room().address() ); + + QByteArray arr = msg.getMessage(); + + int id = iFetchingCommunication->request( msg.getContentTypeForHeader(), arr ); + qDebug() << "CommunicationManager::getSecondaryIdForMeeting: id: " << id; + if( id ) + { + addRequest( ConvertId, id ); + iRequestInfos.first()->meeting = &aMeeting; + } +} + +void CommunicationManager::fetchMeetingDetails( Meeting& aMeeting ) +{ + if( aMeeting.detailsAvailable() ) + { + emit meetingDetailsFetched( aMeeting ); + return; + } + if( aMeeting.secondaryId().isEmpty() ) + { + getSecondaryIdForMeeting( aMeeting ); + } + else + { + getMeetingDetails( aMeeting ); + } +} + +void CommunicationManager::getMeetingDetails( Meeting &aMeeting ) +{ + ReqMsgGetCalendarItem msg( aMeeting.secondaryId() ); + + QByteArray arr = msg.getMessage(); + + int id = iFetchingCommunication->request( msg.getContentTypeForHeader(), arr ); + qDebug() << "CommunicationManager::getMeetingDetails: id: " << id; + if( id ) + { + addRequest( GetCalendarItem, id ); + iRequestInfos.first()->meeting = &aMeeting; + } +} + +void CommunicationManager::requestFinished( int aRequestId, QHttp::Error aError ) +{ + RequestData* rd = takeRequest( aRequestId ); + qDebug() << "CommunicationManager::requestFinished: id: " << aRequestId << " error: " << (int)aError; + + if( aError != QHttp::NoError || rd == NULL ) + { + int err = (int)aError; + if( rd == NULL ) + err = 10; + delete rd; + emit error( ERROR_BASE+(int)err, CommunicationManager::FetchingCommunication ); + return; + } + + const QByteArray& response = iFetchingCommunication->response( aRequestId ); + + switch( rd->type ) + { + case GetUserAvailability: + { + ResMsgGetUserAvailability msg( response ); + + while ( !iMeetings.isEmpty() ) + delete iMeetings.takeFirst(); + + int err = msg.getMeetingsFromResponse( iMeetings, *(rd->room) ); + if( err ) + emit error( ERROR_BASE+8, CommunicationManager::FetchingCommunication ); + else + emit meetingsFetched( iMeetings ); + break; + } + case ConvertId: + { + ResponseMessage msg( response ); + QString id = msg.getNodeValue( QString( "Id" ), + QDomNode::AttributeNode, + QString( "AlternateId" ) ); + qDebug( "ID IS: %s", id.toStdString().data() ); + rd->meeting->setSecondaryId( id ); + getMeetingDetails( *(rd->meeting) ); + break; + } + case GetCalendarItem: + { + ResMsgGetCalendarItem msg( response ); + int err = msg.getMeetingDetailsFromResponse( *(rd->meeting) ); + if( err ) + emit error( ERROR_BASE+9, CommunicationManager::FetchingCommunication ); + else + emit meetingDetailsFetched( *(rd->meeting) ); + break; + } + } + delete rd; +} + +void CommunicationManager::readProgress( int /*aRequestId*/, int aDone, int aTotal ) +{ + emit readProgress( aDone, aTotal, CommunicationManager::FetchingCommunication ); +} + +void CommunicationManager::addRequest( RequestType aType, int aRequestId ) +{ + RequestData* rd = new RequestData( aType, aRequestId ); + iRequestInfos.append( rd ); +} + +CommunicationManager::RequestData* CommunicationManager::takeRequest( int aRequestId ) +{ + if( aRequestId == 0 ) + return NULL; + + for( int i = iRequestInfos.count() - 1; i >= 0 ; i-- ) + { + struct RequestData* rd = iRequestInfos[i]; + if( rd->requestId == aRequestId ) + { + iRequestInfos.removeAt( i ); + return rd; + } + } + return NULL; +} + + + diff --git a/src/IO/Communication/CommunicationManager.h b/src/IO/Communication/CommunicationManager.h new file mode 100644 index 0000000..d1ded78 --- /dev/null +++ b/src/IO/Communication/CommunicationManager.h @@ -0,0 +1,163 @@ +#ifndef COMMUNICATIONMANAGER_H_ +#define COMMUNICATIONMANAGER_H_ + +#include +#include +#include +#include "Communication.h" +#include "Room.h" +#include "MessagingUtils.h" + +class ConnectionSettings; +class Meeting; + +//! Responsible for Exchange server communication and request encoding and response parsing. +/*! + * CommunicationManager holds two connections to the Exchange server: + * Fetching and modifying connection. Fetching connetion is read only connection + * which has static authentication information defined in the application configuration. + * The modifying connection's authentication information varies, according to the user interface. + * + * NOTE! Currently Modifying connection is NOT implemented. + */ +class CommunicationManager : public QObject +{ + Q_OBJECT + +public: + enum CommunicationType + { + FetchingCommunication, + ModifyingCommunication + }; + +public: + //! Constructor. + /*! + * \param aConnection Reference to the fetching ConnectionSettings. + */ + CommunicationManager( const ConnectionSettings &aConnection ); + virtual ~CommunicationManager(); + //! Starts fetching meetings. Meetings are returned by the meetingsFetched signal. + /*! + * Calls the MS Exchange GetUserAvailability WebService method. + * \param aFrom QDateTime to start searching from. + * \param aUntil QDateTime to search meetings until. + * \param aIn Meeting room meetings are searched. + */ + void fetchMeetings( const QDateTime &aFrom, const QDateTime &aUntil, const Room &aIn ); + //! Starts fetching a meeting details. Details are returned by the meetingDetailsFetched signal. + /*! + * \param aMeeting A meeting the detailed information is wanted. + */ + void fetchMeetingDetails( Meeting &aMeeting ); +/* Not supported member functions which are using the modifying communication + void setModifyCredentials( const QString &aUsername, const QString &aPassword ) {}; + void createMeeting( const Meeting &aMeeting ) {}; + void updateMeeting( const Meeting &aMeeting ) {}; + void deleteMeeting( const Meeting &aMeeting ) {}; +*/ + +signals: + //! Emitted when an error happens. Error could be QHttp or SOAP related. + /*! + * \param aCode An error code defined by CommunicationManager. + * \param aType FetchingCommunication or ModifyingCommunication. + */ + void error( int aCode, CommunicationManager::CommunicationType aType ); + //! Emitted when a http request is ongoing. Reports the bytes read from the server. + /*! + * \param aDone Bytes read from the server. + * \param aTotal Bytes total of the response. + * \param aType FetchingCommunication or ModifyingCommunication. + */ + void readProgress( int aDone, int aTotal, CommunicationManager::CommunicationType aType ); + //! Emitted when meetings are fetched. + /*! + * \param aMeetings Meetings found according to the search criteria. + */ + void meetingsFetched( const QList &aMeetings ); + //! Emitted when meeting details are fetched. + /*! + * \param aDetailedMeeting Meeting which contains detailed information. + */ + void meetingDetailsFetched( Meeting &aDetailedMeeting ); + +protected slots: + //! Connected to Communication::requestFinished. + void requestFinished( int aRequestId, QHttp::Error aError ); + //! Connected to Communication::readProgress. + void readProgress( int aRequestId, int aDone, int aTotal ); + +protected: + //! Gets the secondary id number of a meeting. + /*! + * Calls the MS Exchange ConvertId WebService method. Converts primary id + * number to secondary id number. + * \param aMeeting A meeting which contains primaryId number. + */ + void getSecondaryIdForMeeting( Meeting &aMeeting ); + //! Gets the details of a meeting. + /*! + * Calls the MS Exchange GetCalendarItem WebService method. + * \param aMeeting A meeting the detailed information is wanted. + */ + void getMeetingDetails( Meeting &aMeeting ); + + /*! + * Lists the WebService methods known by the CommunicationManager. + */ + enum RequestType + { + GetUserAvailability = 1, + ConvertId, + GetCalendarItem + }; + //! Internal data structure which contains data about requests made to Exchange server. + class RequestData + { + public: + RequestData( RequestType aType, int aRequestId ) : + type( aType ), requestId( aRequestId ), room( NULL ), meeting( NULL ) {}; + ~RequestData() + { + if( type == GetUserAvailability ) + delete room; + }; + //! Holds one of the enum RequestType value + RequestType type; + //! Request ID number returned by the Communication::request + int requestId; + //! Pointer to a Room related to this request. NULL by default. Owned. + Room* room; + //! Pointer to a Meeting related to this request. Not owned. + Meeting* meeting; + }; + //! Adds a request to the iRequestInfos list. + /*! + * \param aType One of the enum RequestType value. + * \param aRequestId Request ID number returned by the Communication::request + */ + void addRequest( RequestType aType, int aRequestId ); + //! Takes RequestData off from the iRequestInfos list. + /*! + * Returns a pointer to RequestData data structure. Removes RequestData from + * the iRequestInfos list. + * \param aRequestId Request ID number returned by the Communication::request + */ + RequestData* takeRequest( int aRequestId ); + +private: + //! Instance of Connection settings of the fetching communication + ConnectionSettings *iConnectionSettings; + //! Instance of the fetching communication + Communication* iFetchingCommunication; + //! Instance of the modifying communication + Communication* iModifyingCommunication; + //! Temporary list which holds the lastest meetings fetched + QList iMeetings; + //! Additional information about requests made to the Exchange server + QList iRequestInfos; +}; + +#endif /*COMMUNICATIONMANAGER_H_*/ diff --git a/src/IO/Communication/MessagingUtils.cpp b/src/IO/Communication/MessagingUtils.cpp new file mode 100644 index 0000000..0f6e63e --- /dev/null +++ b/src/IO/Communication/MessagingUtils.cpp @@ -0,0 +1,1125 @@ +#include + +#include "MessagingUtils.h" +#include "Meeting.h" +#include "Configuration.h" + +/* + * BaseMessage class functions +*/ +BaseMessage::BaseMessage() +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::BaseMessage" ); +#endif + iMessage = new QDomDocument(); +#ifdef MU_DEBUG + qDebug( "BaseMessage::BaseMessage end" ); +#endif +} + +BaseMessage::~BaseMessage() +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::~BaseMessage" ); +#endif + if( iMessage ) + { + delete iMessage; + iMessage = 0; + } +#ifdef MU_DEBUG + qDebug( "BaseMessage::~BaseMessage end" ); +#endif +} + +QList BaseMessage::getNodesByName( const QString& aNodeName, const QString& aParentName, QDomNode* aRootNode ) +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::getNodesByName" ); +#endif + + //TODO: Add implementation for attribute search + + QDomNodeList list; + QList specList; + + list = ( aRootNode ) ? aRootNode->toElement().elementsByTagName( aNodeName ) : iMessage->elementsByTagName( aNodeName ); + + for( int i=0; idocumentElement() : *aRootNode; + if( root.isNull() ) + { +#ifdef MU_DEBUG + qDebug( "BaseMessage::getAttributeFromDocument : RootNode is NULL." ); +#endif + return node; + } + + if( aParentName != QString::null ) + { + if( root.isElement() ) + { + QDomNodeList list = root.toElement().elementsByTagName( aParentName ); + if( list.count() > 0 ) parent = list.item( 0 ); + } + } + + QDomNamedNodeMap attrs = parent.attributes(); + + int count = attrs.count(); + if( count==0 || aIndex >= count || !attrs.contains( aAttributeName ) ) return root; + + QList list; + + for( int i=0; i 0 ) + node = list.at( aIndex ); + +#ifdef MU_DEBUG + qDebug( "BaseMessage::getAttributeFromDocument end." ); +#endif + + return node; +} + +QDomNode BaseMessage::getElementFromDocument( const QString& aElementName, + const QString& aParentName, + int aIndex, + QDomNode* aRootNode ) +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::getElementFromDocument" ); +#endif + QDomNode node; + QDomNodeList elems; + + if( !aRootNode ) + { + elems = iMessage->elementsByTagName( aElementName ); + } + else + { + elems = aRootNode->toElement().elementsByTagName( aElementName ); + } + + int count = elems.count(); + if( count == 0 || aIndex >= count ) + { +#ifdef MU_DEBUG + qDebug( "BaseMessage::getElementFromDocument : No Elements found." ); +#endif + return node; //no elements found + } + + QList list; + + for( int i=0; i 0 ) + node = list.at( aIndex ); + +#ifdef MU_DEBUG + qDebug( "BaseMessage::getElementFromDocument end" ); +#endif + return node; +} + +bool BaseMessage::matchName( const QDomNode& aNode, const QString& aName ) +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::matchName" ); +#endif + if( aNode.isElement() ) + { + if( aNode.toElement().tagName().toLower().compare( aName.toLower() ) == 0 + || aNode.localName().toLower().compare( aName.toLower() ) == 0 ) + { +#ifdef MU_DEBUG + qDebug( "BaseMessage::matchName end" ); +#endif + return true; + } + } + else if( aNode.isAttr() ) + { + if( aNode.toAttr().name().toLower().compare( aName.toLower() ) == 0 + || aNode.localName().toLower().compare( aName.toLower() ) == 0 ) + { +#ifdef MU_DEBUG + qDebug( "BaseMessage::matchName end" ); +#endif + return true; + } + } +#ifdef MU_DEBUG + qDebug( "BaseMessage::matchName : No match." ); +#endif + return false; +} + +QByteArray BaseMessage::getMessage() +{ +#ifdef MU_DEBUG + qDebug( "BaseMessage::getMessage" ); +#endif + QByteArray msg; + if( iMessage ) + { + msg.append( iMessage->toByteArray() ); + } +#ifdef MU_DEBUG + qDebug( "BaseMessage::getMessage end" ); +#endif + return msg; +} + +/* + * RequestMessage class functions +*/ +RequestMessage::RequestMessage( RequestCommandId aCommandId ) : iCurrCmd( aCommandId ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::RequestMessage" ); +#endif + createEnvelopeBase(); + + if( aCommandId != ReqCmdNoCommand ) + { + createMessageStructure( aCommandId ); + } +#ifdef MU_DEBUG + qDebug( "RequestMessage::RequestMessage end" ); +#endif +} + +RequestMessage::RequestMessage( const QString& /*aFileName*/ ) +{ + //createEnvelopeBase(); + //TODO: read operation structures from a file +} + +int RequestMessage::addNode( const QDomNode& aNode, QDomNode::NodeType aNodeType, const QString& aParentName, int aIndex, QDomNode* aRootNode ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::addNode" ); +#endif + + if( !iMessage ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::addNode : iMessage is NULL." ); +#endif + return MsgErrSomeError; + } + + int err = MsgErrNoError; + + if( aNodeType == QDomNode::ElementNode && aNode.isElement() ) + { + if( aParentName == QString::null ) + { + if( !aRootNode ) + iMessage->appendChild( aNode ); + else + aRootNode->appendChild( aNode ); + } + else + { + QDomNodeList list = ( aRootNode ) ? aRootNode->toElement().elementsByTagName( aParentName ) : iMessage->elementsByTagName( aParentName ); + + if( list.count() == 0 || aIndex >= list.count() ) err = MsgErrSomeError; + else + { + QDomElement node = list.at( aIndex ).toElement(); + node.appendChild( aNode ); + } + } + } + else if( aNodeType == QDomNode::AttributeNode && aNode.isAttr() ) + { + if( aParentName != QString::null ) + { + QDomNodeList list = ( aRootNode ) ? aRootNode->toElement().elementsByTagName( aParentName ) : iMessage->elementsByTagName( aParentName ); + + if( list.count() == 0 || aIndex >= list.count() ) err = MsgErrSomeError; + else + { + QDomElement node = list.at( aIndex ).toElement(); + node.setAttributeNode( aNode.toAttr() ); + } + } + else + err = MsgErrSomeError; //Attribute must have a parent specified + } + else + err = MsgErrSomeError; + +#ifdef MU_DEBUG + qDebug( "RequestMessage::addNode end : err=%d", err ); +#endif + return err; +} + +int RequestMessage::setNodeValue( const QString& aNodeName, const QString& aValue, QDomNode::NodeType aNodeType, const QString& aParentName, int aIndex, QDomNode* aRootNode ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::setNodeValue" ); +#endif + + QDomNode node = getNodeFromDocument( aNodeName, aNodeType, aParentName, aIndex, aRootNode ); + if( node.isNull() || !iMessage ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::setNodeValue : Node is NULL or iMessage is NULL." ); +#endif + return MsgErrSomeError; + } + + int err = MsgErrNoError; + + if( node.isElement() ) + { + node.appendChild( iMessage->createTextNode( aValue ) ); + } + else if( node.isAttr() ) + node.toAttr().setValue( aValue ); + else + err = MsgErrSomeError; + +#ifdef MU_DEBUG + qDebug( "RequestMessage::setNodeValue end : err=%d", err ); +#endif + return err; +} + +int RequestMessage::createEnvelopeBase() +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::createEnvelopeBase" ); +#endif + if( iMessage ) + { + delete iMessage; + iMessage = 0; + } + + iMessage = new QDomDocument(); + + int size = sizeof( reqCmdArrayEnvelopeBase ) / sizeof( MessageBodyElement ); + int err = MsgErrNoError; + + QDomNode base = constructArrayToNode( reqCmdArrayEnvelopeBase, size ); + + if( !base.isNull() ) + err = addNode( base ); + else err = MsgErrSomeError; + +#ifdef MU_DEBUG + qDebug( "RequestMessage::createEnvelopeBase end : err=%d", err ); +#endif + return err; +} + +QString RequestMessage::getContentTypeForHeader( RequestCommandId aCommandId ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::getContentTypeForHeader" ); +#endif + QString contentType( QString::null ); + + RequestCommandId cmd = ( aCommandId == ReqCmdNoCommand ) ? iCurrCmd : aCommandId; + + if( cmd != ReqCmdNoCommand ) + { + QString operation( QString::null ); + switch( cmd ) + { + //Used commands + case ReqCmdGetUserAvailability : { operation = "GetUserAvailability"; } break; + case ReqCmdConvertId : { operation = "ConvertId"; } break; + case ReqCmdGetItem : { operation = "GetItem"; } break; + + //Currently unused operations + + /*case ReqCmdAddDelegate : { operation = "AddDelegate"; } break; + case ReqCmdCopyFolder : { operation = "CopyFolder"; } break; + case ReqCmdCopyItem : { operation = "CopyItem"; } break; + case ReqCmdCreateAttachment : { operation = "CreateAttachment"; } break; + case ReqCmdCreateFolder : { operation = "CreateFolder"; } break; + case ReqCmdCreateItem : { operation = "CreateItem"; } break; + case ReqCmdCreateManagedFolder : { operation = "CreateManagedFolder"; } break; + case ReqCmdDeleteAttachment : { operation = "DeleteAttachment"; } break; + case ReqCmdDeleteFolder : { operation = "DeleteFolder"; } break; + case ReqCmdDeleteItem : { operation = "DeleteItem"; } break; + case ReqCmdExpandDL : { operation = "ExpandDL"; } break; + case ReqCmdFindFolder : { operation = "FindFolder"; } break; + case ReqCmdFindItem : { operation = "FindItem"; } break; + case ReqCmdGetAttachment : { operation = "GetAttachment"; } break; + case ReqCmdGetDelegate : { operation = "GetDelegate"; } break; + case ReqCmdGetEvents : { operation = "GetEvents"; } break; + case ReqCmdGetFolder : { operation = "GetFolder"; } break; + case ReqCmdGetUserOofSettings : { operation = "GetUserOofSettings"; } break; + case ReqCmdMoveFolder : { operation = "MoveFolder"; } break; + case ReqCmdMoveItem : { operation = "MoveItem"; } break; + case ReqCmdRemoveDelegate : { operation = "RemoveDelegate"; } break; + case ReqCmdResolveNames : { operation = "ResolveNames"; } break; + case ReqCmdSendItem : { operation = "SendItem"; } break; + case ReqCmdSetUserOofSettings : { operation = "SetUserOofSettings"; } break; + case ReqCmdSubscribe : { operation = "Subscribe"; } break; + case ReqCmdSyncFolderHierarchy : { operation = "SyncFolderHierarchy"; } break; + case ReqCmdSyncFolderItems : { operation = "SyncFolderItems"; } break; + case ReqCmdUnsubscribe : { operation = "Unsubscribe"; } break; + case ReqCmdUpdateDelegate : { operation = "UpdateDelegate"; } break; + case ReqCmdUpdateFolder : { operation = "UpdateFolder"; } break; + case ReqCmdUpdateItem : { operation = "UpdateItem"; } break;*/ + default: break; + }; + + if( operation != QString::null ) + { + QString content( "text/xml; " ); + QString charset( "utf-8; " ); + contentType = QString( content + "charset=" + charset + "action=\"" + ACTION_URL + operation + "\"" ); + } + } +#ifdef MU_DEBUG + qDebug( "RequestMessage::getContentTypeForHeader end : contentType=%s", contentType.toStdString().data() ); +#endif + return contentType; +} + +int RequestMessage::createMessageStructure() +{ + return createMessageStructure( iCurrCmd ); +} + +int RequestMessage::createMessageStructure( RequestCommandId aCommandId ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::createMessageStructure" ); +#endif + if( !iMessage ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::createMessageStructure : iMessage is NULL" ); +#endif + return MsgErrSomeError; + } + + //Should always be soap:Envelope or first element in envelopebase array + QDomNode last = iMessage->documentElement(); + + if( !matchName( last, reqCmdArrayEnvelopeBase[0].iElementName ) ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::createMessageStructure : Document element is not valid SOAP envelope" ); +#endif + return MsgErrSomeError; + } + + QDomNode cmd; + int err = MsgErrNoError; + + switch( aCommandId ) + { + case ReqCmdGetUserAvailability : { cmd = constructArrayToNode( reqCmdArrayGetUserAvailability, sizeof( reqCmdArrayGetUserAvailability ) / sizeof( MessageBodyElement ) ); } break; + case ReqCmdConvertId : { cmd = constructArrayToNode( reqCmdArrayConvertId, sizeof( reqCmdArrayConvertId ) / sizeof( MessageBodyElement ) ); } break; + case ReqCmdGetItem : { cmd = constructArrayToNode( reqCmdArrayGetCalendarItem, sizeof( reqCmdArrayGetCalendarItem ) / sizeof( MessageBodyElement ) ); } break; + default: break; + }; + + if( cmd.isNull() ) err = MsgErrSomeError; + else{ + err = addNode( cmd, QDomNode::ElementNode, reqCmdArrayEnvelopeBase[0].iElementName ); + } +#ifdef MU_DEBUG + qDebug( "RequestMessage::createMessageStructure end : err=%d", err ); +#endif + return err; +} + +QDomNode RequestMessage::constructArrayToNode( const MessageBodyElement* aArray, int aSize ) +{ +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode" ); +#endif + QDomElement target; //final node to be returned + + if( !iMessage || aSize == 0 ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : iMessage is NULL or aSize is 0." ); +#endif + return target; + } + + QDomElement last; // last appended node + + int currentLvl = 0; //current traversal level + int idx = 0; + + //get the root + MessageBodyElement root = aArray[idx]; + if( root.iNodeType != QDomNode::ElementNode || root.iTraversalLevel > 0 ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Malformed message definition. Check array for faults." ); +#endif + return target; //Root must be an element and on level 0 + } + else + { + if( root.iNamespace != QString::null ) + { + //QString prefix = root.iNamespace + target = iMessage->createElementNS( root.iNamespace, root.iElementName ); + } + else + { + target = iMessage->createElement( root.iElementName ); + } + last = target; + idx++; + } + + for( int i=idx; icreateElementNS( element.iNamespace, element.iElementName ); + } + else + { + elem = iMessage->createElement( element.iElementName ); + } + if( element.iTraversalLevel == currentLvl ) + { + if( currentLvl == 0 ) + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Malformed message definition. Check array for faults." ); +#endif + return target; //Only root can be at traversal level 0 + } + else + { + //Sibling to previous + if( !last.parentNode().isNull() ) + last.parentNode().appendChild( elem ); + else + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Malformed message definition. Check array for faults." ); +#endif + return target; //malformed. if last has no parent, it's a rootnode + } + } + } + else if( element.iTraversalLevel > currentLvl ) + { + //check if node is child to previous + //else there is something wrong in array representing the structure + if( element.iTraversalLevel == currentLvl+1 ) + { + last.appendChild( elem ); + } + else + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Malformed message definition. Check array for faults." ); +#endif + return target; + } + } + else if( element.iTraversalLevel < currentLvl ) + { + + //Node is on more shallow level than previous + //let's get it's parent + int diff = currentLvl - element.iTraversalLevel; + + for( int j=0; jcreateAttributeNS( element.iNamespace, element.iElementName ); + else + attr = iMessage->createAttribute( element.iElementName ); + + if( element.iTraversalLevel == currentLvl ) + { + last.setAttributeNode( attr ); + } + else + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Malformed message definition. Check array for faults." ); +#endif + //error in template array. + //attributes for element must be specified right after element + //and must have same traversalId + return target; + } + } + else + { +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : Node type not supported." ); +#endif + //node type not supported + return target; + } + + currentLvl = element.iTraversalLevel; + } +#ifdef MU_DEBUG + qDebug( "RequestMessage::constructArrayToNode : end" ); +#endif + return target; +} + +/* + * ResponseMessage class functions + */ +ResponseMessage::ResponseMessage() +{ +#ifdef MU_DEBUG + qDebug( "ResponseMessage::ResponseMessage" ); +#endif + iMessage = new QDomDocument(); +#ifdef MU_DEBUG + qDebug( "ResponseMessage::ResponseMessage end" ); +#endif +} + +ResponseMessage::ResponseMessage( const QByteArray& aData ) +{ +#ifdef MU_DEBUG + qDebug( "ResponseMessage::ResponseMessage" ); +#endif + iMessage->setContent( aData, true ); +#ifdef MU_DEBUG + qDebug( "ResponseMessage::ResponseMessage end" ); +#endif +} + +QString ResponseMessage::getNodeValue( const QString& aNodeName, QDomNode::NodeType aNodeType, const QString& aParentName, int aIndex, QDomNode* aRootNode ) +{ +#ifdef MU_DEBUG + qDebug( "ResponseMessage::getNodeValue" ); +#endif + QString value = QString::null; + QDomNode node = getNodeFromDocument( aNodeName, aNodeType, aParentName, aIndex, aRootNode ); + if( !node.isNull() ) + { + if( node.isElement() ) + value = node.toElement().text(); + else if( node.isAttr() ) + value = node.toAttr().value(); + } +#ifdef MU_DEBUG + qDebug( "ResponseMessage::getNodeValue end" ); +#endif + return value; +} + +bool ResponseMessage::hasErrors() +{ +#ifdef MU_DEBUG + qDebug( "ResponseMessage::hasErrors" ); +#endif + if( !iMessage ) + { +#ifdef MU_DEBUG + qDebug( "ResponseMessage::hasErrors : iMessage is NULL." ); +#endif + return true; + } + + QDomElement root = iMessage->documentElement(); + bool err = false; + + QString rootname = ( root.prefix() == QString::null ) ? root.tagName() : root.localName(); + err = ( rootname.compare( QString( "Envelope" ) ) == 0 ) ? false : true; + + QDomNodeList list = iMessage->elementsByTagName( QString( "m:ResponseCode" ) ); + for( int i=0; icreateAttribute( QString( "xmlns:t" ) ); + attr.setValue( NS_T ); + addNode( attr, QDomNode::AttributeNode, QString( "Envelope" ) ); + */ +#ifdef MU_DEBUG + qDebug( "ReqMsgConvertMeetingId::ReqMsgConvertMeetingId end" ); +#endif +} + +int ReqMsgConvertMeetingId::setItemId( const QString& aItemId ) +{ +#ifdef MU_DEBUG + qDebug( "ReqMsgConvertMeetingId::setItemId" ); +#endif + int err = setNodeValue( QString( "Id" ), aItemId, QDomNode::AttributeNode, QString( "AlternateId" ) ); +#ifdef MU_DEBUG + qDebug( "ReqMsgConvertMeetingId::setItemId end : err=%d", err ); +#endif + return err; +} + +int ReqMsgConvertMeetingId::setMailbox( const QString& aMailbox ) +{ +#ifdef MU_DEBUG + qDebug( "ReqMsgConvertMeetingId::setMailbox" ); +#endif + int err = setNodeValue( QString( "Mailbox" ), aMailbox, QDomNode::AttributeNode, QString( "AlternateId" ) ); +#ifdef MU_DEBUG + qDebug( "ReqMsgConvertMeetingId::setMailbox end : err=%d", err ); +#endif + return err; +} + +/* + * ReqMsgGetCalendarItem class functions + */ +ReqMsgGetCalendarItem::ReqMsgGetCalendarItem( const QString& aItemId ) : RequestMessage( ReqCmdGetItem ) +{ +#ifdef MU_DEBUG + qDebug( "ReqMsgGetCalendarItem::ReqMsgGetCalendarItem" ); +#endif + if( iMessage && iCurrCmd == ReqCmdGetItem ) + { + setNodeValue( QString( "BaseShape" ), QString( "AllProperties" ) ); + + if( aItemId != QString::null ) + setItemId( aItemId ); + } +#ifdef MU_DEBUG + qDebug( "ReqMsgGetCalendarItem::ReqMsgGetCalendarItem end" ); +#endif +} + +int ReqMsgGetCalendarItem::setItemId( const QString& aItemId ) +{ +#ifdef MU_DEBUG + qDebug( "ReqMsgGetCalendarItem::setItemId" ); +#endif + int err = setNodeValue( QString( "Id" ), aItemId, QDomNode::AttributeNode, QString( "ItemId" ), 0 ); +#ifdef MU_DEBUG + qDebug( "ReqMsgGetCalendarItem::setItemId end : err=%d", err ); +#endif + return err; +} + +/* + * ResMsgGetUserAvailability class functions + */ +ResMsgGetUserAvailability::ResMsgGetUserAvailability( const QByteArray& aData ) : ResponseMessage( aData ) +{ +#ifdef MU_DEBUG + qDebug( "ResMsgGetUserAvailability::ResMsgGetUserAvailability" ); + qDebug( "ResMsgGetUserAvailability::ResMsgGetUserAvailability end" ); +#endif +} + +int ResMsgGetUserAvailability::getMeetingsFromResponse( QList& aMeetings, const Room &aRoom ) +{ +#ifdef MU_DEBUG + qDebug( "ResMsgGetUserAvailability::getMeetingsFromResponse" ); +#endif + if( !iMessage ) + { +#ifdef MU_DEBUG + qDebug( "ResMsgGetUserAvailability::getMeetingsFromResponse : iMessage is NULL" ); +#endif + return MsgErrSomeError; + } + + //TODO: Clean this function a bit (see getMeetingDetailsFromResponse) + + int err = MsgErrNoError; + + QDomNodeList list = iMessage->elementsByTagName( QString( "CalendarEvent" ) ); + + for( int i=0; i +#include +#include +#include + +//Namespace definitions +#define NS_XSI "http://www.w3.org/2001/XMLSchema-instance" +#define NS_XSD "http://www.w3.org/2001/XMLSchema" +#define NS_SOAP "http://schemas.xmlsoap.org/soap/envelope/" +#define NS_T "http://schemas.microsoft.com/exchange/services/2006/types" +#define NS_MSG "http://schemas.microsoft.com/exchange/services/2006/messages" + +//Used for http request header to define web service operation +#define ACTION_URL "http://schemas.microsoft.com/exchange/services/2006/messages/" + +//Set MessagingUtils Debug on/off +//#define MU_DEBUG +#undef MU_DEBUG + +class Meeting; +class Room; + +//All supported operations for web services +//Operations are described here: http://msdn.microsoft.com/en-us/library/bb409286.aspx +enum RequestCommandId +{ + ReqCmdNoCommand = 0, + ReqCmdAddDelegate, //1 + ReqCmdConvertId, //2 Used for converting fetched IDs to match the format in GetItem request + ReqCmdCopyFolder, //3 + ReqCmdCopyItem, //4 + ReqCmdCreateAttachment, //5 + ReqCmdCreateFolder, //6 + ReqCmdCreateItem, //7 + ReqCmdCreateManagedFolder, //8 + ReqCmdDeleteAttachment, //9 + ReqCmdDeleteFolder, //10 + ReqCmdDeleteItem, //11 + ReqCmdExpandDL, //12 + ReqCmdFindFolder, //13 + ReqCmdFindItem, //14 + ReqCmdGetAttachment, //15 + ReqCmdGetDelegate, //16 + ReqCmdGetEvents, //17 + ReqCmdGetFolder, //18 + ReqCmdGetItem, //19 Used for getting the detailed information about some item (calendar event) + ReqCmdGetUserAvailability, //20 Used for fetching meetings + ReqCmdGetUserOofSettings, //21 + ReqCmdMoveFolder, //22 + ReqCmdMoveItem, //23 + ReqCmdRemoveDelegate, //24 + ReqCmdResolveNames, //25 + ReqCmdSendItem, //26 + ReqCmdSetUserOofSettings, //27 + ReqCmdSubscribe, //28 + ReqCmdSyncFolderHierarchy, //29 + ReqCmdSyncFolderItems, //30 + ReqCmdUnsubscribe, //31 + ReqCmdUpdateDelegate, //32 + ReqCmdUpdateFolder, //33 + ReqCmdUpdateItem //34 +}; + +//TODO: Define more specific errors +enum MessagingError +{ + MsgErrNoError = 0, + MsgErrSomeError +}; + +//! Struct to present a node in SOAP envelope message. +/*! + * Struct to present a node in SOAP envelope message. + * Request messages are formed by using predefined arrays + * of MessageBodyElement objects. + */ +typedef struct MessageBodyElement +{ + //! Depth of the node in document tree. For example, root node should be 0. + int iTraversalLevel; + //! Namespace of the node. QString::null if not specified. + QString iNamespace; + //! Name of the node. + QString iElementName; + //! Type of the node. Currently QDomNode::ElementNode and QDomNode::AttributeNode are supported. + QDomNode::NodeType iNodeType; +}; + +//SOAP Envelope templates for web service operations +//Operations are described here: http://msdn.microsoft.com/en-us/library/bb409286.aspx +/* + * NOTES: + * - First node's TraversalLevel must always be 0. + * - Namespace can be defined as an URI or QString::null + * - NodeType can be QDomNode::ElementNode or QDomNode::AttributeNode + * - Nodes must be defined in descending order, according to schema. + * - Attribute must be defined right after it's owner element and it's traversal level must be same. + */ +const MessageBodyElement reqCmdArrayEnvelopeBase[] = +{ + { 0, NS_SOAP, "Envelope", QDomNode::ElementNode }/*, + { 0, "xmlns", "xsi", QDomNode::AttributeNode }, + { 0, "xmlns", "xsd", QDomNode::AttributeNode }, + { 0, "xmlns", "soap", QDomNode::AttributeNode }, + { 0, "xmlns", "t", QDomNode::AttributeNode },*/ +}; + +const MessageBodyElement reqCmdMailboxElement[] = +{ + { 0, NS_T, "MailboxData", QDomNode::ElementNode }, + { 1, NS_T, "Email", QDomNode::ElementNode }, + { 2, NS_T, "Address", QDomNode::ElementNode }, + { 1, NS_T, "AttendeeType", QDomNode::ElementNode }, + { 1, NS_T, "ExcludeConflicts", QDomNode::ElementNode }, +}; + +const MessageBodyElement reqCmdArrayGetUserAvailability[] = +{ + { 0, NS_SOAP, "Body", QDomNode::ElementNode }, + { 1, NS_MSG, "GetUserAvailabilityRequest", QDomNode::ElementNode }, + { 2, NS_T, "TimeZone", QDomNode::ElementNode }, + { 3, QString::null, "Bias", QDomNode::ElementNode }, + { 3, QString::null, "StandardTime", QDomNode::ElementNode }, + { 4, QString::null, "Bias", QDomNode::ElementNode }, + { 4, QString::null, "Time", QDomNode::ElementNode }, + { 4, QString::null, "DayOrder", QDomNode::ElementNode }, + { 4, QString::null, "Month", QDomNode::ElementNode }, + { 4, QString::null, "DayOfWeek", QDomNode::ElementNode }, + // + { 3, QString::null, "DaylightTime", QDomNode::ElementNode }, + { 4, QString::null, "Bias", QDomNode::ElementNode }, + { 4, QString::null, "Time", QDomNode::ElementNode }, + { 4, QString::null, "DayOrder", QDomNode::ElementNode }, + { 4, QString::null, "Month", QDomNode::ElementNode }, + { 4, QString::null, "DayOfWeek", QDomNode::ElementNode }, + // + // + //Use meetingroom addresses to fetch meetings for rooms. Mailboxdata-element for each. + { 2, QString::null, "MailboxDataArray", QDomNode::ElementNode }, + + //MAILBOX DATA(s) MUST BE ADDED AFTERWARDS + //SEE: reqCmdMailboxElement[] + + // + { 2, NS_T, "FreeBusyViewOptions", QDomNode::ElementNode }, + //Use Timewindow to define from which period we want to fetch meetings + { 3, NS_T, "TimeWindow", QDomNode::ElementNode }, + { 4, NS_T, "StartTime", QDomNode::ElementNode }, + { 4, NS_T, "EndTime", QDomNode::ElementNode }, + // + { 3, NS_T, "MergedFreeBusyIntervalInMinutes", QDomNode::ElementNode }, + { 3, NS_T, "RequestedView", QDomNode::ElementNode }, + // + // +}; + +const MessageBodyElement reqCmdArrayGetCalendarItem[] = +{ + { 0, NS_SOAP, "Body", QDomNode::ElementNode }, + { 1, NS_MSG, "GetItem", QDomNode::ElementNode }, + //{ 1, QString::null, "xmlns", QDomNode::AttributeNode }, + { 2, QString::null, "ItemShape", QDomNode::ElementNode }, + { 3, NS_T, "BaseShape", QDomNode::ElementNode }, + { 2, QString::null, "ItemIds", QDomNode::ElementNode }, + { 3, NS_T, "ItemId", QDomNode::ElementNode }, + { 3, QString::null, "Id", QDomNode::AttributeNode } +}; + +const MessageBodyElement reqCmdArrayConvertIdHeader[] = +{ + { 0, NS_SOAP, "Header", QDomNode::ElementNode }, + { 1, NS_T, "RequestServerVersion", QDomNode::ElementNode }, + { 1, QString::null, "Version", QDomNode::AttributeNode }, + +}; + +const MessageBodyElement reqCmdArrayConvertId[] = +{ + { 0, NS_SOAP, "Body", QDomNode::ElementNode }, + { 1, NS_MSG, "ConvertId", QDomNode::ElementNode }, + //{ 1, QString::null, "xmlns:t", QDomNode::AttributeNode }, + //{ 2, "xmlns", "t", QDomNode::AttributeNode }, + { 1, QString::null, "DestinationFormat", QDomNode::AttributeNode }, + { 2, QString::null, "SourceIds", QDomNode::ElementNode }, + { 3, NS_T, "AlternateId", QDomNode::ElementNode }, + { 3, QString::null, "Format", QDomNode::AttributeNode }, + { 3, QString::null, "Id", QDomNode::AttributeNode }, + { 3, QString::null, "Mailbox", QDomNode::AttributeNode }, +}; + + +//! Base class for Request and Response message classes +/*! + * This class provides basic functionality to use generated SOAP envelope DomDocument object. + */ +class BaseMessage : public QObject +{ + Q_OBJECT + +public: + + //! Constructor. + BaseMessage(); + //! Destructor. + virtual ~BaseMessage(); + + //! Current generated SOAP envelope DomDocument object as byte array. + /*! + * Returns current message envelope as byte array. + * This array is used to provide a content for http request. + * \return current message envelope as byte array. + */ + QByteArray getMessage(); + +protected: + + //! List of QDomNode objects matching desired criteria. + /*! + * Returns a list of nodes that matches the given aNodeName. + * Optional parameters can be provided to narrow the search. + * \param aNodeName Name of the node(s). + * \param aParentName Name of the parent node. Optional. + * If not specified, base node of the QDomDocument is used. + * \param aRootNode Root node for the search. Optional. + * If provided, only it's childnodes will be searched. + * If not specified, base node of the QDomDocument is used. + * \return List of QDomNodes matching the criteria. + */ + QList getNodesByName( const QString& aNodeName, + const QString& aParentName = QString::null, + QDomNode* aRootNode = NULL ); + + //! Single node matching desired criteria. + /*! + * Returns a single node that matches the given aNodeName. + * Optional parameters can be provided to narrow the search. + * \param aNodeName Name of the node. + * \param aNodeType Type of the node. Currently QDomNode::ElementNode and QDomNode::AttributeNode are supported. + * \param aParentName Name of the parent node. Optional. + * If not specified, base node of the QDomDocument is used. + * \param aIndex If multiple results are found, an index of desired node can be used. Optional. + * \param aRootNode Root node for the search. Optional. + * If provided, only it's childnodes will be searched. + * If not specified, base node of the QDomDocument is used. + * \return QDomNode matching the criteria. + */ + QDomNode getNodeFromDocument( const QString& aNodeName, + QDomNode::NodeType aNodeType, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Single element matching desired criteria. + /*! + * See getNodeFromDocument for details. + */ + QDomNode getElementFromDocument( const QString& aElementName, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Single attribute matching desired criteria. + /*! + * See getNodeFromDocument for details. + */ + QDomNode getAttributeFromDocument( const QString& aAttributeName, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Compare node name to string. + /*! + * Checks if name of the provided node matches aName. + * Node can be QDomNode::ElementNode or QDomNode::AttributeNode. + * \param aNode Node to compare. + * \param aName Name to compare. + * \return True if comparison matches. Otherwise false. + * NOTE: This function does comparison for node's local name, so it ignores possible prefixes in a tag name. + */ + bool matchName( const QDomNode& aNode, const QString& aName ); + + + +protected: + + /*! + * Current generated SOAP envelope DomDocument object. + */ + QDomDocument* iMessage; + +}; + +//! Base class for Requests +/*! + * This class provides basic functionality to use generated SOAP envelope DomDocument object for http requests. + */ +class RequestMessage : public BaseMessage +{ + +public: + + //! Constructor + /*! + * \param aCommandId Id to identify operation type. + */ + RequestMessage( RequestCommandId aCommandId = ReqCmdNoCommand ); + RequestMessage( const QString& aFileName ); + //! Destructor + virtual ~RequestMessage(){}; + +public: + + //! Add node to message + /*! + * Adds node to a request message. Parameters can be provided to specify location. + * \param aNode Node to add. + * \param aNodeType Type of node. QDomNode::ElementNode and QDomNode::AttributeNode are supported. Default is QDomNode::ElementNode. + * \param aParentName Name of the parent. Optional. If provided, node will be added as it's child. Otherwise root of the QDomDocument is used. + * \param aIndex Index to add. Optional. If multiple parents are found, index to add can be specified. + * \param aRootNode Root node to search. Optional. Searches only it's child nodes for a parent if provided. Otherwise root of the QDomDocument is used. + * \return An error code. + */ + int addNode( const QDomNode& aNode, + QDomNode::NodeType aNodeType = QDomNode::ElementNode, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Set value of the node + /*! + * Sets the value of specified node. Parameters can be used to narrow the search. + * \param aNodeName Name of the node. + * \param aValue Value to set. + * \param aNodeType Type of node. QDomNode::ElementNode and QDomNode::AttributeNode are supported. Default is QDomNode::ElementNode. + * \param aParentName Name of the parent node. Optional. + * If not specified, base node of the QDomDocument is used. + * \param aIndex If multiple results are found, an index of desired node can be used. Optional. + * \param aRootNode Root node for the search. Optional. + * If provided, only it's childnodes will be searched. + * If not specified, base node of the QDomDocument is used. + * \return An error code. + */ + int setNodeValue( const QString& aNodeName, + const QString& aValue, + QDomNode::NodeType aNodeType = QDomNode::ElementNode, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Create the structure of message (soap:Body) + /* + * \return An error code. + */ + int createMessageStructure(); + //! Create the structure of message (soap:Body) + /* + * \param aCommandId Id of command. Structure is constructed according to this. + * \return An error code. + */ + int createMessageStructure( RequestCommandId aCommandId ); + + //! Create the base of the request envelope (soap:Envelope) + /*! + * \return An error code. + */ + int createEnvelopeBase(); + + //! Get operation specific content type string for http request header. + /*! + * \param aCommandId Id of the command (operation) + * \return Content type string for header + */ + QString getContentTypeForHeader( RequestCommandId aCommandId = ReqCmdNoCommand ); + +protected: + + //! Construct an array of MessageBodyElements to a node. + /*! + * Constructs an array of predefined MessageBodyElements to node. + * This function is used to form different parts of message from MessageBodyElements to QDomNodes + * \param aArray Array of MessageBodyElements. + * \param aSize Size of an array. + * \return Created node. + */ + QDomNode constructArrayToNode( const MessageBodyElement* aArray, int aSize ); + +protected: + + //! Id of current operation + RequestCommandId iCurrCmd; +}; + +//! Base class for Responses. +/*! + * This class provides basic functionality to use generated SOAP envelope DomDocument object from http responses. + */ +class ResponseMessage : public BaseMessage +{ + +public: + + //! Constructor + ResponseMessage(); + + //! Constructor + /*! + * \param aData Byte array to construct ResponseMessage from. + */ + ResponseMessage( const QByteArray& aData ); + + //! Destructor + virtual ~ResponseMessage(){}; + + //! Get the value of node. + /*! + * Gets a value of single node. Optional parameters are used to define desired node in more detail. + * \param aNodeName Name of the node. + * \param aNodeType Type of the node. QDomNode::ElementNode and QDomNode::AttributeNode are supported. + * \param aParentname Name of the parent node. Optional. Can be specified to narrow the search. + * \param aIndex Index of the node. Optional. If multiple results are found, index can be defined to specify the result. + * \param aRootNode Root node. Optional. If specified, only it's children are included in search. Otherwise, root node of QDomDocument is used. + * \return Value of the node. + */ + QString getNodeValue( const QString& aNodeName, + QDomNode::NodeType aNodeType, + const QString& aParentName = QString::null, + int aIndex = 0, + QDomNode* aRootNode = NULL ); + + //! Checks if there are any errors in response. + /*! + * If any value of status elements in response message is not success, this function + * returns true. + * \return True if errors exist. Otherwise false. + */ + bool hasErrors(); + +protected: + +}; + + +//Operation specific inherited classes + + +//! Request message for GetUserAvailability operation. +class ReqMsgGetUserAvailability : public RequestMessage +{ + + enum UserAttendeeType + { + AttendeeOrganizer = 0, + AttendeeRequired, + AttendeeOptional, + AttendeeRoom, + AttendeeResource + }; + +public: + + //! Constructor + ReqMsgGetUserAvailability(); + //! Destructor + virtual ~ReqMsgGetUserAvailability(){}; + //! Set the current time zone. + /* + * \return An error code. + */ + int setTimeZone(); //TODO: initializable with parameters? + + //! Add user (mailbox) to search availability status from. + /* + * Add user (mailbox) to search calendar events from. + * Multiple users can be added in one request. + * \param aAddress User's EMail address. + * \param aAttendeeType Type of the attendance. Default is "Required". + * \param aExcludeConflicts Exclude conflicting meetings. Default is "false". + * \return An error code. + */ + int addUser( const QString& aAddress, const QString& aAttendeeType = "Required", const QString& aExcludeConflicts = "false" ); //TODO: Is resource a correct type for room? + + //! Set the time window for request. + /*! + * Set the time window to search availability statuses from. + * \param aStart Start date time. + * \param aEnd End date time. + * \return An error code. + * NOTE: Server might give an error if too wide time window is specified. + * ~3 weeks should be safe. + */ + int setTimeWindow( const QDateTime& aStart, const QDateTime& aEnd ); + +private: + +}; + +//! Request message for ConvertId operation. +/*! + * This is used to convert HexEntryId type unique Id's of calendar events + * To EwsLegacyId type. Latter type of Id must be provided in order to get detailed information for calendar event. + */ +class ReqMsgConvertMeetingId : public RequestMessage +{ +public: + //! Constructor + /*! + * \param aItemId HexEntryId type Id to convert. Optional (though must be set later). + * \param aMailBox EMail address of calendar event's owner. + */ + ReqMsgConvertMeetingId( const QString& aItemId = QString::null, const QString& aMailbox = QString::null ); + + //! Destructor + virtual ~ReqMsgConvertMeetingId(){}; + + //! Set item id. + /* + * \param aItemId Id of calendar event in HexEntryId format + * \return An error code. + */ + int setItemId( const QString& aItemId ); + + //! Set Mailbox. + /*! + * Set the calendar event owner's mailbox EMail address. + * \param aMailBox Mailbox address. + * \return An error code. + */ + int setMailbox( const QString& aMailbox ); + +private: + +}; + +//! Request message for GetItem (Calendar) operation. +/*! + * This is used to get more detailed information about calendar event. + */ +class ReqMsgGetCalendarItem : public RequestMessage +{ +public: + + //! Constructor + /*! + * \param aItemId secondary Id ( EwsLegacyId type obtained from ConvertId operation ) of calendar event. + */ + ReqMsgGetCalendarItem( const QString& aItemId = QString::null ); + + //! Destructor + virtual ~ReqMsgGetCalendarItem(){}; + + //! Set the Id of calendar event. + /*! + * This must be EwsLegacyId type obtained from ConvertId operation. + * \param aItemId Id of item. + * \return An error code. + */ + int setItemId( const QString& aItemId ); + //int setChangeKey( const QString& aChangeKey ){}; + +private: + +}; + +//! Response message for GetUserAvailability operation. +class ResMsgGetUserAvailability : public ResponseMessage +{ +public: + + //! Constructor + /*! + * \param aData Byte array to construct response message from. + */ + ResMsgGetUserAvailability( const QByteArray& aData ); + + //! Destructor + virtual ~ResMsgGetUserAvailability(){}; + //! Get Meetings from constructed response. + /*! + * \param aList List of Meeting objects to fill. + * \param aRoom Room for Meeting objects. + * \return An error code. + */ + int getMeetingsFromResponse( QList& aList, const Room &aRoom ); +}; + +//! Response message for GetItem (Calendar) operation. +class ResMsgGetCalendarItem : public ResponseMessage +{ +public: + //! Constructor + /*! + * \param aData Byte array to construct response message from. + */ + ResMsgGetCalendarItem( const QByteArray& aData ) : ResponseMessage( aData ){}; + + //! Destructor + virtual ~ResMsgGetCalendarItem(){}; + + //! Get meeting details from constructed response. + /*! + * \param aMeeting Meeting to get details for. + * \return An error code. + */ + int getMeetingDetailsFromResponse( Meeting& aMeeting ); + +}; + +#endif /*MESSAGINGUTILS_H_*/ diff --git a/src/IO/DeviceControl/AlarmSender.cpp b/src/IO/DeviceControl/AlarmSender.cpp new file mode 100644 index 0000000..b49742b --- /dev/null +++ b/src/IO/DeviceControl/AlarmSender.cpp @@ -0,0 +1,200 @@ +#include "AlarmSender.h" +#include "DeviceConstants.h" + +#include +#include + +AlarmSender::AlarmSender( DeviceDataStorage *aDataStorage ) +{ + qDebug() << "AlarmSender( DeviceDataStorage * )"; + iDataStorage = aDataStorage; +} + +AlarmSender::~AlarmSender() +{ + qDebug() << "~AlarmSender()"; +} + +bool AlarmSender::sendAlarms( QTime aTurnOnAt, QTime aTurnOffAt, int aDays ) +{ + qDebug() << "AlarmSender::sendAlarms( QTime, QTime, int )"; + QString errortext; + + if ( aDays != 5 ) //if aDays is 5 we set the alarms only for working days + aDays = 7; + + if ( !removeStoredAlarms() ) //remove possible old alarms + return false; + + alarm_event_t eventOn; + alarm_event_t eventOff; + + //zero the alarm events + memset( &eventOn, 0, sizeof( alarm_event_t ) ); + memset( &eventOff, 0, sizeof( alarm_event_t ) ); + + //set the common event fields for the alarm events + eventOn.recurrence = 60 * 24 * 7; //minutes in a week + eventOff.recurrence = 60 * 24 * 7; //minutes in a week + eventOn.recurrence_count = -1; + eventOff.recurrence_count = -1; + eventOn.flags = ( ALARM_EVENT_BOOT | ALARM_EVENT_NO_DIALOG ); + eventOff.flags = ALARM_EVENT_NO_DIALOG; + eventOff.exec_name = ( QString( BinPath + DevStopper ).toLatin1() ).data();; + + //find the first possible points of time to set the first alarms + // Adjust the time. NOTE: alarms cannot be set to past + QDateTime turnOnAtBasis = findFirstTime( aDays, aTurnOnAt ); + QDateTime turnOffAtBasis = findFirstTime( aDays, aTurnOffAt ); + + cookie_t cookie; + int index = 0; + + while ( index++ < aDays ) + { + + //send auto switch on alarms + eventOn.alarm_time = turnOnAtBasis.toTime_t(); + cookie = alarm_event_add( &eventOn ); + if ( !handleCookie( cookie, errortext ) ) + { + emit alarmSendingFailed( DeviceManager::NewAlarmsNotSent, errortext ); + removeAlarms(); + return false; + } + turnOnAtBasis = turnOnAtBasis.addDays( 1 ); //move on to next day + if ( aDays == 5 ) //only for working days + turnOnAtBasis = turnOnAtBasis.addDays( daysToNextWorkingDay( turnOnAtBasis ) ); + + //send auto switch off alarms + eventOff.alarm_time = turnOffAtBasis.toTime_t(); + cookie = alarm_event_add( &eventOff ); + if ( !handleCookie( cookie, errortext ) ) + { + emit alarmSendingFailed( DeviceManager::NewAlarmsNotSent, errortext ); + removeAlarms(); + return false; + } + turnOffAtBasis = turnOffAtBasis.addDays( 1 ); //move on to next day + if ( aDays == 5 ) //only for working days + turnOffAtBasis = turnOffAtBasis.addDays( daysToNextWorkingDay( turnOffAtBasis ) ); + } + if ( !iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::Alarms ), iSentAlarms ) ) + { + emit alarmSendingFailed( DeviceManager::NewAlarmsNotStored, errortext ); + removeAlarms(); + return false; + } + return true; +} + +bool AlarmSender::removeStoredAlarms() +{ + qDebug() << "AlarmSender::removeStoredAlarms()"; + QStringList storedAlarms; + QString errortext; + if ( !iDataStorage->readData( iDataStorage->dataSectionToString( DeviceDataStorage::Alarms ), storedAlarms ) ) + { + emit alarmSendingFailed( DeviceManager::OldAlarmsNotRemoved, errortext ); + return false; + } + + for ( int i = 0; i < storedAlarms.size(); ++i ) + alarm_event_del( storedAlarms.at( i ).toLong() ); + + // clearing the removed alarms from the storage by saving an empty alarm list + storedAlarms.clear(); + iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::Alarms ), storedAlarms ); + // catching an error in this case is not needed + + return true; +} + +void AlarmSender::removeAlarms() +{ + qDebug() << "AlarmSender::removeAlarms()"; + for ( int i = 0; i < iSentAlarms.size(); ++i ) + alarm_event_del( iSentAlarms.at( i ).toLong() ); + + // clearing the removed alarms from the storage by saving an empty alarm list + iSentAlarms.clear(); + iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::Alarms ), iSentAlarms ); + // catching an error in this case is not needed +} + +QDateTime AlarmSender::findFirstTime( const int &aDays, const QTime &aTime ) +{ + qDebug() << "AlarmSender::findFirstTime( const int &, const QTime & )"; + QDateTime currentTime = QDateTime::currentDateTime(); + QDateTime dateTimeBasis = QDateTime::currentDateTime(); + dateTimeBasis = dateTimeBasis.addSecs( dateTimeBasis.time().secsTo( aTime ) ); + + if ( currentTime > dateTimeBasis ) //have to add a day + { + dateTimeBasis = dateTimeBasis.addDays( 1 ); + } + if ( aDays == 5 ) //only for working days + { + dateTimeBasis = dateTimeBasis.addDays( daysToNextWorkingDay( dateTimeBasis ) ); + } + return dateTimeBasis; +} + +int AlarmSender::daysToNextWorkingDay( const QDateTime &aDateTime ) +{ + qDebug() << "AlarmSender::daysToNextWorkingDay( const QDateTime & )"; + QString day = aDateTime.toString( "ddd" ); + if ( day == "Sat" ) + return 2; + else if ( day == "Sun" ) + return 1; + else + return 0; +} + +bool AlarmSender::handleCookie( cookie_t aCookie, QString &aErrorText ) +{ + qDebug() << "AlarmSender::handleCookie( cookie_t, QStringList &, QString & )"; + if ( aCookie == 0 ) + { + aErrorText = mapError( alarmd_get_error() ); + return false; + } + else + { + QString cookieStr; + cookieStr.append( QString( "%1" ).arg( aCookie ) ); + iSentAlarms.append( cookieStr ); + } + return true; +} + +QString AlarmSender::mapError( alarm_error_t aErrorCode ) +{ + qDebug() << "AlarmSender::mapError( alarm_error_t )"; + QString errorMessage; + switch ( aErrorCode ) + { + case ALARMD_SUCCESS: + errorMessage = "No reason found."; //this should never happen + break; + case ALARMD_ERROR_DBUS: + errorMessage = "An error with D-Bus occurred, probably coudn't get a D-Bus connection."; + break; + case ALARMD_ERROR_CONNECTION: + errorMessage = "Could not contact alarmd via D-Bus."; + break; + case ALARMD_ERROR_INTERNAL: + errorMessage = "Some alarmd or libalarm internal error, possibly a version mismatch."; + break; + case ALARMD_ERROR_MEMORY: + errorMessage = "A memory allocation failed."; + break; + case ALARMD_ERROR_ARGUMENT: + errorMessage = "An argument given by caller was invalid."; + break; + default: /* == 6 */ + errorMessage = "Alarm daemon not running."; + } + return errorMessage; +} diff --git a/src/IO/DeviceControl/AlarmSender.h b/src/IO/DeviceControl/AlarmSender.h new file mode 100644 index 0000000..9920378 --- /dev/null +++ b/src/IO/DeviceControl/AlarmSender.h @@ -0,0 +1,107 @@ +#ifndef ALARMSENDER_H_ +#define ALARMSENDER_H_ + +extern "C" +{ +#include +} +#include "DeviceDataStorage.h" + +#include +#include + +//! DeviceControl class. Sends and removes the alarms of the alarm daemon. +/*! + * DeviceControl class. Sends and removes the alarms of the alarm daemon. The alarms make it possible + * to rurn on and off the device in the desired points of time. This optional functionality is supported + * in the "kiosk mode". + */ +class AlarmSender : public QObject +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize an AlarmSender instance + * \param aDataStorage Pointer to the DataStorage class instance. + */ + AlarmSender( DeviceDataStorage *aDataStorage ); + //! Destructor. + virtual ~AlarmSender(); + +signals: + //! Signal. Emitted if error occured during sending the alarms. + /*! + * Signal. Emitted if error occured during sending the alarms. + * \param aErrorCode The error code. + * \param aAddInfo The additional error text. + */ + void alarmSendingFailed( DeviceManager::ErrorCode aErrorCode, const QString &aAddInfo ); + +public slots: + //! Slot. Consists and sends the alarms to the alarm daemon. + /*! + * Slot. Consists and sends the alarms to the alarm daemon. Asks DeviceDataStorage to store the + * information about the sent alarms. + * \param aTurnOnAt The time the device is desired to be turned on. + * \param aTurnOffAt The time the device is desired to be turned off. + * \param aDays The days to indicate if the device is wanted to be turned on/off every day (7) or + * just weekdays (5). + */ + bool sendAlarms( QTime aTurnOnAt, QTime aTurnOffAt, int aDays = 5 ); + //! Slot. Removes the alarms from the alarm daemon. + /*! + * Slot. Removes the alarms from the alarm daemon. Asks DeviceDataStorage to remove information + * about the sent alarms. + */ + void removeAlarms(); + //! Slot. Removes the alarms from the alarm daemon. + /*! + * Slot. Removes the alarms from the alarm daemon. Asks DeviceDataStorage to fetch information about + * the stored alarms. After removing those alarms, asks DeviceDataStorage to remove information + * about the alarms. + */ + bool removeStoredAlarms(); + +private: + //! Finds the first possible point of time to set the first alarms. + /*! + * Finds the first possible point of time to set the first alarms. + * \param aDays The days to indicate if the device is wanted to be turned on/off every day (7) or + * just weekdays (5). + * \param aTime The time of the turning on/off alarm. + * \return The date of the first alarm. + */ + QDateTime findFirstTime( const int &aDays, const QTime &aTime ); + //! Counts days from the received date to the next working day. + /*! + * Returns the day count from the received date to the next working day. + * \param aDateTime The date from where to count the days + * \return The amount of days + */ + int daysToNextWorkingDay( const QDateTime &aDateTime ); + //! Handles the result of an sent alarm. + /*! + * Handles the result of an sent alarm. Updates the received aErrorText parameter in case the alarm + * sending fails. If not, it updates the internal list of succesfully sent alarms. + * \param aCookie The result of an sent alarm. + * \param aErrorText The result of an sent alarm. + * \return True if alarm sending succeeds; otherwise, false. + */ + bool handleCookie( cookie_t aCookie, QString &aErrorText ); + //! Maps the received aErrorCode parameter to a certain error message. + /*! + * Maps the received aErrorCode parameter to a certain error message. + * \param aErrorCode The error code for the alarm sending failure. + * \return The mapped error message. + */ + QString mapError( alarm_error_t aErrorCode ); + +private: + DeviceDataStorage *iDataStorage; + QStringList iSentAlarms; + +}; + +#endif /*ALARMSENDER_H_*/ diff --git a/src/IO/DeviceControl/DeviceConfigurator.cpp b/src/IO/DeviceControl/DeviceConfigurator.cpp new file mode 100644 index 0000000..4fa515e --- /dev/null +++ b/src/IO/DeviceControl/DeviceConfigurator.cpp @@ -0,0 +1,258 @@ +#include "DeviceConfigurator.h" +#include "DeviceDataStorage.h" +#include "DeviceConstants.h" + +#include +#include + +DeviceConfigurator::DeviceConfigurator( DeviceDataStorage *aDataStorage ) +{ + qDebug() << "DeviceConfigurator( DeviceDataStorage * )"; + iDataStorage = aDataStorage; +} + +DeviceConfigurator::~DeviceConfigurator() +{ + qDebug() << "~DeviceConfigurator()"; +} + +bool DeviceConfigurator::toggleScreenSwitchOff( bool aEnable ) +{ + qDebug() << "DeviceConfigurator::toggleScreenSwitchOff( bool )"; + QString command = "gconftool-2"; + QStringList args; + QByteArray result; + QStringList confs; + QStringList defParams; + confs << "/system/osso/dsm/display/display_blank_timeout" + << "/system/osso/dsm/display/display_dim_timeout" + << "/apps/osso/applet/osso-applet-display/turn_off_display" + << "/apps/osso/applet/osso-applet-display/brightness_period"; + defParams << "300" << "120" << "300" << "120"; + QStringList origValues; + + if ( !aEnable ) + { + //disabling the screen "auto-switch-off" and "dimming" + + //using gconftool-2 to get the current values for related configurations + for ( int i = 0; i < confs.size(); ++i ) + { + args.clear(); + args << "-g" << confs[i]; + if ( systemIO( command, args, result ) ) + { + if ( result.toLong() != 0 ) + origValues.append( result ); + } + } + if ( origValues.size() == confs.size() ) //values succesfully fetched, now trying to store them + { + if ( !iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::ScreenSettings ), origValues ) ) + emit configuringError( DeviceManager::ScreenSettingsNotStored ); + } + else //values not fetched, using the default values instead + { + emit configuringError( DeviceManager::ScreenSettingsNotFetched ); + origValues.clear(); + for ( int i = 0; i < defParams.size(); ++i ) + origValues.append( defParams.at( i ) ); + } + + //using gconftool-2 to change the related configurations + for ( int i = 0; i < confs.size(); ++i ) + { + args.clear(); + args << "-s" << confs[i] << "--type=int" << "6000000"; + if ( !systemIO( command, args, result ) ) { + emit configuringError( DeviceManager::ScreenSettingsNotChanged ); + return false; + } + } + } + else + { + //setting the screen's "auto-switch-off" and "dimming" settings back as they were + + //reading stored data from internal config file + if ( !iDataStorage->readData( iDataStorage->dataSectionToString( DeviceDataStorage::ScreenSettings ), origValues ) ) + { + //cannot read, using the default values instead + emit configuringError( DeviceManager::ScreenSettingsNotFetched ); + for ( int i = 0; i < defParams.size(); ++i ) + origValues.append( defParams.at( i ) ); + } + for ( int i = 0; i < origValues.size(); ++i ) + { + args.clear(); + args << "-s" << confs[i] << "--type=int" << origValues.at(i); + if ( !systemIO( command, args, result ) ) { + emit configuringError( DeviceManager::ScreenSettingsNotChanged ); + return false; + } + } + } + return true; +} + +bool DeviceConfigurator::toggleHWKeys( bool aEnable ) +{ + qDebug() << "DeviceConfigurator::toggleHWKeys( bool )"; + QStringList mceLines; + QStringList mceLinesNew; + QString mceSection = "HomeKey"; + QStringList params; + params << "HomeKeyShortAction" << "HomeKeyLongAction"; + + // using the DeviceDataStorage exceptionally for reading data from an external conf file + // /etc/mce/mce.ini + if ( !iDataStorage->readData( mceSection, mceLines, McePath ) ) + { + emit configuringError( DeviceManager::KeySettingsNotFetched ); + return false; + } + + if ( !aEnable ) + { + // disabling the "home"-hw-key + + QStringList mceLinesToStore; + for ( int i = 0; i < mceLines.size(); ++i ) + { + QStringList mceLine = mceLines.at( i ).split( '=' ); + QString param = mceLine.at( 0 ).trimmed(); + //check if this is the correct parameter to store and change + if ( params.contains( param ) ) + { + for ( int j = 0; j < params.size(); ++j ) + { + if ( params.at( j ) == param ) + { + mceLinesToStore.append( mceLines.at( i ) ); + mceLinesNew.append( param + "=disabled" ); + break; + } + } + } + else + mceLinesNew.append( mceLines.at( i ) ); + } + + // storing the mce conf file lines + if ( !iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::KeySettings ), mceLinesToStore ) ) + { + emit configuringError( DeviceManager::KeySettingsNotStored ); + return false; + } + } + else + { + // setting the "home"-hw-key settings back as they were + + // reading the stored mce conf file lines + QStringList storedMceLines; + if ( !iDataStorage->readData( iDataStorage->dataSectionToString( DeviceDataStorage::KeySettings ), storedMceLines ) ) + { + emit configuringError( DeviceManager::KeySettingsNotFetched ); + return false; + } + + bool paramFound = false; + for ( int i = 0; i < mceLines.size(); ++i ) + { + QStringList mceLine = mceLines.at( i ).split( '=' ); + for ( int j = 0; j < storedMceLines.size(); ++j ) + { + QStringList storedMceLine = storedMceLines.at( j ).split( '=' ); + if ( storedMceLine.at( 0 ).trimmed() == mceLine.at( 0 ).trimmed() ) + { + mceLinesNew.append( storedMceLines.at( j ) ); + paramFound = true; + } + } + if ( !paramFound ) + mceLinesNew.append( mceLines.at( i ) ); + else + paramFound = false; + } + } + // using the datastorage exceptionally but this time for changing data in the external conf file + if ( !iDataStorage->storeData( mceSection, mceLinesNew, McePath ) ) + { + emit configuringError( DeviceManager::KeySettingsNotChanged ); + return false; + } + + return true; +} + +bool DeviceConfigurator::toggleInitScript( bool aEnable ) +{ + QByteArray name; + if( !whoAmI( name ) ) { + emit configuringError( DeviceManager::InitScriptNotChanged ); + return false; + } + + QString command = InitScript; + QStringList empty; + QByteArray result; + + if( name != "root" ) + command.prepend( "sudo " ); + + if( aEnable ) + command.append( " install" ); + else + command.append( " remove" ); + + if ( !systemIO( command, empty, result ) ) { + emit configuringError( DeviceManager::InitScriptNotChanged ); + return false; + } + + return true; +} + +bool DeviceConfigurator::restartDevice() +{ + QString command = BinPath + DevStopper; + QStringList args; + QByteArray result; + args.append( "restart" ); + if( !systemIO( command, args, result ) ) { + emit configuringError( DeviceManager::DeviceNotRestarted ); + return false; + } + return true; +} + +bool DeviceConfigurator::whoAmI( QByteArray &aName ) +{ + QString command = "whoami"; + QStringList empty; + if( !systemIO( command, empty, aName ) ) + return false; + return true; +} + +bool DeviceConfigurator::systemIO( const QString &aCommand, const QStringList &aArgs, QByteArray &aResult ) +{ + qDebug() << "DeviceConfigurator::systemIO( QString &, QStringList &, QByteArray &)"; + qDebug() << "Command: " << aCommand; + QProcess process; + if( !aArgs.empty() ) + process.start( aCommand, aArgs ); + else + process.start( aCommand ); + + if( !process.waitForFinished() ) + return false; + aResult = process.readAll(); + if( aResult.endsWith( '\n' ) ) + aResult.chop( 1 ); + + qDebug() << "Result: " << aResult; + + return true; +} diff --git a/src/IO/DeviceControl/DeviceConfigurator.h b/src/IO/DeviceControl/DeviceConfigurator.h new file mode 100644 index 0000000..30775fa --- /dev/null +++ b/src/IO/DeviceControl/DeviceConfigurator.h @@ -0,0 +1,89 @@ +#ifndef DEVICECONFIGURATOR_H_ +#define DEVICECONFIGURATOR_H_ + +#include "DeviceDataStorage.h" + +//! DeviceControl class. Configures the device behavior. +/*! + * DeviceControl class. DeviceControl class. Configures the device behavior. Updates the device system + * files, activates/deactivates the application initialization script, uses the gconftool to configure + * some parameters of the device and uses the devstopper binary to restart the device to activate + * the changes. The changes it makes affect to the hardware keys, the screen dimming enabling/disabling + * and the automated turning on/off the device. These are all supported features of the "kiosk mode". + */ +class DeviceConfigurator : public QObject +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize an AlarmSender instance + * \param aDataStorage Pointer to the DataStorage class instance. + */ + DeviceConfigurator( DeviceDataStorage *aDataStorage ); + //! Destructor. + virtual ~DeviceConfigurator(); + //! Toggles the screen switching on/off and dimming options of the device. + /*! + * Toggles the screen switching on/off and dimming options of the device. Asks DeviceDataStorage to + * read/store the screen related original parameters. Uses systemIO( ... ) method to execute + * gconftool system commands to change the configurations. + * \param aEnable Indicates if the screen switchin off and dimming are wanted to be enabled. + * \return True if configuring of screen options succeeds; otherwise, false. + */ + bool toggleScreenSwitchOff( bool aEnable ); + //! Toggles the enabling of the "home"-hardware key of the device. + /*! + * Toggles the enabling of the "home"-hardware key of the device. Asks DeviceDataStorage to + * read/store the hardware key related original parameters. Uses the DeviceDataStorage to manipulate + * the device system file /etc/mce/mce.ini to enable/disable the hw key. + * \param aEnable Indicates if the "home"-hardware key is wanted to be enabled. + * \return True if configuring of the hw-key succeeds; otherwise, false. + */ + bool toggleHWKeys( bool aEnable ); + //! Toggles the activation of the application initialization script. + /*! + * Toggles the activation of the application initialization script. Uses the systemIO( ... ) method + * to execute the script that uses update-rc.d system command to install/remove the init script + * qtmeetings-launcher. + * \param aEnable Indicates if the script is wanted to be activated/deactivated + * \return True if activating/deactivating of the script succeeds; otherwise, false. + */ + bool toggleInitScript( bool aEnable ); + //! Restarts the device. + /*! + * Restarts the device by using the systemIO( ... ) method to execute devstopper executable. + * \return True if resrating of the device succeeds; otherwise, false. + */ + bool restartDevice(); + //! Resolves the currently used account of the device. + /*! + * Resolves the currently used account of the device by using the systemIO( ... ) method to execute + * whoami system command. + * \return True if resolving the account succeeds; otherwise, false. + */ + static bool whoAmI( QByteArray &aName ); + //! Consists and executes a system command and stores the received output. + /*! + * Consists and executes a system command and stores the received output. + * \param aCommand The whole system command or just the "binary"-part of the command + * \param aArgs The possible arguments for the "binary" + * \param aResult The place to put the output of the command + * \return True if executing the system command succeeds; otherwise, false. + */ + static bool systemIO( const QString &aCommand, const QStringList &aArgs, QByteArray &aResult ); + +signals: + //! Signal. Emitted if error occured during configuring the device. + /*! + * Signal. Emitted if error occured during configuring the device. + * \param aErrorCode The error code. + */ + void configuringError( DeviceManager::ErrorCode aErrorCode ); + +private: + DeviceDataStorage *iDataStorage; +}; + +#endif /*DEVICECONFIGURATOR_H_*/ diff --git a/src/IO/DeviceControl/DeviceConstants.h b/src/IO/DeviceControl/DeviceConstants.h new file mode 100644 index 0000000..9f82a43 --- /dev/null +++ b/src/IO/DeviceControl/DeviceConstants.h @@ -0,0 +1,11 @@ +#ifndef DEVICECONSTANTS_H_ +#define DEVICECONSTANTS_H_ + +static const QString BinPath = "/usr/bin/"; +static const QString DevStopper = "qtmeetings-devstopper"; +static const QString RenameScript = "qtmeetings-rename"; +static const QString InitScript = "qtmeetings-updatercd"; +static const QString DataStoragePath = "/usr/var/qtmeetings.txt"; +static const QString McePath = "/etc/mce/mce.ini"; + +#endif /*DEVICECONSTANTS_H_*/ diff --git a/src/IO/DeviceControl/DeviceDataStorage.cpp b/src/IO/DeviceControl/DeviceDataStorage.cpp new file mode 100644 index 0000000..45250fb --- /dev/null +++ b/src/IO/DeviceControl/DeviceDataStorage.cpp @@ -0,0 +1,210 @@ +#include "DeviceDataStorage.h" +#include "DeviceConfigurator.h" + +#include +#include +#include +#include +#include + +DeviceDataStorage::DeviceDataStorage() +{ + qDebug() << "DeviceDataStorage::DeviceDataStorage()"; +} + +bool DeviceDataStorage::initDataStorage() +{ + qDebug() << "DeviceDataStorage::initDataStorage()"; + //create the conf file if not yet created + QFile file( DataStoragePath ); + + if ( !file.exists() ) { + + // to avoid possible permission errors, create temp data file and rename it after finished + QString DataStoragePathTmp = "/tmp/" + ( DataStoragePath.split( '/' ) ).back() + ".tmp"; + QFile fileTmp( DataStoragePathTmp ); + + if ( !fileTmp.open( QIODevice::WriteOnly | QIODevice::Text ) + || !fileTmp.setPermissions( QFile::ReadUser | QFile::WriteUser | QFile::ReadGroup | QFile::WriteGroup | QFile::ReadOwner | QFile::WriteOwner | QFile::ReadOther | QFile::WriteOther ) ) { + emit dataStorageInitFailed( DeviceManager::FileCreationFailed ); + return false; + } + else { + fileTmp.close(); + file.close(); + if ( !renameFile( DataStoragePathTmp, DataStoragePath ) ) + return false; + } + } + return true; +} + +DeviceDataStorage::~DeviceDataStorage() +{ + qDebug() << "DeviceDataStorage::~DeviceDataStorage()"; +} + +bool DeviceDataStorage::readData( const QString aSection, QStringList &aData, const QString &aFilename ) +{ + qDebug() << "DeviceDataStorage::readData( QString, QStringList &, const QString &)"; + QHash allData; + QStringList sectionOrder; //not needed in this case + + if ( !readDataFromFile( allData, sectionOrder, aFilename ) ) + return false; + + QHash::iterator i; + for ( i = allData.begin(); i != allData.end(); ++i ) + { + if ( i.key() == aSection ) + { + aData = i.value(); + break; + } + } + return true; +} + +bool DeviceDataStorage::storeData( QString aSection, const QStringList &aData, const QString &aFilename ) +{ + qDebug() << "DeviceDataStorage::storeData( QString, const QStringList &, const QString & )"; + QHash allData; + QHash allDataNew; + QStringList sectionOrder; + bool sectionFound = false; + + if ( !readDataFromFile( allData, sectionOrder, aFilename ) ) + return false; + + // replace data under the certain section + QHash::iterator i; + for ( i = allData.begin(); i != allData.end(); ++i ) { + if ( i.key() == aSection ) { + allDataNew.insert( i.key(), aData ); + sectionFound = true; + } + else + allDataNew.insert( i.key(), i.value() ); + } + + // data with this section is not yet in the file + if ( !sectionFound ) { + allDataNew.insert( aSection, aData ); + sectionOrder.append( aSection ); + } + + // to avoid possible permission errors, write the data to new file + QString filenameTmp = "/tmp/" + ( aFilename.split( '/' ) ).back() + ".tmp"; + + if ( !writeDataToFile( allDataNew, sectionOrder, filenameTmp ) ) + return false; + if ( !renameFile( filenameTmp, aFilename ) ) + return false; + + return true; +} + +bool DeviceDataStorage::renameFile( const QString &aFilenameTmp, const QString &aFilename ) +{ + qDebug() << "DeviceDataStorage::renameFile( const QString &, const QString & )"; + + QByteArray result; + if( !DeviceConfigurator::whoAmI( result ) ) + return false; + + QString command; + QStringList empty; + if( result != "root" ) + command = "sudo "; + + command.append( BinPath + RenameScript + " " + aFilenameTmp + " " + aFilename ); + + if( !DeviceConfigurator::systemIO( command, empty, result ) ) + return false; + + return true; +} + +bool DeviceDataStorage::readDataFromFile( QHash &aDataHash, QStringList &aSectionOrder, const QString &aFilename ) +{ + qDebug() << "DeviceDataStorage::readDataFromFile( QHash &, const QString & )"; + QFile file( aFilename ); + + if ( !file.open( QIODevice::ReadOnly | QIODevice::Text ) ) + return false; + + QTextStream in( &file ); + QString dataSection = ""; + QStringList data; + bool firstSection = true; + + while ( !in.atEnd() ) + { + QString line = in.readLine(); + if ( line.at( 0 ) == QChar( '[' ) && line.at( line.length() - 1 ) == QChar( ']' ) ) + { + if ( !firstSection ) + aDataHash.insert( dataSection, data ); + line.replace( QString( "[" ), QString( "" ) ); + line.replace( QString( "]" ), QString( "" ) ); + dataSection = line; + data.clear(); + firstSection = false; + aSectionOrder.append( dataSection ); + } + else + data.append( line ); + } + if ( dataSection != "" ) //the file wasn't empty, saving the last section + aDataHash.insert( dataSection, data ); + file.close(); + + return true; +} + +bool DeviceDataStorage::writeDataToFile( const QHash &aDataHash, const QStringList &aSectionOrder, const QString &aFilename ) +{ + qDebug() << "DeviceDataStorage::writeDataToFile( QHash &, const QString & )"; + + QFile file( aFilename ); + + if ( !file.open( QIODevice::WriteOnly | QIODevice::Text ) ) + return false; + + QTextStream out( &file ); + + QHash::const_iterator i; + for ( int j = 0; j < aSectionOrder.size(); ++j ) + { + for ( i = aDataHash.begin(); i != aDataHash.end(); ++i ) + { + if ( aSectionOrder.at( j ) == i.key() ) + { + out << "[" << i.key() << "]\n"; + QStringList list = i.value(); + for ( int j = 0; j < list.size(); ++j ) + out << list.at( j ) << "\n"; + break; + } + } + } + file.close(); + + return true; +} + +QString DeviceDataStorage::dataSectionToString( DataSection aDataSection ) +{ + qDebug() << "DeviceDataStorage::dataSectionToString( DataSection )"; + switch ( aDataSection ) + { + case Alarms: + return "Alarms"; + case ScreenSettings: + return "ScreenSettings"; + case KeySettings: + return "KeySettings"; + default: //DeviceMode + return "DeviceMode"; + } +} diff --git a/src/IO/DeviceControl/DeviceDataStorage.h b/src/IO/DeviceControl/DeviceDataStorage.h new file mode 100644 index 0000000..a8ca1ad --- /dev/null +++ b/src/IO/DeviceControl/DeviceDataStorage.h @@ -0,0 +1,123 @@ +#ifndef DEVICEDATASTORAGE_H_ +#define DEVICEDATASTORAGE_H_ + +#include "DeviceManager.h" +#include "DeviceConstants.h" + +#include + +#include +#include + +//! DeviceControl class. Reads and stores data form/to the internal data storage. +/*! + * DeviceControl class. Reads and stores data form/to the internal data storage. Datastorage is an + * ini file which contains information about sent alarms, device screen settings, device hardware key + * settings and current application operation mode. The class can be used to manipulate any other ini + * typed files as well. + */ +class DeviceDataStorage : public QObject +{ + Q_OBJECT + +public: + //! Enumeration of data sections + /*! + * Enumeration of data sections + */ + enum DataSection + { + Alarms, /*!< Alarms section. */ + ScreenSettings, /*!< Screen option section. */ + KeySettings, /*!< HW key option section. */ + DeviceMode /*!< Device mode section. */ + }; + +public: + //! Constructor. + /*! + * Constructor to initialize a DeviceDataStorage instance + */ + DeviceDataStorage(); + //! Destructor. + virtual ~DeviceDataStorage(); + //! Creates the internal data storage file. + /*! + * Creates the internal data storage file. + * \return True if file creation succeeds; otherwise false. + */ + bool initDataStorage(); + //! Stores data to an ini file. + /*! + * Stores data to an ini file. Calls readDataFromFile( ... ) method to receive the current content of + * the file and replaces the data under the certain section or creates a completely new section with + * the data. After that it calls the writeDataToFile( ... ) to store the modified content. + * \param aSection The data section under which the data needs to be stored. + * \param aData The data to be stored. + * \param aFilename The file to modify. + * \return True if file modifying succeeds; otherwise false. + */ + bool storeData( QString aSection, const QStringList &aData, const QString &aFilename = DataStoragePath ); + //! Reads data from an ini file. + /*! + * Reads data from an ini file. Calls readDataFromFile( ... ) method to receive the current content + * of the file and finds the data under the certain section and stores it to the aData parameter. + * \param aSection The data section under which the data needs to be red. + * \param aData The parameter to place the found data. + * \param aFilename The file to read. + * \return True if file reading succeeds; otherwise false. + */ + bool readData( QString aSection, QStringList &aData, const QString &aFilename = DataStoragePath ); + //! Transform the data section enum value to QString. + /*! + * Transform the data section enum value to QString. Is used only when handling the internal data + * storage file. + * \param aSection The data section enum value to be transformed. + * \return The QString value that corresponds the enum value. + */ + QString dataSectionToString( DataSection aDataSection ); + +signals: + //! Signal. Emitted if error occured during internal data storage file creation. + /*! + * Signal. Emitted if error occured during internal data storage file creation. + * \param aErrorCode The error code. + */ + void dataStorageInitFailed( DeviceManager::ErrorCode aErrorCode ); + +private: + //! Replaces the original ini file with the temporary ini file that conatins the required changes. + /*! + * Replaces the original ini file with the temporary ini file that conatins the required changes. + * Uses the static DeviceConfigurator method systemIO( ... ) to execute the rename script + * qtmeetings-rename. + * \param aFilenameTmp The temporary file. + * \param aFilename The actual file. + * \return True if file renaming succeeds; otherwise false. + */ + bool renameFile( const QString &aFilenameTmp, const QString &aFilename ); + //! Reads data from an ini file. + /*! + * Reads data from an ini file. Does the actual job to read the data from the ini file. + * \param aDataHash Place to put the content of the file. Key of the hash is for the section and + * value is for the actual data. + * \param aSectionOrder Place to keep track of the sections and their orders in the file. QHash doesn't + * keep track of the order of the keys. + * \param aFilename The file to read. + * \return True if file reading succeeds; otherwise false. + */ + bool readDataFromFile( QHash &aDataHash, QStringList &aSectionOrder, const QString &aFilename ); + //! Stores data to an ini file. + /*! + * Stores data to an ini file. Does the actual job to store the data to the ini file. + * \param aDataHash To read the data to be stored. Key of the hash is for the section and value is for + * the actual data. + * \param aSectionOrder Contains the correct oredr of the sections to be stored. + * \param aFilename The file to store the data. + * \return True if file writing succeeds; otherwise false. + */ + bool writeDataToFile( const QHash &aDataHash, const QStringList &aSectionOrder, const QString &aFilename ); + +}; + +#endif /*DEVICEDATASTORAGE_H_*/ diff --git a/src/IO/DeviceControl/DeviceManager.cpp b/src/IO/DeviceControl/DeviceManager.cpp new file mode 100644 index 0000000..ed5a66a --- /dev/null +++ b/src/IO/DeviceControl/DeviceManager.cpp @@ -0,0 +1,241 @@ +#include "DeviceManager.h" +#include "AlarmSender.h" +#include "HWKeyListener.h" +#include "StartupSettings.h" +#include "DeviceDataStorage.h" +#include "DeviceConfigurator.h" + +#include +#include + +DeviceManager::DeviceManager( StartupSettings *aSettings ) +{ + qDebug() << "DeviceManager::DeviceManager( StartupSettings * )"; + + iSettings = aSettings; + iSendErrorMessages = true; +} + +DeviceManager::~DeviceManager() +{ + qDebug() << "DeviceManager::~DeviceManager()"; +} + +void DeviceManager::initDeviceManager() +{ + qDebug() << "DeviceManager::init()"; + iDataStorage = new DeviceDataStorage(); + connect( iDataStorage, SIGNAL( dataStorageInitFailed( DeviceManager::ErrorCode ) ), + this, SLOT( errorSender( DeviceManager::ErrorCode ) ) ); + + iAlarmSender = new AlarmSender( iDataStorage ); + connect( iAlarmSender, SIGNAL( alarmSendingFailed( DeviceManager::ErrorCode, const QString& ) ), + this, SLOT( errorSender( DeviceManager::ErrorCode, const QString& ) ) ); + + iConfigurator = new DeviceConfigurator( iDataStorage ); + connect( iConfigurator, SIGNAL( configuringError( DeviceManager::ErrorCode ) ), + this, SLOT( errorSender( DeviceManager::ErrorCode ) ) ); + + if ( !iDataStorage->initDataStorage() || !setCurrentOperationMode() ) + iMode = EmptyMode; + + iHWKeyListener = new HWKeyListener(); + handleKeyPresses( true ); +} + +DeviceManager::OperationMode DeviceManager::currentOperationMode() +{ + return iMode; +} + +QString DeviceManager::operationModeToString( OperationMode aMode ) +{ + switch ( aMode ) + { + case KioskMode: + return "Kiosk-mode"; + case StandAloneMode: + return "Stand Alone-mode"; + default: + return ""; + } +} + +void DeviceManager::changeMode( bool aChange ) +{ + qDebug() << "void DeviceManager::changeMode()"; + if( !aChange ) { + handleKeyPresses( true ); + return; + } + + switch ( iMode ) + { + case EmptyMode: + // error occured. Mode cannot be changed + errorSender( ModeNotFetched ); + handleKeyPresses( true ); + break; + case StandAloneMode: + + // change to KioskMode + + // check if auto turn on/off functionality enabled and send turn on/off alarm events to alarm daemon + if ( iSettings->isPowersavingEnabled() ) + { + if ( !iAlarmSender->sendAlarms( iSettings->turnOnAt(), iSettings->turnOffAt() ) ) + { + handleKeyPresses( true ); + return; //this is critical so returning if no success + } + } + + // - disable the certain hw keys (only "home"-hw-key at the moment) + // - register init script to launch the application when ever the device is launched + // - disable the screen "auto-switch-off" and "dimming" + // - store info about the new operation mode + if ( !iConfigurator->toggleHWKeys( false ) || + !iConfigurator->toggleInitScript( true ) || + !iConfigurator->toggleScreenSwitchOff( false ) || + !this->storeOperationMode( KioskMode ) || + !iConfigurator->restartDevice() ) + { + + // we have to roll back if something fails + // of course rolling back may fail as well but it is impossible to catch + iSendErrorMessages = false; + iAlarmSender->removeAlarms(); + iConfigurator->toggleHWKeys( true ); + iConfigurator->toggleInitScript( false ); + iConfigurator->toggleScreenSwitchOff( true ); + iSendErrorMessages = true; + handleKeyPresses( true ); + return; + } + break; + + case KioskMode: + // change to StandAloneInProgress mode + + // - enable the certain hw keys (only "home"-hw-key at the moment) + // - unregister the init script + // - enable the screen "auto-switch-off" and "dimming" + // - store info about the new operation mode + if ( !iConfigurator->toggleHWKeys( true ) || + !iConfigurator->toggleScreenSwitchOff( true ) || + !this->storeOperationMode( StandAloneModeInProgress ) || + !iAlarmSender->removeStoredAlarms() || + !iConfigurator->restartDevice() ) + { + // we have to roll back if something fails + // of course rolling back may fail as well but it is impossible to catch + iSendErrorMessages = false; + iConfigurator->toggleHWKeys( false ); + iConfigurator->toggleInitScript( true ); + iConfigurator->toggleScreenSwitchOff( false ); + this->storeOperationMode( KioskMode ); + iSendErrorMessages = true; + handleKeyPresses( true ); + return; + } + + break; + default: // StandAloneModeInProgress should never come in question + break; + } +} + +bool DeviceManager::setCurrentOperationMode() +{ + qDebug() << "DeviceManager::currentOperationMode()"; + QStringList modeResult; + if ( !iDataStorage->readData( iDataStorage->dataSectionToString( DeviceDataStorage::DeviceMode ), modeResult ) ) + { + errorSender( ModeNotFetched ); + return false; + } + else + { + if ( !modeResult.empty() ) + { + iMode = ( OperationMode )modeResult.at( 0 ).toInt(); + if ( iMode == StandAloneModeInProgress ) + { + if ( !finalizeStandAloneMode() ) + iMode = EmptyMode; + else + iMode = StandAloneMode; + } + } + else //this must be the first time, so no mode info saved yet + iMode = StandAloneMode; + } + return true; +} + +bool DeviceManager::storeOperationMode( OperationMode aMode ) +{ + qDebug() << "DeviceManager::storeOperationMode( const OperationMode & )"; + QStringList modeStrList; + QString str; + modeStrList.append( str.append( QString( "%1" ).arg( aMode ) ) ); + if ( !iDataStorage->storeData( iDataStorage->dataSectionToString( DeviceDataStorage::DeviceMode ), modeStrList ) ) + { + errorSender( ModeNotStored ); + return false; + } + return true; +} + +bool DeviceManager::finalizeStandAloneMode() +{ + qDebug() << "DeviceManager::finalizeStandAloneMode()"; + if ( !storeOperationMode( StandAloneMode ) || !iConfigurator->toggleInitScript( false ) ) + return false; + return true; +} + +void DeviceManager::handleKeyPresses( bool aHandle ) +{ + qDebug() << "DeviceManager::handleKeyPresses( bool )"; + if ( !aHandle ) + disconnect( iHWKeyListener, SIGNAL( HWKeyFullScreenPressed() ), this, SLOT( HWKeyFullScreenPressed() ) ); + else + connect( iHWKeyListener, SIGNAL( HWKeyFullScreenPressed() ), this, SLOT( HWKeyFullScreenPressed() ) ); +} + +void DeviceManager::HWKeyFullScreenPressed() +{ + qDebug() << "DeviceManager::HWKeyFullScreenPressed()"; + + // no more key presses before this one is handled + handleKeyPresses( false ); + + OperationMode nextMode; + switch ( iMode ) + { + case KioskMode: + nextMode = StandAloneMode; + break; + case StandAloneMode: + nextMode = KioskMode; + break; + default: + nextMode = EmptyMode; + break; + } + if ( nextMode != EmptyMode ) + emit changeModeOrdered( nextMode ); + else + handleKeyPresses( true ); +} + +void DeviceManager::errorSender( DeviceManager::ErrorCode aErrorCode, const QString &aAddInfo ) +{ + qDebug() << "DeviceManager::errorSender( ErrorCode, QString & )"; + qDebug() << "DeviceManager::errorSender: aErrorCode == " << aErrorCode << " " << aAddInfo; + if ( !iSendErrorMessages ) + return; + + emit error( ERROR_BASE + ( int )aErrorCode, aAddInfo ); +} diff --git a/src/IO/DeviceControl/DeviceManager.h b/src/IO/DeviceControl/DeviceManager.h new file mode 100644 index 0000000..f3f89dc --- /dev/null +++ b/src/IO/DeviceControl/DeviceManager.h @@ -0,0 +1,171 @@ +#ifndef DEVICEMANAGER_H_ +#define DEVICEMANAGER_H_ + +#include +#include + +class AlarmSender; +class HWKeyListener; +class StartupSettings; +class DeviceDataStorage; +class DeviceConfigurator; + +static const int ERROR_BASE=200; + +//! DeviceControl class. The main class of the DeviceControl. +/*! + * DeviceControl class. The main class of the DeviceControl. Responsible to communicate between the + * BusinessLogic and the device. Takes care of the operation mode changes of the application. + */ +class DeviceManager : public QObject +{ + Q_OBJECT + +public: + //! Enumeration of device modes + /*! + * Enumeration of device modes + */ + enum OperationMode + { + KioskMode, /*!< Device is in kiosk mode. */ + StandAloneModeInProgress, /*!< Device is in stand alone mode. */ + StandAloneMode, /*!< Device is in stand alone mode. */ + EmptyMode /*!< Application cannot read the mode. */ + }; + + //! Enumeration of errors + /*! + * Enumeration of errors + */ + enum ErrorCode + { + FileCreationFailed, /*!< File couldn't be created. */ + OldAlarmsNotRemoved, /*!< Previously sent old alarm events cannot be removed. */ + NewAlarmsNotSent, /*!< New alarms cannot be sent. */ + NewAlarmsNotStored, /*!< Information about new sent alarms cannot be stored. */ + ScreenSettingsNotStored, /*!< Configuration parameters of screen options cannot be stored. */ + ScreenSettingsNotFetched, /*!< Configuration parameters of screen options cannot be fetched. */ + ScreenSettingsNotChanged, /*!< Configuration parameters of screen options cannot be changed. */ + KeySettingsNotFetched, /*!< Configuration parameters of hw key options cannot be fetched. */ + KeySettingsNotStored, /*!< Configuration parameters of hw key options cannot be stored. */ + KeySettingsNotChanged, /*!< Configuration parameters of hw key options cannot be changed. */ + InitScriptNotChanged, /*!< Init script to auto launch the application was not registered/unregistered */ + ModeNotFetched, /*!< Information about the current operation mode cannot be fetched */ + ModeNotStored, /*!< Information about the new opration mode cannot be stored */ + DeviceNotRestarted /*!< Device cannot be restarted */ + }; + +public: + //! Constructor. + /*! + * Constructor to initialize a DeviceManager instance. + * \param aSettings Pointer to the start up configuration settings. + */ + DeviceManager( StartupSettings *aSettings ); + //! Destructor. + virtual ~DeviceManager(); + //! Creates instances of AlarmSender, DeviceConfigurator, DeviceDataStorage and HWKeyListener classes. + /*! + * Creates instances of AlarmSender, DeviceConfigurator, DeviceDataStorage and HWKeyListener classes. + * Connects their signals to the correct errorSender( ... ) slot. + */ + void initDeviceManager(); + //! Gets the current operation mode. + /*! + * Gets the current operation mode of the application. + * \return enum value of the current operation mode. + */ + DeviceManager::OperationMode currentOperationMode(); + //! Gets the string value of an operation mode. + /*! + * Gets the string value of an operation mode. + * \param aMode The enum value of the operation mode. + * \return QString value of the current operation mode. + */ + QString operationModeToString( OperationMode aMode ); + +signals: + //! Signal. Emitted if user tries to change the operation mode. + /*! + * Signal. Emitted if user tries to change the operation mode. + * \param aMode The operation mode that user wants to activate. + */ + void changeModeOrdered( DeviceManager::OperationMode aMode ); + //! Signal. Emitted if an error happens. + /*! + * Signal. Emitted if an error happens. + * \param aCode An error code defined by DeviceManager. + * \param aAddInfo Possible additional information. + */ + void error( int aCode, const QString &aAddInfo ); + +public slots: + //! Slot. Changes the operation mode. + /*! + * Slot. Changes the operation mode. + * \param aChange To indicate if the mode should be changed or not + */ + void changeMode( bool aChange ); + +private slots: + //! Slot. Handles "full screen"-hardware key presses. + /*! + * Slot. Handles "full screen"-hardware key presses. Checks the current operation mode and concludes + * the next (desired) operation mode to be set. Emits a changeModeOrdered( DeviceManager::OperationMode ) + * signal. + */ + void HWKeyFullScreenPressed(); + //! Slot. Sends errors. + /*! + * Slot. Sends errors. + * \param aErrorCode The error code. + * \param aAddInfo The possible additional error text. + */ + void errorSender( DeviceManager::ErrorCode aErrorCode, const QString &aAddInfo = "" ); + +private: + //! Updates the internal indicator of the current operation mode. + /*! + * Updates the internal indicator of the current operation mode by asking the DeviceDataStorage to + * read it from the internal data storage. Calls finalizeStandAloneMode() method in case the mode is + * StandAloneModeInProgress. + * \return True if operation mode fetching succeeds; otherwise, false. + */ + bool setCurrentOperationMode(); + //! Stores the current operation mode. + /*! + * Stores the current operation mode by asking the DeviceDataStorage to write it to the internal + * data storage. + * \param aMode The operation mode that user wants to activate. + * \return True if operation mode storing succeeds; otherwise, false. + */ + bool storeOperationMode( OperationMode aMode ); + //! Asks DeviceConfigurator to remove the deactivate script of the application. + /*! + * Asks DeviceConfigurator to remove the deactivate script of the application. Also asks + * DeviceDataStorage to store the current operation mode (StandAloneMode) . + * \return True if operation mode storing and deactivation of the init script succeed; otherwise, false. + */ + bool finalizeStandAloneMode(); + //! Connects/disconnects the HWKeyListener signals to the private HWKeyFullScreenPressed() slot. + /*! + * Connects/disconnects the HWKeyListener signals to the private HWKeyFullScreenPressed() slot. In case + * a signal is caught the connection is disabled until the signal handling is finished. + * \param aHandle indicates if the signals should be connected or not. + */ + void handleKeyPresses( bool aHandle ); + +private: + AlarmSender *iAlarmSender; + HWKeyListener *iHWKeyListener; + StartupSettings *iSettings; + DeviceDataStorage *iDataStorage; + DeviceConfigurator *iConfigurator; + + OperationMode iMode; + bool iSendErrorMessages; + +}; + +#endif /*DEVICEMANAGER_H_*/ diff --git a/src/IO/DeviceControl/HWKeyListener.cpp b/src/IO/DeviceControl/HWKeyListener.cpp new file mode 100644 index 0000000..2029f79 --- /dev/null +++ b/src/IO/DeviceControl/HWKeyListener.cpp @@ -0,0 +1,39 @@ +#include "HWKeyListener.h" + +#include +#include +#include + +HWKeyListener::HWKeyListener() : QObject() +{ + iApplication = QCoreApplication::instance( ); + iApplication->installEventFilter( this ); +} + +HWKeyListener::~HWKeyListener() +{ + +} + +bool HWKeyListener::eventFilter( QObject*, QEvent* e ) +{ + if ( e->type() == QEvent::KeyPress ) + { + QKeyEvent *keyEvent = static_cast( e ); + switch ( keyEvent->key() ) + { + case Qt::Key_F6: + qDebug() << "HW key full screen pressed"; + emit HWKeyFullScreenPressed(); + break; + case Qt::Key_F7: + qDebug() << "HW key zoom out pressed"; + emit HWKeyZoomOutPressed(); + break; + case Qt::Key_F8: + qDebug() << "HW key zoom in pressed"; + emit HWKeyZoomInPressed(); + } + } + return false; +} diff --git a/src/IO/DeviceControl/HWKeyListener.h b/src/IO/DeviceControl/HWKeyListener.h new file mode 100644 index 0000000..15e2966 --- /dev/null +++ b/src/IO/DeviceControl/HWKeyListener.h @@ -0,0 +1,53 @@ +#ifndef HWKEYLISTENER_H_ +#define HWKEYLISTENER_H_ + +#include + +class QCoreApplication; + +//! DeviceControl class. Monitors hardware key presses. +/*! + * DeviceControl class. Listens the hardware key presses and emits a related signal for the essential ones. + */ +class HWKeyListener : public QObject +{ + Q_OBJECT +public: + //! Constructor. + /*! + * Constructor for HWKeyListener class + */ + HWKeyListener(); + //! Destructor. + virtual ~HWKeyListener(); + +signals: + //! Signals if user presses F6 (Full screen) hardware key + /*! + * The signal is emitted if user presses F6 (Full Screen) hardware key + */ + void HWKeyFullScreenPressed( ); + //! Signals if user presses F7 (Zoom out) hardware key + /*! + * The signal is emitted if user presses F7 (Zoom out) hardware key + */ + void HWKeyZoomOutPressed( ); + //! Signals if user presses F8 (Zoom in) hardware key + /*! + * The signal is emitted if user presses F8 (Zoom in) hardware key + */ + void HWKeyZoomInPressed( ); + +protected: + //! Monitors user's interaction and controls functionality based on user's actions. + /*! + * Monitors user's interaction and controls functionality based on user's actions. + */ + virtual bool eventFilter( QObject *aWatched_object, QEvent *e ); + +private: + QCoreApplication *iApplication; + +}; + +#endif /*HWKEYLISTENER_H_*/ diff --git a/src/UserInterface/Components/DigitalTimeDisplayWidget.cpp b/src/UserInterface/Components/DigitalTimeDisplayWidget.cpp new file mode 100644 index 0000000..3b06ed5 --- /dev/null +++ b/src/UserInterface/Components/DigitalTimeDisplayWidget.cpp @@ -0,0 +1,55 @@ +#include "DigitalTimeDisplayWidget.h" +#include +#include + +//const QString timeFormat( "hh:mm" ); //time display format + +DigitalTimeDisplayWidget::DigitalTimeDisplayWidget( QTime aNow, QString aFormat, QWidget *aParent ) : + TimeDisplayWidget( aNow, aParent ), iTimeFormat( aFormat ) +{ + iDisplay = new QLCDNumber( this ); + iDisplay->setSegmentStyle( QLCDNumber::Flat ); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget( iDisplay ); + layout->setMargin( 0 ); + setLayout( layout ); + + showTime(); +} + +DigitalTimeDisplayWidget::~DigitalTimeDisplayWidget() +{ + delete iDisplay; + iDisplay = 0; +} + +void DigitalTimeDisplayWidget::setFrameVisible( bool aVisible ) +{ + if ( aVisible ) + { + iDisplay->setFrameShape( QFrame::Box ); + } + else + { + iDisplay->setFrameShape( QFrame::NoFrame ); + } +} + +void DigitalTimeDisplayWidget::setSize( int aWidth, int aHeight ) +{ + if ( aWidth > 0 && aHeight > 0 ) + { + setFixedSize( aWidth, aHeight ); + } +} + +void DigitalTimeDisplayWidget::showTime() +{ + iDisplay->display( time().toString( iTimeFormat ) ); +} + +void DigitalTimeDisplayWidget::setFont( const QFont &aFont ) +{ + iDisplay->setFont( aFont ); +} diff --git a/src/UserInterface/Components/DigitalTimeDisplayWidget.h b/src/UserInterface/Components/DigitalTimeDisplayWidget.h new file mode 100644 index 0000000..4273f8b --- /dev/null +++ b/src/UserInterface/Components/DigitalTimeDisplayWidget.h @@ -0,0 +1,64 @@ +#ifndef DIGITALTIMEDISPLAYWIDGET_H_ +#define DIGITALTIMEDISPLAYWIDGET_H_ + +#include "TimeDisplayWidget.h" + +class QLCDNumber; + +//! Userinterface class. Displays time in a digital display. +/*! + * Userinterface class. Displays time in a digital display. Inherits TimeDisplayWidget and diplays time in + * QLCDNumber display. + */ +class DigitalTimeDisplayWidget : public TimeDisplayWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize a DigitalTimeDisplayWidget instance. + * \param aNow Current time. + * \param aFormat Time display format. + * \param aParent Parent widget. Optional. + */ + DigitalTimeDisplayWidget( QTime aNow, QString aFormat, QWidget *aParent = 0 ); + //! Destructor. + virtual ~DigitalTimeDisplayWidget(); + + //! Sets frame visibility. + /*! + * Setst the visibility of the frame around this widget. + * \param aVisible True, if frame is drawn, otherwise false. + */ + virtual void setFrameVisible( bool aVisible ); + //! Sets size. + /*! + * Sets the size. + * \param aWidth Width of the widget on the screen. + * \param aHeight Height of the widget on the screen. + */ + virtual void setSize( int aWidth, int aHeight ); + //! Sets Font + /*! + * Sets the font. + * \param aFont The new font. + */ + virtual void setFont( const QFont &aFont ); + +protected: + //! Displays the time. + /*! + * Displays the time in LCD number display. + */ + virtual void showTime(); + +private: + //! Display to show the time. + QLCDNumber *iDisplay; + //! Format used to show time. + QString iTimeFormat; + +}; + +#endif /*DIGITALTIMEDISPLAYWIDGET_H_*/ diff --git a/src/UserInterface/Components/MeetingRoomCombo.cpp b/src/UserInterface/Components/MeetingRoomCombo.cpp new file mode 100644 index 0000000..4b7a128 --- /dev/null +++ b/src/UserInterface/Components/MeetingRoomCombo.cpp @@ -0,0 +1,106 @@ +#include "MeetingRoomCombo.h" + +#include +#include +#include "Room.h" + +#include + +MeetingRoomCombo::MeetingRoomCombo( QList aRooms, QWidget *aParent ) : + ObservedWidget( aParent ) +{ + iRooms = aRooms; + qSort( iRooms.begin(), iRooms.end(), Room::caseInsensitiveLessThan ); + + QFont regularTextFont; + regularTextFont.setBold( false ); + regularTextFont.setPointSize( 12 ); + + iRoomCombo = new QComboBox( this ); + for ( int i = 0; i < iRooms.count(); i++ ) + { + iRoomCombo->addItem( iRooms.at( i )->name() ); + } + iRoomCombo->setFont( regularTextFont ); + connect( iRoomCombo, SIGNAL( currentIndexChanged( int ) ), this, SLOT( setCurrentIndex( int ) ) ); + connect( iRoomCombo, SIGNAL( currentIndexChanged( const QString & ) ), this, SLOT( setCurrentRoomBy( const QString & ) ) ); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget( iRoomCombo ); + layout->setMargin( 0 ); + setLayout( layout ); +} + +MeetingRoomCombo::~MeetingRoomCombo() +{ + delete iRoomCombo; + iRooms.clear(); +} + +int MeetingRoomCombo::count() +{ + return iRoomCombo->count(); +} + +int MeetingRoomCombo::currentIndex() +{ + return iRoomCombo->currentIndex(); +} + +Room* MeetingRoomCombo::currentRoom() +{ + return currentIndex() >= 0 ? iRooms.at( currentIndex() ) : 0; +} + +void MeetingRoomCombo::setCurrentIndex( int aIndex ) +{ + if ( 0 <= aIndex && aIndex < count() ) + { + iRoomCombo->setCurrentIndex( aIndex ); + } + else + { + iRoomCombo->setCurrentIndex( -1 ); + } + + emit currentRoomChanged( currentRoom() ); + emit currentIndexChanged( currentIndex() ); +} + +void MeetingRoomCombo::setCurrentRoom( Room *aRoom ) +{ + setCurrentIndex( findRoom( aRoom ) ); +} + +void MeetingRoomCombo::setCurrentRoomBy( const QString &aName ) +{ + setCurrentIndex( findRoomBy( aName ) ); +} + +int MeetingRoomCombo::findRoom( Room *aRoom ) +{ + if ( aRoom == 0 ) + { + qDebug() << "MeetingRoomCombo::findRoom\t-1"; + return -1; + } + + for ( int i = 0; i < iRooms.count(); i++ ) + { + if ( aRoom->equals( *( iRooms.at( i ) ) ) ) + { + qDebug() << "MeetingRoomCombo::findRoom\t" << i; + return i; + } + } + qDebug() << "MeetingRoomCombo::findRoom\t-1"; + return -1; +} + +int MeetingRoomCombo::findRoomBy( const QString &aName ) +{ + return iRoomCombo->findText( aName ); +} + + + diff --git a/src/UserInterface/Components/MeetingRoomCombo.h b/src/UserInterface/Components/MeetingRoomCombo.h new file mode 100644 index 0000000..5c7120a --- /dev/null +++ b/src/UserInterface/Components/MeetingRoomCombo.h @@ -0,0 +1,103 @@ +#ifndef MEETINGROOMCOMBO_H_ +#define MEETINGROOMCOMBO_H_ + +#include "ObservedWidget.h" +#include +#include + +class QComboBox; +class Room; + +//! Userinterface class. Displays a list of selectable meeting rooms. +/*! + * Userinterface class. Displays a list of selectable meeting rooms. Customized QComboBox which hides + * all the not needed functionality of the "base" class. + */ +class MeetingRoomCombo : public ObservedWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructs the MeetingRoomCombo. + * \param aRooms List of available rooms. + * \param aParent parent of the widget + */ + MeetingRoomCombo( QList aRooms, QWidget *aParent = 0 ); + //! Destructor. + virtual ~MeetingRoomCombo(); + + //! Number of rooms. + /*! + * \return Number of rooms. + */ + int count(); + //! Current index + /*! + * \return Index of currently selected item. + */ + int currentIndex(); + //! Current room + /*! + * \return Pointer to the room currently selected. + */ + Room* currentRoom(); + +public slots: + //! Sets the current room + /*! + * Sets the current room to match aIndex. + * \param aIndex index of item to be selected. + */ + void setCurrentIndex( int aIndex ); + //! Sets the current room + /*! + * Sets the current room to be aRoom. + * \param aRoom Pointer to the room to be selected. + */ + void setCurrentRoom( Room *aRoom ); + //! Sets the current room + /*! + * Sets the current room based on room's name. + * \param aName Name of the room to be selected. + */ + void setCurrentRoomBy( const QString &aName ); + + //! Searches for the room. + /*! + * Searches for the room "aRoom". + * \param aRoom Pointer to the room to be searched. + * \return Index of room if found. Otherwise negative. + */ + int findRoom( Room *aRoom ); + //! Searches for the room. + /*! + * Searches for the room by its name. + * \param aName Name of the room to be searched. + * \return Index of room if found. Otherwise negative. + */ + int findRoomBy( const QString &aName ); + +signals: + //! Signals change of the current room. + /*! + * Signal is emided when room is changed. + * \param aNewIndex Index of the item now selected. + */ + void currentIndexChanged( int aNewIndex ); + //! Signals change of the current room. + /*! + * Signal is emided when room is changed. + * \param aNewRoom Pointer to the room now selected. + */ + void currentRoomChanged( Room *aNewRoom ); + +private: + //! Combobox containing the names of the rooms. + QComboBox *iRoomCombo; + //! List of rooms. + QList iRooms; +}; + +#endif /*MEETINGROOMCOMBO_H_*/ diff --git a/src/UserInterface/Components/ObservedWidget.cpp b/src/UserInterface/Components/ObservedWidget.cpp new file mode 100644 index 0000000..6dda69a --- /dev/null +++ b/src/UserInterface/Components/ObservedWidget.cpp @@ -0,0 +1,61 @@ +#include "ObservedWidget.h" + +#include +#include +#include +#include +#include + +#include + +ObservedWidget::ObservedWidget( QWidget *aParent ) : + QWidget( aParent ), + iKeyEventsMonitored( true ), + iMouseEventsMonitored( true ), + iTabletEventsMonitored( true ) +{ + setMouseTracking( true ); + + installEventFilter( this ); +} + +ObservedWidget::~ObservedWidget() +{ +} + +void ObservedWidget::setKeyEventsMonitored( bool aIsMonitored ) +{ + iKeyEventsMonitored = aIsMonitored; +} + +void ObservedWidget::setMouseEventsMonitored( bool aIsMonitored ) +{ + iMouseEventsMonitored = aIsMonitored; +} + +void ObservedWidget::setTabletEventsMonitored( bool aIsMonitored ) +{ + iTabletEventsMonitored = aIsMonitored; +} + +bool ObservedWidget::eventFilter( QObject */*aWatched_object*/, QEvent *aEvent ) +{ + switch ( aEvent->type() ) + { + case( QEvent::KeyPress ) : + case( QEvent::TabletMove ) : + case( QEvent::TabletPress ) : +// case( QEvent::MouseMove ) : + case( QEvent::MouseButtonPress ) : + case( QEvent::MouseButtonDblClick ) : + { + qDebug() << "ObservedWidget::eventFilter: " << aEvent; + emit observedEventDetected(); + } + default : + { + break; + } + } + return false; +} diff --git a/src/UserInterface/Components/ObservedWidget.h b/src/UserInterface/Components/ObservedWidget.h new file mode 100644 index 0000000..f07b919 --- /dev/null +++ b/src/UserInterface/Components/ObservedWidget.h @@ -0,0 +1,75 @@ +#ifndef OBSERVEDWIDGET_H_ +#define OBSERVEDWIDGET_H_ + +#include + +class QKeyEvent; +class QTabletEvent; +class QMouseEvent; +class QMouseEvent; +class QMouseEvent; + +//! Userinterface class. Provides event monitoring. +/*! + * UserInterface class. Provides event monitoring. The observed events are mouse, key and + * tablet events. They are used to detect user interadtion with the specified UI element. + */ +class ObservedWidget : public QWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize ObserverWidget instance. + * \param aParent The pointer to the parent widget. Optional. + */ + ObservedWidget( QWidget *aParent = 0 ); + //! Destructor. + virtual ~ObservedWidget(); + + //! Enables/disables monitoring of the key events. + /*! + * Enables/disables monitoring of the key events. Enables if the flag is true; otherwise, disables. + * \param aIsMonitored The flag. + */ + void setKeyEventsMonitored( bool aIsMonitored ); + //! Enables/disables monitoring of the mouse events. + /*! + * Enables/disables monitoring of the mouse events. Enables if the flag is true; otherwise, disables. + * \param aIsMonitored The flag. + */ + void setMouseEventsMonitored( bool aIsMonitored ); + //! Enables/disables monitoring of the tablet events. + /*! + * Enables/disables monitoring of the tablet events. Enables if the flag is true; otherwise, disables. + * \param aIsMonitored The flag. + */ + void setTabletEventsMonitored( bool aIsMonitored ); + +signals: + //! Signals if any of the monitored events is detected.. + /*! + * Signal. Emitted if any of the monitored events is detected. + */ + void observedEventDetected(); + +protected: + //! Filters the detected events. ( TODO: improve event filtering!!! ) + /*! + * Filters the detected events. If an event is being filtered, nothing else can see/detect it anymore, + * it is like if it did not even exist. + * \param aWatched_object The watched object. + * \param aEvent The event which is being filtered. + * \return True if the event was filtered; otherwise, false. + */ + virtual bool eventFilter( QObject* aWatched_object, QEvent* aEvent ); + +private: + bool iKeyEventsMonitored; + bool iMouseEventsMonitored; + bool iTabletEventsMonitored; + +}; + +#endif /*OBSERVEDWIDGET_H_*/ diff --git a/src/UserInterface/Components/ScheduleWidget.cpp b/src/UserInterface/Components/ScheduleWidget.cpp new file mode 100644 index 0000000..425a26f --- /dev/null +++ b/src/UserInterface/Components/ScheduleWidget.cpp @@ -0,0 +1,557 @@ +#include "ScheduleWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include "Meeting.h" + +const QColor ScheduleWidget::sFreeBackground = QColor( 192, 238, 189 ); +const QColor ScheduleWidget::sBusyBackground = QColor( 238, 147, 17 ); +const QColor ScheduleWidget::sHeaderBackground = QColor( Qt::white ); +const QColor ScheduleWidget::sDayHighlightColor = QColor( 255, 235, 160 ); +const QColor ScheduleWidget::sTimeHighlightColor = QColor( Qt::blue ); +const QColor ScheduleWidget::sMainGridColor = QColor( 140, 140, 140 ); +const QColor ScheduleWidget::sHalfGridColor = QColor( 195, 195, 195 ); +const QColor ScheduleWidget::sFrameColor = QColor( Qt::black ); + +ScheduleTableWidget::ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent ) : + QTableWidget( aRows, aColumns, aParent ) +{ + ScheduleWidget* schedule = static_cast( parent() ); + + iMeetingsByDay = new QList[schedule->weekLengthAsDays()]; + iTabletBlocked = false; + iTime.start(); + + setFocusPolicy( Qt::NoFocus ); + setFrameStyle( QFrame::NoFrame ); +} + +ScheduleTableWidget::~ScheduleTableWidget() +{ + delete[] iMeetingsByDay; +} + +void ScheduleTableWidget::paintEvent( QPaintEvent* aEvent ) +{ + QTableWidget::paintEvent( aEvent ); + + ScheduleWidget* schedule = static_cast( parent() ); + QPainter painter( viewport() ); + int rowHeight = rowViewportPosition( 2 ) - rowViewportPosition( 1 ) - 1; + int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1; + + // draw frame around the table + QRect viewportRect = viewport()->rect(); + viewportRect.adjust( 0, 0, -1, -1 ); + painter.setPen( ScheduleWidget::sFrameColor ); + painter.drawRect( viewportRect ); + + // draw horizontal half grid + for ( int i = 1; i < rowCount(); ++i ) + { + int x = columnViewportPosition( 1 ); + int y = rowViewportPosition( i ) + ( rowHeight / 2 ) - 1; + painter.fillRect( QRect( x, y, width() - x - 1, 1 ), ScheduleWidget::sHalfGridColor ); + } + + // draw horizontal main grid + for ( int i = 1; i < rowCount(); ++i ) + { + int y = rowViewportPosition( i ) - 1; + painter.fillRect( QRect( 1, y, width() - 2, 1 ), ScheduleWidget::sMainGridColor ); + } + + // draw vertical main grid + for ( int i = 1; i < columnCount(); ++i ) + { + int x = columnViewportPosition( i ) - 1; + painter.fillRect( QRect( x, 1, 1, height() - 2 ), ScheduleWidget::sMainGridColor ); + } + + // draw current day highlight + QPen pen( ScheduleWidget::sDayHighlightColor ); + pen.setWidth( 3 ); + painter.setPen( pen ); + painter.setBrush( Qt::NoBrush ); + + for ( int i = 1; i < columnCount(); ++i ) + { + if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) ) + { + int x = columnViewportPosition( i ) + 1; + int y = 2; + int w = columnWidth - 3; + int h = height() - 5; + painter.drawRect( x, y, w, h ); + break; + } + } + + // draw meetings + QBrush brush( ScheduleWidget::sBusyBackground ); + painter.setBrush( brush ); + painter.setRenderHint( QPainter::Antialiasing ); + painter.setPen( ScheduleWidget::sFrameColor ); + populateMeetingList(); + + for ( int day = 0; day < schedule->weekLengthAsDays(); ++day ) + { + for ( int i = 0; i < iMeetingsByDay[day].size(); ++i ) + { + painter.drawRoundRect( iMeetingsByDay[day][i].rect, 20, 20 ); + } + } + + // draw current time highlight + painter.setBrush( Qt::NoBrush ); + painter.setRenderHint( QPainter::Antialiasing, false ); + + for ( int i = 1; i < columnCount(); ++i ) + { + if ( schedule->iCurrentDateTime.date() == schedule->iShownDate.addDays( i - 1 ) ) + { + int x = columnViewportPosition( i ) - 1; + int y = computeViewportY( schedule->iCurrentDateTime.time() ); + int w = columnWidth + 2; + int h = 4; + painter.fillRect( x, y, w, h, ScheduleWidget::sTimeHighlightColor ); + break; + } + } +} + +void ScheduleTableWidget::tabletEvent( QTabletEvent* aEvent ) +{ + int ms = iTime.restart(); + + if ( iTabletBlocked && ms > 1000 ) + { + iTabletBlocked = false; + qDebug() << "Tablet block released"; + } + + if ( iTabletBlocked == false ) + { + qDebug() << "Tablet blocked released"; + activateMeeting( aEvent->x(), aEvent->y() ); + } +} + +void ScheduleTableWidget::mouseMoveEvent( QMouseEvent* /* aEvent */ ) +{ + // this is overridden as empty method because otherwise + // unwanted behaviour would occur due to QTableWidget +} + +void ScheduleTableWidget::mousePressEvent( QMouseEvent* aEvent ) +{ + activateMeeting( aEvent->x(), aEvent->y() ); +} + +void ScheduleTableWidget::populateMeetingList() +{ + ScheduleWidget* schedule = static_cast( parent() ); + + for ( int i = 0; i < schedule->weekLengthAsDays(); ++i ) + iMeetingsByDay[i].clear(); + + // insert suitable meetings to list + for ( int i = 0; i < schedule->iMeetings.count(); ++i ) + { + Meeting* meeting = schedule->iMeetings[i]; + int day = meeting->startsAt().date().dayOfWeek() - 1; + if (( meeting->startsAt().date().weekNumber() == schedule->iShownDate.weekNumber() ) && + ( day < schedule->weekLengthAsDays() ) && + ( meeting->endsAt().time() > QTime( schedule->iStartHour, 0 ) ) && + ( meeting->startsAt().time() <= QTime( schedule->iStartHour + schedule->iNumberOfHours - 1, 59 ) ) ) + { + MeetingContainer container; + container.meeting = meeting; + container.rect = QRect( 0, 0, 0, 0 ); + container.rectComputed = false; + iMeetingsByDay[day].append( container ); + } + } + + // compute meeting rectangles + for ( int day = 0; day < schedule->weekLengthAsDays(); ++day ) + { + for ( int i = 0; i < iMeetingsByDay[day].size(); ++i ) + { + if ( iMeetingsByDay[day][i].rectComputed ) + continue; + + QList meetingIndices; + findOverlappingMeetings( day, iMeetingsByDay[day][i].meeting, meetingIndices ); + meetingIndices.append( i ); + + for ( int j = 0; j < meetingIndices.size(); ++j ) + { + if ( iMeetingsByDay[day][meetingIndices[j]].rectComputed ) + continue; + + int columnWidth = columnViewportPosition( 2 ) - columnViewportPosition( 1 ) - 1; + + Meeting* meeting = iMeetingsByDay[day][meetingIndices[j]].meeting; + int x = columnViewportPosition( day + 1 ) + ( int )(( columnWidth / ( float )meetingIndices.size() ) * j ); + int y = computeViewportY( meeting->startsAt().time() ); + int width = ( int )( columnWidth / ( float )meetingIndices.size() + 0.5f ); + int height = computeViewportY( meeting->endsAt().time() ) - y; + + iMeetingsByDay[day][meetingIndices[j]].rect = QRect( x, y, width, height ); + iMeetingsByDay[day][meetingIndices[j]].rectComputed = true; + } + } + } +} + +bool ScheduleTableWidget::findOverlappingMeetings( int aDay, Meeting* aMeeting, QList& aResult ) +{ + QSet overlapSet; + + // first find meetings that overlap with aMeeting + for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i ) + { + Meeting* other = iMeetingsByDay[aDay][i].meeting; + if ( aMeeting != other && aMeeting->overlaps( *(other) ) ) + overlapSet.insert( i ); + } + + // then compare overlappiong ones against every meeting to make sure that + // the returned set can be used to compute rectangles for all cases at once + foreach( int index, overlapSet ) + { + Meeting* meetingInSet = iMeetingsByDay[aDay][index].meeting; + for ( int i = 0; i < iMeetingsByDay[aDay].size(); ++i ) + { + Meeting* other = iMeetingsByDay[aDay][i].meeting; + if ( meetingInSet != other && aMeeting != other && meetingInSet->overlaps( *(other) ) ) + overlapSet.insert( i ); + } + } + + aResult.clear(); + foreach( int index, overlapSet ) + { + aResult.append( index ); + } + + return !aResult.empty(); +} + +void ScheduleTableWidget::activateMeeting( int x, int y ) +{ + ScheduleWidget* schedule = static_cast( parent() ); + + for ( int day = 0; day < schedule->weekLengthAsDays(); ++day ) + { + for ( int i = 0; i < iMeetingsByDay[day].size(); ++i ) + { + if ( iMeetingsByDay[day][i].rect.contains( x, y ) && !iTabletBlocked ) + { + iTabletBlocked = true; + qDebug() << "Activated meeting at x" << x << "y" << y << ":" << iMeetingsByDay[day][i].meeting->toString(); + emit schedule->meetingActivated( iMeetingsByDay[day][i].meeting ); + } + } + } +} + +int ScheduleTableWidget::computeViewportY( QTime aTime ) +{ + ScheduleWidget* schedule = static_cast( parent() ); + int secondsInDisplayDay = schedule->iNumberOfHours * 60 * 60; + int mainY = rowViewportPosition( 1 ) + 1; + int mainHeight = height() - mainY - 1; + + return mainY + ( int )(( QTime( schedule->iStartHour, 0 ).secsTo( aTime ) / ( float )secondsInDisplayDay ) * mainHeight ); +} + +ScheduleWidget::ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent ) : + ObservedWidget( aParent ), + iCurrentDateTime( aCurrentDateTime ), + iStartHour( aSettings->dayStartsAt().hour() ), + iNumberOfHours( aSettings->dayEndsAt().hour() - aSettings->dayStartsAt().hour() + 1 ), + iDaysInSchedule( aSettings->daysInSchedule() ), + iLastRefresh( aCurrentDateTime.time() ) +{ + iStartHour = qBound( 0, iStartHour, 23 ); + iNumberOfHours = qBound( 1, iNumberOfHours, 24 - iStartHour ); + + iScheduleTable = new ScheduleTableWidget(( iNumberOfHours + 1 ) * 1, weekLengthAsDays() + 1, this ); + iScheduleTable->horizontalHeader()->hide(); + iScheduleTable->verticalHeader()->hide(); + iScheduleTable->setHorizontalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + iScheduleTable->setVerticalScrollBarPolicy( Qt::ScrollBarAlwaysOff ); + iScheduleTable->setShowGrid( false ); + + QFont font; + font.setPointSize( 10 ); + + // add empty item to top-left corner, this will be updated in refresh() + QTableWidgetItem *weekItem = new QTableWidgetItem(); + weekItem->setTextAlignment( Qt::AlignCenter ); + weekItem->setFlags( Qt::ItemIsEnabled ); + weekItem->setBackgroundColor( sHeaderBackground ); + weekItem->setFont( font ); + iScheduleTable->setItem( 0, 0, weekItem ); + + // add empty item to main cell + QTableWidgetItem *mainItem = new QTableWidgetItem(); + mainItem->setFlags( Qt::ItemIsEnabled ); + mainItem->setBackgroundColor( sFreeBackground ); + iScheduleTable->setItem( 1, 1, mainItem ); + + // set row header items + QTime time( iStartHour, 0 ); + for ( int i = 1; i < iScheduleTable->rowCount(); ++i ) + { + QTableWidgetItem *item = new QTableWidgetItem( time.toString( "HH:mm" ) ); + item->setTextAlignment( Qt::AlignHCenter ); + item->setFlags( Qt::ItemIsEnabled ); + item->setBackgroundColor( sHeaderBackground ); + item->setFont( font ); + iScheduleTable->setItem( i, 0, item ); + time = time.addSecs( 60 * 60 ); + } + + // set empty column header items, these will be updated in refresh() + for ( int i = 1; i < iScheduleTable->columnCount(); ++i ) + { + QTableWidgetItem *item = new QTableWidgetItem(); + item->setTextAlignment( Qt::AlignCenter ); + item->setFlags( Qt::ItemIsEnabled ); + item->setBackgroundColor( sHeaderBackground ); + item->setFont( font ); + iScheduleTable->setItem( 0, i, item ); + } + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget( iScheduleTable ); + layout->setAlignment( Qt::AlignCenter ); + layout->setMargin( 0 ); + setLayout( layout ); + + showCurrentWeek(); +} + +ScheduleWidget::~ScheduleWidget() +{ + clear(); + delete iScheduleTable; +} + +int ScheduleWidget::shownWeek() +{ + return iShownDate.weekNumber(); +} + +QDate ScheduleWidget::beginningOfShownWeek() +{ + return iShownDate.addDays( -1 * iShownDate.dayOfWeek() + 1 ); +} + +Meeting* ScheduleWidget::currentMeeting() +{ + return meeting( iCurrentDateTime ); +} + +Meeting* ScheduleWidget::meeting( QDateTime aAt ) +{ + for ( int i = 0; i < iMeetings.count(); ++i ) + { + if ( iMeetings[i]->startsAt() <= aAt && iMeetings[i]->endsAt() >= aAt ) + { + return iMeetings[i]; + } + } + + return 0; +} + +void ScheduleWidget::clear() +{ + qDebug() << "ScheduleWidget::clear"; + int i = 0; + while ( !iMeetings.isEmpty() ) + { + i++; + iMeetings.removeFirst(); + } + qDebug() << "Deleted " << i << " items"; +} + +void ScheduleWidget::clear( QDateTime aFrom, QDateTime aUntil ) +{ + for ( int i = 0; i < iMeetings.count(); ++i ) + { + if (( iMeetings[i]->startsAt() >= aFrom && iMeetings[i]->startsAt() <= aUntil ) || + ( iMeetings[i]->startsAt() <= aFrom && iMeetings[i]->endsAt() >= aFrom ) ) + { + iMeetings.removeAt( i ); + --i; + } + } +} + +void ScheduleWidget::refresh() +{ + for ( int i = 0; i < iScheduleTable->columnCount(); ++i ) + { + QTableWidgetItem* item = iScheduleTable->item( 0, i ); + QFont font = item->font(); + if ( i == 0 ) { + item->setText( tr( "Wk %1" ).arg( iShownDate.weekNumber() ) ); + continue; + } + item->setText( iShownDate.addDays( i - 1 ).toString( tr( "ddd d MMM" ) ) ); + + if ( iCurrentDateTime.date() == iShownDate.addDays( i - 1 ) ) + { + // mark current day + item->setBackgroundColor( sDayHighlightColor ); + font.setItalic( true ); + item->setFont( font ); + } + else + { + item->setBackgroundColor( sHeaderBackground ); + font.setItalic( false ); + item->setFont( font ); + } + } + + // force repaint of the main area + iScheduleTable->setSpan( 1, 1, iNumberOfHours, weekLengthAsDays() ); + + iLastRefresh = iCurrentDateTime.time(); +} + +void ScheduleWidget::setCurrentDateTime( QDateTime aCurrentDateTime ) +{ + Meeting* previous = meeting( iCurrentDateTime ); + Meeting* current = meeting( aCurrentDateTime ); + iCurrentDateTime = aCurrentDateTime; + + if ( iLastRefresh.secsTo( iCurrentDateTime.time() ) > sRefreshIntervalInSeconds ) + { + // enough time has elapsed since last refresh + refresh(); + } + + if ( previous != current ) + { + emit currentMeetingChanged( current ); + } +} + +void ScheduleWidget::insertMeeting( Meeting *aMeeting ) +{ + Meeting* previous = meeting( iCurrentDateTime ); + iMeetings.append( aMeeting ); + Meeting* current = meeting( iCurrentDateTime ); + + qDebug() << "Inserted" << aMeeting->toString(); + + refresh(); + + if ( previous != current ) + { + emit currentMeetingChanged( current ); + } +} + +void ScheduleWidget::removeMeeting( Meeting *aMeeting ) +{ + Meeting* previous = meeting( iCurrentDateTime ); + + qDebug() << "Delete" << aMeeting->toString(); + for ( int i = 0; i < iMeetings.count(); ++i ) + { + if ( iMeetings[i]->equals( *(aMeeting) ) ) + { + iMeetings.removeAt( i ); + + refresh(); + + Meeting* current = meeting( iCurrentDateTime ); + if ( previous != current ) + emit currentMeetingChanged( current ); + + return; + } + } +} + +//void ScheduleWidget::updateMeeting( Meeting *aMeeting ) +//{ +// +//} + +void ScheduleWidget::showPreviousWeek() +{ + iShownDate = iShownDate.addDays( -7 ); + refresh(); + emit shownWeekChanged( iShownDate ); +} + +void ScheduleWidget::showCurrentWeek() +{ + iShownDate = iCurrentDateTime.date(); + + // set weekday to monday + iShownDate = iShownDate.addDays( -( iShownDate.dayOfWeek() - 1 ) ); + + refresh(); + emit shownWeekChanged( iShownDate ); +} + +void ScheduleWidget::showNextWeek() +{ + iShownDate = iShownDate.addDays( 7 ); + refresh(); + emit shownWeekChanged( iShownDate ); +} + +int ScheduleWidget::computeHeaderRow( QTime aTime ) +{ + // map given time to correct header row in the schedule table + return aTime.hour() - ( iStartHour - 1 ); +} + +int ScheduleWidget::weekLengthAsDays() +{ + return ( iDaysInSchedule == DisplaySettings::WholeWeekInSchedule ) ? 7 : 5; +} + +void ScheduleWidget::resizeEvent( QResizeEvent* /* aEvent */ ) +{ + QRect rect = iScheduleTable->contentsRect(); + int rowHeight = ( int )( rect.height() / ( float )iScheduleTable->rowCount() ); + int headerRowHeight = rowHeight; + int columnWidth = ( int )( rect.width() / ( iScheduleTable->columnCount() - 0.5f ) ); + int headerColumnWidth = columnWidth / 2; + + iScheduleTable->setRowHeight( 0, headerRowHeight ); + for ( int i = 1; i < iScheduleTable->rowCount(); ++i ) + { + iScheduleTable->setRowHeight( i, rowHeight ); + } + + iScheduleTable->setColumnWidth( 0, headerColumnWidth ); + for ( int i = 1; i < iScheduleTable->columnCount(); ++i ) + { + iScheduleTable->setColumnWidth( i, columnWidth ); + } + + // resize table so that frame size matches exactly + int leftMargin = 0, topMargin = 0, rightMargin = 0, bottomMargin = 0; + iScheduleTable->getContentsMargins( &leftMargin, &topMargin, &rightMargin, &bottomMargin ); + iScheduleTable->resize( columnWidth * iScheduleTable->columnCount() - headerColumnWidth + leftMargin + rightMargin, + rowHeight * iScheduleTable->rowCount() + topMargin + bottomMargin ); +} diff --git a/src/UserInterface/Components/ScheduleWidget.h b/src/UserInterface/Components/ScheduleWidget.h new file mode 100644 index 0000000..690d77b --- /dev/null +++ b/src/UserInterface/Components/ScheduleWidget.h @@ -0,0 +1,346 @@ +#ifndef SCHEDULEWIDGET_H_ +#define SCHEDULEWIDGET_H_ + +#include "ObservedWidget.h" +#include +#include +#include "DisplaySettings.h" +#include + +class QTableWidgetItem; +class Meeting; + +//! UserInterface class. Customizes the QTableWidget class. +/*! + * UserInterface class. inherites QTableWidget to draw a custom table. + */ +class ScheduleTableWidget : public QTableWidget +{ + Q_OBJECT + + //! Container for a meeting. + struct MeetingContainer + { + Meeting* meeting; + QRect rect; + bool rectComputed; + }; + +public: + + //! Constructor. + /*! + * Constructor to initialize ScheduleTableWidget instance. + * \param aRows number of rows + * \param aColumns number of columns + * \param aParent parent widget + */ + ScheduleTableWidget( int aRows, int aColumns, QWidget *aParent = 0 ); + + //! Destructor. + ~ScheduleTableWidget(); + +protected: + //! Handles drawing of main table area. + /*! + * Handles drawing of main table area. + * \param aEvent event + */ + void paintEvent( QPaintEvent* aEvent ); + + //! Forwards relevant information to activateMeeting(). + /*! + * Forwards relevant information to activateMeeting(). + * \param aEvent event. + */ + void tabletEvent( QTabletEvent* aEvent ); + + //! Mouse move event. + /*! + * Implemented as empty method for preventing unwanted QTableWidget behavior. + * \param aEvent event. + */ + void mouseMoveEvent( QMouseEvent* aEvent ); + + //! Forwards relevant information to activateMeeting(). + /*! + * Forwards relevant information to activateMeeting(). + * \param aEvent event. + */ + void mousePressEvent( QMouseEvent* aEvent ); + + +private: + //! Populates meeting list. + /*! + * Populates meeting list. + */ + void populateMeetingList(); + + //! Finds overlapping meetings. + /*! + * Finds overlapping meetings. + * \param aDay day. + * \param aMeeting meeting to compare. + * \param aResult generated list of overlapping meetings, empty if none. + * \return true if overlapping meetings were found, otherwise false. + */ + bool findOverlappingMeetings( int aDay, Meeting* aMeeting, QList& aResult ); + + //! Activates a meeting. + /*! + * Activates a meeting. + * \param x x coordinate + * \param y y coordinate + */ + void activateMeeting( int x, int y ); + + //! Computes y coordinate in viewport for a given time. + /*! + * Computes y coordinate in viewport for a given time. + * \param aTime time + * \return y coordinate + */ + int computeViewportY( QTime aTime ); + +private: + + //! Array of list of meetings, for each day. + QList* iMeetingsByDay; + + //! Timer for tablet event blocking + QTime iTime; + + //! Flag telling if tablet events are blocked + bool iTabletBlocked; +}; + +//! UserInterface class. Defines a custom weekly calendar on the screen. Used by thr WeeklyViewWidget. +/*! + * UserInterface class. Defines a custom weekly calendar on the screen. Used by thr WeeklyViewWidget. + */ +class ScheduleWidget : public ObservedWidget +{ + Q_OBJECT + + friend class ScheduleTableWidget; + +public: + + //! Constructor. + /*! + * Constructor to initialize ScheduleWidget instance. + * \param aCurrentDateTime Current date and time. + * \param aSettings Display settings. + * \param aParent Parent widget. + */ + ScheduleWidget( QDateTime aCurrentDateTime, DisplaySettings *aSettings, QWidget *aParent = 0 ); + + //! Destructor. + virtual ~ScheduleWidget(); + + //! Gets number of current week. + /*! + * Gets number of current week. + * \return Number of week. + */ + int currentWeek(); + + //! Gets number of shown week. + /*! + * Gets number of shown week. + * \return The week which is currently shown. + */ + int shownWeek(); + + //! Gets the first day of shown week. + /*! + * Gets the first day of shown week. + * \return First day of the shown week. + */ + QDate beginningOfShownWeek(); + + //! Gets current meeting. + /*! + * Gets current meeting. + * \return current meeting, 0 if none. + */ + Meeting* currentMeeting(); + + //! Gets meeting at a specified date and time. + /*! + * Gets meeting at a specified date and time. + * \param aAt Time when the meeting is. + * \return meeting At the given time, 0 if none. + */ + Meeting* meeting( QDateTime aAt ); + +signals: + + //! Signal. Emitted if a meeting is activated. + /*! + * Signal. Emitted if a meeting is activated, i.e. the user clicks on a meeting rectangle. + * \param aMeeting Actived meeting. + */ + void meetingActivated( Meeting *aMeeting ); + + //! Signal. Emitted when the current meeting changes to another. + /*! + * Signal. Emitted when the current meeting changes to another. + * \param aNewMeeting New meeting. + */ + void currentMeetingChanged( Meeting *aNewMeeting ); + + //! Signal. Emitted if the shown week has been changed. + /*! + * Signal. Emitted if the shown week has been changed. + * \param aDate The first date of the shown week. + */ + void shownWeekChanged( QDate aDate ); + +public slots: + + //! Slot. Clears all meetings. + /*! + * Slot. Clears all meetings. + */ + void clear(); + + //! Slot. Clears meetings based on a range. + /*! + * Slot. Clears meetings based on a range. + * \param aFrom Date and time from which the meetings are cleared. + * \param aUntil Date and time until which the meetings are cleared. + */ + void clear( QDateTime aFrom, QDateTime aUntil ); + + //! Slot. Refreshes display. + /*! + * Slot. Refreshes display. + */ + void refresh(); + + //! Slot. Sets current date and time. + /*! + * Slot. Sets current date and time. + * \param aCurrentDateTime Current date and time. + */ + void setCurrentDateTime( QDateTime aCurrentDateTime ); + + //! Slot. Inserts a meeting to the schedule. + /*! + * Slot. Inserts a meeting to the schedule. + * \param aMeeting Meeting to be inserted. + */ + void insertMeeting( Meeting *aMeeting ); + + //! Slot. Removes a meeting from the schedule. + /*! + * Slot. Removes a meeting from the schedule. + * \param aMeeting Meeting to be removed. + */ + void removeMeeting( Meeting *aMeeting ); + + //! Slot. Updates a meeting in the schedule. (TODO) + /*! + * Slot updates a meeting in the schedule. + * \param aMeeting Meeting was updated. + */ + void updateMeeting( Meeting */*aMeeting*/ ) {}; + + //! Slot. Shows previous week. + /*! + * Slot. Shows previous week. + */ + void showPreviousWeek(); + + //! Slot. Shows current week. + /*! + * Slot. Shows current week. + */ + void showCurrentWeek(); + + //! Slot. Shows next week. + /*! + * Slot. Shows next week. + */ + void showNextWeek(); + +private: + + //! Computes header row. + /*! + * Computes header row number in schedule table based on given time. + * \param aTime Time. + * \return header Row. + */ + int computeHeaderRow( QTime aTime ); + + //! Gets week length as days. + /*! + * Gets week length as days. + * \return Lenght of the week in days. + */ + int weekLengthAsDays(); + + //! Computes proper cell sizes for the schedule table. + /*! + * Computes proper cell sizes for the schedule table. + * \param aEvent Resize event. + */ + void resizeEvent( QResizeEvent *aEvent ); + +private: + //! Schedule table widget. + ScheduleTableWidget *iScheduleTable; + + //! Current date and time. + QDateTime iCurrentDateTime; + + //! Meetings. + QList iMeetings; /*! Not owned */ + + //! Currently shown week. + QDate iShownDate; + + //! Starting hour of the schedule. + int iStartHour; + + //! Number of hours in the schedule. + int iNumberOfHours; + + //! Variable indicates the length of the week. + DisplaySettings::DaysInSchedule iDaysInSchedule; + + //! When refresh() was called previously + QTime iLastRefresh; + + //! Color for a free cell. + static const QColor sFreeBackground; + + //! Color for a busy cell. + static const QColor sBusyBackground; + + //! Color for headers. + static const QColor sHeaderBackground; + + //! Color for current day highlight. + static const QColor sDayHighlightColor; + + //! Color for current time highlight. + static const QColor sTimeHighlightColor; + + //! Color for main grid. + static const QColor sMainGridColor; + + //! Color for half grid. + static const QColor sHalfGridColor; + + //! Color for frame. + static const QColor sFrameColor; + + //! Refresh interval. + static const int sRefreshIntervalInSeconds = 60; +}; + +#endif /*SCHEDULEWIDGET_H_*/ diff --git a/src/UserInterface/Components/TimeDisplayWidget.cpp b/src/UserInterface/Components/TimeDisplayWidget.cpp new file mode 100644 index 0000000..8267ca0 --- /dev/null +++ b/src/UserInterface/Components/TimeDisplayWidget.cpp @@ -0,0 +1,40 @@ +#include "TimeDisplayWidget.h" + +TimeDisplayWidget::TimeDisplayWidget( QTime aNow, QWidget *aParent ) : + ObservedWidget( aParent ) + +{ + iCurrentTime = aNow; + +// if( aParent && aParent->isWidgetType() ) +// { +// this->setBackgroundColor( aParent->palette().color( QPalette::Window ) ); +// this->setForegroundColor( aParent->palette().color( QPalette::WindowText ) ); +// } +} + +TimeDisplayWidget::~TimeDisplayWidget() +{ +} + +QTime TimeDisplayWidget::time() +{ + return iCurrentTime; +} + +void TimeDisplayWidget::setBackgroundColor( QColor aColor ) +{ + this->setBackgroundColor( aColor ); +} + +void TimeDisplayWidget::setForegroundColor( QColor aColor ) +{ + this->setForegroundColor( aColor ); +} + +void TimeDisplayWidget::setTime( QTime aNow ) +{ + iCurrentTime = aNow; + + showTime(); +} diff --git a/src/UserInterface/Components/TimeDisplayWidget.h b/src/UserInterface/Components/TimeDisplayWidget.h new file mode 100644 index 0000000..a1bcfd4 --- /dev/null +++ b/src/UserInterface/Components/TimeDisplayWidget.h @@ -0,0 +1,86 @@ +#ifndef TIMEDISPLAYWIDGET_H_ +#define TIMEDISPLAYWIDGET_H_ + +#include "ObservedWidget.h" +#include +#include +#include + +//! Abstact. UserInterface class. Displays time. +/*! + * Abstact. UserInterface class. Offers basic functionality to display time. Inherited by + * DigitalTimeDisplayWidget. + */ +class TimeDisplayWidget : public ObservedWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Basic constructor of TimeDisplayWidget. + * \param aNow Current time + * \param aParent Parent widget + */ + TimeDisplayWidget( QTime aNow, QWidget *aParent = 0 ); + //! Destructor + virtual ~TimeDisplayWidget(); + + //! Returns the current time + /*! + * \return Current time stored be the widget + */ + QTime time(); + + //! Set background color + /*! + * \param aColor Color of widget backgroud + */ + void setBackgroundColor( QColor aColor ); + //! Set foreground color + /*! + * \param aColor Color of widget foregroud (text) + */ + void setForegroundColor( QColor aColor ); + //! Set frame visibility + /*! + * Pure virtual function to set the visibility of the frame + * \param aVisible True, if frame is drawn, otherwise false. + */ + virtual void setFrameVisible( bool aVisible ) = 0; + //! Set size + /*! + * Pure virtual function to set the size of widget. + * \param aWidth + * \param aHeight + */ + virtual void setSize( int aWidth, int aHeight ) = 0; + //! Set Font + /*! + * Pure virtual function to set the font. + * \param aFont + */ + virtual void setFont( const QFont &aFont ) = 0; + +public slots: + //! Set time. + /*! + * Updates the time displayd. + * \param aNow Current time. + */ + void setTime( QTime aNow ); + +protected: + //! Displays the time. + /*! + * Pure virtual function to display time. + */ + virtual void showTime() = 0; + +private: + //! Stores the time. + QTime iCurrentTime; + +}; + +#endif /*TIMEDISPLAYWIDGET_H_*/ diff --git a/src/UserInterface/Utils/PasswordDialog.cpp b/src/UserInterface/Utils/PasswordDialog.cpp new file mode 100644 index 0000000..332b0b6 --- /dev/null +++ b/src/UserInterface/Utils/PasswordDialog.cpp @@ -0,0 +1,112 @@ +#include "PasswordDialog.h" +#include +#include +#include +#include +#include +#include +#include + +PasswordDialog::PasswordDialog( QWidget *aParent, const QString &aPassword, const QString &aText, const QString &aTitle ) : + QDialog( aParent ) +{ + setWindowTitle( aTitle.isNull() ? tr( "Enter password" ) : aTitle ); + setModal( true ); + + // Store the password hash to iPasswordHash + // ( aPassword should be allready encoded ) +// QCryptographicHash *hash = new QCryptographicHash( QCryptographicHash::Md5 ); +// hash->addData( aPassword.toUtf8() ); +// iPasswordHash = hash->result(); +// delete hash; + iPasswordHash = aPassword.toUtf8(); + + /* Create the layout: + + .--------title---------. + | text | + | | + | [***_______________] | + | [Cancel] [ OK ] | + '----------------------' + */ + + QVBoxLayout *layout = new QVBoxLayout; + + if ( !aText.isNull() ) + { + QLabel *text = new QLabel( aText ); + layout->addWidget( text ); + layout->addStretch(); + } + + iPasswordEdit = new QLineEdit; + iPasswordEdit->setEchoMode( QLineEdit::Password ); + layout->addWidget( iPasswordEdit ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + QPushButton *buttonOK = new QPushButton( tr( "OK" ) ); + QPushButton *buttonCancel = new QPushButton( tr( "Cancel" ) ); + + buttonLayout->addStretch(); + buttonLayout->addWidget( buttonOK ); + buttonLayout->addWidget( buttonCancel ); + buttonLayout->addStretch(); + layout->addSpacing( 5 ); + layout->addLayout( buttonLayout ); + + // Connect the buttons pressed signals to corresponding slots + connect( buttonOK, SIGNAL( pressed() ), this, SLOT( okButtonPressed() ) ); + connect( buttonCancel, SIGNAL( pressed() ), this, SLOT( cancelButtonPressed() ) ); + + // Enable the layout + setLayout( layout ); +} + +PasswordDialog::~PasswordDialog() +{ +} + +void PasswordDialog::okButtonPressed() +{ + qDebug() << "PasswordDialog::okButtonPressed()"; + + // Get md5 hash from the password entered to the dialog + QCryptographicHash *hash = new QCryptographicHash( QCryptographicHash::Md5 ); + hash->addData( iPasswordEdit->text().toUtf8() ); + QByteArray userpw = hash->result(); + delete hash; + + // Compare the password hashes and emit corresponding signal tellin if the password was correct + if ( iPasswordHash == userpw.toHex() ) + { + emit passwordEntered( PasswordDialog::Correct ); + qDebug() << "Password OK"; + } + else + { + emit passwordEntered( PasswordDialog::Incorrect ); + qDebug() << "Incorrect password!"; + } + + // Close the dialog + close(); +} + +void PasswordDialog::cancelButtonPressed() +{ + qDebug() << "PasswordDialog::cancelButtonPressed()"; + + emit passwordEntered( PasswordDialog::Canceled ); + + close(); +} + +PasswordDialog * PasswordDialog::query( QWidget *aParent, const QString &aPassword, const QString &aText, const QString &aTitle ) +{ + // Create a PasswordDialog instance and show it + PasswordDialog* dlg = new PasswordDialog( aParent, aPassword, aText, aTitle ); + dlg->show(); + return dlg; +} + diff --git a/src/UserInterface/Utils/PasswordDialog.h b/src/UserInterface/Utils/PasswordDialog.h new file mode 100644 index 0000000..5bf8c62 --- /dev/null +++ b/src/UserInterface/Utils/PasswordDialog.h @@ -0,0 +1,69 @@ +#ifndef PASSWORDDIALOG_H_ +#define PASSWORDDIALOG_H_ + +#include + +class QLineEdit; +class QByteArray; + +//! UserInterface class. Responsible for poping-up password query on the screen. +/*! + * UserInterface class. Responsible for poping-up password query on the screen. The class is used + * through static method, just like the QMessageBox in the standard Qt library. + */ +class PasswordDialog : public QDialog +{ + Q_OBJECT + +public: + //! Enumeration of password authenticity. + /*! + * Password authenticity. + */ + enum PasswordStatus + { + Correct, /*!< Correct password. */ + Incorrect, /*!< Incorrect password. */ + Canceled + }; + + //! Creates a new PasswordDialog instance and shows the dialog on the screen. + /*! + * Creates a new Password query dialog. + * \param aParent The parent object. + * \param aPassword The password. + * \param aText Optional. Text displayed in the dialog. + * \param aTitle Optional. Dialog title, defaults to "Enter password". + * \return The instance which was created. + */ + static PasswordDialog * query( QWidget *aParent, const QString &aPassword, + const QString &aText = 0, const QString &aTitle = 0 ); + +signals: + //! Signals the authenticity of the password when the uuser dismisses the dialog. + /*! + * The signal is emitted if user presses a key available for the dialog. + * \param aStatus Password authenticity. + */ + void passwordEntered( PasswordDialog::PasswordStatus aStatus ); + +private slots: + void okButtonPressed(); + void cancelButtonPressed(); + +private: + //! Constructor. + /*! + * Constructor to initialize a PasswordDialog instance. + * \param aParent The parent object. + * \param aPassword The password. + */ + PasswordDialog( QWidget *aParent, const QString &aPassword, const QString &aText, const QString &aTitle ); + //! Destructor. + virtual ~PasswordDialog(); + + QLineEdit *iPasswordEdit; + QByteArray iPasswordHash; +}; + +#endif /*PASSWORDDIALOG_H_*/ diff --git a/src/UserInterface/Utils/PopUpMessageBox.cpp b/src/UserInterface/Utils/PopUpMessageBox.cpp new file mode 100644 index 0000000..2738068 --- /dev/null +++ b/src/UserInterface/Utils/PopUpMessageBox.cpp @@ -0,0 +1,196 @@ +#include "PopUpMessageBox.h" +#include +#include +#include +#include +#include +#include + +PopUpMessageBox::PopUpMessageBox( QWidget *aParent, const QString &aTitle, const QString &aText, Button aButton ) : + QDialog( aParent ) +{ + setWindowTitle( aTitle ); + setModal( true ); + + iIconLabel = new QLabel; + iIconLabel->setBackgroundRole( QPalette::HighlightedText ); + iIconLabel->setMaximumSize( PopUpMessageBox::sIconSize, PopUpMessageBox::sIconSize ); + //iIconLabel->setScaledContents( true ); + + iMessageLabel = new QLabel; + iMessageLabel->setWordWrap( true ); + setMessage( aText ); + + QHBoxLayout *topLayout = new QHBoxLayout; + topLayout->addWidget( iIconLabel ); + topLayout->addSpacing( 5 ); + topLayout->addWidget( iMessageLabel, 10 ); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addLayout( topLayout ); + + iButton = aButton; + switch ( iButton ) + { + case OkButton: + { + QPushButton *okButton = new QPushButton; + okButton->setText( tr( "OK" ) ); + connect( okButton, SIGNAL( pressed() ), this, SLOT( okButtonPressed() ) ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget( okButton ); + buttonLayout->addStretch(); + layout->addSpacing( 5 ); + layout->addLayout( buttonLayout ); + break; + } + case YesNoButton: + { + QPushButton *yesButton = new QPushButton; + yesButton->setText( tr( "Yes" ) ); + connect( yesButton, SIGNAL( pressed() ), this, SLOT( yesButtonPressed() ) ); + + QPushButton *noButton = new QPushButton; + noButton->setText( tr( "No" ) ); + connect( noButton, SIGNAL( pressed() ), this, SLOT( noButtonPressed() ) ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget( yesButton ); + buttonLayout->addWidget( noButton ); + buttonLayout->addStretch(); + layout->addSpacing( 5 ); + layout->addLayout( buttonLayout ); + } + default: // NoButtonAtAll + { + iTimer = new QTimer; + setInterval( PopUpMessageBox::DefaultInterval ); + connect( iTimer, SIGNAL( timeout() ), this, SLOT( close() ) ); + break; + } + } + + setLayout( layout ); +} + +PopUpMessageBox::~PopUpMessageBox() +{ +} + +QString PopUpMessageBox::getTitle() +{ + return windowTitle(); +} + +QString PopUpMessageBox::getMessage() +{ + return iMessageLabel->text(); +} + +bool PopUpMessageBox::setIcon( const QString &aPath ) +{ + iIconLabel->clear(); + QImage image( aPath ); + if ( image.isNull() ) + { + return false; + } + iIconLabel->setPixmap( QPixmap::fromImage( image ) ); + return true; +} + +void PopUpMessageBox::setTitle( const QString &aTitle ) +{ + setWindowTitle( aTitle ); +} + +void PopUpMessageBox::setMessage( const QString &aMsg ) +{ + iMessageLabel->setText( aMsg ); +} + +void PopUpMessageBox::setInterval( int aInternal ) +{ + iTimer->setInterval( aInternal ); +} + +void PopUpMessageBox::popUp() +{ + show(); + if ( iButton == NoButtonAtAll ) + { + iTimer->start(); + } +} + +void PopUpMessageBox::yesButtonPressed() +{ + emit buttonPressed( PopUpMessageBox::YesReturnType ); + close(); +} + +void PopUpMessageBox::noButtonPressed() +{ + emit buttonPressed( PopUpMessageBox::NoReturnType ); + close(); +} + +void PopUpMessageBox::okButtonPressed() +{ + emit buttonPressed( PopUpMessageBox::OkReturnType ); + close(); +} + +PopUpMessageBox * PopUpMessageBox::cancel( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "Cancel" ), aText, NoButtonAtAll ); + msg->setIcon( ":/popup_cancel" ); + msg->popUp(); + QApplication::beep(); + return msg; +} + +PopUpMessageBox * PopUpMessageBox::error( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "Error" ), aText, NoButtonAtAll ); + msg->setIcon( ":/popup_error" ); + msg->popUp(); + QApplication::beep(); + return msg; +} + +PopUpMessageBox * PopUpMessageBox::information( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "Information" ), aText, NoButtonAtAll ); + msg->setIcon( ":/popup_information" ); + msg->popUp(); + return msg; +} + +PopUpMessageBox * PopUpMessageBox::ok( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "OK" ), aText, OkButton ); + msg->setIcon( ":/popup_ok" ); + msg->popUp(); + return msg; +} + +PopUpMessageBox * PopUpMessageBox::warning( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "Warning" ), aText, NoButtonAtAll ); + msg->setIcon( ":/popup_warning" ); + msg->popUp(); + QApplication::beep(); + return msg; +} + +PopUpMessageBox * PopUpMessageBox::question( QWidget *aParent, const QString &aText ) +{ + PopUpMessageBox* msg = new PopUpMessageBox( aParent, tr( "Question" ), aText, YesNoButton ); + msg->setIcon( ":/popup_question" ); + msg->popUp(); + return msg; +} diff --git a/src/UserInterface/Utils/PopUpMessageBox.h b/src/UserInterface/Utils/PopUpMessageBox.h new file mode 100644 index 0000000..ac7670f --- /dev/null +++ b/src/UserInterface/Utils/PopUpMessageBox.h @@ -0,0 +1,184 @@ +#ifndef POPUPMESSAGEBOX_H_ +#define POPUPMESSAGEBOX_H_ + +#include + +class QLabel; + +//! UserInterface class. Responsible for poping-up information messages on the screen. +/*! + * UserInterface class. Responsible for poping-up information messages on the screen. The class is used + * through static methods, just like the QMessageBox in the standard Qt library. + */ +class PopUpMessageBox : public QDialog +{ + Q_OBJECT + +public: + //! Enumeration of dialog buttons. + /*! + * Predefined buttons for the pop up dialogs. + */ + enum Button + { + NoButtonAtAll, /*!< There is no button. Dialog pops up for the specified interval, then automatically closes. */ + OkButton, /*!< OK button on the right. */ + YesNoButton /*!< Yes and No buttons. */ + }; + //! Enumeration of possible return types after the specified Key was pressed. + /*! + * Predefined return types after the specified Key was pressed. + * \sa PopUpMessageBox::Key. + */ + enum ReturnType + { + YesReturnType, /*!< Yes was pressed. */ + NoReturnType, /*!< No was pressed. */ + OkReturnType /*!< OK was pressed. */ + }; + +signals: + //! Signals if user presses a key available for the dialog. + /*! + * The signal is emitted if user presses a key available for the dialog. + * \param aReturnType The return type based on the key. + * \sa PopUpMessageBox::ReturnType. + */ + void buttonPressed( PopUpMessageBox::ReturnType aReturnType ); + +private slots: + void yesButtonPressed(); + void noButtonPressed(); + void okButtonPressed(); + +private: + //! Constructor. + /*! + * Constructor to initialize a PopUpMessageBox instance. + * \param aParent Optional. The parent object. + * \param aTitle Optional. The title of the dialog. + * \param aText Optional. The message the dialog supposed to show. + * \param aKey Optional. The keys which are accessible when the dialog is active. There is no key by default. + * \sa PopUpMessage::Key. + */ + PopUpMessageBox( QWidget *aParent = 0, const QString &aTitle = 0, const QString &aText = 0, Button aButton = NoButtonAtAll ); + //! Destructor. + virtual ~PopUpMessageBox(); + + //! Gets the title. + /*! + * Getter method to provide the title attribute of the object. + * \return The title as a QString. + */ + QString getTitle(); + //! Gets the message text. + /*! + * Getter method to provide the message text of the dialog. + * \return The message as a QString. + */ + QString getMessage(); + //! Sets the icon for the dialog. + /*! + * Sets the icon for the dialog. + * \param aPath The full path of the icon. + * \return TRUE if setting of icon was successful; otherwise, FALSE. + */ + bool setIcon( const QString &aPath ); + //! Sets the title. + /*! + * Sets the title of the current object. + * \param aTitle The new title. + */ + void setTitle( const QString &aTitle ); + //! Sets the message text. + /*! + * Sets the message text of the current object. + * \param aText The new text. + */ + void setMessage( const QString &aText ); + //! Sets the interval of being popped up. + /*! + * Sets the interval of the current object until when it is popped up. + * \param aInterval The new interval. + */ + void setInterval( int aInterval ); + //! Pops the dialog up. + /*! + * Pops the dialog up for the specified time interval if no key is specified; otherwise, until there is + * a key (actually softkey button) pressed. + * \sa PopUpMessageBox::Key. + */ + void popUp(); + +public: + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'Cancel' PopUpMessage instance without buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * cancel( QWidget *aParent, const QString &aText = 0 ); + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'Error' PopUpMessage instance without buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * error( QWidget *aParent, const QString &aText = 0 ); + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'Information' PopUpMessage instance without buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * information( QWidget *aParent, const QString &aText = 0 ); + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'OK' PopUpMessage instance without buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * ok( QWidget *aParent, const QString &aText = 0 ); + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'Question' PopUpMessage instance with 'Yes' and 'No' buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * question( QWidget *aParent, const QString &aText = 0 ); + //! Creates a new PopUpMessageBox instance. + /*! + * Creates a new 'Warning' PopUpMessage instance without buttons. + * \param aParent The parent object. + * \param aText Optional. The message text. + * \return The instance which was created. + * \sa popUp(). + */ + static PopUpMessageBox * warning( QWidget *aParent, const QString &aText = 0 ); + +public: + //! The default pop-up interval in milliseconds. + static const int DefaultInterval = 2500; // ms + +private: + QLabel *iIconLabel; + QLabel *iMessageLabel; + QTimer *iTimer; + Button iButton; + + static const int sIconSize = 25; + +}; + + +#endif /*POPUPMESSAGEBOX_H_*/ diff --git a/src/UserInterface/Utils/ToolBox.cpp b/src/UserInterface/Utils/ToolBox.cpp new file mode 100644 index 0000000..ba3e146 --- /dev/null +++ b/src/UserInterface/Utils/ToolBox.cpp @@ -0,0 +1,10 @@ +#include "ToolBox.h" + +#include + +QLabel* ToolBox::createLabel( const QString &aText, const QFont &aFont ) +{ + QLabel *label = new QLabel( aText ); + label->setFont( aFont ); + return label; +} diff --git a/src/UserInterface/Utils/ToolBox.h b/src/UserInterface/Utils/ToolBox.h new file mode 100644 index 0000000..2ed778a --- /dev/null +++ b/src/UserInterface/Utils/ToolBox.h @@ -0,0 +1,25 @@ +#ifndef TOOLBOX_H_ +#define TOOLBOX_H_ + +#include + +class QLabel; + +//! UserInterface class. Provides reusable functions for component creation. +/*! + * UserInterface class. Provides reusable functions for component creation. + */ +class ToolBox +{ +public: + //! Creates a label. + /*! + * Creates a label based on the specified text and font type. + * \param aText The text being shown in the label. + * \param aFont The font being used to specify the text. + */ + static QLabel* createLabel( const QString &aText, const QFont &aFont ); + +}; + +#endif /*TOOLBOX_H_*/ diff --git a/src/UserInterface/Views/MeetingInfoDialog.cpp b/src/UserInterface/Views/MeetingInfoDialog.cpp new file mode 100644 index 0000000..95e2cce --- /dev/null +++ b/src/UserInterface/Views/MeetingInfoDialog.cpp @@ -0,0 +1,85 @@ +#include "MeetingInfoDialog.h" +#include "ToolBox.h" +#include "Meeting.h" +#include "Room.h" +#include +#include +#include +#include +#include + +MeetingInfoDialog::MeetingInfoDialog( Meeting *aMeeting, QWidget *aParent ) : + QDialog( aParent ) +{ + setWindowTitle( tr( "Details" ) ); + + QFont normalFont; + normalFont.setPointSize( 11 ); + + QFont boldFont; + boldFont.setPointSize( 11 ); + boldFont.setBold( true ); + + QLabel *subjectLabel = ToolBox::createLabel( tr( "Subject:" ), boldFont ); + QLabel *subjectContent = ToolBox::createLabel( aMeeting->subject(), normalFont ); + + QLabel *descriptionLabel = ToolBox::createLabel( tr( "Description:" ), boldFont ); + QTextEdit *descriptionContent = new QTextEdit( "" ); + descriptionContent->setHtml( aMeeting->description() ); + descriptionContent->setReadOnly( true ); + descriptionContent->setFont( normalFont ); + + QLabel *organizerLabel = NULL; + QLabel *organizerContent = NULL; + + QString roomAddr = aMeeting->room().address(); + QString organizer = aMeeting->organizer(); + if( !organizer.contains( roomAddr ) ) + { + organizerLabel = ToolBox::createLabel( tr( "Organizer:" ), boldFont ); + organizerContent = ToolBox::createLabel( aMeeting->organizer(), normalFont ); + } + QLabel *startsAtLabel = ToolBox::createLabel( tr( "Starts at:" ), boldFont ); + QLabel *startsAtContent = ToolBox::createLabel( aMeeting->startsAt().toString( tr( "d MMMM yyyy hh:mm" ) ), normalFont ); + + QLabel *endsAtLabel = ToolBox::createLabel( tr( "Ends at:" ), boldFont ); + QLabel *endsAtContent = ToolBox::createLabel( aMeeting->endsAt().toString( tr( "d MMMM yyyy hh:mm" ) ), normalFont ); + + QPushButton *button = new QPushButton; + button->setText( tr( "OK" ) ); + connect( button, SIGNAL( clicked() ), this, SLOT( close() ) ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch(); + buttonLayout->addWidget( button ); + buttonLayout->addStretch(); + + QVBoxLayout *layout = new QVBoxLayout; + layout->addWidget( subjectLabel ); + layout->addWidget( subjectContent ); + layout->addSpacing( 5 ); + layout->addWidget( descriptionLabel ); + layout->addWidget( descriptionContent ); + layout->addSpacing( 5 ); + if( organizerLabel ) + layout->addWidget( organizerLabel ); + if( organizerContent ) + layout->addWidget( organizerContent ); + layout->addSpacing( 5 ); + layout->addWidget( startsAtLabel ); + layout->addWidget( startsAtContent ); + layout->addSpacing( 5 ); + layout->addWidget( endsAtLabel ); + layout->addWidget( endsAtContent ); + layout->addSpacing( 5 ); + layout->addStretch(); + layout->addLayout( buttonLayout ); + setLayout( layout ); + + setMinimumWidth( MeetingInfoDialog::width ); + setMinimumHeight( MeetingInfoDialog::height ); +} + +MeetingInfoDialog::~MeetingInfoDialog() +{ +} diff --git a/src/UserInterface/Views/MeetingInfoDialog.h b/src/UserInterface/Views/MeetingInfoDialog.h new file mode 100644 index 0000000..a62cb32 --- /dev/null +++ b/src/UserInterface/Views/MeetingInfoDialog.h @@ -0,0 +1,33 @@ +#ifndef MEETINGINFODIALOG_H_ +#define MEETINGINFODIALOG_H_ + +#include + +class Meeting; +class QLabel; + +//! UserInterface class. Provides detailed information about meetings in a dialog box. +/*! + * UserInterface class. Provides detailed information about meetings in a dialog box. + */ +class MeetingInfoDialog : public QDialog +{ +public: + //! Constructor. + /*! + * Constructor to initialize MeetingInfoDialog instances. + * \param aMeeting The Meeting instance which is needed to be presented in detail. + * \param aParent Pointer to the parent widget. Optional. + */ + MeetingInfoDialog( Meeting *aMeeting, QWidget *aParent = 0 ); + //! Destructor. + virtual ~MeetingInfoDialog(); + +private: + + static const int width = 200; + static const int height = 400; + +}; + +#endif /*MEETINGINFODIALOG_H_*/ diff --git a/src/UserInterface/Views/RoomStatusIndicatorWidget.cpp b/src/UserInterface/Views/RoomStatusIndicatorWidget.cpp new file mode 100644 index 0000000..7421a47 --- /dev/null +++ b/src/UserInterface/Views/RoomStatusIndicatorWidget.cpp @@ -0,0 +1,120 @@ +#include "RoomStatusIndicatorWidget.h" + +#include +#include +#include +#include "DigitalTimeDisplayWidget.h" +#include "ToolBox.h" + +#include + +QTime RoomStatusIndicatorWidget::endOfTheDay = QTime( 23, 59, 0, 0 ); + +RoomStatusIndicatorWidget::RoomStatusIndicatorWidget( Room *aDefaultRoom, Room::Status aStatus, QTime aUntil, QString aTimeFormat, QWidget *aParent ) : + ObservedWidget( aParent ), iTimeFormat( aTimeFormat ) +{ + QFont importantTextFont; + //importantTextFont.setBold( true ); + importantTextFont.setPointSize( 20 ); + + QFont regularTextFont; + //regularTextFont.setBold( true ); + regularTextFont.setPointSize( 12 ); + + // display for current time + // Note: the time display receives current time info from Engine::clock() + iTimeDisplay = new DigitalTimeDisplayWidget( QTime::currentTime(), iTimeFormat, this ); + iTimeDisplay->setFrameVisible( false ); + iTimeDisplay->setSize( 250, 120 ); + + // Pegasus + iDefaultRoomLabel = ToolBox::createLabel( aDefaultRoom->name(), importantTextFont ); + iDefaultRoomLabel->setAlignment( Qt::AlignHCenter ); + iDefaultRoomLabel->setStyleSheet( "background-color: transparent" ); + // is busy + iStatusLabel = ToolBox::createLabel( tr( "is %1" ).arg( statusToText( aStatus ) ), importantTextFont ); + iStatusLabel->setAlignment( Qt::AlignHCenter ); + iStatusLabel->setStyleSheet( "background-color: transparent" ); + + // until 13:22 + iUntilTextLabel = ToolBox::createLabel( tr( "until %1" ).arg( aUntil.toString( iTimeFormat ) ), importantTextFont ); + iUntilTextLabel->setAlignment( Qt::AlignHCenter ); + iUntilTextLabel->setStyleSheet( "background-color: transparent" ); + + QVBoxLayout *topLayout = new QVBoxLayout; + topLayout->addStretch(); + topLayout->addWidget( iTimeDisplay ); + topLayout->addWidget( iDefaultRoomLabel ); + topLayout->addWidget( iStatusLabel ); + topLayout->addWidget( iUntilTextLabel ); + topLayout->addStretch(); + + QHBoxLayout *mainLayout = new QHBoxLayout; + mainLayout->addLayout( topLayout ); + mainLayout->addStretch(); + mainLayout->setMargin( 65 ); + setLayout( mainLayout ); + + statusChanged( aStatus, aUntil ); + + setFocusPolicy( Qt::StrongFocus ); + setEnabled( true ); // enable mouse & key events +} + +RoomStatusIndicatorWidget::~RoomStatusIndicatorWidget() +{ + delete iTimeDisplay; + iTimeDisplay = 0; +} + +QString RoomStatusIndicatorWidget::statusToText( const Room::Status aStatus ) +{ + return ( aStatus == Room::BusyStatus ) ? tr( "busy" ) : tr( "free" ); +} + +QPalette RoomStatusIndicatorWidget::createPalette( Room::Status aStatus ) +{ + QPixmap pixmap( aStatus == Room::BusyStatus ? ":roomstatus_busy" : ":roomstatus_free" ); + + // The image needs to be moved in normal mode so the traffic light not partly outside the screen + const int xoffset( 60 ); + const int yoffset( 19 ); + int cropwidth( pixmap.width() - xoffset ); + int cropheight( pixmap.height() - yoffset ); + + QBrush brush; + if ( windowState() == Qt::WindowFullScreen ) + { + // Use the full image in full screen mode + brush.setTexture( pixmap ); + } + else + { + // Take part of the image so the traffic lights are moved xoffset poxels to left + // and yoffset pixels to up + brush.setTexture( pixmap.copy( xoffset, yoffset, cropwidth, cropheight ) ); + } + + QPalette palette; + palette.setBrush( QPalette::Window, brush ); + return palette; +} + +void RoomStatusIndicatorWidget::setCurrentTime( QTime aCurrentTime ) +{ + iTimeDisplay->setTime( aCurrentTime ); +} + +void RoomStatusIndicatorWidget::statusChanged( const Room::Status aStatus, const QTime aUntil ) +{ + iStatusLabel->setText( tr( "is %1" ).arg( statusToText( aStatus ) ) ); + if ( aUntil == RoomStatusIndicatorWidget::endOfTheDay ) + { + iUntilTextLabel->setText( tr( "whole day." ) ); + } + else + { + iUntilTextLabel->setText( tr( "until %1" ).arg( aUntil.toString( iTimeFormat ) ) ); + } + setPalette( createPalette( aStatus ) ); +} diff --git a/src/UserInterface/Views/RoomStatusIndicatorWidget.h b/src/UserInterface/Views/RoomStatusIndicatorWidget.h new file mode 100644 index 0000000..615051e --- /dev/null +++ b/src/UserInterface/Views/RoomStatusIndicatorWidget.h @@ -0,0 +1,86 @@ +#ifndef ROOMSTATUSINDICATORWIDGET_H_ +#define ROOMSTATUSINDICATORWIDGET_H_ + +#include "ObservedWidget.h" +#include +#include +#include + +#include "Room.h" + +class QLabel; +class QVBoxLayout; +class TimeDisplayWidget; + +//! UserInterface class. Indicates if the default meeting room is busy or not. +/*! + * UserInterface class. Indicates if the default meeting room is busy or not. This widget + * appears on the screen if there was no user interaction with the device for a ceratin period of time, + * and disappears if there is any. Its function is to behave like a screen saver on one hand, and + * to provide details about the current availability on the other hand. + */ +class RoomStatusIndicatorWidget : public ObservedWidget +{ + Q_OBJECT + +public: + //! Contructor. + /*! + * Constructor to initialize RoomStatusIndicatorWidget instances. + * \param aDefaultRoom The room which's status is indicated by the current instance. + * \param aStatus The current status of the room. + * \param aUntil Time until the availability information is valid. + * \param aTimeFormat Time format string. + * \param aParent Pointer to the parent widget. Optional. + * + */ + RoomStatusIndicatorWidget( Room *aDefaultRoom, Room::Status aStatus, QTime aUntil, QString aTimeFormat, QWidget *aParent = 0 ); + //! Destructor. + virtual ~RoomStatusIndicatorWidget(); + +public slots: + //! Slot. Sets current time. + /*! + * Slots. Sets current time on the widget. It is used to provide up-to-date time for the widget's + * TimeDisplayWidget. + * \param aCurrentTime The current time. + */ + void setCurrentTime( QTime aCurrentTime ); + //! Slot. Used to indicate changes in the status of the default room. + /*! + * Slot. Used to indicate changes in the status of the default room. If the specified until time equals + * with the end of the day, i.e. there are no further meetings today or the room is busy until the end + * of the day, then not the until time is shown but a human readable informative message instead. + * \param aStatus The new status of the meeting room. + * \param aUntil The new time until the specified status is valid. + */ + void statusChanged( const Room::Status aStatus, const QTime aUntil ); + +private: + //! Translates the status into human readable text. + /*! + * Translates the status into human readable text. + * \param aStatus The status to be translated. + * \return The string translation of the status. + */ + QString statusToText( const Room::Status aStatus ); + //! Creates a palette used to indicate background color based on the specified status. + /*! + * Creates a palette used to indicate background color based on the specified status. + * \param aStatus The status. + * \return The palette. + */ + QPalette createPalette( Room::Status aStatus ); + +private: + QLabel *iDefaultRoomLabel; + QLabel *iStatusLabel; + QLabel *iUntilTextLabel; + TimeDisplayWidget *iTimeDisplay; + QString iTimeFormat; + + static QTime endOfTheDay; + +}; + +#endif /*ROOMSTATUSINDICATORWIDGET_H_*/ diff --git a/src/UserInterface/Views/SettingsView.cpp b/src/UserInterface/Views/SettingsView.cpp new file mode 100644 index 0000000..caa5cfe --- /dev/null +++ b/src/UserInterface/Views/SettingsView.cpp @@ -0,0 +1,420 @@ +#include "SettingsView.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Configuration.h" +#include "Room.h" +#include "DisplaySettings.h" +#include "ConnectionSettings.h" +#include "StartupSettings.h" + +#include + +SettingsView::SettingsView( QWidget *aParent ) : + ObservedWidget( aParent ) +{ + qDebug() << "SettingsView::ctor invoked"; + // Prepare the tabbed view + iTabWidget = new QTabWidget; + + iSettingsTab = initSettingsTab(); + iWeekViewTab = initWeekViewTab(); + iResourcesTab = initResourcesTab(); + iKioskModeTab = initKioskModeTab(); + + iTabWidget->addTab( iSettingsTab, tr( "Settings" ) ); + iTabWidget->addTab( iWeekViewTab, tr( "Weekly View" ) ); + iTabWidget->addTab( iResourcesTab, tr( "Resources" ) ); + iTabWidget->addTab( iKioskModeTab, tr( "KIOSK Mode" ) ); + + // Prepare the buttons and button layout + QHBoxLayout *buttonLayout = new QHBoxLayout; + iOkButton = new QPushButton; + iOkButton->setText( tr( "OK" ) ); + iCancelButton = new QPushButton; + iCancelButton->setText( tr( "Cancel" ) ); + buttonLayout->addWidget( iOkButton ); + buttonLayout->addWidget( iCancelButton ); + + // Handle the main layout + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( iTabWidget ); + mainLayout->addLayout( buttonLayout ); + + setLayout( mainLayout ); + + // Handle component connections + connect( iOkButton, SIGNAL( pressed() ), this, SLOT( okClicked() ) ); + connect( iCancelButton, SIGNAL( pressed() ), this, SLOT( cancelClicked() ) ); +} + +SettingsView::~SettingsView() +{ + if ( iTabWidget != 0 ) + { + delete iTabWidget; + iTabWidget = 0; + } + if ( iOkButton != 0 ) + { + delete iOkButton; + iOkButton = 0; + } + if ( iCancelButton != 0 ) + { + delete iCancelButton; + iCancelButton = 0; + } + if ( iSettingsTab != 0 ) + { + delete iSettingsTab; + iSettingsTab = 0; + } + if ( iWeekViewTab != 0 ) + { + delete iWeekViewTab; + iWeekViewTab = 0; + } + if ( iResourcesTab != 0 ) + { + delete iResourcesTab; + iResourcesTab = 0; + } + if ( iKioskModeTab != 0 ) + { + delete iKioskModeTab; + iKioskModeTab = 0; + } + if ( iUserName != 0 ) + { + delete iUserName; + iUserName = 0; + } + if ( iPassword != 0 ) + { + delete iPassword; + iPassword = 0; + } + if ( iServerAddress != 0 ) + { + delete iServerAddress; + iServerAddress = 0; + } + if ( iDayStartTime != 0 ) + { + delete iDayStartTime; + iDayStartTime = 0; + } + if ( iDayEndTime != 0 ) + { + delete iDayEndTime; + iDayEndTime = 0; + } + if ( iFiveDays != 0 ) + { + delete iFiveDays; + iFiveDays = 0; + } + if ( iSevenDays != 0 ) + { + delete iSevenDays; + iSevenDays = 0; + } + if ( iRefreshInterval != 0 ) + { + delete iRefreshInterval; + iRefreshInterval = 0; + } + if ( iPowerSaveEnabled != 0 ) + { + delete iPowerSaveEnabled; + iPowerSaveEnabled = 0; + } + if ( iPowerSaveStartTime != 0 ) + { + delete iPowerSaveStartTime; + iPowerSaveStartTime = 0; + } + if ( iPowerSaveEndTime != 0 ) + { + delete iPowerSaveEndTime; + iPowerSaveEndTime = 0; + } +} + +QWidget *SettingsView::initSettingsTab() +{ + QWidget *widget = new QWidget( iTabWidget ); + + // Prepare the widgets that are member variables + iUserName = new QLineEdit; + iPassword = new QLineEdit; + iPassword->setEchoMode( QLineEdit::Password ); + iServerAddress = new QLineEdit; + iRefreshInterval = new QLineEdit; + QIntValidator *qiv = new QIntValidator( 0 ); + iRefreshInterval->setValidator( qiv ); + + iUserName->setText( Configuration::instance()->connectionSettings()->username() ); + iPassword->setText( Configuration::instance()->connectionSettings()->password() ); + iServerAddress->setText( Configuration::instance()->connectionSettings()->serverUrl().toString() ); + QString refreshIntervalStr; + refreshIntervalStr.setNum( Configuration::instance()->connectionSettings()->refreshInterval() ); + iRefreshInterval->setText( refreshIntervalStr ); + + // Create the group boxes + QGroupBox *userInformationGroup = new QGroupBox( tr( "User Information" ) ); + QGroupBox *serverInformationGroup = new QGroupBox( tr( "Server Information" ) ); + + // Prepare the user infromation group box + QGridLayout *ugl = new QGridLayout; + QLabel *userNameLabel = new QLabel( tr( "Username:" ) ); + QLabel *passwordLabel = new QLabel( tr( "Password:" ) ); + + ugl->addWidget( userNameLabel, 0, 0 ); + ugl->addWidget( iUserName, 0, 1 ); + ugl->addWidget( passwordLabel, 1, 0 ); + ugl->addWidget( iPassword, 1, 1 ); + + userInformationGroup->setLayout( ugl ); + + // Prepare the server information group box + QGridLayout *sgl = new QGridLayout; + QLabel *serverURLLabel = new QLabel( tr( "Server URL:" ) ); + QLabel *refreshLabel = new QLabel( tr( "Refresh interval" ) ); + QLabel *secondsLabel = new QLabel( tr( "seconds" ) ); + + sgl->addWidget( serverURLLabel, 0, 0, 1, 2 ); + sgl->addWidget( iServerAddress, 0, 1 ); + sgl->addWidget( refreshLabel, 1, 0 ); + sgl->addWidget( iRefreshInterval, 1, 1 ); + sgl->addWidget( secondsLabel, 1, 2 ); + + serverInformationGroup->setLayout( sgl ); + + // Prepare and set the main layout + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( userInformationGroup ); + mainLayout->addWidget( serverInformationGroup ); + + widget->setLayout( mainLayout ); + + return widget; +} + +QWidget *SettingsView::initWeekViewTab() +{ + QWidget *widget = new QWidget( iTabWidget ); + + // Prepare the member variable widgets + iFiveDays = new QRadioButton( tr( "5" ) ); + iSevenDays = new QRadioButton( tr( "7" ) ); + iDayStartTime = new QTimeEdit; + iDayEndTime = new QTimeEdit; + + if ( Configuration::instance()->displaySettings()->daysInSchedule() == DisplaySettings::WeekdaysInSchedule ) + { + iFiveDays->setChecked( true ); + iSevenDays->setChecked( false ); + } + else + { + iFiveDays->setChecked( false ); + iSevenDays->setChecked( true ); + } + iDayStartTime->setTime( Configuration::instance()->displaySettings()->dayStartsAt() ); + iDayEndTime->setTime( Configuration::instance()->displaySettings()->dayEndsAt() ); + + // Create group box and the grid layout + QGroupBox *weeklyInformation = new QGroupBox( tr( "Weekly View" ) ); + QGridLayout *wgl = new QGridLayout; + + // Prepare the number of days row + QLabel *daysLabel = new QLabel( tr( "Days:" ) ); + + wgl->addWidget( daysLabel, 0, 0 ); + wgl->addWidget( iFiveDays, 0, 1 ); + wgl->addWidget( iSevenDays, 0, 2 ); + + // Preare the day starts row + QLabel *dayStartsLabel = new QLabel( tr( "Day starts:" ) ); + + wgl->addWidget( dayStartsLabel, 1, 0 ); + wgl->addWidget( iDayStartTime, 1, 1, 1, 2 ); + + // Prepare the day ends row + QLabel *dayEndsLabel = new QLabel( tr( "Day ends:" ) ); + + wgl->addWidget( dayEndsLabel, 2, 0 ); + wgl->addWidget( iDayEndTime, 2, 1, 1, 2 ); + + weeklyInformation->setLayout( wgl ); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( weeklyInformation ); + + widget->setLayout( mainLayout ); + + return widget; +} + +QWidget *SettingsView::initResourcesTab() +{ + QWidget *widget = new QWidget( iTabWidget ); + + QHBoxLayout *mainLayout = new QHBoxLayout; + + // Available resources + QVBoxLayout *availableResourcesLayout = new QVBoxLayout; + QLabel *availableResourcesLabel = new QLabel( tr( "Available Resources:" ) ); + QListView *availableResourcesList = new QListView; + + // Fill the list + QList rooms = Configuration::instance()->rooms(); + for ( int i = 0; i < rooms.count(); i++ ) + { + Room *tmp_room = ( Room * ) rooms.at( i ); + qDebug() << "Room: " << tmp_room->name(); + } + + availableResourcesLayout->addWidget( availableResourcesLabel ); + availableResourcesLayout->addWidget( availableResourcesList ); + + // Selected resources + QVBoxLayout *selectedResourcesLayout = new QVBoxLayout; + QLabel *selectedResourcesLabel = new QLabel( tr( "Selected Resources:" ) ); + QListView *selectedResourcesList = new QListView; + + selectedResourcesLayout->addWidget( selectedResourcesLabel ); + selectedResourcesLayout->addWidget( selectedResourcesList ); + + // Button lauout + QVBoxLayout *controlButtonsLayout = new QVBoxLayout; + QPushButton *addButton = new QPushButton( tr( "->" ) ); + QPushButton *removeButton = new QPushButton( tr( "<-" ) ); + controlButtonsLayout->addWidget( addButton ); + controlButtonsLayout->addWidget( removeButton ); + + // Prepare main layout + mainLayout->addLayout( availableResourcesLayout ); + mainLayout->addLayout( controlButtonsLayout ); + mainLayout->addLayout( selectedResourcesLayout ); + + widget->setLayout( mainLayout ); + + return widget; +} + +QWidget *SettingsView::initKioskModeTab() +{ + QWidget *widget = new QWidget( iTabWidget ); + + // Prepare member variable widgets + iPowerSaveEnabled = new QCheckBox( tr( "Power save enabled" ) ); + iPowerSaveStartTime = new QTimeEdit; + iPowerSaveEndTime = new QTimeEdit; + + if ( Configuration::instance()->startupSettings()->isPowersavingEnabled() ) + { + iPowerSaveEnabled->setChecked( true ); + } + else + { + iPowerSaveEnabled->setChecked( false ); + } + iPowerSaveStartTime->setTime( Configuration::instance()->startupSettings()->turnOnAt() ); + iPowerSaveEndTime->setTime( Configuration::instance()->startupSettings()->turnOffAt() ); + + // Prepare the admin password box + QGroupBox *adminPasswordGroup = new QGroupBox( tr( "Admin Password" ) ); + QLabel *oldPwdLabel = new QLabel( tr( "Old password:" ) ); + QLabel *newPwdLabel = new QLabel( tr( "New password:" ) ); + QLabel *confirmPwdLabel = new QLabel( tr( "Confirm password:" ) ); + QPushButton *applyPwdButton = new QPushButton( tr( "Apply" ) ); + + QLineEdit *oldPwdEdit = new QLineEdit; + QLineEdit *newPwdEdit = new QLineEdit; + QLineEdit *confirmPwdEdit = new QLineEdit; + + oldPwdEdit->setEchoMode( QLineEdit::Password ); + newPwdEdit->setEchoMode( QLineEdit::Password ); + confirmPwdEdit->setEchoMode( QLineEdit::Password ); + + QGridLayout *agl = new QGridLayout; + + agl->addWidget( oldPwdLabel, 0, 0 ); + agl->addWidget( oldPwdEdit, 0, 1 ); + agl->addWidget( newPwdLabel, 1, 0 ); + agl->addWidget( newPwdEdit, 1, 1 ); + agl->addWidget( confirmPwdLabel, 2, 0 ); + agl->addWidget( confirmPwdEdit, 2, 1 ); + agl->addWidget( applyPwdButton, 3, 0, 1, 2, Qt::AlignRight ); + + adminPasswordGroup->setLayout( agl ); + + // Prepare the power save options + QGroupBox *powerSaveGroup = new QGroupBox( tr( "Power Save" ) ); + QLabel *switchedOnLabel = new QLabel( tr( "Switched on from:" ) ); + QLabel *toLabel = new QLabel( tr( "to" ) ); + QGridLayout *psgl = new QGridLayout; + + psgl->addWidget( iPowerSaveEnabled, 0, 0, 1, 4, Qt::AlignLeft ); + psgl->addWidget( switchedOnLabel, 1, 0 ); + psgl->addWidget( iPowerSaveStartTime, 1, 1 ); + psgl->addWidget( toLabel, 1, 2 ); + psgl->addWidget( iPowerSaveEndTime, 1, 3 ); + + powerSaveGroup->setLayout( psgl ); + + // Prepare the main layout + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( adminPasswordGroup ); + mainLayout->addWidget( powerSaveGroup ); + + widget->setLayout( mainLayout ); + + return widget; +} + +void SettingsView::okClicked() +{ + qDebug() << "[SettingsView::okClicked] "; + + // Collect the configration data + QTime calendarStart = iDayStartTime->time(); + QTime calendarEnd = iDayEndTime->time(); + QTime powerSaveStart = iPowerSaveStartTime->time(); + QTime powerSaveEnd = iPowerSaveEndTime->time(); + + QString userName = iUserName->text(); + QString password = iPassword->text(); + QString serverAddress = iServerAddress->text(); + QString refreshInterval = iRefreshInterval->text(); + + bool fiveDays = iFiveDays->isChecked(); + bool sevenDays = iSevenDays->isChecked(); + bool powerSaveEnabled = iPowerSaveEnabled->isChecked(); + + // TODO : Set the values to configuration and save it + + close(); +} + +void SettingsView::cancelClicked() +{ + qDebug() << "[SettingsView::cancelClicked] "; + close(); +} diff --git a/src/UserInterface/Views/SettingsView.h b/src/UserInterface/Views/SettingsView.h new file mode 100644 index 0000000..8b9bbeb --- /dev/null +++ b/src/UserInterface/Views/SettingsView.h @@ -0,0 +1,84 @@ +#ifndef SETTINGSVIEW_H_ +#define SETTINGSVIEW_H_ + +#include "ObservedWidget.h" + +class QTabWidget; +class QPushButton; +class QLineEdit; +class QTimeEdit; +class QRadioButton; +class QCheckBox; + +//! User interface class. Shows the settings view and handles configuration changes. +class SettingsView : public ObservedWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize and prepare the settings view. This calls internally + * the helper methods to initialize individual setting tabs. + * \param Parent component. + */ + SettingsView( QWidget *aParent = 0 ); + //! Destructor. + virtual ~SettingsView(); + +private slots: + //! Slot to handle the Ok button pressing. + void okClicked(); + //! Slot to handle the cancel button pressing. + void cancelClicked(); + +private: + //! Initialize "Settings" tab. + QWidget *initSettingsTab(); + //! Initialize "Week View" tab. + QWidget *initWeekViewTab(); + //! Intialize "Resources" tab. + QWidget *initResourcesTab(); + //! Initialize "KIOSK Mode" tab. + QWidget *initKioskModeTab(); + + //! The tabbed settings view component. + QTabWidget *iTabWidget; + //! OK button to dismiss the settings view with saving the settings. + QPushButton *iOkButton; + //! Cancel button to dismiss the settings view without saving settings. + QPushButton *iCancelButton; + //! Settings tab. + QWidget *iSettingsTab; + //! Week View tab. + QWidget *iWeekViewTab; + //! Resources tab. + QWidget *iResourcesTab; + //! KIOSK Mode tab. + QWidget *iKioskModeTab; + + //! User name to the remote server. + QLineEdit *iUserName; + //! Password to the remote server. + QLineEdit *iPassword; + //! Remote server address. + QLineEdit *iServerAddress; + //! Refresh interval in seconds. + QLineEdit *iRefreshInterval; + //! When the day starts in the calendar view. + QTimeEdit *iDayStartTime; + //! When the day ends in the calendar view. + QTimeEdit *iDayEndTime; + //! Selection for showing five days in the calendar. + QRadioButton *iFiveDays; + //! Selection for showing seven days in the calendar. + QRadioButton *iSevenDays; + //! Power saving enabling. + QCheckBox *iPowerSaveEnabled; + //! Start time for activating power save. + QTimeEdit *iPowerSaveStartTime; + //! End time for deactivating power save. + QTimeEdit *iPowerSaveEndTime; +}; + +#endif /*SETTINGSVIEW_H_*/ diff --git a/src/UserInterface/Views/WeeklyViewWidget.cpp b/src/UserInterface/Views/WeeklyViewWidget.cpp new file mode 100644 index 0000000..b33e517 --- /dev/null +++ b/src/UserInterface/Views/WeeklyViewWidget.cpp @@ -0,0 +1,202 @@ +#include "WeeklyViewWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include "Configuration.h" +#include "DisplaySettings.h" +#include "Meeting.h" +#include "Room.h" +#include "MeetingRoomCombo.h" +#include "DigitalTimeDisplayWidget.h" +#include "ScheduleWidget.h" +#include "ToolBox.h" +#include "MeetingInfoDialog.h" + +#include + +WeeklyViewWidget::WeeklyViewWidget( QDateTime aCurrentDateTime, Configuration *aConfiguration, QWidget *aParent ) : + ObservedWidget( aParent ), iConfiguration( aConfiguration ) +{ + + // ***************************************** + // Construct all the needed widgets + QFont importantTextFont; + importantTextFont.setBold( true ); + importantTextFont.setPointSize( 20 ); + + QFont regularTextFont; + regularTextFont.setBold( true ); + regularTextFont.setPointSize( 12 ); + + iSettingsButton = new QPushButton; + iSettingsButton->setIcon( QPixmap( ":button_settings" ) ); + iSettingsButton->setFixedWidth( 36 ); + connect( iSettingsButton, SIGNAL( clicked() ), this, SIGNAL( showSettingsView() ) ); + + iCurrentDayLabel = ToolBox::createLabel( aCurrentDateTime.toString( iConfiguration->displaySettings()->dateFormat() ), regularTextFont ); + iCurrentWeekLabel = ToolBox::createLabel( tr( "Wk %1" ).arg( aCurrentDateTime.date().weekNumber() ), regularTextFont ); + + iRoomsCombo = new MeetingRoomCombo( iConfiguration->rooms(), this ); + iRoomsCombo->setCurrentRoom( iConfiguration->defaultRoom() ); + connect( iRoomsCombo, SIGNAL( observedEventDetected() ), this, SIGNAL( observedEventDetected() ) ); + + iTimeDisplay = new DigitalTimeDisplayWidget( aCurrentDateTime.time(), iConfiguration->displaySettings()->timeFormat(), this ); + iTimeDisplay->setFrameVisible( false ); + iTimeDisplay->setFont( regularTextFont ); + connect( iTimeDisplay, SIGNAL( observedEventDetected() ), this, SIGNAL( observedEventDetected() ) ); + + iSchedule = new ScheduleWidget( aCurrentDateTime, iConfiguration->displaySettings(), this ); + connect( iSchedule, SIGNAL( shownWeekChanged( QDate ) ), this, SIGNAL( shownWeekChanged( QDate ) ) ); + connect( iSchedule, SIGNAL( observedEventDetected() ), this, SIGNAL( observedEventDetected() ) ); + connect( iSchedule, SIGNAL( meetingActivated( Meeting* ) ), this, SIGNAL( meetingActivated( Meeting* ) ) ); + + iPreviousWeekButton = new QPushButton( this ); + iPreviousWeekButton->setText( tr( "<<" ) ); + iPreviousWeekButton->setFixedWidth( 60 ); + connect( iPreviousWeekButton, SIGNAL( clicked() ), iSchedule, SLOT( showPreviousWeek() ) ); + + iCurrentWeekButton = new QPushButton( this ); + iCurrentWeekButton->setFixedWidth( 100 ); + iCurrentWeekButton->setText( tr( "Current" ) ); + connect( iCurrentWeekButton, SIGNAL( clicked() ), iSchedule, SLOT( showCurrentWeek() ) ); + + iNextWeekButton = new QPushButton( this ); + iNextWeekButton->setFixedWidth( 60 ); + iNextWeekButton->setText( tr( ">>" ) ); + connect( iNextWeekButton, SIGNAL( clicked() ), iSchedule, SLOT( showNextWeek() ) ); + + // ********************************** + // Create the view's layout + QHBoxLayout *tableLayout = new QHBoxLayout; + tableLayout->addWidget( iSchedule ); + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addWidget( iRoomsCombo ); + bottomLayout->addWidget( iTimeDisplay ); + QVBoxLayout *dateLayout = new QVBoxLayout; + dateLayout->addWidget( iCurrentDayLabel ); + dateLayout->addWidget( iCurrentWeekLabel ); + bottomLayout->addLayout( dateLayout ); + bottomLayout->addWidget( iPreviousWeekButton ); + bottomLayout->addWidget( iCurrentWeekButton ); + bottomLayout->addWidget( iNextWeekButton ); + bottomLayout->addWidget( iSettingsButton ); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout( tableLayout ); + mainLayout->addLayout( bottomLayout ); + setLayout( mainLayout ); + + QPalette palette; + palette.setColor( QPalette::Window, Qt::white ); + palette.setColor( QPalette::Foreground, Qt::darkGray ); + setPalette( palette ); + + // ****************************************** + // Handle all the signal connections + // TODO : this solution if interaction monitoring is not elegant enough + connect( iPreviousWeekButton, SIGNAL( clicked() ), this, SIGNAL( observedEventDetected() ) ); + connect( iCurrentWeekButton, SIGNAL( clicked() ), this, SIGNAL( observedEventDetected() ) ); + connect( iNextWeekButton, SIGNAL( clicked() ), this, SIGNAL( observedEventDetected() ) ); + connect( iRoomsCombo, SIGNAL( currentRoomChanged( Room * ) ), this, SIGNAL( observedEventDetected() ) ); + connect( iRoomsCombo, SIGNAL( currentIndexChanged( int ) ), this, SIGNAL( observedEventDetected() ) ); + // TODO: connect RoomCombo signals to change meetings data. + connect( iRoomsCombo, SIGNAL( currentRoomChanged( Room * ) ), iSchedule, SLOT( clear() ) ); + connect( iRoomsCombo, SIGNAL( currentRoomChanged( Room * ) ), this, SIGNAL( currentRoomChanged( Room * ) ) ); + connect( iRoomsCombo, SIGNAL( currentRoomChanged( Room * ) ), iSchedule, SLOT( refresh() ) ); +} + +WeeklyViewWidget::~WeeklyViewWidget() +{ + if ( iRoomsCombo ) + { + delete iRoomsCombo; + iRoomsCombo = 0; + } + if ( iTimeDisplay ) + { + delete iTimeDisplay; + iTimeDisplay = 0; + } + if ( iSchedule ) + { + delete iSchedule; + iSchedule = 0; + } + if ( iCurrentDayLabel ) + { + delete iCurrentDayLabel; + iCurrentDayLabel = 0; + } + if ( iCurrentWeekLabel ) + { + delete iCurrentWeekLabel; + iCurrentWeekLabel = 0; + } + if ( iPreviousWeekButton ) + { + delete iPreviousWeekButton; + iPreviousWeekButton = 0; + } + if ( iCurrentWeekButton ) + { + delete iCurrentWeekButton; + iCurrentWeekButton = 0; + } + if ( iNextWeekButton ) + { + delete iNextWeekButton; + iNextWeekButton = 0; + } + if ( iSettingsButton ) + { + delete iSettingsButton; + iSettingsButton = 0; + } +} + +Meeting* WeeklyViewWidget::currentMeeting() +{ + return iSchedule->currentMeeting(); +} + +Room* WeeklyViewWidget::currentRoom() +{ + return iRoomsCombo->currentRoom(); +} + +void WeeklyViewWidget::setCurrentDateTime( QDateTime aCurrentDateTime ) +{ + iCurrentDayLabel->setText( aCurrentDateTime.date().toString( iConfiguration->displaySettings()->dateFormat() ) ); + + iCurrentWeekLabel->setText( tr( "Wk %1" ).arg( aCurrentDateTime.date().weekNumber() ) ); + + iTimeDisplay->setTime( aCurrentDateTime.time() ); + + iSchedule->setCurrentDateTime( aCurrentDateTime ); +} + +void WeeklyViewWidget::insertMeeting( Meeting *aMeeting ) +{ + iSchedule->insertMeeting( aMeeting ); +} + +void WeeklyViewWidget::deleteMeeting( Meeting *aMeeting ) +{ + iSchedule->removeMeeting( aMeeting ); +} + +void WeeklyViewWidget::updateMeeting( Meeting *aMeeting ) +{ + iSchedule->updateMeeting( aMeeting ); +} + +QDate WeeklyViewWidget::beginnigOfShownWeek() +{ + return iSchedule->beginningOfShownWeek(); +} diff --git a/src/UserInterface/Views/WeeklyViewWidget.h b/src/UserInterface/Views/WeeklyViewWidget.h new file mode 100644 index 0000000..f0da141 --- /dev/null +++ b/src/UserInterface/Views/WeeklyViewWidget.h @@ -0,0 +1,169 @@ +#ifndef WEEKLYVIEWWIDGET_H_ +#define WEEKLYVIEWWIDGET_H_ + +#include "ObservedWidget.h" +#include + +class QLabel; +class QPushButton; +class MeetingRoomCombo; +class TimeDisplayWidget; +class ScheduleWidget; +class Configuration; +class Meeting; +class Room; + +//! Userinterface class. Shows a weekly calendar for the selected room, provides date and time information. +/*! + * UserInterface class. Shows the current date and time and selected week's calendar for + * selected meeting room. User can select meeting room, browse weeks back and forth, and can navigate + * back to the current week. + */ +class WeeklyViewWidget : public ObservedWidget +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor to initialize an WeeklyViewWidget instance. + * \param aCurrentDateTime + * \param aConfiguration Pointer to the configuration object. Not owned. + * \param aParent Pointer to the parent widget. + */ + WeeklyViewWidget( QDateTime aCurrentDateTime, Configuration *aConfiguration, QWidget *aParent = 0 ); + //! Destructor. + virtual ~WeeklyViewWidget(); + + //! Current meeting + /*! + * Returns the current meeting if any + * \return Current meeting if any, otherwise null pointer + */ + Meeting* currentMeeting(); + //! Current room + /*! + * Returns the currently selected meeting room. + * \return Selected meeting room. + */ + Room* currentRoom(); + + //! First day of week currently displayd + /*! + * Returns the first day of week currently displayd. + * \return First day of week. + */ + QDate beginnigOfShownWeek(); + +signals: + //! Signals change of the meeting room. + /*! + * Signal is emited when meeting room is changed. + * \param aNewRoom Selected meeting room. + */ + void currentRoomChanged( Room *aNewRoom ); + //! Signals change of the current meeting. + /*! + * Signal is emited when new meeting is started. + * \param aNewMeeting Meeting that has been started. + */ + void currentMeetingChanged( Meeting *aNewMeeting ); + //! Meeting activated. + /*! + * Signal is emitted when a meeting is clicked by the user. + * \param aMeeting actived meeting. + */ + void meetingActivated( Meeting *aMeeting ); + //! Signals creation of new meeting. + /*! + * Signal is emited when new meeting is created. + * \param aMeeting Meeting that has been created. + * \param aUsername User who created the meeting. + * \param aPassword Password of the "aUsername" + */ + void meetingToCreate( Meeting *aMeeting, const QString &aUsername, const QString &aPassword ); + //! Signals deletion of a meeting. + /*! + * Signal is emited when meeting is deleted. + * \param aMeeting Deleted meeting. + * \param aUsername User who deleted the meeting. + * \param aPassword Password of the "aUsername" + */ + void meetingToDelete( Meeting *aMeeting, const QString &aUsername, const QString &aPassword ); + //! Signals modifications of a meeting. + /*! + * Signal is emited when meeting is modified. + * \param aMeeting Modified meeting. + * \param aUsername User who modified the meeting. + * \param aPassword Password of the "aUsername" + */ + void meetingToUpdate( Meeting *aMeeting, const QString &aUsername, const QString &aPassword ); + + //! Signals + /*! + * Signal is emited when settings button is clicked. + */ + void showSettingsView(); + + //! Signal. Emitted if the shown week has been changed. + /*! + * Signal. Emitted if the shown week has been changed. + * \param aDate The first date of the shown week. + */ + void shownWeekChanged( QDate aDate ); + +public slots: + //! Sets the date and time + /*! + * Sets the current date and time + * \param aCurrentDateTime Date and time to be displayd. + */ + void setCurrentDateTime( QDateTime aCurrentDateTime ); + //! Insert meeting + /*! + * Inserts new meeting into the calendar + * \param aMeeting Meeting to be inserted. + */ + void insertMeeting( Meeting *aMeeting ); + //! Delete meeting + /*! + * Removes meeting from the calendar + * \param aMeeting Meeting to be deleted. + */ + void deleteMeeting( Meeting *aMeeting ); + //! Update meeting + /*! + * Updates the display of given meeting in calendar. + * \param aMeeting Meeting to be updated. + */ + void updateMeeting( Meeting *aMeeting ); + +private: + //! Displays the selectable meeting rooms. + MeetingRoomCombo *iRoomsCombo; + //! Displays the time. + TimeDisplayWidget *iTimeDisplay; + //! Displays the calendar. + ScheduleWidget *iSchedule; + //! Displays the date. + QLabel *iCurrentDayLabel; + //! Displays the week number. + QLabel *iCurrentWeekLabel; + //! Button used to display previous week's calendar. + QPushButton *iPreviousWeekButton; + //! Button used to display current week's calendar. + QPushButton *iCurrentWeekButton; + //! Button used to display next week's calendar. + QPushButton *iNextWeekButton; + //! Settings button. TODO : Correct widget is needed!! + QPushButton *iSettingsButton; + //! About button. TODO : Correct widget is needed!! + QPushButton *iAboutButton; + /* ! + * Pointer to configuration object. + * Contains configurable data and IS NOT OWNED by the widget. + */ + Configuration *iConfiguration; +}; + +#endif /*WEEKLYVIEWWIDGET_H_*/ diff --git a/src/UserInterface/WindowManager.cpp b/src/UserInterface/WindowManager.cpp new file mode 100644 index 0000000..edb7b95 --- /dev/null +++ b/src/UserInterface/WindowManager.cpp @@ -0,0 +1,272 @@ +#include "WindowManager.h" + +#include +#include +#include "Configuration.h" +#include "DisplaySettings.h" +#include "Meeting.h" +#include "Room.h" +#include "Engine.h" +#include "Clock.h" +#include "WeeklyViewWidget.h" +#include "RoomStatusIndicatorWidget.h" +#include "MeetingInfoDialog.h" +#include "PopUpMessageBox.h" +#include "DeviceManager.h" +#include "SettingsView.h" + +#include + +const int IDLE_TIME_MULTIPLIER = 60000; // Multiplies milliseconds to minutes + +WindowManager::WindowManager() : + QObject(), + iApplicationName( tr( "Qt Meetings" ) ), + iWeeklyView( 0 ), + iRoomStatusView( 0 ), + iMeetingInfo( 0 ) +{ + iEngine = new Engine; + connect( iEngine, SIGNAL( initializationFailed() ), this, SLOT( closeApplication() ) ); + connect( this, SIGNAL( roomStatusInfoNeeded( Room * ) ), iEngine, SLOT( roomStatusInfoNeeded( Room * ) ) ); + connect( iEngine, SIGNAL( roomStatusChanged( Room *, Room::Status, QTime ) ), this, SLOT( roomStatusChanged( Room *, Room::Status, QTime ) ) ); + connect( iEngine->clock(), SIGNAL( tick( QDateTime ) ), this, SLOT( distributeDateTimeInfo( QDateTime ) ) ); + connect( iEngine, SIGNAL( error( QString ) ), this, SLOT( error( QString ) ) ); + connect( iEngine->deviceManager(), SIGNAL( changeModeOrdered( DeviceManager::OperationMode ) ), this, SLOT( changeModeOrdered( DeviceManager::OperationMode ) ) ); + + iWeeklyView = new WeeklyViewWidget( QDateTime::currentDateTime(), iEngine->configuration() ); + iWeeklyView->setWindowTitle( iApplicationName ); + connect( iEngine, SIGNAL( meetingAdded( Meeting * ) ), iWeeklyView, SLOT( insertMeeting( Meeting * ) ) ); + connect( iEngine, SIGNAL( meetingDeleted( Meeting * ) ), iWeeklyView, SLOT( deleteMeeting( Meeting * ) ) ); + connect( iWeeklyView, SIGNAL( observedEventDetected() ), this, SLOT( observedEventDetected() ) ); + connect( iWeeklyView, SIGNAL( meetingActivated( Meeting * ) ), iEngine, SLOT( fetchMeetingDetails( Meeting* ) ) ); + connect( iEngine, SIGNAL( meetingDetailsFetched( Meeting* ) ), this, SLOT( showMeetingInfo( Meeting * ) ) ); + connect( iWeeklyView, SIGNAL( currentRoomChanged( Room * ) ), iEngine, SLOT( currentRoomChanged( Room * ) ) ); + connect( iWeeklyView, SIGNAL( currentRoomChanged( Room * ) ), this, SLOT( fetchMeetings( Room * ) ) ); + // TODO: fetch meetings for specific week + connect( iWeeklyView, SIGNAL( shownWeekChanged( QDate ) ), this, SLOT( fetchMeetings( QDate ) ) ); + + iIdleTimeCounter = new QTimer(); + iIdleTimeCounter->setSingleShot( true ); + iIdleTimeCounter->setInterval( IDLE_TIME_MULTIPLIER * iEngine->configuration()->displaySettings()->screensaver() ); + iIdleTimeCounter->start(); + connect( iIdleTimeCounter, SIGNAL( timeout() ), this, SLOT( showRoomStatus() ) ); + + if( iEngine->deviceManager()->currentOperationMode() == DeviceManager::KioskMode ) + iWeeklyView->setWindowState( Qt::WindowFullScreen ); + else + iWeeklyView->setWindowState( Qt::WindowMaximized ); + showWeeklyView(); + + //QTimer::singleShot( 0, this, SLOT( closeApplication() ) ); +} + +WindowManager::~WindowManager() +{ + if ( iWeeklyView != 0 ) + { + delete iWeeklyView; + iWeeklyView = 0; + } + + if ( iRoomStatusView != 0 ) + { + delete iRoomStatusView; + iRoomStatusView = 0; + } + + if ( iMeetingInfo != 0 ) + { + delete iMeetingInfo; + iMeetingInfo = 0; + } + + if ( iIdleTimeCounter ) + { + iIdleTimeCounter->stop(); + delete iIdleTimeCounter; + iIdleTimeCounter = 0; + } +} + +void WindowManager::closeApplication() +{ + qDebug() << "WindowManager::closeApplication\tclose application"; + // closes application after 1 second + QTimer::singleShot( 1000, QApplication::instance(), SLOT( quit() ) ); +} + +void WindowManager::distributeDateTimeInfo( QDateTime aCurrentDateTime ) +{ + if ( iRoomStatusView != 0 && iRoomStatusView->isActiveWindow() ) + { + iRoomStatusView->setCurrentTime( aCurrentDateTime.time() ); + } + + if ( iWeeklyView != 0 && iWeeklyView->isActiveWindow() ) + { + iWeeklyView->setCurrentDateTime( aCurrentDateTime ); + } +} + +void WindowManager::roomStatusChanged( Room *aRoom, Room::Status aStatus, QTime aTime ) +{ + // currently works only for default room + if ( aRoom->equals( *(iEngine->defaultRoom()) ) ) + { + if ( iRoomStatusView == 0 ) + { + iRoomStatusView = new RoomStatusIndicatorWidget( aRoom, aStatus, aTime, iEngine->configuration()->displaySettings()->timeFormat() ); + iRoomStatusView->setWindowTitle( iApplicationName ); + connect( iRoomStatusView, SIGNAL( observedEventDetected() ), this, SLOT( observedEventDetected() ) ); + if( iEngine->deviceManager()->currentOperationMode() == DeviceManager::KioskMode ) + iRoomStatusView->setWindowState( Qt::WindowFullScreen ); + else + iRoomStatusView->setWindowState( Qt::WindowMaximized ); + } + else + { + iRoomStatusView->statusChanged( aStatus, aTime ); + } + + if ( !iWeeklyView->isVisible() && !iRoomStatusView->isVisible() ) + { + showRoomStatus(); + } + } +} + +void WindowManager::showRoomStatus() +{ + qDebug() << "WindowManager::showRoomStatus"; + + if ( iRoomStatusView == 0 ) + { + iEngine->roomStatusInfoNeeded( iWeeklyView->currentRoom() ); + } + else + { + iRoomStatusView->show(); + if ( iWeeklyView->isVisible() ) + { + iWeeklyView->hide(); + } + /* Causes SEGMENTATION FAULT + if ( iSettingsView->isVisible() ) + { + iSettingsView->hide(); + } + */ + } + + // closing/deleting meeting info dialog + if ( iMeetingInfo != 0 ) + { + iMeetingInfo->hide(); + } +} + +void WindowManager::showWeeklyView() +{ + qDebug() << "WindowManager::showWeeklyView"; + if ( iRoomStatusView != 0 && iRoomStatusView->isVisible() ) + { + iRoomStatusView->hide(); + } + + iWeeklyView->show(); +} + +void WindowManager::showMeetingInfo( Meeting *aMeeting ) +{ + iMeetingInfo = new MeetingInfoDialog( aMeeting ); + // Display modal dialog + iMeetingInfo->exec(); + + delete iMeetingInfo; + iMeetingInfo = 0; +} + +void WindowManager::showSettingsView() +{ + // TODO : give the Torspo for the person who was responsible to write this method +} + +void WindowManager::error( const QString &aErrorMessage ) +{ + qDebug() << "WindowManager::showErrorPopup"; + + PopUpMessageBox::error( 0, aErrorMessage ); +} + +void WindowManager::observedEventDetected() +{ + // if event was detected on room status view + if ( iRoomStatusView != 0 && iRoomStatusView->isVisible() ) + { + // show weekly view + showWeeklyView(); + } + // otherwise + else + { + // prepare to restart idle counter + if ( iIdleTimeCounter->isActive() ) + { + iIdleTimeCounter->stop(); + } + } + // (re)start idle counter + iIdleTimeCounter->start(); +} + +void WindowManager::fetchMeetings( Room * aRoom ) +{ + QDateTime from( iWeeklyView->beginnigOfShownWeek() ); + QDateTime to( from.addDays( 8 ) ); + qDebug() << "WindowManager::fetchMeetings from " << from.toString( "d.m. h:mm" ) + << " to " << to.toString( "d.m. h:mm" ); + iEngine->fetchMeetings( from, to, aRoom ); +} + +void WindowManager::fetchMeetings( QDate aFrom ) +{ + QDateTime from( aFrom ); + QDateTime to( aFrom.addDays( 7 ), QTime( 23, 59 ) ); + qDebug() << "WindowManager::fetchMeetings from " << from.toString( "d.m. h:mm" ) + << " to " << to.toString( "d.m. h:mm" ); + iEngine->fetchMeetings( from, to, iWeeklyView->currentRoom() ); +} + +void WindowManager::changeModeOrdered( DeviceManager::OperationMode aMode ) +{ + QString message = tr( "You are about to change operation mode to %1." ) + .arg( iEngine->deviceManager()->operationModeToString( aMode ) ); + PasswordDialog *dlg = PasswordDialog::query( 0, iEngine->configuration()->adminPassword(), message ); + qDebug() << "WindowManager::changeModeOrdered/tpassword: " << iEngine->configuration()->adminPassword(); + //TODO make this modal!!! + connect( dlg, SIGNAL( passwordEntered( PasswordDialog::PasswordStatus ) ), + this, SLOT( passwordEntered( PasswordDialog::PasswordStatus ) ) ); +} + +void WindowManager::passwordEntered( PasswordDialog::PasswordStatus aPasswordStatus ) +{ + switch ( aPasswordStatus ) + { + case PasswordDialog::Correct : + { + iEngine->deviceManager()->changeMode( true ); + break; + } + case PasswordDialog::Incorrect : + { + error( tr( "Incorrect password." ) ); + iEngine->deviceManager()->changeMode( false ); + break; + } + default : //case PasswordDialog::Canceled + { + iEngine->deviceManager()->changeMode( false ); + } + } +} diff --git a/src/UserInterface/WindowManager.h b/src/UserInterface/WindowManager.h new file mode 100644 index 0000000..f1f72b5 --- /dev/null +++ b/src/UserInterface/WindowManager.h @@ -0,0 +1,125 @@ +#ifndef WINDOWMANAGER_H_ +#define WINDOWMANAGER_H_ + +#include +#include +#include "Room.h" +#include "Meeting.h" +#include "PasswordDialog.h" +#include "DeviceManager.h" + +class QTimer; +class RoomStatusIndicatorWidget; +class WeeklyViewWidget; +class Engine; +class MeetingInfoDialog; +class SettingsView; + +//! UserInterface class. Behaves as a proxy between the user interface and application's business logic. +/*! + * UserInterface class. Controls the whole user interface, starting with displaying the appropriate + * views. It behaves as a proxy between the user interface and application's business logic, it connects + * the specified components together and forwards the data to the correct place. It also manages the correct + * appearance of current views on the screen. + */ +class WindowManager : public QObject +{ + Q_OBJECT + +public: + //! Constructor. + /*! + * Constructor of WindowManager. + */ + WindowManager(); + //! Destructor. + virtual ~WindowManager(); + +signals: + //! Request current status of the room. + /*! + * Signal is emitted when there is need to check current status of room aRoom. + * \param aRoom Meetingroom which status is requested. + */ + void roomStatusInfoNeeded( Room *aRoom ); + +private slots: + //! Closes the application. + void closeApplication(); + //! Updates the time. + /*! + * Forwards the signal of changed time to current view. + * \param aCurrentDateTime Current date and time. + */ + void distributeDateTimeInfo( QDateTime aCurrentDateTime ); + //! Updates the rooms status. + /*! + * Forwards the signal of changed status to current view. + * \param aRoom Room which status is changed. + * \param aStatus Current status of room. + * \param aTime Time when status is changed. + */ + void roomStatusChanged( Room *aRoom, Room::Status aStatus, QTime aTime ); + //! Displays the weekly view + void showWeeklyView(); + //! Displays the screensaver (room status view) + void showRoomStatus(); + //! Displays the settings view + void showSettingsView(); + /*! + * Displays the meeting info dialog + * \param aMeeting Meeting to be displayd + */ + void showMeetingInfo( Meeting *aMeeting ); + /*! + * Displays an error message + * \param aErrorMessage Message to be displayd + */ + void error( const QString &aErrorMessage ); + //! Restarts the timer to launch the screensaver. + void observedEventDetected(); + //! Slot for fetching meetings. + /*! + * Slot. Fetches meetings for room aRoom for currently visible week + * \param aRoom + */ + void fetchMeetings( Room *aRoom ); + //! Slot for fetching meetings. + /*! + * Slot. Fetches meetings for current room from date aFrom to week ahead. + * \param aFrom Date where to begin fetching + */ + void fetchMeetings( QDate aFrom ); + //! Slot for popping up the confirmation dialog to change the current operation mode + /*! + * Slot. Asks PopUpMessageBox to pop up a confirmation dialog. + * \param aMode The operation mode to be changed to + */ + void changeModeOrdered( DeviceManager::OperationMode aMode ); + //! Slot for receiving the status of the entered password + /*! + * Slot. Receives the status of the entered password and makes the DeviceManager to change the + * operation mode if the password is correct. + * \param aPasswordStatus The status of the password. + */ + void passwordEntered( PasswordDialog::PasswordStatus aPasswordStatus ); + +private: + //! Name of the application. + QString iApplicationName; + //! Pointer to the weekly view. + WeeklyViewWidget *iWeeklyView; + //! Pointer to the screensaver (room status view). + RoomStatusIndicatorWidget *iRoomStatusView; + //! Pointer to the meeting info dialog + MeetingInfoDialog *iMeetingInfo; + //! Pointer to the engine. + Engine *iEngine; + //! Timer to launch the screensaver widget + QTimer *iIdleTimeCounter; + //! Pointer to the settings view + SettingsView *iSettingsView; + +}; + +#endif /*WINDOWMANAGER_H_*/ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..9ed1c36 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include "WindowManager.h" + +using namespace std; + +ofstream logfile; + +void DebugOutputHandler( QtMsgType type, const char *msg ) { + switch( type ) { + case QtDebugMsg: + logfile << QTime::currentTime().toString().toAscii().data() << " Debug: " << msg << "\n"; + logfile.flush(); + break; + case QtCriticalMsg: + logfile << QTime::currentTime().toString().toAscii().data() << " Critical: " << msg << "\n"; + logfile.flush(); + break; + case QtWarningMsg: + logfile << QTime::currentTime().toString().toAscii().data() << " Warning: " << msg << "\n"; + logfile.flush(); + break; + case QtFatalMsg: + logfile << QTime::currentTime().toString().toAscii().data() << " Fatal: " << msg << "\n"; + logfile.flush(); + } +} + +int main( int argc, char *argv[] ) +{ + #ifndef QT_NO_DEBUG_OUTPUT + logfile.open( "/tmp/qtmeetings.log", ios::app ); + qInstallMsgHandler( DebugOutputHandler ); + #endif + + QApplication app( argc, argv ); + WindowManager *windowManager = new WindowManager; + return app.exec(); +} diff --git a/tests/BusinessLogic/Engine/QtMeetings.conf b/tests/BusinessLogic/Engine/QtMeetings.conf new file mode 100644 index 0000000..c77d2c7 --- /dev/null +++ b/tests/BusinessLogic/Engine/QtMeetings.conf @@ -0,0 +1,58 @@ + + + + + + 192.168.0.35 + maemo + P@ssw0rd + + + 60 + + + + + + + + + + + + + + ddd dd MMM + hh:mm + + + + + + TestRoom +
meetingroomtest@test.local
+
+ + + Pegasus +
meetingroom.pegasus_jyv@ixonos.com
+
+ + Taurus +
meetingroom.taurus_jyv@ixonos.com
+
+ + Hercules +
meetingroom.hercules@ixonos.com
+
+
+ + + + +
diff --git a/tests/BusinessLogic/Engine/TestEngine.cpp b/tests/BusinessLogic/Engine/TestEngine.cpp new file mode 100644 index 0000000..752111b --- /dev/null +++ b/tests/BusinessLogic/Engine/TestEngine.cpp @@ -0,0 +1,91 @@ +#include +#include + +#include "Engine.h" +#include "Clock.h" +#include "Configuration.h" +#include "Room.h" +#include "Meeting.h" +#include "TestEngine.h" + + +void TestEngine::initTestCase() +{ + iEngine = new Engine; + QVERIFY( iEngine != 0 ); +} + +void TestEngine::cleanupTestCase() +{ + delete iEngine; + iEngine = 0; +} + +void TestEngine::testClock() +{ + ( void )iEngine->clock(); +} + +void TestEngine::testConfiguration() +{ + QCOMPARE( iEngine->configuration(), Configuration::instance() ); +} + +void TestEngine::testDefaultRoom() +{ + QCOMPARE( iEngine->defaultRoom()->equals( Configuration::instance()->defaultRoom() ), true ); +} + +void TestEngine::testRoomStatusInfoNeeded() +{ + QSignalSpy spy( iEngine, SIGNAL( roomStatusChanged( Room*, Room::Status, QTime ) ) ); + Room* room = new Room( "foo", "bar" ); + iEngine->roomStatusInfoNeeded( room ); + QVERIFY( spy.count() ); + delete room; +} + +void TestEngine::testFetchMeetings() +{ + Room* room = new Room( "foo", "bar" ); + iEngine->fetchMeetings( QDateTime( QDate( 2009, 4, 1 ), QTime( 0, 0, 0 ) ), + QDateTime( QDate( 2009, 4, 25 ), QTime( 0, 0, 0 ) ), room ); + delete room; +} + +void TestEngine::testCreateMeeting() +{ + Room* room = new Room( "foo", "bar" ); + Meeting* meeting = new Meeting( 1234, room, QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 0, 0 ) ), + QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 30, 0 ) ) ); + iEngine->createMeeting( meeting, "foo", "bar" ); + delete meeting; + delete room; +} + +void TestEngine::testUpdateMeeting() +{ + Room* room = new Room( "foo", "bar" ); + Meeting* meeting = new Meeting( 1234, room, QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 0, 0 ) ), + QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 30, 0 ) ) ); + iEngine->updateMeeting( meeting, "foo", "bar" ); + delete meeting; + delete room; +} + +void TestEngine::testDeleteMeeting() +{ + Room* room = new Room( "foo", "bar" ); + Meeting* meeting = new Meeting( 1234, room, QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 0, 0 ) ), + QDateTime( QDate( 2009, 4, 1 ), QTime( 12, 30, 0 ) ) ); + iEngine->deleteMeeting( meeting, "foo", "bar" ); + delete meeting; + delete room; +} + +void TestEngine::testCurrentRoomChanged() +{ + Room* room = new Room( "foo", "bar" ); + iEngine->currentRoomChanged( room ); + delete room; +} diff --git a/tests/BusinessLogic/Engine/TestEngine.h b/tests/BusinessLogic/Engine/TestEngine.h new file mode 100644 index 0000000..e621ccf --- /dev/null +++ b/tests/BusinessLogic/Engine/TestEngine.h @@ -0,0 +1,27 @@ +#include + +#include "Engine.h" +#include "Clock.h" +#include "Configuration.h" +#include "Room.h" + +class TestEngine: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testClock(); + void testConfiguration(); + void testDefaultRoom(); + void testRoomStatusInfoNeeded(); + void testFetchMeetings(); + void testCreateMeeting(); + void testUpdateMeeting(); + void testDeleteMeeting(); + void testCurrentRoomChanged(); + void cleanupTestCase(); + +private: + Engine *iEngine; +}; diff --git a/tests/BusinessLogic/Engine/TestEngineOnly.cpp b/tests/BusinessLogic/Engine/TestEngineOnly.cpp new file mode 100644 index 0000000..d6f9b55 --- /dev/null +++ b/tests/BusinessLogic/Engine/TestEngineOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestEngine.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestEngine testEngine; + QTest::qExec( &testEngine ); +} diff --git a/tests/BusinessLogic/Engine/TestEngineOnly.pro b/tests/BusinessLogic/Engine/TestEngineOnly.pro new file mode 100644 index 0000000..33926c0 --- /dev/null +++ b/tests/BusinessLogic/Engine/TestEngineOnly.pro @@ -0,0 +1,49 @@ +QT += testlib \ + xml \ + network +TEMPLATE = app +CONFIG += qtestlib +CONFIG += link_pkgconfig +PKGCONFIG += libalarm +RESOURCES += ../../../resources/BusinessLogic.qrc +INCLUDEPATH += ../../../src/Domain/ \ + ../../../src/Domain/Configuration/ \ + ../../../src/BusinessLogic/ \ + ../../../src/BusinessLogic/Utils/ \ + ../../../src/IO/Communication/ \ + ../../../src/IO/DeviceControl/ +HEADERS += ../../../src/Domain/Room.h \ + ../../../src/Domain/Meeting.h \ + ../../../src/Domain/Configuration/Configuration.h \ + ../../../src/Domain/Configuration/ConnectionSettings.h \ + ../../../src/Domain/Configuration/StartupSettings.h \ + ../../../src/Domain/Configuration/DisplaySettings.h \ + ../../../src/IO/Communication/CommunicationManager.h \ + ../../../src/IO/Communication/Communication.h \ + ../../../src/IO/DeviceControl/DeviceManager.h \ + ../../../src/IO/DeviceControl/DeviceConfigurator.h \ + ../../../src/IO/DeviceControl/HWKeyListener.h \ + ../../../src/IO/DeviceControl/AlarmSender.h \ + ../../../src/IO/DeviceControl/DeviceDataStorage.h \ + ../../../src/BusinessLogic/Utils/ErrorMapper.h \ + ../../../src/BusinessLogic/Utils/Clock.h \ + ../../../src/BusinessLogic/Engine.h \ + TestEngine.h +SOURCES += ../../../src/Domain/Room.cpp \ + ../../../src/Domain/Meeting.cpp \ + ../../../src/Domain/Configuration/Configuration.cpp \ + ../../../src/Domain/Configuration/ConnectionSettings.cpp \ + ../../../src/Domain/Configuration/StartupSettings.cpp \ + ../../../src/Domain/Configuration/DisplaySettings.cpp \ + ../../../src/IO/Communication/CommunicationManager.cpp \ + ../../../src/IO/Communication/Communication.cpp \ + ../../../src/IO/DeviceControl/DeviceManager.cpp \ + ../../../src/IO/DeviceControl/DeviceConfigurator.cpp \ + ../../../src/IO/DeviceControl/HWKeyListener.cpp \ + ../../../src/IO/DeviceControl/AlarmSender.cpp \ + ../../../src/IO/DeviceControl/DeviceDataStorage.cpp \ + ../../../src/BusinessLogic/Utils/ErrorMapper.cpp \ + ../../../src/BusinessLogic/Utils/Clock.cpp \ + ../../../src/BusinessLogic/Engine.cpp \ + TestEngine.cpp \ + TestEngineOnly.cpp diff --git a/tests/BusinessLogic/Utils/Clock/TestClock.cpp b/tests/BusinessLogic/Utils/Clock/TestClock.cpp new file mode 100644 index 0000000..2424a1a --- /dev/null +++ b/tests/BusinessLogic/Utils/Clock/TestClock.cpp @@ -0,0 +1,42 @@ +#include +#include "Clock.h" +#include +#include "TestClock.h" + +void TestClock::initTestCase() +{ + iClock = new Clock(); + QVERIFY( iClock != 0 ); + QSignalSpy spy( iClock, SIGNAL( tick( QDateTime ) ) ); + QVERIFY( spy.isValid() ); + QTest::qWait( 5000 ); + QVERIFY( spy.count() > 0 ); + QList args = spy.takeFirst(); + QVERIFY( args.at( 0 ).type() == QVariant::DateTime ); +} + +void TestClock::cleanupTestCase() +{ + delete iClock; + iClock = 0; +} + +void TestClock::testDatetime() +{ + QCOMPARE( iClock->datetime(), QDateTime::currentDateTime() ); +} + +void TestClock::testDate() +{ + QCOMPARE( iClock->today(), QDateTime::currentDateTime().date() ); +} + +void TestClock::testTime() +{ + QCOMPARE( iClock->time(), QDateTime::currentDateTime().time() ); +} + +void TestClock::testSynchronizeDatetime() +{ + iClock->syncronizeDateTime(); +} diff --git a/tests/BusinessLogic/Utils/Clock/TestClock.h b/tests/BusinessLogic/Utils/Clock/TestClock.h new file mode 100644 index 0000000..6fdd8fd --- /dev/null +++ b/tests/BusinessLogic/Utils/Clock/TestClock.h @@ -0,0 +1,18 @@ +#include "Clock.h" + +class TestClock: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testDatetime(); + void testDate(); + void testTime(); + void testSynchronizeDatetime(); + void cleanupTestCase(); + +private: + Clock *iClock; + +}; diff --git a/tests/BusinessLogic/Utils/Clock/TestClockOnly.cpp b/tests/BusinessLogic/Utils/Clock/TestClockOnly.cpp new file mode 100644 index 0000000..4173fe5 --- /dev/null +++ b/tests/BusinessLogic/Utils/Clock/TestClockOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestClock.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestClock testClock; + QTest::qExec( &testClock ); +} diff --git a/tests/BusinessLogic/Utils/Clock/TestClockOnly.pro b/tests/BusinessLogic/Utils/Clock/TestClockOnly.pro new file mode 100644 index 0000000..de69d5f --- /dev/null +++ b/tests/BusinessLogic/Utils/Clock/TestClockOnly.pro @@ -0,0 +1,9 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/BusinessLogic/Utils/ +HEADERS += ../../../../src/BusinessLogic/Utils/Clock.h \ + TestClock.h +SOURCES += ../../../../src/BusinessLogic/Utils/Clock.cpp \ + TestClock.cpp \ + TestClockOnly.cpp diff --git a/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.cpp b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.cpp new file mode 100644 index 0000000..aa12175 --- /dev/null +++ b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.cpp @@ -0,0 +1,18 @@ +#include +#include "ErrorMapper.h" +#include "TestErrorMapper.h" + +void TestErrorMapper::initTestCase() +{ +} + +void TestErrorMapper::cleanupTestCase() +{ +} + +void TestErrorMapper::testCodeToString() +{ + QCOMPARE( QString::compare( ErrorMapper::codeToString( 0 ), QString( "Not really an error. Everything went just fine." ) ), 0 ); + QCOMPARE( QString::compare( ErrorMapper::codeToString( 666 ), QString( "For unit test purposes." ) ), 0 ); + QCOMPARE( QString::compare( ErrorMapper::codeToString( -1 ), QString( "" ) ), 0 ); +} diff --git a/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.h b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.h new file mode 100644 index 0000000..c22cdef --- /dev/null +++ b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapper.h @@ -0,0 +1,12 @@ +#include + +class TestErrorMapper: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void testCodeToString(); + void cleanupTestCase(); + +}; diff --git a/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.cpp b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.cpp new file mode 100644 index 0000000..2e7a8a5 --- /dev/null +++ b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestErrorMapper.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestErrorMapper testErrorMapper; + QTest::qExec( &testErrorMapper ); +} diff --git a/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.pro b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.pro new file mode 100644 index 0000000..9f02586 --- /dev/null +++ b/tests/BusinessLogic/Utils/ErrorMapper/TestErrorMapperOnly.pro @@ -0,0 +1,11 @@ +QT += testlib +QT += xml +TEMPLATE = app +CONFIG += qtestlib +RESOURCES = ../../../../resources/BusinessLogic.qrc +INCLUDEPATH += ../../../../src/BusinessLogic/Utils/ +HEADERS += ../../../../src/BusinessLogic/Utils/ErrorMapper.h \ + TestErrorMapper.h +SOURCES += ../../../../src/BusinessLogic/Utils/ErrorMapper.cpp \ + TestErrorMapper.cpp \ + TestErrorMapperOnly.cpp diff --git a/tests/Domain/Configuration/Configuration/QtMeetings.conf b/tests/Domain/Configuration/Configuration/QtMeetings.conf new file mode 100644 index 0000000..5025781 --- /dev/null +++ b/tests/Domain/Configuration/Configuration/QtMeetings.conf @@ -0,0 +1,45 @@ + + + + + jklexch01.ixonos.com + username + password--> + + 65 + + + + + + + + + + + + + + ddd dd MMM + hh:mm + + + + + Pegasus +
meetingroom.pegasus_jyv@ixonos.com
+
+ + Taurus +
meetingroom.taurus_jyv@ixonos.com
+
+ + Hercules +
meetingroom.hercules@ixonos.com
+
+
+ + + + +
diff --git a/tests/Domain/Configuration/Configuration/TestConfiguration.cpp b/tests/Domain/Configuration/Configuration/TestConfiguration.cpp new file mode 100644 index 0000000..130fe91 --- /dev/null +++ b/tests/Domain/Configuration/Configuration/TestConfiguration.cpp @@ -0,0 +1,165 @@ +#include +#include "Configuration.h" +#include "ConnectionSettings.h" +#include "DisplaySettings.h" +#include "StartupSettings.h" +#include "Room.h" +#include "TestConfiguration.h" + +const QString sAdminPw = "admin"; +const QString sUsername = "username"; +const QString sServerPw = "password"; +const QString sServerUrl = "jklexch01.ixonos.com"; +const unsigned int sRefreshInterval = 30; +const DisplaySettings::DateFormat sDateFormat = DisplaySettings::LongDateFormat; +const DisplaySettings::TimeFormat sTimeFormat = DisplaySettings::TwelveHoursTimeFormat; +const DisplaySettings::DaysInSchedule sDaysInSchedule = DisplaySettings::WholeWeekInSchedule; +const QTime sDayStartsAt = QTime(6,0); +const QTime sDayEndsAt = QTime(18,0); +const int sScreensaver = 1; + +const bool sIsPowersavingEnabled = false; +const QTime sTurnOnAt = QTime(5,45); +const QTime sTurnOffAt = QTime(18,15); + + + +void TestConfiguration::initTestCase() +{ + iConf = Configuration::instance(); + QVERIFY( iConf != 0 ); + + // Set values to empty configuration + QList testrooms; + testrooms.append( new Room( "Taurus", "meetingroom.taurus_jyv@ixonos.com" ) ); + testrooms.append( new Room( "Pegasus", "meetingroom.pegasus_jyv@ixonos.com" ) ); + testrooms.append( new Room( "Hercules", "meetingroom.hercules@ixonos.com" ) ); + iConf->setRooms(testrooms); + iConf->setAdminPassword(sAdminPw); + + // connection + iConf->connectionSettings()->setServerUrl(sServerUrl); + iConf->connectionSettings()->setUsername(sUsername); + iConf->connectionSettings()->setPassword(sServerPw); + iConf->connectionSettings()->setRefreshInterval(sRefreshInterval); + + // display + iConf->displaySettings()->setDateFormat(sDateFormat); + iConf->displaySettings()->setTimeFormat(sTimeFormat); + iConf->displaySettings()->setDayStartsAt(sDayStartsAt); + iConf->displaySettings()->setDayEndsAt(sDayEndsAt); + iConf->displaySettings()->setDaysInSchedule(sDaysInSchedule); + iConf->displaySettings()->setScreensaver(sScreensaver); + + // startup + iConf->startupSettings()->setPowersavingEnabled(sIsPowersavingEnabled); + iConf->startupSettings()->setTurnOnAt(sTurnOnAt); + iConf->startupSettings()->setTurnOffAt(sTurnOffAt); +} + +void TestConfiguration::cleanupTestCase() +{ + delete iConf; + iConf = NULL; +} + +void TestConfiguration::init() +{ +} + +void TestConfiguration::cleanup() +{ +} + +void TestConfiguration::testInstance() +{ + QCOMPARE( iConf, Configuration::instance() ); +} + +void TestConfiguration::testSave() +{ + // Just check there is no crash + // The following testcases make sure the settings are saved correctly + iConf->save(); +/* + // delete old configuration + delete iConf; + iConf = NULL; + // ...and create new instance + iConf = Configuration::instance(); + QVERIFY( iConf != 0 ); */ +} + + +void TestConfiguration::testDefaultRoom() +{ + + Room* room = iConf->defaultRoom(); + QVERIFY( room != 0 ); + QCOMPARE( room->name(), QString("Taurus") ); + QCOMPARE( room->address(), QString("meetingroom.taurus_jyv@ixonos.com") ); +} + +void TestConfiguration::testConnectionSettings() +{ + ConnectionSettings* conn = iConf->connectionSettings(); + QVERIFY( conn != 0 ); + QCOMPARE( conn->serverUrl().toString(), sServerUrl ); + QCOMPARE( conn->username(), sUsername ); + QCOMPARE( conn->password(), sServerPw ); + QCOMPARE( conn->refreshInterval(), sRefreshInterval ); +} + +void TestConfiguration::testLanguageCode() +{ + QCOMPARE( iConf->languageCode(), QString( "EN" ) ); +} + +void TestConfiguration::testRooms() +{ + QList testrooms; + testrooms.append( new Room( "Taurus", "meetingroom.taurus_jyv@ixonos.com" ) ); + testrooms.append( new Room( "Pegasus", "meetingroom.pegasus_jyv@ixonos.com" ) ); + testrooms.append( new Room( "Hercules", "meetingroom.hercules@ixonos.com" ) ); + + QList rooms = iConf->rooms(); + QVERIFY( rooms.count() == 3 ); + QCOMPARE( rooms[0], iConf->defaultRoom() ); + for ( int i = 0; i < rooms.count(); i++ ) + { + Room* room = rooms[i]; + Room* testroom = testrooms[i]; + QCOMPARE( room->name(), testroom->name() ); + QCOMPARE( room->address(), testroom->address() ); + } +} + +void TestConfiguration::testStartupSettings() +{ + StartupSettings *settings = iConf->startupSettings(); + + QVERIFY( settings->isPowersavingEnabled() == sIsPowersavingEnabled ); + QCOMPARE( settings->turnOnAt(), sTurnOnAt ); + QCOMPARE( settings->turnOffAt(), sTurnOffAt ); +} + +void TestConfiguration::testDisplaySettings() +{ + DisplaySettings *settings = iConf->displaySettings(); + + QVERIFY( settings->daysInSchedule() == sDaysInSchedule ); + QCOMPARE( settings->dayStartsAt(), sDayStartsAt ); + QCOMPARE( settings->dayEndsAt(), sDayEndsAt ); + QCOMPARE( settings->dateFormat(), QString( "dddd d MMMM yyyy" ) ); + QCOMPARE( settings->timeFormat(), QString( "hh:mm ap" ) ); +} + +void TestConfiguration::testAdminPassword() +{ + QCryptographicHash *hash = new QCryptographicHash( QCryptographicHash::Md5 ); + hash->addData( sAdminPw.toUtf8() ); + QByteArray password = hash->result(); + delete hash; + + QCOMPARE( iConf->adminPassword(), QString( password ) ); +} diff --git a/tests/Domain/Configuration/Configuration/TestConfiguration.h b/tests/Domain/Configuration/Configuration/TestConfiguration.h new file mode 100644 index 0000000..c056cb8 --- /dev/null +++ b/tests/Domain/Configuration/Configuration/TestConfiguration.h @@ -0,0 +1,29 @@ +#include +#include "Configuration.h" +#include "ConnectionSettings.h" +#include "DisplaySettings.h" +#include "StartupSettings.h" +#include "Room.h" + +class TestConfiguration: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void testInstance(); + void testSave(); + void testConnectionSettings(); + void testDefaultRoom(); + void testLanguageCode(); + void testRooms(); + void testStartupSettings(); + void testDisplaySettings(); + void testAdminPassword(); + void cleanupTestCase(); + +private: + Configuration* iConf; +}; diff --git a/tests/Domain/Configuration/Configuration/TestConfigurationOnly.cpp b/tests/Domain/Configuration/Configuration/TestConfigurationOnly.cpp new file mode 100644 index 0000000..4b903cb --- /dev/null +++ b/tests/Domain/Configuration/Configuration/TestConfigurationOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestConfiguration.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestConfiguration testConfiguration; + QTest::qExec( &testConfiguration ); +} diff --git a/tests/Domain/Configuration/Configuration/TestConfigurationOnly.pro b/tests/Domain/Configuration/Configuration/TestConfigurationOnly.pro new file mode 100644 index 0000000..c35ef6b --- /dev/null +++ b/tests/Domain/Configuration/Configuration/TestConfigurationOnly.pro @@ -0,0 +1,18 @@ +QT += testlib xml +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/Domain/ \ + ../../../../src/Domain/Configuration/ +HEADERS += ../../../../src/Domain/Configuration/Configuration.h \ + ../../../../src/Domain/Configuration/ConnectionSettings.h \ + ../../../../src/Domain/Configuration/DisplaySettings.h \ + ../../../../src/Domain/Configuration/StartupSettings.h \ + ../../../../src/Domain/Room.h \ + TestConfiguration.h +SOURCES += ../../../../src/Domain/Configuration/Configuration.cpp \ + ../../../../src/Domain/Configuration/ConnectionSettings.cpp \ + ../../../../src/Domain/Configuration/DisplaySettings.cpp \ + ../../../../src/Domain/Configuration/StartupSettings.cpp \ + ../../../../src/Domain/Room.cpp \ + TestConfiguration.cpp \ + TestConfigurationOnly.cpp diff --git a/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.cpp b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.cpp new file mode 100644 index 0000000..745ce01 --- /dev/null +++ b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.cpp @@ -0,0 +1,50 @@ +#include +#include "ConnectionSettings.h" +#include "TestConnectionSettings.h" + +static const QString URL = "http://this.is.just/a_test_url/for unit test"; +static const QString USERNAME = "Username"; +static const QString PASSWORD = "Password"; +static const unsigned int INTERVAL = 60; + +void TestConnectionSettings::initTestCase() +{ + iConnection = new ConnectionSettings( QUrl( URL ), USERNAME, PASSWORD, INTERVAL ); + QVERIFY( iConnection != 0 ); +} + +void TestConnectionSettings::cleanupTestCase() +{ + delete iConnection; + iConnection = NULL; +} + +void TestConnectionSettings::init() +{ + //qDebug("Debug line"); +} + +void TestConnectionSettings::cleanup() +{ + //nothing to do +} + +void TestConnectionSettings::testServerUrl() +{ + QCOMPARE( iConnection->serverUrl().toString(), URL ); +} + +void TestConnectionSettings::testUsername() +{ + QCOMPARE( iConnection->username(), USERNAME ); +} + +void TestConnectionSettings::testPassword() +{ + QCOMPARE( iConnection->password(), PASSWORD ); +} + +void TestConnectionSettings::testInterval() +{ + QCOMPARE( iConnection->refreshInterval(), INTERVAL ); +} diff --git a/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.h b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.h new file mode 100644 index 0000000..e641b66 --- /dev/null +++ b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettings.h @@ -0,0 +1,50 @@ +#include +#include "ConnectionSettings.h" + +class TestConnectionSettings: public QObject +{ + Q_OBJECT + +private slots: + /*! + * \function initTestCase is executed before testcase + */ + void initTestCase(); + /*! + * \function init is executed before individual test + */ + void init(); + /*! + * + * \function cleanup is executed after individual test + */ + void cleanup(); + + /*! + * test for server url + */ + void testServerUrl(); + /*! + * test for username + */ + void testUsername(); + + /*! + * test for password + */ + void testPassword(); + + /*! + * test for refresh interval + */ + void testInterval(); + + /*! + * \function cleanupTestCase is executed after testcase + */ + void cleanupTestCase(); + +private: + ConnectionSettings *iConnection; + +}; diff --git a/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.cpp b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.cpp new file mode 100644 index 0000000..aa132d7 --- /dev/null +++ b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestConnectionSettings.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestConnectionSettings testConnectionSettings; + QTest::qExec( &testConnectionSettings ); +} diff --git a/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.pro b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.pro new file mode 100644 index 0000000..a845b2a --- /dev/null +++ b/tests/Domain/Configuration/ConnectionSettings/TestConnectionSettingsOnly.pro @@ -0,0 +1,9 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/Domain/Configuration/ +HEADERS += ../../../../src/Domain/Configuration/ConnectionSettings.h \ + TestConnectionSettings.h +SOURCES += ../../../../src/Domain/Configuration/ConnectionSettings.cpp \ + TestConnectionSettings.cpp \ + TestConnectionSettingsOnly.cpp diff --git a/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.cpp b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.cpp new file mode 100644 index 0000000..ca5c090 --- /dev/null +++ b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.cpp @@ -0,0 +1,72 @@ +#include +#include +#include "DisplaySettings.h" +#include "TestDisplaySettings.h" + +const QTime TIME_0800 = QTime( 8, 00 ); +const QTime TIME_0845 = QTime( 8, 45 ); +const QTime TIME_1700 = QTime( 17, 00 ); +const QTime TIME_1710 = QTime( 17, 10 ); + +const QString DF_LONG = "dddd d MMMM yyyy"; +const QString DF_SHORT = "ddd d MMM"; + +const QString TF_12 = "hh:mm ap"; +const QString TF_24 = "hh:mm"; + +void TestDisplaySettings::initTestCase() +{ + iSettings_Long_12_7_800_1700 = new DisplaySettings( DisplaySettings::LongDateFormat, DisplaySettings::TwelveHoursTimeFormat, DisplaySettings::WholeWeekInSchedule, TIME_0800, TIME_1700 ); + iSettings_Short_24_5_845_1710 = new DisplaySettings( DisplaySettings::ShortDateFormat, DisplaySettings::TwentyFourHoursTimeFormat, DisplaySettings::WeekdaysInSchedule, TIME_0845, TIME_1710 ); + QVERIFY( iSettings_Long_12_7_800_1700 != 0 ); + QVERIFY( iSettings_Short_24_5_845_1710 != 0 ); +} + +void TestDisplaySettings::cleanupTestCase() +{ + delete iSettings_Long_12_7_800_1700; + iSettings_Long_12_7_800_1700 = 0; + + delete iSettings_Short_24_5_845_1710; + iSettings_Short_24_5_845_1710 = 0; +} + +void TestDisplaySettings::init() +{ + //qDebug("Debug line"); +} + +void TestDisplaySettings::cleanup() +{ + //nothing to do +} + +void TestDisplaySettings::testDateFormat() +{ + QCOMPARE( iSettings_Long_12_7_800_1700->dateFormat(), DF_LONG ); + QCOMPARE( iSettings_Short_24_5_845_1710->dateFormat(), DF_SHORT ); +} + +void TestDisplaySettings::testTimeFormat() +{ + QCOMPARE( iSettings_Long_12_7_800_1700->timeFormat(), TF_12 ); + QCOMPARE( iSettings_Short_24_5_845_1710->timeFormat(), TF_24 ); +} + +void TestDisplaySettings::testDaysInSchedule() +{ + QCOMPARE( iSettings_Long_12_7_800_1700->daysInSchedule(), DisplaySettings::WholeWeekInSchedule ); + QCOMPARE( iSettings_Short_24_5_845_1710->daysInSchedule(), DisplaySettings::WeekdaysInSchedule ); +} + +void TestDisplaySettings::testDayStartsAt() +{ + QCOMPARE( iSettings_Long_12_7_800_1700->dayStartsAt(), TIME_0800 ); + QCOMPARE( iSettings_Short_24_5_845_1710->dayStartsAt(), TIME_0845 ); +} + +void TestDisplaySettings::tetsDayEndsAt() +{ + QCOMPARE( iSettings_Long_12_7_800_1700->dayEndsAt(), TIME_1700 ); + QCOMPARE( iSettings_Short_24_5_845_1710->dayEndsAt(), TIME_1710 ); +} diff --git a/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.h b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.h new file mode 100644 index 0000000..8c5d6ba --- /dev/null +++ b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettings.h @@ -0,0 +1,25 @@ +#include +#include +#include "DisplaySettings.h" + +class TestDisplaySettings: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void init(); + void cleanup(); + + void testDateFormat(); + void testTimeFormat(); + void testDaysInSchedule(); + void testDayStartsAt(); + void tetsDayEndsAt(); + +private: + DisplaySettings *iSettings_Long_12_7_800_1700; + DisplaySettings *iSettings_Short_24_5_845_1710; + +}; diff --git a/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.cpp b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.cpp new file mode 100644 index 0000000..19f4cc2 --- /dev/null +++ b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestDisplaySettings.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestDisplaySettings testDisplaySettings; + QTest::qExec( &testDisplaySettings ); +} diff --git a/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.pro b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.pro new file mode 100644 index 0000000..4f0d68d --- /dev/null +++ b/tests/Domain/Configuration/DisplaySettings/TestDisplaySettingsOnly.pro @@ -0,0 +1,9 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/Domain/Configuration/ +HEADERS += ../../../../src/Domain/Configuration/DisplaySettings.h \ + TestDisplaySettings.h +SOURCES += ../../../../src/Domain/Configuration/DisplaySettings.cpp \ + TestDisplaySettings.cpp \ + TestDisplaySettingsOnly.cpp diff --git a/tests/Domain/Configuration/StartupSettings/TestStartupSettings.cpp b/tests/Domain/Configuration/StartupSettings/TestStartupSettings.cpp new file mode 100644 index 0000000..fff16b9 --- /dev/null +++ b/tests/Domain/Configuration/StartupSettings/TestStartupSettings.cpp @@ -0,0 +1,55 @@ +#include +#include +#include "StartupSettings.h" +#include "TestStartupSettings.h" + +const QTime TIME_0800 = QTime( 8, 00 ); +const QTime TIME_0845 = QTime( 8, 45 ); +const QTime TIME_1700 = QTime( 17, 00 ); +const QTime TIME_1710 = QTime( 17, 10 ); + +void TestStartupSettings::initTestCase() +{ + iSettings_f_800_1700 = new StartupSettings( false, TIME_0800, TIME_1700 ); + iSettings_t_845_1710 = new StartupSettings( true, TIME_0845, TIME_1710 ); + + QVERIFY( iSettings_f_800_1700 != 0 ); + QVERIFY( iSettings_t_845_1710 != 0 ); +} + +void TestStartupSettings::cleanupTestCase() +{ + delete iSettings_f_800_1700; + iSettings_f_800_1700 = 0; + + delete iSettings_t_845_1710; + iSettings_t_845_1710 = 0; +} + +void TestStartupSettings::init() +{ + //qDebug("Debug line"); +} + +void TestStartupSettings::cleanup() +{ + //nothing to do +} + +void TestStartupSettings::testIsPowersavingEnabled() +{ + QCOMPARE( iSettings_f_800_1700->isPowersavingEnabled(), false ); + QCOMPARE( iSettings_t_845_1710->isPowersavingEnabled(), true ); +} + +void TestStartupSettings::testTurnOnAt() +{ + QCOMPARE( iSettings_f_800_1700->turnOnAt(), TIME_0800 ); + QCOMPARE( iSettings_t_845_1710->turnOnAt(), TIME_0845 ); +} + +void TestStartupSettings::testTurnOffAt() +{ + QCOMPARE( iSettings_f_800_1700->turnOffAt(), TIME_1700 ); + QCOMPARE( iSettings_t_845_1710->turnOffAt(), TIME_1710 ); +} diff --git a/tests/Domain/Configuration/StartupSettings/TestStartupSettings.h b/tests/Domain/Configuration/StartupSettings/TestStartupSettings.h new file mode 100644 index 0000000..244c63e --- /dev/null +++ b/tests/Domain/Configuration/StartupSettings/TestStartupSettings.h @@ -0,0 +1,23 @@ +#include +#include "StartupSettings.h" +#include + +class TestStartupSettings: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanupTestCase(); + void cleanup(); + + void testIsPowersavingEnabled(); + void testTurnOnAt(); + void testTurnOffAt(); + +private: + StartupSettings *iSettings_f_800_1700; + StartupSettings *iSettings_t_845_1710; + +}; diff --git a/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.cpp b/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.cpp new file mode 100644 index 0000000..1d1d638 --- /dev/null +++ b/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestStartupSettings.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestStartupSettings testStartupSettings; + QTest::qExec( &testStartupSettings ); +} diff --git a/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.pro b/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.pro new file mode 100644 index 0000000..39264fc --- /dev/null +++ b/tests/Domain/Configuration/StartupSettings/TestStartupSettingsOnly.pro @@ -0,0 +1,9 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/Domain/Configuration/ +HEADERS += ../../../../src/Domain/Configuration/StartupSettings.h \ + TestStartupSettings.h +SOURCES += ../../../../src/Domain/Configuration/StartupSettings.cpp \ + TestStartupSettings.cpp \ + TestStartupSettingsOnly.cpp diff --git a/tests/Domain/Meeting/TestMeeting.cpp b/tests/Domain/Meeting/TestMeeting.cpp new file mode 100644 index 0000000..e38434b --- /dev/null +++ b/tests/Domain/Meeting/TestMeeting.cpp @@ -0,0 +1,184 @@ +#include +#include "Meeting.h" +#include "Room.h" +#include "TestMeeting.h" + +void TestMeeting::initTestCase() +{ + iPrimaryId1 = 5; + iPrimaryId2 = 0xa6c8eef1; + iSecondaryId1 = 0; + iSecondaryId2 = 0xFFFF0005; + + iRoom = new Room( "Pegasus", "meetingroom.pegasus_jyv@ixonos.com" ); + QVERIFY( iRoom != 0 ); + + iOrganizer1Name = "Test Organizer"; + iOrganizer1EMail = "test.organizer@company.url"; + iOrganizer2EMail = "Another Organizer"; + iOrganizer2Name = "another.organizer@company.url"; + + iStartsAt1 = QDateTime( QDate( 2007, 3, 13 ), QTime( 13, 0, 0 ) ); + iStartsAt2 = QDateTime( QDate( 2007, 3, 14 ), QTime( 13, 0, 0 ) ); + + iEndsAt1 = QDateTime( QDate( 2007, 3, 13 ), QTime( 14, 0, 0 ) ); + iEndsAt2 = QDateTime( QDate( 2007, 3, 14 ), QTime( 14, 0, 0 ) ); + + iSubject1 = "Test subject"; + iSubject2 = "Another subject"; + + iDescription1 = "This is a description. This is actually a plain text description, not as is was important at all."; + iDescription2 = "

This is a description. This is actually a HTML text description, not as is was important at all."; + + iMeeting1 = new Meeting( iPrimaryId1, iRoom, iStartsAt1, iEndsAt1, iOrganizer1Name, iOrganizer1EMail, iSubject1 ); + QVERIFY( iMeeting1 != 0 ); + + iMeeting2 = new Meeting( iPrimaryId1, iRoom, iStartsAt1, iEndsAt1, "", "", iSubject1, iDescription2 ); + QVERIFY( iMeeting2 != 0 ); +} + +void TestMeeting::cleanupTestCase() +{ + delete iMeeting1; + iMeeting1 = NULL; + delete iMeeting2; + iMeeting2 = NULL; + delete iRoom; + iRoom = NULL; +} + +void TestMeeting::init() +{ + //qDebug("Debug line"); +} + +void TestMeeting::cleanup() +{ + //nothing to do +} + +void TestMeeting::testPrimaryId() +{ + QCOMPARE( iMeeting1->primaryId(), iPrimaryId1 ); + QCOMPARE( iMeeting2->primaryId(), iPrimaryId1 ); +} + +void TestMeeting::testSecondaryId() +{ + iMeeting1->setSecondaryId( iSecondaryId1 ); + QCOMPARE( iMeeting1->secondaryId(), iSecondaryId1 ); + QCOMPARE( iMeeting2->secondaryId(), 0 ); +} + +void TestMeeting::testGetAndSetOrganizer() +{ + QCOMPARE( iMeeting2->organizer(), QString( "" ) ); + + iMeeting2->setOrganizer( iOrganizer2Name, "" ); + QCOMPARE( iMeeting2->organizer(), iOrganizer2Name ); + + iMeeting2->setOrganizer( "", iOrganizer2EMail ); + QCOMPARE( iMeeting2->organizer(), iOrganizer2EMail ); + + iMeeting2->setOrganizer( iOrganizer2Name, iOrganizer2EMail ); + QCOMPARE( iMeeting2->organizer(), QString( "%1 <%2>" ).arg( iOrganizer2Name ).arg( iOrganizer2EMail ) ); +} + +void TestMeeting::testStartsAt() +{ + QCOMPARE( iMeeting1->startsAt(), iStartsAt1 ); +} + +void TestMeeting::testEndsAt() +{ + QCOMPARE( iMeeting1->endsAt(), iEndsAt1 ); +} + +void TestMeeting::testSubject() +{ + QCOMPARE( iMeeting1->subject(), iSubject1 ); +} + +void TestMeeting::testDescription() +{ + QCOMPARE( iMeeting1->description(), QString( "" ) ); + QCOMPARE( iMeeting2->description(), iDescription2 ); +} + +void TestMeeting::testDetailsAvailable() +{ + QCOMPARE( iMeeting1->detailsAvailable(), true ); + QCOMPARE( iMeeting2->detailsAvailable(), false ); +} + +void TestMeeting::testSetSecondaryId() +{ + iMeeting1->setSecondaryId( -1 ); + QCOMPARE( iMeeting1->secondaryId(), -1 ); + + iMeeting1->setSecondaryId( iSecondaryId1 ); + QCOMPARE( iMeeting1->secondaryId(), iSecondaryId1 ); + + iMeeting2->setSecondaryId( iSecondaryId2 ); + QCOMPARE( iMeeting2->secondaryId(), iSecondaryId2 ); +} + +void TestMeeting::testSetStartsAt() +{ + iMeeting1->setStartsAt( iStartsAt2 ); + QCOMPARE( iMeeting1->startsAt(), iStartsAt2 ); + + iMeeting1->setStartsAt( iStartsAt1 ); + QCOMPARE( iMeeting1->startsAt(), iStartsAt1 ); +} + +void TestMeeting::testSetEndsAt() +{ + iMeeting1->setEndsAt( iEndsAt2 ); + QCOMPARE( iMeeting1->endsAt(), iEndsAt2 ); + + iMeeting1->setEndsAt( iEndsAt1 ); + QCOMPARE( iMeeting1->endsAt(), iEndsAt1 ); +} + +void TestMeeting::testSetSubject() +{ + iMeeting1->setSubject( iSubject2 ); + QCOMPARE( iMeeting1->subject(), iSubject2 ); + + iMeeting1->setSubject( iSubject1 ); + QCOMPARE( iMeeting1->subject(), iSubject1 ); +} + +void TestMeeting::testSetDescription() +{ + iMeeting1->setDescription( iDescription1 ); + QCOMPARE( iMeeting1->description(), iDescription1 ); + + iMeeting2->setDescription( iDescription2 ); + QCOMPARE( iMeeting2->description(), iDescription2 ); +} + +void TestMeeting::testEquals() +{ + iMeeting2->setEndsAt( iEndsAt2 ); + QCOMPARE( iMeeting1->equals( iMeeting2 ), false ); + + iMeeting2->setEndsAt( iEndsAt1 ); + QCOMPARE( iMeeting1->equals( iMeeting2 ), true ); +} + +void TestMeeting::testToString() +{ + QString meeting1ToString = QString( "[MEETING: id1:%1 id2:%2 in:%3 from:%4 until:%5 by:%6 subject:%7 description:%8]" ) + .arg( iPrimaryId1 ) + .arg( iSecondaryId1 ) + .arg( iRoom->toString() ) + .arg( iStartsAt1.toString() ) + .arg( iEndsAt1.toString() ) + .arg( iMeeting1->organizer() ) + .arg( iSubject1 ) + .arg( iDescription1 ); + + QCOMPARE( iMeeting1->toString(), meeting1ToString ); +} diff --git a/tests/Domain/Meeting/TestMeeting.h b/tests/Domain/Meeting/TestMeeting.h new file mode 100644 index 0000000..31c06a2 --- /dev/null +++ b/tests/Domain/Meeting/TestMeeting.h @@ -0,0 +1,45 @@ +#include +#include + +class Meeting; +class Room; + +class TestMeeting: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void testPrimaryId(); + void testSecondaryId(); + void testGetAndSetOrganizer(); + void testStartsAt(); + void testEndsAt(); + void testSubject(); + void testDescription(); + void testDetailsAvailable(); + void testSetSecondaryId(); + void testSetStartsAt(); + void testSetEndsAt(); + void testSetSubject(); + void testSetDescription(); + void testEquals(); + void testToString(); + void cleanupTestCase(); + +private: + Meeting *iMeeting1, *iMeeting2; + + int iPrimaryId1, iPrimaryId2; + int iSecondaryId1, iSecondaryId2; + Room *iRoom; + QString iOrganizer1Name, iOrganizer1EMail; + QString iOrganizer2Name, iOrganizer2EMail; + QDateTime iStartsAt1, iStartsAt2; + QDateTime iEndsAt1, iEndsAt2; + QString iSubject1, iSubject2; + QString iDescription1, iDescription2; + +}; diff --git a/tests/Domain/Meeting/TestMeetingOnly.cpp b/tests/Domain/Meeting/TestMeetingOnly.cpp new file mode 100644 index 0000000..5830ec8 --- /dev/null +++ b/tests/Domain/Meeting/TestMeetingOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestMeeting.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestMeeting testMeeting; + QTest::qExec( &testMeeting ); +} diff --git a/tests/Domain/Meeting/TestMeetingOnly.pro b/tests/Domain/Meeting/TestMeetingOnly.pro new file mode 100644 index 0000000..c0cc0bc --- /dev/null +++ b/tests/Domain/Meeting/TestMeetingOnly.pro @@ -0,0 +1,11 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../src/Domain/ +HEADERS += ../../../src/Domain/Meeting.h \ + ../../../src/Domain/Room.h \ + TestMeeting.h +SOURCES += ../../../src/Domain/Meeting.cpp \ + ../../../src/Domain/Room.cpp \ + TestMeeting.cpp \ + TestMeetingOnly.cpp diff --git a/tests/Domain/Room/TestRoom.cpp b/tests/Domain/Room/TestRoom.cpp new file mode 100644 index 0000000..6895896 --- /dev/null +++ b/tests/Domain/Room/TestRoom.cpp @@ -0,0 +1,71 @@ +#include + +#include "Room.h" +#include "TestRoom.h" + +void TestRoom::initTestCase() +{ + iName1 = "myName"; + iName2 = "otherName"; + iAddress1 = "myAddress"; + iAddress2 = "otherAddress"; + + iRoom1 = new Room( iName1, iAddress1 ); + iRoom2 = new Room( iName1, iAddress2 ); + iRoom3 = new Room( iName2, iAddress1 ); + iRoom4 = new Room( iName2, iAddress2 ); + + QVERIFY( iRoom1 != 0 ); + QVERIFY( iRoom2 != 0 ); + QVERIFY( iRoom2 != 0 ); + QVERIFY( iRoom4 != 0 ); +} + +void TestRoom::cleanupTestCase() +{ + delete iRoom1; + iRoom1 = 0; + delete iRoom2; + iRoom2 = 0; + delete iRoom2; + iRoom2 = 0; + delete iRoom4; + iRoom4 = 0; +} + +void TestRoom::init() +{ + //qDebug("Debug line"); +} + +void TestRoom::cleanup() +{ + //nothing to do +} + +void TestRoom::testName() +{ + QCOMPARE( iRoom1->name(), iName1 ); +} + +void TestRoom::testAddress() +{ + QCOMPARE( iRoom1->address(), iAddress1 ); +} + +void TestRoom::testEquals() +{ + QCOMPARE( iRoom1->equals( iRoom1 ), true ); + QCOMPARE( iRoom1->equals( iRoom2 ), false ); + QCOMPARE( iRoom1->equals( iRoom3 ), false ); + QCOMPARE( iRoom1->equals( iRoom4 ), false ); +} + +void TestRoom::testToString() +{ + QString room1ToString = QString( "[Room: name:%1 address:%2 ]" ) + .arg( iName1 ) + .arg( iAddress1 ); + + QCOMPARE( iRoom1->toString(), room1ToString ); +} diff --git a/tests/Domain/Room/TestRoom.h b/tests/Domain/Room/TestRoom.h new file mode 100644 index 0000000..5e5b373 --- /dev/null +++ b/tests/Domain/Room/TestRoom.h @@ -0,0 +1,50 @@ +#include + +#include "Room.h" + +class TestRoom: public QObject +{ + Q_OBJECT + +private slots: + /*! + * \function initTestCase is executed before testcase + */ + void initTestCase(); + /*! + * \function init is executed before individual test + */ + void init(); + /*! + * + * \function cleanup is executed after individual test + */ + void cleanup(); + /*! + * test for name + */ + void testName(); + /*! + * test for address + */ + void testAddress(); + /*! + * test for equals + */ + void testEquals(); + /*! + * test for toString + */ + void testToString(); + /*! + * \function cleanupTestCase is executed after testcase + */ + void cleanupTestCase(); + +private: + Room *iRoom1, *iRoom2, *iRoom3, *iRoom4; + + QString iName1, iName2; + QString iAddress1, iAddress2; + +}; diff --git a/tests/Domain/Room/TestRoomOnly.cpp b/tests/Domain/Room/TestRoomOnly.cpp new file mode 100644 index 0000000..f8f97dd --- /dev/null +++ b/tests/Domain/Room/TestRoomOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestRoom.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestRoom testRoom; + QTest::qExec( &testRoom ); +} diff --git a/tests/Domain/Room/TestRoomOnly.pro b/tests/Domain/Room/TestRoomOnly.pro new file mode 100644 index 0000000..935522a --- /dev/null +++ b/tests/Domain/Room/TestRoomOnly.pro @@ -0,0 +1,10 @@ + QT += testlib + TEMPLATE = app + CONFIG += qtestlib + INCLUDEPATH += ../../../src/Domain/ + HEADERS += ../../../src/Domain/Room.h \ + TestRoom.h + SOURCES += ../../../src/Domain/Room.cpp \ + TestRoom.cpp \ + TestRoomOnly.cpp + diff --git a/tests/IO/Communication/TestCommunication.cpp b/tests/IO/Communication/TestCommunication.cpp new file mode 100644 index 0000000..df94925 --- /dev/null +++ b/tests/IO/Communication/TestCommunication.cpp @@ -0,0 +1,100 @@ +#include +#include "Configuration.h" +#include "ConnectionSettings.h" +#include "TestCommunication.h" +#include "Communication.h" +#include "MessagingUtils.h" +#include +#include + +static const QString server = "192.168.0.35"; +static const QString user = "maemo"; +static const QString pass = "P@ssw0rd"; +static const unsigned int refresh = 1; + +void TestCommunication::initTestCase() +{ + ConnectionSettings *conn = new ConnectionSettings( server, user, pass, refresh ); + QVERIFY( conn != 0 ); + iComm = new Communication( *conn ); + QVERIFY( iComm != 0 ); + delete conn; + conn = NULL; + connect( iComm, + SIGNAL( requestFinished( int, QHttp::Error ) ), + this, + SLOT( requestFinished( int, QHttp::Error ) ) + ); +} + +void TestCommunication::cleanupTestCase() +{ + delete iComm; +} + +void TestCommunication::init() { } + +void TestCommunication::cleanup() { } + +void TestCommunication::testPublicSignals() +{ + QSignalSpy rp( iComm, SIGNAL(readProgress( int, int, int )) ); + QCOMPARE( rp.isValid(), true ); + QSignalSpy rs( iComm, SIGNAL(requestStarted( int )) ); + QCOMPARE( rs.isValid(), true ); + qRegisterMetaType("QHttp::Error"); + QSignalSpy rf( iComm, SIGNAL(requestFinished( int, QHttp::Error )) ); + QCOMPARE( rf.isValid(), true ); + + iRequestId = iComm->request( "", QString("").toAscii() ); + for(int i=0; iRequestId != 0 && i < 5000/250; i++ ) + QTest::qWait( 250 ); + + //verify requestStarted + QCOMPARE( rs.count(), 1 ); + QList args = rs.takeFirst(); + QCOMPARE( args.at(0).type(), QVariant::Int ); + + //verify requestFinished + QCOMPARE( rf.count(), 1 ); + args = rf.takeFirst(); + QCOMPARE( args.at(0).type(), QVariant::Int ); + QCOMPARE( args.at(1).typeName(), "QHttp::Error" ); + + //verify readProgress + QCOMPARE( rp.count(), 1 ); + args = rp.takeFirst(); + QCOMPARE( args.at(0).type(), QVariant::Int ); + QCOMPARE( args.at(1).type(), QVariant::Int ); + QCOMPARE( args.at(2).type(), QVariant::Int ); +} + +void TestCommunication::testRequestAndResponse() +{ + const QString command = "text/xml; charset=utf-8; action=\"http://schemas.microsoft.com/exchange/services/2006/messages/GetUserAvailability\""; + QFile file("input.xml"); + if(!file.open(QIODevice::ReadOnly)) + { + QFAIL( "Error opening input.xml" ); + } + QByteArray content = file.readAll(); + + iRequestId = iComm->request( command, content ); + int id_fail = iComm->request( "", QString("").toAscii() ); + QVERIFY( iRequestId != 0 ); + QCOMPARE( id_fail, 0 ); + for(int i=0; iRequestId != 0 && i < 5000/250; i++ ) + QTest::qWait( 250 ); + const QByteArray& response = iComm->response( 0 ); + QVERIFY( response.contains("GetUserAvailabilityResponse") ); + iRequestId = iComm->request( "", QString("").toAscii() ); + QVERIFY( iRequestId != 0 ); + QVERIFY( response.isEmpty() ); +} + +void TestCommunication::requestFinished( int aRequestId, QHttp::Error /*aError*/ ) +{ + QCOMPARE( aRequestId, iRequestId ); + iRequestId = 0; +} + diff --git a/tests/IO/Communication/TestCommunication.h b/tests/IO/Communication/TestCommunication.h new file mode 100644 index 0000000..1fd9e4d --- /dev/null +++ b/tests/IO/Communication/TestCommunication.h @@ -0,0 +1,21 @@ +#include +#include "Communication.h" +#include "ConnectionSettings.h" + +class TestCommunication: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + void testPublicSignals(); + void testRequestAndResponse(); + void requestFinished( int aRequestId, QHttp::Error aError ); + +private: + Communication* iComm; + int iRequestId; +}; diff --git a/tests/IO/Communication/TestCommunicationOnly.cpp b/tests/IO/Communication/TestCommunicationOnly.cpp new file mode 100644 index 0000000..c2be501 --- /dev/null +++ b/tests/IO/Communication/TestCommunicationOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestCommunication.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestCommunication test; + QTest::qExec( &test ); +} diff --git a/tests/IO/Communication/TestCommunicationOnly.pro b/tests/IO/Communication/TestCommunicationOnly.pro new file mode 100644 index 0000000..7def6c5 --- /dev/null +++ b/tests/IO/Communication/TestCommunicationOnly.pro @@ -0,0 +1,13 @@ +QT += testlib xml network +TEMPLATE = app +CONFIG += qtestlib debug +INCLUDEPATH += ../../../src/Domain/ \ + ../../../src/Domain/Configuration/ \ + ../../../src/IO/Communication/ +HEADERS += ../../../src/Domain/Configuration/ConnectionSettings.h \ + TestCommunication.h \ + ../../../src/IO/Communication/Communication.h +SOURCES += ../../../src/Domain/Configuration/ConnectionSettings.cpp \ + ../../../src/IO/Communication/Communication.cpp \ + TestCommunication.cpp \ + TestCommunicationOnly.cpp diff --git a/tests/IO/Communication/input.xml b/tests/IO/Communication/input.xml new file mode 100644 index 0000000..3fb8cfd --- /dev/null +++ b/tests/IO/Communication/input.xml @@ -0,0 +1,40 @@ + + + + + 120 + + 0 + + 23 + 4 + Thursday + + + -60 + + 23 + 4 + Thursday + + + + + + meetingroomtest@test.local + + Room + false + + + + + 2009-04-20T00:00:00 + 2009-04-24T23:59:59 + + 60 + DetailedMerged + + + + diff --git a/tests/IO/CommunicationManager/TestCommunicationManager.cpp b/tests/IO/CommunicationManager/TestCommunicationManager.cpp new file mode 100644 index 0000000..a339d43 --- /dev/null +++ b/tests/IO/CommunicationManager/TestCommunicationManager.cpp @@ -0,0 +1,151 @@ +#include +#include "Configuration.h" +#include "ConnectionSettings.h" +#include "TestCommunicationManager.h" +#include "Communication.h" +#include "Room.h" +#include "Meeting.h" +#include +#include +#include + +static const QString server = "192.168.0.35"; +static const QString user = "maemo"; +static const QString pass = "P@ssw0rd"; +static const unsigned int refresh = 1; + +void TestCommunicationManager::initTestCase() +{ + ConnectionSettings *conn = new ConnectionSettings( server, user, pass, refresh ); + QVERIFY( conn != 0 ); + iMgr = new CommunicationManager( *conn ); + QVERIFY( iMgr != 0 ); + delete conn; + conn = NULL; +} + +void TestCommunicationManager::cleanupTestCase() +{ + delete iMgr; +} + +void TestCommunicationManager::init() { } + +void TestCommunicationManager::cleanup() { } + +void TestCommunicationManager::testErrorSignal() +{ + ConnectionSettings *conn = new ConnectionSettings( QString(""), user, pass, refresh ); + CommunicationManager *mgr = new CommunicationManager( *conn ); + delete conn; + + QDateTime start = QDateTime::currentDateTime(); + QDateTime end = start.addDays(14); + Room room( "Room name","Room address" ); + + connect( mgr, + SIGNAL( error( int, CommunicationManager::CommunicationType ) ), + this, + SLOT( handleError(int, CommunicationManager::CommunicationType ) ) + ); + + iError = 0; + mgr->fetchMeetings( start, end, room ); + for(int i=0; iError == 0 && i < 5000/250; i++ ) + QTest::qWait( 250 ); + + if( iError == 0 ) + { + QFAIL( "error signal not emitted" ); + } + + delete mgr; +} + +void TestCommunicationManager::testFetchMeetings() +{ + qRegisterMetaType("CommunicationManager::CommunicationType"); + QSignalSpy rp( iMgr, SIGNAL(readProgress( int, int, CommunicationManager::CommunicationType )) ); + QCOMPARE( rp.isValid(), true ); + + connect( iMgr, + SIGNAL( meetingsFetched( const QList& ) ), + this, + SLOT( meetingsFetched( const QList& ) ) + ); + connect( iMgr, + SIGNAL( error( int, CommunicationManager::CommunicationType ) ), + this, + SLOT( handleError(int, CommunicationManager::CommunicationType ) ) + ); + QDateTime start = QDateTime::fromString( QString("04.05.2009"), QString("dd.MM.yyyy") ); + QDateTime end = QDateTime::fromString( QString("09.05.2009"), QString("dd.MM.yyyy") ); + Room room( "TestRoom", "meetingroomtest@test.local" ); + iMgr->fetchMeetings( start, end, room ); + iError = 0; + for(int i=0; iError == 0 && i < 5000/250; i++ ) + QTest::qWait( 250 ); + + if( iError == 0 ) + { + QFAIL("signal meetingsFetched not emitted"); + } + + QVERIFY( iMeetings.count() > 0 ); + for( int i=0; i < iMeetings.count(); i++) + { + Meeting *m = iMeetings[i]; + qDebug() << m->toString(); + } + + //verify readProgress + QCOMPARE( rp.count(), 1 ); + QList args = rp.takeFirst(); + QCOMPARE( args.at(0).type(), QVariant::Int ); + QCOMPARE( args.at(1).type(), QVariant::Int ); + QCOMPARE( args.at(2).typeName(), "CommunicationManager::CommunicationType" ); +} + +void TestCommunicationManager::testFetchMeetingDetails() +{ + connect( iMgr, + SIGNAL( meetingDetailsFetched( Meeting& ) ), + this, + SLOT( meetingDetailsFetched( Meeting& ) ) + ); + Meeting* m = iMeetings[0]; + QVERIFY( m->secondaryId().isEmpty() ); +// QVERIFY( !m->primaryId().isEmpty() ); + iMgr->fetchMeetingDetails( *m ); + iError = 0; + for(int i=0; iError == 0 && i < 5000/250; i++ ) + QTest::qWait( 250 ); + + if( iError == 0 ) + { + QFAIL("signal meetingDetailsFetched not emitted"); + } + QVERIFY( !m->secondaryId().isEmpty() ); + QVERIFY( m->detailsAvailable() ); + qDebug() << m->toString(); +} + +void TestCommunicationManager::handleError( int aCode, CommunicationManager::CommunicationType aType ) +{ + QWARN("TestCommunicationManager::handleError"); + QCOMPARE( aType, CommunicationManager::FetchingCommunication ); + QVERIFY( aCode != 0 ); + iError = aCode; +} + +void TestCommunicationManager::meetingsFetched( const QList &aMeetings ) +{ + iMeetings = aMeetings; + iError = 1; +} + +void TestCommunicationManager::meetingDetailsFetched( Meeting &aDetailedMeeting ) +{ + QCOMPARE( &aDetailedMeeting, iMeetings[0] ); + iError = 1; +} diff --git a/tests/IO/CommunicationManager/TestCommunicationManager.h b/tests/IO/CommunicationManager/TestCommunicationManager.h new file mode 100644 index 0000000..e7f607d --- /dev/null +++ b/tests/IO/CommunicationManager/TestCommunicationManager.h @@ -0,0 +1,26 @@ +#include +#include "Communication.h" +#include "ConnectionSettings.h" +#include "CommunicationManager.h" + +class TestCommunicationManager: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanup(); + void cleanupTestCase(); + void testErrorSignal(); + void testFetchMeetings(); + void testFetchMeetingDetails(); + void meetingsFetched( const QList &aMeetings ); + void meetingDetailsFetched( Meeting &aDetailedMeeting ); + void handleError( int aCode, CommunicationManager::CommunicationType aType ); + +private: + CommunicationManager* iMgr; + int iError; + QList iMeetings; +}; diff --git a/tests/IO/CommunicationManager/TestCommunicationManagerOnly.cpp b/tests/IO/CommunicationManager/TestCommunicationManagerOnly.cpp new file mode 100644 index 0000000..e6297a1 --- /dev/null +++ b/tests/IO/CommunicationManager/TestCommunicationManagerOnly.cpp @@ -0,0 +1,11 @@ +#include +#include +#include "TestCommunicationManager.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestCommunicationManager test; + QTest::qExec( &test ); +} diff --git a/tests/IO/CommunicationManager/TestCommunicationManagerOnly.pro b/tests/IO/CommunicationManager/TestCommunicationManagerOnly.pro new file mode 100644 index 0000000..2612b2e --- /dev/null +++ b/tests/IO/CommunicationManager/TestCommunicationManagerOnly.pro @@ -0,0 +1,21 @@ +QT += testlib xml network +TEMPLATE = app +CONFIG += qtestlib debug +INCLUDEPATH += ../../../src/Domain/ \ + ../../../src/Domain/Configuration/ \ + ../../../src/IO/Communication/ +HEADERS += ../../../src/Domain/Configuration/ConnectionSettings.h \ + TestCommunicationManager.h \ + ../../../src/IO/Communication/Communication.h \ + ../../../src/IO/Communication/CommunicationManager.h \ + ../../../src/Domain/Meeting.h \ + ../../../src/Domain/Room.h \ + ../../../src/IO/Communication/MessagingUtils.h +SOURCES += ../../../src/Domain/Configuration/ConnectionSettings.cpp \ + ../../../src/IO/Communication/Communication.cpp \ + ../../../src/IO/Communication/CommunicationManager.cpp \ + ../../../src/Domain/Meeting.cpp \ + ../../../src/Domain/Room.cpp \ + ../../../src/IO/Communication/MessagingUtils.cpp \ + TestCommunicationManager.cpp \ + TestCommunicationManagerOnly.cpp diff --git a/tests/QtMeetings.conf b/tests/QtMeetings.conf new file mode 100644 index 0000000..5025781 --- /dev/null +++ b/tests/QtMeetings.conf @@ -0,0 +1,45 @@ + + + + + jklexch01.ixonos.com + username + password--> + + 65 + + + + + + + + + + + + + + ddd dd MMM + hh:mm + + + + + Pegasus +

meetingroom.pegasus_jyv@ixonos.com
+ + + Taurus +
meetingroom.taurus_jyv@ixonos.com
+
+ + Hercules +
meetingroom.hercules@ixonos.com
+
+ + + + + + diff --git a/tests/TestQtMeetings.cpp b/tests/TestQtMeetings.cpp new file mode 100644 index 0000000..7cbc36c --- /dev/null +++ b/tests/TestQtMeetings.cpp @@ -0,0 +1,69 @@ +#include +#include + +// Domain +#include "TestRoom.h" +#include "TestMeeting.h" +#include "TestConnectionSettings.h" +#include "TestDisplaySettings.h" +#include "TestStartupSettings.h" +#include "TestConfiguration.h" + +// Communication +// TODO : includes here + +// DeviceControl +// TODO : includes here + +// BusinessLogic +#include "TestClock.h" +#include "TestErrorMapper.h" +#include "TestEngine.h" + +// UserInterface +#include "TestMeetingRoomCombo.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + /************ Domain ************/ + TestRoom testRoom; + QTest::qExec( &testRoom ); + + TestMeeting testMeeting; + QTest::qExec( &testMeeting ); + + TestConnectionSettings testConnectionSettings; + QTest::qExec( &testConnectionSettings ); + + TestDisplaySettings testDisplaySettings; + QTest::qExec( &testDisplaySettings ); + + TestStartupSettings testStartupSettings; + QTest::qExec( &testStartupSettings ); + + TestConfiguration testConfiguration; + QTest::qExec( &testConfiguration ); + + /************ Communication ************/ + // TODO : tests here + + /************ DeviceControl ************/ + // TODO : tests here + + /************ BusinessLogic ************/ + TestClock testClock; + QTest::qExec( &testClock ); + + TestErrorMapper testErrorMapper; + QTest::qExec( &testErrorMapper ); + + TestEngine testEngine; + // TODO : TestEngine ends up in Segmentation fault for some reason if run together with other tests. Individually everything goes OK. +// QTest::qExec( &testEngine ); + + /************ UserInterface ************/ + TestMeetingRoomCombo testMeetingRoomCombo; + QTest::qExec( &testMeetingRoomCombo ); +} diff --git a/tests/TestQtMeetings.pro b/tests/TestQtMeetings.pro new file mode 100644 index 0000000..4f6f51b --- /dev/null +++ b/tests/TestQtMeetings.pro @@ -0,0 +1,107 @@ +QT += testlib \ + xml \ + network +TEMPLATE = app +CONFIG += qtestlib +CONFIG += link_pkgconfig +PKGCONFIG += libalarm + +INCLUDEPATH += ../src/Domain/ \ + ../src/Domain/Configuration/ \ + ../src/IO/ \ + ../src/IO/Communication/ \ + ../src/IO/DeviceControl/ \ + ../src/BusinessLogic/ \ + ../src/BusinessLogic/Utils/ \ + ../src/UserInterface/ \ + ../src/UserInterface/Components/ \ + ../src/UserInterface/Utils/ \ + ../src/UserInterface/Views/ \ + Domain/Room/ \ + Domain/Meeting/ \ + Domain/Configuration/ConnectionSettings/ \ + Domain/Configuration/DisplaySettings/ \ + Domain/Configuration/StartupSettings/ \ + Domain/Configuration/Configuration/ \ + BusinessLogic/Utils/Clock/ \ + BusinessLogic/Utils/ErrorMapper/ \ + BusinessLogic/Engine/ \ + UserInterface/Components/MeetingRoomCombo/ + +HEADERS += ../src/Domain/Room.h \ + ../src/Domain/Meeting.h \ + ../src/Domain/Configuration/ConnectionSettings.h \ + ../src/Domain/Configuration/StartupSettings.h \ + ../src/Domain/Configuration/DisplaySettings.h \ + ../src/Domain/Configuration/Configuration.h \ + ../src/IO/Communication/Communication.h \ + ../src/IO/Communication/CommunicationManager.h \ + ../src/IO/DeviceControl/AlarmSender.h \ + ../src/IO/DeviceControl/HWKeyListener.h \ + ../src/IO/DeviceControl/DeviceDataStorage.h \ + ../src/IO/DeviceControl/DeviceManager.h \ + ../src/BusinessLogic/Utils/ErrorMapper.h \ + ../src/BusinessLogic/Utils/Clock.h \ + ../src/BusinessLogic/Engine.h \ + ../src/UserInterface/Utils/ToolBox.h \ + ../src/UserInterface/Utils/PopUpMessageBox.h \ + ../src/UserInterface/Components/ObservedWidget.h \ + ../src/UserInterface/Components/TimeDisplayWidget.h \ + ../src/UserInterface/Components/DigitalTimeDisplayWidget.h \ + ../src/UserInterface/Components/MeetingRoomCombo.h \ + ../src/UserInterface/Components/ScheduleWidget.h \ + ../src/UserInterface/Views/RoomStatusIndicatorWidget.h \ + ../src/UserInterface/Views/WeeklyViewWidget.h \ + ../src/UserInterface/Views/MeetingInfoDialog.h \ + ../src/UserInterface/WindowManager.h \ + Domain/Room/TestRoom.h \ + Domain/Meeting/TestMeeting.h \ + Domain/Configuration/ConnectionSettings/TestConnectionSettings.h \ + Domain/Configuration/DisplaySettings/TestDisplaySettings.h \ + Domain/Configuration/StartupSettings/TestStartupSettings.h \ + Domain/Configuration/Configuration/TestConfiguration.h \ + BusinessLogic/Utils/Clock/TestClock.h \ + BusinessLogic/Utils/ErrorMapper/TestErrorMapper.h \ + BusinessLogic/Engine/TestEngine.h \ + UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.h + +SOURCES += ../src/Domain/Room.cpp \ + ../src/Domain/Meeting.cpp \ + ../src/Domain/Configuration/ConnectionSettings.cpp \ + ../src/Domain/Configuration/StartupSettings.cpp \ + ../src/Domain/Configuration/DisplaySettings.cpp \ + ../src/Domain/Configuration/Configuration.cpp \ + ../src/IO/Communication/Communication.cpp \ + ../src/IO/Communication/CommunicationManager.cpp \ + ../src/IO/DeviceControl/AlarmSender.cpp \ + ../src/IO/DeviceControl/HWKeyListener.cpp \ + ../src/IO/DeviceControl/DeviceDataStorage.cpp \ + ../src/IO/DeviceControl/DeviceManager.cpp \ + ../src/BusinessLogic/Utils/ErrorMapper.cpp \ + ../src/BusinessLogic/Utils/Clock.cpp \ + ../src/BusinessLogic/Engine.cpp \ + ../src/UserInterface/Utils/ToolBox.cpp \ + ../src/UserInterface/Utils/PopUpMessageBox.cpp \ + ../src/UserInterface/Components/ObservedWidget.cpp \ + ../src/UserInterface/Components/TimeDisplayWidget.cpp \ + ../src/UserInterface/Components/DigitalTimeDisplayWidget.cpp \ + ../src/UserInterface/Components/MeetingRoomCombo.cpp \ + ../src/UserInterface/Components/ScheduleWidget.cpp \ + ../src/UserInterface/Views/RoomStatusIndicatorWidget.cpp \ + ../src/UserInterface/Views/WeeklyViewWidget.cpp \ + ../src/UserInterface/Views/MeetingInfoDialog.cpp \ + ../src/UserInterface/WindowManager.cpp \ + Domain/Room/TestRoom.cpp \ + Domain/Meeting/TestMeeting.cpp \ + Domain/Configuration/ConnectionSettings/TestConnectionSettings.cpp \ + Domain/Configuration/DisplaySettings/TestDisplaySettings.cpp \ + Domain/Configuration/StartupSettings/TestStartupSettings.cpp \ + Domain/Configuration/Configuration/TestConfiguration.cpp \ + BusinessLogic/Utils/Clock/TestClock.cpp \ + BusinessLogic/Utils/ErrorMapper/TestErrorMapper.cpp \ + BusinessLogic/Engine/TestEngine.cpp \ + UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.cpp \ + TestQtMeetings.cpp + +RESOURCES += ../resources/BusinessLogic.qrc \ + ../resources/UserInterface.qrc diff --git a/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.cpp b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.cpp new file mode 100644 index 0000000..3a37d6f --- /dev/null +++ b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.cpp @@ -0,0 +1,101 @@ +#include +#include "MeetingRoomCombo.h" +#include "Room.h" +#include "TestMeetingRoomCombo.h" +#include + +void TestMeetingRoomCombo::initTestCase() +{ + iRooms.append( new Room( "Hercules", "meetingroom.hercules@ixonos.com" ) ); + iRooms.append( new Room( "Pegasus", "meetingroom.pegasus_jyv@ixonos.com" ) ); + iRooms.append( new Room( "Taurus", "meetingroom.taurus_jyv@ixonos.com" ) ); + + iCombo = new MeetingRoomCombo( iRooms ); + QVERIFY( iCombo != 0 ); +} + +void TestMeetingRoomCombo::cleanupTestCase() +{ + delete iCombo; + iCombo = NULL; +} + +void TestMeetingRoomCombo::init() +{ +} + +void TestMeetingRoomCombo::cleanup() +{ +} + +void TestMeetingRoomCombo::testCount() +{ + QCOMPARE( iCombo->count(), iRooms.count() ); +} + +void TestMeetingRoomCombo::testCurrentIndex() +{ + iCombo->setCurrentIndex( 0 ); + QCOMPARE( iCombo->currentIndex(), 0 ); + + iCombo->setCurrentIndex( -10 ); + QCOMPARE( iCombo->currentIndex(), -1 ); + + iCombo->setCurrentIndex( 10 ); + QCOMPARE( iCombo->currentIndex(), -1 ); + + iCombo->setCurrentRoom( iRooms.at( 1 ) ); + QCOMPARE( iCombo->currentIndex(), 1 ); +} + +void TestMeetingRoomCombo::testCurrentRoom() +{ + iCombo->setCurrentRoom( iRooms.at( 1 ) ); + QCOMPARE( iCombo->currentRoom()->equals( iRooms.at( 1 ) ), true ); + QCOMPARE( iCombo->currentRoom()->equals( iRooms.at( 0 ) ), false ); +} + +void TestMeetingRoomCombo::testSetCurrentIndex() +{ + for ( int i = 0; i < iRooms.count(); i++ ) + { + iCombo->setCurrentIndex( i ); + QCOMPARE( iCombo->currentIndex(), i ); + } +} + +void TestMeetingRoomCombo::testSetCurrentRoom() +{ + for ( int i = 0; i < iRooms.count(); i++ ) + { + iCombo->setCurrentRoom( iRooms[i] ); + QCOMPARE( iCombo->currentRoom(), iRooms[i] ); + } +} + +void TestMeetingRoomCombo::testSetCurrentRoomBy() +{ + iCombo->setCurrentRoomBy( iRooms.at( 0 )->name() ); + QCOMPARE( iCombo->currentIndex(), 0 ); + + iCombo->setCurrentRoomBy( iRooms.at( 2 )->name() ); + QCOMPARE( iCombo->currentIndex(), 2 ); + + iCombo->setCurrentRoomBy( "WRONG NAME" ); + QCOMPARE( iCombo->currentIndex(), -1 ); +} + +void TestMeetingRoomCombo::testFindRoom() +{ + for ( int i = 0; i < iRooms.count(); i++ ) + { + QCOMPARE( iCombo->findRoom( iRooms[i] ), i ); + } +} + +void TestMeetingRoomCombo::testFindRoomBy() +{ + QCOMPARE( iCombo->findRoomBy( iRooms.at( 0 )->name() ), 0 ); + QCOMPARE( iCombo->findRoomBy( iRooms.at( 1 )->name() ), 1 ); + QCOMPARE( iCombo->findRoomBy( "WRONG NAME" ), -1 ); +} diff --git a/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.h b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.h new file mode 100644 index 0000000..6372270 --- /dev/null +++ b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomCombo.h @@ -0,0 +1,27 @@ +#include +#include "MeetingRoomCombo.h" +#include "Room.h" + +class TestMeetingRoomCombo: public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void init(); + void cleanup(); + + void testCount(); + void testCurrentIndex(); + void testCurrentRoom(); + void testSetCurrentIndex(); + void testSetCurrentRoom(); + void testSetCurrentRoomBy(); + void testFindRoom(); + void testFindRoomBy(); + void cleanupTestCase(); + +private: + MeetingRoomCombo* iCombo; + QList iRooms; +}; diff --git a/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.cpp b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.cpp new file mode 100644 index 0000000..835f70c --- /dev/null +++ b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.cpp @@ -0,0 +1,12 @@ +#include +#include + +#include "TestMeetingRoomCombo.h" + +int main( int argc, char *argv[] ) +{ + QApplication app( argc, argv ); + + TestMeetingRoomCombo testMeetingRoomCombo; + QTest::qExec( &testMeetingRoomCombo ); +} diff --git a/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.pro b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.pro new file mode 100644 index 0000000..bd1148d --- /dev/null +++ b/tests/UserInterface/Components/MeetingRoomCombo/TestMeetingRoomComboOnly.pro @@ -0,0 +1,14 @@ +QT += testlib +TEMPLATE = app +CONFIG += qtestlib +INCLUDEPATH += ../../../../src/UserInterface/Components/ \ + ../../../../src/Domain/ +HEADERS += ../../../../src/UserInterface/Components/MeetingRoomCombo.h \ + ../../../../src/UserInterface/Components/ObservedWidget.h \ + ../../../../src/Domain/Room.h \ + TestMeetingRoomCombo.h +SOURCES += ../../../../src/UserInterface/Components/MeetingRoomCombo.cpp \ + ../../../../src/UserInterface/Components/ObservedWidget.cpp \ + ../../../../src/Domain/Room.cpp \ + TestMeetingRoomCombo.cpp \ + TestMeetingRoomComboOnly.cpp diff --git a/welcome b/welcome deleted file mode 100644 index 6bb1d85..0000000 --- a/welcome +++ /dev/null @@ -1 +0,0 @@ -welcome qtmeetings -- 1.7.9.5