123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100 |
- #!/usr/bin/env python3
- """
- # timeshift - Simple work time scheduler
- # Copyright (c) 2009-2020 Michael Buesch <m@bues.ch>
- # Licensed under the GNU/GPL version 2 or later.
- """
- import sys
- import os
- import base64
- import sqlite3 as sql
- import pathlib
- try:
- raise ImportError #FIXME
- from PySide2.QtCore import *
- from PySide2.QtGui import *
- from PySide2.QtWidgets import *
- usingPySide = True
- except ImportError as e:
- from PyQt5.QtCore import *
- from PyQt5.QtGui import *
- from PyQt5.QtWidgets import *
- usingPySide = False
- isAndroid = any("ANDROID" in k.upper() for k in os.environ.keys())
- # Shift types
- SHIFT_DEFAULT = -1 # (not DB ABI)
- SHIFT_EARLY = 0
- SHIFT_LATE = 1
- SHIFT_NIGHT = 2
- SHIFT_DAY = 3
- # Day type overrides
- DTYPE_DEFAULT = 0 # (not DB ABI)
- DTYPE_COMPTIME = 1
- DTYPE_HOLIDAY = 2
- DTYPE_FEASTDAY = 3
- DTYPE_SHORTTIME = 4
- # Day flags
- DFLAG_UNCERTAIN = (1 << 0)
- DFLAG_ATTENDANT = (1 << 1)
- def toBase64(string):
- return base64.standard_b64encode(
- string.encode("UTF-8", "ignore")).decode("UTF-8", "ignore")
- def fromBase64(b64str):
- return base64.standard_b64decode(
- b64str.encode("UTF-8", "ignore")).decode("UTF-8", "ignore")
- class Wrapper(object): # Must not be QObject derived.
- __slots__ = ( "obj", )
- def __init__(self, obj):
- self.obj = obj
- def floatEqual(f0, f1):
- return abs(f0 - f1) < 0.001
- def QDateToId(qdate):
- """Convert a QDate object to a unique integer ID."""
- return QDateTime(qdate).toMSecsSinceEpoch()
- def IdToQDate(idNum):
- """Convert a unique integer ID to a QDate object."""
- return QDateTime.fromMSecsSinceEpoch(int(idNum)).date()
- class TsException(Exception): pass
- class ICal_Event(QObject):
- def __init__(self):
- QObject.__init__(self)
- self.props = { }
- def addProp(self, prop):
- self.props[prop.name] = prop
- def getProp(self, name):
- try:
- return self.props[name]
- except KeyError as e:
- return None
- def getDateRange(self):
- # Returns a list of QDate objects
- start = self.getProp("DTSTART")
- if not start:
- raise TsException("No DTSTART property")
- end = self.getProp("DTEND")
- if not end:
- dur = self.getProp("DURATION")
- if not dur:
- return [ start.toQDate() ]
- raise TsException("TODO: DURATION property "
- "not implemented, yet.")
- ret, date = [], start.toQDate()
- while date < end.toQDate():
- ret.append(date)
- date = date.addDays(1)
- return ret
- class ICal_Prop(QObject):
- def __init__(self, name, params, value):
- QObject.__init__(self)
- self.name = name
- self.params = params
- self.value = value
- def toQDate(self):
- date = QDate.fromString(self.value, Qt.ISODate)
- if date.isValid():
- return date
- date = QDate.fromString(self.value, "yyyyMMdd")
- if date.isValid():
- return date
- raise TsException("Date property '%s' "
- "format error" % self.name)
- class ICal(QObject):
- "Simple iCalendar parser"
- def __init__(self):
- # QObject.__init__(self)
- pass
- def getEvents(self):
- return self.__events
- def __parseParams(self, params):
- # Returns a list of tuples: (paramName, paramValue)
- ret = []
- for param in params:
- p = param.split('=')
- if len(p) != 2:
- raise TsException("Invalid parameter '%s'" % param)
- p[0] = p[0].upper()
- ret.append(tuple(p))
- return ret
- def __unknown(self, propName, value):
- print("ical: Ignoring unexpected '%s:%s'" %\
- (propName, value))
- def parseICal(self, data):
- self.__events = []
- inCalendar = False
- curEvent = None
- for line in data.splitlines():
- if not line.strip():
- continue
- vstart = line.find(':')
- value = line[vstart+1:]
- prop = line[:vstart].split(';')
- propName = prop[0].strip().upper()
- try:
- propParams = prop[1:]
- except IndexError as e:
- propParams = []
- propParams = self.__parseParams(propParams)
- if not inCalendar:
- if propName == "BEGIN" and\
- value.strip().upper() == "VCALENDAR":
- inCalendar = True
- continue
- self.__unknown(propName, value)
- continue
- if not curEvent:
- if propName in ("METHOD", "PRODID", "VERSION"):
- continue
- if propName == "BEGIN" and\
- value.strip().upper() == "VEVENT":
- curEvent = ICal_Event()
- continue
- if propName == "END" and\
- value.strip().upper() == "VCALENDAR":
- curEvent = None
- inCalendar = False
- continue
- self.__unknown(propName, value)
- continue
- if propName == "END" and\
- value.strip().upper() == "VEVENT":
- self.__events.append(curEvent)
- curEvent = None
- continue
- curEvent.addProp(ICal_Prop(propName, propParams, value))
- class ICalImport_Opts(QObject):
- def __init__(self, setShift, setDayType):
- QObject.__init__(self)
- self.setShift = setShift
- self.setDayType = setDayType
- class ICalImport(ICal):
- def __init__(self, widget, db):
- ICal.__init__(self)
- self.widget = widget
- self.db = db
- def importICal(self, data, opts):
- self.parseICal(data)
- for event in self.getEvents():
- summary = event.getProp("SUMMARY")
- if not summary:
- raise TsException(
- "Event does not have SUMMARY attribute")
- for date in event.getDateRange():
- self.__doImport(event, date, opts)
- def __doImport(self, event, date, opts):
- if opts.setDayType != DTYPE_DEFAULT:
- newDType = opts.setDayType
- curDType = self.db.getDayTypeOverride(date)
- if curDType is not None and\
- curDType != newDType:
- yes = self.__question(date,
- "Has day-type override",
- date.toString() + ": "
- "Already has day type. Override?")
- if not yes:
- newDType = curDType
- if curDType != newDType:
- self.db.setDayTypeOverride(date, newDType)
- if opts.setShift != SHIFT_DEFAULT:
- newShift = opts.setShift
- curShift = self.db.getShiftOverride(date)
- if curShift is not None and\
- curShift != newShift:
- yes = self.__question(date,
- "Has shift override",
- date.toString() + ": "
- "Already has shift. Override?")
- if not yes:
- newShift = curShift
- if curShift != newShift:
- self.db.setShiftOverride(date, newShift)
- summary = event.getProp("SUMMARY").value
- newComment = summary
- curComment = self.db.getComment(date)
- if curComment and\
- curComment != newComment:
- yes = self.__question(date, "Comment exists",
- "A comment exists:\n'" + curComment +\
- "'\n\nAppend '%s'?" % summary)
- if yes:
- newComment = curComment + '\n' + summary
- else:
- newComment = curComment
- if curComment != newComment:
- self.db.setComment(date, newComment)
- def __question(self, date, caption, text):
- res = QMessageBox.question(self.widget,
- date.toString() + ": " + caption,
- text,
- QMessageBox.Yes | QMessageBox.No |\
- QMessageBox.Cancel)
- if res & QMessageBox.Cancel:
- raise TsException("Cancelled")
- return bool(res & QMessageBox.Yes)
- class ICalImportDialog(QDialog, ICalImport):
- def __init__(self, parent, db):
- QDialog.__init__(self, parent)
- ICalImport.__init__(self, self, db)
- self.setWindowTitle("iCalendar Import")
- self.setLayout(QGridLayout())
- if isAndroid:
- self.layout().setSpacing(50)
- self.modGroup = QGroupBox("Tagesoptionen pro ical-event setzen")
- self.modGroup.setLayout(QGridLayout())
- self.layout().addWidget(self.modGroup, 0, 0)
- self.typeCombo = DayTypeComboBox(self)
- self.modGroup.layout().addWidget(self.typeCombo, 0, 0)
- self.shiftCombo = ShiftComboBox(self, defaultShift=True)
- self.modGroup.layout().addWidget(self.shiftCombo, 1, 0)
- self.fileButton = QPushButton("iCal Datei Import")
- self.layout().addWidget(self.fileButton, 1, 0)
- self.fileButton.released.connect(self.fileImport)
- def fileImport(self):
- fn, fil = QFileDialog.getOpenFileName(self,
- "iCalendar Datei",
- "",
- "iCalendar Dateien (*.ics);;"
- "Alle Dateien (*)")
- if not fn:
- return
- self.__fileImport(fn)
- self.accept()
- def __fileImport(self, filename):
- try:
- fd = open(filename, "rb")
- data = fd.read().decode("UTF-8")
- fd.close()
- except (IOError, UnicodeError) as e:
- QMessageBox.critical(self,
- "iCal Laden fehlgeschlagen",
- "Laden fehlgeschlagen:\n" + str(e))
- return
- opts = ICalImport_Opts(
- setShift=self.shiftCombo.selectedShift(),
- setDayType=self.typeCombo.selectedDayType()
- )
- try:
- self.importICal(data, opts)
- except TsException as e:
- QMessageBox.critical(self,
- "iCal Import fehlgeschlagen",
- "Import fehlgeschagen:\n" + str(e))
- class DayTypeComboBox(QComboBox):
- def __init__(self, parent=None):
- QComboBox.__init__(self, parent)
- self.addItem("---", DTYPE_DEFAULT)
- self.addItem("Zeitausgleich", DTYPE_COMPTIME)
- self.addItem("Urlaub", DTYPE_HOLIDAY)
- self.addItem("Feiertag", DTYPE_FEASTDAY)
- self.addItem("Kurzarbeit", DTYPE_SHORTTIME)
- def selectedDayType(self):
- return self.itemData(self.currentIndex())
- class ShiftComboBox(QComboBox):
- def __init__(self, parent=None, shortNames=False, defaultShift=False):
- QComboBox.__init__(self, parent)
- sfx = "" if shortNames else "schicht"
- if defaultShift:
- self.addItem("Regulaere Schicht", SHIFT_DEFAULT)
- self.addItem("Frueh" + sfx, SHIFT_EARLY)
- self.addItem("Nacht" + sfx, SHIFT_NIGHT)
- self.addItem("Spaet" + sfx, SHIFT_LATE)
- self.addItem("Normal" + sfx, SHIFT_DAY)
- def selectedShift(self):
- return self.itemData(self.currentIndex())
- class ShiftConfigItem(QObject):
- def __init__(self, name, shift, workTime, breakTime, attendanceTime):
- QObject.__init__(self)
- self.name = name
- self.shift = shift
- self.workTime = workTime
- self.breakTime = breakTime
- self.attendanceTime = attendanceTime
- @staticmethod
- def toBytes(item):
- return ";".join(
- ( toBase64(item.name),
- str(item.shift),
- str(item.workTime),
- str(item.breakTime),
- str(item.attendanceTime),
- )
- ).encode("UTF-8", "ignore")
- @staticmethod
- def fromBytes(b):
- string = b.decode("UTF-8", "ignore")
- elems = string.split(";")
- try:
- return ShiftConfigItem(
- fromBase64(elems[0]),
- int(elems[1], 10),
- float(elems[2]),
- float(elems[3]),
- float(elems[4])
- )
- except (IndexError, ValueError) as e:
- raise TsException("ShiftConfigItem.fromBytes() "
- "invalid string: " + string)
- class Preset(QObject):
- def __init__(self, name, dayType, shift, workTime, breakTime, attendanceTime):
- QObject.__init__(self)
- self.name = name
- self.dayType = dayType
- self.shift = shift
- self.workTime = workTime
- self.breakTime = breakTime
- self.attendanceTime = attendanceTime
- @staticmethod
- def toBytes(preset):
- return ";".join(
- ( toBase64(preset.name),
- str(preset.dayType),
- str(preset.shift),
- str(preset.workTime),
- str(preset.breakTime),
- str(preset.attendanceTime),
- )
- ).encode("UTF-8", "ignore")
- @staticmethod
- def fromBytes(b):
- string = b.decode("UTF-8", "ignore")
- elems = string.split(";")
- try:
- return Preset(
- fromBase64(elems[0]),
- int(elems[1], 10),
- int(elems[2], 10),
- float(elems[3]),
- float(elems[4]),
- float(elems[5])
- )
- except (IndexError, ValueError) as e:
- raise TsException("Preset.fromBytes() "
- "invalid string: " + string)
- class Snapshot(QObject):
- def __init__(self, date, shiftConfigIndex, accountValue,
- holidaysLeft):
- QObject.__init__(self)
- self.date = date
- self.shiftConfigIndex = shiftConfigIndex
- self.accountValue = accountValue
- self.holidaysLeft = holidaysLeft
- @staticmethod
- def toBytes(snapshot):
- return ";".join(
- ( str(QDateToId(snapshot.date)),
- str(snapshot.shiftConfigIndex),
- str(snapshot.accountValue),
- str(snapshot.holidaysLeft),
- )
- ).encode("UTF-8", "ignore")
- @staticmethod
- def fromBytes(b):
- string = b.decode("UTF-8", "ignore")
- elems = string.split(";")
- try:
- return Snapshot(
- IdToQDate(int(elems[0], 10)),
- int(elems[1], 10),
- float(elems[2]),
- int(elems[3], 10)
- )
- except (IndexError, ValueError) as e:
- raise TsException("Snapshot.fromBytes() "
- "invalid string: " + string)
- class TsDatabase(QObject):
- INMEM = ":memory:"
- VERSION = 2
- COMPAT_VERSIONS = ( 2, )
- sql.register_adapter(QDate, QDateToId)
- sql.register_converter("QDate", IdToQDate)
- sql.register_adapter(ShiftConfigItem, ShiftConfigItem.toBytes)
- sql.register_converter("ShiftConfigItem", ShiftConfigItem.fromBytes)
- sql.register_adapter(Preset, Preset.toBytes)
- sql.register_converter("Preset", Preset.fromBytes)
- sql.register_adapter(Snapshot, Snapshot.toBytes)
- sql.register_converter("Snapshot", Snapshot.fromBytes)
- TAB_params = "params(name TEXT, data TEXT)"
- TAB_dayflags = "dayFlags(date QDate, value INTEGER)"
- TAB_ovr_daytype = "override_dayType(date QDate, value TEXT)"
- TAB_ovr_shift = "override_shift(date QDate, value TEXT)"
- TAB_ovr_worktm = "override_workTime(date QDate, value TEXT)"
- TAB_ovr_brtm = "override_breakTime(date QDate, value TEXT)"
- TAB_ovr_atttm = "override_attendanceTime(date QDate, value TEXT)"
- TAB_snaps = "snapshots(date QDate, snapshot Snapshot)"
- TAB_comments = "comments(date QDate, comment TEXT)"
- TAB_shconf = "shiftConfig(idx INTEGER, item ShiftConfigItem)"
- TAB_presets = "presets(idx INTEGER, preset Preset)"
- def __init__(self):
- QObject.__init__(self)
- self.commitTimer = QTimer(self)
- self.commitTimer.setSingleShot(True)
- self.commitTimer.timeout.connect(self.__commitTimerTimeout)
- self.__reset()
- self.open(self.INMEM)
- def __del__(self):
- self.conn.close()
- def __sqlError(self, exception):
- msg = "SQL error: " + str(exception)
- print(msg)
- import traceback
- traceback.print_stack()
- raise TsException(msg)
- def __reset(self):
- self.conn = None
- self.filename = None
- self.cachedShiftConfig = None
- def __close(self):
- if not self.conn:
- return
- try:
- if not self.isInMemory():
- print("Closing database...")
- self.commit()
- self.conn.cursor().execute("VACUUM;")
- self.commit()
- self.conn.close()
- self.__reset()
- except sql.Error as e:
- self.__sqlError(e)
- def close(self):
- self.__close()
- self.open(self.INMEM)
- def open(self, filename):
- try:
- self.__close()
- self.conn = sql.connect(str(filename),
- detect_types=sql.PARSE_DECLTYPES)
- self.filename = filename
- if not self.isInMemory():
- self.__checkDatabaseVersion()
- self.__initTables(self.conn)
- if self.isInMemory():
- self.__setDatabaseVersion()
- except sql.Error as e:
- self.__sqlError(e)
- def __setDatabaseVersion(self):
- try:
- self.__setParameter("dbVersion", self.VERSION)
- except sql.Error as e:
- self.__sqlError(e)
- def __checkDatabaseVersion(self):
- try:
- dbVer = int(self.__getParameter("dbVersion"), 10)
- if dbVer not in self.COMPAT_VERSIONS:
- raise TsException("Unsupported database "
- "version v%d" % dbVer)
- except sql.Error as e:
- self.__sqlError(e)
- except ValueError as e:
- raise TsException("Invalid database version info")
- def getFilename(self):
- return self.filename
- def isInMemory(self):
- return self.filename == self.INMEM
- def commit(self):
- try:
- self.conn.commit()
- except sql.Error as e:
- self.__sqlError(e)
- def __commitTimerTimeout(self):
- print("Committing database...")
- self.commit()
- def scheduleCommit(self, msec=5000):
- self.commitTimer.start(msec)
- def __initTables(self, conn):
- script = (
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_params,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_dayflags,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_ovr_daytype,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_ovr_shift,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_ovr_worktm,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_ovr_brtm,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_ovr_atttm,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_snaps,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_comments,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_shconf,
- "CREATE TABLE IF NOT EXISTS %s;" % self.TAB_presets,
- )
- conn.cursor().executescript("\n".join(script))
- conn.commit()
- def resetDatabase(self):
- self.conn.cursor().executescript("""
- DROP TABLE IF EXISTS params;
- DROP TABLE IF EXISTS dayFlags;
- DROP TABLE IF EXISTS override_dayType;
- DROP TABLE IF EXISTS override_shift;
- DROP TABLE IF EXISTS override_workTime;
- DROP TABLE IF EXISTS override_breakTime;
- DROP TABLE IF EXISTS override_attendanceTime;
- DROP TABLE IF EXISTS snapshots;
- DROP TABLE IF EXISTS comments;
- DROP TABLE IF EXISTS shiftConfig;
- DROP TABLE IF EXISTS presets;
- """)
- self.conn.commit()
- self.conn.cursor().execute("VACUUM;")
- self.conn.commit()
- self.__initTables(self.conn)
- self.__setDatabaseVersion()
- self.conn.commit()
- def __cloneTab(self, sourceCursor, targetCursor, tabSignature):
- tabName = tabSignature.split("(")[0].strip()
- columns = tabSignature.split("(")[1].split(")")[0]
- columns = [ c.split()[0] for c in columns.split(",") ]
- columns = ", ".join(columns)
- targetCursor.execute("DROP TABLE IF EXISTS %s;" % tabName)
- targetCursor.execute("CREATE TABLE %s;" % tabSignature)
- sourceCursor.execute("SELECT %s FROM %s;" % (columns, tabName))
- for rowData in sourceCursor.fetchall():
- valTmpl = ", ".join("?" * len(columns.split(",")))
- targetCursor.execute("INSERT INTO %s(%s) VALUES(%s);" %\
- (tabName, columns, valTmpl),
- rowData)
- def clone(self, target):
- try:
- cloneconn = sql.connect(str(target),
- detect_types=sql.PARSE_DECLTYPES)
- for tabSignature in (self.TAB_params, self.TAB_dayflags,
- self.TAB_ovr_daytype, self.TAB_ovr_shift,
- self.TAB_ovr_worktm, self.TAB_ovr_brtm,
- self.TAB_ovr_atttm, self.TAB_snaps,
- self.TAB_comments, self.TAB_shconf,
- self.TAB_presets):
- self.__cloneTab(sourceCursor=self.conn.cursor(),
- targetCursor=cloneconn.cursor(),
- tabSignature=tabSignature)
- cloneconn.commit()
- cloneconn.cursor().execute("VACUUM;")
- cloneconn.commit()
- cloneconn.close()
- except sql.Error as e:
- self.__sqlError(e)
- def __setParameter(self, param, value):
- try:
- c = self.conn.cursor()
- c.execute("DELETE FROM params WHERE name=?;", (str(param),))
- if value is not None:
- c.execute("INSERT INTO params(name, data) VALUES(?, ?);",
- (str(param), str(value)))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def __getParameter(self, param):
- try:
- c = self.conn.cursor()
- c.execute("SELECT data FROM params WHERE name=?;", (param,))
- value = c.fetchone()
- if value:
- return value[0]
- return None
- except sql.Error as e:
- self.__sqlError(e)
- def setDayFlags(self, date, value):
- try:
- c = self.conn.cursor()
- c.execute("DELETE FROM dayFlags WHERE date=?;", (date,))
- c.execute("INSERT INTO dayFlags(date, value) VALUES(?, ?);",
- (date, int(value) & 0xFFFFFFFF))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def getDayFlags(self, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT value FROM dayFlags WHERE date=?;", (date,))
- value = c.fetchone()
- if not value:
- return 0
- return int(value[0]) & 0xFFFFFFFF
- except sql.Error as e:
- self.__sqlError(e)
- def __setOverride(self, table, date, value):
- try:
- c = self.conn.cursor()
- c.execute("DELETE FROM %s WHERE date=?;" % table, (date,))
- if value is not None:
- c.execute("INSERT INTO %s(date, value) VALUES(?, ?);" % table,
- (date, str(value)))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def __getOverride(self, table, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT value FROM %s WHERE date=?;" % table, (date,))
- value = c.fetchone()
- if value:
- return value[0]
- return None
- except sql.Error as e:
- self.__sqlError(e)
- def __hasOverride(self, table, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT COUNT(*) FROM %s WHERE date=?;" % table, (date,))
- value = c.fetchone()
- if value:
- return value[0] > 0
- return False
- except sql.Error as e:
- self.__sqlError(e)
- def setDayTypeOverride(self, date, daytype):
- self.__setOverride("override_dayType", date, daytype)
- def hasDayTypeOverride(self, date):
- return self.__hasOverride("override_dayType", date)
- def getDayTypeOverride(self, date):
- try:
- return int(self.__getOverride("override_dayType", date), 10)
- except (ValueError, TypeError) as e:
- return None
- def findDayTypeDates(self, daytype, beginDate, endDate):
- # Find all dates with the specified "daytype" between
- # "beginDate" and "endDate".
- # XXX: Currently unused.
- try:
- c = self.conn.cursor()
- c.execute("""
- SELECT date FROM override_dayType WHERE
- (value=? AND date>=? AND date<=?);
- """, (daytype, beginDate, endDate))
- dates = c.fetchall()
- return [ d[0] for d in dates ]
- except (ValueError, TypeError) as e:
- return None
- def setShiftOverride(self, date, shift):
- self.__setOverride("override_shift", date, shift)
- def hasShiftOverride(self, date):
- return self.__hasOverride("override_shift", date)
- def getShiftOverride(self, date):
- try:
- return int(self.__getOverride("override_shift", date), 10)
- except (ValueError, TypeError) as e:
- return None
- def setWorkTimeOverride(self, date, workTime):
- self.__setOverride("override_workTime", date, workTime)
- def hasWorkTimeOverride(self, date):
- return self.__hasOverride("override_workTime", date)
- def getWorkTimeOverride(self, date):
- try:
- return float(self.__getOverride("override_workTime", date))
- except (ValueError, TypeError) as e:
- return None
- def setBreakTimeOverride(self, date, breakTime):
- self.__setOverride("override_breakTime", date, breakTime)
- def hasBreakTimeOverride(self, date):
- return self.__hasOverride("override_breakTime", date)
- def getBreakTimeOverride(self, date):
- try:
- return float(self.__getOverride("override_breakTime", date))
- except (ValueError, TypeError) as e:
- return None
- def setAttendanceTimeOverride(self, date, attendanceTime):
- self.__setOverride("override_attendanceTime", date, attendanceTime)
- def hasAttendanceTimeOverride(self, date):
- return self.__hasOverride("override_attendanceTime", date)
- def getAttendanceTimeOverride(self, date):
- try:
- return float(self.__getOverride("override_attendanceTime", date))
- except (ValueError, TypeError) as e:
- return None
- def setShiftConfigItems(self, items):
- self.cachedShiftConfig = items
- try:
- c = self.conn.cursor()
- c.execute("DROP TABLE IF EXISTS shiftConfig;")
- c.execute("CREATE TABLE %s;" % self.TAB_shconf)
- for (index, item) in enumerate(items):
- c.execute("INSERT INTO shiftConfig(idx, item) VALUES(?, ?);",
- (index, item))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def getShiftConfigItems(self):
- if self.cachedShiftConfig is not None:
- return self.cachedShiftConfig
- try:
- c = self.conn.cursor()
- c.execute("CREATE TABLE IF NOT EXISTS %s;" % self.TAB_shconf)
- c.execute('SELECT item FROM shiftConfig ORDER BY "idx";')
- items = c.fetchall()
- items = [ i[0] for i in items ]
- self.cachedShiftConfig = items
- return items
- except sql.Error as e:
- self.__sqlError(e)
- def setPresets(self, presets):
- try:
- c = self.conn.cursor()
- c.execute("DROP TABLE IF EXISTS presets;")
- c.execute("CREATE TABLE %s;" % self.TAB_presets)
- for (index, preset) in enumerate(presets):
- c.execute("INSERT INTO presets(idx, preset) VALUES(?, ?);",
- (index, preset))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def getPresets(self):
- try:
- c = self.conn.cursor()
- c.execute("CREATE TABLE IF NOT EXISTS %s;" % self.TAB_presets)
- c.execute('SELECT preset FROM presets ORDER BY "idx";')
- presets = c.fetchall()
- return [ p[0] for p in presets ]
- except sql.Error as e:
- self.__sqlError(e)
- def setSnapshot(self, date, snapshot):
- try:
- c = self.conn.cursor()
- c.execute("DELETE FROM snapshots WHERE date=?;", (date,))
- if snapshot is not None:
- c.execute("INSERT INTO snapshots(date, snapshot) VALUES(?, ?);",
- (date, snapshot))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def hasSnapshot(self, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT COUNT(*) FROM snapshots WHERE date=?;", (date,))
- value = c.fetchone()
- if value:
- return value[0] > 0
- return False
- except sql.Error as e:
- self.__sqlError(e)
- def getSnapshot(self, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT snapshot FROM snapshots WHERE date=?;", (date,))
- snapshot = c.fetchone()
- if snapshot:
- snapshot = snapshot[0]
- return snapshot
- except sql.Error as e:
- self.__sqlError(e)
- def getAllSnapshots(self):
- try:
- c = self.conn.cursor()
- c.execute("SELECT snapshot FROM snapshots;")
- snapshots = c.fetchall()
- return [ s[0] for s in snapshots ]
- except sql.Error as e:
- self.__sqlError(e)
- def findSnapshotForDate(self, date):
- # Get the snapshot that is active for a certain date.
- try:
- c = self.conn.cursor()
- c.execute("SELECT snapshot FROM snapshots WHERE date<=? "
- "ORDER BY date DESC", (date,))
- snapshot = c.fetchone()
- if snapshot:
- return snapshot[0]
- return None
- except sql.Error as e:
- self.__sqlError(e)
- def setComment(self, date, comment):
- try:
- c = self.conn.cursor()
- c.execute("DELETE FROM comments WHERE date=?;", (date,))
- if comment:
- c.execute("INSERT INTO comments(date, comment) VALUES(?, ?);",
- (date, str(comment)))
- self.scheduleCommit()
- except sql.Error as e:
- self.__sqlError(e)
- def hasComment(self, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT COUNT(*) FROM comments WHERE date=?;", (date,))
- value = c.fetchone()
- if value:
- return value[0] > 0
- return False
- except sql.Error as e:
- self.__sqlError(e)
- def getComment(self, date):
- try:
- c = self.conn.cursor()
- c.execute("SELECT comment FROM comments WHERE date=?;", (date,))
- comment = c.fetchone()
- if comment:
- comment = comment[0]
- return comment
- except sql.Error as e:
- self.__sqlError(e)
- class TimeSpinBox(QWidget):
- def __init__(self, parent, val=0.0, minVal=0.0, maxVal=24.0,
- step=0.1, decimals=2, prefix=None, suffix="h"):
- QWidget.__init__(self, parent)
- self.setLayout(QGridLayout())
- self.layout().setContentsMargins(QMargins())
- self.__spinBox = QDoubleSpinBox(self)
- if isAndroid:
- self.__spinBox.setButtonSymbols(QDoubleSpinBox.NoButtons)
- self.layout().addWidget(self.__spinBox, 0, 0, 2, 1)
- if isAndroid:
- self.__upButton = QPushButton("+", self)
- self.layout().addWidget(self.__upButton, 0, 1)
- self.__upButton.released.connect(self.__spinBox.stepUp)
- if isAndroid:
- self.__downButton = QPushButton("-", self)
- self.layout().addWidget(self.__downButton, 1, 1)
- self.__downButton.released.connect(self.__spinBox.stepDown)
- self.setDecimals(decimals)
- self.setMinimum(minVal)
- self.setMaximum(maxVal)
- self.setValue(val)
- self.setSingleStep(step)
- self.setAccelerated(True)
- self.setKeyboardTracking(False)
- if suffix:
- self.setSuffix(" " + suffix)
- if prefix:
- self.setPrefix(prefix + " ")
- def __getattr__(self, name):
- return getattr(self.__spinBox, name)
- class DaySpinBox(QSpinBox):
- def __init__(self, parent, val=0, minVal=0, maxVal=365,
- step=1, prefix=None, suffix="Tage"):
- QSpinBox.__init__(self, parent)
- self.setMinimum(minVal)
- self.setMaximum(maxVal)
- self.setValue(val)
- self.setSingleStep(step)
- if suffix:
- self.setSuffix(" " + suffix)
- if prefix:
- self.setPrefix(prefix + " ")
- class ShiftConfigDialog(QDialog):
- def __init__(self, mainWidget):
- QDialog.__init__(self, mainWidget)
- self.mainWidget = mainWidget
- self.setWindowTitle("Schichtkonfiguration")
- self.setLayout(QGridLayout())
- if isAndroid:
- self.layout().setSpacing(50)
- self.itemList = QListWidget(self)
- self.layout().addWidget(self.itemList, 0, 0, 10, 2)
- self.addButton = QPushButton("Neu", self)
- self.layout().addWidget(self.addButton, 11, 0)
- self.removeButton = QPushButton("Loeschen", self)
- self.layout().addWidget(self.removeButton, 11, 1)
- label = QLabel("Name", self)
- self.layout().addWidget(label, 0, 2)
- self.nameEdit = QLineEdit(self)
- self.layout().addWidget(self.nameEdit, 0, 3)
- label = QLabel("Schicht", self)
- self.layout().addWidget(label, 1, 2)
- self.shiftCombo = ShiftComboBox(self, shortNames=True)
- self.layout().addWidget(self.shiftCombo, 1, 3)
- label = QLabel("Arbeitszeit", self)
- self.layout().addWidget(label, 2, 2)
- self.workTime = TimeSpinBox(self)
- self.layout().addWidget(self.workTime, 2, 3)
- label = QLabel("Pausenzeit", self)
- self.layout().addWidget(label, 3, 2)
- self.breakTime = TimeSpinBox(self)
- self.layout().addWidget(self.breakTime, 3, 3)
- label = QLabel("Anwesenheit", self)
- self.layout().addWidget(label, 4, 2)
- self.attendanceTime = TimeSpinBox(self)
- self.layout().addWidget(self.attendanceTime, 4, 3)
- self.updateBlocked = False
- self.loadConfig()
- self.itemList.currentRowChanged.connect(self.itemChanged)
- self.addButton.released.connect(self.addItem)
- self.removeButton.released.connect(self.removeItem)
- self.nameEdit.textChanged.connect(self.updateCurrentItem)
- self.shiftCombo.currentIndexChanged.connect(self.updateCurrentItem)
- self.workTime.valueChanged.connect(self.updateCurrentItem)
- self.breakTime.valueChanged.connect(self.updateCurrentItem)
- self.attendanceTime.valueChanged.connect(self.updateCurrentItem)
- def setInputEnabled(self, enable):
- self.removeButton.setEnabled(enable)
- self.nameEdit.setEnabled(enable)
- self.shiftCombo.setEnabled(enable)
- self.workTime.setEnabled(enable)
- self.breakTime.setEnabled(enable)
- self.attendanceTime.setEnabled(enable)
- def loadConfig(self):
- self.itemList.clear()
- shiftConfig = self.mainWidget.db.getShiftConfigItems()
- count = 1
- for cfg in shiftConfig:
- name = "%d \"%s\"" % (count, cfg.name)
- count += 1
- self.itemList.addItem(name)
- if shiftConfig:
- self.itemList.setCurrentRow(0)
- currentItem = shiftConfig[0]
- else:
- currentItem = None
- self.loadItem(currentItem)
- self.setInputEnabled(currentItem is not None)
- def loadItem(self, item=None):
- self.updateBlocked = True
- self.nameEdit.clear()
- self.shiftCombo.setCurrentIndex(0)
- self.workTime.setValue(0.0)
- self.breakTime.setValue(0.0)
- self.attendanceTime.setValue(0.0)
- if item:
- self.nameEdit.setText(item.name)
- index = self.shiftCombo.findData(item.shift)
- self.shiftCombo.setCurrentIndex(index)
- self.workTime.setValue(item.workTime)
- self.breakTime.setValue(item.breakTime)
- self.attendanceTime.setValue(item.attendanceTime)
- self.updateBlocked = False
- def updateItem(self, item):
- item.name = self.nameEdit.text()
- index = self.shiftCombo.currentIndex()
- item.shift = self.shiftCombo.itemData(index)
- item.workTime = self.workTime.value()
- item.breakTime = self.breakTime.value()
- item.attendanceTime = self.attendanceTime.value()
- def itemChanged(self, row):
- if row >= 0:
- shiftConfig = self.mainWidget.db.getShiftConfigItems()
- self.loadItem(shiftConfig[row])
- def updateCurrentItem(self):
- if self.updateBlocked:
- return
- index = self.itemList.currentRow()
- if index < 0:
- return
- shiftConfig = self.mainWidget.db.getShiftConfigItems()
- self.updateItem(shiftConfig[index])
- self.mainWidget.db.setShiftConfigItems(shiftConfig)
- name = "%d \"%s\"" % (index + 1, shiftConfig[index].name)
- self.itemList.item(index).setText(name)
- def addItem(self):
- shiftConfig = self.mainWidget.db.getShiftConfigItems()
- index = self.itemList.currentRow()
- if index < 0:
- index = 0
- else:
- index += 1
- item = ShiftConfigItem("Unbenannt", SHIFT_DAY, 7.0, 0.75, 8.0)
- shiftConfig.insert(index, item)
- self.mainWidget.db.setShiftConfigItems(shiftConfig)
- self.loadConfig()
- self.itemList.setCurrentRow(index)
- def removeItem(self):
- index = self.itemList.currentRow()
- if index < 0:
- return
- for snapshot in self.mainWidget.db.getAllSnapshots():
- if snapshot.shiftConfigIndex >= self.itemList.count() - 1:
- dateString = snapshot.date.toString("dd.MM.yyyy")
- QMessageBox.critical(self, "Eintrag wird verwendet",
- "Der Eintrag wird von einem Schnappschuss (%s) verwendet. "
- "Loeschen nicht moeglich." % dateString)
- return
- res = QMessageBox.question(self, "Eintrag loeschen?",
- "'%s' loeschen?" % self.itemList.item(index).text(),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- shiftConfig = self.mainWidget.db.getShiftConfigItems()
- shiftConfig.pop(index)
- self.mainWidget.db.setShiftConfigItems(shiftConfig)
- self.loadConfig()
- if index >= self.itemList.count() and index > 0:
- index -= 1
- self.itemList.setCurrentRow(index)
- class EnhancedDialog(QDialog):
- def __init__(self, mainWidget):
- QDialog.__init__(self, mainWidget)
- self.mainWidget = mainWidget
- date = mainWidget.calendar.selectedDate()
- dayFlags = mainWidget.db.getDayFlags(date)
- self.setWindowTitle("Erweitert")
- self.setLayout(QGridLayout())
- if isAndroid:
- self.layout().setSpacing(50)
- self.commentGroup = QGroupBox("Kommentar", self)
- self.commentGroup.setLayout(QGridLayout())
- self.layout().addWidget(self.commentGroup, 0, 0)
- self.comment = QTextEdit(self)
- self.commentGroup.layout().addWidget(self.comment, 0, 0)
- self.comment.document().setPlainText(mainWidget.getCommentFor(date))
- self.flagsGroup = QGroupBox("Tagesoptionen", self)
- self.flagsGroup.setLayout(QGridLayout())
- self.layout().addWidget(self.flagsGroup, 1, 0)
- self.uncertainCheckBox = QCheckBox("Unbestaetigt", self)
- self.flagsGroup.layout().addWidget(self.uncertainCheckBox, 0, 0)
- cs = Qt.Checked if dayFlags & DFLAG_UNCERTAIN else Qt.Unchecked
- self.uncertainCheckBox.setCheckState(cs)
- self.attendantCheckBox = QCheckBox("Anwesenheitsmarker", self)
- self.flagsGroup.layout().addWidget(self.attendantCheckBox, 1, 0)
- cs = Qt.Checked if dayFlags & DFLAG_ATTENDANT else Qt.Unchecked
- self.attendantCheckBox.setCheckState(cs)
- self.uncertainCheckBox.stateChanged.connect(self.__flagCheckBoxChanged)
- self.attendantCheckBox.stateChanged.connect(self.__flagCheckBoxChanged)
- def __flagCheckBoxChanged(self, newState):
- self.commitAndAccept()
- def closeEvent(self, e):
- self.commitAndAccept()
- def commitAndAccept(self):
- self.commit()
- self.accept()
- def commit(self):
- date = self.mainWidget.calendar.selectedDate()
- dayFlags = oldDayFlags = self.mainWidget.getDayFlags(date)
- for checkBox, flag in ((self.uncertainCheckBox, DFLAG_UNCERTAIN),
- (self.attendantCheckBox, DFLAG_ATTENDANT)):
- dayFlags &= ~flag
- dayFlags |= flag if checkBox.checkState() == Qt.Checked else 0
- if dayFlags != oldDayFlags:
- self.mainWidget.setDayFlags(date, dayFlags)
- if dayFlags & DFLAG_ATTENDANT:
- # Automatically reset day type, if attendant flag was set.
- self.mainWidget.setDayType(date, DTYPE_DEFAULT)
- old = self.mainWidget.getCommentFor(date)
- new = self.comment.document().toPlainText()
- if old != new:
- self.mainWidget.setCommentFor(date, new)
- self.mainWidget.recalculate()
- class ManageDialog(QDialog):
- def __init__(self, mainWidget):
- QDialog.__init__(self, mainWidget)
- self.mainWidget = mainWidget
- self.setWindowTitle("Verwalten")
- self.setLayout(QGridLayout())
- if isAndroid:
- self.layout().setSpacing(50)
- self.fileGroup = QGroupBox("Datei", self)
- self.fileGroup.setLayout(QGridLayout())
- self.layout().addWidget(self.fileGroup, 0, 0)
- self.setDbButton = QPushButton("Datenbank waehlen", self)
- self.fileGroup.layout().addWidget(self.setDbButton, 0, 0)
- self.setDbButton.released.connect(self.loadDatabase)
- self.resetCalButton = QPushButton("Kalender loeschen", self)
- self.fileGroup.layout().addWidget(self.resetCalButton, 1, 0)
- self.resetCalButton.released.connect(self.resetCalendar)
- self.schedConfButton = QPushButton("Schichtkonfig", self)
- self.fileGroup.layout().addWidget(self.schedConfButton, 2, 0)
- self.schedConfButton.released.connect(self.doShiftConfig)
- self.icalButton = QPushButton("iCalendar import", self)
- self.fileGroup.layout().addWidget(self.icalButton, 3, 0)
- self.icalButton.released.connect(self.icalImport)
- def loadDatabase(self):
- self.mainWidget.loadDatabase()
- self.accept()
- def resetCalendar(self):
- res = QMessageBox.question(self, "Kalender loeschen?",
- "Wollen Sie wirklich alle Kalendereintraege "
- "und Parameter loeschen?",
- QMessageBox.Yes | QMessageBox.No)
- if res == QMessageBox.Yes:
- self.mainWidget.resetState()
- self.accept()
- def doShiftConfig(self):
- dlg = ShiftConfigDialog(self.mainWidget)
- dlg.exec_()
- self.mainWidget.worldUpdate()
- self.accept()
- def icalImport(self):
- dlg = ICalImportDialog(self.mainWidget, self.mainWidget.db)
- dlg.exec_()
- self.mainWidget.worldUpdate()
- self.accept()
- class PresetDialog(QDialog):
- def __init__(self, mainWidget):
- QDialog.__init__(self, mainWidget)
- self.setWindowTitle("Vorgaben")
- self.setLayout(QGridLayout())
- self.mainWidget = mainWidget
- if isAndroid:
- self.layout().setSpacing(50)
- self.presetList = QListWidget(self)
- self.layout().addWidget(self.presetList, 0, 0, 4, 2)
- self.addButton = QPushButton("Neu", self)
- self.layout().addWidget(self.addButton, 4, 0)
- self.removeButton = QPushButton("Loeschen", self)
- self.layout().addWidget(self.removeButton, 4, 1)
- self.commitButton = QPushButton("Vorgabe uebernehmen", self)
- self.layout().addWidget(self.commitButton, 5, 0, 1, 2)
- self.nameEdit = QLineEdit(self)
- self.layout().addWidget(self.nameEdit, 0, 2)
- self.typeCombo = DayTypeComboBox(self)
- self.layout().addWidget(self.typeCombo, 1, 2)
- self.shiftCombo = ShiftComboBox(self)
- self.layout().addWidget(self.shiftCombo, 2, 2)
- self.workTime = TimeSpinBox(self, prefix="Arb.zeit")
- self.layout().addWidget(self.workTime, 3, 2)
- self.breakTime = TimeSpinBox(self, prefix="Pause")
- self.layout().addWidget(self.breakTime, 4, 2)
- self.attendanceTime = TimeSpinBox(self, prefix="Anwes.")
- self.layout().addWidget(self.attendanceTime, 5, 2)
- self.presetChangeBlocked = False
- self.loadPresets()
- self.presetSelectionChanged()
- self.presetList.currentRowChanged.connect(self.presetSelectionChanged)
- self.presetList.itemDoubleClicked.connect(self.commitPressed)
- self.addButton.released.connect(self.addPreset)
- self.removeButton.released.connect(self.removePreset)
- self.commitButton.released.connect(self.commitPressed)
- self.nameEdit.textChanged.connect(self.presetChanged)
- self.typeCombo.currentIndexChanged.connect(self.presetChanged)
- self.shiftCombo.currentIndexChanged.connect(self.presetChanged)
- self.workTime.valueChanged.connect(self.presetChanged)
- self.breakTime.valueChanged.connect(self.presetChanged)
- self.attendanceTime.valueChanged.connect(self.presetChanged)
- def __addPreset(self, preset):
- item = QListWidgetItem(preset.name)
- item.setData(Qt.UserRole, Wrapper(preset))
- self.presetList.addItem(item)
- def loadPresets(self):
- date = self.mainWidget.calendar.selectedDate()
- shiftConfigItem = self.mainWidget.getShiftConfigItemForDate(date)
- assert(shiftConfigItem)
- self.presetList.clear()
- self.__addPreset( # index0 => special for reset
- Preset(name="--- zuruecksetzen ---",
- dayType=DTYPE_DEFAULT,
- shift=shiftConfigItem.shift,
- workTime=shiftConfigItem.workTime,
- breakTime=shiftConfigItem.breakTime,
- attendanceTime=shiftConfigItem.attendanceTime
- )
- )
- for preset in self.mainWidget.db.getPresets():
- self.__addPreset(preset)
- self.presetList.setCurrentRow(0)
- def applyPreset(self, preset):
- mainWidget = self.mainWidget
- index = mainWidget.typeCombo.findData(preset.dayType)
- mainWidget.typeCombo.setCurrentIndex(index)
- index = mainWidget.shiftCombo.findData(preset.shift)
- mainWidget.shiftCombo.setCurrentIndex(index)
- mainWidget.workTime.setValue(preset.workTime)
- mainWidget.breakTime.setValue(preset.breakTime)
- mainWidget.attendanceTime.setValue(preset.attendanceTime)
- def __enableEdit(self, enable):
- self.nameEdit.setEnabled(enable)
- self.typeCombo.setEnabled(enable)
- self.shiftCombo.setEnabled(enable)
- self.workTime.setEnabled(enable)
- self.breakTime.setEnabled(enable)
- self.attendanceTime.setEnabled(enable)
- def presetSelectionChanged(self):
- index = self.presetList.currentRow()
- self.__enableEdit(index > 0)
- self.removeButton.setEnabled(index > 0)
- self.commitButton.setEnabled(index >= 0)
- item = self.presetList.currentItem()
- if not item:
- return
- preset = item.data(Qt.UserRole).obj
- self.presetChangeBlocked = True
- self.nameEdit.setText(preset.name)
- index = self.typeCombo.findData(preset.dayType)
- self.typeCombo.setCurrentIndex(index)
- index = self.shiftCombo.findData(preset.shift)
- self.shiftCombo.setCurrentIndex(index)
- self.workTime.setValue(preset.workTime)
- self.breakTime.setValue(preset.breakTime)
- self.attendanceTime.setValue(preset.attendanceTime)
- self.presetChangeBlocked = False
- def __updatePresetItem(self, preset):
- preset.name = self.nameEdit.text()
- index = self.typeCombo.currentIndex()
- preset.dayType = self.typeCombo.itemData(index)
- index = self.shiftCombo.currentIndex()
- preset.shift = self.shiftCombo.itemData(index)
- preset.workTime = self.workTime.value()
- preset.breakTime = self.breakTime.value()
- preset.attendanceTime = self.attendanceTime.value()
- def presetChanged(self):
- if self.presetChangeBlocked:
- return
- row = self.presetList.currentRow()
- if row <= 0:
- return
- item = self.presetList.item(row)
- item.setText(self.nameEdit.text())
- self.__updatePresetItem(item.data(Qt.UserRole).obj)
- presets = self.mainWidget.db.getPresets()
- self.__updatePresetItem(presets[row - 1])
- self.mainWidget.db.setPresets(presets)
- def addPreset(self):
- presets = self.mainWidget.db.getPresets()
- index = self.presetList.currentRow()
- if index <= 0:
- index = 1
- else:
- index += 1
- date = self.mainWidget.calendar.selectedDate()
- shiftConfigItem = self.mainWidget.getShiftConfigItemForDate(date)
- preset = Preset(name="Unbenannt",
- dayType=DTYPE_DEFAULT,
- shift=shiftConfigItem.shift,
- workTime=shiftConfigItem.workTime,
- breakTime=shiftConfigItem.breakTime,
- attendanceTime=shiftConfigItem.attendanceTime
- )
- presets.insert(index - 1, preset)
- self.mainWidget.db.setPresets(presets)
- self.loadPresets()
- self.presetList.setCurrentRow(index)
- def removePreset(self):
- index = self.presetList.currentRow()
- if index <= 0:
- return
- res = QMessageBox.question(self, "Vorgabe loeschen?",
- "'%s' loeschen?" % self.presetList.item(index).text(),
- QMessageBox.Yes | QMessageBox.No)
- if res != QMessageBox.Yes:
- return
- presets = self.mainWidget.db.getPresets()
- presets.pop(index - 1)
- self.mainWidget.db.setPresets(presets)
- self.loadPresets()
- if index >= self.presetList.count() and index > 0:
- index -= 1
- self.presetList.setCurrentRow(index)
- def commitPressed(self):
- item = self.presetList.currentItem()
- if item:
- preset = item.data(Qt.UserRole).obj
- self.applyPreset(preset)
- self.accept()
- class SnapshotDialog(QDialog):
- def __init__(self, mainWidget, accountState):
- QDialog.__init__(self, mainWidget)
- self.setWindowTitle("Schnappschuss setzen")
- self.setLayout(QGridLayout())
- self.mainWidget = mainWidget
- self.date = accountState.date
- if isAndroid:
- self.layout().setSpacing(50)
- self.dateLabel = QLabel(self.date.toString("dddd, dd.MM.yyyy"), self)
- self.layout().addWidget(self.dateLabel, 0, 0)
- l = QLabel("Startschicht:", self)
- self.layout().addWidget(l, 1, 0)
- self.shiftConfig = QComboBox(self)
- shiftConfig = mainWidget.db.getShiftConfigItems()
- assert(shiftConfig)
- for index, cfg in enumerate(shiftConfig):
- name = "%d \"%s\"" % (index + 1, cfg.name)
- self.shiftConfig.addItem(name, index)
- self.layout().addWidget(self.shiftConfig, 1, 1)
- index = self.shiftConfig.findData(accountState.shiftConfigIndex)
- if index >= 0:
- self.shiftConfig.setCurrentIndex(index)
- l = QLabel("Zeitkontostand:", self)
- self.layout().addWidget(l, 2, 0)
- self.accountValue = TimeSpinBox(self,
- val=accountState.accountAtStartOfDay,
- minVal=-1000.0, maxVal=1000.0,
- step=0.1, decimals=1)
- self.layout().addWidget(self.accountValue, 2, 1)
- l = QLabel("Urlaubsstand:", self)
- self.layout().addWidget(l, 3, 0)
- self.holidays = DaySpinBox(self,
- val=accountState.holidaysAtStartOfDay)
- self.layout().addWidget(self.holidays, 3, 1)
- self.removeButton = QPushButton("Schnappschuss loeschen", self)
- self.layout().addWidget(self.removeButton, 4, 0, 1, 2)
- self.removeButton.released.connect(self.removeSnapshot)
- if not mainWidget.dateHasSnapshot(self.date):
- self.removeButton.hide()
- self.okButton = QPushButton("Setzen", self)
- self.layout().addWidget(self.okButton, 5, 0)
- self.okButton.released.connect(self.ok)
- self.cancelButton = QPushButton("Abbrechen", self)
- self.layout().addWidget(self.cancelButton, 5, 1)
- self.cancelButton.released.connect(self.cancel)
- def ok(self):
- self.accept()
- def cancel(self):
- self.reject()
- def removeSnapshot(self):
- res = QMessageBox.question(self, "Schnappschuss loeschen?",
- "Wollen Sie den Schnappschuss wirklich loeschen?",
- QMessageBox.Yes | QMessageBox.No)
- if res == QMessageBox.Yes:
- self.mainWidget.removeSnapshot(self.date)
- self.reject()
- def getSnapshot(self):
- index = self.shiftConfig.currentIndex()
- shiftConfigIndex = self.shiftConfig.itemData(index)
- accValue = self.accountValue.value()
- holidaysLeft = self.holidays.value()
- return Snapshot(self.date, shiftConfigIndex,
- accValue, holidaysLeft)
- class Calendar(QCalendarWidget):
- def __init__(self, mainWidget):
- self.__initPens()
- QCalendarWidget.__init__(self, mainWidget)
- self.mainWidget = mainWidget
- self.setFirstDayOfWeek(Qt.Monday)
- self.today = QDate.currentDate()
- self.armTodayTimer()
- def todayTimer(self):
- self.today = self.today.addDays(1)
- self.setSelectedDate(self.today)
- self.armTodayTimer()
- self.redraw()
- def armTodayTimer(self):
- tomorrow = QDateTime(self.today)
- tomorrow = tomorrow.addDays(1)
- secs = QDateTime.currentDateTime().secsTo(tomorrow)
- QTimer.singleShot(secs * 1000, self.todayTimer)
- def __initPens(self):
- self.snapshotPen = QPen(QColor("#007FFF"))
- self.snapshotPen.setWidth(5)
- self.framePen = QPen(QColor("#006400"))
- self.framePen.setWidth(1)
- self.todayPen = QPen(QColor("#006400"))
- self.todayPen.setWidth(6)
- self.commentPen = QPen(QColor("#FF0000"))
- self.commentPen.setWidth(2)
- self.overridesPen = QPen(QColor("#9F9F9F"))
- self.overridesPen.setWidth(20 if isAndroid else 5)
- self.centerPen = QPen(QColor("#007FFF"))
- self.centerPen.setWidth(1)
- self.lowerLeftPen = QPen(QColor("#FF0000"))
- self.lowerLeftPen.setWidth(1)
- self.lowerRightPen = QPen(QColor("#304F7F"))
- self.lowerRightPen.setWidth(1)
- typeLetter = {
- DTYPE_DEFAULT : None,
- DTYPE_COMPTIME : "Z",
- DTYPE_HOLIDAY : "U",
- DTYPE_SHORTTIME : "C",
- DTYPE_FEASTDAY : "F",
- }
- shiftLetter = {
- SHIFT_EARLY : "F",
- SHIFT_LATE : "S",
- SHIFT_NIGHT : "N",
- SHIFT_DAY : "O",
- }
- def paintCell(self, painter, rect, date):
- QCalendarWidget.paintCell(self, painter, rect, date)
- painter.save()
- mainWidget, font = self.mainWidget, painter.font()
- db = mainWidget.db
- rx, ry, rw, rh = rect.x(), rect.y(), rect.width(), rect.height()
- dayFlags = db.getDayFlags(date)
- font.setBold(True)
- painter.setFont(font)
- if mainWidget.dateHasSnapshot(date):
- painter.setPen(self.snapshotPen)
- else:
- painter.setPen(self.framePen)
- painter.drawRect(rx, ry, rw - 1, rh - 1)
- if date == self.today:
- painter.setPen(self.todayPen)
- for (x, y) in ((3, 3), (rw - 3, 3),
- (3, rh - 3),
- (rw - 3, rh - 3)):
- painter.drawPoint(rx + x, ry + y)
- if mainWidget.dateHasComment(date):
- painter.setPen(self.commentPen)
- painter.drawRect(rx + 3, ry + 3, rw - 3 - 3, rh - 3 - 3)
- if mainWidget.dateHasTimeOverrides(date):
- painter.setPen(self.overridesPen)
- painter.drawPoint(rx + rw - 8, ry + 8)
- text = self.typeLetter[mainWidget.getDayType(date)]
- if not text:
- if dayFlags & DFLAG_ATTENDANT:
- text = "A"
- if text:
- painter.setPen(self.lowerLeftPen)
- painter.drawText(rx + 4, ry + rh - 4, text)
- shiftOverride = db.getShiftOverride(date)
- if shiftOverride is not None:
- text = self.shiftLetter[shiftOverride]
- painter.setPen(self.lowerRightPen)
- metrics = QFontMetrics(painter.font())
- painter.drawText(rx + rw - metrics.width(text) - 4,
- ry + rh - 4,
- text)
- if dayFlags & DFLAG_UNCERTAIN:
- text = "???"
- painter.setPen(self.centerPen)
- metrics = QFontMetrics(painter.font())
- painter.drawText(rx + rw // 2 - metrics.width(text) // 2,
- ry + rh // 2 + metrics.height() // 2,
- text)
- painter.restore()
- def redraw(self):
- if self.isVisible():
- self.hide()
- self.show()
- class AccountState(QObject):
- "Calculated account state."
- def __init__(self, date, shiftConfigIndex=0,
- accountAtStartOfDay=0.0, accountAtEndOfDay=0.0,
- holidaysAtStartOfDay=0, holidaysAtEndOfDay=0):
- QObject.__init__(self)
- self.date = date
- self.shiftConfigIndex = shiftConfigIndex
- self.accountAtStartOfDay = accountAtStartOfDay
- self.accountAtEndOfDay = accountAtEndOfDay
- self.holidaysAtStartOfDay = holidaysAtStartOfDay
- self.holidaysAtEndOfDay = holidaysAtEndOfDay
- class MainWidget(QWidget):
- def __init__(self, parent=None):
- QWidget.__init__(self, parent)
- self.setFocusPolicy(Qt.StrongFocus)
- self.setLayout(QGridLayout())
- if isAndroid:
- self.layout().setSpacing(50)
- self.worldUpdateTimer = QTimer(self)
- self.worldUpdateTimer.setSingleShot(True)
- self.worldUpdateTimer.timeout.connect(self.__worldUpdateTimerTimeout)
- vbox = QVBoxLayout()
- self.workTime = TimeSpinBox(self, prefix="Arb.zeit")
- vbox.addWidget(self.workTime)
- self.breakTime = TimeSpinBox(self, prefix="Pause")
- vbox.addWidget(self.breakTime)
- self.attendanceTime = TimeSpinBox(self, prefix="Anwes.")
- vbox.addWidget(self.attendanceTime)
- self.layout().addLayout(vbox, 0, 0)
- vbox = QVBoxLayout()
- self.typeCombo = DayTypeComboBox(self)
- vbox.addWidget(self.typeCombo)
- self.shiftCombo = ShiftComboBox(self)
- vbox.addWidget(self.shiftCombo)
- self.presetButton = QPushButton("Vorgaben", self)
- vbox.addWidget(self.presetButton)
- self.presetButton.released.connect(self.doPresets)
- self.layout().addLayout(vbox, 0, 1)
- self.calendar = Calendar(self)
- self.layout().addWidget(self.calendar, 3, 0, 1, 2)
- self.calendar.selectionChanged.connect(self.recalculate)
- self.typeCombo.currentIndexChanged.connect(self.overrideChanged)
- self.shiftCombo.currentIndexChanged.connect(self.overrideChanged)
- self.workTime.valueChanged.connect(self.overrideChanged)
- self.breakTime.valueChanged.connect(self.overrideChanged)
- self.attendanceTime.valueChanged.connect(self.overrideChanged)
- self.overrideChangeBlocked = False
- hbox = QHBoxLayout()
- self.manageButton = QPushButton("Verwalten", self)
- hbox.addWidget(self.manageButton)
- self.manageButton.released.connect(self.doManage)
- self.snapshotButton = QPushButton("Schnappschuss", self)
- hbox.addWidget(self.snapshotButton)
- self.snapshotButton.released.connect(self.doSnapshot)
- self.enhancedButton = QPushButton("Erweitert", self)
- hbox.addWidget(self.enhancedButton)
- self.enhancedButton.released.connect(self.doEnhanced)
- self.layout().addLayout(hbox, 4, 0, 1, 2)
- self.output = QLabel(self)
- self.output.setAlignment(Qt.AlignHCenter)
- self.output.setFrameShape(QFrame.Panel)
- self.output.setFrameShadow(QFrame.Raised)
- self.layout().addWidget(self.output, 5, 0, 1, 2)
- self.db = TsDatabase()
- self.resetState()
- def shutdown(self):
- self.db.close()
- def resetState(self):
- self.db.resetDatabase()
- self.worldUpdate()
- def worldUpdate(self):
- self.updateTitle()
- self.recalculate()
- self.calendar.redraw()
- def __worldUpdateTimerTimeout(self):
- self.worldUpdate()
- def scheduleWorldUpdate(self, msec=1000):
- self.worldUpdateTimer.start(msec)
- def doLoadDatabase(self, filename, quiet=False):
- try:
- print("Loading database: %s" % filename)
- fi = QFileInfo(filename)
- if not fi.exists() and self.db.isInMemory():
- # Clone the in-memory DB to the new file
- self.db.clone(filename)
- self.db.open(filename)
- self.worldUpdate()
- except Exception as e:
- if not quiet:
- QMessageBox.critical(self, "Laden fehlgeschlagen",
- "Laden fehlgeschlagen:\n" + str(e))
- return False
- return True
- def loadDatabase(self):
- fn, fil = QFileDialog.getSaveFileName(self,
- "Datenbank laden", "",
- "Timeshift Dateien (*.tmd *.tms *.tmz);;"
- "Alle Dateien (*)",
- "",
- QFileDialog.DontConfirmOverwrite)
- if fn:
- self.doLoadDatabase(fn)
- def updateTitle(self):
- if self.db.isInMemory():
- suffix = "<in memory>"
- else:
- fi = QFileInfo(self.db.getFilename())
- suffix = fi.fileName()
- self.parent().setTitleSuffix(suffix)
- def dateHasComment(self, date):
- return self.db.hasComment(date)
- def getCommentFor(self, date):
- comment = self.db.getComment(date)
- return comment if comment else ""
- def setCommentFor(self, date, text):
- self.db.setComment(date, text)
- def doEnhanced(self):
- dlg = EnhancedDialog(self)
- dlg.exec_()
- def doManage(self):
- dlg = ManageDialog(self)
- dlg.exec_()
- def doPresets(self):
- dlg = PresetDialog(self)
- dlg.exec_()
- def __removeSnapshot(self, date):
- self.db.setSnapshot(date, None)
- def removeSnapshot(self, date):
- self.__removeSnapshot(date)
- self.worldUpdate()
- def __setSnapshot(self, snapshot):
- self.db.setSnapshot(snapshot.date, snapshot)
- def doSnapshot(self):
- if not self.db.getShiftConfigItems():
- QMessageBox.critical(self, "Kein Schichtsystem",
- "Kein Schichtsystem konfiguriert")
- return
- date = self.calendar.selectedDate()
- snapshot = self.getSnapshotFor(date)
- accState = AccountState(date)
- if snapshot is None:
- # Calculate the account state w.r.t. the
- # last shapshot.
- snapshot = self.db.findSnapshotForDate(date)
- if snapshot:
- accState = self.__calcAccountState(
- snapshot, date)
- else:
- # We already have a snapshot on that day. Modify it.
- accState.shiftConfigIndex = snapshot.shiftConfigIndex
- accState.accountAtStartOfDay = snapshot.accountValue
- accState.holidaysAtStartOfDay = snapshot.holidaysLeft
- dlg = SnapshotDialog(self, accState)
- if dlg.exec_():
- self.__setSnapshot(dlg.getSnapshot())
- self.worldUpdate()
- def dateHasSnapshot(self, date):
- return self.db.hasSnapshot(date)
- def getSnapshotFor(self, date):
- return self.db.getSnapshot(date)
- def overrideChanged(self):
- if self.overrideChangeBlocked or not self.db.getShiftConfigItems():
- return
- date = self.calendar.selectedDate()
- shiftConfigItem = self.getShiftConfigItemForDate(date)
- assert(shiftConfigItem)
- # Day type
- index = self.typeCombo.currentIndex()
- self.setDayType(date, self.typeCombo.itemData(index))
- # Shift override
- index = self.shiftCombo.currentIndex()
- shift = self.shiftCombo.itemData(index)
- if shift == shiftConfigItem.shift:
- shift = None
- self.setShiftOverride(date, shift)
- # Work time override
- workTime = self.workTime.value()
- if floatEqual(workTime, shiftConfigItem.workTime):
- workTime = None
- self.setWorkTimeOverride(date, workTime)
- # Break time override
- breakTime = self.breakTime.value()
- if floatEqual(breakTime, shiftConfigItem.breakTime):
- breakTime = None
- self.setBreakTimeOverride(date, breakTime)
- # Attendance time override
- attendanceTime = self.attendanceTime.value()
- if floatEqual(attendanceTime, shiftConfigItem.attendanceTime):
- attendanceTime = None
- self.setAttendanceTimeOverride(date, attendanceTime)
- self.scheduleWorldUpdate()
- def getShiftConfigIndexForDate(self, date):
- # Find the shift config index that's valid for the date.
- # May return -1 on error.
- snapshot = self.db.findSnapshotForDate(date)
- if not snapshot:
- return -1
- daysBetween = snapshot.date.daysTo(date)
- assert(daysBetween >= 0)
- index = snapshot.shiftConfigIndex
- index += daysBetween
- index %= len(self.db.getShiftConfigItems())
- return index
- def getShiftConfigItemForDate(self, date):
- index = self.getShiftConfigIndexForDate(date)
- if index >= 0:
- return self.db.getShiftConfigItems()[index]
- return None
- def enableOverrideControls(self, enable):
- self.typeCombo.setEnabled(enable)
- self.shiftCombo.setEnabled(enable)
- self.workTime.setEnabled(enable)
- self.breakTime.setEnabled(enable)
- self.attendanceTime.setEnabled(enable)
- self.presetButton.setEnabled(enable)
- def getDayType(self, date):
- dtype = self.db.getDayTypeOverride(date)
- return dtype if dtype is not None else DTYPE_DEFAULT
- def setDayType(self, date, dtype):
- dtype = dtype if dtype != DTYPE_DEFAULT else None
- self.db.setDayTypeOverride(date, dtype)
- if dtype:
- # Automatically clear attendant flag, if a dtype is set.
- self.setDayFlags(date, self.getDayFlags(date) & ~DFLAG_ATTENDANT)
- def getDayFlags(self, date):
- return self.db.getDayFlags(date)
- def setDayFlags(self, date, newFlags):
- self.db.setDayFlags(date, newFlags)
- def setShiftOverride(self, date, shift):
- self.db.setShiftOverride(date, shift)
- def getRealShift(self, date, shiftConfigItem):
- shift = self.db.getShiftOverride(date)
- return shift if shift is not None else shiftConfigItem.shift
- def setWorkTimeOverride(self, date, workTime):
- self.db.setWorkTimeOverride(date, workTime)
- def getRealWorkTime(self, date, shiftConfigItem):
- time = self.db.getWorkTimeOverride(date)
- return time if time is not None else shiftConfigItem.workTime
- def setBreakTimeOverride(self, date, breakTime):
- self.db.setBreakTimeOverride(date, breakTime)
- def getRealBreakTime(self, date, shiftConfigItem):
- time = self.db.getBreakTimeOverride(date)
- return time if time is not None else shiftConfigItem.breakTime
- def setAttendanceTimeOverride(self, date, attendanceTime):
- self.db.setAttendanceTimeOverride(date, attendanceTime)
- def getRealAttendanceTime(self, date, shiftConfigItem):
- time = self.db.getAttendanceTimeOverride(date)
- return time if time is not None else shiftConfigItem.attendanceTime
- def dateHasTimeOverrides(self, date):
- return self.db.hasAttendanceTimeOverride(date) or\
- self.db.hasWorkTimeOverride(date) or\
- self.db.hasBreakTimeOverride(date)
- def __calcAccountState(self, snapshot, endDate):
- shiftConfig = self.db.getShiftConfigItems()
- nrShiftConfigs = len(shiftConfig)
- state = AccountState(
- date = snapshot.date,
- shiftConfigIndex = snapshot.shiftConfigIndex,
- accountAtStartOfDay = snapshot.accountValue,
- accountAtEndOfDay = snapshot.accountValue,
- holidaysAtStartOfDay = snapshot.holidaysLeft,
- holidaysAtEndOfDay = snapshot.holidaysLeft
- )
- assert(state.date <= endDate)
- while True:
- shiftConfigItem = shiftConfig[state.shiftConfigIndex]
- currentShift = self.getRealShift(state.date, shiftConfigItem)
- workTime = self.getRealWorkTime(state.date, shiftConfigItem)
- breakTime = self.getRealBreakTime(state.date, shiftConfigItem)
- attendanceTime = self.getRealAttendanceTime(state.date, shiftConfigItem)
- dtype = self.getDayType(state.date)
- if dtype == DTYPE_DEFAULT:
- if attendanceTime > 0.001:
- state.accountAtEndOfDay += attendanceTime
- state.accountAtEndOfDay -= workTime
- state.accountAtEndOfDay -= breakTime
- elif dtype == DTYPE_COMPTIME:
- state.accountAtEndOfDay -= workTime
- elif dtype == DTYPE_HOLIDAY:
- state.holidaysAtEndOfDay -= 1
- elif dtype in (DTYPE_FEASTDAY, DTYPE_SHORTTIME):
- pass # no change
- else:
- assert(0)
- if state.date == endDate:
- break
- state.date = state.date.addDays(1)
- state.shiftConfigIndex = (state.shiftConfigIndex + 1) % nrShiftConfigs
- state.accountAtStartOfDay = state.accountAtEndOfDay
- state.holidaysAtStartOfDay = state.holidaysAtEndOfDay
- return state
- def recalculate(self):
- selDate = self.calendar.selectedDate()
- shiftConfig = self.db.getShiftConfigItems()
- if not shiftConfig:
- self.output.setText("Kein Schichtsystem konfiguriert")
- self.enableOverrideControls(False)
- return
- # First find the next snapshot.
- snapshot = self.db.findSnapshotForDate(selDate)
- if not snapshot:
- dateString = selDate.toString("dd.MM.yyyy")
- self.output.setText("Kein Schnappschuss vor dem %s gesetzt" %\
- dateString)
- self.enableOverrideControls(False)
- return
- self.enableOverrideControls(True)
- # Then calculate the account state
- accState = self.__calcAccountState(snapshot, selDate)
- shiftConfigItem = shiftConfig[accState.shiftConfigIndex]
- dtype = self.getDayType(selDate)
- shift = self.getRealShift(selDate, shiftConfigItem)
- workTime = self.getRealWorkTime(selDate, shiftConfigItem)
- breakTime = self.getRealBreakTime(selDate, shiftConfigItem)
- attendanceTime = self.getRealAttendanceTime(selDate, shiftConfigItem)
- self.overrideChangeBlocked = True
- self.typeCombo.setCurrentIndex(self.typeCombo.findData(dtype))
- self.shiftCombo.setCurrentIndex(self.shiftCombo.findData(shift))
- self.workTime.setValue(workTime)
- self.breakTime.setValue(breakTime)
- self.attendanceTime.setValue(attendanceTime)
- self.overrideChangeBlocked = False
- dateString = selDate.toString("dd.MM.yyyy")
- self.output.setText("Stand %s: %.1f h -> %.1f h U: %d d" %\
- (dateString, round(accState.accountAtStartOfDay, 1),
- round(accState.accountAtEndOfDay, 1),
- accState.holidaysAtEndOfDay))
- class MainWindow(QMainWindow):
- def __init__(self, parent=None):
- QMainWindow.__init__(self, parent)
- self.titleSuffix = None
- self.__updateTitle()
- self.setCentralWidget(MainWidget(self))
- def loadDatabase(self, filename, quiet=False):
- return self.centralWidget().doLoadDatabase(filename, quiet)
- def __updateTitle(self):
- title = "Zeitkonto"
- if self.titleSuffix:
- title += " - " + self.titleSuffix
- self.setWindowTitle(title)
- def setTitleSuffix(self, suffix):
- self.titleSuffix = suffix
- self.__updateTitle()
- def closeEvent(self, e):
- self.centralWidget().shutdown()
- def listdir(p):
- try:
- return os.listdir(p)
- except Exception as e:
- return []
- def main(argv):
- print("Using PySide: %s" % usingPySide)
- app = QApplication(argv)
- mainwnd = MainWindow()
- if len(argv) == 2 and argv[1].strip():
- if not mainwnd.loadDatabase(argv[1]):
- return 1
- else:
- if not (listdir("/mnt/sdcard") and
- mainwnd.loadDatabase("/mnt/sdcard/timeshift.tmd",
- quiet=True)):
- dbPath = str(pathlib.Path.home() / ".timeshift.tmd")
- if not mainwnd.loadDatabase(dbPath, quiet=True):
- print("Failed to load default database.", file=sys.stderr)
- mainwnd.show()
- return app.exec_()
- if __name__ == "__main__":
- sys.exit(main(sys.argv))
|