Просмотр исходного кода

Merge branch 'master' of https://devel.mephi.ru/dyokunev/tasks

# Conflicts:
#	mainwindowandroid.ui
#	mephi-tasks.pro
#	qtredmine
Anton A Truttse лет назад: 8
Родитель
Сommit
0ad91b82e7

+ 56 - 0
android/AndroidManifest.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0"?>
+<manifest package="org.qtproject.mephiTasks" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="0.6" android:versionCode="6" android:installLocation="auto">
+    <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:icon="@drawable/icon">
+	<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop">
+	    <intent-filter>
+		<action android:name="android.intent.action.MAIN"/>
+		<category android:name="android.intent.category.LAUNCHER"/>
+	    </intent-filter>
+	    <meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
+	    <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
+	    <meta-data android:name="android.app.repository" android:value="default"/>
+	    <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
+	    <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
+	    <!-- Deploy Qt libs as part of package -->
+	    <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
+	    <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
+	    <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
+	    <!-- Run with local libs -->
+	    <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
+	    <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
+	    <meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/>
+	    <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/>
+	    <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/>
+	    <!--  Messages maps -->
+	    <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
+	    <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
+	    <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
+	    <!--  Messages maps -->
+
+	    <!-- Splash screen -->
+	    <!--
+	    <meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/>
+	    -->
+	    <!-- Splash screen -->
+
+	    <!-- Background running -->
+	    <!-- Warning: changing this value to true may cause unexpected crashes if the
+			  application still try to draw after
+			  "applicationStateChanged(Qt::ApplicationSuspended)"
+			  signal is sent! -->
+	    <meta-data android:name="android.app.background_running" android:value="false"/>
+	    <!-- Background running -->
+	</activity>
+    </application>
+    <uses-sdk android:minSdkVersion="9" android:targetSdkVersion="14"/>
+    <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
+
+    <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
+	 Remove the comment if you do not require these default permissions. -->
+    <!-- %%INSERT_PERMISSIONS -->
+
+    <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
+	 Remove the comment if you do not require these default features. -->
+    <!-- %%INSERT_FEATURES -->
+
+</manifest>

+ 57 - 0
android/build.gradle

@@ -0,0 +1,57 @@
+buildscript {
+    repositories {
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:1.1.0'
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+    }
+}
+
+apply plugin: 'com.android.application'
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+}
+
+android {
+    /*******************************************************
+     * The following variables:
+     * - androidBuildToolsVersion,
+     * - androidCompileSdkVersion
+     * - qt5AndroidDir - holds the path to qt android files
+     *                   needed to build any Qt application
+     *                   on Android.
+     *
+     * are defined in gradle.properties file. This file is
+     * updated by QtCreator and androiddeployqt tools.
+     * Changing them manually might break the compilation!
+     *******************************************************/
+
+    compileSdkVersion androidCompileSdkVersion.toInteger()
+
+    buildToolsVersion androidBuildToolsVersion
+
+    sourceSets {
+        main {
+            manifest.srcFile 'AndroidManifest.xml'
+            java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
+            aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
+            res.srcDirs = [qt5AndroidDir + '/res', 'res']
+            resources.srcDirs = ['src']
+            renderscript.srcDirs = ['src']
+            assets.srcDirs = ['assets']
+            jniLibs.srcDirs = ['libs']
+       }
+    }
+
+    lintOptions {
+        abortOnError false
+    }
+}

BIN
android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Wed Apr 10 15:27:10 PDT 2013
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip

+ 164 - 0
android/gradlew

@@ -0,0 +1,164 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched.
+if $cygwin ; then
+    [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+fi
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >&-
+APP_HOME="`pwd -P`"
+cd "$SAVED" >&-
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
android/gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

BIN
android/res/drawable-hdpi/icon.png


BIN
android/res/drawable-ldpi/icon.png


BIN
android/res/drawable-mdpi/icon.png


+ 25 - 0
android/res/values/libs.xml

@@ -0,0 +1,25 @@
+<?xml version='1.0' encoding='utf-8'?>
+<resources>
+    <array name="qt_sources">
+        <item>https://download.qt-project.org/ministro/android/qt5/qt-5.4</item>
+    </array>
+
+    <!-- The following is handled automatically by the deployment tool. It should
+         not be edited manually. -->
+
+    <array name="bundled_libs">
+        <!-- %%INSERT_EXTRA_LIBS%% -->
+    </array>
+
+     <array name="qt_libs">
+         <!-- %%INSERT_QT_LIBS%% -->
+     </array>
+
+    <array name="bundled_in_lib">
+        <!-- %%INSERT_BUNDLED_IN_LIB%% -->
+    </array>
+    <array name="bundled_in_assets">
+        <!-- %%INSERT_BUNDLED_IN_ASSETS%% -->
+    </array>
+
+</resources>

+ 22 - 0
common.h

@@ -1,6 +1,14 @@
 #ifndef COMMON_H
 #define COMMON_H
 
+#if _MSC_VER && !__INTEL_COMPILER
+#pragma pointers_to_members( full_generality, virtual_inheritance )
+#endif
+
+#if __ANDROID__ || _M_ARM || __WINRT__
+#define __MOBILE__
+#endif
+
 #include <QApplication>
 
 #include "redmine.h"
@@ -20,9 +28,23 @@ struct settings {
 	QString apiKey;
 	QString issuesFilter;
 	enum mode mode;
+	bool hideOnStart;
 };
 extern struct settings settings;
 extern void loadSettings();
 extern void saveSettings();
 
+bool issueCmpFunct_statusIsClosed_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_statusPosition_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_statusPosition_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_updatedOn_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_updatedOn_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_name_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_name_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_assignee_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_assignee_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_dueTo_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool issueCmpFunct_dueTo_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b );
+bool timeEntryCmpFunct_from_lt ( const QJsonObject &timeEntry_a, const QJsonObject &timeEntry_b );
+
 #endif // COMMON_H

+ 10 - 3
logtimewindow.cpp

@@ -16,10 +16,16 @@ LogTimeWindow::LogTimeWindow ( QWidget *parent ) :
 
 	this->timeEntry.setRedmine ( redmine );
 
+	QTime currentTime      = QTime::currentTime();
+	QTime initialSinceTime = QTime::fromString ( "09:00", "hh':'mm" );
+	if (initialSinceTime > currentTime) {
+		initialSinceTime = currentTime;
+	}
+
 	this->ui->untilInput->setDate ( QDate::currentDate() );
 	this->ui->sinceInput->setDate ( QDate::currentDate() );
