From 3081d7de2dd639665fbdea602c6138ab18b63d35 Mon Sep 17 00:00:00 2001 From: 0xFEEDC0DE64 <0xFEEDC0DE64@gmail.com> Date: Mon, 17 Sep 2018 19:51:36 +0200 Subject: [PATCH] Imported existing sources --- .gitmodules | 33 + DbZeiterfassung.pro | 10 + plugins/absenceplugin | 1 + plugins/advancedviewplugin | 1 + plugins/devtoolsplugin | 1 + plugins/lunchmealplugin | 1 + plugins/plugin.pri | 5 + plugins/plugins.pro | 15 + plugins/presenceplugin | 1 + plugins/profileplugin | 1 + plugins/reportsplugin | 1 + plugins/sketchplugin | 1 + plugins/updaterplugin | 1 + plugins/weatherplugin | 1 + plugins/webradioplugin | 1 + win32/Qt.conf | 0 zeiterfassung/icon.ico | Bin 0 -> 9662 bytes zeiterfassung/images/splash.png | Bin 0 -> 43571 bytes zeiterfassung/installs.pri | 52 + zeiterfassung/main.cpp | 379 +++++ zeiterfassung/strips/bookingendstrip.ui | 103 ++ zeiterfassung/strips/bookingstartstrip.ui | 93 ++ zeiterfassung/strips/timeassignmentstrip.ui | 147 ++ zeiterfassung/themes/dark_theme.qss | 1297 +++++++++++++++++ .../themes/dark_theme/Hmovetoolbar.png | Bin 0 -> 220 bytes .../themes/dark_theme/Hsepartoolbar.png | Bin 0 -> 172 bytes .../themes/dark_theme/Vmovetoolbar.png | Bin 0 -> 228 bytes .../themes/dark_theme/Vsepartoolbar.png | Bin 0 -> 187 bytes .../themes/dark_theme/branch_closed-on.png | Bin 0 -> 147 bytes .../themes/dark_theme/branch_closed.png | Bin 0 -> 160 bytes .../themes/dark_theme/branch_open-on.png | Bin 0 -> 150 bytes .../themes/dark_theme/branch_open.png | Bin 0 -> 166 bytes .../themes/dark_theme/checkbox_checked.png | Bin 0 -> 492 bytes .../dark_theme/checkbox_checked_disabled.png | Bin 0 -> 491 bytes .../dark_theme/checkbox_checked_focus.png | Bin 0 -> 252 bytes .../dark_theme/checkbox_indeterminate.png | Bin 0 -> 493 bytes .../checkbox_indeterminate_disabled.png | Bin 0 -> 492 bytes .../checkbox_indeterminate_focus.png | Bin 0 -> 249 bytes .../themes/dark_theme/checkbox_unchecked.png | Bin 0 -> 464 bytes .../checkbox_unchecked_disabled.png | Bin 0 -> 464 bytes .../dark_theme/checkbox_unchecked_focus.png | Bin 0 -> 240 bytes .../themes/dark_theme/close-hover.png | Bin 0 -> 598 bytes .../themes/dark_theme/close-pressed.png | Bin 0 -> 598 bytes zeiterfassung/themes/dark_theme/close.png | Bin 0 -> 586 bytes .../themes/dark_theme/down_arrow.png | Bin 0 -> 165 bytes .../themes/dark_theme/down_arrow_disabled.png | Bin 0 -> 166 bytes .../themes/dark_theme/left_arrow.png | Bin 0 -> 166 bytes .../themes/dark_theme/left_arrow_disabled.png | Bin 0 -> 166 bytes .../themes/dark_theme/radio_checked.png | Bin 0 -> 940 bytes .../dark_theme/radio_checked_disabled.png | Bin 0 -> 972 bytes .../themes/dark_theme/radio_checked_focus.png | Bin 0 -> 846 bytes .../themes/dark_theme/radio_unchecked.png | Bin 0 -> 728 bytes .../dark_theme/radio_unchecked_disabled.png | Bin 0 -> 760 bytes .../dark_theme/radio_unchecked_focus.png | Bin 0 -> 646 bytes .../themes/dark_theme/right_arrow.png | Bin 0 -> 160 bytes .../dark_theme/right_arrow_disabled.png | Bin 0 -> 160 bytes zeiterfassung/themes/dark_theme/sizegrip.png | Bin 0 -> 129 bytes .../dark_theme/stylesheet-branch-end.png | Bin 0 -> 224 bytes .../dark_theme/stylesheet-branch-more.png | Bin 0 -> 182 bytes .../themes/dark_theme/stylesheet-vline.png | Bin 0 -> 239 bytes .../themes/dark_theme/transparent.png | Bin 0 -> 195 bytes zeiterfassung/themes/dark_theme/undock.png | Bin 0 -> 578 bytes zeiterfassung/themes/dark_theme/up_arrow.png | Bin 0 -> 158 bytes .../themes/dark_theme/up_arrow_disabled.png | Bin 0 -> 159 bytes .../translations/zeiterfassung_de.ts | 134 ++ .../translations/zeiterfassung_en.ts | 134 ++ zeiterfassung/zeiterfassung.pro | 25 + zeiterfassung/zeiterfassung_resources.qrc | 5 + zeiterfassungcorelib/cpp14polyfills.h | 43 + .../replies/createbookingreply.cpp | 26 + .../replies/createbookingreply.h | 22 + .../replies/createtimeassignmentreply.cpp | 64 + .../replies/createtimeassignmentreply.h | 25 + .../replies/deletebookingreply.cpp | 27 + .../replies/deletebookingreply.h | 22 + .../replies/deletetimeassignmentreply.cpp | 27 + .../replies/deletetimeassignmentreply.h | 22 + .../replies/getabsencesreply.cpp | 77 + .../replies/getabsencesreply.h | 43 + .../replies/getbookingsreply.cpp | 73 + .../replies/getbookingsreply.h | 39 + .../replies/getdayinforeply.cpp | 74 + .../replies/getdayinforeply.h | 40 + .../replies/getpresencestatusreply.cpp | 71 + .../replies/getpresencestatusreply.h | 35 + .../replies/getprojectsreply.cpp | 87 ++ .../replies/getprojectsreply.h | 34 + .../replies/getreportreply.cpp | 51 + zeiterfassungcorelib/replies/getreportreply.h | 27 + .../replies/gettimeassignmentsreply.cpp | 78 + .../replies/gettimeassignmentsreply.h | 42 + .../replies/getuserinforeply.cpp | 170 +++ .../replies/getuserinforeply.h | 53 + .../replies/loginpagereply.cpp | 34 + zeiterfassungcorelib/replies/loginpagereply.h | 22 + zeiterfassungcorelib/replies/loginreply.cpp | 54 + zeiterfassungcorelib/replies/loginreply.h | 22 + .../replies/updatebookingreply.cpp | 59 + .../replies/updatebookingreply.h | 23 + .../replies/updatetimeassignmentreply.cpp | 64 + .../replies/updatetimeassignmentreply.h | 25 + .../replies/zeiterfassungreply.cpp | 62 + .../replies/zeiterfassungreply.h | 38 + zeiterfassungcorelib/timeutils.cpp | 23 + zeiterfassungcorelib/timeutils.h | 10 + .../translations/zeiterfassungcorelib_de.ts | 295 ++++ .../translations/zeiterfassungcorelib_en.ts | 295 ++++ zeiterfassungcorelib/zeiterfassungapi.cpp | 324 ++++ zeiterfassungcorelib/zeiterfassungapi.h | 78 + zeiterfassungcorelib/zeiterfassungcorelib.pro | 63 + .../zeiterfassungcorelib_global.h | 9 + .../zeiterfassungsettings.cpp | 311 ++++ zeiterfassungcorelib/zeiterfassungsettings.h | 93 ++ .../dialogs/authenticationdialog.cpp | 34 + .../dialogs/authenticationdialog.h | 24 + .../dialogs/authenticationdialog.ui | 144 ++ .../dialogs/languageselectiondialog.cpp | 27 + .../dialogs/languageselectiondialog.h | 23 + .../dialogs/languageselectiondialog.ui | 103 ++ .../dialogs/settingsdialog.cpp | 145 ++ zeiterfassungguilib/dialogs/settingsdialog.h | 28 + zeiterfassungguilib/dialogs/settingsdialog.ui | 113 ++ zeiterfassungguilib/images/about.png | Bin 0 -> 5513 bytes zeiterfassungguilib/images/authentication.png | Bin 0 -> 6495 bytes zeiterfassungguilib/images/help.png | Bin 0 -> 4971 bytes zeiterfassungguilib/images/icon.png | Bin 0 -> 3660 bytes zeiterfassungguilib/images/next.png | Bin 0 -> 4079 bytes zeiterfassungguilib/images/next_week.png | Bin 0 -> 5573 bytes zeiterfassungguilib/images/now.png | Bin 0 -> 7213 bytes zeiterfassungguilib/images/previous.png | Bin 0 -> 4201 bytes zeiterfassungguilib/images/previous_week.png | Bin 0 -> 5569 bytes zeiterfassungguilib/images/qt.png | Bin 0 -> 2384 bytes zeiterfassungguilib/images/quit.png | Bin 0 -> 3837 bytes zeiterfassungguilib/images/refresh.png | Bin 0 -> 6914 bytes zeiterfassungguilib/images/settings.png | Bin 0 -> 6050 bytes zeiterfassungguilib/images/today.png | Bin 0 -> 5070 bytes zeiterfassungguilib/images/user.png | Bin 0 -> 5577 bytes zeiterfassungguilib/mainwindow.cpp | 472 ++++++ zeiterfassungguilib/mainwindow.h | 88 ++ zeiterfassungguilib/mainwindow.ui | 370 +++++ zeiterfassungguilib/settingswidget.cpp | 6 + zeiterfassungguilib/settingswidget.h | 19 + zeiterfassungguilib/stripfactory.cpp | 107 ++ zeiterfassungguilib/stripfactory.h | 35 + zeiterfassungguilib/stripswidget.cpp | 676 +++++++++ zeiterfassungguilib/stripswidget.h | 120 ++ .../translations/zeiterfassungguilib_de.ts | 443 ++++++ .../translations/zeiterfassungguilib_en.ts | 443 ++++++ zeiterfassungguilib/zeiterfassungdialog.cpp | 13 + zeiterfassungguilib/zeiterfassungdialog.h | 13 + zeiterfassungguilib/zeiterfassungguilib.pro | 42 + .../zeiterfassungguilib_global.h | 9 + .../zeiterfassungguilib_resources.qrc | 19 + zeiterfassungguilib/zeiterfassungplugin.cpp | 13 + zeiterfassungguilib/zeiterfassungplugin.h | 24 + 155 files changed, 9172 insertions(+) create mode 100644 .gitmodules create mode 100644 DbZeiterfassung.pro create mode 160000 plugins/absenceplugin create mode 160000 plugins/advancedviewplugin create mode 160000 plugins/devtoolsplugin create mode 160000 plugins/lunchmealplugin create mode 100644 plugins/plugin.pri create mode 100644 plugins/plugins.pro create mode 160000 plugins/presenceplugin create mode 160000 plugins/profileplugin create mode 160000 plugins/reportsplugin create mode 160000 plugins/sketchplugin create mode 160000 plugins/updaterplugin create mode 160000 plugins/weatherplugin create mode 160000 plugins/webradioplugin create mode 100644 win32/Qt.conf create mode 100644 zeiterfassung/icon.ico create mode 100644 zeiterfassung/images/splash.png create mode 100644 zeiterfassung/installs.pri create mode 100755 zeiterfassung/main.cpp create mode 100644 zeiterfassung/strips/bookingendstrip.ui create mode 100644 zeiterfassung/strips/bookingstartstrip.ui create mode 100644 zeiterfassung/strips/timeassignmentstrip.ui create mode 100644 zeiterfassung/themes/dark_theme.qss create mode 100644 zeiterfassung/themes/dark_theme/Hmovetoolbar.png create mode 100644 zeiterfassung/themes/dark_theme/Hsepartoolbar.png create mode 100644 zeiterfassung/themes/dark_theme/Vmovetoolbar.png create mode 100644 zeiterfassung/themes/dark_theme/Vsepartoolbar.png create mode 100644 zeiterfassung/themes/dark_theme/branch_closed-on.png create mode 100644 zeiterfassung/themes/dark_theme/branch_closed.png create mode 100644 zeiterfassung/themes/dark_theme/branch_open-on.png create mode 100644 zeiterfassung/themes/dark_theme/branch_open.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_checked.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_checked_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_checked_focus.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_indeterminate.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_indeterminate_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_indeterminate_focus.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_unchecked.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_unchecked_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/checkbox_unchecked_focus.png create mode 100644 zeiterfassung/themes/dark_theme/close-hover.png create mode 100644 zeiterfassung/themes/dark_theme/close-pressed.png create mode 100644 zeiterfassung/themes/dark_theme/close.png create mode 100644 zeiterfassung/themes/dark_theme/down_arrow.png create mode 100644 zeiterfassung/themes/dark_theme/down_arrow_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/left_arrow.png create mode 100644 zeiterfassung/themes/dark_theme/left_arrow_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/radio_checked.png create mode 100644 zeiterfassung/themes/dark_theme/radio_checked_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/radio_checked_focus.png create mode 100644 zeiterfassung/themes/dark_theme/radio_unchecked.png create mode 100644 zeiterfassung/themes/dark_theme/radio_unchecked_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/radio_unchecked_focus.png create mode 100644 zeiterfassung/themes/dark_theme/right_arrow.png create mode 100644 zeiterfassung/themes/dark_theme/right_arrow_disabled.png create mode 100644 zeiterfassung/themes/dark_theme/sizegrip.png create mode 100644 zeiterfassung/themes/dark_theme/stylesheet-branch-end.png create mode 100644 zeiterfassung/themes/dark_theme/stylesheet-branch-more.png create mode 100644 zeiterfassung/themes/dark_theme/stylesheet-vline.png create mode 100644 zeiterfassung/themes/dark_theme/transparent.png create mode 100644 zeiterfassung/themes/dark_theme/undock.png create mode 100644 zeiterfassung/themes/dark_theme/up_arrow.png create mode 100644 zeiterfassung/themes/dark_theme/up_arrow_disabled.png create mode 100644 zeiterfassung/translations/zeiterfassung_de.ts create mode 100644 zeiterfassung/translations/zeiterfassung_en.ts create mode 100755 zeiterfassung/zeiterfassung.pro create mode 100644 zeiterfassung/zeiterfassung_resources.qrc create mode 100644 zeiterfassungcorelib/cpp14polyfills.h create mode 100644 zeiterfassungcorelib/replies/createbookingreply.cpp create mode 100644 zeiterfassungcorelib/replies/createbookingreply.h create mode 100644 zeiterfassungcorelib/replies/createtimeassignmentreply.cpp create mode 100644 zeiterfassungcorelib/replies/createtimeassignmentreply.h create mode 100644 zeiterfassungcorelib/replies/deletebookingreply.cpp create mode 100644 zeiterfassungcorelib/replies/deletebookingreply.h create mode 100644 zeiterfassungcorelib/replies/deletetimeassignmentreply.cpp create mode 100644 zeiterfassungcorelib/replies/deletetimeassignmentreply.h create mode 100644 zeiterfassungcorelib/replies/getabsencesreply.cpp create mode 100644 zeiterfassungcorelib/replies/getabsencesreply.h create mode 100644 zeiterfassungcorelib/replies/getbookingsreply.cpp create mode 100644 zeiterfassungcorelib/replies/getbookingsreply.h create mode 100644 zeiterfassungcorelib/replies/getdayinforeply.cpp create mode 100644 zeiterfassungcorelib/replies/getdayinforeply.h create mode 100644 zeiterfassungcorelib/replies/getpresencestatusreply.cpp create mode 100644 zeiterfassungcorelib/replies/getpresencestatusreply.h create mode 100644 zeiterfassungcorelib/replies/getprojectsreply.cpp create mode 100644 zeiterfassungcorelib/replies/getprojectsreply.h create mode 100644 zeiterfassungcorelib/replies/getreportreply.cpp create mode 100644 zeiterfassungcorelib/replies/getreportreply.h create mode 100644 zeiterfassungcorelib/replies/gettimeassignmentsreply.cpp create mode 100644 zeiterfassungcorelib/replies/gettimeassignmentsreply.h create mode 100644 zeiterfassungcorelib/replies/getuserinforeply.cpp create mode 100644 zeiterfassungcorelib/replies/getuserinforeply.h create mode 100644 zeiterfassungcorelib/replies/loginpagereply.cpp create mode 100644 zeiterfassungcorelib/replies/loginpagereply.h create mode 100644 zeiterfassungcorelib/replies/loginreply.cpp create mode 100644 zeiterfassungcorelib/replies/loginreply.h create mode 100644 zeiterfassungcorelib/replies/updatebookingreply.cpp create mode 100644 zeiterfassungcorelib/replies/updatebookingreply.h create mode 100644 zeiterfassungcorelib/replies/updatetimeassignmentreply.cpp create mode 100644 zeiterfassungcorelib/replies/updatetimeassignmentreply.h create mode 100644 zeiterfassungcorelib/replies/zeiterfassungreply.cpp create mode 100644 zeiterfassungcorelib/replies/zeiterfassungreply.h create mode 100644 zeiterfassungcorelib/timeutils.cpp create mode 100644 zeiterfassungcorelib/timeutils.h create mode 100644 zeiterfassungcorelib/translations/zeiterfassungcorelib_de.ts create mode 100644 zeiterfassungcorelib/translations/zeiterfassungcorelib_en.ts create mode 100644 zeiterfassungcorelib/zeiterfassungapi.cpp create mode 100644 zeiterfassungcorelib/zeiterfassungapi.h create mode 100644 zeiterfassungcorelib/zeiterfassungcorelib.pro create mode 100644 zeiterfassungcorelib/zeiterfassungcorelib_global.h create mode 100644 zeiterfassungcorelib/zeiterfassungsettings.cpp create mode 100644 zeiterfassungcorelib/zeiterfassungsettings.h create mode 100644 zeiterfassungguilib/dialogs/authenticationdialog.cpp create mode 100644 zeiterfassungguilib/dialogs/authenticationdialog.h create mode 100644 zeiterfassungguilib/dialogs/authenticationdialog.ui create mode 100644 zeiterfassungguilib/dialogs/languageselectiondialog.cpp create mode 100644 zeiterfassungguilib/dialogs/languageselectiondialog.h create mode 100644 zeiterfassungguilib/dialogs/languageselectiondialog.ui create mode 100644 zeiterfassungguilib/dialogs/settingsdialog.cpp create mode 100644 zeiterfassungguilib/dialogs/settingsdialog.h create mode 100644 zeiterfassungguilib/dialogs/settingsdialog.ui create mode 100644 zeiterfassungguilib/images/about.png create mode 100644 zeiterfassungguilib/images/authentication.png create mode 100644 zeiterfassungguilib/images/help.png create mode 100644 zeiterfassungguilib/images/icon.png create mode 100644 zeiterfassungguilib/images/next.png create mode 100644 zeiterfassungguilib/images/next_week.png create mode 100644 zeiterfassungguilib/images/now.png create mode 100644 zeiterfassungguilib/images/previous.png create mode 100644 zeiterfassungguilib/images/previous_week.png create mode 100644 zeiterfassungguilib/images/qt.png create mode 100644 zeiterfassungguilib/images/quit.png create mode 100644 zeiterfassungguilib/images/refresh.png create mode 100644 zeiterfassungguilib/images/settings.png create mode 100644 zeiterfassungguilib/images/today.png create mode 100644 zeiterfassungguilib/images/user.png create mode 100644 zeiterfassungguilib/mainwindow.cpp create mode 100644 zeiterfassungguilib/mainwindow.h create mode 100644 zeiterfassungguilib/mainwindow.ui create mode 100644 zeiterfassungguilib/settingswidget.cpp create mode 100644 zeiterfassungguilib/settingswidget.h create mode 100644 zeiterfassungguilib/stripfactory.cpp create mode 100644 zeiterfassungguilib/stripfactory.h create mode 100644 zeiterfassungguilib/stripswidget.cpp create mode 100644 zeiterfassungguilib/stripswidget.h create mode 100644 zeiterfassungguilib/translations/zeiterfassungguilib_de.ts create mode 100644 zeiterfassungguilib/translations/zeiterfassungguilib_en.ts create mode 100644 zeiterfassungguilib/zeiterfassungdialog.cpp create mode 100644 zeiterfassungguilib/zeiterfassungdialog.h create mode 100644 zeiterfassungguilib/zeiterfassungguilib.pro create mode 100644 zeiterfassungguilib/zeiterfassungguilib_global.h create mode 100644 zeiterfassungguilib/zeiterfassungguilib_resources.qrc create mode 100644 zeiterfassungguilib/zeiterfassungplugin.cpp create mode 100644 zeiterfassungguilib/zeiterfassungplugin.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..362a663 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,33 @@ +[submodule "plugins/absenceplugin"] + path = plugins/absenceplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-absenceplugin.git +[submodule "plugins/advancedviewplugin"] + path = plugins/advancedviewplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-advancedviewplugin.git +[submodule "plugins/devtoolsplugin"] + path = plugins/devtoolsplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-devtoolsplugin.git +[submodule "plugins/lunchmealplugin"] + path = plugins/lunchmealplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-lunchmealplugin.git +[submodule "plugins/presenceplugin"] + path = plugins/presenceplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-presenceplugin.git +[submodule "plugins/profileplugin"] + path = plugins/profileplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-profileplugin.git +[submodule "plugins/reportsplugin"] + path = plugins/reportsplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-reportsplugin.git +[submodule "plugins/sketchplugin"] + path = plugins/sketchplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-sketchplugin.git +[submodule "plugins/updaterplugin"] + path = plugins/updaterplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-updaterplugin.git +[submodule "plugins/weatherplugin"] + path = plugins/weatherplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-weatherplugin.git +[submodule "plugins/webradioplugin"] + path = plugins/webradioplugin + url = https://github.com/0xFEEDC0DE64/DbZeiterfassung-webradioplugin.git diff --git a/DbZeiterfassung.pro b/DbZeiterfassung.pro new file mode 100644 index 0000000..549a7d4 --- /dev/null +++ b/DbZeiterfassung.pro @@ -0,0 +1,10 @@ +TEMPLATE = subdirs + +SUBDIRS += plugins \ + zeiterfassung \ + zeiterfassungcorelib \ + zeiterfassungguilib + +plugins.depends += zeiterfassungcorelib zeiterfassungguilib +zeiterfassung.depends += zeiterfassungcorelib zeiterfassungguilib +zeiterfassungguilib.depends += zeiterfassungcorelib diff --git a/plugins/absenceplugin b/plugins/absenceplugin new file mode 160000 index 0000000..af02fa7 --- /dev/null +++ b/plugins/absenceplugin @@ -0,0 +1 @@ +Subproject commit af02fa79fda2251b9a9ee410c2493345ddf242a2 diff --git a/plugins/advancedviewplugin b/plugins/advancedviewplugin new file mode 160000 index 0000000..4ff069d --- /dev/null +++ b/plugins/advancedviewplugin @@ -0,0 +1 @@ +Subproject commit 4ff069de1ba7fb0ce03b1d171f9cde432d7470b8 diff --git a/plugins/devtoolsplugin b/plugins/devtoolsplugin new file mode 160000 index 0000000..04b8ade --- /dev/null +++ b/plugins/devtoolsplugin @@ -0,0 +1 @@ +Subproject commit 04b8ade09e1a77e3d0eb71c827a1b54d9ba4d61a diff --git a/plugins/lunchmealplugin b/plugins/lunchmealplugin new file mode 160000 index 0000000..6c48560 --- /dev/null +++ b/plugins/lunchmealplugin @@ -0,0 +1 @@ +Subproject commit 6c4856072478956173b429671f90ef13c72f5ad3 diff --git a/plugins/plugin.pri b/plugins/plugin.pri new file mode 100644 index 0000000..dcedc42 --- /dev/null +++ b/plugins/plugin.pri @@ -0,0 +1,5 @@ +PROJECT_ROOT = ../../.. +TEMPLATE = lib +CONFIG += shared +DESTDIR = $${OUT_PWD}/$${PROJECT_ROOT}/bin/plugins/zeiterfassung +include(../../project.pri) diff --git a/plugins/plugins.pro b/plugins/plugins.pro new file mode 100644 index 0000000..8e24668 --- /dev/null +++ b/plugins/plugins.pro @@ -0,0 +1,15 @@ +TEMPLATE = subdirs + +SUBDIRS += absenceplugin \ + advancedviewplugin \ + devtoolsplugin \ + lunchmealplugin \ + profileplugin \ + presenceplugin \ + reportsplugin \ + sketchplugin \ + updaterplugin \ + weatherplugin \ + webradioplugin + +OTHER_FILES += plugin.pri diff --git a/plugins/presenceplugin b/plugins/presenceplugin new file mode 160000 index 0000000..cb64211 --- /dev/null +++ b/plugins/presenceplugin @@ -0,0 +1 @@ +Subproject commit cb64211fcd50ef6692a228198811ddbd368ff194 diff --git a/plugins/profileplugin b/plugins/profileplugin new file mode 160000 index 0000000..c1f1b88 --- /dev/null +++ b/plugins/profileplugin @@ -0,0 +1 @@ +Subproject commit c1f1b884ff743b64a0b18b3b4d8eb3339190524d diff --git a/plugins/reportsplugin b/plugins/reportsplugin new file mode 160000 index 0000000..b24ac9e --- /dev/null +++ b/plugins/reportsplugin @@ -0,0 +1 @@ +Subproject commit b24ac9e00a7fe26acdfbfda1e1d19e12099eaae6 diff --git a/plugins/sketchplugin b/plugins/sketchplugin new file mode 160000 index 0000000..30fad70 --- /dev/null +++ b/plugins/sketchplugin @@ -0,0 +1 @@ +Subproject commit 30fad70d72ded1bc5573bed5a4df17647558fb13 diff --git a/plugins/updaterplugin b/plugins/updaterplugin new file mode 160000 index 0000000..26da75b --- /dev/null +++ b/plugins/updaterplugin @@ -0,0 +1 @@ +Subproject commit 26da75bf63e3405f87a1887b0ece4553dc295bf4 diff --git a/plugins/weatherplugin b/plugins/weatherplugin new file mode 160000 index 0000000..1b5a756 --- /dev/null +++ b/plugins/weatherplugin @@ -0,0 +1 @@ +Subproject commit 1b5a7563f685dcb5d61f3521a0fb934fdf3747e4 diff --git a/plugins/webradioplugin b/plugins/webradioplugin new file mode 160000 index 0000000..967b138 --- /dev/null +++ b/plugins/webradioplugin @@ -0,0 +1 @@ +Subproject commit 967b13871c31d967f85fdcdc52be34226919a36d diff --git a/win32/Qt.conf b/win32/Qt.conf new file mode 100644 index 0000000..e69de29 diff --git a/zeiterfassung/icon.ico b/zeiterfassung/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6c882cad6b5635b1e14744c717b63e2630efb2b3 GIT binary patch literal 9662 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 literal 0 HcmV?d00001 diff --git a/zeiterfassung/images/splash.png b/zeiterfassung/images/splash.png new file mode 100644 index 0000000000000000000000000000000000000000..7140cd2a5c6eebebc5cace9321e55415a4bc8955 GIT binary patch literal 43571 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 z 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungsettings.h" +#include "dialogs/languageselectiondialog.h" +#include "zeiterfassungapi.h" +#include "dialogs/authenticationdialog.h" +#include "zeiterfassungplugin.h" +#include "mainwindow.h" +#include "replies/loginpagereply.h" +#include "replies/loginreply.h" +#include "replies/getuserinforeply.h" +#include "stripfactory.h" + +struct { + QTranslator qtTranslator; + QTranslator zeiterfassungTranslator; + QTranslator zeiterfassungcorelibTranslator; + QTranslator zeiterfassungguilibTranslator; +} translators; + +QSet plugins; + +bool loadAndInstallTranslator(QTranslator &translator, const QString &filename) +{ + static auto dir = QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("translations")); + + if(!translator.load(QLocale(), filename, QStringLiteral("_"), dir)) + { + qWarning() << "could not load translation" << filename; + return false; + } + + if(!QCoreApplication::installTranslator(&translator)) + { + qWarning() << "could not install translation" << filename; + return false; + } + + return true; +} + +bool loadTranslations(QSplashScreen &splashScreen, ZeiterfassungSettings &settings) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Loading translations..."), Qt::AlignHCenter | Qt::AlignBottom); + + if(settings.language() == QLocale::AnyLanguage) + { + LanguageSelectionDialog dialog(&splashScreen); + + again: + if(dialog.exec() != QDialog::Accepted) + return false; + + if(dialog.language() == QLocale::AnyLanguage) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Invalid language selection!"), + QCoreApplication::translate("main", "Invalid language selection!") % "\n\n" % + QCoreApplication::translate("main", "You did not select a valid language!")); + goto again; + } + + settings.setLanguage(dialog.language()); + } + + QLocale::setDefault(QLocale(settings.language(), QLocale::Austria)); + + loadAndInstallTranslator(translators.qtTranslator, QStringLiteral("qt")); + loadAndInstallTranslator(translators.zeiterfassungTranslator, QStringLiteral("zeiterfassung")); + loadAndInstallTranslator(translators.zeiterfassungcorelibTranslator, QStringLiteral("zeiterfassungcorelib")); + loadAndInstallTranslator(translators.zeiterfassungguilibTranslator, QStringLiteral("zeiterfassungguilib")); + + return true; +} + +bool loadTheme(QSplashScreen &splashScreen, ZeiterfassungSettings &settings) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Loading theme..."), Qt::AlignHCenter | Qt::AlignBottom); + + if(settings.theme().isEmpty()) + return true; + + auto themePath = QDir(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("themes"))).absoluteFilePath(settings.theme()); + + QFile file(themePath % ".qss"); + + if(!file.exists()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load theme!"), + QCoreApplication::translate("main", "Could not load theme!") % "\n\n" % + QCoreApplication::translate("main", "Theme file does not exist!")); + return false; + } + + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load theme!"), + QCoreApplication::translate("main", "Could not load theme!") % "\n\n" % + file.errorString()); + return false; + } + + QTextStream textStream(&file); + qApp->setStyleSheet(textStream.readAll().replace(QStringLiteral("@THEME_RESOURCES@"), themePath)); + + return true; +} + +bool loadStripLayouts(QSplashScreen &splashScreen, StripFactory &stripFactory) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Loading strip layouts..."), Qt::AlignHCenter | Qt::AlignBottom); + + if(!stripFactory.load(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("strips")))) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load strips!"), + QCoreApplication::translate("main", "Could not load strips!") % "\n\n" % stripFactory.errorString()); + return false; + } + + { + auto widget = stripFactory.createBookingStartStrip(); + if(!widget) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load strips!"), + QCoreApplication::translate("main", "Could not load strips!") % "\n\n" % stripFactory.errorString()); + return false; + } + } + + { + auto widget = stripFactory.createBookingEndStrip(); + if(!widget) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load strips!"), + QCoreApplication::translate("main", "Could not load strips!") % "\n\n" % stripFactory.errorString()); + return false; + } + } + + { + auto widget = stripFactory.createTimeAssignmentStrip(); + if(!widget) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load strips!"), + QCoreApplication::translate("main", "Could not load strips!") % "\n\n" % stripFactory.errorString()); + return false; + } + } + + return true; +} + +bool loadLoginPage(QSplashScreen &splashScreen, ZeiterfassungSettings &settings, ZeiterfassungApi &erfassung) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Loading login page..."), Qt::AlignHCenter | Qt::AlignBottom); + + again: + auto reply = erfassung.doLoginPage(); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not access Zeiterfassung!"), + QCoreApplication::translate("main", "Could not access Zeiterfassung!") % "\n\n" % reply->message()); + + inputAgain: + bool ok; + auto text = QInputDialog::getText(&splashScreen, QCoreApplication::translate("main", "Base url"), + QCoreApplication::translate("main", "Please enter the base url to the Zeiterfassung:"), + QLineEdit::Normal, settings.url().toString(), &ok); + + if(!ok) + return false; + + auto url = QUrl::fromUserInput(text); + if(!url.isValid()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Invalid url!"), + QCoreApplication::translate("main", "This url is not valid!")); + goto inputAgain; + } + + settings.setUrl(url); + erfassung.setUrl(url); + + goto again; + } + + return true; +} + +bool doAuthentication(QSplashScreen &splashScreen, ZeiterfassungSettings &settings, ZeiterfassungApi &erfassung) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Authenticating..."), Qt::AlignHCenter | Qt::AlignBottom); + + if(settings.username().isNull() || settings.password().isNull()) + { + AuthenticationDialog dialog(&splashScreen); + + if(dialog.exec() != QDialog::Accepted) + return false; + + settings.setUsername(dialog.username()); + settings.setPassword(dialog.password()); + } + + { + again: + auto reply = erfassung.doLogin(settings.username(), settings.password()); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not authenticate with Zeiterfassung!"), + QCoreApplication::translate("main", "Could not authenticate with Zeiterfassung!") % "\n\n" % reply->message()); + + AuthenticationDialog dialog(&splashScreen); + dialog.setUsername(settings.username()); + dialog.setPassword(settings.password()); + + if(dialog.exec() != QDialog::Accepted) + return false; + + settings.setUsername(dialog.username()); + settings.setPassword(dialog.password()); + + goto again; + } + } + + return true; +} + +bool loadUserInfo(QSplashScreen &splashScreen, ZeiterfassungApi &erfassung, GetUserInfoReply::UserInfo &userInfo) +{ + splashScreen.showMessage(QCoreApplication::translate("main", "Getting user information..."), Qt::AlignHCenter | Qt::AlignBottom); + + { + auto reply = erfassung.doUserInfo(); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not get user information!"), + QCoreApplication::translate("main", "Could not get user information!") % "\n\n" % reply->message()); + return false; + } + + userInfo = reply->userInfo(); + } + + return true; +} + +bool loadPlugins(QSplashScreen &splashScreen) +{ + auto ok = true; + + QDir dir( + QDir( + QDir( + QCoreApplication::applicationDirPath() + ).absoluteFilePath(QStringLiteral("plugins")) + ).absoluteFilePath(QStringLiteral("zeiterfassung")) + ); + + for(const auto &fileInfo : dir.entryInfoList(QDir::Files | QDir::NoSymLinks)) + { + if(!QLibrary::isLibrary(fileInfo.filePath())) + { + qWarning() << "skipping" << fileInfo.fileName() << "because no QLibrary"; + continue; // to skip windows junk files + } + + QPluginLoader pluginLoader(fileInfo.filePath()); + if(!pluginLoader.load()) + { + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Could not load plugin %0!").arg(fileInfo.fileName()), + QCoreApplication::translate("main", "Could not load plugin %0!").arg(fileInfo.fileName()) % + "\n\n" % pluginLoader.errorString()); + ok = false; + continue; + } + + if(auto plugin = qobject_cast(pluginLoader.instance())) + plugins.insert(plugin); + else + QMessageBox::warning(&splashScreen, QCoreApplication::translate("main", "Plugin not valid %0!"), + QCoreApplication::translate("main", "Plugin not valid %0!").arg(pluginLoader.fileName()) % + "\n\n" % pluginLoader.errorString()); + + } + + return ok; +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + + qSetMessagePattern(QStringLiteral("%{time dd.MM.yyyy HH:mm:ss.zzz} " + "[" + "%{if-debug}D%{endif}" + "%{if-info}I%{endif}" + "%{if-warning}W%{endif}" + "%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}" + "] " + "%{function}(): " + "%{message}")); + + QCoreApplication::setOrganizationDomain(QStringLiteral("brunner.ninja")); + QCoreApplication::setOrganizationName(QStringLiteral("db-software")); + QCoreApplication::setApplicationName(QStringLiteral("zeiterfassung")); + QCoreApplication::setApplicationVersion(QStringLiteral("1.5")); + + QSplashScreen splashScreen(QPixmap(QStringLiteral(":/zeiterfassung/images/splash.png"))); + splashScreen.showMessage(QCoreApplication::translate("main", "Loading settings..."), Qt::AlignHCenter | Qt::AlignBottom); + splashScreen.show(); + + ZeiterfassungSettings settings(&app); + + if(!loadTranslations(splashScreen, settings)) + return -1; + + // not critical if it fails + //if(!loadTheme(splashScreen, settings)) + // return -2; + loadTheme(splashScreen, settings); + + StripFactory stripFactory(&app); + + if(!loadStripLayouts(splashScreen, stripFactory)) + return -3; + + ZeiterfassungApi erfassung(settings.url().toString(), &app); + + if(!loadLoginPage(splashScreen, settings, erfassung)) + return -4; + + if(!doAuthentication(splashScreen, settings, erfassung)) + return -5; + + GetUserInfoReply::UserInfo userInfo; + + if(!loadUserInfo(splashScreen, erfassung, userInfo)) + return -6; + + loadPlugins(splashScreen); + + MainWindow mainWindow(settings, erfassung, userInfo, stripFactory, plugins); + splashScreen.finish(&mainWindow); + + for(auto &plugin : plugins) + plugin->attachTo(mainWindow); + + mainWindow.show(); + + return app.exec(); +} diff --git a/zeiterfassung/strips/bookingendstrip.ui b/zeiterfassung/strips/bookingendstrip.ui new file mode 100644 index 0000000..e901bb5 --- /dev/null +++ b/zeiterfassung/strips/bookingendstrip.ui @@ -0,0 +1,103 @@ + + + bookingEndStrip + + + + 0 + 0 + 500 + 44 + + + + QLabel { color: black; } #bookingEndStrip { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #FF7F7F, stop:1 #BF6F6F); } + + + QFrame::WinPanel + + + QFrame::Raised + + + + 10 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 12 + 75 + true + + + + QLabel { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #EEEEEE, stop:1 #BBBBBB); } + + + QFrame::WinPanel + + + QFrame::Sunken + + + 9:99 + + + + + + + + 12 + + + + background-color: rgba(0,0,0,30); + + + END + + + + + + + background-color: rgba(0,0,0,30); + + + 9:99h + + + + + + + background-color: rgba(0,0,0,30); + + + 0123456 + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + diff --git a/zeiterfassung/strips/bookingstartstrip.ui b/zeiterfassung/strips/bookingstartstrip.ui new file mode 100644 index 0000000..1d7851c --- /dev/null +++ b/zeiterfassung/strips/bookingstartstrip.ui @@ -0,0 +1,93 @@ + + + bookingStartStrip + + + + 0 + 0 + 500 + 44 + + + + QLabel { color: black; } #bookingStartStrip { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #7FFF7F, stop:1 #6FBF6F); } + + + QFrame::WinPanel + + + QFrame::Raised + + + + 10 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 12 + 75 + true + + + + QLabel { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #EEEEEE, stop:1 #BBBBBB); } + + + QFrame::WinPanel + + + QFrame::Sunken + + + 9:99 + + + + + + + + 12 + + + + background-color: rgba(0,0,0,30); + + + START + + + + + + + background-color: rgba(0,0,0,30); + + + 0123456 + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + diff --git a/zeiterfassung/strips/timeassignmentstrip.ui b/zeiterfassung/strips/timeassignmentstrip.ui new file mode 100644 index 0000000..e64d910 --- /dev/null +++ b/zeiterfassung/strips/timeassignmentstrip.ui @@ -0,0 +1,147 @@ + + + timeAssignmentStrip + + + + 0 + 0 + 500 + 73 + + + + QLabel { color: black; } #timeAssignmentStrip { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #7FFFFF, stop:1 #6FBFBF); } + + + QFrame::WinPanel + + + QFrame::Raised + + + + 3 + + + 5 + + + 5 + + + 5 + + + 5 + + + + + 10 + + + + + + 12 + 75 + true + + + + QLabel { background-color: qlineargradient( x1:0 y1:0, x2:0 y2:1, stop:0 #EEEEEE, stop:1 #BBBBBB); } + + + QFrame::WinPanel + + + QFrame::Sunken + + + 9:99 + + + + + + + + 8 + + + + background-color: rgba(0,0,0,30); + + + PROJECT 1 +12345 + + + + + + + background-color: rgba(0,0,0,30); + + + 0123456 + + + Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing + + + + + + + + + + + + 80 + 0 + + + + background-color: rgba(0,0,0,30); + + + Subproject + + + + + + + + 80 + 0 + + + + background-color: rgba(0,0,0,30); + + + Workpackage + + + + + + + background-color: rgba(0,0,0,30); + + + Text + + + + + + + + + + diff --git a/zeiterfassung/themes/dark_theme.qss b/zeiterfassung/themes/dark_theme.qss new file mode 100644 index 0000000..102aafb --- /dev/null +++ b/zeiterfassung/themes/dark_theme.qss @@ -0,0 +1,1297 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) <2013-2014> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +QToolTip +{ + border: 1px solid #76797C; + background-color: rgb(90, 102, 117);; + color: white; + padding: 5px; + opacity: 200; +} + +QWidget +{ + color: #eff0f1; + background-color: #31363b; + selection-background-color:#3daee9; + selection-color: #eff0f1; + background-clip: border; + border-image: none; + border: 0px transparent black; + outline: 0; +} + +QWidget:item:hover +{ + background-color: #18465d; + color: #eff0f1; +} + +QWidget:item:selected +{ + background-color: #18465d; +} + +QCheckBox +{ + spacing: 5px; + outline: none; + color: #eff0f1; + margin-bottom: 2px; +} + +QCheckBox:disabled +{ + color: #76797C; +} + +QCheckBox::indicator, +QGroupBox::indicator +{ + width: 18px; + height: 18px; +} +QGroupBox::indicator +{ + margin-left: 2px; +} + +QCheckBox::indicator:unchecked +{ + image: url(@THEME_RESOURCES@/checkbox_unchecked.png); +} + +QCheckBox::indicator:unchecked:hover, +QCheckBox::indicator:unchecked:focus, +QCheckBox::indicator:unchecked:pressed, +QGroupBox::indicator:unchecked:hover, +QGroupBox::indicator:unchecked:focus, +QGroupBox::indicator:unchecked:pressed +{ + border: none; + image: url(@THEME_RESOURCES@/checkbox_unchecked_focus.png); +} + +QCheckBox::indicator:checked +{ + image: url(@THEME_RESOURCES@/checkbox_checked.png); +} + +QCheckBox::indicator:checked:hover, +QCheckBox::indicator:checked:focus, +QCheckBox::indicator:checked:pressed, +QGroupBox::indicator:checked:hover, +QGroupBox::indicator:checked:focus, +QGroupBox::indicator:checked:pressed +{ + border: none; + image: url(@THEME_RESOURCES@/checkbox_checked_focus.png); +} + + +QCheckBox::indicator:indeterminate +{ + image: url(@THEME_RESOURCES@/checkbox_indeterminate.png); +} + +QCheckBox::indicator:indeterminate:focus, +QCheckBox::indicator:indeterminate:hover, +QCheckBox::indicator:indeterminate:pressed +{ + image: url(@THEME_RESOURCES@/checkbox_indeterminate_focus.png); +} + +QCheckBox::indicator:checked:disabled, +QGroupBox::indicator:checked:disabled +{ + image: url(@THEME_RESOURCES@/checkbox_checked_disabled.png); +} + +QCheckBox::indicator:unchecked:disabled, +QGroupBox::indicator:unchecked:disabled +{ + image: url(@THEME_RESOURCES@/checkbox_unchecked_disabled.png); +} + +QRadioButton +{ + spacing: 5px; + outline: none; + color: #eff0f1; + margin-bottom: 2px; +} + +QRadioButton:disabled +{ + color: #76797C; +} +QRadioButton::indicator +{ + width: 21px; + height: 21px; +} + +QRadioButton::indicator:unchecked +{ + image: url(@THEME_RESOURCES@/radio_unchecked.png); +} + + +QRadioButton::indicator:unchecked:hover, +QRadioButton::indicator:unchecked:focus, +QRadioButton::indicator:unchecked:pressed +{ + border: none; + outline: none; + image: url(@THEME_RESOURCES@/radio_unchecked_focus.png); +} + +QRadioButton::indicator:checked +{ + border: none; + outline: none; + image: url(@THEME_RESOURCES@/radio_checked.png); +} + +QRadioButton::indicator:checked:hover, +QRadioButton::indicator:checked:focus, +QRadioButton::indicator:checked:pressed +{ + border: none; + outline: none; + image: url(@THEME_RESOURCES@/radio_checked_focus.png); +} + +QRadioButton::indicator:checked:disabled +{ + outline: none; + image: url(@THEME_RESOURCES@/radio_checked_disabled.png); +} + +QRadioButton::indicator:unchecked:disabled +{ + image: url(@THEME_RESOURCES@/radio_unchecked_disabled.png); +} + + +QMenuBar +{ + background-color: #31363b; + color: #eff0f1; +} + +QMenuBar::item +{ + background: transparent; +} + +QMenuBar::item:selected +{ + background: transparent; + border: 1px solid #76797C; +} + +QMenuBar::item:pressed +{ + border: 1px solid #76797C; + background-color: #3daee9; + color: #eff0f1; + margin-bottom:-1px; + padding-bottom:1px; +} + +QMenu +{ + border: 1px solid #76797C; + color: #eff0f1; + margin: 2px; +} + +QMenu::icon +{ + margin: 5px; +} + +QMenu::item +{ + padding: 5px 30px 5px 30px; + border: 1px solid transparent; /* reserve space for selection border */ +} + +QMenu::item:selected +{ + color: #eff0f1; +} + +QMenu::separator { + height: 2px; + background: lightblue; + margin-left: 10px; + margin-right: 5px; +} + +QMenu::indicator { + width: 18px; + height: 18px; +} + +/* non-exclusive indicator = check box style indicator + (see QActionGroup::setExclusive) */ +QMenu::indicator:non-exclusive:unchecked { + image: url(@THEME_RESOURCES@/checkbox_unchecked.png); +} + +QMenu::indicator:non-exclusive:unchecked:selected { + image: url(@THEME_RESOURCES@/checkbox_unchecked_disabled.png); +} + +QMenu::indicator:non-exclusive:checked { + image: url(@THEME_RESOURCES@/checkbox_checked.png); +} + +QMenu::indicator:non-exclusive:checked:selected { + image: url(@THEME_RESOURCES@/checkbox_checked_disabled.png); +} + +/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ +QMenu::indicator:exclusive:unchecked { + image: url(@THEME_RESOURCES@/radio_unchecked.png); +} + +QMenu::indicator:exclusive:unchecked:selected { + image: url(@THEME_RESOURCES@/radio_unchecked_disabled.png); +} + +QMenu::indicator:exclusive:checked { + image: url(@THEME_RESOURCES@/radio_checked.png); +} + +QMenu::indicator:exclusive:checked:selected { + image: url(@THEME_RESOURCES@/radio_checked_disabled.png); +} + +QMenu::right-arrow { + margin: 5px; + image: url(@THEME_RESOURCES@/right_arrow.png) +} + + +QWidget:disabled +{ + color: #454545; + background-color: #31363b; +} + +QAbstractItemView +{ + alternate-background-color: #31363b; + color: #eff0f1; + border: 1px solid 3A3939; + border-radius: 2px; +} + +QWidget:focus, QMenuBar:focus +{ + border: 1px solid #3daee9; +} + +QTabWidget:focus, QCheckBox:focus, QRadioButton:focus, QSlider:focus +{ + border: none; +} + +QLineEdit +{ + background-color: #232629; + padding: 5px; + border-style: solid; + border: 1px solid #76797C; + border-radius: 2px; + color: #eff0f1; +} + +QAbstractItemView QLineEdit +{ + padding: 0; +} + +QGroupBox { + border:1px solid #76797C; + border-radius: 2px; + margin-top: 20px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top center; + padding-left: 10px; + padding-right: 10px; + padding-top: 10px; +} + +QAbstractScrollArea +{ + border-radius: 2px; + border: 1px solid #76797C; + background-color: transparent; +} + +QScrollBar:horizontal +{ + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent #2A2929; + border-radius: 4px; + background-color: #2A2929; +} + +QScrollBar::handle:horizontal +{ + background-color: #605F5F; + min-width: 5px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal +{ + margin: 0px 3px 0px 3px; + border-image: url(@THEME_RESOURCES@/right_arrow_disabled.png); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal +{ + margin: 0px 3px 0px 3px; + border-image: url(@THEME_RESOURCES@/left_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on +{ + border-image: url(@THEME_RESOURCES@/right_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + + +QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on +{ + border-image: url(@THEME_RESOURCES@/left_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal +{ + background: none; +} + + +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal +{ + background: none; +} + +QScrollBar:vertical +{ + background-color: #2A2929; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent #2A2929; + border-radius: 4px; +} + +QScrollBar::handle:vertical +{ + background-color: #605F5F; + min-height: 5px; + border-radius: 4px; +} + +QScrollBar::sub-line:vertical +{ + margin: 3px 0px 3px 0px; + border-image: url(@THEME_RESOURCES@/up_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical +{ + margin: 3px 0px 3px 0px; + border-image: url(@THEME_RESOURCES@/down_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on +{ + + border-image: url(@THEME_RESOURCES@/up_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on +{ + border-image: url(@THEME_RESOURCES@/down_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical +{ + background: none; +} + + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical +{ + background: none; +} + +QTextEdit +{ + background-color: #232629; + color: #eff0f1; + border: 1px solid #76797C; +} + +QPlainTextEdit +{ + background-color: #232629;; + color: #eff0f1; + border-radius: 2px; + border: 1px solid #76797C; +} + +QHeaderView::section +{ + background-color: #76797C; + color: #eff0f1; + padding: 5px; + border: 1px solid #76797C; +} + +QSizeGrip { + image: url(@THEME_RESOURCES@/sizegrip.png); + width: 12px; + height: 12px; +} + + +QMainWindow::separator +{ + background-color: #31363b; + color: white; + padding-left: 4px; + spacing: 2px; + border: 1px dashed #76797C; +} + +QMainWindow::separator:hover +{ + + background-color: #787876; + color: white; + padding-left: 4px; + border: 1px solid #76797C; + spacing: 2px; +} + + +QMenu::separator +{ + height: 1px; + background-color: #76797C; + color: white; + padding-left: 4px; + margin-left: 10px; + margin-right: 5px; +} + + +QFrame +{ + border-radius: 2px; + border: 1px solid #76797C; +} + +QFrame[frameShape="0"] +{ + border-radius: 2px; + border: 1px transparent #76797C; +} + +QStackedWidget +{ + border: 1px transparent black; +} + +QToolBar { + border: 1px transparent #393838; + background: 1px solid #31363b; + font-weight: bold; +} + +QToolBar::handle:horizontal { + image: url(@THEME_RESOURCES@/Hmovetoolbar.png); +} +QToolBar::handle:vertical { + image: url(@THEME_RESOURCES@/Vmovetoolbar.png); +} +QToolBar::separator:horizontal { + image: url(@THEME_RESOURCES@/Hsepartoolbar.png); +} +QToolBar::separator:vertical { + image: url(@THEME_RESOURCES@/Vsepartoolbar.png); +} +QToolButton#qt_toolbar_ext_button { + background: #58595a +} + +QPushButton +{ + color: #eff0f1; + background-color: #31363b; + border-width: 1px; + border-color: #76797C; + border-style: solid; + padding: 5px; + border-radius: 2px; + outline: none; +} + +QPushButton:disabled +{ + background-color: #31363b; + border-width: 1px; + border-color: #454545; + border-style: solid; + padding-top: 5px; + padding-bottom: 5px; + padding-left: 10px; + padding-right: 10px; + border-radius: 2px; + color: #454545; +} + +QPushButton:focus { + background-color: #3daee9; + color: white; +} + +QPushButton:pressed +{ + background-color: #3daee9; + padding-top: -15px; + padding-bottom: -17px; +} + +QComboBox +{ + selection-background-color: #3daee9; + border-style: solid; + border: 1px solid #76797C; + border-radius: 2px; + padding: 5px; + min-width: 75px; +} + +QPushButton:checked{ + background-color: #76797C; + border-color: #6A6969; +} + +QComboBox:hover,QPushButton:hover,QAbstractSpinBox:hover,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QAbstractView:hover,QTreeView:hover +{ + border: 1px solid #3daee9; + color: #eff0f1; +} + +QComboBox:on +{ + padding-top: 3px; + padding-left: 4px; + selection-background-color: #4a4a4a; +} + +QComboBox QAbstractItemView +{ + background-color: #232629; + border-radius: 2px; + border: 1px solid #76797C; + selection-background-color: #18465d; +} + +QComboBox::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QComboBox::down-arrow +{ + image: url(@THEME_RESOURCES@/down_arrow_disabled.png); +} + +QComboBox::down-arrow:on, QComboBox::down-arrow:hover, +QComboBox::down-arrow:focus +{ + image: url(@THEME_RESOURCES@/down_arrow.png); +} + +QAbstractSpinBox { + padding: 5px; + border: 1px solid #76797C; + background-color: #232629; + color: #eff0f1; + border-radius: 2px; + min-width: 75px; +} + +QAbstractSpinBox:up-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center right; +} + +QAbstractSpinBox:down-button +{ + background-color: transparent; + subcontrol-origin: border; + subcontrol-position: center left; +} + +QAbstractSpinBox::up-arrow,QAbstractSpinBox::up-arrow:disabled,QAbstractSpinBox::up-arrow:off { + image: url(@THEME_RESOURCES@/up_arrow_disabled.png); + width: 10px; + height: 10px; +} +QAbstractSpinBox::up-arrow:hover +{ + image: url(@THEME_RESOURCES@/up_arrow.png); +} + + +QAbstractSpinBox::down-arrow,QAbstractSpinBox::down-arrow:disabled,QAbstractSpinBox::down-arrow:off +{ + image: url(@THEME_RESOURCES@/down_arrow_disabled.png); + width: 10px; + height: 10px; +} +QAbstractSpinBox::down-arrow:hover +{ + image: url(@THEME_RESOURCES@/down_arrow.png); +} + + +QLabel +{ + border: 0px solid black; +} + +QTabWidget{ + border: 0px transparent black; +} + +QTabWidget::pane { + border: 1px solid #76797C; + padding: 5px; + margin: 0px; +} + +QTabWidget::tab-bar { + left: 5px; /* move to the right by 5px */ +} + +QTabBar +{ + qproperty-drawBase: 0; + border-radius: 3px; +} + +QTabBar:focus +{ + border: 0px transparent black; +} + +QTabBar::close-button { + image: url(@THEME_RESOURCES@/close.png); + background: transparent; +} + +QTabBar::close-button:hover +{ + image: url(@THEME_RESOURCES@/close-hover.png); + background: transparent; +} + +QTabBar::close-button:pressed { + image: url(@THEME_RESOURCES@/close-pressed.png); + background: transparent; +} + +/* TOP TABS */ +QTabBar::tab:top { + color: #eff0f1; + border: 1px solid #76797C; + border-bottom: 1px transparent black; + background-color: #31363b; + padding: 5px; + min-width: 50px; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-bottom: 1px transparent black; + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +QTabBar::tab:top:!selected:hover { + background-color: #3daee9; +} + +/* BOTTOM TABS */ +QTabBar::tab:bottom { + color: #eff0f1; + border: 1px solid #76797C; + border-top: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + min-width: 50px; +} + +QTabBar::tab:bottom:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-top: 1px transparent black; + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:bottom:!selected:hover { + background-color: #3daee9; +} + +/* LEFT TABS */ +QTabBar::tab:left { + color: #eff0f1; + border: 1px solid #76797C; + border-left: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; + min-height: 50px; +} + +QTabBar::tab:left:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-left: 1px transparent black; + border-top-right-radius: 2px; + border-bottom-right-radius: 2px; +} + +QTabBar::tab:left:!selected:hover { + background-color: #3daee9; +} + + +/* RIGHT TABS */ +QTabBar::tab:right { + color: #eff0f1; + border: 1px solid #76797C; + border-right: 1px transparent black; + background-color: #31363b; + padding: 5px; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; + min-height: 50px; +} + +QTabBar::tab:right:!selected +{ + color: #eff0f1; + background-color: #54575B; + border: 1px solid #76797C; + border-right: 1px transparent black; + border-top-left-radius: 2px; + border-bottom-left-radius: 2px; +} + +QTabBar::tab:right:!selected:hover { + background-color: #3daee9; +} + +QTabBar QToolButton::right-arrow:enabled { + image: url(@THEME_RESOURCES@/right_arrow.png); + } + + QTabBar QToolButton::left-arrow:enabled { + image: url(@THEME_RESOURCES@/left_arrow.png); + } + +QTabBar QToolButton::right-arrow:disabled { + image: url(@THEME_RESOURCES@/right_arrow_disabled.png); + } + + QTabBar QToolButton::left-arrow:disabled { + image: url(@THEME_RESOURCES@/left_arrow_disabled.png); + } + + +QDockWidget { + background: #31363b; + border: 1px solid #403F3F; + titlebar-close-icon: url(@THEME_RESOURCES@/close.png); + titlebar-normal-icon: url(@THEME_RESOURCES@/undock.png); +} + +QDockWidget::close-button, QDockWidget::float-button { + border: 1px solid transparent; + border-radius: 2px; + background: transparent; +} + +QDockWidget::close-button:hover, QDockWidget::float-button:hover { + background: rgba(255, 255, 255, 10); +} + +QDockWidget::close-button:pressed, QDockWidget::float-button:pressed { + padding: 1px -1px -1px 1px; + background: rgba(255, 255, 255, 10); +} + +QTreeView, QListView +{ + border: 1px solid #76797C; + background-color: #232629; +} + +QTreeView:branch:selected, QTreeView:branch:hover +{ + background: url(@THEME_RESOURCES@/transparent.png); +} + +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(@THEME_RESOURCES@/transparent.png); +} + +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(@THEME_RESOURCES@/transparent.png); +} + +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(@THEME_RESOURCES@/transparent.png); +} + +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + image: url(@THEME_RESOURCES@/branch_closed.png); +} + +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + image: url(@THEME_RESOURCES@/branch_open.png); +} + +QTreeView::branch:has-children:!has-siblings:closed:hover, +QTreeView::branch:closed:has-children:has-siblings:hover { + image: url(@THEME_RESOURCES@/branch_closed-on.png); + } + +QTreeView::branch:open:has-children:!has-siblings:hover, +QTreeView::branch:open:has-children:has-siblings:hover { + image: url(@THEME_RESOURCES@/branch_open-on.png); + } + +QListView::item:!selected:hover, QTreeView::item:!selected:hover { + background: #18465d; + outline: 0; + color: #eff0f1 +} + +QListView::item:selected:hover, QTreeView::item:selected:hover { + background: #287399; + color: #eff0f1; +} + +QTreeView::indicator:checked, QListView::indicator:checked +{ + image: url(@THEME_RESOURCES@/checkbox_checked.png); +} + +QTreeView::indicator:checked, QListView::indicator:checked +{ + image: url(@THEME_RESOURCES@/checkbox_unchecked.png); +} + +QTreeView::indicator:checked:hover, +QTreeView::indicator:checked:focus, +QTreeView::indicator:checked:pressed, +QListView::indicator:checked:hover, +QListView::indicator:checked:focus, +QListView::indicator:checked:pressed +{ + image: url(@THEME_RESOURCES@/checkbox_checked_focus.png); +} + +QTreeView::indicator:unchecked:hover, +QTreeView::indicator:unchecked:focus, +QTreeView::indicator:unchecked:pressed, +QListView::indicator:unchecked:hover, +QListView::indicator:unchecked:focus, +QListView::indicator:unchecked:pressed +{ + image: url(@THEME_RESOURCES@/checkbox_unchecked_focus.png); +} + +QSlider::groove:horizontal { + border: 1px solid #565a5e; + height: 4px; + background: #565a5e; + margin: 0px; + border-radius: 2px; +} + +QSlider::handle:horizontal { + background: #232629; + border: 1px solid #565a5e; + width: 16px; + height: 16px; + margin: -8px 0; + border-radius: 9px; +} + +QSlider::groove:vertical { + border: 1px solid #565a5e; + width: 4px; + background: #565a5e; + margin: 0px; + border-radius: 3px; +} + +QSlider::handle:vertical { + background: #232629; + border: 1px solid #565a5e; + width: 16px; + height: 16px; + margin: 0 -8px; + border-radius: 9px; +} + +QToolButton { + background-color: transparent; + border: 1px transparent #76797C; + border-radius: 2px; + margin: 3px; + padding: 5px; +} + +QToolButton[popupMode="1"] { /* only for MenuButtonPopup */ + padding-right: 20px; /* make way for the popup button */ + border: 1px #76797C; + border-radius: 5px; +} + +QToolButton[popupMode="2"] { /* only for InstantPopup */ + padding-right: 10px; /* make way for the popup button */ + border: 1px #76797C; +} + + +QToolButton:hover, QToolButton::menu-button:hover { + background-color: transparent; + border: 1px solid #3daee9; + padding: 5px; +} + +QToolButton:checked, QToolButton:pressed, + QToolButton::menu-button:pressed { + background-color: #3daee9; + border: 1px solid #3daee9; + padding: 5px; +} + +/* the subcontrol below is used only in the InstantPopup or DelayedPopup mode */ +QToolButton::menu-indicator { + image: url(@THEME_RESOURCES@/down_arrow.png); + top: -7px; left: -2px; /* shift it a bit */ +} + +/* the subcontrols below are used only in the MenuButtonPopup mode */ +QToolButton::menu-button { + border: 1px transparent #76797C; + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; + /* 16px width + 4px for border = 20px allocated above */ + width: 16px; + outline: none; +} + +QToolButton::menu-arrow { + image: url(@THEME_RESOURCES@/down_arrow.png); +} + +QToolButton::menu-arrow:open { + border: 1px solid #76797C; +} + +QPushButton::menu-indicator { + subcontrol-origin: padding; + subcontrol-position: bottom right; + left: 8px; +} + +QTableView +{ + border: 1px solid #76797C; + gridline-color: #31363b; + background-color: #232629; +} + + +QTableView, QHeaderView +{ + border-radius: 0px; +} + +QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed { + background: #18465d; + color: #eff0f1; +} + +QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active { + background: #287399; + color: #eff0f1; +} + + +QHeaderView +{ + background-color: #31363b; + border: 1px transparent; + border-radius: 0px; + margin: 0px; + padding: 0px; + +} + +QHeaderView::section { + background-color: #31363b; + color: #eff0f1; + padding: 5px; + border: 1px solid #76797C; + border-radius: 0px; + text-align: center; +} + +QHeaderView::section::vertical::first, QHeaderView::section::vertical::only-one +{ + border-top: 1px solid #76797C; +} + +QHeaderView::section::vertical +{ + border-top: transparent; +} + +QHeaderView::section::horizontal::first, QHeaderView::section::horizontal::only-one +{ + border-left: 1px solid #76797C; +} + +QHeaderView::section::horizontal +{ + border-left: transparent; +} + + +QHeaderView::section:checked + { + color: white; + background-color: #334e5e; + } + + /* style the sort indicator */ +QHeaderView::down-arrow { + image: url(@THEME_RESOURCES@/down_arrow.png); +} + +QHeaderView::up-arrow { + image: url(@THEME_RESOURCES@/up_arrow.png); +} + + +QTableCornerButton::section { + background-color: #31363b; + border: 1px transparent #76797C; + border-radius: 0px; +} + +QToolBox { + padding: 5px; + border: 1px transparent black; +} + +QToolBox::tab { + color: #eff0f1; + background-color: #31363b; + border: 1px solid #76797C; + border-bottom: 1px transparent #31363b; + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +QToolBox::tab:selected { /* italicize selected tabs */ + font: italic; + background-color: #31363b; + border-color: #3daee9; + } + +QStatusBar::item { + border: 0px transparent dark; + } + + +QFrame[height="3"], QFrame[width="3"] { + background-color: #76797C; +} + + +QSplitter::handle { + border: 1px dashed #76797C; +} + +QSplitter::handle:hover { + background-color: #787876; + border: 1px solid #76797C; +} + +QSplitter::handle:horizontal { + width: 1px; +} + +QSplitter::handle:vertical { + height: 1px; +} + +QProgressBar { + border: 1px solid #76797C; + border-radius: 5px; + text-align: center; +} + +QProgressBar::chunk { + background-color: #05B8CC; +} + +QDateEdit +{ + selection-background-color: #3daee9; + border-style: solid; + border: 1px solid #3375A3; + border-radius: 2px; + padding: 1px; + min-width: 75px; +} + +QDateEdit:on +{ + padding-top: 3px; + padding-left: 4px; + selection-background-color: #4a4a4a; +} + +QDateEdit QAbstractItemView +{ + background-color: #232629; + border-radius: 2px; + border: 1px solid #3375A3; + selection-background-color: #3daee9; +} + +QDateEdit::drop-down +{ + subcontrol-origin: padding; + subcontrol-position: top right; + width: 15px; + border-left-width: 0px; + border-left-color: darkgray; + border-left-style: solid; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} + +QDateEdit::down-arrow +{ + image: url(@THEME_RESOURCES@/down_arrow_disabled.png); +} + +QDateEdit::down-arrow:on, QDateEdit::down-arrow:hover, +QDateEdit::down-arrow:focus +{ + image: url(@THEME_RESOURCES@/down_arrow.png); +} diff --git a/zeiterfassung/themes/dark_theme/Hmovetoolbar.png b/zeiterfassung/themes/dark_theme/Hmovetoolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..cead99ed108a83715a939fc293dd7692008ac6b2 GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0y~yU=UznU~u4IV_;wquKhlZfq{W7$=lt9VKYM|L*<>j z!G9PS7&r?&B8wRqxP?KOkzv*x2?hoR_7YEDSN8iXk^;Jt>%CrIU|?X7%?ybsan8@p zP0cG|00HNs)Wnk16ovB4k_-iRPv3wPy;OFPeV#6kArXh)o-^cPP~c&9_`0|@I(}8~ z!IMW_pBS-9^=&Jg8K`sfeHKXpJ&|A6j;v!~V6gObaSX9Iot&V+ zcI5bR<0Hq9FYYi*3~mV)T7Pb$#>u85|N4)l$?z@=JQ;X0(6fe>?QsUjL@iG{28IrA XmdloU6YLon7#KWV{an^LB{Ts5Tr)C9 literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/Vmovetoolbar.png b/zeiterfassung/themes/dark_theme/Vmovetoolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..512edcecd69bd11ef3d58930ba8304473319e183 GIT binary patch literal 228 zcmeAS@N?(olHy`uVBq!ia0y~yU@&7~VBq3lV_;zT|934n0|NtNage(c!@6@aFBupZ z*pj^6T^Q;a+8L%kySHpT0|NtRfk$L90|U1(2s1Lwnj^u$z`$PO>FdgVheMoGOMAwI zm@ftZd>duW@ARGztUqC@Zv0drJKjVz`)??>gTe~DWM4fg6&F5 literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/Vsepartoolbar.png b/zeiterfassung/themes/dark_theme/Vsepartoolbar.png new file mode 100644 index 0000000000000000000000000000000000000000..d9dc1561b473019a12091725e724a44d979dc238 GIT binary patch literal 187 zcmeAS@N?(olHy`uVBq!ia0y~yV6bOkU|{E9V_;y|Up7^qfq{XsILO_JVcj{ImkbOH zY)RhkE)4%caKYZ?lNlHoI14-?iy0WWg+Z8+Vb&Z8kO3v0zOL+dIK(+kwL?z@Wic=? zSbMrShFF|VPLN<#7B&!CykcLimTK9b<gTe~DWM4fu2nP3 literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/branch_closed-on.png b/zeiterfassung/themes/dark_theme/branch_closed-on.png new file mode 100644 index 0000000000000000000000000000000000000000..d081e9b3b90d774450a8ea48f1184019e33a755a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU|?flVBq9nVPIg`J$b4a0|Nt7lDE4H!{x2v3t~W0 z1s;*b3=G`DAk4@xYmNj10|R@Br>`sf9d33W9&IlhgAxV?1{F^i#}JO|$q5du|NkG3 u@Hr8qmJ+F!a#4mUdwuXO1@!l~}cCxuyBUe53Oj7#OrX zT^vI=t|uoPVCdoDDYjGKUczBuWT3#kjKjddz-flSK@mm~;ef4+85xf4WSgTe~DWM4f^v@-A literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_checked.png b/zeiterfassung/themes/dark_theme/checkbox_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..830cfee6568b718535c61564b95caf2d17c0958a GIT binary patch literal 492 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?nVv3=ArY-_r(63Sb`WUWzj0B^b4I3ch9!4er}Vvb5h;*&O4XGPZnM4+;2!wS z;YDSO&69-%Jv}cyk_Jlyrzcwl_Pk=ez$Dc%qwo3Gpw6hMtW>6ahoFA-Y7qSffN z08Fj@dtlrDpZ=5oG8r4q_3FGCzqyD>c!8*E;AWjoTffa%Vk`fgg(a`SZBNX-_QZ-1 zE}On~hu8)4AF2h0N=8U8l49VVbnwYifnBc@PtLwB>U93Q{-uK|_Ol{iulxPYc#_}F pjx!C0tFFgwuMWRp{NKZlag9V(So8H4c?=8;44$rjF6*2UngE-z%RvAD literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_checked_disabled.png b/zeiterfassung/themes/dark_theme/checkbox_checked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..cb63cc2fac47ad304451f864be5fb9b9085910ee GIT binary patch literal 491 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?8J;eVArY-_r~CRHa*$}7zg|G(3iE<+0pV@)oXbu`^r#ic8yYSw@t9u0bNxsK zd*OS2ritfUrzkAYk%`H;f7j>!m&#?nmnDC^pZIfQ{u#~yX08Lvl+S4ws4Mj<@aQf$ z&2q$CjYaDK!wV+=K9+5bN*frrL|vPAOemP~zJxpnOWvjY{H3ckURgFV7sTm3cb|9N z@kzh|mTTJ>_ssO;mFRKpi#g1=ZqW+$mnEDMt2}Jy%@@7FzAI6}Zb#d3&ZY-)6ZKZL zrZPo_#9w2bpJr3XR`k(e@2dCGi&tCSUz+;%&-8Da_sYfVl{+1jCUB@QfvGij_}BcJ z|KDV@|AKyxcc+&bFWS!fMVv+CUBjcwopa*FcL%L}voMM!Pn^YOqIW{_6tCFu)mQJ9 z9tc*FRamUlTT%Bf;Sx(tg2m0fc2*0!-}r>h3(RP{HqWr$pmTL-a$o3@zE_MBN|io* oJeME1H)HGViHocMP29sQR+y#kQu5EAfq{X+)78&qol`;+0EPg>MgRZ+ literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_checked_focus.png b/zeiterfassung/themes/dark_theme/checkbox_checked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..671be273b06e2b721f494379ab61e527932ba69e GIT binary patch literal 252 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0VruY)k7lg8`{1_lQ95>H=O_J^#zT$X(2-wHS~Ffep_x;TbJ98OM z`u)HC;^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?S)MMAArY-_r=Rva-vp*Ff|aFB;kFE~97F{#fB9Rc7I^MjL^6}-!~Ug3J0DiwJHc5q z(V{Q=WX(Ni(=FHR7-AcOwk=R8;`9-+DCcGWV3Tj+KZ(si^y+H;JhK1`#$SmNcDvis zGq>ONcbNaBrdFJxtns_=!Qx8hZ(*D{s;vjEv7UOZw#IN*-aGY=hXcay-v7lX!qn=Z zxZ#`qhIhM5=dOM4p!i`m%dsa~Q+d^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?nVv3=ArY-_r=RsY>>%QH-nTibUe2MGRlDSM@r~HGGdh~~^>ekV&7724%X59{ z558CT>b8;GF=NxCr0zE|fkM)J;}X;^Jv zxA}vp25a5a1KdihTc@7uxF_}Dldr==wiWYDX3zedv^{EVZpiBc4`w&07jf$FYfoKm zz_a}snWID7M7#J8lUHx3vIVCg!08`z>hX4Qo literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_indeterminate_focus.png b/zeiterfassung/themes/dark_theme/checkbox_indeterminate_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..415f9b6e14e21d52aa6595bbc80dd34adfa2ba2b GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0VruY)k7lg8`{1_lQ95>H=O_J^#zTvmdaC;ZA87#P|-T^vIq4ksr_ylfC` z{r=y6@!MbF|1&ZlK6EftV{<$9e+J)z=BGDg7|R>p?wbBTP=vM9IOuXv%roP}QpKsd z$J5n>ZFP>Pn_YHz-W15*W7W`lqsDuCj>RlCH(7&M4UWNl7dC~-^fm>u^W?Dy>=si= vF%!03l~j|)H?NQL?yj^Ftp{y<3=AA`7O9p(0*4qF7#KWV{an^LB{Ts5G$vHP literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_unchecked.png b/zeiterfassung/themes/dark_theme/checkbox_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..2159aca9a10f75729912579b33a1226e575799aa GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?zMd|QArY-_r(63Sb`W8B?izULJ42&3L(n^?Q@w8^L<;1cQfKyR9+u5mu~?&A z;Y*E^-KWI`Jv}}<#e^!fqN6I)RK?#PtatCyU!`T%_qXlD(nFdX9-sKEXu#Zd?EBh| z3{8hn)y~J!qA!`f6Piw}7T)`c@dE3ujw7|_xj%QL)g3#wQGe#2jTdvi^%xebf711w zRC4KTT2EHO+FAxThTcC8&QF9QmKD5<;!Eh6nSW^u`x=+8uTIal*Oyxoa7gp zmi^Z7W`&(DE5{v%wj-|iYi&Gl*5X1%>r=Vqq4JAu(BaPK#^W^4ERf0S#OCLAd>SZs0e3IhWJgQu&X J%Q~loCIDQHzg++T literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/checkbox_unchecked_disabled.png b/zeiterfassung/themes/dark_theme/checkbox_unchecked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..ade721e81ba47fa792d4586516b8744f8c49c8bb GIT binary patch literal 464 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}?zMd|QArY-_ulxEPa*#Oo@qaW2$1BDQ+8vzH=M{IKx*_HHz%I$jDbsU$1<&=C z0`|i9>`aZH5FKwG>A-K3OZnw(B$#)xMz0nIYon;Od356UUy5q zf4^FHHEa2bu!e_X2LyMrfBNk6?BMsmlWV7>yZmzSbB#Lr@7duc%oPW6zJC9ev^4ff zMd{}`PU=02qRr&hPOiv5GquYk(3YJmj3NGhO~H=O_J^#zTvpoak4!(xz`#)J>Eaj?aX2|a;$?$i z>-Ycmi{JhV|DTch@S%gD8k^g(|1mdKI;Vst0JT|Cn*aa+ literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/close-hover.png b/zeiterfassung/themes/dark_theme/close-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..657943a668b734138256aa7bb178661030fd4a1f GIT binary patch literal 598 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hElCp-6+d@B{;Czsv4XWvL@9D%q*01U z!4wWj!F$0?Jsj^eo!r{Ddri1_aII2`+7CHSBk7veEV0oKIji@ligbwndtX%fbjSSj zB7yJk>(p*naoj8Y?WTpv9#av+FM&c;pQcHOn7-eu8u2N2o_+JcK8dOo)VFq(dtD5P*TA^*Iwnd_9S4dQA^DjCZx(!8z~<(=iFoS)%h)AEr0m$~>^@`P%zGzba>*n3>R4-3d+y(lI7Q*8XyvpP*>kyeXaCcOR>sS+XYyGGsk t9`Eh9i#3<@ESz_?k|WV>PRz#d6-@3MqS>un>=;4O>*?y}vd$@?2>><1^ClMOQP`oh&d=?lFt z=ciiUvYAvZobkza>N6QRX92!;QSGodmMuRw?tlC-@N@a}-?7UC0=Q)~T{bywPC23R zKts?-Y0q*NNx?h8Ej@hsQxz6BN4uWz`Cu!QV)i49bJw;*!r$snYY1rn`B_%|bjLhD zk-+!&b!xY(IPR7HcGJRSkEw{^mq4MaPtznsOyBQSjrf$iamMlk*ZzE-v|;M;UGF-= z-!GPWIrIDNrWe~dl-!s$Jy-nCYNKlKV80(@zi`X)69pLm=kq#<+NxTZ{rPR>pxIu1oyD!t)tWz}H8FJB73PUi8e%rF4!knLb}kL6 z@&6irPEYW$Y57V1&Wgb}deC_?8UzIaY%uHyjab=t~JAB2lZ_n-~ zU3_EPe=dKY!bDpy<;vKJD}A@+b^1!ZUX+skDK>uUS)Hb`NUOyy6JCFcREd!HU88P% tkN5W5#hOcc7S20c$&qL`CuZaK3Z}pXQ4`BAF)4$h*VEO{Wt~$(698sW^c4UA literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/close.png b/zeiterfassung/themes/dark_theme/close.png new file mode 100644 index 0000000000000000000000000000000000000000..bc0f5761096ef24b8b4461d9252b42dad48d0d45 GIT binary patch literal 586 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEliNZ92o3C#tA@u-{|C)d$t8FXFiLZ~V3mRS5g>*y3Mt%yn16ufNXTD$f?W zvODp8uk+z(XThcYlME(pKek{+;d|YKlhz+el4Dx>@ww{4DaWnecZ9!REcJ5c_uDNm z%s7>_7;k=_@Ska(ki>`h_J-q&j^CVa34KjIQxn55XC6!l=n2Sp~G*9|XOw;VE5 z%`mIj#?srCK1aQUE2FW03d2p_zHc_kfi5?B`wqE;G^I&>78EiOu4!S}?i_eT;@dfP zTM<{EgdVR8Yd+|325C%BH`(*NyzZ}uRFT>Y#rV*Mr_V%JZCqUGtut%y^NT;vimPmL zUpwi$=*+&9-_ve%ZPfSTQ`!GM^yV3*jOj~_y*PI2&3A43dhtk|5p%{3TX|o_RG+Uo i=bSk{KYY@+`?&nSj;Kdx-^+M{qSVvX&t;ucLK6U%E&0;` literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/down_arrow.png b/zeiterfassung/themes/dark_theme/down_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..e271f7f90b4132c9d740058d8b6c915dbdd626d2 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0y~yVBln6U|{25VPIg`eXhojfq{XsILO_JVcj{ImkbOH zOiAAEE(~*mP5#7!)E0O|7Bet#3xhBt!>l~}cCxtUmUXGmuEal|aXmTV07DNCPqCc>_Yw{RBLfBQWgG?u22L{s4vH{}2nTFk%*b$TC;NohH2G!* P1_lOCS3j3^P6f{ QFfcH9y85}Sb4q9e07qdf=>Px# literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/left_arrow_disabled.png b/zeiterfassung/themes/dark_theme/left_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b9af8a34edb5f8dd767bf6afa303b89a31d38f GIT binary patch literal 166 zcmeAS@N?(olHy`uVBq!ia0y~yU|?flVBq9nVPIg`J$b4a0|NtNage(c!@6@aFBupZ zn3BBRT^Rni_n+AhQdi&+S^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa~tnw~C>ArY-_r}}5ago+%if1cWra_iXMl~X*zw@tLtSa;d?D3`B8TJkM(ebuvW z>=#w~)@=${;dwV@=Ak{3m-FT+1Rs1fzqM5Q&*dee6N9`8ysoPntDJrCJ7$l$?}R<( zvM*R-I1^+k#4H)lBVNpx+0HRG?=iqy?FqwMD2 zuYAR{>glKG8M+LI*|xKor-ZQ=94g@09C(nU;CjNlInQgqhOn?WUf6y$M~~sXbWL_* zgjMywKey^)?tk~UC=j$@)wy^3hQ`_R=i4*?8P8=o(!O6k^V-}wd!`)wYV%(0L)F#q z>5bio-TN5MEWNqJO@Z(C^fa^LbpbzQ5;8Mi&OCVVVCLj|YFw6TYfryET4RV%u}292SPQQnS~7 z-x{@Cec7^C45Ih{-#gRBzUgMNXY0#1+c`E|&FJBdD_GKh%beeM=4&JCef_!BHlNDX zKKlx={AGA%A+IERH_z<6%G}8s4r$2;qIN%+XIt{3DS<`PGWX&=@95rRtPIyn>kq#% zyRes`d5)+3+sIi8T%}e^E-2+y|6BZE>N?hhZ!y2;9=Diw>i*VZk;2?914DaG}-mH`5o(wzdbi!%$usc@=vVw@dvNg z$<1X6+byQBLQ(d6?SGa7htHl(J#)=4_|!wDJU$tl9XuO?^q%v?x%}Fd&3H3s8{3`U zYU`s(9|Pl8mrCsr4KBNU@!ZBOZIAcNJpa60B~qd`u6$ec+U!{F6VE?eTb$D=(0cdQ z$H(_7`=*=W6Yovk!e!F9EaB>zdy*Cf4@|F@zGn`p-On;-%FQ!hzLaRcoYNN?_w_I9 z1Lg%!s_rGJ?^9X*tM23(J4UhC^V&;4T8mxX+uJ*7iT44gtKavp4qL6SUKoDr=^gQ@ zr})#pXgym|IfMbntC0RXcsG~?boH7_9v;ISMC)uU0`iATT_Iqv-SIYmIIIF zKlLQ=oJbV=%DFP{L-&$fWvfedb)VaQxo~*@NB)YZexC%NWI8c0Ffe$!`njxgN@xNA D@ms|$ literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/radio_checked_disabled.png b/zeiterfassung/themes/dark_theme/radio_checked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..bf0051ede5f00d8cecf17b37c6bb9de38bb24e19 GIT binary patch literal 972 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa~to}Mm_ArY-_r=87?36(fje}3-F881`BF3nvziEknI*5f_da!epkcsu$0$>aqbYwLXT$4V6H}BXns4T3 zxi8%L#B1rLPkwRdKl5>?8B||Blhz)z^3InpB{wwdwn#fD|NMPmXVA_+mFoEdEZzsA zm)pLVG5Gxa?QMqOx!+3GfNXM!5~vB$n!cZ5#+*4diN(o}W1Bgzdh?}eKeH%4nX)ND zLqv}0$IUk)A7*+!b`%JyVk?N!?-+r&*2jh+Xk*j;RWc#Eq_iSjasFCr|6xw;A=F{7VH(fJj?HL?!8i3aOF?|v?S_pD~sXX@t>lbvyM&DDFe=gH=O_J^#z+?*<>F0}?TFfiTrba4!cIQ(|ndGC-w zk>mE?e@L{N=Q0Ui5_4N!(&pG>rnY!?V~;?YCjPwGKWB zR|VaQ_r9B4@AM$*UHakYzH=`Yuve_(efiX`d?njSmzxG^T_PU}zPeBNrZ0P)y({q0 zXZ1(({;&SM)qc-)wa8Cd9MsVu=gw+4Qh&YL$7$ z1GlMvgGBEBRxEJZ{P~=q>v6q*SMR@Bv1xy(=$ZhQ2@|G%$}bREHLvI^hsde6ZL+bv z^53PHq`!OFL^V2PFx{Gvyn9br-lvT9iULPVrrzb(X;FKiv1!o@pNcvrg;xfew^@&0 z`8h){LWeEw(zQmt2R#RJGMHKqq_BpaZZmB7DQ8~L!uY^Bbld0SRi1~BCTT^@iqh7+ zy=R|^se$k;2SZWaObJ0(t#?0_8Ft2%?-iJ_`9N-N1Vhb%>us$a1*-*S6#rcEn>XRl z>#$p=?EgI5vwQ1%oxbY2V`kGP?)~Avc529T@Bj016O!5Uj=y_To@syWhl9xC12PlN zso(u>IxBqo)V=mfDPd+!zjtqaY`}bSL0kKQ%-jgER`m^O!Vi|z8p<9JPq3I@lK%g+ z$2FP2>8@{kQgTC;Z$`UM%*{%8=3r=lNRUsqj``<$#n7Am!j23K3=E#GelF{r5}E)y CbB8bh literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/radio_unchecked.png b/zeiterfassung/themes/dark_theme/radio_unchecked.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4def65c64a9d55441f82fe66fc7f46e5b73a75 GIT binary patch literal 728 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}Cp`I>|ArY-_r~3K_2MV;!-zg>Z-d}+C;07Vz4IlXrusvq;@F~B*FP78GsIx92 zqlxwZA=|ZY)^&=7*XbNt`>{JCiAQnC#y0y4ZEb3`v0D_66-4W8*!;fi*%_Pkb2BAh ztX%#-R_wSX&*6Q4vr4_vCbMxa*q^&9;agyC=y`nr)H)qeSF`juQ{6W~oovaC|1qloZ8!yl76U~_ZcjnBq z=a*de-*xccTBn5@GA`+u^ne4!{`P!TbT5R)GroQSu k=C<7Y{mSL@{#})~yYk+JchCP+1_lNOPgg&ebxsLQ045ejk^lez literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/radio_unchecked_disabled.png b/zeiterfassung/themes/dark_theme/radio_unchecked_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..6ece890e750b0685bbd818f22e5fbf999ccd35e1 GIT binary patch literal 760 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}EX7WqAsieW95oy%9SjT% zoCO|{#S9F**Fl)kNn>^e0|SF(iEBhjaDG}zd16s2LwR|*US?i)adKios$PCk`s{Z$ zQVa}CrJgR1ArY-_r=9f^4wPt{zh3l`Q3Z3DZ)Qa2GW(Bg3Tjg3Q(A6rGd{v?;3i-a zD5mFeaYvE!mXaHjW;{BzL(p8O=a!k!0Rac*Htqu zotfEamiw>#i2n_fv*+FwRCu^ta_~}QYIwz`^q#e=`f65TUEROq8@X@HJhP$e2}@N2 z|Ds(hl&rTFvba>t2Msnktz=T(qpQ#;)SZsRG&Dnag zB%2&(&pE4ehtoEddOiIVWHj-Ip3--rlPODM^ww9V+R8U(&YF8SHg@%a%-Vk@#fX&u8j#FTGYE?=jvi2I_upup?jp69xc4~e@d?PFyKxq98VGQm+n;kVpB z)-uHADft`GL4Ms|kH&b{r&_*`V(Z?PMz!@3W& z9SG_EtF&%5^EGRyg{z7+PV4Si?_?Rono&zH^lv(5gS?`QNfzk9dmV^bCb P0|SGntDnm{r-UW|M`=yg literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/radio_unchecked_focus.png b/zeiterfassung/themes/dark_theme/radio_unchecked_focus.png new file mode 100644 index 0000000000000000000000000000000000000000..564e022d33d93bab458674a4820d943299333857 GIT binary patch literal 646 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Y)RhkE)4%caKYZ?lNlHo zI14-?iy0VruY)k7lg8`{1_lQ95>H=O_J^#z+??iqIY)XK7?^}TT^vIq4!@nYKRY;3 zAshnu3h3e>-*u8<=3kDo)_2MEpG5J`gy-~k%oNT4GS0dzH>|( z%o>aux0Z|D*rWLHM_T&r|D|uA%*CGR)1$L;YnCry>x@1_yX&>_1tE&8Il;MbiAnKYW~8ba_%^z z#j!JMMHseytBsh*;NUXliZy3D!wd_CT(gHBK@4enOd0O%37@$Rq;RS3;CPUl!XVVg zFSPt_mBGXZ%G!(;t}dr1GM+iN&ELh>?BQICbMNbKs_xz+W!XM0$yH(Mysh0!g>G)U z7CFI7dgYg^F050$s>?Qpu=U=t;{U9Du<@pKLF}w=&u1qTi#MoGt<#xmcYXb{tNi=t zHyz1!zwqc@*ypfW?4C1ciZk{!o>lW$BYZL3I_vn`BR$L+3^U&QJ)i#ntY?_SJjTxb z>Nij9oBYO<^Z0qoEnogl+C4pX_skTd6@M5nt$EgR%RM8Jfq{X+)78&qol`;+0E8qQ Aa{vGU literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/right_arrow.png b/zeiterfassung/themes/dark_theme/right_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..9b0a4e6a7a8097818d9c0626c84f19f4d690dd31 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0y~yU|?flVBq9nVPIg`J$b4a0|NtNage(c!@6@aFBupZ zn3BBRT^Rni_n+AhQdi&+SEal|aXmS~f%X6S!x26wV$@P1)l!ZY@GVxHb2y_T5#)m07S2-hc{i0A7#J8lUHx3v IIVCg!0Q-9=WdHyG literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/right_arrow_disabled.png b/zeiterfassung/themes/dark_theme/right_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..5c0bee402ff962fe285183b321feba59dac4f3d6 GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0y~yU|?flVBq9nVPIg`J$b4a0|NtNage(c!@6@aFBupZ zn3BBRT^Rni_n+AhQdi&+Sk07#J8lUHx3v IIVCg!0RC7gAOHXW literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/sizegrip.png b/zeiterfassung/themes/dark_theme/sizegrip.png new file mode 100644 index 0000000000000000000000000000000000000000..350583aaac4aa474ac449eaea2cc7ddd060276b9 GIT binary patch literal 129 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRdMrH;E236Z!B?bltwg8_HR|W=#b?erZXJ6B0 zU|?V@3GxeOaCmkjje&td)6>Nz(!sM1rC-2ha+zM<2rMwpeI*@Z@PO%TWH}e g*?iSqXK(y9mdKI;Vst0DBB1#Q*>R literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/stylesheet-branch-end.png b/zeiterfassung/themes/dark_theme/stylesheet-branch-end.png new file mode 100644 index 0000000000000000000000000000000000000000..cb5d3b51f8cf7fd0f0ed26cfea722fa5a632dc1a GIT binary patch literal 224 zcmeAS@N?(olHy`uVBq!ia0y~yUuUCj- zU|`7iba4!+xb^nJMqUO34ra#_3wVw%@;uL?u}eQTa{9(C@Aeh1UUlck*A@k*7L5)8 zMJExL4gp0jC6^8XRzW3~jwTL4B^SpQjz9rL!V3PZViyW!DG8f5FNA@Cfx*+&&t;uc GLK6T|14Eer literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/transparent.png b/zeiterfassung/themes/dark_theme/transparent.png new file mode 100644 index 0000000000000000000000000000000000000000..483df25137d1f267b9793bb6068bb58e8825f11f GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hElYWU|=ut^mS#w&mt)xZXMrT;>*CmAe$KyQR1ARo12m?w-NFAiRT*4Z!n;%CcpX@y z#_~F_LXG2fY>hU{>(~lyj@^@f7z@0~derZg`@Q~igLa?IWzKbaa=Ztt z1^Qbaobl7z<{!(hvF&}9oPywI3@R*NZmWIGHrTzHL9{YJdyRLU{>RSxI`y&*ZwZY?&bMia)nMux3kJPh&WCCv0ih+Og7!u zji=}E%oRMmN_>G_pWEC6HcHFo`~M3cUnioeIpN1MmX;s&tU{@(M(+i*jC@xTn zQhj#%fg!i5zlmMm$~8tDQ5&}?wM;6i6y;buUG3RaNr&Ud_L#J`T)etFMg{vd$@?2>^|N=fD5} literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/up_arrow.png b/zeiterfassung/themes/dark_theme/up_arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..abcc7245212f19a5dbff1bb19647b1dd4bb05b6a GIT binary patch literal 158 zcmeAS@N?(olHy`uVBq!ia0y~yVBln6U|{25VPIg`eXhojfq{XsILO_JVcj{ImkbOH zOiAAEE)4(M`_JqLsV(q`EM{Qf76xHPhFNnY7#JAXOFVsD+3#?ObBOZ)?f;?2z`&s7 z>Eal|aXmTV0Pnx~?HeT~vP$qibn?*Rk(7{_$SR<9k%8gVR`yF}yEh$WU|?YIboFyt I=akR{015Ib8~^|S literal 0 HcmV?d00001 diff --git a/zeiterfassung/themes/dark_theme/up_arrow_disabled.png b/zeiterfassung/themes/dark_theme/up_arrow_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..b9c8e3b53519f086536accd2e8adb193b66817d8 GIT binary patch literal 159 zcmeAS@N?(olHy`uVBq!ia0y~yVBln6U|{25VPIg`eXhojfq{XsILO_JVcj{ImkbOH zOiAAEE)4(M`_JqLsV(q`EM{Qf76xHPhFNnY7#JAXOFVsD+3#?ObBM_QRKCg0z`&sF z>Eal|aXmTV0PnM!hK&*vS%X*}IB|$-GV$>AoM7UZn!&)ZW)1rUzf_Yo3=9kmp00i_ I>zopr0PfHyJpcdz literal 0 HcmV?d00001 diff --git a/zeiterfassung/translations/zeiterfassung_de.ts b/zeiterfassung/translations/zeiterfassung_de.ts new file mode 100644 index 0000000..a5213e5 --- /dev/null +++ b/zeiterfassung/translations/zeiterfassung_de.ts @@ -0,0 +1,134 @@ + + + + + bookingEndStrip + + + END + GEHEN + + + + bookingStartStrip + + + START + KOMMEN + + + + main + + + Loading settings... + Lade Einstellungen... + + + + Loading translations... + Lade Übersetzungen... + + + + + Invalid language selection! + Ungültige Sprachauswahl! + + + + You did not select a valid language! + Sie haben keine gültige Sprachauswahl getroffen! + + + + Loading theme... + Lade Aussehen... + + + + + + + Could not load theme! + Konnte Aussehen nicht laden! + + + + Theme file does not exist! + Aussehen-Datei existiert nicht! + + + + Loading login page... + Lade Login-Seite... + + + + + Could not access Zeiterfassung! + Konnte Zeiterfassung nicht erreichen! + + + + Base url + Basis URL + + + + Please enter the base url to the Zeiterfassung: + Bitte geben Sie die Basis URL zur Zeiterfassung ein: + + + + Authenticating... + Authentifiziere... + + + + + Could not authenticate with Zeiterfassung! + Konnte nicht mit Zeiterfassung authentifizieren! + + + + Getting user information... + Hole Benutzer Information... + + + + + Could not get user information! + Konnte Benutzer Information nicht holen! + + + + + Could not load plugin %0! + Konnte Plugin %0 nicht laden! + + + + + Plugin not valid %0! + Plugin %0 nicht gültig! + + + + Loading strip layouts... + Lade Streifenlayouts... + + + + + + + + + + + Could not load strips! + Konnte Streifenlayouts nicht laden! + + + diff --git a/zeiterfassung/translations/zeiterfassung_en.ts b/zeiterfassung/translations/zeiterfassung_en.ts new file mode 100644 index 0000000..f881ea3 --- /dev/null +++ b/zeiterfassung/translations/zeiterfassung_en.ts @@ -0,0 +1,134 @@ + + + + + bookingEndStrip + + + END + + + + + bookingStartStrip + + + START + + + + + main + + + Loading settings... + + + + + Loading translations... + + + + + + Invalid language selection! + + + + + You did not select a valid language! + + + + + Loading theme... + + + + + + + + Could not load theme! + + + + + Theme file does not exist! + + + + + Loading login page... + + + + + + Could not access Zeiterfassung! + + + + + Base url + + + + + Please enter the base url to the Zeiterfassung: + + + + + Authenticating... + + + + + + Could not authenticate with Zeiterfassung! + + + + + Getting user information... + + + + + + Could not get user information! + + + + + + Could not load plugin %0! + + + + + + Plugin not valid %0! + + + + + Loading strip layouts... + + + + + + + + + + + + Could not load strips! + + + + diff --git a/zeiterfassung/zeiterfassung.pro b/zeiterfassung/zeiterfassung.pro new file mode 100755 index 0000000..bb31fd8 --- /dev/null +++ b/zeiterfassung/zeiterfassung.pro @@ -0,0 +1,25 @@ +QT += core network gui widgets + +DBLIBS += zeiterfassungcore zeiterfassunggui + +TARGET = zeiterfassung + +PROJECT_ROOT = ../.. + +RC_ICONS = icon.ico + +SOURCES += main.cpp + +HEADERS += + +FORMS += strips/bookingstartstrip.ui \ + strips/bookingendstrip.ui \ + strips/timeassignmentstrip.ui + +RESOURCES += zeiterfassung_resources.qrc + +TRANSLATIONS += translations/zeiterfassung_en.ts \ + translations/zeiterfassung_de.ts + +include($${PROJECT_ROOT}/app.pri) +include(installs.pri) diff --git a/zeiterfassung/zeiterfassung_resources.qrc b/zeiterfassung/zeiterfassung_resources.qrc new file mode 100644 index 0000000..e5e1897 --- /dev/null +++ b/zeiterfassung/zeiterfassung_resources.qrc @@ -0,0 +1,5 @@ + + + images/splash.png + + diff --git a/zeiterfassungcorelib/cpp14polyfills.h b/zeiterfassungcorelib/cpp14polyfills.h new file mode 100644 index 0000000..d345a00 --- /dev/null +++ b/zeiterfassungcorelib/cpp14polyfills.h @@ -0,0 +1,43 @@ +#pragma once + +#if __cplusplus < 201402L && _MSC_VER < 1800 + +// std includes +#include +#include +#include +#include +#include + +namespace std { + template struct _Unique_if { + typedef unique_ptr _Single_object; + }; + + template struct _Unique_if { + typedef unique_ptr _Unknown_bound; + }; + + template struct _Unique_if { + typedef void _Known_bound; + }; + + template + typename _Unique_if::_Single_object + make_unique(Args&&... args) { + return unique_ptr(new T(std::forward(args)...)); + } + + template + typename _Unique_if::_Unknown_bound + make_unique(size_t n) { + typedef typename remove_extent::type U; + return unique_ptr(new U[n]()); + } + + template + typename _Unique_if::_Known_bound + make_unique(Args&&...) = delete; +} + +#endif // __cplusplus < 201402L diff --git a/zeiterfassungcorelib/replies/createbookingreply.cpp b/zeiterfassungcorelib/replies/createbookingreply.cpp new file mode 100644 index 0000000..0b73d39 --- /dev/null +++ b/zeiterfassungcorelib/replies/createbookingreply.cpp @@ -0,0 +1,26 @@ +#include "createbookingreply.h" + +CreateBookingReply::CreateBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &CreateBookingReply::requestFinished); +} + +void CreateBookingReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + // empty response so nothing to check + setSuccess(true); + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/createbookingreply.h b/zeiterfassungcorelib/replies/createbookingreply.h new file mode 100644 index 0000000..1fa6e1f --- /dev/null +++ b/zeiterfassungcorelib/replies/createbookingreply.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT CreateBookingReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit CreateBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; +}; diff --git a/zeiterfassungcorelib/replies/createtimeassignmentreply.cpp b/zeiterfassungcorelib/replies/createtimeassignmentreply.cpp new file mode 100644 index 0000000..68c6e7f --- /dev/null +++ b/zeiterfassungcorelib/replies/createtimeassignmentreply.cpp @@ -0,0 +1,64 @@ +#include "createtimeassignmentreply.h" + +#include +#include +#include +#include + +CreateTimeAssignmentReply::CreateTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)), + m_timeAssignmentId(-1) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &CreateTimeAssignmentReply::requestFinished); +} + +int CreateTimeAssignmentReply::timeAssignmentId() const +{ + return m_timeAssignmentId; +} + +void CreateTimeAssignmentReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isObject()) + { + setSuccess(false); + setMessage(tr("JSON document is not an object!")); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + setSuccess(false); + setMessage(tr("JSON does not contain bookingNr!")); + goto end; + } + + setSuccess(true); + m_timeAssignmentId = obj.value(QStringLiteral("bookingNr")).toInt(); + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/createtimeassignmentreply.h b/zeiterfassungcorelib/replies/createtimeassignmentreply.h new file mode 100644 index 0000000..451bb68 --- /dev/null +++ b/zeiterfassungcorelib/replies/createtimeassignmentreply.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT CreateTimeAssignmentReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit CreateTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + int timeAssignmentId() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + int m_timeAssignmentId; +}; diff --git a/zeiterfassungcorelib/replies/deletebookingreply.cpp b/zeiterfassungcorelib/replies/deletebookingreply.cpp new file mode 100644 index 0000000..f16916c --- /dev/null +++ b/zeiterfassungcorelib/replies/deletebookingreply.cpp @@ -0,0 +1,27 @@ +#include "deletebookingreply.h" + +DeleteBookingReply::DeleteBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &DeleteBookingReply::requestFinished); +} + +void DeleteBookingReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + //should be empty, so nothing to check... + + setSuccess(true); + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/deletebookingreply.h b/zeiterfassungcorelib/replies/deletebookingreply.h new file mode 100644 index 0000000..9a32f29 --- /dev/null +++ b/zeiterfassungcorelib/replies/deletebookingreply.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT DeleteBookingReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit DeleteBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; +}; diff --git a/zeiterfassungcorelib/replies/deletetimeassignmentreply.cpp b/zeiterfassungcorelib/replies/deletetimeassignmentreply.cpp new file mode 100644 index 0000000..2b54ddc --- /dev/null +++ b/zeiterfassungcorelib/replies/deletetimeassignmentreply.cpp @@ -0,0 +1,27 @@ +#include "deletetimeassignmentreply.h" + +DeleteTimeAssignmentReply::DeleteTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &DeleteTimeAssignmentReply::requestFinished); +} + +void DeleteTimeAssignmentReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + //only contains deleted id, so nothing to check here + + setSuccess(true); + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/deletetimeassignmentreply.h b/zeiterfassungcorelib/replies/deletetimeassignmentreply.h new file mode 100644 index 0000000..13c6b37 --- /dev/null +++ b/zeiterfassungcorelib/replies/deletetimeassignmentreply.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT DeleteTimeAssignmentReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit DeleteTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; +}; diff --git a/zeiterfassungcorelib/replies/getabsencesreply.cpp b/zeiterfassungcorelib/replies/getabsencesreply.cpp new file mode 100644 index 0000000..84c8ea5 --- /dev/null +++ b/zeiterfassungcorelib/replies/getabsencesreply.cpp @@ -0,0 +1,77 @@ +#include "getabsencesreply.h" + +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetAbsencesReply::GetAbsencesReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetAbsencesReply::requestFinished); +} + +const QVector &GetAbsencesReply::absences() const +{ + return m_absences; +} + +void GetAbsencesReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document is not an array!")); + goto end; + } + + auto arr = document.array(); + + setSuccess(true); + m_absences.clear(); + m_absences.reserve(arr.count()); + for(const auto &val : arr) + { + auto obj = val.toObject(); + + m_absences.append({ + obj.value(QStringLiteral("altRepresentative")).toInt(), + obj.value(QStringLiteral("compositeId")).toString(), + parseDate(obj.value(QStringLiteral("end"))), + obj.value(QStringLiteral("hourCategory")).toString(), + obj.value(QStringLiteral("openMarking")).toString(), + obj.value(QStringLiteral("persNr")).toInt(), + obj.value(QStringLiteral("representative")).toInt(), + parseDate(obj.value(QStringLiteral("start"))), + obj.value(QStringLiteral("text")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getabsencesreply.h b/zeiterfassungcorelib/replies/getabsencesreply.h new file mode 100644 index 0000000..57ed691 --- /dev/null +++ b/zeiterfassungcorelib/replies/getabsencesreply.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetAbsencesReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetAbsencesReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct Absence + { + int altRepresentative; + QString compositeId; + QDate end; + QString hourCategory; + QString openMarking; + int persNr; + int representative; + QDate start; + QString text; + }; + + const QVector &absences() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_absences; +}; diff --git a/zeiterfassungcorelib/replies/getbookingsreply.cpp b/zeiterfassungcorelib/replies/getbookingsreply.cpp new file mode 100644 index 0000000..5d7d95e --- /dev/null +++ b/zeiterfassungcorelib/replies/getbookingsreply.cpp @@ -0,0 +1,73 @@ +#include "getbookingsreply.h" + +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetBookingsReply::GetBookingsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetBookingsReply::requestFinished); +} + +const QVector &GetBookingsReply::bookings() const +{ + return m_bookings; +} + +void GetBookingsReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document is not an array!")); + goto end; + } + + auto arr = document.array(); + + setSuccess(true); + m_bookings.clear(); + m_bookings.reserve(arr.count()); + for(const auto &val : arr) + { + auto obj = val.toObject(); + + m_bookings.append({ + obj.value(QStringLiteral("bookingNr")).toInt(), + parseDate(obj.value(QStringLiteral("bookingDate"))), + parseTime(obj.value(QStringLiteral("bookingTime"))), + parseTime(obj.value(QStringLiteral("bookingTimespan"))), + obj.value(QStringLiteral("bookingType")).toString(), + obj.value(QStringLiteral("text")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getbookingsreply.h b/zeiterfassungcorelib/replies/getbookingsreply.h new file mode 100644 index 0000000..cf200ab --- /dev/null +++ b/zeiterfassungcorelib/replies/getbookingsreply.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetBookingsReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetBookingsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct Booking + { + int id; + QDate date; + QTime time; + QTime timespan; + QString type; + QString text; + }; + + const QVector &bookings() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_bookings; +}; diff --git a/zeiterfassungcorelib/replies/getdayinforeply.cpp b/zeiterfassungcorelib/replies/getdayinforeply.cpp new file mode 100644 index 0000000..2b20a01 --- /dev/null +++ b/zeiterfassungcorelib/replies/getdayinforeply.cpp @@ -0,0 +1,74 @@ +#include "getdayinforeply.h" + +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetDayinfoReply::GetDayinfoReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetDayinfoReply::requestFinished); +} + +const QVector &GetDayinfoReply::dayinfos() const +{ + return m_dayinfos; +} + +void GetDayinfoReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document is not an array!")); + goto end; + } + + auto arr = document.array(); + + setSuccess(true); + m_dayinfos.clear(); + m_dayinfos.reserve(arr.count()); + for(const auto &val : arr) + { + auto obj = val.toObject(); + + m_dayinfos.append({ + obj.value(QStringLiteral("className")).toString(), + obj.value(QStringLiteral("persNr")).toInt(), + parseDate(obj.value(QStringLiteral("date"))), + parseTime(obj.value(QStringLiteral("ist"))), + parseTime(obj.value(QStringLiteral("soll"))), + obj.value(QStringLiteral("compositeId")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getdayinforeply.h b/zeiterfassungcorelib/replies/getdayinforeply.h new file mode 100644 index 0000000..855b216 --- /dev/null +++ b/zeiterfassungcorelib/replies/getdayinforeply.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetDayinfoReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetDayinfoReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct Dayinfo + { + QString className; + int userId; + QDate date; + QTime ist; + QTime soll; + QString compositeId; + }; + + const QVector &dayinfos() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_dayinfos; +}; diff --git a/zeiterfassungcorelib/replies/getpresencestatusreply.cpp b/zeiterfassungcorelib/replies/getpresencestatusreply.cpp new file mode 100644 index 0000000..8dca891 --- /dev/null +++ b/zeiterfassungcorelib/replies/getpresencestatusreply.cpp @@ -0,0 +1,71 @@ +#include "getpresencestatusreply.h" + +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetPresenceStatusReply::GetPresenceStatusReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetPresenceStatusReply::requestFinished); +} + +const QVector &GetPresenceStatusReply::presenceStatuses() const +{ + return m_presenceStatuses; +} + +void GetPresenceStatusReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document is not an array!")); + goto end; + } + + auto arr = document.array(); + + setSuccess(true); + m_presenceStatuses.clear(); + m_presenceStatuses.reserve(arr.count()); + for(const auto &val : arr) + { + auto obj = val.toObject(); + + m_presenceStatuses.append({ + obj.value(QStringLiteral("persNr")).toInt(), + obj.value(QStringLiteral("firstName")).toString(), + obj.value(QStringLiteral("lastName")).toString(), + obj.value(QStringLiteral("presence")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getpresencestatusreply.h b/zeiterfassungcorelib/replies/getpresencestatusreply.h new file mode 100644 index 0000000..7360ba3 --- /dev/null +++ b/zeiterfassungcorelib/replies/getpresencestatusreply.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetPresenceStatusReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetPresenceStatusReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct PresenceStatus + { + int userId; + QString firstName; + QString lastName; + QString presence; + }; + + const QVector &presenceStatuses() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_presenceStatuses; +}; diff --git a/zeiterfassungcorelib/replies/getprojectsreply.cpp b/zeiterfassungcorelib/replies/getprojectsreply.cpp new file mode 100644 index 0000000..58b47d4 --- /dev/null +++ b/zeiterfassungcorelib/replies/getprojectsreply.cpp @@ -0,0 +1,87 @@ +#include "getprojectsreply.h" + +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetProjectsReply::GetProjectsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetProjectsReply::requestFinished); +} + +const QVector &GetProjectsReply::projects() const +{ + return m_projects; +} + +void GetProjectsReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isObject()) + { + setSuccess(false); + setMessage(tr("JSON document is not an object!")); + goto end; + } + + auto rootObj = document.object(); + + if(!rootObj.contains(QStringLiteral("elements"))) + { + setSuccess(false); + setMessage(tr("JSON does not contain elements!")); + goto end; + } + + auto elements = rootObj.value(QStringLiteral("elements")); + + if(!elements.isArray()) + { + setSuccess(false); + setMessage(tr("elements is not an array!")); + goto end; + } + + auto elementsArr = elements.toArray(); + + setSuccess(true); + m_projects.clear(); + m_projects.reserve(elementsArr.count()); + for(const auto &val : elementsArr) + { + auto obj = val.toObject(); + + m_projects.append({ + obj.value(QStringLiteral("label")).toString(), + obj.value(QStringLiteral("value")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getprojectsreply.h b/zeiterfassungcorelib/replies/getprojectsreply.h new file mode 100644 index 0000000..4f0d923 --- /dev/null +++ b/zeiterfassungcorelib/replies/getprojectsreply.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetProjectsReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetProjectsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct Project + { + QString label; + QString value; + }; + + const QVector &projects() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_projects; +}; diff --git a/zeiterfassungcorelib/replies/getreportreply.cpp b/zeiterfassungcorelib/replies/getreportreply.cpp new file mode 100644 index 0000000..41e004b --- /dev/null +++ b/zeiterfassungcorelib/replies/getreportreply.cpp @@ -0,0 +1,51 @@ +#include "getreportreply.h" + +#include "zeiterfassungapi.h" + +GetReportReply::GetReportReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetReportReply::request0Finished); +} + +const QByteArray &GetReportReply::content() const +{ + return m_content; +} + +void GetReportReply::request0Finished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + m_reply = Q_NULLPTR; + Q_EMIT finished(); + return; + } + + QUrl url(zeiterfassung()->url()); + url.setPath(QString(m_reply->readAll())); + + m_reply = std::unique_ptr(zeiterfassung()->manager()->get(QNetworkRequest(url))); + connect(m_reply.get(), &QNetworkReply::finished, this, &GetReportReply::request1Finished); +} + +void GetReportReply::request1Finished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + setSuccess(true); + m_content = m_reply->readAll(); + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getreportreply.h b/zeiterfassungcorelib/replies/getreportreply.h new file mode 100644 index 0000000..5721314 --- /dev/null +++ b/zeiterfassungcorelib/replies/getreportreply.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT GetReportReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetReportReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + const QByteArray &content() const; + +private Q_SLOTS: + void request0Finished(); + void request1Finished(); + +private: + std::unique_ptr m_reply; + QByteArray m_content; +}; diff --git a/zeiterfassungcorelib/replies/gettimeassignmentsreply.cpp b/zeiterfassungcorelib/replies/gettimeassignmentsreply.cpp new file mode 100644 index 0000000..57b272c --- /dev/null +++ b/zeiterfassungcorelib/replies/gettimeassignmentsreply.cpp @@ -0,0 +1,78 @@ +#include "gettimeassignmentsreply.h" + +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetTimeAssignmentsReply::GetTimeAssignmentsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &GetTimeAssignmentsReply::requestFinished); +} + +const QVector &GetTimeAssignmentsReply::timeAssignments() const +{ + return m_timeAssignments; +} + +void GetTimeAssignmentsReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document is not an array!")); + goto end; + } + + auto arr = document.array(); + + setSuccess(true); + m_timeAssignments.clear(); + m_timeAssignments.reserve(arr.count()); + for(const auto &val : arr) + { + auto obj = val.toObject(); + + auto koWertList = obj.value(QStringLiteral("koWertList")).toArray(); + + m_timeAssignments.append({ + obj.value(QStringLiteral("bookingNr")).toInt(), + parseDate(obj.value(QStringLiteral("bookingDate"))), + parseTime(obj.value(QStringLiteral("bookingTime"))), + parseTime(obj.value(QStringLiteral("bookingTimespan"))), + obj.value(QStringLiteral("text")).toString(), + koWertList.at(0).toObject().value(QStringLiteral("value")).toString(), + koWertList.at(1).toObject().value(QStringLiteral("value")).toString(), + koWertList.at(2).toObject().value(QStringLiteral("value")).toString() + }); + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/gettimeassignmentsreply.h b/zeiterfassungcorelib/replies/gettimeassignmentsreply.h new file mode 100644 index 0000000..63493a6 --- /dev/null +++ b/zeiterfassungcorelib/replies/gettimeassignmentsreply.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetTimeAssignmentsReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetTimeAssignmentsReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + struct TimeAssignment + { + int id; + QDate date; + QTime time; + QTime timespan; + QString text; + QString project; + QString subproject; + QString workpackage; + }; + + const QVector &timeAssignments() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + QVector m_timeAssignments; +}; diff --git a/zeiterfassungcorelib/replies/getuserinforeply.cpp b/zeiterfassungcorelib/replies/getuserinforeply.cpp new file mode 100644 index 0000000..640f165 --- /dev/null +++ b/zeiterfassungcorelib/replies/getuserinforeply.cpp @@ -0,0 +1,170 @@ +#include "getuserinforeply.h" + +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" + +GetUserInfoReply::GetUserInfoReply(std::unique_ptr &&reply0, std::unique_ptr &&reply1, + ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply0(std::move(reply0)), + m_reply1(std::move(reply1)) +{ + Q_ASSERT(m_reply0 != Q_NULLPTR); + Q_ASSERT(m_reply1 != Q_NULLPTR); + + connect(m_reply0.get(), &QNetworkReply::finished, this, &GetUserInfoReply::request0Finished); + connect(m_reply1.get(), &QNetworkReply::finished, this, &GetUserInfoReply::request1Finished); +} + +const GetUserInfoReply::UserInfo &GetUserInfoReply::userInfo() const +{ + return m_userInfo; +} + +void GetUserInfoReply::request0Finished() +{ + if(m_reply0->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request 0 error occured: %0").arg(m_reply0->errorString())); + m_reply1 = Q_NULLPTR; + goto end; + } + + { + QJsonParseError error; + auto document = QJsonDocument::fromJson(m_reply0->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON 0 failed: %0").arg(error.errorString())); + m_reply1 = Q_NULLPTR; + goto end; + } + + if(!document.isObject()) + { + setSuccess(false); + setMessage(tr("JSON document 0 is not an object!")); + m_reply1 = Q_NULLPTR; + goto end; + } + + auto rootObj = document.object(); + + if(!rootObj.contains(QStringLiteral("evoAppsUser"))) + { + setSuccess(false); + setMessage(tr("JSON 0 does not contain evoAppsUser!")); + m_reply1 = Q_NULLPTR; + goto end; + } + + auto evoAppsUser = rootObj.value(QStringLiteral("evoAppsUser")); + + if(!evoAppsUser.isObject()) + { + setSuccess(false); + setMessage(tr("evoAppsUser is not an object!")); + m_reply1 = Q_NULLPTR; + goto end; + } + + auto evoAppsUserObj = evoAppsUser.toObject(); + + if(!m_reply1) + setSuccess(true); + + m_userInfo.userId = evoAppsUserObj.value(QStringLiteral("persNr")).toInt(); + m_userInfo.email = evoAppsUserObj.value(QStringLiteral("email")).toString(); + m_userInfo.longUsername = evoAppsUserObj.value(QStringLiteral("longUsername")).toString(); + m_userInfo.text = evoAppsUserObj.value(QStringLiteral("text")).toString(); + m_userInfo.username = evoAppsUserObj.value(QStringLiteral("username")).toString(); + } + + end: + m_reply0 = Q_NULLPTR; + + if(!m_reply1) + Q_EMIT finished(); +} + +void GetUserInfoReply::request1Finished() +{ + if(m_reply1->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request 1 error occured: %0").arg(m_reply0->errorString())); + m_reply0 = Q_NULLPTR; + goto end; + } + + { + QJsonParseError error; + auto document = QJsonDocument::fromJson(m_reply1->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON 1 failed: %0").arg(error.errorString())); + m_reply0 = Q_NULLPTR; + goto end; + } + + if(!document.isArray()) + { + setSuccess(false); + setMessage(tr("JSON document 1 is not an array!")); + m_reply0 = Q_NULLPTR; + goto end; + } + + auto arr = document.array(); + + if(arr.isEmpty()) + { + setSuccess(false); + setMessage(tr("JSON array 1 is empty!")); + m_reply0 = Q_NULLPTR; + goto end; + } + + auto first = arr.first(); + + if(!first.isObject()) + { + setSuccess(false); + setMessage(tr("JSON array value is not an object!")); + m_reply0 = Q_NULLPTR; + goto end; + } + + auto obj = first.toObject(); + + if(!m_reply0) + setSuccess(true); + + m_userInfo.street = obj.value(QStringLiteral("gemeinde")).toString(); + m_userInfo.city = obj.value(QStringLiteral("ort")).toString(); + m_userInfo.employedSince = parseDate(obj.value(QStringLiteral("angFrom"))); + m_userInfo.employedTill = parseDate(obj.value(QStringLiteral("angTill"))); + m_userInfo.placeOfBirth = obj.value(QStringLiteral("gebOrt")).toString(); + m_userInfo.zipcode = obj.value(QStringLiteral("plz")).toString(); + m_userInfo.religion = obj.value(QStringLiteral("religion")).toString(); + m_userInfo.department = obj.value(QStringLiteral("bereich")).toString(); + m_userInfo.verwendgr = obj.value(QStringLiteral("verwendgr")).toString(); + m_userInfo.taetig = obj.value(QStringLiteral("taetig")).toString(); + m_userInfo.arbverh = obj.value(QStringLiteral("arbverh")).toString(); + m_userInfo.betriebsnr = obj.value(QStringLiteral("betriebsnr")).toString(); + } + + end: + m_reply1 = Q_NULLPTR; + + if(!m_reply0) + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/getuserinforeply.h b/zeiterfassungcorelib/replies/getuserinforeply.h new file mode 100644 index 0000000..ef20ff6 --- /dev/null +++ b/zeiterfassungcorelib/replies/getuserinforeply.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT GetUserInfoReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit GetUserInfoReply(std::unique_ptr &&reply0, std::unique_ptr &&reply1, + ZeiterfassungApi *zeiterfassung); + + struct UserInfo + { + int userId; + QString email; + QString longUsername; + QString text; + QString username; + QString street; + QString city; + QDate employedSince; + QDate employedTill; + QString placeOfBirth; + QString zipcode; + QString religion; + QString department; + QString verwendgr; + QString taetig; + QString arbverh; + QString betriebsnr; + }; + + const UserInfo &userInfo() const; + +private Q_SLOTS: + void request0Finished(); + void request1Finished(); + +private: + std::unique_ptr m_reply0; + std::unique_ptr m_reply1; + UserInfo m_userInfo; +}; diff --git a/zeiterfassungcorelib/replies/loginpagereply.cpp b/zeiterfassungcorelib/replies/loginpagereply.cpp new file mode 100644 index 0000000..1157581 --- /dev/null +++ b/zeiterfassungcorelib/replies/loginpagereply.cpp @@ -0,0 +1,34 @@ +#include "loginpagereply.h" + +#include + +LoginPageReply::LoginPageReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &LoginPageReply::requestFinished); +} + +void LoginPageReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + if(!m_reply->readAll().contains(QByteArrayLiteral("evoApps Anmeldung"))) + { + setSuccess(false); + setMessage(tr("Could not find necessary keywords in login page!")); + goto end; + } + + setSuccess(true); + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/loginpagereply.h b/zeiterfassungcorelib/replies/loginpagereply.h new file mode 100644 index 0000000..50342dd --- /dev/null +++ b/zeiterfassungcorelib/replies/loginpagereply.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT LoginPageReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit LoginPageReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; +}; diff --git a/zeiterfassungcorelib/replies/loginreply.cpp b/zeiterfassungcorelib/replies/loginreply.cpp new file mode 100644 index 0000000..abc64ca --- /dev/null +++ b/zeiterfassungcorelib/replies/loginreply.cpp @@ -0,0 +1,54 @@ +#include "loginreply.h" + +#include + +LoginReply::LoginReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &LoginReply::requestFinished); +} + +void LoginReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + if(!m_reply->hasRawHeader(QByteArrayLiteral("Location"))) + { + setSuccess(false); + setMessage(tr("Response did not contain a Location header.")); + goto end; + } + + { + auto location = m_reply->rawHeader(QByteArrayLiteral("Location")); + + if(location == QByteArrayLiteral("/evoApps/pages/home.jsp")) + { + setSuccess(true); + goto end; + } + else if(location == QByteArrayLiteral("/evoApps/pages/login.jsp?error=user")) + { + setSuccess(false); + setMessage(tr("Authentication failure. Please check username and password.")); + goto end; + } + else + { + setSuccess(false); + setMessage(tr("An unknown authentication failure occured. Redirected to: %0").arg(QString(location))); + goto end; + } + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/loginreply.h b/zeiterfassungcorelib/replies/loginreply.h new file mode 100644 index 0000000..3a6ec29 --- /dev/null +++ b/zeiterfassungcorelib/replies/loginreply.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT LoginReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit LoginReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; +}; diff --git a/zeiterfassungcorelib/replies/updatebookingreply.cpp b/zeiterfassungcorelib/replies/updatebookingreply.cpp new file mode 100644 index 0000000..5386d22 --- /dev/null +++ b/zeiterfassungcorelib/replies/updatebookingreply.cpp @@ -0,0 +1,59 @@ +#include "updatebookingreply.h" + +#include +#include +#include +#include + +UpdateBookingReply::UpdateBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)), + m_bookingId(-1) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &UpdateBookingReply::requestFinished); +} + +void UpdateBookingReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isObject()) + { + setSuccess(false); + setMessage(tr("JSON document is not an object!")); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + setSuccess(false); + setMessage(tr("JSON does not contain bookingNr!")); + goto end; + } + + setSuccess(true); + m_bookingId = obj.value(QStringLiteral("bookingNr")).toInt(); + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/updatebookingreply.h b/zeiterfassungcorelib/replies/updatebookingreply.h new file mode 100644 index 0000000..0741045 --- /dev/null +++ b/zeiterfassungcorelib/replies/updatebookingreply.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT UpdateBookingReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit UpdateBookingReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + int m_bookingId; +}; diff --git a/zeiterfassungcorelib/replies/updatetimeassignmentreply.cpp b/zeiterfassungcorelib/replies/updatetimeassignmentreply.cpp new file mode 100644 index 0000000..100981d --- /dev/null +++ b/zeiterfassungcorelib/replies/updatetimeassignmentreply.cpp @@ -0,0 +1,64 @@ +#include "updatetimeassignmentreply.h" + +#include +#include +#include +#include + +UpdateTimeAssignmentReply::UpdateTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung) : + ZeiterfassungReply(zeiterfassung), + m_reply(std::move(reply)), + m_timeAssignmentId(-1) +{ + connect(m_reply.get(), &QNetworkReply::finished, this, &UpdateTimeAssignmentReply::requestFinished); +} + +int UpdateTimeAssignmentReply::timeAssignmentId() const +{ + return m_timeAssignmentId; +} + +void UpdateTimeAssignmentReply::requestFinished() +{ + if(m_reply->error() != QNetworkReply::NoError) + { + setSuccess(false); + setMessage(tr("Request error occured: %0").arg(m_reply->errorString())); + goto end; + } + + { + QJsonParseError error; + QJsonDocument document = QJsonDocument::fromJson(m_reply->readAll(), &error); + if(error.error != QJsonParseError::NoError) + { + setSuccess(false); + setMessage(tr("Parsing JSON failed: %0").arg(error.errorString())); + goto end; + } + + if(!document.isObject()) + { + setSuccess(false); + setMessage(tr("JSON document is not an object!")); + goto end; + } + + auto obj = document.object(); + + if(!obj.contains(QStringLiteral("bookingNr"))) + { + setSuccess(false); + setMessage(tr("JSON does not contain bookingNr!")); + goto end; + } + + setSuccess(true); + m_timeAssignmentId = obj.value(QStringLiteral("bookingNr")).toInt(); + } + + end: + m_reply = Q_NULLPTR; + + Q_EMIT finished(); +} diff --git a/zeiterfassungcorelib/replies/updatetimeassignmentreply.h b/zeiterfassungcorelib/replies/updatetimeassignmentreply.h new file mode 100644 index 0000000..f13a4d6 --- /dev/null +++ b/zeiterfassungcorelib/replies/updatetimeassignmentreply.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +#include "zeiterfassungcorelib_global.h" +#include "zeiterfassungreply.h" + +class ZEITERFASSUNGCORELIB_EXPORT UpdateTimeAssignmentReply : public ZeiterfassungReply +{ + Q_OBJECT + +public: + explicit UpdateTimeAssignmentReply(std::unique_ptr &&reply, ZeiterfassungApi *zeiterfassung); + + int timeAssignmentId() const; + +private Q_SLOTS: + void requestFinished(); + +private: + std::unique_ptr m_reply; + int m_timeAssignmentId; +}; diff --git a/zeiterfassungcorelib/replies/zeiterfassungreply.cpp b/zeiterfassungcorelib/replies/zeiterfassungreply.cpp new file mode 100644 index 0000000..2270dc1 --- /dev/null +++ b/zeiterfassungcorelib/replies/zeiterfassungreply.cpp @@ -0,0 +1,62 @@ +#include "zeiterfassungreply.h" + +#include +#include + +#include "zeiterfassungapi.h" + +ZeiterfassungReply::ZeiterfassungReply(ZeiterfassungApi *zeiterfassung) : + QObject(zeiterfassung), + m_zeiterfassung(zeiterfassung), + m_success(false) +{ + +} + +bool ZeiterfassungReply::success() const +{ + return m_success; +} + +const QString &ZeiterfassungReply::message() const +{ + return m_message; +} + +void ZeiterfassungReply::waitForFinished() +{ + QEventLoop eventLoop; + connect(this, &ZeiterfassungReply::finished, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); +} + +QDate ZeiterfassungReply::parseDate(const QJsonValue &value) +{ + if(value.isNull()) + return QDate(); + + return QDate::fromString(QString::number(value.toInt()), QStringLiteral("yyyyMMdd")); +} + +QTime ZeiterfassungReply::parseTime(const QJsonValue &value) +{ + if(value.isNull()) + return QTime(); + + return QTime::fromString(QStringLiteral("%0").arg(value.toInt(), 6, 10, QChar('0')), QStringLiteral("HHmmss")); +} + +ZeiterfassungApi *ZeiterfassungReply::zeiterfassung() const +{ + return m_zeiterfassung; +} + +void ZeiterfassungReply::setSuccess(bool success) +{ + m_success = success; +} + +void ZeiterfassungReply::setMessage(const QString &message) +{ + m_message = message; +} diff --git a/zeiterfassungcorelib/replies/zeiterfassungreply.h b/zeiterfassungcorelib/replies/zeiterfassungreply.h new file mode 100644 index 0000000..8a46aa5 --- /dev/null +++ b/zeiterfassungcorelib/replies/zeiterfassungreply.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "zeiterfassungcorelib_global.h" + +class QJsonValue; + +class ZeiterfassungApi; + +class ZEITERFASSUNGCORELIB_EXPORT ZeiterfassungReply : public QObject +{ + Q_OBJECT + +public: + explicit ZeiterfassungReply(ZeiterfassungApi *zeiterfassung); + + bool success() const; + const QString &message() const; + + void waitForFinished(); + + static QDate parseDate(const QJsonValue &value); + static QTime parseTime(const QJsonValue &value); + +Q_SIGNALS: + void finished(); + +protected: + ZeiterfassungApi *zeiterfassung() const; + void setSuccess(bool success); + void setMessage(const QString &message); + +private: + ZeiterfassungApi *m_zeiterfassung; + bool m_success; + QString m_message; +}; diff --git a/zeiterfassungcorelib/timeutils.cpp b/zeiterfassungcorelib/timeutils.cpp new file mode 100644 index 0000000..e20eb62 --- /dev/null +++ b/zeiterfassungcorelib/timeutils.cpp @@ -0,0 +1,23 @@ +#include "timeutils.h" + +int timeToSeconds(const QTime &time) +{ + return QTime(0, 0).secsTo(time); +} + +QTime timeBetween(const QTime &l, const QTime &r) +{ + Q_ASSERT(l <= r); + return QTime(0, 0).addSecs(l.secsTo(r)); +} + +QTime timeAdd(const QTime &l, const QTime &r) +{ + Q_ASSERT(timeToSeconds(l) + timeToSeconds(r) < 86400); + return l.addSecs(QTime(0, 0).secsTo(r)); +} + +QTime timeNormalise(const QTime &time) +{ + return QTime(time.hour(), time.minute()); +} diff --git a/zeiterfassungcorelib/timeutils.h b/zeiterfassungcorelib/timeutils.h new file mode 100644 index 0000000..905ca05 --- /dev/null +++ b/zeiterfassungcorelib/timeutils.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +#include "zeiterfassungcorelib_global.h" + +int ZEITERFASSUNGCORELIB_EXPORT timeToSeconds(const QTime &time); +QTime ZEITERFASSUNGCORELIB_EXPORT timeBetween(const QTime &l, const QTime &r); +QTime ZEITERFASSUNGCORELIB_EXPORT timeAdd(const QTime &l, const QTime &r); +QTime ZEITERFASSUNGCORELIB_EXPORT timeNormalise(const QTime &time); diff --git a/zeiterfassungcorelib/translations/zeiterfassungcorelib_de.ts b/zeiterfassungcorelib/translations/zeiterfassungcorelib_de.ts new file mode 100644 index 0000000..8a1a595 --- /dev/null +++ b/zeiterfassungcorelib/translations/zeiterfassungcorelib_de.ts @@ -0,0 +1,295 @@ + + + + + CreateBookingReply + + + Request error occured: %0 + + + + + CreateTimeAssignmentReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + + DeleteBookingReply + + + Request error occured: %0 + + + + + DeleteTimeAssignmentReply + + + Request error occured: %0 + + + + + GetAbsencesReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetBookingsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetPresenceStatusReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetProjectsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain elements! + + + + + elements is not an array! + + + + + GetReportReply + + + + Request error occured: %0 + + + + + GetTimeAssignmentsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetUserInfoReply + + + Request 0 error occured: %0 + + + + + Parsing JSON 0 failed: %0 + + + + + JSON document 0 is not an object! + + + + + JSON 0 does not contain evoAppsUser! + + + + + evoAppsUser is not an object! + + + + + Request 1 error occured: %0 + + + + + Parsing JSON 1 failed: %0 + + + + + JSON document 1 is not an array! + + + + + JSON array 1 is empty! + + + + + JSON array value is not an object! + + + + + LoginPageReply + + + Request error occured: %0 + + + + + Could not find necessary keywords in login page! + + + + + LoginReply + + + Request error occured: %0 + + + + + Response did not contain a Location header. + + + + + Authentication failure. Please check username and password. + + + + + An unknown authentication failure occured. Redirected to: %0 + + + + + UpdateBookingReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + + UpdateTimeAssignmentReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + diff --git a/zeiterfassungcorelib/translations/zeiterfassungcorelib_en.ts b/zeiterfassungcorelib/translations/zeiterfassungcorelib_en.ts new file mode 100644 index 0000000..402d10b --- /dev/null +++ b/zeiterfassungcorelib/translations/zeiterfassungcorelib_en.ts @@ -0,0 +1,295 @@ + + + + + CreateBookingReply + + + Request error occured: %0 + + + + + CreateTimeAssignmentReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + + DeleteBookingReply + + + Request error occured: %0 + + + + + DeleteTimeAssignmentReply + + + Request error occured: %0 + + + + + GetAbsencesReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetBookingsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetPresenceStatusReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetProjectsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain elements! + + + + + elements is not an array! + + + + + GetReportReply + + + + Request error occured: %0 + + + + + GetTimeAssignmentsReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an array! + + + + + GetUserInfoReply + + + Request 0 error occured: %0 + + + + + Parsing JSON 0 failed: %0 + + + + + JSON document 0 is not an object! + + + + + JSON 0 does not contain evoAppsUser! + + + + + evoAppsUser is not an object! + + + + + Request 1 error occured: %0 + + + + + Parsing JSON 1 failed: %0 + + + + + JSON document 1 is not an array! + + + + + JSON array 1 is empty! + + + + + JSON array value is not an object! + + + + + LoginPageReply + + + Request error occured: %0 + + + + + Could not find necessary keywords in login page! + + + + + LoginReply + + + Request error occured: %0 + + + + + Response did not contain a Location header. + + + + + Authentication failure. Please check username and password. + + + + + An unknown authentication failure occured. Redirected to: %0 + + + + + UpdateBookingReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + + UpdateTimeAssignmentReply + + + Request error occured: %0 + + + + + Parsing JSON failed: %0 + + + + + JSON document is not an object! + + + + + JSON does not contain bookingNr! + + + + diff --git a/zeiterfassungcorelib/zeiterfassungapi.cpp b/zeiterfassungcorelib/zeiterfassungapi.cpp new file mode 100644 index 0000000..78f2803 --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungapi.cpp @@ -0,0 +1,324 @@ +#include "zeiterfassungapi.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "replies/loginpagereply.h" +#include "replies/loginreply.h" +#include "replies/getuserinforeply.h" +#include "replies/getbookingsreply.h" +#include "replies/createbookingreply.h" +#include "replies/updatebookingreply.h" +#include "replies/deletebookingreply.h" +#include "replies/gettimeassignmentsreply.h" +#include "replies/createtimeassignmentreply.h" +#include "replies/updatetimeassignmentreply.h" +#include "replies/deletetimeassignmentreply.h" +#include "replies/getprojectsreply.h" +#include "replies/getreportreply.h" +#include "replies/getpresencestatusreply.h" +#include "replies/getabsencesreply.h" +#include "replies/getdayinforeply.h" + +//add support for pre cpp14 compilers +#include "cpp14polyfills.h" + +ZeiterfassungApi::ZeiterfassungApi(const QUrl &url, QObject *parent) : + QObject(parent), + m_url(url), + m_manager(new QNetworkAccessManager(this)) +{ +} + +const QUrl &ZeiterfassungApi::url() const +{ + return m_url; +} + +void ZeiterfassungApi::setUrl(const QUrl &url) +{ + m_url = url; +} + +QNetworkAccessManager *ZeiterfassungApi::manager() const +{ + return m_manager; +} + +std::unique_ptr ZeiterfassungApi::doLoginPage() +{ + QNetworkRequest request(QUrl(m_url.toString() % "pages/login.jsp")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doLogin(const QString &username, const QString &password) +{ + QNetworkRequest request(QUrl(m_url.toString() % "pages/j_spring_security_check")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/x-www-form-urlencoded")); + request.setMaximumRedirectsAllowed(0); + + auto data = QStringLiteral("j_username=%0&j_password=%1&login=Anmelden").arg(username).arg(password).toUtf8(); + + return std::make_unique(std::unique_ptr(m_manager->post(request, data)), this); +} + +std::unique_ptr ZeiterfassungApi::doUserInfo() +{ + QNetworkRequest request0(QUrl(m_url.toString() % "json/evoAppsUserInfoDialogController/load-EvoAppsUserInfoTO")); + request0.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("home")); + + QNetworkRequest request1(QUrl(m_url.toString() % "json/persons")); + request1.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request0)), + std::unique_ptr(m_manager->get(request1)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetBookings(int userId, const QDate &start, const QDate &end) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/bookings?start=%1&end=%2&pnrLst=%3") + .arg(m_url.toString()) + .arg(formatDate(start)) + .arg(formatDate(end)) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doCreateBooking(int userId, const QDate &date, const QTime &time, const QTime ×pan, + const QString &type, const QString &text) +{ + QNetworkRequest request(QUrl(m_url.toString() % "json/booking")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = formatDate(date).toInt(); + obj[QStringLiteral("bookingTime")] = formatTime(time).toInt(); + obj[QStringLiteral("bookingTimespan")] = formatTime(timespan).toInt(); + obj[QStringLiteral("bookingType")] = type; + obj[QStringLiteral("hourCategory")] = QStringLiteral(""); + obj[QStringLiteral("empfEinh")] = QStringLiteral(""); + obj[QStringLiteral("bewEinh")] = QStringLiteral(""); + obj[QStringLiteral("text")] = text; + + auto data = QJsonDocument(obj).toJson(); + + return std::make_unique(std::unique_ptr(m_manager->post(request, data)), this); +} + +std::unique_ptr ZeiterfassungApi::doUpdateBooking(int bookingId, int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &type, const QString &text) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/booking/%1").arg(m_url.toString()).arg(bookingId))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = bookingId; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = formatDate(date).toInt(); + obj[QStringLiteral("bookingTime")] = formatTime(time).toInt(); + obj[QStringLiteral("bookingTimespan")] = formatTime(timespan).toInt(); + obj[QStringLiteral("bookingType")] = type; + obj[QStringLiteral("hourCategory")] = QStringLiteral(""); + obj[QStringLiteral("empfEinh")] = QStringLiteral(""); + obj[QStringLiteral("bewEinh")] = QStringLiteral(""); + obj[QStringLiteral("text")] = text; + + auto data = QJsonDocument(obj).toJson(); + + return std::make_unique(std::unique_ptr(m_manager->put(request, data)), this); +} + +std::unique_ptr ZeiterfassungApi::doDeleteBooking(int bookingId) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/booking/%1?text=") + .arg(m_url.toString()) + .arg(bookingId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->deleteResource(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetTimeAssignments(int userId, const QDate &start, const QDate &end) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking?start=%1&end=%2&pnrLst=%3") + .arg(m_url.toString()) + .arg(formatDate(start)) + .arg(formatDate(end)) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doCreateTimeAssignment(int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &project, + const QString &subproject, const QString &workpackage, + const QString &text) +{ + QNetworkRequest request(QUrl(m_url.toString() % "json/azebooking")); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = QJsonValue::Null; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = formatDate(date).toInt(); + obj[QStringLiteral("bookingTime")] = formatTime(time).toInt(); + obj[QStringLiteral("bookingTimespan")] = formatTime(timespan).toInt(); + obj[QStringLiteral("text")] = text; + { + QJsonArray koWertList; + { + QJsonObject obj; + obj[QStringLiteral("value")] = project; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = subproject; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = workpackage; + koWertList << obj; + } + obj[QStringLiteral("koWertList")] = koWertList; + } + + auto data = QJsonDocument(obj).toJson(); + + return std::make_unique(std::unique_ptr(m_manager->post(request, data)), this); +} + +std::unique_ptr ZeiterfassungApi::doUpdateTimeAssignment(int timeAssignmentId, int userId, const QDate &date, + const QTime &time, const QTime ×pan, const QString &project, + const QString &subproject, const QString &workpackage, + const QString &text) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking/%1").arg(m_url.toString()).arg(timeAssignmentId))); + request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArrayLiteral("application/json")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + QJsonObject obj; + obj[QStringLiteral("bookingNr")] = timeAssignmentId; + obj[QStringLiteral("persNr")] = userId; + obj[QStringLiteral("bookingDate")] = formatDate(date).toInt(); + obj[QStringLiteral("bookingTime")] = formatTime(time).toInt(); + obj[QStringLiteral("bookingTimespan")] = formatTime(timespan).toInt(); + obj[QStringLiteral("bookingType")] = QJsonValue::Null; + obj[QStringLiteral("hourCategory")] = QJsonValue::Null; + obj[QStringLiteral("bewEinh")] = QJsonValue::Null; + obj[QStringLiteral("empfEinh")] = QJsonValue::Null; + obj[QStringLiteral("einstuf")] = 0; + obj[QStringLiteral("text")] = text; + { + QJsonArray koWertList; + { + QJsonObject obj; + obj[QStringLiteral("value")] = project; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = subproject; + koWertList << obj; + } + { + QJsonObject obj; + obj[QStringLiteral("value")] = workpackage; + koWertList << obj; + } + obj[QStringLiteral("koWertList")] = koWertList; + } + + auto data = QJsonDocument(obj).toJson(); + + return std::make_unique(std::unique_ptr(m_manager->put(request, data)), this); +} + +std::unique_ptr ZeiterfassungApi::doDeleteTimeAssignment(int timeAssignmentId) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/azebooking/%1") + .arg(m_url.toString()) + .arg(timeAssignmentId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->deleteResource(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetProjects(int userId, const QDate &date) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/combobox?persnr=%1&date=%2&dqkey=KOST&kowert0=&kowert1=&kowert2=&term=") + .arg(m_url.toString()) + .arg(userId) + .arg(formatDate(date)))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetReport(int userId, const QDate &date) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/auswertung/month?persNr=%1&date=%2") + .arg(m_url.toString()) + .arg(userId) + .arg(formatDate(date)))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetPresenceStatus() +{ + QNetworkRequest request(QUrl(m_url.toString() % "json/presencestatus")); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("presenceStatus")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetAbsences(int userId, const QDate &start, const QDate &end) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/fulldayAbsences?start=%1&end=%2&pnrLst=%3") + .arg(m_url.toString()) + .arg(formatDate(start)) + .arg(formatDate(end)) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +std::unique_ptr ZeiterfassungApi::doGetDayinfo(int userId, const QDate &start, const QDate &end) +{ + QNetworkRequest request(QUrl(QStringLiteral("%0json/dayinfo?start=%1&end=%2&pnrLst=%3") + .arg(m_url.toString()) + .arg(formatDate(start)) + .arg(formatDate(end)) + .arg(userId))); + request.setRawHeader(QByteArrayLiteral("sisAppName"), QByteArrayLiteral("bookingCalendar")); + + return std::make_unique(std::unique_ptr(m_manager->get(request)), this); +} + +QString ZeiterfassungApi::formatDate(const QDate &date) +{ + return date.toString(QStringLiteral("yyyyMMdd")); +} + +QString ZeiterfassungApi::formatTime(const QTime &time) +{ + return time.toString(QStringLiteral("Hmmss")); +} diff --git a/zeiterfassungcorelib/zeiterfassungapi.h b/zeiterfassungcorelib/zeiterfassungapi.h new file mode 100644 index 0000000..edd4bc6 --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungapi.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" + +class QNetworkAccessManager; + +class LoginPageReply; +class LoginReply; +class GetUserInfoReply; +class GetBookingsReply; +class CreateBookingReply; +class UpdateBookingReply; +class DeleteBookingReply; +class GetTimeAssignmentsReply; +class CreateTimeAssignmentReply; +class UpdateTimeAssignmentReply; +class DeleteTimeAssignmentReply; +class GetProjectsReply; +class GetReportReply; +class GetPresenceStatusReply; +class GetAbsencesReply; +class GetDayinfoReply; + +class ZEITERFASSUNGCORELIB_EXPORT ZeiterfassungApi : public QObject +{ + Q_OBJECT + +public: + explicit ZeiterfassungApi(const QUrl &url, QObject *parent = Q_NULLPTR); + + const QUrl &url() const; + void setUrl(const QUrl &url); + + QNetworkAccessManager *manager() const; + + std::unique_ptr doLoginPage(); + std::unique_ptr doLogin(const QString &username, const QString &password); + std::unique_ptr doUserInfo(); + + std::unique_ptr doGetBookings(int userId, const QDate &start, const QDate &end); + std::unique_ptr doCreateBooking(int userId, const QDate &date, const QTime &time, const QTime ×pan, + const QString &type, const QString &text); + std::unique_ptr doUpdateBooking(int bookingId, int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &type, const QString &text); + std::unique_ptr doDeleteBooking(int bookingId); + + std::unique_ptr doGetTimeAssignments(int userId, const QDate &start, const QDate &end); + std::unique_ptr doCreateTimeAssignment(int userId, const QDate &date, const QTime &time, + const QTime ×pan, const QString &project, + const QString &subproject, const QString &workpackage, + const QString &text); + std::unique_ptr doUpdateTimeAssignment(int timeAssignmentId, int userId, const QDate &date, + const QTime &time, const QTime ×pan, const QString &project, + const QString &subproject, const QString &workpackage, + const QString &text); + std::unique_ptr doDeleteTimeAssignment(int timeAssignmentId); + + std::unique_ptr doGetProjects(int userId, const QDate &date); + std::unique_ptr doGetReport(int userId, const QDate &date); + std::unique_ptr doGetPresenceStatus(); + std::unique_ptr doGetAbsences(int userId, const QDate &start, const QDate &end); + std::unique_ptr doGetDayinfo(int userId, const QDate &start, const QDate &end); + +private: + static QString formatDate(const QDate &date); + static QString formatTime(const QTime &time); + + QUrl m_url; + QNetworkAccessManager *m_manager; +}; diff --git a/zeiterfassungcorelib/zeiterfassungcorelib.pro b/zeiterfassungcorelib/zeiterfassungcorelib.pro new file mode 100644 index 0000000..5fdcf4a --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungcorelib.pro @@ -0,0 +1,63 @@ +QT += core network +QT -= gui widgets + +DBLIBS += + +TARGET = zeiterfassungcore + +PROJECT_ROOT = ../.. + +DEFINES += ZEITERFASSUNGCORELIB_LIBRARY + +SOURCES += timeutils.cpp \ + zeiterfassungapi.cpp \ + zeiterfassungsettings.cpp \ + replies/createbookingreply.cpp \ + replies/createtimeassignmentreply.cpp \ + replies/deletebookingreply.cpp \ + replies/deletetimeassignmentreply.cpp \ + replies/getabsencesreply.cpp \ + replies/getbookingsreply.cpp \ + replies/getdayinforeply.cpp \ + replies/getpresencestatusreply.cpp \ + replies/getprojectsreply.cpp \ + replies/getreportreply.cpp \ + replies/gettimeassignmentsreply.cpp \ + replies/getuserinforeply.cpp \ + replies/loginpagereply.cpp \ + replies/loginreply.cpp \ + replies/updatebookingreply.cpp \ + replies/updatetimeassignmentreply.cpp \ + replies/zeiterfassungreply.cpp + +HEADERS += cpp14polyfills.h \ + timeutils.h \ + zeiterfassungapi.h \ + zeiterfassungcorelib_global.h \ + zeiterfassungsettings.h \ + replies/createbookingreply.h \ + replies/createtimeassignmentreply.h \ + replies/deletebookingreply.h \ + replies/deletetimeassignmentreply.h \ + replies/getabsencesreply.h \ + replies/getbookingsreply.h \ + replies/getdayinforeply.h \ + replies/getpresencestatusreply.h \ + replies/getprojectsreply.h \ + replies/getreportreply.h \ + replies/gettimeassignmentsreply.h \ + replies/getuserinforeply.h \ + replies/loginpagereply.h \ + replies/loginreply.h \ + replies/updatebookingreply.h \ + replies/updatetimeassignmentreply.h \ + replies/zeiterfassungreply.h + +FORMS += + +RESOURCES += + +TRANSLATIONS += translations/zeiterfassungcorelib_en.ts \ + translations/zeiterfassungcorelib_de.ts + +include($${PROJECT_ROOT}/lib.pri) diff --git a/zeiterfassungcorelib/zeiterfassungcorelib_global.h b/zeiterfassungcorelib/zeiterfassungcorelib_global.h new file mode 100644 index 0000000..dd5e1c9 --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungcorelib_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(ZEITERFASSUNGCORELIB_LIBRARY) +# define ZEITERFASSUNGCORELIB_EXPORT Q_DECL_EXPORT +#else +# define ZEITERFASSUNGCORELIB_EXPORT Q_DECL_IMPORT +#endif diff --git a/zeiterfassungcorelib/zeiterfassungsettings.cpp b/zeiterfassungcorelib/zeiterfassungsettings.cpp new file mode 100644 index 0000000..590872f --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungsettings.cpp @@ -0,0 +1,311 @@ +#include "zeiterfassungsettings.h" + +#include + +const QString ZeiterfassungSettings::m_language("language"); +const QString ZeiterfassungSettings::m_url("url"); +const QString ZeiterfassungSettings::m_username("username"); +const QString ZeiterfassungSettings::m_password("password"); +const QString ZeiterfassungSettings::m_projects("projects"); +const QString ZeiterfassungSettings::m_subprojects("subprojects"); +const QString ZeiterfassungSettings::m_workpackages("workpackages"); +const QString ZeiterfassungSettings::m_texts("texte"); +const QString ZeiterfassungSettings::m_theme("theme"); +const QLocale::Language ZeiterfassungSettings::m_defaultLanguage(QLocale::AnyLanguage); +const QUrl ZeiterfassungSettings::m_defaultUrl(QStringLiteral("http://10.1.0.11:8080/evoApps/")); + +ZeiterfassungSettings::ZeiterfassungSettings(const QString &organization, + const QString &application, QObject *parent) : + QSettings(organization, application, parent) +{ + +} + +ZeiterfassungSettings::ZeiterfassungSettings(QSettings::Scope scope, const QString &organization, + const QString &application, QObject *parent) : + QSettings(scope, organization, application, parent) +{ + +} + +ZeiterfassungSettings::ZeiterfassungSettings(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application, QObject *parent) : + QSettings(format, scope, organization, application, parent) +{ + +} + +ZeiterfassungSettings::ZeiterfassungSettings(const QString &fileName, QSettings::Format format, QObject *parent) : + QSettings(fileName, format, parent) +{ + +} + +ZeiterfassungSettings::ZeiterfassungSettings(QObject *parent) : + QSettings(parent) +{ +} + +QLocale::Language ZeiterfassungSettings::language() const +{ + return value(m_language, m_defaultLanguage).value(); +} + +bool ZeiterfassungSettings::setLanguage(QLocale::Language language) +{ + if(this->language() == language) + return true; + + if(m_defaultLanguage == language) + remove(m_language); + else + setValue(m_language, language); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT languageChanged(language); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +QUrl ZeiterfassungSettings::url() const +{ + return value(m_url, m_defaultUrl).toUrl(); +} + +bool ZeiterfassungSettings::setUrl(const QUrl &url) +{ + if(this->url() == url) + return true; + + if(m_defaultUrl == url) + remove(m_url); + else + setValue(m_url, url); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT urlChanged(url); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +QString ZeiterfassungSettings::username() const +{ + return value(m_username).toString(); +} + +bool ZeiterfassungSettings::setUsername(const QString &username) +{ + if(this->username() == username) + return true; + + if(username.isEmpty()) + remove(m_username); + else + setValue(m_username, username); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT usernameChanged(username); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +QString ZeiterfassungSettings::password() const +{ + return value(m_password).toString(); +} + +bool ZeiterfassungSettings::setPassword(const QString &password) +{ + if(this->password() == password) + return true; + + if(password.isEmpty()) + remove(m_password); + else + setValue(m_password, password); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT passwordChanged(password); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +QStringList ZeiterfassungSettings::projects() const +{ + return value(m_projects).toStringList(); +} + +bool ZeiterfassungSettings::setProjects(const QStringList &projects) +{ + if(this->projects() == projects) + return true; + + if(projects.isEmpty()) + remove(m_projects); + else + setValue(m_projects, projects); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT projectsChanged(projects); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +bool ZeiterfassungSettings::prependProject(const QString &project) +{ + return setProjects(prependItem(projects(), project)); +} + +QStringList ZeiterfassungSettings::subprojects() const +{ + return value(m_subprojects).toStringList(); +} + +bool ZeiterfassungSettings::setSubprojects(const QStringList &subprojects) +{ + if(this->subprojects() == subprojects) + return true; + + if(subprojects.isEmpty()) + remove(m_subprojects); + else + setValue(m_subprojects, subprojects); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT subprojectsChanged(subprojects); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +bool ZeiterfassungSettings::prependSubproject(const QString &subproject) +{ + return setSubprojects(prependItem(subprojects(), subproject)); +} + +QStringList ZeiterfassungSettings::workpackages() const +{ + return value(m_workpackages).toStringList(); +} + +bool ZeiterfassungSettings::setWorkpackages(const QStringList &workpackages) +{ + if(this->workpackages() == workpackages) + return true; + + if(workpackages.isEmpty()) + remove(m_workpackages); + else + setValue(m_workpackages, workpackages); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT workpackagesChanged(workpackages); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +bool ZeiterfassungSettings::prependWorkpackage(const QString &workpackage) +{ + return setWorkpackages(prependItem(workpackages(), workpackage)); +} + +QStringList ZeiterfassungSettings::texts() const +{ + return value(m_texts).toStringList(); +} + +bool ZeiterfassungSettings::setTexts(const QStringList &texts) +{ + if(this->texts() == texts) + return true; + + if(m_texts.isEmpty()) + remove(m_texts); + else + setValue(m_texts, texts); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT textsChanged(texts); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +bool ZeiterfassungSettings::prependText(const QString &text) +{ + return setTexts(prependItem(texts(), text)); +} + +QString ZeiterfassungSettings::theme() const +{ + return value(m_theme).toString(); +} + +bool ZeiterfassungSettings::setTheme(const QString &theme) +{ + if(this->theme() == theme) + return true; + + if(theme.isEmpty()) + remove(m_theme); + else + setValue(m_theme, theme); + + sync(); + + const auto success = status() == QSettings::NoError; + if(success) + Q_EMIT themeChanged(theme); + else + Q_EMIT saveErrorOccured(); + + return success; +} + +QStringList ZeiterfassungSettings::prependItem(QStringList list, const QString &item) +{ + if(item.trimmed().isEmpty()) + return list; + + list.removeAll(item); + list.prepend(item); + + return list; +} diff --git a/zeiterfassungcorelib/zeiterfassungsettings.h b/zeiterfassungcorelib/zeiterfassungsettings.h new file mode 100644 index 0000000..b1b58d7 --- /dev/null +++ b/zeiterfassungcorelib/zeiterfassungsettings.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungcorelib_global.h" + +class ZEITERFASSUNGCORELIB_EXPORT ZeiterfassungSettings : public QSettings +{ + Q_OBJECT + Q_PROPERTY(QLocale::Language language READ language WRITE setLanguage NOTIFY languageChanged) + Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) + Q_PROPERTY(QString username READ username WRITE setUsername NOTIFY usernameChanged) + Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged) + Q_PROPERTY(QStringList projects READ projects WRITE setProjects NOTIFY projectsChanged) + Q_PROPERTY(QStringList subprojects READ subprojects WRITE setSubprojects NOTIFY subprojectsChanged) + Q_PROPERTY(QStringList workpackages READ workpackages WRITE setWorkpackages NOTIFY workpackagesChanged) + Q_PROPERTY(QStringList texts READ texts WRITE setTexts NOTIFY textsChanged) + Q_PROPERTY(QString theme READ theme WRITE setTheme NOTIFY themeChanged) + +public: + explicit ZeiterfassungSettings(const QString &organization, + const QString &application = QString(), QObject *parent = Q_NULLPTR); + ZeiterfassungSettings(Scope scope, const QString &organization, + const QString &application = QString(), QObject *parent = Q_NULLPTR); + ZeiterfassungSettings(Format format, Scope scope, const QString &organization, + const QString &application = QString(), QObject *parent = Q_NULLPTR); + ZeiterfassungSettings(const QString &fileName, Format format, QObject *parent = Q_NULLPTR); + explicit ZeiterfassungSettings(QObject *parent = Q_NULLPTR); + + QLocale::Language language() const; + bool setLanguage(QLocale::Language language); + + QUrl url() const; + bool setUrl(const QUrl &url); + + QString username() const; + bool setUsername(const QString &username); + + QString password() const; + bool setPassword(const QString &password); + + QStringList projects() const; + bool setProjects(const QStringList &projects); + bool prependProject(const QString &project); + + QStringList subprojects() const; + bool setSubprojects(const QStringList &subprojects); + bool prependSubproject(const QString &subproject); + + QStringList workpackages() const; + bool setWorkpackages(const QStringList &workpackages); + bool prependWorkpackage(const QString &workpackage); + + QStringList texts() const; + bool setTexts(const QStringList &texts); + bool prependText(const QString &text); + + QString theme() const; + bool setTheme(const QString &theme); + +Q_SIGNALS: + void saveErrorOccured(); + + void languageChanged(QLocale::Language language); + void urlChanged(const QUrl &url); + void usernameChanged(const QString &username); + void passwordChanged(const QString &password); + void projectsChanged(const QStringList &projects); + void subprojectsChanged(const QStringList &subprojects); + void workpackagesChanged(const QStringList &workpackages); + void textsChanged(const QStringList &texts); + void themeChanged(const QString &theme); + +private: + QStringList prependItem(QStringList list, const QString &item); + + static const QString m_language; + static const QString m_url; + static const QString m_username; + static const QString m_password; + static const QString m_projects; + static const QString m_subprojects; + static const QString m_workpackages; + static const QString m_texts; + static const QString m_theme; + static const QLocale::Language m_defaultLanguage; + static const QUrl m_defaultUrl; +}; diff --git a/zeiterfassungguilib/dialogs/authenticationdialog.cpp b/zeiterfassungguilib/dialogs/authenticationdialog.cpp new file mode 100644 index 0000000..d4dbfd2 --- /dev/null +++ b/zeiterfassungguilib/dialogs/authenticationdialog.cpp @@ -0,0 +1,34 @@ +#include "authenticationdialog.h" +#include "ui_authenticationdialog.h" + +AuthenticationDialog::AuthenticationDialog(QWidget *parent) : + ZeiterfassungDialog(parent), + ui(new Ui::AuthenticationDialog) +{ + ui->setupUi(this); +} + +AuthenticationDialog::~AuthenticationDialog() +{ + delete ui; +} + +QString AuthenticationDialog::username() const +{ + return ui->lineEditUsername->text(); +} + +void AuthenticationDialog::setUsername(const QString &username) +{ + ui->lineEditUsername->setText(username); +} + +QString AuthenticationDialog::password() const +{ + return ui->lineEditPassword->text(); +} + +void AuthenticationDialog::setPassword(const QString &password) +{ + ui->lineEditPassword->setText(password); +} diff --git a/zeiterfassungguilib/dialogs/authenticationdialog.h b/zeiterfassungguilib/dialogs/authenticationdialog.h new file mode 100644 index 0000000..97c26c2 --- /dev/null +++ b/zeiterfassungguilib/dialogs/authenticationdialog.h @@ -0,0 +1,24 @@ +#pragma once + +#include "zeiterfassungguilib_global.h" +#include "zeiterfassungdialog.h" + +namespace Ui { class AuthenticationDialog; } + +class ZEITERFASSUNGGUILIB_EXPORT AuthenticationDialog : public ZeiterfassungDialog +{ + Q_OBJECT + +public: + explicit AuthenticationDialog(QWidget *parent = Q_NULLPTR); + ~AuthenticationDialog(); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + +private: + Ui::AuthenticationDialog *ui; +}; diff --git a/zeiterfassungguilib/dialogs/authenticationdialog.ui b/zeiterfassungguilib/dialogs/authenticationdialog.ui new file mode 100644 index 0000000..e68034f --- /dev/null +++ b/zeiterfassungguilib/dialogs/authenticationdialog.ui @@ -0,0 +1,144 @@ + + + AuthenticationDialog + + + + 0 + 0 + 394 + 181 + + + + + 16777215 + 0 + + + + Authentication + + + + + + + + + 48 + 48 + + + + + 48 + 48 + + + + :/zeiterfassungguilib/images/authentication.png + + + true + + + + + + + + 20 + + + + Authentication + + + + + + + + + + + Username: + + + lineEditUsername + + + + + + + Password: + + + lineEditPassword + + + + + + + + + + QLineEdit::Password + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + AuthenticationDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AuthenticationDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/zeiterfassungguilib/dialogs/languageselectiondialog.cpp b/zeiterfassungguilib/dialogs/languageselectiondialog.cpp new file mode 100644 index 0000000..c39f5bc --- /dev/null +++ b/zeiterfassungguilib/dialogs/languageselectiondialog.cpp @@ -0,0 +1,27 @@ +#include "languageselectiondialog.h" +#include "ui_languageselectiondialog.h" + +LanguageSelectionDialog::LanguageSelectionDialog(QWidget *parent) : + ZeiterfassungDialog(parent), + ui(new Ui::LanguageSelectionDialog) +{ + ui->setupUi(this); + + ui->comboBoxLanguage->addItem(tr("English"), QLocale::English); + ui->comboBoxLanguage->addItem(tr("German"), QLocale::German); +} + +LanguageSelectionDialog::~LanguageSelectionDialog() +{ + delete ui; +} + +QLocale::Language LanguageSelectionDialog::language() const +{ + return ui->comboBoxLanguage->currentData().value(); +} + +void LanguageSelectionDialog::setLanguage(QLocale::Language language) +{ + ui->comboBoxLanguage->setCurrentIndex(ui->comboBoxLanguage->findData(language)); +} diff --git a/zeiterfassungguilib/dialogs/languageselectiondialog.h b/zeiterfassungguilib/dialogs/languageselectiondialog.h new file mode 100644 index 0000000..5fa1482 --- /dev/null +++ b/zeiterfassungguilib/dialogs/languageselectiondialog.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +#include "zeiterfassungguilib_global.h" +#include "zeiterfassungdialog.h" + +namespace Ui { class LanguageSelectionDialog; } + +class ZEITERFASSUNGGUILIB_EXPORT LanguageSelectionDialog : public ZeiterfassungDialog +{ + Q_OBJECT + +public: + explicit LanguageSelectionDialog(QWidget *parent = Q_NULLPTR); + ~LanguageSelectionDialog(); + + QLocale::Language language() const; + void setLanguage(QLocale::Language language); + +private: + Ui::LanguageSelectionDialog *ui; +}; diff --git a/zeiterfassungguilib/dialogs/languageselectiondialog.ui b/zeiterfassungguilib/dialogs/languageselectiondialog.ui new file mode 100644 index 0000000..aafb0a9 --- /dev/null +++ b/zeiterfassungguilib/dialogs/languageselectiondialog.ui @@ -0,0 +1,103 @@ + + + LanguageSelectionDialog + + + + 0 + 0 + 400 + 157 + + + + + 16777215 + 0 + + + + Language selection + + + + + + + 20 + + + + Language selection + + + + + + + Please select a language: + + + + + + + + + Language: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + LanguageSelectionDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + LanguageSelectionDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/zeiterfassungguilib/dialogs/settingsdialog.cpp b/zeiterfassungguilib/dialogs/settingsdialog.cpp new file mode 100644 index 0000000..c1a41e9 --- /dev/null +++ b/zeiterfassungguilib/dialogs/settingsdialog.cpp @@ -0,0 +1,145 @@ +#include "settingsdialog.h" +#include "ui_settingsdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungsettings.h" +#include "zeiterfassungplugin.h" +#include "settingswidget.h" + +SettingsDialog::SettingsDialog(ZeiterfassungSettings &settings, const QSet &plugins, QWidget *parent) : + ZeiterfassungDialog(parent), + ui(new Ui::SettingsDialog), + m_settings(settings) +{ + ui->setupUi(this); + + ui->comboBoxLanguage->addItem(tr("English"), QLocale::English); + ui->comboBoxLanguage->addItem(tr("German"), QLocale::German); + + { + auto index = ui->comboBoxLanguage->findData(m_settings.language()); + if(index == -1) + QMessageBox::warning(this, tr("Invalid settings!"), tr("Invalid settings!") % "\n\n" % tr("Unknown language!")); + ui->comboBoxLanguage->setCurrentIndex(index); + } + + ui->comboBoxTheme->addItem(tr("Default"), QString()); + + for(const auto &entry : QDir(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("themes"))).entryInfoList(QStringList { QStringLiteral("*.qss") }, QDir::Files)) + ui->comboBoxTheme->addItem(entry.baseName(), entry.baseName()); + + if(!m_settings.theme().isEmpty()) + { + auto index = ui->comboBoxTheme->findData(m_settings.theme()); + if(index == -1) + QMessageBox::warning(this, tr("Invalid settings!"), tr("Invalid settings!") % "\n\n" % tr("Unknown theme!")); + ui->comboBoxTheme->setCurrentIndex(index); + } + + for(const auto plugin : plugins) + { + auto widget = plugin->settingsWidget(m_settings, this); + if(!widget) + continue; + + ui->verticalLayout->addWidget(widget); + m_settingsWidgets.append(widget); + } + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &SettingsDialog::submit); +} + +SettingsDialog::~SettingsDialog() +{ + delete ui; +} + +void SettingsDialog::submit() +{ + if(ui->comboBoxLanguage->currentIndex() == -1 || + ui->comboBoxTheme->currentIndex() == -1) + { + QMessageBox::warning(this, tr("Invalid settings!"), tr("Invalid settings!") % "\n\n" % tr("Please fill all options with valid values!")); + return; + } + + for(const auto widget : m_settingsWidgets) + { + QString message; + if(!widget->isValid(message)) + { + QMessageBox::warning(this, tr("Invalid settings!"), tr("Invalid settings!") % "\n\n" % message); + return; + } + } + + if(ui->comboBoxLanguage->currentData().value() != m_settings.language()) + { + if(!m_settings.setLanguage(ui->comboBoxLanguage->currentData().value())) + { + errorOccured(); + return; + } + + //TODO #73 Allow changing of the language without restart + QMessageBox::information(this, tr("Restart required!"), tr("To apply the new settings a restart is required!")); + } + + auto theme = ui->comboBoxTheme->currentData().toString(); + if(theme != m_settings.theme()) + { + QString styleSheet; + if(!theme.isEmpty()) + { + auto themePath = QDir(QDir(QCoreApplication::applicationDirPath()).absoluteFilePath(QStringLiteral("themes"))).absoluteFilePath(theme); + + QFile file(themePath % ".qss"); + + if(!file.exists()) + { + QMessageBox::warning(this, tr("Could not load theme!"), tr("Could not load theme!") % "\n\n" % tr("Theme file does not exist!")); + return; + } + + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QMessageBox::warning(this, tr("Could not load theme!"), tr("Could not load theme!") % "\n\n" % file.errorString()); + return; + } + + QTextStream textStream(&file); + styleSheet = textStream.readAll().replace(QStringLiteral("@THEME_RESOURCES@"), themePath); + } + + if(!m_settings.setTheme(theme)) + { + errorOccured(); + return; + } + + qApp->setStyleSheet(styleSheet); + } + + for(const auto widget : m_settingsWidgets) + { + if(!widget->apply()) + { + errorOccured(); + return; + } + } + + accept(); +} + +void SettingsDialog::errorOccured() +{ + QMessageBox::warning(this, tr("Could not save settings!"), tr("Could not load settings!") % "\n\n" % tr("Make sure you have writing permissions!")); +} diff --git a/zeiterfassungguilib/dialogs/settingsdialog.h b/zeiterfassungguilib/dialogs/settingsdialog.h new file mode 100644 index 0000000..263d4c9 --- /dev/null +++ b/zeiterfassungguilib/dialogs/settingsdialog.h @@ -0,0 +1,28 @@ +#pragma once + +#include "zeiterfassungguilib_global.h" +#include "zeiterfassungdialog.h" + +namespace Ui { class SettingsDialog; } +class ZeiterfassungSettings; +class ZeiterfassungPlugin; +class SettingsWidget; + +class ZEITERFASSUNGGUILIB_EXPORT SettingsDialog : public ZeiterfassungDialog +{ + Q_OBJECT + +public: + explicit SettingsDialog(ZeiterfassungSettings &settings, const QSet &plugins, QWidget *parent = Q_NULLPTR); + ~SettingsDialog(); + +private Q_SLOTS: + void submit(); + +private: + void errorOccured(); + + Ui::SettingsDialog *ui; + ZeiterfassungSettings &m_settings; + QVector m_settingsWidgets; +}; diff --git a/zeiterfassungguilib/dialogs/settingsdialog.ui b/zeiterfassungguilib/dialogs/settingsdialog.ui new file mode 100644 index 0000000..f1eed28 --- /dev/null +++ b/zeiterfassungguilib/dialogs/settingsdialog.ui @@ -0,0 +1,113 @@ + + + SettingsDialog + + + + 0 + 0 + 340 + 164 + + + + + 16777215 + 0 + + + + Settings + + + + + + + 20 + + + + Settings + + + + + + + + + + + Language: + + + comboBoxLanguage + + + + + + + + + + Theme: + + + comboBoxTheme + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + + + buttonBox + rejected() + SettingsDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/zeiterfassungguilib/images/about.png b/zeiterfassungguilib/images/about.png new file mode 100644 index 0000000000000000000000000000000000000000..b83e92690f3924b705dbafa712f98f50683c8744 GIT binary patch literal 5513 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↦oqu+d%=MR3D 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 bYTZnbYOpuno3AbP0l+XkK-&;_R literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/help.png b/zeiterfassungguilib/images/help.png new file mode 100644 index 0000000000000000000000000000000000000000..9242cc87228bba7357435b2aaa177e25c1281940 GIT binary patch literal 4971 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 E0I9tt2><{9 literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/icon.png b/zeiterfassungguilib/images/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0b80d08924187481aa79b8390bb610710e6dbe9b GIT binary patch literal 3660 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 S^Hrxoy+Th{KbLh*2~7ZNnxc3B literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/next.png b/zeiterfassungguilib/images/next.png new file mode 100644 index 0000000000000000000000000000000000000000..0df2a6ceafaf7528b57c0e0090ebbfb3f3988ef8 GIT binary patch literal 4079 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hEaJuGt~XX6F6+*w+pfx)XhR;*B1pi%0Z zV1Lu0w~bF0r=RG0{M?O|zaBlP z^3MJ?)B5?n;(ht`ucNc)TOZh2m^VlI|9p1S-%LtQ|Ezqjd^!F1n}CASK@Zb^B@#DP zqs!!$rklNc{qO6#gY(zy{hU?2%rU(^OQ_<{>%-Aj^CPzZ-oNO^Jsl-;S=m+V>h|Vm z9CnSh-}l3(InRob;mhRL<$t~JKYV!Yv2v)7mUf7nd!p|T&Sx*4R(8wnS(tpW|D4IU zWkLt4`T1gQe4hQ6Be{9z=4Fqh>%V!%Sk|jq@I@bS(Y|qv|9te0MQ_)rG<2kF33B}Y z%-kULn8=&e3^NP_x9jW``d@KbRO6{c(qVn$`?BI}Mux-&3rSb6Z6>5*xI#|0l* z9J-LvVLAQGu}|-Hj~svI%QpK~&c=`W&r9@n=e{%j{(5sygFudx%SKn3*(QfH+(KD* ze3cG=y!v_^pXx+0;lIMSf84a3ds@#Tn`1%?LvmtIqv5i~j$~y;v6@%PJDyv|ANnb; z<(%`Q{m`~_zvr7;(^(mFlsh;D(?li+Oms2rX)NNr|9iLoqwe}of*qW!M{>7d^2RT>PG9(WT%N7*eDjCPvaze*y?7Bd zH(QEPiAnHer-y>l`^E;2oJVY|ChN=&rzzVm_iHlkIVfxzW0P<6uk7S;!@YY~xIB5U z_{qur`l_AFn;QzB6+Q3T%Dvoo-j1AD|My?icW>VrrrN4Lr22B(EC$P z+gpj>-}8~%!oq+jt>I`Z#{rMNr5{TfZ>*0fI&<=naPQ<h_gbXejrrJ;2FT%I2{CZ5=5cfGUVIFsKA0mjV^Es9DSPno~Su6o${^|xSjC+C+~ zfxA14&zAOVw>%ckC}?23YAeUFh$FmPX9pkO_9k`qsat1rmg*JHp1S9N(XkfhDYf#^ zA74I7OIGz|PnPN}-Vt>8NZ7&BdMoM8%cd166|uX_dl#|i)1GqQh7Q&v!UET4Pf;=V zopk=+u}L#?*^b=q`t`g=RZ-QPcjfPvt(o65{@NH>b)Ao~ldLazDbn#FlXCIYgRuzXRfD`K^-AC0oLau%B1aG7 zY^jC(j?O8L<^sy4OqWj?wy|Vyj!oIy$)(&JRlyV9O&+9;{h4FH>}}ads_0(&Ykgf502fNf6w5`g>fip_vymB|M+t|ZeEdFo9=I&!^ny^UphFGnDMkSwwF!l5q591cgG7Yr9>E!^7n?d_(Z zP&?m3BbpNj3ANNbv1PiM)z5aago$};3d)5^TwzOTk#duvRYWMZhUGWRtR0WJy zDy-CUVevWl!=Y~b)447#SG8L;IJ7rB+I2^i@lEK2C$g_E@u0)v{%dqW$; zE}S|Yr~crz<^rK&ug`|De@e2w_pv{?Vk?=zs!`6@scgIZ(}^cmrfeE$Du{Rgqf zB5E&AO*b%~Vpz_6u0iO7)?$vO$D|XlG1Pwh{U=?ZKeE=psl!2}Q6+ns*5gn29x{qV zb7@RrdUI>1H1qK`h84#hUTL@QdT;eD>_MYpb))$&_Z2aE3-<2i<%wT5Be34$Rd|T% zONU;q3tt0XZp^DWJ9){}Z%a~}4gNWLAM=^)qVAB{x-E0ELdVC|zUv>nc({1+p_&vZ8ojg<8d)|4T2|^l*9$~LIfBs8fIK_RVY5VnUxrZBVe=924 zx>R}o|CPFaU+e>QRfWE(bE^Z7UpzH2Ik>$n@94a{ziap%GWQ7{S=w1&z_jt5W`Wi3 z{Tq*d;Xi+D()zo7H_{w`{qg5*WGtMo+od2^lU24NTxcoB)0M)8Mn(-^&;QfcI;_le zVegVJYun9r`0s1}C}h!@5xC;s<@z7b?(W_FuD14f_U1CPCLcOM@Rn16g>4fv7>I=Jo7slitS0!+;P>Z##P(%+D*R+ELdm1?dihh_jo@{ z7J4wV?n~lCck#W)|G(7s*&ia1vBH7TDmm|@a>{|7`-^t_3b87!{QmLZFPE02Xh~D! zo_AaQ6E^&pVEDtdWE$TWo02ECcUAnt*ckVOf0*Ytfy-fE@R@aXsoRvBQm)U4EUjNq zBz2U3Ytr#+zCW&hzH1;D-uY#_C;NZ13!8u4ta{?2@MjcLkXQ3SNCl z{ycYzQ=Xf4Rr!~M!;kHqbC+l>X?j2T(DwFiwn5f+%WuZ8YVdyI4f@2(|6uQeMDgcp zT2BS1OX}NgliF>T+s45s%NX#(>^*hFCNAnXM_lbM z&4^@YrCRNk5AIF8m+senajqm&hr+3;4ogK7cIK58%&j+B*|PWW?~iw4PZzBD&MS~% z?V-Q&P360(Y0UXnj7$tC+d4&N+(|BJJdkndqh@MOO_}btH9!4lWQ8)6?$`Lb_SL<2 z7r%4Y1ao@u_^o`BIpH;<+iAvcvm9-z-+wPG_$=n|M8l(Y>8Q~hLU(D%DHJ3RA1by~C+5BVfOyiB>qEpl} z-`HPhpB}cx>He`^foB{G-ZxyiyLI1JU-Mnx=Qiw1T=7|N&w(vIv&G)r+9mk+{Qicw zcl8e)6IA#jEuhQEpL3%~tx@-!AGJv@=f`|`E?V`m&uObsZN`HiVnJE1x%YpS|FO4uehl2bdhRBC{6FI!?py!% z%6HyiU|>)!ag8WRE=o--N!3kBEJb88S*r3as??!FFYqr6%X+=H}-W>*W`v>*qj} z6zeBd>J}&J8|xY9E94g`5TnD#J-|obNY79|y5A-l=0=F~kV3)0Sl7@p#L&RX)X2)z zNY}v3%D}*{;rVO^1_oh}8-w#pi;`0r{3|NcQ}Yx&lk@Y6-$<-`!N9_9gpMNAUFbIG1ba4!+xb=2c^_1As(&P5O%jLdX zp7&9ke6rZXNr*$S#r<-}f^M&syKY^*x;1NSi6YaYrqG%#Q@5Q~Sg>i$iVh7Xw$)qL zX5Ub9cLcv-vQyxNXM|#vdo&%PwDe{&U}K z{e}4}uKc=Y9Cd$Zkz?J<#($sYy|=&gd9wAon7e2zKd()9`+Q8|+4hu8%S)cr+dsCRz1ZVF&%~O4clq~SoUvS};Quwo z;{Vg?ngT4h8lRu^?T(g9p6e4g7cp_Yzm_{A1rlYPKQ8k7be;{*%tYg z%B^dPXC0c}C@moN?A~W%uJ=EGO844+t6r4zbGds}Wy*5{UDxTw)65@9WQP6N&~c~t zQ`%#R8fZAC}yUEwv6F$t0Ga*t<-we)vP+}&d+EogdtOQlfT zzq`SuI%q&j{W*@TmSj^_ zcwpglvhby$-REiBiq2JXTV|(fuTx%efuqRugl)usCC59DQu%$isu-oN;+j-gdGqw= zk_X0juARBGS#0B`)tcKnO&V(iChiWMyIsP4j-;9Yp@P;M4~tlwZ_3!q6maN%nf|#V zT`zU+4x8_@?T&dbR94DooR;)M=+O4=_q7!gGZS(?AMKc^*|s*+t)L?1P;vr~R+z-n z#%H|R5!Wjko|k9XUBRSp6oM4Ss;^fr@#w`6n62m(x1ZjcAuJjzv?ch!PiY& zxl*}Rxf`8Q9y&ZJ%5Y>~trv5yYpJh=TfmZr6Tb!Cr5=fC>S#akC3EefvRAxokD2Wj zFD<&&dOC9763%3YR;5Ez52UqK>9ZAfhyVGcBAAe+a$*ZNiv(9|?9sGaX6q8}?&9OJ z6nEuOk`~;_{evSZ%E81{KWPKks;VQ>{B4KUwTay_*Gs>9tMZ94UzbJ49?2ct%JO2$ zXOFIyTllc6J>H&4CA7ga>xW<)!%~hi&uJgNEM0Sd$BfFgH(6Heh)k4r`LRhNQrGQK z)r1t5^$EpBO|fe2w#l05`7ut_-fDbJ6OSt0;kcuPQ%KjH&Z)u z)K=_aKA64Z*H-mJFWwn%C3sq6ThpH9MI5_jeCuR-Ovn+1HU7^EZ_98zGI@PtL(kUi zXOm7_Xh^j1cGL(zN$xn4`QyN1k@f$sX8SZ)+6e{y+~*)>Tg&;nzFM$2dXq*{zsrl9 zHCzUQ2f5YS*F+w=DJ^#C+8jqg0V~0uCMR+eu2oceKW>kc7U~IkYV|u~DPMGJZKJdQ za~nVF{oPNyr3)`5WwiuZCe0C`vep;Y%VZ!3ow(V1x+>Sek9C1xCRg!j<5J@cmw*8*` zwEC}SA1fW0YVu31OslWq+^T}B{T4+0O+9pS!-~ttKgziAwHJm?udit}g zIr7rES*CAI#A{8`Hu654%yFOL9P7ko{du->Oe=&I1cnrUaY;)F%wGMe_v(WnowO6X zn8H?nTj^*0S~5(h`dH$Ejh&&{))6=6xj#B~lKm&M%e$iwgIqrOUbn4$BhpbiL#Nei zE5qCy6QTsSimh3&aA`;w6Z6a-?#87;ks_?7$M^nOrtH_r)9shLJizA6Jm$qAPOJ}2 z8rL(VE8MyE=i^%TfWr@?uggX<7QXkJvwxLBj z`t&BRJ+tO={gFK($+3M~-J_R#Co&)2zQ$Kn;63XR6;p-YED_o7S2TPXdoQ2NGBw>Y zOKtsmP{MSIlL?+PO=uizy79`oq)UHz^R(nk^Kk59z!e4GoN~_l$a&-_7+pnQ_ zKXc!|nStj2%GUCG6i50UFjk#b8_K$Eo&Ac#R?;WWKXFU1311egt@J}LrPn~xrG>S9 z`~0g+j}>=+n|^as^`wU%UxfXkl#ZP1uUprlEoU^**?8l1FlD#(38irrie7e!+8M0MlU(7?@Mdw8) zE@9(};_P%|S~O|yMw{z?6)CHxU%fy1e|C?bXoyars)7`^&;!n*wXVS{Yq#%GcYY+Y z#y)t(?)@Hb=OrB|Zds%MM8-L+RmLfd<+_&e(+P8JRp(imoZONB@s{GICJP>qHu1Z! z99J~>g>kSS<4|9`(miUWa!~P>Z;`S;p5!>nIJ#aqvB1pfcyrXbfyBAw51S@wErNI1{VuXy*=^IzqCgIZ<9$-Q0My{>G|Ib5{)$rrnF zt`nM@?=@OI>wl(F>Zi89TTtb#(Pi(;9RBAwz1-F!Y3CuUxUR7${n3f^mO1$+emY$< zHhIpQ%i90fdD`0F>XNH7cup==IngihcTMmUNo&Pde!Y1`HfbqruZ`!fvG!OS?!9SA zm!OYVLvSsV`0;pA-#+1hRf4vcY*(#X{rjcm1mApFeT>km&F8=n8x zauM_Sb7htE)!U8M8^5?e=4+lEcI@kn?h*r^@9rC5NlVvP4!yX@34`cOS)1tX; z1>?H?>nD5IvBog#Z+Rwr>yw;5ubWNfh9fBpG8xnxZrSq)jYd@7f*gS8-d5LGH zJ5qWMKmM)#Mvfz?>G8U63MStcZGzm$;K-2rvn8KmL|8yUVmWWbjH1#|8Nhm(%?Cm?8;`&myTVl0<{R@NO zxf7~RdjI*mM*V8W^FKR2h}Inb!zS*|)BlF|hOx*Z4L6{cIWC4bHNQO)nGs!(adM~4 zb@7dR)jPib>|^LzRKxy5sqUHUDb-*0TXUvexGZA87V5fW9lJsB+-}u)?WSW#g3ec! zvYs-2QoB&~xq&2y^)Dx{#z_myt_f)S3K|B_(!4%%+Y!|=?tK?-zG#$^yBD4K--9J8 zH>Yg%D{+B+>^oyGWs{APHnk%%VTX> zNO!E0L-oal7t3$nlisvgUu8>|1oMl;2XPbh70=miRji$FQ~1G<@#XE<%)7=-$v$^3 zEz@xr%NVh#4!A=AQso-z3l>93K-GnZFy zq3NHb_yY@<%=NwJaGSG;`_%WwkNf)dt3nSf7g|s#EmK~=_W0HkwIdIV-+g(cVxm6j zyABv2xbj_xIGgjLsD;ulU-eqnFOo5ls9GQWXI7)X$b?t+ zg%Lc*B5KSdr~R4rrhDJQ%?A}bc=q2AzhoSz9+=9mWUjnPEJFCS*0z^B+IqCN^7=Ss zIV=4<8BkFW?{VKH zu>L52pmyXYS6KD`nuo8MW&t z4M!3?tKZgiw|o8h;>L4E-^z#OP`y0I%lTWbK2E>z_Q3Oif1xgFt5+-tjMW9l5ldFwT0_Vwi>Z7b_=f5=01PTwWqJ)$GdsTC8oRAhkHAXePg+h_n@rB zI`EgFo~`@~i_8a#H~G3bmU347Svld^zlPS&I=9`K%BCq9B!q}mc!ef)ay^~L@7Ei& zs836B+nP6b^LOTlw;mKd#i$fl;&d#;&`sn9Q_*HEeuk;@_~*tv2xXeE-OMX@MBa@qzYFNL>O>iu=-L-z+_qN7<&HzQJ#trc^A6uE65!Gs% zH7Dm_f$)iXhNM5H{qT3^K+*rSTtp_ z>7HJEQRDU&pZ4o&?Ch@Zb*2B+?fuwRU-4ce=Hs!azXku!`#iJpvvzgGWFBFis8qSR zZ?5Rpbw1eHzg=$o#+}=F61X0*uy4_Rv()tQX~p*;wKr!@$@m{Hzklz4dG*9U{Juu7 z3;083{AXZbP%UwdC`m3#O)N>(O-U?CWiT=@u+%j)(lxLMF*LF=F}E@@&^0i(GB9|N zzV0VX14yM6L>GgRfuVt}fsw9}L5PvDm63^+kp)D<+i9=nqiD#@PsvQHglaIUure@#Xh@v9H<5vX!4%{qpUmXcyy8@bjFOT9D}8;iU72O6$@#gt`FX{9`9gTe~ HDWM4f_)jfk literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/now.png b/zeiterfassungguilib/images/now.png new file mode 100644 index 0000000000000000000000000000000000000000..f138cb4f24d352f005377df93cae59c02f97c73c GIT binary patch literal 7213 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 G&;$Try-@A| literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/qt.png b/zeiterfassungguilib/images/qt.png new file mode 100644 index 0000000000000000000000000000000000000000..f0088ac5978f38c343b3be6bd7e9a06fe73bd6ce GIT binary patch literal 2384 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 literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/settings.png b/zeiterfassungguilib/images/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..dcdb7557904d3ecc01e96bb739665659d33a3f56 GIT binary patch literal 6050 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 literal 0 HcmV?d00001 diff --git a/zeiterfassungguilib/images/user.png b/zeiterfassungguilib/images/user.png new file mode 100644 index 0000000000000000000000000000000000000000..e6e955fcc9165f5bc8b6b7ce89bc2263dcd199e8 GIT binary patch literal 5577 zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hELqiJ#!!HH~hK3gm45bDP46hOx7_4S6Fo+k- z*%fHRz`($k4nJNUsNB#yF{oG9baS0>^ZaBf$98=N}0S z48l#GE{-7;x9-l)?3w$#^nU&M&+oq1zBjr(S5ohAs)zR#zsSDW@W7O#8-#bPy!_2M z$Gjkly>a5&Oy29cJjXv=TlDvWTHj;_!$}9+#N2)Fx;SQC$(!u4?ZJ(ysk&QM1T4}B zTGD3xgeQIH>D@Nb_It|PoPP+fRCFzGU^gy){^(rszw?&=i|2i|U;m)=$Emz(e+C%_ zH3oJa=H_m%j~}Q0P&5e13wURM<&yaiZ}6#h_qAZ@P7;Hf<8^eo;cgB87rC6B&sdUz~jOOQrr z;1r?80}<{^ZI%c`Jil;jLU`A~4GXlmx2!TaCg3iYDDmdzN!#}t40SKmdvz2~OSL3= z*vTZVJs8z|QEIXKvEQO`FBMx3Shkz7ouB;my>gt!>8?d5G*36J(%Kj%+$Om1y{fq` z1JlLsNfSPP6g!)C>e#Vk-=*V2J@Qj*__BGtepXzPE^IlR;Tsw{#no>j|H@NL8;*E( zT%Wvmwiv_OubPSpS!r`tzGFFkdish}Tx(6e!hf7v-z&e=K3&%>+vfcG{FVk;#swj+ zp<*JgybjaaOr#9Y?KC#mW%#+FV$XGHBkS9#dH?mMiA|4;ig8-Wwf^mWCkJ$H~6m-pze@>D8;1pV!A(Y@1wt@8Y6I ze?I2lIGkT~{NK#j$=`i_NYRR#vl4DUzGQA|) ze(dX4SDmN+v39-&*U3$6#>SInEQ==mu6t3|cgM0{+-u+WH=XDIe^j^su=4fB>#wf~ zR()@`yKYlZa6bM{-F0cXS3TPej~Tppz9#-w!q-b|uUFf${gnyL6cBI~d|P%brMze5 zOT$ZH&G#0ZH}<#t|Ma;0-*dm`{geN{@>e4h!-LrWugsg6KUdfN{h<51KC!GkoW-%h zblRqn(%J<)=d6|pup~O%;rQq*pUk^nPv_?I{<(j?+^=~$dw$Kw|9_V4j;r3InENsQ zmp=dh;~!U^TYv7%Sp&0c%d|v}#oWC6$;R^rM?`Go^O@@NpFb5pU%ON1jlb=;*8Ul< zcW!^pci_0)|8V>H%P!{Y>#y$?_t#aZop1X)O=5nI;88(_pBoaM9+Fz_ulwQDY3_(@ zN%?%C)`$N~F4dpE|LO&_0*cY!jD4gaTdpCUKW?+)2cbI+^76! z!Lx+5JfV?^Yh0@mHde@)auoiUQFgE2_8YtWK0T+CrOXeK=l@?_Z)17!*XO0(*`I!V zUjN}zclVEZ$NxOE-E!RM&jI(Gubd9qhmS?gzpO4U!N=Y5TtHdi(1HKmar^)K@BjN@ zdD$H9b>87W?*F?sUw+=KoWCD`9SfLewe`elb@9mPn+mx{_7wanxqBny$dSO7FDh=C zpY|-!;5y}GC?m4uEQ_YCnOTvV+NSwFo1YzT@PGd$uKW)3zo+W^B<>X6zGE)Y31RrryH z#m)1pcmIFaZ)bI9<==N1sy`N-r6!H0imqyPU*f0k#(VE1Qs{J#B{{pyo4Gp8Es z>u=h2ZTZbi<9ly~R6T9k4PFU!zny5lOHMj^`^j#v#aoVkuU(d|R(W`ByXR$*SS_|@ zuIC+#HGZ7&Wj|0-{>FaF)Ms~grizJA``A3;*$<1v7~|U9%{R8}e8*|gzBO-&V8kh} z$>Lp`E@eFa@VB9JrDxgMw~h)L-e(g61b*E6eecgaf4M!G3_q^_|N5~0M&5FMo!E%S zcW!b&adlmMRi$guuB)b}?(vxmx#>x-e{0sbB~Kr9Z)` zqEMvf$uh=-va(0YUeaP>({}FI()66YrLbqhC$7LJo7^54C_mo1E#y`}*sMhXPf}gu z!Z>6uzn$<=N2MppN?4dfQ?t7(F;0->-|zQ7PxklieaBGq!8rcL{mk_L8Bc@yKR?%- z@LaV{MA0_?@~5?8Gq#Euca`UwO<%amZF#&_o9kQFOce{0+Ye6MQ#V=o# z3-KTOrmJ}V_;+2lA$GT#{fnRTVq#(ve?HRk==}0myJ!2gq`7yyzMoyX)lswfCbL)Q zdaqzj-)R*k)`n-@8oty>Wwvdd_HctkDA)b(b?-`99~}H%@3rmr&GR{5+H~f{FS{?@ zv}etaD{j$qwl2!r?dag*QlHd-||N5cQfj%rb{pdWQH^Y?6x&dYzhf-#Ht zSVczehb0bqTMyaVByaajoi@+R=B9qZwW5EQ1hRE5eg4?gu*f{cKqKS&)rY?u(&NP$ z($bIJPoDeuP-|#d`1Os8ozi~4%+G2rWxleY$5E>D3Y*K{x@_5^VBI3VWEn5%YpbnP zPHoCtVswh@TE?fuzA)XI`_<+j-mkyfmd)4bZ2zl!N#>_@b@u5`A8GyEuwwIFjc0e4 zeM}1$HIv9)*YVryrS5lq?X9A0sY^AqTtlzches{kdhAA&Q~9;PC#r|{DDX5#=T$ym zA5-jGaF*fE31;{AXJ_@kdv%L#wf1U_r5mm#Zck&_wtMfsJzLx|W1APel5E@*B(n6L z)!QWQDN#z7S2=07UM*!7eHajWazVVnk9i%79vokPwJ)2m@#f8yn|KCCrOAZks1&7&Vc< zo#PhEzW)jzZkvlVgl+zqoVqk3IyU`+e9~Iyh<53nd+$E`7L>JR%HqJ87uk1A6nGUo zPkNR1(X%0yk$%@h-!evNuGy+^K*~38ibVFu+V_pma^Ej4|Kn}W*08<)f$>=#?|ZYJ ztM2V(3=K_MBSS>lD4!@QGUFg zIU$#~;f|Qt>^MWFp#Cq1-r6jfdTiIMl&xVQQy1Oz%2c^{x1gCJ!}RjvdCv_cp7F4i z_L+qp@VNEOt!w=hAzj;+duF;@LuG%RxR`K7%_1+vK4P~d(=N6JtG4}ckZzUiTzxL; zfMAQAqr@HNKi{R99CpvW_pU&s)oE(h>uKrkdh6Yn*DZY=pU0R#?Rd~BDTX80D$bXe9er%ZuaI81Y|APE zr%iKz&RPAQ^-@JNL(cpD{IrR)971b9U7ebku!Tv-Q)XhCVWH4PkIA*>EDhVwo#)nk zdbdzy!V=E68_n)%uYY9Selcg~K9Spg<)5=Zf9YL&Jjw0de$T%9;!MAOcLs!thJ{xI z85v$w%w3kUc1l>>o`?Ue-;4CiTbG62IvC0yq&DPN^9e;1BWkUrTd)syO@*O)4TOV zyX#KD<3c;MbU&`rK4@_INr8clAe-IgVA-^C|KbnRnpHeK6ig=O7ltL4Y&HF|;p8*Z zg)4K9gk3uvaP{fd%oVFnUCjP^ewmiT^NMv!xh`kk-amZhjmYE4xoYaSSms$={P1`6 zyQ285>=~LzZ%+5C%`Nz@n?Gl!RC4b3hD|HnnGh~yrI(kgYG4% zHIhsB=ZY$lj zJr}$6T;bJ@yJn`U)8lP7PkU?k$N$^=+gDF)Vo_&FoO9uXQIFx2p8}!JB4V4?3ive! zCx{-(+`yHty@6}B_ZmIJT?byttZsWz)S!25W!vkJO8&)Ohc2X7m9;s$^KGuLlQ-V2 zcGKSc@Rf@0=Y4Y{tbX^p+v%;U`FQ2fp-|h1XjAEBd%KY@_;T=@nYT|j9{zmhepLr^JO76A`+3j4ohvB# zuk-tR*~g_%cHg(WR{cp|{$K31bGAxrZMW~*u;ASHb$OG%&DwHM-nQxr_oE%ktrkhm zUlulh-~V~}yXp^(%GKrNtzT?@9iDS_t6jD&Y)WR+ zgh{Jb$+t;7Q>~1fBrY1d^y0CElGsaJiWBCkaMUa^Q1EoRv_#DRumXe3-+vP>FFzNm zKELwS($DAr+)aLxec!1g`P|OSwb|x!|F3Od_j2Ry>r43dKR%oPjY(5FtV5H%n29fP zkMqfOKX>nD=aZdr)85{CcmBGoFHJlj-TG_p9cSHDp8k5*(q%1y{sEGo7NwMy9@d># znVS6ajQ8{D{`*T-?W*_{`S6V=yS}Hz3& z_4||JG`+Dps>-m?i37i)8QQ{4z=XV~wbgZrJ(az^_mp8Xq8_9ogQ`q%A?q*MN zJeQ#5b194KA{U%G%q}p^a$r+&@R-|Z=6QobYDI&h0YlXRwsUKhkFYs?5lDKmMz`JT z<$C*%NBV4Bc0Ohn`hNZ_XMI)FvcwmbiZye%*A_2a(aYE9(C|L%vAK4j_5lk;lXIGL zR?nFtdM-4m!;5PnduZx3aq+hs!{csVv|RX~{d>}_9+g6$0}KodswJ)wCCNppi6yDJ zDTyVi3`Pb9mb!+9x`w79hUQiVMpnkgx(4P}1_s3r8|J_?fK*yRbTJqi7#ipr80s1t zh8S2{85>y{SwJ)_nY>ybMMG|WN@iLmZVgw@2~TEVU`Pi!$tN>8HLp08A)}s~N0FsLJ><3oddV7@3XFW1WltJci~sRn!Fz4@xsppjHhS3j3^P6 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "zeiterfassungapi.h" +#include "timeutils.h" +#include "zeiterfassungsettings.h" +#include "stripfactory.h" +#include "stripswidget.h" +#include "dialogs/settingsdialog.h" +#include "replies/getprojectsreply.h" +#include "replies/createbookingreply.h" +#include "replies/createtimeassignmentreply.h" +#include "replies/updatetimeassignmentreply.h" + +MainWindow::MainWindow(ZeiterfassungSettings &settings, ZeiterfassungApi &erfassung, const GetUserInfoReply::UserInfo &userInfo, + StripFactory &stripFactory, const QSet &plugins, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_settings(settings), + m_erfassung(erfassung), + m_userInfo(userInfo), + m_stripFactory(stripFactory), + m_plugins(plugins), + m_currentStripWidget(Q_NULLPTR), + m_timerId(-1) +{ + ui->setupUi(this); + + setWindowTitle(tr("Zeiterfassung - %0 (%1)").arg(m_userInfo.text).arg(m_userInfo.email)); + + ui->actionQuit->setShortcut(QKeySequence::Quit); + + connect(ui->actionToday, &QAction::triggered, this, [=](){ ui->dateEditDate->setDate(QDate::currentDate()); }); + + ui->actionRefresh->setShortcut(QKeySequence::Refresh); + connect(ui->actionRefresh, &QAction::triggered, this, &MainWindow::refreshEverything); + + connect(ui->actionSettings, &QAction::triggered, this, [this](){ SettingsDialog(m_settings, m_plugins, this).exec(); }); + + ui->actionHelp->setShortcut(QKeySequence::HelpContents); + + connect(ui->actionAboutQt, &QAction::triggered, this, [this](){ QMessageBox::aboutQt(this); }); + + ui->dateEditDate->setDate(QDate::currentDate()); + connect(ui->dateEditDate, &QDateTimeEdit::dateChanged, this, &MainWindow::dateChangedSlot); + + connect(ui->pushButtonPrevWeek, &QAbstractButton::pressed, this, [=](){ ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(-7)); }); + connect(ui->pushButtonPrev, &QAbstractButton::pressed, this, [=](){ ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(-1)); }); + connect(ui->pushButtonNext, &QAbstractButton::pressed, this, [=](){ ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(1)); }); + connect(ui->pushButtonNextWeek, &QAbstractButton::pressed, this, [=](){ ui->dateEditDate->setDate(ui->dateEditDate->date().addDays(7)); }); + + connect(ui->timeEditTime, &QTimeEdit::timeChanged, this, [&](){ + if(m_timerId != -1) + { + killTimer(m_timerId); + m_timerId = -1; + } + }); + ui->timeEditTime->setTime(timeNormalise(QTime::currentTime())); + + connect(ui->pushButtonNow, &QAbstractButton::pressed, this, &MainWindow::pushButtonNowPressed); + + m_getProjectsReply = erfassung.doGetProjects(userInfo.userId, QDate::currentDate()); + connect(m_getProjectsReply.get(), &ZeiterfassungReply::finished, this, &MainWindow::getProjectsFinished); + + ui->comboBoxSubproject->lineEdit()->setPlaceholderText(tr("Subproject")); + ui->comboBoxWorkpackage->lineEdit()->setPlaceholderText(tr("Workpackage")); + ui->comboBoxText->lineEdit()->setPlaceholderText(tr("Text")); + + updateComboboxes(); + + connect(ui->pushButtonStart, &QAbstractButton::pressed, this, &MainWindow::pushButtonStartPressed); + connect(ui->pushButtonEnd, &QAbstractButton::pressed, this, &MainWindow::pushButtonEndPressed); + + m_timerId = startTimer(60000); + + for(quint8 i = 0; i < 7; i++) + { + m_stripsWidgets[i] = new StripsWidget(*this, ui->widgetWeek); + connect(this, &MainWindow::refreshEverything, m_stripsWidgets[i], &StripsWidget::refresh); + ui->layoutWeek->addWidget(m_stripsWidgets[i]); + } + + dateChangedSlot(ui->dateEditDate->date()); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +QMenu *MainWindow::menuFile() const +{ + return ui->menuFile; +} + +QMenu *MainWindow::menuView() const +{ + return ui->menuView; +} + +QMenu *MainWindow::menuTools() const +{ + return ui->menuTools; +} + +QMenu *MainWindow::menuAbout() const +{ + return ui->menuAbout; +} + +QToolBar *MainWindow::toolBar() const +{ + return ui->mainToolBar; +} + +ZeiterfassungSettings &MainWindow::settings() const +{ + return m_settings; +} + +ZeiterfassungApi &MainWindow::erfassung() const +{ + return m_erfassung; +} + +const GetUserInfoReply::UserInfo &MainWindow::userInfo() const +{ + return m_userInfo; +} + +StripFactory &MainWindow::stripFactory() const +{ + return m_stripFactory; +} + +QDate MainWindow::date() const +{ + return ui->dateEditDate->date(); +} + +void MainWindow::setDate(const QDate &date) +{ + ui->dateEditDate->setDate(date); +} + +const QMap &MainWindow::projects() const +{ + return m_projects; +} + +const std::array &MainWindow::stripsWidgets() const +{ + return m_stripsWidgets; +} + +void MainWindow::timerEvent(QTimerEvent *event) +{ + if(event->timerId() == m_timerId) + { + QSignalBlocker blocker(ui->timeEditTime); + ui->timeEditTime->setTime(timeNormalise(QTime::currentTime())); + } + else + QMainWindow::timerEvent(event); +} + +void MainWindow::getProjectsFinished() +{ + if(m_getProjectsReply->success()) + { + m_projects.clear(); + + for(const auto &project : m_getProjectsReply->projects()) + m_projects.insert(project.value, project.label); + + updateComboboxes(); + } + else + QMessageBox::warning(this, tr("Could not load bookings!"), + tr("Could not load bookings!") % "\n\n" % m_getProjectsReply->message()); + + m_getProjectsReply = Q_NULLPTR; +} + +void MainWindow::pushButtonNowPressed() +{ + ui->dateEditDate->setDate(QDate::currentDate()); + ui->timeEditTime->setTime(timeNormalise(QTime::currentTime())); +} + +void MainWindow::pushButtonStartPressed() +{ + if(m_timerId != -1) + { + killTimer(m_timerId); + m_timerId = -1; + } + + if(m_currentStripWidget->bookings().rbegin() == m_currentStripWidget->bookings().rend() || + m_currentStripWidget->bookings().rbegin()->type == QStringLiteral("G")) + { + auto reply = m_erfassung.doCreateBooking(m_userInfo.userId, ui->dateEditDate->date(), + timeNormalise(ui->timeEditTime->time()), QTime(0, 0), + QStringLiteral("K"), QStringLiteral("")); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(this, tr("Could not create booking!"), tr("Could not create booking!") % "\n\n" % reply->message()); + m_currentStripWidget->refresh(); + goto after; + } + } + + { + auto timeAssignmentTime = m_currentStripWidget->timeAssignmentTime(); + + if(m_currentStripWidget->timeAssignments().rbegin() != m_currentStripWidget->timeAssignments().rend()) + { + auto timeAssignment = *m_currentStripWidget->timeAssignments().rbegin(); + if(timeAssignment.timespan == QTime(0, 0)) + { + auto timespan = timeBetween(m_currentStripWidget->lastTimeAssignmentStart(), ui->timeEditTime->time()); + + auto reply = m_erfassung.doUpdateTimeAssignment(timeAssignment.id, m_userInfo.userId, timeAssignment.date, + timeAssignment.time, timespan, + timeAssignment.project, timeAssignment.subproject, + timeAssignment.workpackage, timeAssignment.text); + + reply->waitForFinished(); + + if(reply->success()) + timeAssignmentTime = timeAdd(timeAssignmentTime, timespan); + else + { + QMessageBox::warning(this, tr("Could not edit time assignment!"), tr("Could not edit time assignment!") % "\n\n" % reply->message()); + m_currentStripWidget->refresh(); + goto after; + } + } + } + + { + auto reply = m_erfassung.doCreateTimeAssignment(m_userInfo.userId, ui->dateEditDate->date(), + timeAssignmentTime, QTime(0, 0), + ui->comboBoxProject->currentData().toString(), ui->comboBoxSubproject->currentText(), + ui->comboBoxWorkpackage->currentText(), ui->comboBoxText->currentText()); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(this, tr("Could not create time assignment!"), tr("Could not create time assignment!") % "\n\n" % reply->message()); + m_currentStripWidget->refresh(); + goto after; + } + } + } + + m_settings.prependProject(ui->comboBoxProject->currentData().toString()); + m_settings.prependSubproject(ui->comboBoxSubproject->currentText()); + m_settings.prependWorkpackage(ui->comboBoxWorkpackage->currentText()); + m_settings.prependText(ui->comboBoxText->currentText()); + + updateComboboxes(); + + //m_currentStripWidget->refresh(); + //refreshReport(); + Q_EMIT refreshEverything(); + + after: + m_timerId = startTimer(60000); +} + +void MainWindow::pushButtonEndPressed() +{ + if(m_timerId != -1) + { + killTimer(m_timerId); + m_timerId = -1; + } + + { + auto timeAssignment = *m_currentStripWidget->timeAssignments().rbegin(); + Q_ASSERT(timeAssignment.timespan == QTime(0, 0)); + + auto timespan = timeBetween(m_currentStripWidget->lastTimeAssignmentStart(), ui->timeEditTime->time()); + + auto reply = m_erfassung.doUpdateTimeAssignment(timeAssignment.id, m_userInfo.userId, timeAssignment.date, + timeAssignment.time, timespan, + timeAssignment.project, timeAssignment.subproject, + timeAssignment.workpackage, timeAssignment.text); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(this, tr("Could not edit time assignment!"), tr("Could not edit time assignment!") % "\n\n" % reply->message()); + m_currentStripWidget->refresh(); + goto after; + } + } + + { + auto reply = m_erfassung.doCreateBooking(m_userInfo.userId, ui->dateEditDate->date(), + timeNormalise(ui->timeEditTime->time()), QTime(0, 0), + QStringLiteral("G"), QStringLiteral("")); + + reply->waitForFinished(); + + if(!reply->success()) + { + QMessageBox::warning(this, tr("Could not create booking!"), tr("Could not create booking!") % "\n\n" % reply->message()); + m_currentStripWidget->refresh(); + goto after; + } + } + + //m_currentStripWidget->refresh(); + //refreshReport(); + Q_EMIT refreshEverything(); + + after: + m_timerId = startTimer(60000); +} + +void MainWindow::dateChangedSlot(const QDate &date) +{ + auto firstDayOfWeek = date.addDays(-(ui->dateEditDate->date().dayOfWeek() - 1)); + + for(quint8 i = 0; i < 7; i++) + { + auto weekDay = firstDayOfWeek.addDays(i); + + m_stripsWidgets[i]->setDate(weekDay); + + if(weekDay == date && m_currentStripWidget != m_stripsWidgets[i]) + { + if(m_currentStripWidget) + { + m_currentStripWidget->setHighlighted(false); + disconnect(m_currentStripWidget, &StripsWidget::minimumTimeChanged, this, &MainWindow::minimumTimeChanged); + disconnect(m_currentStripWidget, &StripsWidget::startEnabledChanged, this, &MainWindow::startEnabledChanged); + disconnect(m_currentStripWidget, &StripsWidget::endEnabledChanged, this, &MainWindow::endEnabledChanged); + } + + m_currentStripWidget = m_stripsWidgets[i]; + + m_currentStripWidget->setHighlighted(true); + + minimumTimeChanged(); + startEnabledChanged(); + endEnabledChanged(); + + connect(m_currentStripWidget, &StripsWidget::minimumTimeChanged, this, &MainWindow::minimumTimeChanged); + connect(m_currentStripWidget, &StripsWidget::startEnabledChanged, this, &MainWindow::startEnabledChanged); + connect(m_currentStripWidget, &StripsWidget::endEnabledChanged, this, &MainWindow::endEnabledChanged); + } + } + + Q_EMIT dateChanged(ui->dateEditDate->date()); +} + +void MainWindow::minimumTimeChanged() +{ + ui->timeEditTime->setMinimumTime(m_currentStripWidget->minimumTime()); +} + +void MainWindow::startEnabledChanged() +{ + auto startEnabled = m_currentStripWidget->startEnabled(); + auto endEnabled = m_currentStripWidget->endEnabled(); + + ui->timeEditTime->setEnabled(startEnabled || endEnabled); + ui->pushButtonNow->setEnabled(startEnabled || endEnabled); + + ui->comboBoxProject->setEnabled(startEnabled); + ui->comboBoxSubproject->setEnabled(startEnabled); + ui->comboBoxWorkpackage->setEnabled(startEnabled); + ui->comboBoxText->setEnabled(startEnabled); + + ui->pushButtonStart->setEnabled(startEnabled); + ui->pushButtonStart->setText(endEnabled ? tr("Switch") : tr("Start")); +} + +void MainWindow::endEnabledChanged() +{ + auto startEnabled = m_currentStripWidget->startEnabled(); + auto endEnabled = m_currentStripWidget->endEnabled(); + + ui->timeEditTime->setEnabled(startEnabled || endEnabled); + ui->pushButtonNow->setEnabled(startEnabled || endEnabled); + + ui->pushButtonStart->setText(endEnabled ? tr("Switch") : tr("Start")); + ui->pushButtonEnd->setEnabled(endEnabled); +} + +void MainWindow::updateComboboxes() +{ + ui->comboBoxProject->clear(); + + { + auto preferedProjects = m_settings.projects(); + + for(const auto &preferedProject : preferedProjects) + { + if(!m_projects.contains(preferedProject)) + { + qWarning() << "cannot find project" << preferedProject; + continue; + } + + ui->comboBoxProject->addItem(tr("%0 (%1)").arg(m_projects.value(preferedProject)).arg(preferedProject), preferedProject); + } + + if(preferedProjects.count()) + ui->comboBoxProject->insertSeparator(ui->comboBoxProject->count()); + + for(auto iter = m_projects.constBegin(); iter != m_projects.constEnd(); iter++) + { + if(!preferedProjects.contains(iter.key())) + ui->comboBoxProject->addItem(tr("%0 (%1)").arg(iter.value()).arg(iter.key()), iter.key()); + } + } + + ui->comboBoxSubproject->clear(); + + { + auto subprojects = m_settings.subprojects(); + for(const auto &subproject : subprojects) + ui->comboBoxSubproject->addItem(subproject); + if(subprojects.count()) + ui->comboBoxSubproject->setCurrentText(QString()); + } + + ui->comboBoxWorkpackage->clear(); + + { + auto workpackages = m_settings.workpackages(); + for(const auto &workpackage : workpackages) + ui->comboBoxWorkpackage->addItem(workpackage); + if(workpackages.count()) + ui->comboBoxWorkpackage->setCurrentText(QString()); + } + + ui->comboBoxText->clear(); + + { + auto texte = m_settings.texts(); + for(const auto &text : texte) + ui->comboBoxText->addItem(text); + if(texte.count()) + ui->comboBoxText->setCurrentText(QString()); + } +} diff --git a/zeiterfassungguilib/mainwindow.h b/zeiterfassungguilib/mainwindow.h new file mode 100644 index 0000000..48d8c2d --- /dev/null +++ b/zeiterfassungguilib/mainwindow.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +#include +#include + +#include "zeiterfassungguilib_global.h" +#include "replies/getuserinforeply.h" +#include "replies/getprojectsreply.h" +#include "replies/getpresencestatusreply.h" + +class QMenu; +class QToolBar; +class QLabel; +class QBoxLayout; + +namespace Ui { class MainWindow; } +class ZeiterfassungSettings; +class StripFactory; +class ZeiterfassungPlugin; +class StripsWidget; + +class ZEITERFASSUNGGUILIB_EXPORT MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(ZeiterfassungSettings &settings, ZeiterfassungApi &erfassung, const GetUserInfoReply::UserInfo &userInfo, + StripFactory &stripFactory, const QSet &plugins, QWidget *parent = Q_NULLPTR); + ~MainWindow(); + + QMenu *menuFile() const; + QMenu *menuView() const; + QMenu *menuTools() const; + QMenu *menuAbout() const; + QToolBar *toolBar() const; + + ZeiterfassungSettings &settings() const; + ZeiterfassungApi &erfassung() const; + const GetUserInfoReply::UserInfo &userInfo() const; + StripFactory &stripFactory() const; + + QDate date() const; + void setDate(const QDate &date); + + const QMap &projects() const; + const std::array &stripsWidgets() const; + +Q_SIGNALS: + void dateChanged(const QDate &date); + void refreshEverything(); + +protected: + // QObject interface + virtual void timerEvent(QTimerEvent *event) Q_DECL_OVERRIDE; + +private Q_SLOTS: + void getProjectsFinished(); + void pushButtonNowPressed(); + void pushButtonStartPressed(); + void pushButtonEndPressed(); + void dateChangedSlot(const QDate &date); + + void minimumTimeChanged(); + void startEnabledChanged(); + void endEnabledChanged(); + +private: + void updateComboboxes(); + + Ui::MainWindow *ui; + ZeiterfassungSettings &m_settings; + ZeiterfassungApi &m_erfassung; + const GetUserInfoReply::UserInfo &m_userInfo; + StripFactory &m_stripFactory; + const QSet &m_plugins; + + std::unique_ptr m_getProjectsReply; + + QMap m_projects; + + std::array m_stripsWidgets; + StripsWidget *m_currentStripWidget; + + int m_timerId; +}; diff --git a/zeiterfassungguilib/mainwindow.ui b/zeiterfassungguilib/mainwindow.ui new file mode 100644 index 0000000..4e3720f --- /dev/null +++ b/zeiterfassungguilib/mainwindow.ui @@ -0,0 +1,370 @@ + + + MainWindow + + + + 0 + 0 + 1411 + 575 + + + + + :/zeiterfassungguilib/images/icon.png:/zeiterfassungguilib/images/icon.png + + + + + + + + + Previous week + + + + + + + :/zeiterfassungguilib/images/previous_week.png:/zeiterfassungguilib/images/previous_week.png + + + + + + + Previous day + + + + + + + :/zeiterfassungguilib/images/previous.png:/zeiterfassungguilib/images/previous.png + + + + + + + + + + Next day + + + + + + + :/zeiterfassungguilib/images/next.png:/zeiterfassungguilib/images/next.png + + + + + + + Next week + + + + + + + :/zeiterfassungguilib/images/next_week.png:/zeiterfassungguilib/images/next_week.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Now + + + + + + + :/zeiterfassungguilib/images/now.png:/zeiterfassungguilib/images/now.png + + + + + + + + 200 + 0 + + + + + + + + + 200 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + + 200 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + + 200 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + Start + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + End + + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + true + + + + + 0 + 0 + 1393 + 428 + + + + + + + + + + + + 0 + 0 + 1411 + 26 + + + + + &File + + + + + + &About + + + + + + + + + + &View + + + + + + + &Tools + + + + + + + + + + TopToolBarArea + + + false + + + + + + + + + :/zeiterfassungguilib/images/quit.png:/zeiterfassungguilib/images/quit.png + + + &Quit + + + + + + :/zeiterfassungguilib/images/user.png:/zeiterfassungguilib/images/user.png + + + About &Me + + + + + + :/zeiterfassungguilib/images/about.png:/zeiterfassungguilib/images/about.png + + + About &zeiterfassung + + + + + + :/zeiterfassungguilib/images/qt.png:/zeiterfassungguilib/images/qt.png + + + About &Qt + + + + + + :/zeiterfassungguilib/images/today.png:/zeiterfassungguilib/images/today.png + + + &Today + + + + + + :/zeiterfassungguilib/images/refresh.png:/zeiterfassungguilib/images/refresh.png + + + &Refresh everything + + + + + + :/zeiterfassungguilib/images/settings.png:/zeiterfassungguilib/images/settings.png + + + &Settings + + + + + + :/zeiterfassungguilib/images/help.png:/zeiterfassungguilib/images/help.png + + + Help + + + + + + + + + actionQuit + triggered() + MainWindow + close() + + + -1 + -1 + + + 458 + 296 + + + + + diff --git a/zeiterfassungguilib/settingswidget.cpp b/zeiterfassungguilib/settingswidget.cpp new file mode 100644 index 0000000..1a0fa90 --- /dev/null +++ b/zeiterfassungguilib/settingswidget.cpp @@ -0,0 +1,6 @@ +#include "settingswidget.h" + +SettingsWidget::SettingsWidget(QWidget *parent) : + QWidget(parent) +{ +} diff --git a/zeiterfassungguilib/settingswidget.h b/zeiterfassungguilib/settingswidget.h new file mode 100644 index 0000000..c3e16aa --- /dev/null +++ b/zeiterfassungguilib/settingswidget.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +#include "zeiterfassungguilib_global.h" + +class ZEITERFASSUNGGUILIB_EXPORT SettingsWidget : public QWidget +{ + Q_OBJECT + +public: + explicit SettingsWidget(QWidget *parent = Q_NULLPTR); + + virtual bool isValid(QString &message) const { Q_UNUSED(message) return true; } + +public Q_SLOTS: + virtual bool apply() = 0; +}; diff --git a/zeiterfassungguilib/stripfactory.cpp b/zeiterfassungguilib/stripfactory.cpp new file mode 100644 index 0000000..d48b282 --- /dev/null +++ b/zeiterfassungguilib/stripfactory.cpp @@ -0,0 +1,107 @@ +#include "stripfactory.h" + +#include +#include +#include +#include +#include + +StripFactory::StripFactory(QObject *parent) : + QObject(parent), + m_loader(new QUiLoader(this)) +{ + +} + +bool StripFactory::load(const QString &stripsPath) +{ + QDir stripsDir(stripsPath); + + { + QFile file(stripsDir.absoluteFilePath(QStringLiteral("bookingstartstrip.ui"))); + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + m_errorString = file.errorString(); + return false; + } + m_bookingStartStrip = file.readAll(); + } + + { + QFile file(stripsDir.absoluteFilePath(QStringLiteral("bookingendstrip.ui"))); + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + m_errorString = file.errorString(); + return false; + } + m_bookingEndStrip = file.readAll(); + } + + { + QFile file(stripsDir.absoluteFilePath(QStringLiteral("timeassignmentstrip.ui"))); + if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) + { + m_errorString = file.errorString(); + return false; + } + m_timeAssignmentStrip = file.readAll(); + } + + return true; +} + +const QString &StripFactory::errorString() const +{ + return m_errorString; +} + +std::unique_ptr StripFactory::createBookingStartStrip(QWidget *parent) +{ + QBuffer buffer(&m_bookingStartStrip); + if(!buffer.open(QIODevice::ReadOnly)) + { + qCritical() << "could not open buffer" << buffer.errorString(); + return Q_NULLPTR; + } + + auto widget = std::unique_ptr(m_loader->load(&buffer, parent)); + + if(!widget) + m_errorString = m_loader->errorString(); + + return widget; +} + +std::unique_ptr StripFactory::createBookingEndStrip(QWidget *parent) +{ + QBuffer buffer(&m_bookingEndStrip); + if(!buffer.open(QIODevice::ReadOnly)) + { + qCritical() << "could not open buffer" << buffer.errorString(); + return Q_NULLPTR; + } + + auto widget = std::unique_ptr(m_loader->load(&buffer, parent)); + + if(!widget) + m_errorString = m_loader->errorString(); + + return widget; +} + +std::unique_ptr StripFactory::createTimeAssignmentStrip(QWidget *parent) +{ + QBuffer buffer(&m_timeAssignmentStrip); + if(!buffer.open(QIODevice::ReadOnly)) + { + qCritical() << "could not open buffer" << buffer.errorString(); + return Q_NULLPTR; + } + + auto widget = std::unique_ptr(m_loader->load(&buffer, parent)); + + if(!widget) + m_errorString = m_loader->errorString(); + + return widget; +} diff --git a/zeiterfassungguilib/stripfactory.h b/zeiterfassungguilib/stripfactory.h new file mode 100644 index 0000000..665d22e --- /dev/null +++ b/zeiterfassungguilib/stripfactory.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include +#include + +#include "zeiterfassungguilib_global.h" + +class QUiLoader; +class QByteArray; + +class ZEITERFASSUNGGUILIB_EXPORT StripFactory : public QObject +{ + Q_OBJECT + +public: + explicit StripFactory(QObject *parent = Q_NULLPTR); + + bool load(const QString &stripsPath); + const QString &errorString() const; + + std::unique_ptr createBookingStartStrip(QWidget *parent = Q_NULLPTR); + std::unique_ptr createBookingEndStrip(QWidget *parent = Q_NULLPTR); + std::unique_ptr createTimeAssignmentStrip(QWidget *parent = Q_NULLPTR); + +private: + QUiLoader *m_loader; + + QString m_errorString; + + QByteArray m_bookingStartStrip; + QByteArray m_bookingEndStrip; + QByteArray m_timeAssignmentStrip; +}; diff --git a/zeiterfassungguilib/stripswidget.cpp b/zeiterfassungguilib/stripswidget.cpp new file mode 100644 index 0000000..e2e9ec6 --- /dev/null +++ b/zeiterfassungguilib/stripswidget.cpp @@ -0,0 +1,676 @@ +#include "stripswidget.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "mainwindow.h" +#include "zeiterfassungapi.h" +#include "timeutils.h" +#include "stripfactory.h" + +StripsWidget::StripsWidget(MainWindow &mainWindow, QWidget *parent) : + QFrame(parent), + m_mainWindow(mainWindow), + m_refreshing(false), + m_refreshingDayinfo(false), + m_refreshingBookings(false), + m_refreshingTimeAssignments(false), + m_startEnabled(false), + m_endEnabled(false) +{ + auto layout = new QVBoxLayout(this); + + m_headerLayout = new QHBoxLayout; + + m_label[0] = new QLabel; + { + auto font = m_label[0]->font(); + font.setBold(true); + m_label[0]->setFont(font); + } + m_headerLayout->addWidget(m_label[0], 1); + + layout->addLayout(m_headerLayout); + + m_label[1] = new QLabel; + layout->addWidget(m_label[1]); + + m_stripsLayout = new QVBoxLayout; + layout->addLayout(m_stripsLayout); + + layout->addStretch(1); + + setLayout(layout); +} + +MainWindow &StripsWidget::mainWindow() const +{ + return m_mainWindow; +} + +QBoxLayout *StripsWidget::headerLayout() const +{ + return m_headerLayout; +} + +QBoxLayout *StripsWidget::stripsLayout() const +{ + return m_stripsLayout; +} + +QLabel *StripsWidget::label0() const +{ + return m_label[0]; +} + +QLabel *StripsWidget::label1() const +{ + return m_label[1]; +} + +const QDate &StripsWidget::date() const +{ + return m_date; +} + +void StripsWidget::setDate(const QDate &date) +{ + if(m_date != date) + { + Q_EMIT dateChanged(m_date = date); + + if(m_date.isValid()) + m_label[0]->setText(QLocale().toString(m_date, QLocale::LongFormat)); + else + m_label[0]->setText(tr("Invalid")); + + refresh(); + } +} + +bool StripsWidget::highlighted() const +{ + return m_highlighted; +} + +void StripsWidget::setHighlighted(bool highlighted) +{ + if(m_highlighted != highlighted) + { + Q_EMIT highlightedChanged(m_highlighted = highlighted); + setFrameStyle(highlighted ? QFrame::Box : QFrame::NoFrame); + } +} + +const GetDayinfoReply::Dayinfo &StripsWidget::dayinfo() const +{ + return m_dayinfo; +} + +const QVector &StripsWidget::bookings() const +{ + return m_bookings; +} + +const QVector &StripsWidget::timeAssignments() const +{ + return m_timeAssignments; +} + +const QTime &StripsWidget::timeAssignmentTime() const +{ + return m_timeAssignmentTime; +} + +const QTime &StripsWidget::lastTimeAssignmentStart() const +{ + return m_lastTimeAssignmentStart; +} + +const QTime &StripsWidget::minimumTime() const +{ + return m_minimumTime; +} + +bool StripsWidget::refreshing() const +{ + return m_refreshing; +} + +bool StripsWidget::refreshingDayinfo() const +{ + return m_refreshingDayinfo; +} + +bool StripsWidget::refreshingBookings() const +{ + return m_refreshingBookings; +} + +bool StripsWidget::refreshingTimeAssignments() const +{ + return m_refreshingTimeAssignments; +} + +bool StripsWidget::startEnabled() const +{ + return m_startEnabled; +} + +bool StripsWidget::endEnabled() const +{ + return m_endEnabled; +} + +void StripsWidget::refresh() +{ + clearStrips(); + + m_label[1]->setText(QString()); + m_stripsLayout->addWidget(new QLabel(tr("Loading..."), this)); + + refreshDayinfo(); + refreshBookings(false); + refreshTimeAssignments(false); +} + +void StripsWidget::refreshDayinfo() +{ + if(!m_date.isValid()) + { + qWarning() << "invalid date"; + return; + } + + if(!m_refreshing) + Q_EMIT refreshingChanged(m_refreshing = true); + + if(!m_refreshingDayinfo) + Q_EMIT refreshingDayinfoChanged(m_refreshingDayinfo = true); + + invalidateValues(); + + m_getDayinfoReply = m_mainWindow.erfassung().doGetDayinfo(m_mainWindow.userInfo().userId, m_date, m_date); + connect(m_getDayinfoReply.get(), &ZeiterfassungReply::finished, this, &StripsWidget::getDayinfoFinished); +} + +void StripsWidget::refreshBookings(bool createLabel) +{ + if(!m_date.isValid()) + { + qWarning() << "invalid date"; + return; + } + + if(createLabel) + { + clearStrips(); + + m_stripsLayout->addWidget(new QLabel(tr("Loading..."), this)); + } + + if(!m_bookings.empty()) + { + m_bookings.clear(); + Q_EMIT bookingsChanged(m_bookings); + } + + if(!m_refreshing) + Q_EMIT refreshingChanged(m_refreshing = true); + + if(!m_refreshingBookings) + Q_EMIT refreshingBookingsChanged(m_refreshingBookings = true); + + invalidateValues(); + + m_getBookingsReply = m_mainWindow.erfassung().doGetBookings(m_mainWindow.userInfo().userId, m_date, m_date); + connect(m_getBookingsReply.get(), &ZeiterfassungReply::finished, this, &StripsWidget::getBookingsFinished); +} + +void StripsWidget::refreshTimeAssignments(bool createLabel) +{ + if(!m_date.isValid()) + { + qWarning() << "invalid date"; + return; + } + + if(createLabel) + { + clearStrips(); + + m_stripsLayout->addWidget(new QLabel(tr("Loading..."), this)); + } + + if(!m_timeAssignments.empty()) + { + m_timeAssignments.clear(); + Q_EMIT timeAssignmentsChanged(m_timeAssignments); + } + + if(!m_refreshing) + Q_EMIT refreshingChanged(m_refreshing = true); + + if(!m_refreshingTimeAssignments) + Q_EMIT refreshingTimeAssignmentsChanged(m_refreshingTimeAssignments = true); + + invalidateValues(); + + m_getTimeAssignmentsReply = m_mainWindow.erfassung().doGetTimeAssignments(m_mainWindow.userInfo().userId, m_date, m_date); + connect(m_getTimeAssignmentsReply.get(), &ZeiterfassungReply::finished, this, &StripsWidget::getTimeAssignmentsFinished); +} + +bool StripsWidget::createStrips() +{ + clearStrips(); + + QTime timeAssignmentTime(0, 0); + QTime lastTimeAssignmentStart; + QTime minimumTime(0, 0); + bool endEnabled = false; + + auto bookingsIter = m_bookings.constBegin(); + auto timeAssignmentsIter = m_timeAssignments.constBegin(); + + auto bookingTimespan = QTime(0, 0); + + const GetBookingsReply::Booking *lastBooking = Q_NULLPTR; + + QString errorMessage; + + while(true) + { + if(bookingsIter == m_bookings.constEnd() && + timeAssignmentsIter == m_timeAssignments.constEnd()) + { + goto after; + } + + if(bookingsIter == m_bookings.constEnd()) + { + errorMessage = tr("Missing booking!"); + goto after; + } + + auto startBooking = *bookingsIter++; + if(startBooking.type != QStringLiteral("K")) + { + errorMessage = tr("Expected start booking, instead got type %0\nBooking ID: %1") + .arg(startBooking.type) + .arg(startBooking.id); + goto after; + } + + if(lastBooking) + { + auto breakTime = timeBetween(lastBooking->time, startBooking.time); + auto label = new QLabel(tr("%0: %1").arg(tr("Break")).arg(tr("%0h").arg(QLocale().toString(breakTime, QLocale::ShortFormat))), this); + m_stripsLayout->addWidget(label); + } + + lastBooking = &startBooking; + + lastTimeAssignmentStart = startBooking.time; + appendBookingStartStrip(startBooking.id, startBooking.time); + + if(timeAssignmentsIter == m_timeAssignments.constEnd()) + { + errorMessage = tr("Missing time assignment!"); + goto after; + } + + auto timeAssignment = *timeAssignmentsIter++; + + appendTimeAssignmentStrip(timeAssignment.id, timeAssignment.timespan, buildProjectString(timeAssignment.project), + timeAssignment.subproject, timeAssignment.workpackage, timeAssignment.text); + + if(timeAssignment.timespan == QTime(0, 0)) + { + if(bookingsIter != m_bookings.constEnd()) + { + errorMessage = tr("There is another booking after an unfinished time assignment.\nBooking ID: %0\nTime assignment ID: %1") + .arg(bookingsIter->id) + .arg(timeAssignment.id); + goto after; + } + + if(timeAssignmentsIter != m_timeAssignments.constEnd()) + { + errorMessage = tr("There is another time assignment after an unfinished time assignment.\nTime assignment ID: %0\nTime assignment ID: %1") + .arg(timeAssignmentsIter->id) + .arg(timeAssignment.id); + goto after; + } + + minimumTime = timeAdd(lastTimeAssignmentStart, QTime(0, 1)); + endEnabled = true; + goto after; + } + else + { + timeAssignmentTime = timeAdd(timeAssignmentTime, timeAssignment.timespan); + lastTimeAssignmentStart = timeAdd(lastTimeAssignmentStart, timeAssignment.timespan); + + if(bookingsIter == m_bookings.constEnd()) + { + while(true) + { + if(timeAssignmentsIter == m_timeAssignments.constEnd()) + { + errorMessage = tr("The last time assignment is finished without end booking\nTime assignment ID: %0") + .arg(timeAssignment.id); + goto after; + } + + timeAssignment = *timeAssignmentsIter++; + + appendTimeAssignmentStrip(timeAssignment.id, timeAssignment.timespan, buildProjectString(timeAssignment.project), + timeAssignment.subproject, timeAssignment.workpackage, timeAssignment.text); + + if(timeAssignment.timespan == QTime(0, 0)) + { + if(timeAssignmentsIter != m_timeAssignments.constEnd()) + { + errorMessage = tr("There is another time assignment after an unfinished time assignment.\n" + "Time assignment ID: %0\nTime assignment ID: %1") + .arg(timeAssignment.id) + .arg(timeAssignmentsIter->id); + goto after; + } + + minimumTime = timeAdd(lastTimeAssignmentStart, QTime(0, 1)); + endEnabled = true; + goto after; + } + else + { + timeAssignmentTime = timeAdd(timeAssignmentTime, timeAssignment.timespan); + lastTimeAssignmentStart = timeAdd(lastTimeAssignmentStart, timeAssignment.timespan); + } + } + } + else + { + auto endBooking = *bookingsIter++; + if(endBooking.type != QStringLiteral("G")) + { + errorMessage = tr("Expected end booking, instead got type %0\nBooking ID: %1") + .arg(endBooking.type) + .arg(endBooking.id); + goto after; + } + + lastBooking = &endBooking; + + auto currBookingDuration = timeBetween(startBooking.time, endBooking.time); + bookingTimespan = timeAdd(bookingTimespan, currBookingDuration); + minimumTime = timeAdd(endBooking.time, QTime(0, 1)); + + while(timeAssignmentTime < bookingTimespan) + { + if(timeAssignmentsIter == m_timeAssignments.constEnd()) + { + errorMessage = tr("Missing time assignment! Missing: %0") + .arg(tr("%0h").arg(QLocale().toString(timeBetween(timeAssignmentTime, bookingTimespan), QLocale::ShortFormat))); + + appendBookingEndStrip(endBooking.id, endBooking.time, currBookingDuration); + + goto after; + } + + timeAssignment = *timeAssignmentsIter++; + + appendTimeAssignmentStrip(timeAssignment.id, timeAssignment.timespan, buildProjectString(timeAssignment.project), + timeAssignment.subproject, timeAssignment.workpackage, timeAssignment.text); + + if(timeAssignment.timespan == QTime(0, 0)) + { + if(bookingsIter != m_bookings.constEnd()) + { + errorMessage = tr("There is another booking after an unfinished time assignment.\n" + "Booking ID: %0\nTime assignment ID: %1") + .arg(bookingsIter->id) + .arg(timeAssignment.id); + goto after; + } + + if(timeAssignmentsIter != m_timeAssignments.constEnd()) + { + errorMessage = tr("There is another time assignment after an unfinished time assignment.\nTime assignment ID: %0\nTime assignment ID: %1") + .arg(timeAssignmentsIter->id) + .arg(timeAssignment.id); + goto after; + } + + minimumTime = timeAdd(lastTimeAssignmentStart, QTime(0, 1)); + endEnabled = true; + goto after; + } + else + { + timeAssignmentTime = timeAdd(timeAssignmentTime, timeAssignment.timespan); + } + } + + if(timeAssignmentTime > bookingTimespan) + { + errorMessage = tr("Time assignment time longer than booking time!\nTime assignment: %0\nBooking: %1") + .arg(QLocale().toString(timeAssignmentTime, QLocale::ShortFormat)) + .arg(QLocale().toString(bookingTimespan, QLocale::ShortFormat)); + } + + appendBookingEndStrip(endBooking.id, endBooking.time, currBookingDuration); + + if(timeAssignmentTime > bookingTimespan) + goto after; + } + } + } + + after: + auto startEnabled = errorMessage.isEmpty(); + + if(errorMessage.isEmpty()) + { + auto label = new QLabel(tr("%0: %1") + .arg(tr("Assigned time")) + .arg(tr("%0h").arg(QLocale().toString(timeAssignmentTime, QLocale::ShortFormat))), this); + m_stripsLayout->addWidget(label); + } + else + { + startEnabled = false; + endEnabled = false; + + timeAssignmentTime = QTime(); + lastTimeAssignmentStart = QTime(); + + auto label = new QLabel(tr("Strip rendering aborted due error.\n" + "Your bookings and time assignments for this day are in an illegal state!") % "\n" % + errorMessage, this); + label->setStyleSheet(QStringLiteral("color: red;")); + m_stripsLayout->addWidget(label); + } + + if(m_timeAssignmentTime != timeAssignmentTime) + Q_EMIT timeAssignmentTimeChanged(m_timeAssignmentTime = timeAssignmentTime); + + if(m_lastTimeAssignmentStart != lastTimeAssignmentStart) + Q_EMIT lastTimeAssignmentStartChanged(m_lastTimeAssignmentStart = lastTimeAssignmentStart); + + if(m_minimumTime != minimumTime) + Q_EMIT minimumTimeChanged(m_minimumTime = minimumTime); + + if(m_startEnabled != startEnabled) + Q_EMIT startEnabledChanged(m_startEnabled = startEnabled); + + if(m_endEnabled != endEnabled) + Q_EMIT endEnabledChanged(m_endEnabled = endEnabled); + + return !errorMessage.isEmpty(); +} + +void StripsWidget::clearStrips() +{ + while(QLayoutItem *item = m_stripsLayout->takeAt(0)) + { + delete item->widget(); + delete item; + } +} + +void StripsWidget::getDayinfoFinished() +{ + Q_EMIT dayinfoChanged(m_dayinfo = m_getDayinfoReply->dayinfos().first()); + + m_label[1]->setText(QString("%0 - %1").arg(QLocale().toString(m_dayinfo.soll, QLocale::ShortFormat), + QLocale().toString(m_dayinfo.ist, QLocale::ShortFormat))); +} + +void StripsWidget::getBookingsFinished() +{ + Q_EMIT bookingsChanged(m_bookings = m_getBookingsReply->bookings()); + + if(m_refreshingBookings) + Q_EMIT refreshingBookingsChanged(m_refreshingBookings = false); + + if(m_refreshing && !m_getDayinfoReply && !m_getTimeAssignmentsReply) + Q_EMIT refreshingChanged(m_refreshing = false); + + if(!m_getTimeAssignmentsReply) + createStrips(); + + m_getBookingsReply = Q_NULLPTR; +} + +void StripsWidget::getTimeAssignmentsFinished() +{ + Q_EMIT timeAssignmentsChanged(m_timeAssignments = m_getTimeAssignmentsReply->timeAssignments()); + + if(m_refreshingTimeAssignments) + Q_EMIT refreshingTimeAssignmentsChanged(m_refreshingTimeAssignments = false); + + if(m_refreshing && !m_getDayinfoReply && !m_getBookingsReply) + Q_EMIT refreshingChanged(m_refreshing = false); + + if(!m_getBookingsReply) + createStrips(); + + m_getTimeAssignmentsReply = Q_NULLPTR; +} + +void StripsWidget::invalidateValues() +{ + if(m_timeAssignmentTime.isValid()) + Q_EMIT timeAssignmentTimeChanged(m_timeAssignmentTime = QTime()); + + if(m_lastTimeAssignmentStart.isValid()) + Q_EMIT lastTimeAssignmentStartChanged(m_lastTimeAssignmentStart = QTime()); + + if(m_minimumTime.isValid()) + Q_EMIT minimumTimeChanged(m_minimumTime = QTime()); + + if(m_startEnabled) + Q_EMIT startEnabledChanged(m_startEnabled = false); + + if(m_endEnabled) + Q_EMIT endEnabledChanged(m_endEnabled = false); +} + +QString StripsWidget::buildProjectString(const QString &project) const +{ + if(m_mainWindow.projects().contains(project)) + return m_mainWindow.projects().value(project) % "\n" % project; + else + { + qWarning() << "could not find project" << project; + return project; + } +} + +QWidget *StripsWidget::appendBookingStartStrip(int id, const QTime &time) +{ + auto widget = m_mainWindow.stripFactory().createBookingStartStrip(this).release(); + + if(auto labelTime = widget->findChild(QStringLiteral("labelTime"))) + labelTime->setProperty("text", QLocale().toString(time, QLocale::ShortFormat)); + else + qWarning() << "no labelTime found!"; + + if(auto labelId = widget->findChild(QStringLiteral("labelId"))) + labelId->setProperty("text", QString::number(id)); + else + qWarning() << "no labelId found!"; + + m_stripsLayout->addWidget(widget); + + return widget; +} + +QWidget *StripsWidget::appendBookingEndStrip(int id, const QTime &time, const QTime &duration) +{ + auto widget = m_mainWindow.stripFactory().createBookingEndStrip(this).release(); + + if(auto labelTime = widget->findChild(QStringLiteral("labelTime"))) + labelTime->setProperty("text", QLocale().toString(time, QLocale::ShortFormat)); + else + qWarning() << "no labelTime found!"; + + if(auto labelDuration = widget->findChild(QStringLiteral("labelDuration"))) + labelDuration->setProperty("text", tr("%0h").arg(QLocale().toString(duration, QLocale::ShortFormat))); + else + qWarning() << "no labelDuration found!"; + + if(auto labelId = widget->findChild(QStringLiteral("labelId"))) + labelId->setProperty("text", QString::number(id)); + else + qWarning() << "no labelId found!"; + + m_stripsLayout->addWidget(widget); + + return widget; +} + +QWidget *StripsWidget::appendTimeAssignmentStrip(int id, const QTime &duration, const QString &project, const QString &subproject, const QString &workpackage, const QString &text) +{ + auto widget = m_mainWindow.stripFactory().createTimeAssignmentStrip(this).release(); + + if(auto labelTime = widget->findChild(QStringLiteral("labelTime"))) + labelTime->setProperty("text", duration == QTime(0, 0) ? tr("Open") : QLocale().toString(duration, QLocale::ShortFormat)); + else + qWarning() << "no labelTime found!"; + + if(auto labelProject = widget->findChild(QStringLiteral("labelProject"))) + labelProject->setProperty("text", project); + else + qWarning() << "no labelProject found!"; + + if(auto labelId = widget->findChild(QStringLiteral("labelId"))) + labelId->setProperty("text", QString::number(id)); + else + qWarning() << "no labelId found!"; + + if(auto labelSubproject = widget->findChild(QStringLiteral("labelSubproject"))) + labelSubproject->setProperty("text", subproject); + else + qWarning() << "no labelSubproject found!"; + + if(auto labelWorkpackage = widget->findChild(QStringLiteral("labelWorkpackage"))) + labelWorkpackage->setProperty("text", workpackage); + else + qWarning() << "no labelWorkpackage found!"; + + if(auto labelText = widget->findChild(QStringLiteral("labelText"))) + labelText->setProperty("text", text); + else + qWarning() << "no labelText found!"; + + m_stripsLayout->addWidget(widget); + + return widget; +} diff --git a/zeiterfassungguilib/stripswidget.h b/zeiterfassungguilib/stripswidget.h new file mode 100644 index 0000000..67ce629 --- /dev/null +++ b/zeiterfassungguilib/stripswidget.h @@ -0,0 +1,120 @@ +#pragma once + +#include + +#include +#include + +#include "zeiterfassungguilib_global.h" +#include "replies/getdayinforeply.h" +#include "replies/getbookingsreply.h" +#include "replies/gettimeassignmentsreply.h" + +class QBoxLayout; +class QLabel; +template class QVector; + +class MainWindow; + +class ZEITERFASSUNGGUILIB_EXPORT StripsWidget : public QFrame +{ + Q_OBJECT + +public: + explicit StripsWidget(MainWindow &mainWindow, QWidget *parent = Q_NULLPTR); + + MainWindow &mainWindow() const; + + QBoxLayout *headerLayout() const; + QBoxLayout *stripsLayout() const; + + QLabel *label0() const; + QLabel *label1() const; + + const QDate &date() const; + void setDate(const QDate &date); + + bool highlighted() const; + void setHighlighted(bool highlighted); + + const GetDayinfoReply::Dayinfo &dayinfo() const; + const QVector &bookings() const; + const QVector &timeAssignments() const; + + const QTime &timeAssignmentTime() const; + const QTime &lastTimeAssignmentStart() const; + const QTime &minimumTime() const; + bool refreshing() const; + bool refreshingDayinfo() const; + bool refreshingBookings() const; + bool refreshingTimeAssignments() const; + bool startEnabled() const; + bool endEnabled() const; + + void refresh(); + void refreshDayinfo(); + void refreshBookings(bool createLabel = true); + void refreshTimeAssignments(bool createLabel = true); + bool createStrips(); + void clearStrips(); + +Q_SIGNALS: + void dateChanged(const QDate &date); + void highlightedChanged(bool highlighted); + + void dayinfoChanged(const GetDayinfoReply::Dayinfo &dayinfo); + void bookingsChanged(const QVector &bookings); + void timeAssignmentsChanged(const QVector &timeAssignments); + + void timeAssignmentTimeChanged(const QTime &timeAssignmentTime); + void lastTimeAssignmentStartChanged(const QTime &lastTimeAssignmentStart); + void minimumTimeChanged(const QTime &minimumTime); + void refreshingChanged(bool refreshing); + void refreshingDayinfoChanged(bool refreshingDayinfo); + void refreshingBookingsChanged(bool refreshingBookings); + void refreshingTimeAssignmentsChanged(bool refreshingTimeAssignments); + void startEnabledChanged(bool startEnabled); + void endEnabledChanged(bool endEnabled); + +private Q_SLOTS: + void getDayinfoFinished(); + void getBookingsFinished(); + void getTimeAssignmentsFinished(); + +private: + void invalidateValues(); + QString buildProjectString(const QString &project) const; + + QWidget *appendBookingStartStrip(int id, const QTime &time); + QWidget *appendBookingEndStrip(int id, const QTime &time, const QTime &duration); + QWidget *appendTimeAssignmentStrip(int id, const QTime &duration, const QString &project, const QString &subproject, + const QString &workpackage, const QString &text); + + MainWindow &m_mainWindow; + + QBoxLayout *m_headerLayout; + QBoxLayout *m_stripsLayout; + + QLabel *m_label[2]; + + QDate m_date; + bool m_highlighted; + + GetDayinfoReply::Dayinfo m_dayinfo; + QVector m_bookings; + QVector m_timeAssignments; + + QTime m_timeAssignmentTime; + QTime m_lastTimeAssignmentStart; + QTime m_minimumTime; + bool m_refreshing; + bool m_refreshingDayinfo; + bool m_refreshingBookings; + bool m_refreshingTimeAssignments; + bool m_startEnabled; + bool m_endEnabled; + + std::unique_ptr m_getDayinfoReply; + std::unique_ptr m_getBookingsReply; + std::unique_ptr m_getTimeAssignmentsReply; +}; diff --git a/zeiterfassungguilib/translations/zeiterfassungguilib_de.ts b/zeiterfassungguilib/translations/zeiterfassungguilib_de.ts new file mode 100644 index 0000000..626b154 --- /dev/null +++ b/zeiterfassungguilib/translations/zeiterfassungguilib_de.ts @@ -0,0 +1,443 @@ + + + + + AuthenticationDialog + + + + Authentication + Authentifizierung + + + + Username: + Benutzername: + + + + Password: + Passwort: + + + + LanguageSelectionDialog + + + + Language selection + Sprachauswahl + + + + Please select a language: + Bitte wählen Sie eine Sprache: + + + + Language: + Sprache: + + + + English + Englisch + + + + German + Deutsch + + + + MainWindow + + + Previous day + Vorheriger Tag + + + + Next day + Nächster Tag + + + + Now + Jetzt + + + + + + Start + Kommen + + + + End + Gehen + + + + &File + &Datei + + + + &About + &Über + + + + &View + &Ansicht + + + + &Tools + &Werkzeuge + + + + &Quit + &Beenden + + + + About &Me + Über &mich + + + + About &zeiterfassung + Über &zeiterfassung + + + + About &Qt + Über &Qt + + + + &Today + &Heute + + + + &Refresh everything + Alles &neu laden + + + + &Settings + &Einstellungen + + + + Help + Hilfe + + + + Zeiterfassung - %0 (%1) + Zeiterfassung - %0 (%1) + + + + Subproject + Subprojekt + + + + Workpackage + Arbeitspaket + + + + Text + Text + + + + + Could not load bookings! + Konnte Buchungen nicht laden! + + + + + Could not create booking! + Konnte Buchung nicht erstellen! + + + + + Could not edit time assignment! + Konnte Kontierung nicht bearbeiten! + + + + + %0 (%1) + %0 (%1) + + + + Could not create time assignment! + Konnte Kontierung nicht erstellen! + + + + + Switch + Wechseln + + + + SettingsDialog + + + + Settings + Einstellungen + + + + Language: + Sprache: + + + + Theme: + Aussehen: + + + + English + Englisch + + + + German + Deutsch + + + + + + Invalid settings! + Ungültige Einstellungen! + + + + Unknown language! + Unbekannte Sprache! + + + + Default + Standard + + + + Unknown theme! + Unbekanntes Aussehen! + + + + Please fill all options with valid values! + Bitte füllen Sie alle Felder mit gültigen Werten! + + + + + Could not load theme! + Konnte Aussehen nicht laden! + + + + Theme file does not exist! + Aussehen-Datei existiert nicht! + + + + Restart required! + Neustart erforderlich! + + + + To apply the new settings a restart is required! + Um die neuen Einstellungen zu übernehmen, ist ein Neustart erforderlich! + + + + StripsWidget + + + + + Loading... + Lade... + + + + Missing booking! + Kontierung fehlend! + + + + Expected start booking, instead got type %0 +Booking ID: %1 + + + + + + %0: %1 + %0: %1 + + + + Break + Pause + + + + + + + %0h + %0h + + + + + + + + + HH:mm + HH:mm + + + + Missing time assignment! + Kontierung fehlend! + + + + + + HH:mm:ss + HH:mm:ss + + + + + There is another booking after an unfinished time assignment. +Booking ID: %0 +Time assignment ID: %1 + + + + + + + There is another time assignment after an unfinished time assignment. +Time assignment ID: %0 +Time assignment ID: %1 + + + + + The last time assignment is finished without end booking +Time assignment ID: %0 + + + + + Expected end booking, instead got type %0 +Booking ID: %1 + + + + + Missing time assignment! Missing: %0 + Kontierung fehlend! %0 nicht kontiert + + + + Assigned time + Kontierte Zeit + + + + dd.MM.yyyy + dd.MM.yyyy + + + + %0 (%1) + %0 (%1) + + + + Time assignment time longer than booking time! +Time assignment: %0 +Booking: %1 + + + + + Strip rendering aborted due error. +Your bookings and time assignments for this day are in an illegal state! + + + + + Monday + Montag + + + + Tuesday + Dienstag + + + + Wednesday + Mittwoch + + + + Thursday + Donnerstag + + + + Friday + Freitag + + + + Saturday + Samstag + + + + Sunday + Sonntag + + + + Invalid + Ungültig + + + + Open + Offen + + + diff --git a/zeiterfassungguilib/translations/zeiterfassungguilib_en.ts b/zeiterfassungguilib/translations/zeiterfassungguilib_en.ts new file mode 100644 index 0000000..b96835a --- /dev/null +++ b/zeiterfassungguilib/translations/zeiterfassungguilib_en.ts @@ -0,0 +1,443 @@ + + + + + AuthenticationDialog + + + + Authentication + + + + + Username: + + + + + Password: + + + + + LanguageSelectionDialog + + + + Language selection + + + + + Please select a language: + + + + + Language: + + + + + English + + + + + German + + + + + MainWindow + + + Previous day + + + + + Next day + + + + + Now + + + + + + + Start + + + + + End + + + + + &File + + + + + &About + + + + + &View + + + + + &Tools + + + + + &Quit + + + + + About &Me + + + + + About &zeiterfassung + + + + + About &Qt + + + + + &Today + + + + + &Refresh everything + + + + + &Settings + + + + + Help + + + + + Zeiterfassung - %0 (%1) + + + + + Subproject + + + + + Workpackage + + + + + Text + + + + + + Could not load bookings! + + + + + + Could not create booking! + + + + + Could not create time assignment! + + + + + + Could not edit time assignment! + + + + + + Switch + + + + + + %0 (%1) + + + + + SettingsDialog + + + + Settings + + + + + Language: + + + + + Theme: + + + + + English + + + + + German + + + + + + + Invalid settings! + + + + + Unknown language! + + + + + Default + + + + + Unknown theme! + + + + + Please fill all options with valid values! + + + + + + Could not load theme! + + + + + Theme file does not exist! + + + + + Restart required! + + + + + To apply the new settings a restart is required! + + + + + StripsWidget + + + + + Loading... + + + + + Missing booking! + + + + + Expected start booking, instead got type %0 +Booking ID: %1 + + + + + + %0: %1 + + + + + Break + + + + + + + + %0h + + + + + + + + + + HH:mm + + + + + Missing time assignment! + + + + + + + HH:mm:ss + + + + + + There is another booking after an unfinished time assignment. +Booking ID: %0 +Time assignment ID: %1 + + + + + + + There is another time assignment after an unfinished time assignment. +Time assignment ID: %0 +Time assignment ID: %1 + + + + + The last time assignment is finished without end booking +Time assignment ID: %0 + + + + + Expected end booking, instead got type %0 +Booking ID: %1 + + + + + Missing time assignment! Missing: %0 + + + + + Time assignment time longer than booking time! +Time assignment: %0 +Booking: %1 + + + + + Assigned time + + + + + Strip rendering aborted due error. +Your bookings and time assignments for this day are in an illegal state! + + + + + %0 (%1) + + + + + Monday + + + + + Tuesday + + + + + Wednesday + + + + + Thursday + + + + + Friday + + + + + Saturday + + + + + Sunday + + + + + dd.MM.yyyy + + + + + Invalid + + + + + Open + + + + diff --git a/zeiterfassungguilib/zeiterfassungdialog.cpp b/zeiterfassungguilib/zeiterfassungdialog.cpp new file mode 100644 index 0000000..3b68510 --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungdialog.cpp @@ -0,0 +1,13 @@ +#include "zeiterfassungdialog.h" + +#include + +ZeiterfassungDialog::ZeiterfassungDialog(QWidget *parent) : + QDialog(parent) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)) + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +#else + setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); +#endif +} diff --git a/zeiterfassungguilib/zeiterfassungdialog.h b/zeiterfassungguilib/zeiterfassungdialog.h new file mode 100644 index 0000000..0608d51 --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungdialog.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include "zeiterfassungguilib_global.h" + +class ZEITERFASSUNGGUILIB_EXPORT ZeiterfassungDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ZeiterfassungDialog(QWidget *parent = Q_NULLPTR); +}; diff --git a/zeiterfassungguilib/zeiterfassungguilib.pro b/zeiterfassungguilib/zeiterfassungguilib.pro new file mode 100644 index 0000000..a9f30c7 --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungguilib.pro @@ -0,0 +1,42 @@ +QT += core network gui widgets uitools + +DBLIBS += zeiterfassungcore + +TARGET = zeiterfassunggui + +PROJECT_ROOT = ../.. + +DEFINES += ZEITERFASSUNGGUILIB_LIBRARY + +SOURCES += mainwindow.cpp \ + settingswidget.cpp \ + stripfactory.cpp \ + stripswidget.cpp \ + zeiterfassungdialog.cpp \ + zeiterfassungplugin.cpp \ + dialogs/authenticationdialog.cpp \ + dialogs/languageselectiondialog.cpp \ + dialogs/settingsdialog.cpp + +HEADERS += mainwindow.h \ + settingswidget.h \ + stripfactory.h \ + stripswidget.h \ + zeiterfassungguilib_global.h \ + zeiterfassungdialog.h \ + zeiterfassungplugin.h \ + dialogs/authenticationdialog.h \ + dialogs/languageselectiondialog.h \ + dialogs/settingsdialog.h + +FORMS += mainwindow.ui \ + dialogs/settingsdialog.ui \ + dialogs/languageselectiondialog.ui \ + dialogs/authenticationdialog.ui + +RESOURCES += zeiterfassungguilib_resources.qrc + +TRANSLATIONS += translations/zeiterfassungguilib_en.ts \ + translations/zeiterfassungguilib_de.ts + +include($${PROJECT_ROOT}/lib.pri) diff --git a/zeiterfassungguilib/zeiterfassungguilib_global.h b/zeiterfassungguilib/zeiterfassungguilib_global.h new file mode 100644 index 0000000..34860db --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungguilib_global.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#if defined(ZEITERFASSUNGGUILIB_LIBRARY) +# define ZEITERFASSUNGGUILIB_EXPORT Q_DECL_EXPORT +#else +# define ZEITERFASSUNGGUILIB_EXPORT Q_DECL_IMPORT +#endif diff --git a/zeiterfassungguilib/zeiterfassungguilib_resources.qrc b/zeiterfassungguilib/zeiterfassungguilib_resources.qrc new file mode 100644 index 0000000..f5bc43c --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungguilib_resources.qrc @@ -0,0 +1,19 @@ + + + images/about.png + images/authentication.png + images/help.png + images/icon.png + images/next.png + images/next_week.png + images/now.png + images/previous.png + images/previous_week.png + images/qt.png + images/quit.png + images/refresh.png + images/settings.png + images/today.png + images/user.png + + diff --git a/zeiterfassungguilib/zeiterfassungplugin.cpp b/zeiterfassungguilib/zeiterfassungplugin.cpp new file mode 100644 index 0000000..0f0913a --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungplugin.cpp @@ -0,0 +1,13 @@ +#include "zeiterfassungplugin.h" + +ZeiterfassungPlugin::ZeiterfassungPlugin(QObject *parent) : + QObject(parent) +{ +} + +SettingsWidget *ZeiterfassungPlugin::settingsWidget(ZeiterfassungSettings &settings, QWidget *parent) const +{ + Q_UNUSED(settings) + Q_UNUSED(parent) + return Q_NULLPTR; +} diff --git a/zeiterfassungguilib/zeiterfassungplugin.h b/zeiterfassungguilib/zeiterfassungplugin.h new file mode 100644 index 0000000..df6b8b1 --- /dev/null +++ b/zeiterfassungguilib/zeiterfassungplugin.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +#include "zeiterfassungguilib_global.h" + +class MainWindow; +class StripsWidget; +class SettingsWidget; +class ZeiterfassungSettings; + +class ZEITERFASSUNGGUILIB_EXPORT ZeiterfassungPlugin : public QObject +{ + Q_OBJECT + +public: + explicit ZeiterfassungPlugin(QObject *parent = Q_NULLPTR); + + virtual void attachTo(MainWindow &mainWindow) { Q_UNUSED(mainWindow) } + + virtual SettingsWidget *settingsWidget(ZeiterfassungSettings &settings, QWidget *parent = Q_NULLPTR) const; +}; + +Q_DECLARE_INTERFACE(ZeiterfassungPlugin, "dbsoftware.zeiterfassung.plugin/1.0")