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 0000000..6c882ca Binary files /dev/null and b/zeiterfassung/icon.ico differ diff --git a/zeiterfassung/images/splash.png b/zeiterfassung/images/splash.png new file mode 100644 index 0000000..7140cd2 Binary files /dev/null and b/zeiterfassung/images/splash.png differ diff --git a/zeiterfassung/installs.pri b/zeiterfassung/installs.pri new file mode 100644 index 0000000..5434983 --- /dev/null +++ b/zeiterfassung/installs.pri @@ -0,0 +1,52 @@ +themesInstall.path = $${DESTDIR}/themes +themesInstall.files = themes/dark_theme.qss +INSTALLS += themesInstall + +darkThemeInstall.path = $${DESTDIR}/themes/dark_theme +darkThemeInstall.files = themes/dark_theme/checkbox_indeterminate_disabled.png \ + themes/dark_theme/radio_unchecked.png \ + themes/dark_theme/up_arrow.png \ + themes/dark_theme/branch_closed-on.png \ + themes/dark_theme/checkbox_checked_disabled.png \ + themes/dark_theme/checkbox_unchecked.png \ + themes/dark_theme/checkbox_indeterminate.png \ + themes/dark_theme/stylesheet-branch-more.png \ + themes/dark_theme/checkbox_checked.png \ + themes/dark_theme/checkbox_unchecked_disabled.png \ + themes/dark_theme/radio_checked.png \ + themes/dark_theme/checkbox_indeterminate_focus.png \ + themes/dark_theme/checkbox_checked_focus.png \ + themes/dark_theme/branch_closed.png \ + themes/dark_theme/Vsepartoolbar.png \ + themes/dark_theme/radio_checked_disabled.png \ + themes/dark_theme/left_arrow.png \ + themes/dark_theme/Vmovetoolbar.png \ + themes/dark_theme/branch_open-on.png \ + themes/dark_theme/close.png \ + themes/dark_theme/stylesheet-branch-end.png \ + themes/dark_theme/stylesheet-vline.png \ + themes/dark_theme/down_arrow_disabled.png \ + themes/dark_theme/radio_unchecked_disabled.png \ + themes/dark_theme/left_arrow_disabled.png \ + themes/dark_theme/Hmovetoolbar.png \ + themes/dark_theme/close-pressed.png \ + themes/dark_theme/up_arrow_disabled.png \ + themes/dark_theme/branch_open.png \ + themes/dark_theme/radio_checked_focus.png \ + themes/dark_theme/sizegrip.png \ + themes/dark_theme/checkbox_unchecked_focus.png \ + themes/dark_theme/right_arrow_disabled.png \ + themes/dark_theme/Hsepartoolbar.png \ + themes/dark_theme/undock.png \ + themes/dark_theme/transparent.png \ + themes/dark_theme/close-hover.png \ + themes/dark_theme/radio_unchecked_focus.png \ + themes/dark_theme/down_arrow.png \ + themes/dark_theme/right_arrow.png +INSTALLS += darkThemeInstall + +stripLayoutsInstall.path = $${DESTDIR}/strips +stripLayoutsInstall.files = strips/bookingstartstrip.ui \ + strips/bookingendstrip.ui \ + strips/timeassignmentstrip.ui +INSTALLS += stripLayoutsInstall diff --git a/zeiterfassung/main.cpp b/zeiterfassung/main.cpp new file mode 100755 index 0000000..b185274 --- /dev/null +++ b/zeiterfassung/main.cpp @@ -0,0 +1,379 @@ +#include + +#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 0000000..cead99e Binary files /dev/null and b/zeiterfassung/themes/dark_theme/Hmovetoolbar.png differ diff --git a/zeiterfassung/themes/dark_theme/Hsepartoolbar.png b/zeiterfassung/themes/dark_theme/Hsepartoolbar.png new file mode 100644 index 0000000..7f183c8 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/Hsepartoolbar.png differ diff --git a/zeiterfassung/themes/dark_theme/Vmovetoolbar.png b/zeiterfassung/themes/dark_theme/Vmovetoolbar.png new file mode 100644 index 0000000..512edce Binary files /dev/null and b/zeiterfassung/themes/dark_theme/Vmovetoolbar.png differ diff --git a/zeiterfassung/themes/dark_theme/Vsepartoolbar.png b/zeiterfassung/themes/dark_theme/Vsepartoolbar.png new file mode 100644 index 0000000..d9dc156 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/Vsepartoolbar.png differ 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 0000000..d081e9b Binary files /dev/null and b/zeiterfassung/themes/dark_theme/branch_closed-on.png differ diff --git a/zeiterfassung/themes/dark_theme/branch_closed.png b/zeiterfassung/themes/dark_theme/branch_closed.png new file mode 100644 index 0000000..d652159 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/branch_closed.png differ diff --git a/zeiterfassung/themes/dark_theme/branch_open-on.png b/zeiterfassung/themes/dark_theme/branch_open-on.png new file mode 100644 index 0000000..ec372b2 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/branch_open-on.png differ diff --git a/zeiterfassung/themes/dark_theme/branch_open.png b/zeiterfassung/themes/dark_theme/branch_open.png new file mode 100644 index 0000000..66f8e1a Binary files /dev/null and b/zeiterfassung/themes/dark_theme/branch_open.png differ diff --git a/zeiterfassung/themes/dark_theme/checkbox_checked.png b/zeiterfassung/themes/dark_theme/checkbox_checked.png new file mode 100644 index 0000000..830cfee Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_checked.png differ 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 0000000..cb63cc2 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_checked_disabled.png differ 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 0000000..671be27 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_checked_focus.png differ diff --git a/zeiterfassung/themes/dark_theme/checkbox_indeterminate.png b/zeiterfassung/themes/dark_theme/checkbox_indeterminate.png new file mode 100644 index 0000000..41024f7 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_indeterminate.png differ diff --git a/zeiterfassung/themes/dark_theme/checkbox_indeterminate_disabled.png b/zeiterfassung/themes/dark_theme/checkbox_indeterminate_disabled.png new file mode 100644 index 0000000..abdc01d Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_indeterminate_disabled.png differ 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 0000000..415f9b6 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_indeterminate_focus.png differ diff --git a/zeiterfassung/themes/dark_theme/checkbox_unchecked.png b/zeiterfassung/themes/dark_theme/checkbox_unchecked.png new file mode 100644 index 0000000..2159aca Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_unchecked.png differ 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 0000000..ade721e Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_unchecked_disabled.png differ diff --git a/zeiterfassung/themes/dark_theme/checkbox_unchecked_focus.png b/zeiterfassung/themes/dark_theme/checkbox_unchecked_focus.png new file mode 100644 index 0000000..e4258cc Binary files /dev/null and b/zeiterfassung/themes/dark_theme/checkbox_unchecked_focus.png differ diff --git a/zeiterfassung/themes/dark_theme/close-hover.png b/zeiterfassung/themes/dark_theme/close-hover.png new file mode 100644 index 0000000..657943a Binary files /dev/null and b/zeiterfassung/themes/dark_theme/close-hover.png differ diff --git a/zeiterfassung/themes/dark_theme/close-pressed.png b/zeiterfassung/themes/dark_theme/close-pressed.png new file mode 100644 index 0000000..937d005 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/close-pressed.png differ diff --git a/zeiterfassung/themes/dark_theme/close.png b/zeiterfassung/themes/dark_theme/close.png new file mode 100644 index 0000000..bc0f576 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/close.png differ diff --git a/zeiterfassung/themes/dark_theme/down_arrow.png b/zeiterfassung/themes/dark_theme/down_arrow.png new file mode 100644 index 0000000..e271f7f Binary files /dev/null and b/zeiterfassung/themes/dark_theme/down_arrow.png differ diff --git a/zeiterfassung/themes/dark_theme/down_arrow_disabled.png b/zeiterfassung/themes/dark_theme/down_arrow_disabled.png new file mode 100644 index 0000000..5805d98 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/down_arrow_disabled.png differ diff --git a/zeiterfassung/themes/dark_theme/left_arrow.png b/zeiterfassung/themes/dark_theme/left_arrow.png new file mode 100644 index 0000000..f808d2d Binary files /dev/null and b/zeiterfassung/themes/dark_theme/left_arrow.png differ 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 0000000..f5b9af8 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/left_arrow_disabled.png differ diff --git a/zeiterfassung/themes/dark_theme/radio_checked.png b/zeiterfassung/themes/dark_theme/radio_checked.png new file mode 100644 index 0000000..235e6b0 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_checked.png differ 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 0000000..bf0051e Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_checked_disabled.png differ diff --git a/zeiterfassung/themes/dark_theme/radio_checked_focus.png b/zeiterfassung/themes/dark_theme/radio_checked_focus.png new file mode 100644 index 0000000..700c6b5 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_checked_focus.png differ diff --git a/zeiterfassung/themes/dark_theme/radio_unchecked.png b/zeiterfassung/themes/dark_theme/radio_unchecked.png new file mode 100644 index 0000000..9a4def6 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_unchecked.png differ 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 0000000..6ece890 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_unchecked_disabled.png differ 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 0000000..564e022 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/radio_unchecked_focus.png differ diff --git a/zeiterfassung/themes/dark_theme/right_arrow.png b/zeiterfassung/themes/dark_theme/right_arrow.png new file mode 100644 index 0000000..9b0a4e6 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/right_arrow.png differ 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 0000000..5c0bee4 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/right_arrow_disabled.png differ diff --git a/zeiterfassung/themes/dark_theme/sizegrip.png b/zeiterfassung/themes/dark_theme/sizegrip.png new file mode 100644 index 0000000..350583a Binary files /dev/null and b/zeiterfassung/themes/dark_theme/sizegrip.png differ 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 0000000..cb5d3b5 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/stylesheet-branch-end.png differ diff --git a/zeiterfassung/themes/dark_theme/stylesheet-branch-more.png b/zeiterfassung/themes/dark_theme/stylesheet-branch-more.png new file mode 100644 index 0000000..6271140 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/stylesheet-branch-more.png differ diff --git a/zeiterfassung/themes/dark_theme/stylesheet-vline.png b/zeiterfassung/themes/dark_theme/stylesheet-vline.png new file mode 100644 index 0000000..87536cc Binary files /dev/null and b/zeiterfassung/themes/dark_theme/stylesheet-vline.png differ diff --git a/zeiterfassung/themes/dark_theme/transparent.png b/zeiterfassung/themes/dark_theme/transparent.png new file mode 100644 index 0000000..483df25 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/transparent.png differ diff --git a/zeiterfassung/themes/dark_theme/undock.png b/zeiterfassung/themes/dark_theme/undock.png new file mode 100644 index 0000000..88691d7 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/undock.png differ diff --git a/zeiterfassung/themes/dark_theme/up_arrow.png b/zeiterfassung/themes/dark_theme/up_arrow.png new file mode 100644 index 0000000..abcc724 Binary files /dev/null and b/zeiterfassung/themes/dark_theme/up_arrow.png differ 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 0000000..b9c8e3b Binary files /dev/null and b/zeiterfassung/themes/dark_theme/up_arrow_disabled.png differ 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 0000000..b83e926 Binary files /dev/null and b/zeiterfassungguilib/images/about.png differ diff --git a/zeiterfassungguilib/images/authentication.png b/zeiterfassungguilib/images/authentication.png new file mode 100644 index 0000000..4685f3b Binary files /dev/null and b/zeiterfassungguilib/images/authentication.png differ diff --git a/zeiterfassungguilib/images/help.png b/zeiterfassungguilib/images/help.png new file mode 100644 index 0000000..9242cc8 Binary files /dev/null and b/zeiterfassungguilib/images/help.png differ diff --git a/zeiterfassungguilib/images/icon.png b/zeiterfassungguilib/images/icon.png new file mode 100644 index 0000000..0b80d08 Binary files /dev/null and b/zeiterfassungguilib/images/icon.png differ diff --git a/zeiterfassungguilib/images/next.png b/zeiterfassungguilib/images/next.png new file mode 100644 index 0000000..0df2a6c Binary files /dev/null and b/zeiterfassungguilib/images/next.png differ diff --git a/zeiterfassungguilib/images/next_week.png b/zeiterfassungguilib/images/next_week.png new file mode 100644 index 0000000..1eff4d8 Binary files /dev/null and b/zeiterfassungguilib/images/next_week.png differ diff --git a/zeiterfassungguilib/images/now.png b/zeiterfassungguilib/images/now.png new file mode 100644 index 0000000..f138cb4 Binary files /dev/null and b/zeiterfassungguilib/images/now.png differ diff --git a/zeiterfassungguilib/images/previous.png b/zeiterfassungguilib/images/previous.png new file mode 100644 index 0000000..2bf9523 Binary files /dev/null and b/zeiterfassungguilib/images/previous.png differ diff --git a/zeiterfassungguilib/images/previous_week.png b/zeiterfassungguilib/images/previous_week.png new file mode 100644 index 0000000..b636bbe Binary files /dev/null and b/zeiterfassungguilib/images/previous_week.png differ diff --git a/zeiterfassungguilib/images/qt.png b/zeiterfassungguilib/images/qt.png new file mode 100644 index 0000000..f0088ac Binary files /dev/null and b/zeiterfassungguilib/images/qt.png differ diff --git a/zeiterfassungguilib/images/quit.png b/zeiterfassungguilib/images/quit.png new file mode 100644 index 0000000..77c586c Binary files /dev/null and b/zeiterfassungguilib/images/quit.png differ diff --git a/zeiterfassungguilib/images/refresh.png b/zeiterfassungguilib/images/refresh.png new file mode 100644 index 0000000..f3585b7 Binary files /dev/null and b/zeiterfassungguilib/images/refresh.png differ diff --git a/zeiterfassungguilib/images/settings.png b/zeiterfassungguilib/images/settings.png new file mode 100644 index 0000000..dcdb755 Binary files /dev/null and b/zeiterfassungguilib/images/settings.png differ diff --git a/zeiterfassungguilib/images/today.png b/zeiterfassungguilib/images/today.png new file mode 100644 index 0000000..d0923ae Binary files /dev/null and b/zeiterfassungguilib/images/today.png differ diff --git a/zeiterfassungguilib/images/user.png b/zeiterfassungguilib/images/user.png new file mode 100644 index 0000000..e6e955f Binary files /dev/null and b/zeiterfassungguilib/images/user.png differ diff --git a/zeiterfassungguilib/mainwindow.cpp b/zeiterfassungguilib/mainwindow.cpp new file mode 100644 index 0000000..e3d93ad --- /dev/null +++ b/zeiterfassungguilib/mainwindow.cpp @@ -0,0 +1,472 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#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")