-	this->ui->untilInput->setTime ( QTime::currentTime() );
-	this->ui->sinceInput->setTime ( QTime::fromString ( "09:00", "hh':'mm" ) );
+	this->ui->untilInput->setTime ( currentTime );
+	this->ui->sinceInput->setTime ( initialSinceTime );
 	this->setWindowTitle ( "Система «Задачи» НИЯУ МИФИ: Учёт времени" );
 	connect ( redmine, SIGNAL ( callback_call       ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
               this,    SLOT   ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
@@ -34,13 +40,14 @@ LogTimeWindow::LogTimeWindow ( QWidget *parent ) :
 
 LogTimeWindow::~LogTimeWindow()
 {
+	this->on_destructor();
 	delete ui;
 }
 
 void LogTimeWindow::on_saveSuccess()
 {
 	qDebug ( "LogTimeWindow::on_saveSuccess()" );
-	redmine->get_time_entries ( NULL, NULL, NULL, false, "user_id=me&limit=1" ); // Just to update the cache
+    redmine->get_time_entries ( ( void * ) NULL, NULL, NULL, false, "user_id=me&limit=1" ); // Just to update the cache
 	delete this;
 }
 

+ 3 - 0
logtimewindow.h

@@ -36,6 +36,9 @@ public:
 	QHash<int, QList<QJsonObject>> issues_byProjectId;
 	QHash<int, QList<QJsonObject>> issuesFiltered_byProjectId;
 
+signals:
+	void on_destructor();
+
 private slots:
 	void on_cancel_clicked();
 	void on_accept_clicked();

+ 93 - 5
main.cpp

@@ -18,7 +18,7 @@
  */
 
 #ifndef __ANDROID__
-#	define __ANDROID__
+//#	define __ANDROID__
 #endif
 
 #include "mainwindow-rector.h"
@@ -42,6 +42,7 @@ void loadSettings()
 	QSettings qsettings ( settings.settingsFilePath, QSettings::IniFormat );
 	settings.apiKey       = qsettings.value ( "apiKey" ).toString();
 	settings.issuesFilter = qsettings.value ( "issuesFilter" ).toString();
+	settings.hideOnStart  = qsettings.value ( "hideOnStart" ).toBool();
 	QString mode          = qsettings.value ( "mode" ).toString();
 
 	if ( mode == "rector" ) {
@@ -53,12 +54,94 @@ void loadSettings()
 	return;
 }
 
+
+bool issueCmpFunct_statusIsClosed_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	int issue_statusIsClosed_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["is_closed"].toBool();
+	int issue_statusIsClosed_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["is_closed"].toBool();
+	return issue_statusIsClosed_a < issue_statusIsClosed_b;
+}
+
+
+bool issueCmpFunct_statusPosition_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	int issue_statusPosition_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["position"].toInt();
+	int issue_statusPosition_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["position"].toInt();
+	return issue_statusPosition_a < issue_statusPosition_b;
+}
+bool issueCmpFunct_statusPosition_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	int issue_statusPosition_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["position"].toInt();
+	int issue_statusPosition_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["position"].toInt();
+	return issue_statusPosition_a > issue_statusPosition_b;
+}
+
+bool issueCmpFunct_updatedOn_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_updatedOn_a = issue_a["updated_on"].toString();
+	QString issue_updatedOn_b = issue_b["updated_on"].toString();
+	return issue_updatedOn_a < issue_updatedOn_b;
+}
+bool issueCmpFunct_updatedOn_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_updatedOn_a = issue_a["updated_on"].toString();
+	QString issue_updatedOn_b = issue_b["updated_on"].toString();
+	return issue_updatedOn_a > issue_updatedOn_b;
+}
+
+bool issueCmpFunct_name_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_name_a = issue_a["name"].toString();
+	QString issue_name_b = issue_b["name"].toString();
+	return issue_name_a < issue_name_b;
+}
+bool issueCmpFunct_name_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_name_a = issue_a["name"].toString();
+	QString issue_name_b = issue_b["name"].toString();
+	return issue_name_a > issue_name_b;
+}
+
+bool issueCmpFunct_assignee_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_assignee_a = issue_a["assigned_to"].toObject() ["name"].toString();
+	QString issue_assignee_b = issue_b["assigned_to"].toObject() ["name"].toString();
+	return issue_assignee_a < issue_assignee_b;
+}
+bool issueCmpFunct_assignee_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_assignee_a = issue_a["assigned_to"].toObject() ["name"].toString();
+	QString issue_assignee_b = issue_b["assigned_to"].toObject() ["name"].toString();
+	return issue_assignee_a > issue_assignee_b;
+}
+
+bool issueCmpFunct_dueTo_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_dueTo_a = issue_a["due_date"].toString();
+	QString issue_dueTo_b = issue_b["due_date"].toString();
+	return issue_dueTo_a < issue_dueTo_b;
+}
+bool issueCmpFunct_dueTo_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
+{
+	QString issue_dueTo_a = issue_a["due_date"].toString();
+	QString issue_dueTo_b = issue_b["due_date"].toString();
+	return issue_dueTo_a > issue_dueTo_b;
+}
+
+bool timeEntryCmpFunct_from_lt ( const QJsonObject &timeEntry_a, const QJsonObject &timeEntry_b )
+{
+	QString timeEntry_from_a = timeEntry_a["from"].toString();
+	QString timeEntry_from_b = timeEntry_b["from"].toString();
+	return timeEntry_from_a < timeEntry_from_b;
+}
+
 void saveSettings()
 {
 	QSettings qsettings ( settings.settingsFilePath, QSettings::IniFormat );
 	qsettings.setValue ( "apiKey",        settings.apiKey );
 	qsettings.setValue ( "issuesFilter",  settings.issuesFilter );
 	qsettings.setValue ( "mode",         ( settings.mode == MODE_RECTOR ) ? "rector" : "full" );
+	qsettings.setValue ( "hideOnStart",   settings.hideOnStart );
 	return;
 }
 
@@ -103,7 +186,8 @@ int main ( int argc, char *argv[] )
 		redmine = &_redmine;
 		redmine->apiKey ( settings.apiKey );
 		redmine->init();
-#ifdef __ANDROID__
+#ifdef __MOBILE__
+		qDebug("Mode: MOBILE");
 		MainWindowAndroid w;
 		w.show();
 		rc = a.exec();
@@ -112,7 +196,9 @@ int main ( int argc, char *argv[] )
 		switch ( settings.mode ) {
 			case MODE_RECTOR: {
 					MainWindowRector w;
-					w.show();
+                    qDebug("Mode: RECTOR");
+                    if (!settings.hideOnStart)
+						w.show();
 					a.setQuitOnLastWindowClosed ( false );
 					rc = a.exec();
 					break;
@@ -120,8 +206,10 @@ int main ( int argc, char *argv[] )
 
 			default: {
 					MainWindowFull w;
-					w.show();
-					//a.setQuitOnLastWindowClosed(false);
+                    qDebug("Mode: FULL");
+                    if (!settings.hideOnStart)
+						w.show();
+					a.setQuitOnLastWindowClosed ( false );
 					rc = a.exec();
 					break;
 				}

+ 123 - 80
mainwindow-common.cpp

@@ -18,81 +18,10 @@
  */
 
 #include <QSettings>
+#include <QMenu>
 
 #include "mainwindow-common.h"
 
-bool issueCmpFunct_statusIsClosed_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	int issue_statusIsClosed_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["is_closed"].toBool();
-	int issue_statusIsClosed_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["is_closed"].toBool();
-	return issue_statusIsClosed_a < issue_statusIsClosed_b;
-}
-
-
-bool issueCmpFunct_statusPosition_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	int issue_statusPosition_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["position"].toInt();
-	int issue_statusPosition_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["position"].toInt();
-	return issue_statusPosition_a < issue_statusPosition_b;
-}
-bool issueCmpFunct_statusPosition_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	int issue_statusPosition_a = redmine->get_issue_status ( issue_a["status"].toObject() ["id"].toInt() ) ["position"].toInt();
-	int issue_statusPosition_b = redmine->get_issue_status ( issue_b["status"].toObject() ["id"].toInt() ) ["position"].toInt();
-	return issue_statusPosition_a > issue_statusPosition_b;
-}
-
-bool issueCmpFunct_updatedOn_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_updatedOn_a = issue_a["updated_on"].toString();
-	QString issue_updatedOn_b = issue_b["updated_on"].toString();
-	return issue_updatedOn_a < issue_updatedOn_b;
-}
-bool issueCmpFunct_updatedOn_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_updatedOn_a = issue_a["updated_on"].toString();
-	QString issue_updatedOn_b = issue_b["updated_on"].toString();
-	return issue_updatedOn_a > issue_updatedOn_b;
-}
-
-bool issueCmpFunct_name_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_name_a = issue_a["name"].toString();
-	QString issue_name_b = issue_b["name"].toString();
-	return issue_name_a < issue_name_b;
-}
-bool issueCmpFunct_name_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_name_a = issue_a["name"].toString();
-	QString issue_name_b = issue_b["name"].toString();
-	return issue_name_a > issue_name_b;
-}
-
-bool issueCmpFunct_assignee_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_assignee_a = issue_a["assigned_to"].toObject() ["name"].toString();
-	QString issue_assignee_b = issue_b["assigned_to"].toObject() ["name"].toString();
-	return issue_assignee_a < issue_assignee_b;
-}
-bool issueCmpFunct_assignee_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_assignee_a = issue_a["assigned_to"].toObject() ["name"].toString();
-	QString issue_assignee_b = issue_b["assigned_to"].toObject() ["name"].toString();
-	return issue_assignee_a > issue_assignee_b;
-}
-
-bool issueCmpFunct_dueTo_lt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_dueTo_a = issue_a["due_date"].toString();
-	QString issue_dueTo_b = issue_b["due_date"].toString();
-	return issue_dueTo_a < issue_dueTo_b;
-}
-bool issueCmpFunct_dueTo_gt ( const QJsonObject &issue_a, const QJsonObject &issue_b )
-{
-	QString issue_dueTo_a = issue_a["due_date"].toString();
-	QString issue_dueTo_b = issue_b["due_date"].toString();
-	return issue_dueTo_a > issue_dueTo_b;
-}
 
 MainWindowCommon::MainWindowCommon ( QWidget *parent ) :
 	QMainWindow ( parent )
