[Workman-svn] SF.net SVN: workman:[39] trunk/src
An unobtrusive time-tracking program for self-employed people
Status: Pre-Alpha
Brought to you by:
jmsilva
|
From: <jm...@us...> - 2011-10-21 00:53:33
|
Revision: 39
http://workman.svn.sourceforge.net/workman/?rev=39&view=rev
Author: jmsilva
Date: 2011-10-21 00:53:26 +0000 (Fri, 21 Oct 2011)
Log Message:
-----------
- Can now view work sessions.
Modified Paths:
--------------
trunk/src/gui/dialogs.py
trunk/src/gui/main_window.py
trunk/src/gui/view_sessions.ui
trunk/src/workman.py
trunk/src/workman_types.py
Modified: trunk/src/gui/dialogs.py
===================================================================
--- trunk/src/gui/dialogs.py 2011-10-21 00:53:11 UTC (rev 38)
+++ trunk/src/gui/dialogs.py 2011-10-21 00:53:26 UTC (rev 39)
@@ -16,10 +16,10 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from PyQt4.QtGui import QDialog, QDialogButtonBox
+from PyQt4.QtGui import QDialog, QDialogButtonBox, QWidget
from gui import compiled_ui
from workman import DataStore
-from gui.compiled_ui import Ui_startSessionDialog, Ui_endSessionDialog
+from gui.compiled_ui import Ui_startSessionDialog, Ui_endSessionDialog, Ui_viewSessionsDialog
'''
Created on 2009/12/18
@@ -277,4 +277,125 @@
def accept(self):
self.session = self.dataStore.activeSession
self.dataStore.endSession(self.workSummary.toPlainText())
- QDialog.accept(self)
\ No newline at end of file
+ QDialog.accept(self)
+
+
+
+class ViewSessionsDialog(QDialog, Ui_viewSessionsDialog):
+
+ def __init__(self, dataStore):
+ QDialog.__init__(self)
+ self.setupUi(self)
+ self.dataStore = dataStore
+ self.ignoreComboBoxChanges = False
+ self.employerIndex = 0
+ self.projectIndex = 0
+ self.__populateEmployerBox()
+
+ def dateBoxesChanged(self, index):
+ self.__reloadItemModel()
+
+ def employerBoxChanged(self, index):
+ if self.ignoreComboBoxChanges:
+ return
+ if index == self.employerChoice.count() - 1:
+ self.ignoreComboBoxChanges = True
+ self.employerChoice.setCurrentIndex(self.employerIndex)
+ employerDialog = NewEmployerDialog(self.dataStore)
+ if employerDialog.exec_() == QDialog.Accepted:
+ self.__populateEmployerBox(employerDialog.employerNameBox.text())
+ self.ignoreComboBoxChanges = False
+ else:
+ self.employerIndex = index
+ self.__populateProjectBox()
+ self.__reloadItemModel()
+
+
+ def projectBoxChanged(self, index):
+ if self.ignoreComboBoxChanges:
+ return
+ if index == self.projectChoice.count() - 1:
+ self.ignoreComboBoxChanges = True
+ self.projectChoice.setCurrentIndex(self.projectIndex)
+ projectDialog = NewProjectDialog(self.dataStore)
+ result = projectDialog.exec_()
+
+ '''TODO: Reflect the other dialog's
+ selections in the combo boxes'''
+ if projectDialog.createdEmployer:
+ self.__populateEmployerBox()
+ else:
+ self.__populateProjectBox()
+ self.ignoreComboBoxChanges = False
+ else:
+ self.projectIndex = index
+ self.__reloadItemModel()
+
+
+
+ def __populateEmployerBox(
+ self, defaultEmployer = None, defaultProject = None):
+ self.ignoreComboBoxChanges = True
+ self.employerChoice.clear()
+ self.employerChoice.addItem(self.tr("None (personal project)"))
+ items = 1
+ for i in self.dataStore.employers.values():
+ if i.name == DataStore.NO_EMPLOYER:
+ continue
+ self.employerChoice.addItem(i.name)
+ if i.name == defaultEmployer:
+ self.employerIndex = items
+ self.employerChoice.setCurrentIndex(items)
+ items += 1
+
+ if(items > 1):
+ self.employerChoice.insertSeparator(1)
+ self.employerChoice.insertSeparator(items + 1)
+ self.employerChoice.addItem(self.tr("New..."))
+ self.projectIndex = 0
+ self.__populateProjectBox(defaultProject)
+ self.ignoreComboBoxChanges = False
+
+ def __populateProjectBox(self, defaultItem = None):
+ self.ignoreComboBoxChanges = True
+ self.projectChoice.clear()
+ self.projectChoice.addItem(self.tr("Unsorted"))
+ items = 1
+ if self.employerIndex == 0:
+ employerName = DataStore.NO_EMPLOYER
+ else:
+ employerName = self.employerChoice.currentText()
+
+ for i in self.dataStore.employers[employerName].projects.values():
+ if i.name == DataStore.NO_PROJECT:
+ continue
+ self.projectChoice.addItem(i.name)
+ if i.name == defaultItem:
+ self.projectIndex = items
+ self.projectChoice.setCurrentIndex(items)
+ items += 1
+
+ if(items > 1):
+ self.projectChoice.insertSeparator(1)
+ self.projectChoice.insertSeparator(items + 1)
+ self.projectChoice.addItem(self.tr("New..."))
+ self.__reloadItemModel()
+ self.ignoreComboBoxChanges = False
+
+ def __reloadItemModel(self):
+ if self.employerChoice.currentIndex() == 0:
+ employer = DataStore.NO_EMPLOYER
+ else:
+ employer = self.employerChoice.currentText()
+
+ if self.projectChoice.currentIndex() == 0:
+ project = DataStore.NO_PROJECT
+ else:
+ project = self.projectChoice.currentText()
+
+ startDate = self.startDateWidget.dateTime()
+ endDate = self.endDateWidget.dateTime()
+ model = self.dataStore.getSessionItemModel(
+ employer, project, startDate, endDate)
+
+ self.sessionList.setModel(model)
\ No newline at end of file
Modified: trunk/src/gui/main_window.py
===================================================================
--- trunk/src/gui/main_window.py 2011-10-21 00:53:11 UTC (rev 38)
+++ trunk/src/gui/main_window.py 2011-10-21 00:53:26 UTC (rev 39)
@@ -59,10 +59,10 @@
self.sessionMenu.addAction(self.tr("&Show/Hide window"),
self.showOrHide)
self.sessionMenu.addSeparator()
- self.sessionMenu.addAction(self.tr("&Stop working..."),
- self.endSession)
self.sessionMenu.addAction(self.tr("Take a &break"),
self.startBreak)
+ self.sessionMenu.addAction(self.tr("&Stop working..."),
+ self.endSession)
self.sessionMenu.addSeparator()
self.sessionMenu.addAction(self.tr("&Quit"),
self.quit)
@@ -105,7 +105,7 @@
self.__setStartButton(True)
self.trayIcon.setContextMenu(self.regularMenu)
- #TODO: Show some info about the session in the text
+ #TODO: Show some info about the session in the message text
self.trayIcon.showMessage(self.tr("Work session ended"),
self.tr("The work session has ended."))
self.show()
@@ -128,6 +128,7 @@
self.dataStore.endBreak()
self.trayIcon.showMessage(self.tr("Ending your break"),
self.tr("Break time's over. Resuming time tracking..."))
+ self.trayIcon.setContextMenu(self.sessionMenu)
def showOrHide(self):
self.setVisible(not self.isVisible())
@@ -153,8 +154,10 @@
def viewSessions(self):
'''Opens the view sessions dialog'''
- #TODO
- pass
+ dialog = dialogs.ViewSessionsDialog(self.dataStore)
+ dialog.setModal(False)
+ dialog.show()
+ dialog.exec_()
def __setStartButton(self, start):
if start:
Modified: trunk/src/gui/view_sessions.ui
===================================================================
--- trunk/src/gui/view_sessions.ui 2011-10-21 00:53:11 UTC (rev 38)
+++ trunk/src/gui/view_sessions.ui 2011-10-21 00:53:26 UTC (rev 39)
@@ -1,17 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
- <class>sessionsWindow</class>
- <widget class="QDialog" name="sessionsWindow">
+ <class>viewSessionsDialog</class>
+ <widget class="QDialog" name="viewSessionsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
- <width>389</width>
+ <width>647</width>
<height>326</height>
</rect>
</property>
<property name="windowTitle">
- <string/>
+ <string>View sessions</string>
</property>
<property name="windowIcon">
<iconset resource="res.qrc">
@@ -20,71 +20,85 @@
<property name="locale">
<locale language="English" country="UnitedStates"/>
</property>
- <layout class="QGridLayout" name="gridLayout" columnstretch="3,1,0">
+ <layout class="QGridLayout" name="gridLayout" columnstretch="3,0,0">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
- <item row="0" column="0">
+ <item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Project</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QComboBox" name="projectChoice">
- <item>
- <property name="text">
- <string>None (personal project)</string>
- </property>
- </item>
- </widget>
+ <item row="1" column="1">
+ <widget class="QComboBox" name="projectChoice"/>
</item>
- <item row="1" column="0">
+ <item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Starting date</string>
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="2" column="1">
<widget class="QDateEdit" name="startDateWidget">
+ <property name="displayFormat">
+ <string>yyyy/MM/dd</string>
+ </property>
<property name="calendarPopup">
<bool>true</bool>
</property>
+ <property name="date">
+ <date>
+ <year>1900</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
</widget>
</item>
- <item row="2" column="0">
+ <item row="3" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Ending date</string>
</property>
</widget>
</item>
- <item row="2" column="1">
+ <item row="3" column="1">
<widget class="QDateEdit" name="endDateWidget">
+ <property name="displayFormat">
+ <string>yyyy/MM/dd</string>
+ </property>
<property name="calendarPopup">
<bool>true</bool>
</property>
+ <property name="date">
+ <date>
+ <year>2050</year>
+ <month>1</month>
+ <day>1</day>
+ </date>
+ </property>
</widget>
</item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_4">
+ <property name="text">
+ <string>Employer</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="employerChoice"/>
+ </item>
</layout>
</item>
- <item row="2" column="0" colspan="3">
- <widget class="QListView" name="sessionList"/>
- </item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QPushButton" name="searchButton">
- <property name="text">
- <string>&Search</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QPushButton" name="closeButton">
<property name="text">
<string>&Close</string>
@@ -106,13 +120,43 @@
</item>
</layout>
</item>
+ <item row="2" column="0" colspan="3">
+ <widget class="QTableView" name="sessionList">
+ <property name="editTriggers">
+ <set>QAbstractItemView::NoEditTriggers</set>
+ </property>
+ <property name="alternatingRowColors">
+ <bool>true</bool>
+ </property>
+ <property name="selectionMode">
+ <enum>QAbstractItemView::SingleSelection</enum>
+ </property>
+ <property name="selectionBehavior">
+ <enum>QAbstractItemView::SelectRows</enum>
+ </property>
+ <property name="showGrid">
+ <bool>false</bool>
+ </property>
+ <property name="cornerButtonEnabled">
+ <bool>false</bool>
+ </property>
+ <attribute name="horizontalHeaderHighlightSections">
+ <bool>false</bool>
+ </attribute>
+ <attribute name="horizontalHeaderStretchLastSection">
+ <bool>true</bool>
+ </attribute>
+ <attribute name="verticalHeaderVisible">
+ <bool>false</bool>
+ </attribute>
+ </widget>
+ </item>
</layout>
</widget>
<tabstops>
<tabstop>projectChoice</tabstop>
<tabstop>startDateWidget</tabstop>
<tabstop>endDateWidget</tabstop>
- <tabstop>searchButton</tabstop>
<tabstop>closeButton</tabstop>
<tabstop>sessionList</tabstop>
</tabstops>
@@ -123,12 +167,12 @@
<connection>
<sender>closeButton</sender>
<signal>clicked()</signal>
- <receiver>sessionsWindow</receiver>
+ <receiver>viewSessionsDialog</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
- <x>427</x>
- <y>53</y>
+ <x>372</x>
+ <y>71</y>
</hint>
<hint type="destinationlabel">
<x>434</x>
@@ -136,5 +180,74 @@
</hint>
</hints>
</connection>
+ <connection>
+ <sender>employerChoice</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>viewSessionsDialog</receiver>
+ <slot>employerBoxChanged()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>134</x>
+ <y>19</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>0</x>
+ <y>21</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>projectChoice</sender>
+ <signal>currentIndexChanged(int)</signal>
+ <receiver>viewSessionsDialog</receiver>
+ <slot>projectBoxChanged()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>201</x>
+ <y>52</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>84</x>
+ <y>48</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>startDateWidget</sender>
+ <signal>dateChanged(QDate)</signal>
+ <receiver>viewSessionsDialog</receiver>
+ <slot>dateBoxesChanged()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>171</x>
+ <y>88</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>313</x>
+ <y>99</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>endDateWidget</sender>
+ <signal>dateChanged(QDate)</signal>
+ <receiver>viewSessionsDialog</receiver>
+ <slot>dateBoxesChanged()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>215</x>
+ <y>117</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>300</x>
+ <y>116</y>
+ </hint>
+ </hints>
+ </connection>
</connections>
+ <slots>
+ <slot>employerBoxChanged()</slot>
+ <slot>projectBoxChanged()</slot>
+ <slot>dateBoxesChanged()</slot>
+ </slots>
</ui>
Modified: trunk/src/workman.py
===================================================================
--- trunk/src/workman.py 2011-10-21 00:53:11 UTC (rev 38)
+++ trunk/src/workman.py 2011-10-21 00:53:26 UTC (rev 39)
@@ -16,8 +16,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from datetime import datetime
-from PyQt4 import QtGui
+from PyQt4 import QtGui, QtCore
import sys
import workman_types
'''
@@ -59,7 +58,7 @@
self.activeSession is None)
session = workman_types.Session(
self.employers[employerName].projects[projectName],
- datetime.now())
+ QtCore.QDateTime.currentDateTime())
self.activeSession = session
#TODO: write data to a file for disaster recovery
@@ -70,9 +69,10 @@
if self.db is not None:
return #TODO: write session and its breaks to database
+ endTime = QtCore.QDateTime.currentDateTime()
if self.activeBreak is not None:
- self.endBreak()
- self.activeSession.endTime = datetime.now()
+ self.endBreak(endTime)
+ self.activeSession.endTime = endTime
self.activeSession.message = message
self.activeSession = None
@@ -82,11 +82,14 @@
self.activeBreak = workman_types.Break(
self.activeSession, reason = reason)
- def endBreak(self):
+ def endBreak(self, endTime = None):
#TODO: write data to a file for disaster recovery
assert(self.activeBreak is not None and
- self.activeSession is not None)
- self.activeBreak.endTime = datetime.now()
+ self.activeSession is not None)
+ if endTime is None:
+ self.activeBreak.endTime = QtCore.QDateTime.currentDateTime()
+ else:
+ self.activeBreak.endTime = endTime
self.activeBreak = None
def addEmployer(self, name, description, rate):
@@ -156,7 +159,87 @@
else:
return self.projectItemModel
+ def __newSessionItemModelRow(self,
+ startDate, endDate, timeWorked,
+ numBreaks, breakTime, summary):
+ format = QtGui.qApp.tr("yyyy-MM-dd hh:mm:ss")
+ timeFormat = QtGui.qApp.tr("hh'h'mm'm'ss's'")
+ startDateItem = QtGui.QStandardItem(startDate.toString(format))
+ if endDate is None:
+ endDateItem = QtGui.QStandardItem(QtGui.qApp.tr("In progress"))
+ else:
+ endDateItem = QtGui.QStandardItem(endDate.toString(format))
+ baseTime = QtCore.QTime(0, 0)
+ timeWorkedObject = baseTime.addSecs(timeWorked)
+ timeWorkedItem = QtGui.QStandardItem(
+ timeWorkedObject.toString(timeFormat))
+ numBreaksItem = QtGui.QStandardItem(repr(numBreaks))
+
+
+ breakTimeObject = baseTime.addSecs(breakTime)
+ breakTimeItem = QtGui.QStandardItem(
+ breakTimeObject.toString(timeFormat))
+
+ summaryItem = QtGui.QStandardItem(summary)
+ return( [startDateItem, endDateItem, timeWorkedItem,
+ numBreaksItem, breakTimeItem, summaryItem] )
+
+
+ def getSessionItemModel(self, employer, project, startDate, endDate):
+ assert(employer in self.employers.keys() and
+ project in self.employers[employer].projects.keys() and
+ startDate <= endDate)
+
+ result = self.projectItemModel = QtGui.QStandardItemModel()
+ result.setHorizontalHeaderLabels(
+ [result.tr("Start Time"),
+ result.tr("End time"),
+ result.tr("Time worked (excl. breaks)"),
+ result.tr("Number of breaks"),
+ result.tr("Break time"),
+ result.tr("Work summary")])
+
+
+ project = self.employers[employer].projects[project]
+ for i in project.sessions:
+ assert i.startTime is not None
+ if i.startTime < startDate or i.startTime > endDate:
+ continue
+
+ if i.endTime is None:
+ timeWorked = (
+ i.startTime.secsTo(
+ QtCore.QDateTime.currentDateTime()))
+ else:
+ timeWorked = i.startTime.secsTo(i.endTime)
+
+ numBreaks = 0
+ breakTime = 0
+ print (timeWorked, breakTime)
+ for j in i.breaks:
+ assert j.startTime is not None
+ if j.endTime is None:
+ curBreakTime = j.startTime.secsTo(
+ QtCore.QDateTime.currentDateTime())
+ else:
+ curBreakTime = j.startTime.secsTo(j.endTime)
+
+ timeWorked -= curBreakTime
+ breakTime += curBreakTime
+ numBreaks += 1
+ print (timeWorked, breakTime, curBreakTime)
+
+
+ row = self.__newSessionItemModelRow(i.startTime, i.endTime,
+ timeWorked, numBreaks, breakTime, i.message)
+ result.appendRow(row)
+
+ return result
+
+
+
+
if __name__ == '__main__':
from gui.main_window import MainWindow
Modified: trunk/src/workman_types.py
===================================================================
--- trunk/src/workman_types.py 2011-10-21 00:53:11 UTC (rev 38)
+++ trunk/src/workman_types.py 2011-10-21 00:53:26 UTC (rev 39)
@@ -24,7 +24,7 @@
@author: João Miguel Ferreira da Silva
'''
-from datetime import datetime
+from PyQt4.QtCore import QDateTime
class Employer:
'''
@@ -58,8 +58,9 @@
employer.projects[self.name] = self
class Session:
- def __init__(self, project = None, startTime = datetime.now(),
+ def __init__(self, project = None, startTime = QDateTime.currentDateTime(),
endTime = None, message = '', breaks = None):
+ #TODO: Handle timezone problems
self.startTime = startTime
self.endTime = endTime
self.setProject(project)
@@ -77,7 +78,8 @@
class Break:
def __init__(self, session = None, reason = '',
- startTime = datetime.now(), endTime = None):
+ startTime = QDateTime.currentDateTime(), endTime = None):
+ #TODO: Handle timezone problems
self.startTime = startTime
self.endTime = endTime
self.reason = reason
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|