d436cb9b3360ec5aaec9670443fab77a5a071de3
[maegirls] / trunk / src / win.py
1 #!/usr/bin/env python
2 # coding=UTF-8
3
4 # Copyright (C) 2010 Stefanos Harhalakis
5 #
6 # This file is part of maegirls.
7 #
8 # maegirls is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # maegirls is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with maegirls.  If not, see <http://www.gnu.org/licenses/>.
20 #
21 # $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
22
23 __version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
24
25 from PyQt4.QtGui import *
26 from PyQt4.QtCore import *
27
28 import sys
29 import time
30 import locale
31
32 from graph import DaysGraph
33 import config
34 import algo
35
36 app=None
37 win=None
38
39 class ConfigDialog(QDialog):
40     def __init__(self, *args, **kwargs):
41         QDialog.__init__(self, *args, **kwargs)
42
43         self.editName=QLineEdit(self)
44         self.editCycle=QSpinBox(self)
45         self.editCurrent=QSpinBox(self)
46         self.editCycle.setRange(10,50)
47         self.editCurrent.setRange(1,50)
48         self.editCurrent.setWrapping(True)
49         self.editCycle.setSuffix(self.tr(" days"))
50
51         self.editCycle.valueChanged.connect(self.slotEditCycleChanged)
52
53         self.l0=QHBoxLayout(self)
54
55         l1=QFormLayout()
56         l1.addRow(self.tr("Name:"), self.editName)
57         l1.addRow(self.tr("Cycle length:"), self.editCycle)
58         l1.addRow(self.tr("Current day in cycle:"), self.editCurrent)
59
60         self.l0.addLayout(l1)
61
62         spacer=QSpacerItem(20, 20, QSizePolicy.Expanding)
63         self.l0.addItem(spacer)
64
65         l2=QVBoxLayout()
66         self.l0.addLayout(l2)
67
68         self.buttonOk=QPushButton(self)
69         self.buttonOk.setText(self.tr("OK"))
70         self.buttonOk.clicked.connect(self.slotButOk)
71         l2.addWidget(self.buttonOk)
72
73         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
74         l2.addItem(spacer)
75
76         self.setWindowTitle(self.tr("Configuration"))
77
78     def slotButOk(self):
79         self.name=str(self.editName.text())
80         self.cycle=self.editCycle.value()
81         self.current=self.editCurrent.value()-1
82
83         self.accept()
84
85     def slotEditCycleChanged(self, value):
86         self.editCurrent.setMaximum(value)
87
88     # current starts from 0
89     def initValues(self, dt):
90         self.dt=dt
91         self.editName.setText(dt['name'])
92         self.editCycle.setValue(dt['cycle'])
93         self.editCurrent.setValue(dt['day0']+1)
94
95 class MyMsgDialog(QDialog):
96     """
97     A Dialog to show a finger-scrollable message
98
99     Typical usage:
100
101     class Koko(MyMsgDialog):
102         def __init__(....)
103             MyMsgDialog.__init__(....)
104
105
106             self.setWindowTitle("My title")
107     
108             l1=QLabel("koko", self.w)
109             self.l.addWidget(l1)
110             ...
111
112             self.l is a QVBoxLayout. Add everything there.
113             self.w is a QWidget. Use it as parent.
114
115     """
116     def __init__(self, *args, **kwargs):
117         QDialog.__init__(self, *args, **kwargs)
118
119         # This freaking thing is hard
120         # It needs two layouts, one extra widget, the fingerscrollable
121         # property set to true *and* setWidgetResizable(True)
122         self._mm_l0=QVBoxLayout(self)
123
124         self._mm_q=QScrollArea(self)
125         self._mm_q.setWidgetResizable(True)
126         self._mm_q.setProperty('FingerScrollable', True)
127
128         self.w=QWidget(self._mm_q)
129
130         self.l=QVBoxLayout(self.w)
131         self._mm_q.setWidget(self.w)
132         self._mm_l0.addWidget(self._mm_q)
133
134 class AboutDialog(MyMsgDialog):
135     def __init__(self, *args, **kwargs):
136         MyMsgDialog.__init__(self, *args, **kwargs)
137
138         txt=self.tr("""
139 <html><style>
140 div.title {
141     text-decoration:    underline;
142 }
143 </style>
144 <body>
145 <p> A program to monitor the women's cycle.  Good for planning (or acting ;-).
146 Inspired by "MyGirls" app which is (was?) available for Java ME capable phones.
147
148 <p style="color: orange;">
149 WARNING!!! This app is not guaranteed to be accurate or correct!  You cannot 
150 trust this program, or any program, to give accurate predictions!
151 The whole women-cycle-thing highly depends on a number of factors that
152 only a doctor can tell.
153 <p>
154 <div class="title">Copyright</div>
155 <p> Copyright &copy; 2010, Stefanos Harhalakis &lt;v13@v13.gr&gt;
156
157 <p> Send comments and bug reports to the above address.
158
159 <div class="title">License</div>
160 <p> This program is free software: you can redistribute it and/or modify
161 it under the terms of the GNU General Public License as published by
162 the Free Software Foundation, either version 3 of the License, or
163 (at your option) any later version.
164 <p> This program is distributed in the hope that it will be useful,
165 but WITHOUT ANY WARRANTY; without even the implied warranty of
166 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
167 GNU General Public License for more details.
168 <p> You should have received a copy of the GNU General Public License
169 along with this program.  If not, see <http://www.gnu.org/licenses/>.
170 </body>
171 </html>
172         """)
173
174         self.setWindowTitle(self.tr("About MaeGirls"))
175
176         self.ltitle=QLabel("MaeGirls v" + config.version, self.w)
177         self.ltitle.setObjectName("title")
178         self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
179         self.ltitle.setAlignment(Qt.AlignCenter)
180         self.l.addWidget(self.ltitle)
181
182         self.label=QLabel(txt, self.w)
183         self.label.setWordWrap(True)
184         self.label.setTextFormat(Qt.RichText)
185         self.label.setAlignment(Qt.AlignJustify)
186         self.l.addWidget(self.label)
187
188         self.ltitle.setStyleSheet("""
189         QLabel {
190             font-size:      25pt;
191             color:          rgb(192,192,192);
192             margin-bottom:  0.5ex;
193             }
194         """)
195
196 class HelpDialog(MyMsgDialog):
197     def __init__(self, *args, **kwargs):
198         MyMsgDialog.__init__(self, *args, **kwargs)
199
200         txt=self.tr("""
201 <p> MaeGirls shows information about women's cycle using some generic
202 guidelines:  It assumes that the ovulation happens 14 days before the start
203 of the next period and that the period cycle is constant. Also, it assumes
204 that sperm can live for 4 days, while an egg can live for 2 days.
205
206 <p style="color: orange;">
207 WARNING!!! This is not always correct. There are FAR TOO MANY exceptions
208 to the above rules!!! You MUST consult a doctor in order to get accurate
209 predictions!!!
210
211 <p> Assuming that you understand the risks of blindly trusting this program,
212 you become entitled to read the graph as follows:
213 <p> <span style="color: red">In red:</span> The days that menstruation
214 happens, assumed to last 5 days.
215 <p> <span style="color: green">In green:</span> The fertile days as described above.
216 <p> <span style="color: blue">In blue:</span> The days of PMS
217 (Premenstrual Syndrome), assumed to last 7 days.
218
219 <p> Navigation is easy: Use left-right finger movement to move the calendar
220 view. Use up-down finger movement to zoom in/out.
221
222 <p> This program allows for "monitoring" the cycle of multiple girls.
223         """)
224
225         self.setWindowTitle(self.tr("Help"))
226
227         self.label=QLabel(txt, self.w)
228         self.label.setWordWrap(True)
229         self.label.setTextFormat(Qt.RichText)
230         self.label.setAlignment(Qt.AlignJustify)
231         self.l.addWidget(self.label)
232
233 class GirlsDialog(QDialog):
234     def __init__(self, *args, **kwargs):
235         QDialog.__init__(self, *args, **kwargs)
236
237         self.l0=QHBoxLayout(self)
238
239         self.lstm=QStringListModel()
240         self.lst=QListView(self)
241         self.lst.setModel(self.lstm)
242
243         self.lst.setProperty("FingerScrollable", True)
244
245         self.l0.addWidget(self.lst)
246
247         self.buttonNew=QPushButton(self)
248         self.buttonNew.setText(self.tr("New"))
249
250         self.buttonSelect=QPushButton(self)
251         self.buttonSelect.setText(self.tr("Select"))
252
253         self.buttonDelete=QPushButton(self)
254         self.buttonDelete.setText(self.tr("Delete"))
255
256         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
257
258         self.l1=QVBoxLayout()
259         self.l0.addLayout(self.l1)
260         self.l1.addWidget(self.buttonNew)
261         self.l1.addWidget(self.buttonSelect)
262         self.l1.addWidget(self.buttonDelete)
263         self.l1.addItem(spacer)
264
265         self.buttonNew.clicked.connect(self.slotNew)
266         self.buttonDelete.clicked.connect(self.slotDelete)
267         self.buttonSelect.clicked.connect(self.slotSelect)
268
269     def _get_selection(self):
270         sel=self.lst.selectedIndexes()
271         if len(sel)==1:
272             d=sel[0]
273             ret=str(d.data().toString())
274         else:
275             ret=None
276
277         return(ret)
278
279     def exec_(self, current):
280         # Set data
281         girls=config.loadGirls()
282         dt=girls.keys()
283         dt.sort()
284         self.lstm.setStringList(dt)
285
286         self.what=""
287         self.which=None
288
289         # Set current selection
290         idx=dt.index(current)
291
292         # Either I'm doing something stupid, or this is a QT bug
293         # The selection works but isn't shown
294         idx2=self.lstm.index(idx, 0)
295         self.lst.setCurrentIndex(idx2)
296         # Give if focus to show the current selection - is this normal?
297         self.lst.setFocus(Qt.OtherFocusReason)
298
299         # Run
300         QDialog.exec_(self)
301
302     def slotNew(self):
303         self.what="new"
304         self.which=None
305         self.accept()
306
307     def slotDelete(self):
308         self.what="delete"
309         self.which=self._get_selection()
310         self.accept()
311         
312     def slotSelect(self):
313         self.what="select"
314         self.which=self._get_selection()
315         self.accept()
316
317 class MaeGirls(QMainWindow):
318     def __init__(self, algo):
319         QMainWindow.__init__(self)
320
321         self.setupUi(algo)
322
323 #       self.dlgConfig=ConfigDialog(self)
324 #       self.dlgAbout=AboutDialog(self)
325 #       self.dlgHelp=HelpDialog(self)
326         self.dlgConfig=None
327         self.dlgAbout=None
328         self.dlgHelp=None
329         self.dlgGirls=None
330
331         self.algo=algo
332
333     def setupUi(self, algo):
334         self.centralwidget=QWidget(self)
335         self.setCentralWidget(self.centralwidget)
336
337         self.l0=QVBoxLayout(self.centralwidget)
338
339         self.dg=DaysGraph(algo, self.centralwidget)
340         self.l0.addWidget(self.dg)
341
342         # Menu
343         self.menuconfig=QAction(self.tr('Configure'), self)
344         self.menuconfig.triggered.connect(self.menuConfig)
345
346         self.menureset=QAction(self.tr('Go to today'), self)
347         self.menureset.triggered.connect(self.menuReset)
348
349         self.menugirls=QAction(self.tr('Girls'), self)
350         self.menugirls.triggered.connect(self.menuGirls)
351
352         self.menuabout=QAction(self.tr('About'), self)
353         self.menuabout.triggered.connect(self.menuAbout)
354
355         self.menuhelp=QAction(self.tr('Help'), self)
356         self.menuhelp.triggered.connect(self.menuHelp)
357
358         m=self.menuBar()
359         m.addAction(self.menureset)
360         m.addAction(self.menuconfig)
361         m.addAction(self.menugirls)
362         m.addAction(self.menuhelp)
363         m.addAction(self.menuabout)
364
365     def updateTitle(self):
366         txt="MaeGirls - %s" % (self.girl, )
367         self.setWindowTitle(txt)
368
369     def setAlgo(self, algo):
370         self.dg.setAlgo(algo)
371
372     def setGirl(self, name):
373         cfg=config.loadGirl(name)
374         self.girl=name
375         self.algo.setReference(cfg['day0'], cfg['cycle'])
376         self.update()
377         self.updateTitle()
378
379     def menuConfig(self):
380         if self.dlgConfig==None:
381             self.dlgConfig=ConfigDialog(self)
382
383         dt={
384             'name':     self.girl,
385             'cycle':    self.algo.cycleLength(),
386             'day0':     self.algo.currentDayInCycle()
387             }
388
389         self.dlgConfig.initValues(dt)
390
391         ret=self.dlgConfig.exec_()
392
393         if ret==self.dlgConfig.Accepted:
394             today=algo.today()
395
396             name=self.dlgConfig.name
397             day0=today-self.dlgConfig.current
398
399             dt={
400                 'cycle':        self.dlgConfig.cycle,
401                 'day0':         day0,
402                 }
403
404             config.storeGirl(name, dt)
405             config.setCurrentGirl(name)
406
407             # If this is a rename, remove the old one
408             if self.girl!=name:
409                 config.removeGirl(self.girl)
410
411             self.setGirl(name)
412
413             self.update()
414
415     def menuGirls(self):
416         if self.dlgGirls==None:
417             self.dlgGirls=GirlsDialog(self)
418
419         ret=self.dlgGirls.exec_(self.girl)
420
421         what=self.dlgGirls.what
422         which=self.dlgGirls.which
423         if what=='new':
424             # Determine a unique name
425             base="newgirl"
426             idx=0
427             name=base
428             while config.girlExists(name):
429                 idx+=1
430                 name="%s%d" % (base, idx)
431             # Store this
432             config.newGirl(name)
433             # Set it as current
434             config.setCurrentGirl(name)
435             self.setGirl(name)
436             # Edit it
437             self.menuConfig()
438         elif what=='delete' and which!=None:
439             if self.girl==which:
440                 msg=QMessageBox(self)
441                 msg.setText(self.tr('You cannot delete the current girl'))
442                 msg.exec_()
443             else:
444                 config.removeGirl(which)
445         elif what=='select' and which!=None:
446             config.setCurrentGirl(which)
447             self.setGirl(which)
448
449     def menuAbout(self):
450         if self.dlgAbout==None:
451             self.dlgAbout=AboutDialog(self)
452
453         ret=self.dlgAbout.exec_()
454
455     def menuHelp(self):
456         if self.dlgHelp==None:
457             self.dlgHelp=HelpDialog(self)
458
459         ret=self.dlgHelp.exec_()
460
461     def menuReset(self):
462         self.dg.reset()
463
464 def init(algo):
465     global app
466     global win
467     global qttr, maetr
468
469     # Create the application
470     app=QApplication(sys.argv)
471
472     # This returns the country and *NOT* the required locale
473     # This means that if you have'set language==en_GB and countr==Greece
474     # it will return el_GR.
475     # IOW: If you want english messages this will not work
476     #loc=QLocale.system().name()
477
478     loc=locale.setlocale(locale.LC_MESSAGES, '')
479
480     # Load translations
481     qttr=QTranslator()
482     qttr.load("qt_" + loc,
483         QLibraryInfo.location(QLibraryInfo.TranslationsPath))
484     app.installTranslator(qttr)
485
486     maetr=QTranslator()
487     maetr.load("maegirls_" + loc,
488         "/usr/share/maegirls/translations")
489
490     # Install the translation
491     app.installTranslator(maetr)
492
493     # One day support portrait mode
494     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
495
496     # Create the main window
497     win=MaeGirls(algo)
498     win.show()
499
500 def setAlgo(algo):
501     global win
502     win.setAlgo(algo)
503
504 def setGirl(name):
505     global win
506     win.setGirl(name)
507
508 def doit():
509     global app
510     app.exec_()
511
512 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
513