@@ -110,10 +39,22 @@ MainWindowCommon::MainWindowCommon ( QWidget *parent ) :
 	this->sortFunctMap.insert ( SORT_UPDATED_ON_ASC,      issueCmpFunct_updatedOn_lt );
 	this->sortFunctMap.insert ( SORT_UPDATED_ON_DESC,     issueCmpFunct_updatedOn_gt );
 	this->sortFunctMap.insert ( SORT_STATUS_ISCLOSED_ASC, issueCmpFunct_statusIsClosed_lt );
+
+	this->sortFunctMap.insert ( SORT_TIMEENTRY_FROM_ASC,  timeEntryCmpFunct_from_lt );
+
 	memset ( this->sortColumn, 0, sizeof ( this->sortColumn ) );
-	this->sortColumn[0] = ( MainWindowCommon::ESortColumn ) qsettings.value ( "sortcode"  ).toInt();
-	this->sortLogicalIndex =                             qsettings.value ( "sortcolumn" ).toInt();
-	this->sortOrder        =              ( Qt::SortOrder ) qsettings.value ( "sortorder" ).toInt();
+	this->sortColumn[0] = ( MainWindowCommon::ESortColumn ) qsettings.value ( "sortcode"   ).toInt();
+	this->sortLogicalIndex =                                qsettings.value ( "sortcolumn" ).toInt();
+	this->sortOrder        =              ( Qt::SortOrder ) qsettings.value ( "sortorder"  ).toInt();
+
+	this->createIconComboBox();
+	this->createTrayIcon();
+	this->status ( GOOD );
+	this->setIcon ( GOOD );
+	this->trayIcon->show();
+
+	this->logTimeWindow = NULL;
+
 	return;
 }
 
@@ -140,7 +81,7 @@ void MainWindowCommon::get_roles_callback ( QNetworkReply *reply, QJsonDocument
 
 int MainWindowCommon::updateRoles()
 {
-	redmine->get_roles ( ( Redmine::callback_t ) &MainWindowCommon::get_roles_callback, this );
+    redmine->get_roles ( ( Redmine::callback_t ) &MainWindowCommon::get_roles_callback, this );
 	return 0;
 }
 
@@ -157,7 +98,7 @@ void MainWindowCommon::get_memberships_callback ( QNetworkReply *reply, QJsonDoc
 
 int MainWindowCommon::updateMemberships()
 {
-	redmine->get_memberships ( ( Redmine::callback_t ) &MainWindowCommon::get_memberships_callback, this );
+    redmine->get_memberships ( ( Redmine::callback_t ) &MainWindowCommon::get_memberships_callback, this );
 	return 0;
 }
 
@@ -177,7 +118,7 @@ void MainWindowCommon::get_enumerations_callback ( QNetworkReply *reply, QJsonDo
 
 int MainWindowCommon::updateEnumerations()
 {
-	redmine->get_enumerations ( ( Redmine::callback_t ) &MainWindowCommon::get_enumerations_callback, this );
+    redmine->get_enumerations ( ( Redmine::callback_t ) &MainWindowCommon::get_enumerations_callback, this );
 	return 0;
 }
 
@@ -210,7 +151,7 @@ QList<QJsonObject> MainWindowCommon::issues_get_byProjectId ( int project_id )
 
 int MainWindowCommon::updateProjects()
 {
-	redmine->get_projects ( ( Redmine::callback_t ) &MainWindowCommon::get_projects_callback, this );
+    redmine->get_projects ( ( Redmine::callback_t ) &MainWindowCommon::get_projects_callback, this );
 	return 0;
 }
 
@@ -239,8 +180,110 @@ void MainWindowCommon::get_issues_callback ( QNetworkReply *reply, QJsonDocument
 
 int MainWindowCommon::updateIssues()
 {
-	redmine->get_issues ( ( Redmine::callback_t ) &MainWindowCommon::get_issues_callback, this, false, "status=*&limit=10000" );
+    redmine->get_issues ( ( Redmine::callback_t ) &MainWindowCommon::get_issues_callback, this, false, "status=*&limit=10000" );
 	return 0;
 }
 
 /**** /updateIssues ****/
+
+/**** tray-related stuff ****/
+
+void MainWindowCommon::createTrayIcon()
+{
+	this->trayIconMenu = new QMenu ( this );
+	this->trayIcon = new QSystemTrayIcon ( this );
+	this->trayIcon->setContextMenu ( trayIconMenu );
+
+	return;
+}
+
+void MainWindowCommon::setIcon ( EIcon index )
+{
+	//qDebug("icon: %i", index);
+	QIcon icon = this->iconComboBox.itemIcon ( index );
+	this->trayIcon->setIcon ( icon );
+	this->setWindowIcon ( icon );
+	this->trayIcon->setToolTip ( this->iconComboBox.itemText ( index ) );
+}
+
+void MainWindowCommon::createIconComboBox()
+{
+	this->iconComboBox.addItem ( QIcon ( ":/images/good.png" ), tr ( "Просроченных задач нет" ) );
+	this->iconComboBox.addItem ( QIcon ( ":/images/bad.png" ),  tr ( "Есть просроченные задачи" ) );
+	return;
+}
+
+void MainWindowCommon::on_closeLogTimeWindow()
+{
+	this->logTimeWindow = NULL;
+
+	return;
+}
+
+void MainWindowCommon::openLogTimeWindow()
+{
+	if (this->logTimeWindow != NULL)
+		delete this->logTimeWindow;
+	this->logTimeWindow = new LogTimeWindow;
+
+	connect ( this->logTimeWindow, SIGNAL ( on_destructor() ), this, SLOT ( on_closeLogTimeWindow() ) );
+
+	this->logTimeWindow->show();
+
+	return;
+}
+
+
+void MainWindowCommon::on_closeShowTimeWindow()
+{
+	this->showTimeWindow = NULL;
+
+	return;
+}
+
+void MainWindowCommon::openShowTimeWindow()
+{
+	if (this->showTimeWindow != NULL)
+		delete this->showTimeWindow;
+	this->showTimeWindow = new ShowTimeWindow;
+
+	connect ( this->showTimeWindow, SIGNAL ( on_destructor() ), this, SLOT ( on_closeShowTimeWindow() ) );
+
+	this->showTimeWindow->show();
+
+	return;
+}
+
+
+void MainWindowCommon::showOnTop()
+{
+#ifdef Q_OS_WIN32
+	// raise() doesn't work :(
+	Qt::WindowFlags flags_old   = this->windowFlags();
+	Qt::WindowFlags flags_ontop = flags_old | Qt::WindowStaysOnTopHint;
+	this->setWindowFlags ( flags_ontop );
+	this->show();
+	this->setWindowFlags ( flags_old );
+	this->show();
+#else
+	this->show();
+	this->raise();
+#endif
+	return;
+}
+
+void MainWindowCommon::toggleShowHide()
+{
+	if ( this->isVisible() ) {
+		settings.hideOnStart = true;
+		this->hide();
+	} else {
+		settings.hideOnStart = false;
+		this->showOnTop();
+	}
+
+	return;
+}
+
+
+/**** /tray-related stuff ****/

+ 49 - 0
mainwindow-common.h

@@ -4,11 +4,14 @@
 #include <QMainWindow>
 #include <QTableWidgetSelectionRange>
 #include <QMutex>
+#include <QSystemTrayIcon>
 
 #include "redmineitemtree.h"
 #include "roles.h"
 #include "memberships.h"
 #include "enumerations.h"
+#include "logtimewindow.h"
+#include "showtimewindow.h"
 
 #include "common.h"
 
@@ -29,6 +32,27 @@ public:
 	RedmineItemTree projects;
 	RedmineItemTree issues;
 
+	enum EIcon {
+		GOOD = 0,
+		BAD
+	};
+	typedef EIcon EStatus;
+
+	EStatus status        ()
+	{
+		return this->_status;
+	}
+	EStatus status        ( EStatus newstatus )
+	{
+		return this->_status = newstatus;
+	}
+	EStatus statusWorsenTo ( EStatus newstatus )
+	{
+		return this->_status = qMax ( this->_status, newstatus );
+	}
+
+	void setIcon ( EIcon index );
+
 protected:
 
 	static const int SORT_DEPTH = 3;
@@ -52,6 +76,8 @@ protected:
 		SORT_UPDATED_ON_DESC,
 
 		SORT_STATUS_ISCLOSED_ASC,
+
+		SORT_TIMEENTRY_FROM_ASC,
 	};
 
 	typedef bool ( *sortfunct_t ) ( const QJsonObject &issue_a, const QJsonObject &issue_b );
@@ -73,15 +99,33 @@ protected:
 	Memberships memberships;
 	Enumerations enumerations;
 
+	QComboBox iconComboBox;
+	QSystemTrayIcon *trayIcon;
+	QMenu *trayIconMenu;
+
+	void createIconComboBox();
+	void createTrayIcon();
+
+	void showOnTop();
+
+	//virtual void createTrayActions();
+
 protected slots:
 	int updateEnumerations();
 	int updateMemberships();
 	int updateProjects();
 	int updateIssues();
 	int updateRoles();
+	void openLogTimeWindow();
+	void on_closeLogTimeWindow();
+	void openShowTimeWindow();
+	void on_closeShowTimeWindow();
 
 signals:
 
+protected slots:
+	void toggleShowHide();
+
 public slots:
 
 private:
@@ -98,6 +142,11 @@ private:
 
 	QMutex updateProjectsMutex;
 	QMutex updateIssuesMutex;
+
+
+	EStatus _status;
+	LogTimeWindow  *logTimeWindow;
+	ShowTimeWindow *showTimeWindow;
 };
 
 #endif // MAINWINDOWCOMMON_H

+ 52 - 0
mainwindow-full.cpp

@@ -92,6 +92,11 @@ MainWindowFull::MainWindowFull ( QWidget *parent ) :
 	connect ( &this->projectsDisplayRetryTimer, SIGNAL ( timeout() ), this, SLOT ( projects_display() ) );
 	this->ui->issuesFilter_field_assigned_to->addItem ( "", 0 );
 	this->ui->issuesFilter_field_status     ->addItem ( "", 0 );
+
+	this->createTrayActions();
+	connect ( this->trayIcon, SIGNAL ( activated     ( QSystemTrayIcon::ActivationReason ) ),
+		  this,		  SLOT   ( iconActivated ( QSystemTrayIcon::ActivationReason ) ) );
+
 	return;
 }
 
@@ -1088,3 +1093,50 @@ void MainWindowFull::on_issuesFilter_field_status_currentIndexChanged ( int inde
 		this->issues_display();
 	}
 }
+
+
+/**** tray-related stuff ****/
+
+void MainWindowFull::createTrayActions()
+{
+	this->showHideAction = new QAction ( tr ( "Показать/Спрятать" ), this );
+	connect ( this->showHideAction, SIGNAL ( triggered() ), this, SLOT ( toggleShowHide() ) );
+
+	this->openLogTimeWindowAction = new QAction ( tr ( "Зажурналировать время" ), this );
+	connect ( this->openLogTimeWindowAction, SIGNAL ( triggered() ), this, SLOT ( openLogTimeWindow() ) );
+
+	this->openShowTimeWindowAction = new QAction ( tr ( "Журнал времени" ), this );
+	connect ( this->openShowTimeWindowAction, SIGNAL ( triggered() ), this, SLOT ( openShowTimeWindow() ) );
+
+	this->quitAction = new QAction ( tr ( "Завершить" ), this );
+	connect ( this->quitAction, SIGNAL ( triggered() ), qApp, SLOT ( quit() ) );
+
+	this->trayIconMenu->addAction ( this->showHideAction );
+	this->trayIconMenu->addAction ( this->openLogTimeWindowAction );
+	this->trayIconMenu->addAction ( this->openShowTimeWindowAction );
+	this->trayIconMenu->addAction ( this->quitAction );
+
+	return;
+}
+
+void MainWindowFull::iconActivated ( QSystemTrayIcon::ActivationReason reason )
+{
+	qDebug("MainWindowFull::iconActivated(%i)", reason);
+
+	switch ( reason ) {
+		case QSystemTrayIcon::Trigger:
+		case QSystemTrayIcon::DoubleClick:
+			this->toggleShowHide();
+			break;
+
+		case QSystemTrayIcon::MiddleClick:
+			this->openLogTimeWindow();
+			break;
+
+		default:
+			break;
+	}
+}
+
+
+/**** /tray-related stuff ****/

+ 12 - 0
mainwindow-full.h

@@ -34,6 +34,9 @@ public:
 protected:
 	bool eventFilter ( QObject *obj, QEvent *event );
 
+protected slots:
+
+
 private slots:
 	void on_actionHelp_triggered();
 	void on_actionQuit_triggered();
@@ -152,6 +155,15 @@ private:
 	int issue_column_cur = 0;
 
 	QJsonObject issue;
+
+	void createTrayActions();
+
+	QAction *showHideAction;
+	QAction *quitAction;
+	QAction *openLogTimeWindowAction;
+	QAction *openShowTimeWindowAction;
+
+	void iconActivated ( QSystemTrayIcon::ActivationReason reason );
 };
 
 #endif // MAINWINDOWFULL_H

+ 12 - 64
mainwindow-rector.cpp

@@ -25,8 +25,8 @@
 #include <QStringList>
 #include <QDesktopServices>
 #include <QDateTime>
-#include <QMenu>
 #include <QList>
+#include <QMenu>
 #include <QScrollBar>
 
 void MainWindowRector::issuesSetup()
@@ -106,12 +106,12 @@ MainWindowRector::MainWindowRector ( QWidget *parent ) :
 	          this,    SLOT (  callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
 	this->issuesSetup();
 	this->updateTasks();
-	this->createIconComboBox();
+
 	this->createTrayActions();
-	this->createTrayIcon();
-	this->status ( GOOD );
-	this->setIcon ( GOOD );
-	this->trayIcon->show();
+	connect ( this->trayIcon, SIGNAL ( activated     ( QSystemTrayIcon::ActivationReason ) ),
+		  this,		  SLOT   ( iconActivated ( QSystemTrayIcon::ActivationReason ) ) );
+
+
 	this->timerUpdateTasks = new QTimer ( this );
 	connect ( this->timerUpdateTasks, SIGNAL ( timeout() ), this, SLOT ( updateTasks() ) );
 	this->timerUpdateTasks->start ( 10000 );
@@ -193,7 +193,7 @@ void MainWindowRector::issue_display_oneissue ( int pos )
 					// TODO: fix a memleak if redmine->get_user doesn't success
 					append_assignee_arg_p->pos     = pos;
 					redmine->get_user ( coassignee_id,
-					                    ( Redmine::callback_t ) &MainWindowRector::append_assignee,
+                                        ( Redmine::callback_t ) &MainWindowRector::append_assignee,
 					                    ( void * ) append_assignee_arg_p );
 				}
 			}
@@ -350,40 +350,15 @@ void MainWindowRector::on_actionHelp_triggered()
 	return;
 }
 
-void MainWindowRector::showOnTop()
-{
-#ifdef Q_OS_WIN32
-	// raise() doesn't work :(
-	Qt::WindowFlags flags_old   = this->windowFlags();
-	Qt::WindowFlags flags_ontop = flags_old | Qt::WindowStaysOnTopHint;
-	this->setWindowFlags ( flags_ontop );
-	this->show();
-	this->setWindowFlags ( flags_old );
-	this->show();
-#else
-	this->show();
-	this->raise();
-#endif
-	return;
-}
-
-void MainWindowRector::toggleShowHide()
-{
-	if ( this->isVisible() )
-		this->hide();
-	else {
-		this->showOnTop();
-	}
-
-	return;
-}
-
 void MainWindowRector::createTrayActions()
 {
-	showHideAction = new QAction ( tr ( "Показать/Спрятать" ), this );
+	this->showHideAction = new QAction ( tr ( "Показать/Спрятать" ), this );
 	connect ( showHideAction, SIGNAL ( triggered() ), this, SLOT ( toggleShowHide() ) );
-	quitAction = new QAction ( tr ( "Завершить" ), this );
+	this->quitAction = new QAction ( tr ( "Завершить" ), this );
 	connect ( quitAction, SIGNAL ( triggered() ), qApp, SLOT ( quit() ) );
+
+	this->trayIconMenu->addAction ( showHideAction );
+	this->trayIconMenu->addAction ( quitAction );
 }
 
 void MainWindowRector::iconActivated ( QSystemTrayIcon::ActivationReason reason )
@@ -402,33 +377,6 @@ void MainWindowRector::iconActivated ( QSystemTrayIcon::ActivationReason reason
 	}
 }
 
-void MainWindowRector::createTrayIcon()
-{
-	trayIconMenu = new QMenu ( this );
-	trayIconMenu->addAction ( showHideAction );
-	trayIconMenu->addAction ( quitAction );
-	trayIcon = new QSystemTrayIcon ( this );
-	trayIcon->setContextMenu ( trayIconMenu );
-	connect ( trayIcon, SIGNAL ( activated ( QSystemTrayIcon::ActivationReason ) ),
-	          this, SLOT ( iconActivated ( QSystemTrayIcon::ActivationReason ) ) );
-}
-
-void MainWindowRector::setIcon ( EIcon index )
-{
-	//qDebug("icon: %i", index);
-	QIcon icon = this->iconComboBox.itemIcon ( index );
-	this->trayIcon->setIcon ( icon );
-	this->setWindowIcon ( icon );
-	this->trayIcon->setToolTip ( this->iconComboBox.itemText ( index ) );
-}
-
-void MainWindowRector::createIconComboBox()
-{
-	this->iconComboBox.addItem ( QIcon ( ":/images/good.png" ), tr ( "Просроченных задач нет" ) );
-	this->iconComboBox.addItem ( QIcon ( ":/images/bad.png" ),  tr ( "Есть просроченные задачи" ) );
-	return;
-}
-
 void MainWindowRector::resizeEvent ( QResizeEvent *event )
 {
 	ui->issues->resize ( this->width(), this->height() - 50 );

+ 4 - 34
mainwindow-rector.h

@@ -2,7 +2,6 @@
 #define MAINWINDOW_RECTOR_H
 
 #include <QMainWindow>
-#include <QSystemTrayIcon>
 #include <QComboBox>
 #include <QGroupBox>
 #include <QCheckBox>
@@ -26,36 +25,16 @@ class MainWindowRector : public MainWindowCommon
 	CALLBACK_DISPATCHER ( Redmine, MainWindowRector, NULL )
 
 public:
-	enum EIcon {
-		GOOD = 0,
-		BAD
-	};
-	typedef EIcon EStatus;
-
-	EStatus status        ()
-	{
-		return this->_status;
-	}
-	EStatus status        ( EStatus newstatus )
-	{
-		return this->_status = newstatus;
-	}
-	EStatus statusWorsenTo ( EStatus newstatus )
-	{
-		return this->_status = qMax ( this->_status, newstatus );
-	}
-
-	void setIcon ( EIcon index );
+
 	explicit MainWindowRector ( QWidget *parent = 0 );
 	~MainWindowRector();
 
+protected slots:
 
 private slots:
 	void on_actionExit_triggered();
 	void on_actionHelp_triggered();
 	void issues_doubleClick ( int row, int column );
-	void toggleShowHide();
-	void iconActivated ( QSystemTrayIcon::ActivationReason reason );
 	int updateTasks();
 
 	void on_issues_itemSelectionChanged();
@@ -73,17 +52,10 @@ private:
 
 	Ui::MainWindowRector *ui;
 
-	QComboBox iconComboBox;
-
 	QAction *showHideAction;
 	QAction *quitAction;
 
-	QSystemTrayIcon *trayIcon;
-	QMenu *trayIconMenu;
-
-	void createIconComboBox();
 	void createTrayActions();
-	void createTrayIcon();
 
 	void resizeEvent ( QResizeEvent *event );
 	void issuesSetup();
@@ -91,15 +63,13 @@ private:
 	QList <QJsonObject> issues_list;
 	QTimer *timerUpdateTasks;
 
-	void showOnTop();
-
 	void get_issues_callback ( QNetworkReply *reply, QJsonDocument *json, void *arg );
 
-	EStatus _status;
-
 	QMap <int, enum ESortColumn> sortColumnAscByIdx;
 	QMap <int, enum ESortColumn> sortColumnDescByIdx;
 
+	void iconActivated ( QSystemTrayIcon::ActivationReason reason );
+
 };
 
 #endif // MAINWINDOW_RECTOR_H

+ 10 - 0
mainwindowandroid.cpp

@@ -2,6 +2,7 @@
 #include "ui_mainwindowandroid.h"
 
 #include "logtimewindow.h"
+#include "showtimewindow.h"
 
 MainWindowAndroid::MainWindowAndroid ( QWidget *parent ) :
 	QFrame ( parent ),
@@ -9,6 +10,9 @@ MainWindowAndroid::MainWindowAndroid ( QWidget *parent ) :
 {
 	this->ui->setupUi ( this );
 	this->setWindowTitle ( "Система «Задачи» НИЯУ МИФИ" );
+#ifdef __WINRT__
+	delete this->ui->quitButton;
+#endif
 }
 
 MainWindowAndroid::~MainWindowAndroid()
@@ -26,3 +30,9 @@ void MainWindowAndroid::on_logTimeWindowButton_clicked()
 	LogTimeWindow *w = new LogTimeWindow();
 	w->show();
 }
+
+void MainWindowAndroid::on_showTimeWindowButton_clicked()
+{
+	ShowTimeWindow *w = new ShowTimeWindow();
+	w->show();
+}

+ 3 - 1
mainwindowandroid.h

@@ -21,7 +21,9 @@ private slots:
 
 	void on_logTimeWindowButton_clicked();
 
-private:
+	void on_showTimeWindowButton_clicked();
+
+	private:
 	Ui::MainWindowAndroid *ui;
 };
 

+ 2 - 8
mainwindowandroid.ui

@@ -36,15 +36,9 @@
       </widget>
      </item>
      <item>
-      <widget class="QPushButton" name="pushButton">
-       <property name="sizePolicy">
-        <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
-         <horstretch>0</horstretch>
-         <verstretch>0</verstretch>
-        </sizepolicy>
-       </property>
+      <widget class="QPushButton" name="showTimeWindowButton">
        <property name="text">
-        <string>PushButton</string>
+        <string>Посмотреть журнал</string>
        </property>
       </widget>
      </item>

+ 30 - 6
mephi-tasks.pro

@@ -11,7 +11,9 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 TARGET = mephi-tasks
 TEMPLATE = app
 #QMAKE_CXX = ccache g++
-QMAKE_CXXFLAGS += -std=c++11 -O0 #-march=native
+gcc:QMAKE_CXXFLAGS += -std=c++11
+gcc:QMAKE_CXXFLAGS_RELEASE += -O2 -march=native
+gcc:QMAKE_CXXFLAGS_DEBUG += -O0 -ggdb3
 
 SOURCES += main.cpp\
     helpwindow.cpp \
@@ -31,7 +33,8 @@ SOURCES += main.cpp\
     loginwindow.cpp \
     mainwindowandroid.cpp \
     logtimewindow.cpp \
-    redmineclass_time_entry.cpp
+    redmineclass_time_entry.cpp \
+    showtimewindow.cpp
 
 HEADERS  += \
     helpwindow.h \
@@ -52,7 +55,8 @@ HEADERS  += \
     loginwindow.h \
     mainwindowandroid.h \
     logtimewindow.h \
-    redmineclass_time_entry.h
+    redmineclass_time_entry.h \
+    showtimewindow.h
 
 FORMS    += \
     helpwindow.ui \
@@ -63,12 +67,32 @@ FORMS    += \
     signingwindow.ui \
     loginwindow.ui \
     mainwindowandroid.ui \
-    logtimewindow.ui
+    logtimewindow.ui \
+    showtimewindow.ui
 
-win32:CONFIG(release, debug|release): LIBS += -L$$PWD/build-qtredmine-Desktop_Qt_5_5_1_MinGW_32bit-Release/release/ -lqtredmine
-else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/build-qtredmine-Desktop_Qt_5_5_1_MinGW_32bit-Debug/debug/ -lqtredmine
+win32:CONFIG(release, debug|release): LIBS += -L$$PWD/build-qtredmine/release/ -lqtredmine
+else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/build-qtredmine/debug/ -lqtredmine
 else:unix: LIBS += -L$$PWD/build-qtredmine-Desktop/ -lqtredmine
 
+win32:contains(QT_ARCH, i386) {
+    LIBS += -LC:/OpenSSL-Win32/lib -llibeay32
+    INCLUDEPATH += C:/OpenSSL-Win32/include
+} else {
+    LIBS += -LC:/OpenSSL-Win64/lib -llibeay32
+    INCLUDEPATH += C:/OpenSSL-Win64/include
+}
+
+winrt {
+    winphone:equals(WINSDK_VER, 8.0) {
+        WINRT_MANIFEST.capabilities += ID_CAP_NETWORKING
+    } else {
+        WINRT_MANIFEST.capabilities += internetClient
+    }
+    CONFIG += windeployqt
+    QMAKE_CXXFLAGS += -D__WINRT__
+}
+
+
 INCLUDEPATH += $$PWD/qtredmine
 DEPENDPATH += $$PWD/qtredmine
 

+ 1 - 1
qtredmine

@@ -1 +1 @@
-Subproject commit fd6b96a9407f79c9a49f84c79488343a19693af7
+Subproject commit d564fb78c78f0354d8dc297a22843ad06a6354c7

+ 59 - 4
redmine.cpp

@@ -22,10 +22,23 @@
 
 #include <QDir>
 #include <QFile>
+#include <QStandardPaths>
+#include <QMessageBox>
 
 Redmine::Redmine()
 {
+	if ( ! QSslSocket::supportsSsl() ) {
+		qDebug ( "! QSslSocket::supportsSsl()" );
+		QMessageBox messageBox;
+		messageBox.critical(0, "Error", "Отсутствует поддержка SSL. Проверьте наличие библиотек libeay32.dll и ssleay32.dll, либо установите пакет OpenSSL.");
+	}
 	this->setBaseUrl ( SERVER_URL );
+
+#ifdef __MOBILE__
+	this->cacheBasePath = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
+#else
+	this->cacheBasePath = "cache/";
+#endif
 }
 
 Redmine::~Redmine()
@@ -76,8 +89,8 @@ int Redmine::init()
 		this->setAuth ( this->_apiKey );
 	}
 
-	connect ( this, SIGNAL ( requestFinished ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
-	          this, SLOT ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
+	connect ( this, SIGNAL ( requestFinished     ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
+		  this, SLOT   ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
 	this->initBarrier_jobsDone = 0;
 	QNetworkReply *updateIssueStatusesReply = this->updateIssueStatuses();
 	// Wait until issue statuses will be received:
@@ -99,7 +112,7 @@ int Redmine::init()
 
 void Redmine::cacheLoad()
 {
-	QDir dir = QDir ( "cache/" + this->apiKey() );
+	QDir dir = QDir ( this->cacheBasePath + this->apiKey() );
 	QFileInfoList fileInfoList = dir.entryInfoList ( QDir::Files );
 
 	for ( int i = 0; i < fileInfoList.size(); ++i ) {
@@ -205,7 +218,8 @@ QNetworkReply *Redmine::request ( RedmineClient::EMode    mode,
 
 	if ( !this->cache[signature].isEmpty() ) {
 		qDebug ( "Found cache for \"%s\"", signature.toStdString().c_str() );
-		this->callback_call ( obj_ptr, callback, NULL, &this->cache[signature], callback_arg );
+		if (obj_ptr != NULL && callback != NULL)
+			this->callback_call ( obj_ptr, callback, NULL, &this->cache[signature], callback_arg );
 	}
 
 	/*
@@ -499,9 +513,19 @@ QNetworkReply *Redmine::get_time_entries ( void *obj_ptr, callback_t callback,
 QNetworkReply *Redmine::get_time_entries ( callback_t callback,
         void *arg, bool free_arg, QString filterOptions )
 {
+	qDebug("deprecated variant of Redmine::get_time_entries had been called");
+
 	return this->get_time_entries ( NULL, callback, arg, free_arg, filterOptions );
 }
 
+QNetworkReply *Redmine::get_time_entries ( int userId, void *obj_ptr, callback_t callback,
+	void *arg, bool free_arg, QString filterOptions )
+{
+	QString userId_str = ( userId == 0 ? "" : QString ( "user_id=" + QString::number(userId) ) );
+
+	return this->get_time_entries ( obj_ptr, callback, arg, free_arg, userId_str+"&"+filterOptions );
+}
+
 /********* /get_time_entries *********/
 
 /********* get_projects *********/
@@ -580,14 +604,23 @@ QNetworkReply *Redmine::get_user ( int user_id,
 
 QDateTime Redmine::parseDateTime ( QString date_str )
 {
+	qDebug ( "Used deprecated function Redmine::parseDateTime(). Use \"QDateTime::fromString (arg, Qt::ISODate)\" instead." );
+
 	// TODO: FIXME: make this function working on any timezone.
 	QDateTime date;
 	date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+03:00'" );
 
 	if ( !date.isValid() )
+		date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+0300'" );
+
+	if ( !date.isValid() )
 		// TODO: FIXME: add a hour
 		date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+04:00'" );
 
+	if ( !date.isValid() )
+		// TODO: FIXME: add a hour
+		date = QDateTime::fromString ( date_str, "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'zzz'+0400'" );
+
 	return date;
 }
 
@@ -606,3 +639,25 @@ QUrl Redmine::getUrl ( QString objectType, int objectId )
 }
 
 /********* /getUrl *********/
+
+/********* get_stuff_to_do *********/
+
+QNetworkReply *Redmine::get_stuff_to_do ( void *obj_ptr, callback_t callback,
+				 int user_id,
+				 void *arg, bool free_arg,
+				 QString filterOptions )
+{
+	QString user_id_str = ( user_id == 0 ? "" : "user_id=" + QString::number ( user_id ) );
+
+	return this->request(
+				GET,
+				"stuff_to_do",
+				obj_ptr,
+				callback,
+				arg,
+				free_arg,
+				user_id_str+"&"+filterOptions );
+}
+
+/********* /get_stuff_to_do *********/
+

+ 23 - 10
redmine.h

@@ -50,6 +50,8 @@ private:
 
 	void callback_cache ( QNetworkReply *reply, QJsonDocument *obj, void *_real_callback_info );
 
+	QString cacheBasePath;
+
 public:
 
 	QString apiKey ( QString apiKey );
@@ -100,30 +102,31 @@ public:
 
 	/* Request all roles
 	 */
-	QNetworkReply *get_roles ( callback_t callback, void *arg, bool free_arg = false );
+	QNetworkReply *get_roles ( callback_t callback, void *arg = NULL, bool free_arg = false );
 
 	/* Request all issues
 	 */
-	QNetworkReply *get_issues ( callback_t callback, void *arg, bool free_arg = false, QString customFilters = "" );
-	QNetworkReply *get_issues ( void *obj_ptr, callback_t callback, void *arg, bool free_arg = false, QString customFilters = "" );
+	QNetworkReply *get_issues ( callback_t callback, void *arg = NULL, bool free_arg = false, QString customFilters = "" );
+	QNetworkReply *get_issues ( void *obj_ptr, callback_t callback, void *arg = NULL, bool free_arg = false, QString customFilters = "" );
 
 	/* Request all projects
 	 */
-	QNetworkReply *get_projects ( callback_t callback, void *arg, bool free_arg = false, QString filterOptions = "" );
-	QNetworkReply *get_projects ( void *obj_ptr, callback_t callback, void *arg, bool free_arg = false, QString filterOptions = "" );
+	QNetworkReply *get_projects ( callback_t callback, void *arg = NULL, bool free_arg = false, QString filterOptions = "" );
+	QNetworkReply *get_projects ( void *obj_ptr, callback_t callback, void *arg = NULL, bool free_arg = false, QString filterOptions = "" );
 
 	/* Request all memberships
 	 */
-	QNetworkReply *get_memberships ( callback_t callback, void *arg, bool free_arg = false );
+	QNetworkReply *get_memberships ( callback_t callback, void *arg = NULL, bool free_arg = false );
 
 	/* Request all field values enumerations
 	 */
-	QNetworkReply *get_enumerations ( callback_t callback, void *arg, bool free_arg = false );
+	QNetworkReply *get_enumerations ( callback_t callback, void *arg = NULL, bool free_arg = false );
 
 	/* Request all issues
 	 */
-	QNetworkReply *get_time_entries ( void *obj_ptr, callback_t callback, void *arg, bool free_arg = false, QString filterOptions = "" );
-	QNetworkReply *get_time_entries ( callback_t callback, void *arg, bool free_arg = false, QString filterOptions = "" );
+	QNetworkReply *get_time_entries ( int userId, void *obj_ptr, callback_t callback, void *arg = NULL, bool free_arg = false, QString filterOptions = "" );
+	QNetworkReply *get_time_entries ( void *obj_ptr, callback_t callback, void *arg = NULL, bool free_arg = false, QString filterOptions = "" );
+	QNetworkReply *get_time_entries ( callback_t callback, void *arg = NULL, bool free_arg = false, QString filterOptions = "" );
 
 	/* Get issue status info
 	 */
@@ -136,7 +139,17 @@ public:
 	 */
 	QNetworkReply *get_user ( int user_id,
 	                          callback_t callback,
-	                          void *arg );
+				  void *arg = NULL );
+
+	/* Request stuffToDo index info of user with ID "user_id".
+	 * Set "user_id" to zero to get info for current user.
+	 *
+	 * Related to plugin: https://github.com/mephi-ut/stuff_to_do_plugin
+	 */
+	QNetworkReply *get_stuff_to_do ( void *obj_ptr, callback_t callback,
+					 int user_id = 0,
+					 void *arg = NULL, bool free_arg = false,
+					 QString filterOptions = "" );
 
 	/* Parses JSON values like "2015-03-13T21:34:28.000+03:00" to QDateTime
 	 */

+ 3 - 1
redmineclass_time_entry.cpp

@@ -7,6 +7,9 @@ void RedmineClass_TimeEntry::init()
 	this->redmine   = NULL;
 	this->id        = 0;
 	this->saveReply = NULL;
+
+	connect ( &this->saveTimer, SIGNAL ( timeout() ), this, SLOT ( saveTimeout() ) );
+
 	return;
 }
 
@@ -143,7 +146,6 @@ int RedmineClass_TimeEntry::save()
 	this->saveReply = this->redmine->request ( mode, "time_entries", this, (Redmine::callback_t)&RedmineClass_TimeEntry::saveCallback, NULL, false, "", timeEntries );
 
 	this->saveTimer.setSingleShot ( true );
-	connect ( &this->saveTimer, SIGNAL ( timeout() ), this, SLOT ( saveTimeout() ) );
 	this->saveTimer.start ( REDMINE_BASETIMEOUT );
 
 	return 0;

+ 228 - 0
showtimewindow.cpp

@@ -0,0 +1,228 @@
+#include "showtimewindow.h"
+#include "ui_showtimewindow.h"
+
+ShowTimeWindow::ShowTimeWindow(QWidget *parent) :
+	QWidget(parent),
+	ui(new Ui::ShowTimeWindow)
+{
+	ui->setupUi(this);
+
+	connect ( redmine, SIGNAL ( callback_call       ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ),
+		  this,    SLOT   ( callback_dispatcher ( void*, callback_t, QNetworkReply*, QJsonDocument*, void* ) ) );
+
+	this->updateUsers();
+	this->updateTimeEntries(0);
+
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 0, QHeaderView::Interactive );
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 1, QHeaderView::Interactive );
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 2, QHeaderView::Interactive );
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 3, QHeaderView::Interactive );
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 4, QHeaderView::Interactive );
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 5, QHeaderView::Interactive );
+
+#ifdef __MOBILE__
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 6, QHeaderView::Interactive );
+
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 0, 80 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 1, 80 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 2, 72 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 3, 104 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 4, 82 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 5, 500 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 6, 500 );
+#else
+	this->ui->timeEntries->horizontalHeader()->setSectionResizeMode ( 6, QHeaderView::Stretch );
+
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 0, 50 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 1, 50 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 2, 45 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 3, 70 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 4, 60 );
+	this->ui->timeEntries->horizontalHeader()->resizeSection ( 5, 200 );
+
+	this->ui->date->setMaximumHeight(120);
+#endif
+
+	this->ui->timeEntries->horizontalHeader()->setSortIndicatorShown ( true );
+
+	this->ui->date->setSelectedDate(QDate::currentDate());
+}
+
+ShowTimeWindow::~ShowTimeWindow()
+{
+	delete ui;
+}
+
+/********* updateUsers *********/
+
+
+void ShowTimeWindow::updateUsers_callback ( QNetworkReply *reply, QJsonDocument *json, void *arg )
+{
+	( void ) reply;
+	( void ) arg;
+
+	QJsonArray  users = json->object() ["users"].toArray();
+
+	qDebug ( "ShowTimeWindow::updateUsers_callback: users.count() == %i", users.count() );
+
+	this->ui->user->clear();
+	this->ui->user->addItem ( "Я", 0 );
+
+	foreach (const QJsonValue &userV, users) {
+		QJsonObject user = userV.toObject();
+
+		int userId = user["id"].toInt();
+		QString userDisplayName = user["name"].toString();
+		this->ui->user->addItem ( userDisplayName, userId );
+
+		// To make a cache:
+		redmine->get_time_entries( userId, NULL, NULL, NULL, false, "limit=1000" );
+	}
+
+	return;
+}
+
+void ShowTimeWindow::updateUsers()
+{
+	redmine->get_stuff_to_do ( this, ( Redmine::callback_t ) &ShowTimeWindow::updateUsers_callback );
+	return;
+}
+
+/********* /updateUsers *********/
+
+/********* updateTimeEntries *********/
+
+void ShowTimeWindow::timeEntries_display()
+{
+	QList<QJsonObject> list;
+	//this->ui->timeEntries->clear();
+	//this->ui->timeEntries->setRowCount ( this->timeEntries.count() );
+
+	QDate dateSelected = this->ui->date->selectedDate();
+
+	foreach (const QJsonValue &timeEntryV, this->timeEntries) {
+		QJsonObject timeEntry = timeEntryV.toObject();
+		QDateTime from = QDateTime::fromString(timeEntry ["from"].toString(), Qt::ISODate);
+
+		QDate date = from.date();
+
+		if (date != dateSelected)
+			continue;
+
+		list.append(timeEntry);
+	}
+
+	qSort ( list.begin(), list.end(), timeEntryCmpFunct_from_lt );
+
+	this->ui->timeEntries->setRowCount ( list.size() );
+
+	for (int row = 0; row < list.size(); ++row) {
+	    QJsonObject timeEntry = list.at(row);
+
+	    QDateTime from = QDateTime::fromString(timeEntry ["from"].toString(), Qt::ISODate);
+	    QDateTime to   = QDateTime::fromString(timeEntry ["to"  ].toString(), Qt::ISODate);
+	    int sec_diff  = from.time().secsTo(to.time());
+
+	    //qDebug ( "\"from\": \"%s\", sec_diff == %i; from.minute() == %i; to.minute() == %i", timeEntry ["from"].toString().toStdString().c_str(), sec_diff, from.time().minute(), to.time().minute() );
+
+	    this->ui->timeEntries->setItem(row, 0, new QTableWidgetItem ( from.toString("hh:mm") ) );
+	    this->ui->timeEntries->setItem(row, 1, new QTableWidgetItem ( to  .toString("hh:mm") ) );
+	    this->ui->timeEntries->setItem(row, 2, new QTableWidgetItem ( QString::number( sec_diff/60 ) ) );
+	    this->ui->timeEntries->setItem(row, 3, new QTableWidgetItem ( timeEntry ["activity"].toObject() ["name"].toString() ) );
+
+	    if (timeEntry ["issue"].toObject().empty()) {
+		    QString projectIdentifier = timeEntry ["project"].toObject() ["identifier"].toString();
+		    QString projectName;
+		    QString userLogin = timeEntry ["user"].toObject() ["login"].toString();
+
+		    if ( projectIdentifier.toLower() == userLogin.toLower() ) {
+			    projectName = "Личный проект";
+		    } else {
+			    projectName = timeEntry ["project"].toObject() ["name"].toString();
+		    }
+
+		    //qDebug(("projectIdentifier == \""+projectIdentifier+"\"; userLogin == \""+userLogin+"\"; projectName == \""+projectName+"\"").toStdString().c_str());
+
+		    this->ui->timeEntries->setItem(row, 4, new QTableWidgetItem ( projectIdentifier ) );
+		    this->ui->timeEntries->setItem(row, 5, new QTableWidgetItem ( projectName       ) );
+	    } else {
+		    this->ui->timeEntries->setItem(row, 4, new QTableWidgetItem ( QString::number(timeEntry ["issue"].toObject()    ["id"].toInt()) ) );
+		    this->ui->timeEntries->setItem(row, 5, new QTableWidgetItem ( timeEntry ["issue"].toObject()    ["subject"].toString() ) );
+	    }
+
+	    this->ui->timeEntries->setItem(row, 6, new QTableWidgetItem ( timeEntry ["comments"].toString() ) );
+	}
+}
+
+void ShowTimeWindow::updateTimeEntries_callback ( QNetworkReply *reply, QJsonDocument *json, void *arg )
+{
+	( void ) reply;
+	( void ) arg;
+
+	this->timeEntries = json->object() ["time_entries"].toArray();
+
+	qDebug ( "ShowTimeWindow::updateTimeEntries_callback: timeEntries.count() == %i", timeEntries.count() );
+
+	this->timeEntries_display();
+	return;
+}
+
+void ShowTimeWindow::updateTimeEntries(int userId)
+{
+	redmine->get_time_entries( userId, this, ( Redmine::callback_t ) &ShowTimeWindow::updateTimeEntries_callback, NULL, false, "limit=1000" );
+	return;
+}
+
+/********* /updateTimeEntries *********/
+
+void ShowTimeWindow::on_closeButton_clicked()
+{
+	delete this;
+}
+
+void ShowTimeWindow::on_date_selectionChanged()
+{
+	this->timeEntries_display();
+}
+
+void ShowTimeWindow::on_timeEntries_itemSelectionChanged()
+{
+	QTableWidget                     *timeEntries        = this->ui->timeEntries;
+	int                               columns_count      = timeEntries->columnCount();
+	int                               rows_count         = timeEntries->rowCount();
+	QList<QTableWidgetSelectionRange> selected_list      = timeEntries->selectedRanges();
+	foreach ( QTableWidgetSelectionRange range, selected_list ) {
+		if ( range.leftColumn() != 0 || range.rightColumn() != columns_count - 1 )
+			timeEntries->setRangeSelected (
+			    QTableWidgetSelectionRange (
+				range.topRow(),    0,
+				range.bottomRow(), columns_count - 1
+			    ),
+			    true
+			);
+		else
+
+			/* Workaround: Drop selection if everything is selected
+			 * it's required to do not select everything on sort switching
+			 */
+			if ( range.leftColumn() == 0 && range.rightColumn() == columns_count - 1 &&
+			     range.topRow()     == 0 && range.bottomRow()   == rows_count - 1 ) {
+				timeEntries->setRangeSelected (
+				    QTableWidgetSelectionRange ( 0, 0, rows_count - 1, columns_count - 1 ),
+				    false
+				);
+				break;
+			}
+	}
+}
+
+void ShowTimeWindow::on_user_currentIndexChanged(int index)
+{
+	(void) index;
+
+	int userId = this->ui->user->currentData().toInt();
+
+	this->updateTimeEntries(userId);
+
+	return;
+}

+ 46 - 0
showtimewindow.h

@@ -0,0 +1,46 @@
+#ifndef SHOWTIMEWINDOW_H
+#define SHOWTIMEWINDOW_H
+
+#include <QWidget>
+
+#include "common.h"
+#include "redmineitemtree.h"
+
+namespace Ui {
+	class ShowTimeWindow;
+}
+
+class ShowTimeWindow : public QWidget
+{
+	Q_OBJECT
+	CALLBACK_DISPATCHER ( Redmine, ShowTimeWindow, this )
+
+public:
+	explicit ShowTimeWindow(QWidget *parent = 0);
+	~ShowTimeWindow();
+
+	private slots:
+	void on_closeButton_clicked();
+
+	void on_date_selectionChanged();
+
+	void on_timeEntries_itemSelectionChanged();
+
+	void on_user_currentIndexChanged(int index);
+
+	private:
+	Ui::ShowTimeWindow *ui;
+
+	void updateUsers();
+	void updateTimeEntries ( int userId );
+	void updateUsers_callback       ( QNetworkReply *reply, QJsonDocument *json, void *arg );
+	void updateTimeEntries_callback ( QNetworkReply *reply, QJsonDocument *json, void *arg );
+
+	//RedmineItemTree users;
+	//RedmineItemTree timeEntries;
+	QJsonArray timeEntries;
+private:
+	void timeEntries_display();
+};
+
+#endif // SHOWTIMEWINDOW_H

+ 93 - 0
showtimewindow.ui

@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ShowTimeWindow</class>
+ <widget class="QWidget" name="ShowTimeWindow">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>699</width>
+    <height>452</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Form</string>
+  </property>
+  <layout class="QHBoxLayout" name="horizontalLayout">
+   <item>
+    <layout class="QVBoxLayout" name="layout">
+     <item>
+      <widget class="QComboBox" name="user">
+       <item>
+        <property name="text">
+         <string>Я</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item>
+      <widget class="QCalendarWidget" name="date">
+       <property name="maximumSize">
+        <size>
+         <width>16777215</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <property name="locale">
+        <locale language="Russian" country="Russia"/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTableWidget" name="timeEntries">
+       <column>
+        <property name="text">
+         <string>с</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>по</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>мин.</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>тип</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>#</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>задача/проект</string>
+        </property>
+       </column>
+       <column>
+        <property name="text">
+         <string>комментарий</string>
+        </property>
+       </column>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="closeButton">
+       <property name="text">
+        <string>Закрыть</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>