ee61857ef2e949b016464df6ceed7b648f575121
[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
170 <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
171 </body>
172 </html>
173         """)
174
175         self.setWindowTitle(self.tr("About MaeGirls"))
176
177         self.ltitle=QLabel("MaeGirls v" + config.version, self.w)
178         self.ltitle.setObjectName("title")
179         self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
180         self.ltitle.setAlignment(Qt.AlignCenter)
181         self.l.addWidget(self.ltitle)
182
183         self.label=QLabel(txt, self.w)
184         self.label.setWordWrap(True)
185         self.label.setTextFormat(Qt.RichText)
186         self.label.setAlignment(Qt.AlignJustify)
187         self.l.addWidget(self.label)
188
189         self.ltitle.setStyleSheet("""
190         QLabel {
191             font-size:      25pt;
192             color:          rgb(192,192,192);
193             margin-bottom:  0.5ex;
194             }
195         """)
196
197 class HelpDialog(MyMsgDialog):
198     def __init__(self, *args, **kwargs):
199         MyMsgDialog.__init__(self, *args, **kwargs)
200
201         txt=self.tr("""
202 <p> MaeGirls shows information about women's cycle using some generic
203 guidelines:  It assumes that ovulation happens 14 days before the start
204 of the next period and that the period cycle is constant. Also, it assumes
205 that sperm can live for 4 days, while an egg can live for 2 days.
206
207 <p style="color: orange;">
208 WARNING!!! This is not always correct. There are FAR TOO MANY exceptions
209 to the above rules!!! You MUST consult a doctor in order to get accurate
210 predictions!!!
211
212 <p> Assuming that you understand the risks of blindly trusting this program,
213 you become entitled to read the graph as follows:
214 <p> <span style="color: red">In red:</span> The days that menstruation
215 happens, assumed to last 5 days.
216 <p> <span style="color: green">In green:</span> The fertile days as described above.
217 <p> <span style="color: blue">In blue:</span> The days of PMS
218 (Premenstrual Syndrome), assumed to last 7 days.
219
220 <p> Navigation is easy: Use left-right finger movement to move the calendar
221 view. Use up-down finger movement to zoom in/out.
222
223 <p> This program allows for "monitoring" the cycle of multiple girls.
224         """)
225
226         self.setWindowTitle(self.tr("Help"))
227
228         self.label=QLabel(txt, self.w)
229         self.label.setWordWrap(True)
230         self.label.setTextFormat(Qt.RichText)
231         self.label.setAlignment(Qt.AlignJustify)
232         self.l.addWidget(self.label)
233
234 class GirlsDialog(QDialog):
235     def __init__(self, *args, **kwargs):
236         QDialog.__init__(self, *args, **kwargs)
237
238         self.l0=QHBoxLayout(self)
239
240         self.lstm=QStringListModel()
241         self.lst=QListView(self)
242         self.lst.setModel(self.lstm)
243
244         self.lst.setProperty("FingerScrollable", True)
245
246         self.l0.addWidget(self.lst)
247
248         self.buttonNew=QPushButton(self)
249         self.buttonNew.setText(self.tr("New"))
250
251         self.buttonSelect=QPushButton(self)
252         self.buttonSelect.setText(self.tr("Select"))
253
254         self.buttonDelete=QPushButton(self)
255         self.buttonDelete.setText(self.tr("Delete"))
256
257         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
258
259         self.l1=QVBoxLayout()
260         self.l0.addLayout(self.l1)
261         self.l1.addWidget(self.buttonNew)
262         self.l1.addWidget(self.buttonSelect)
263         self.l1.addWidget(self.buttonDelete)
264         self.l1.addItem(spacer)
265
266         self.buttonNew.clicked.connect(self.slotNew)
267         self.buttonDelete.clicked.connect(self.slotDelete)
268         self.buttonSelect.clicked.connect(self.slotSelect)
269
270     def _get_selection(self):
271         sel=self.lst.selectedIndexes()
272         if len(sel)==1:
273             d=sel[0]
274             ret=str(d.data().toString())
275         else:
276             ret=None
277
278         return(ret)
279
280     def exec_(self, current):
281         # Set data
282         girls=config.loadGirls()
283         dt=girls.keys()
284         dt.sort()
285         self.lstm.setStringList(dt)
286
287         self.what=""
288         self.which=None
289
290         # Set current selection
291         idx=dt.index(current)
292
293         # Either I'm doing something stupid, or this is a QT bug
294         # The selection works but isn't shown
295         idx2=self.lstm.index(idx, 0)
296         self.lst.setCurrentIndex(idx2)
297         # Give if focus to show the current selection - is this normal?
298         self.lst.setFocus(Qt.OtherFocusReason)
299
300         # Run
301         QDialog.exec_(self)
302
303     def slotNew(self):
304         self.what="new"
305         self.which=None
306         self.accept()
307
308     def slotDelete(self):
309         self.what="delete"
310         self.which=self._get_selection()
311         self.accept()
312         
313     def slotSelect(self):
314         self.what="select"
315         self.which=self._get_selection()
316         self.accept()
317
318 class MaeGirls(QMainWindow):
319     def __init__(self, algo):
320         QMainWindow.__init__(self)
321
322         self.setupUi(algo)
323
324 #       self.dlgConfig=ConfigDialog(self)
325 #       self.dlgAbout=AboutDialog(self)
326 #       self.dlgHelp=HelpDialog(self)
327         self.dlgConfig=None
328         self.dlgAbout=None
329         self.dlgHelp=None
330         self.dlgGirls=None
331
332         self.algo=algo
333
334     def setupUi(self, algo):
335         self.centralwidget=QWidget(self)
336         self.setCentralWidget(self.centralwidget)
337
338         self.l0=QVBoxLayout(self.centralwidget)
339
340         self.dg=DaysGraph(algo, self.centralwidget)
341         self.l0.addWidget(self.dg)
342
343         # Menu
344         self.menuconfig=QAction(self.tr('Configure'), self)
345         self.menuconfig.triggered.connect(self.menuConfig)
346
347         self.menureset=QAction(self.tr('Go to today'), self)
348         self.menureset.triggered.connect(self.menuReset)
349
350         self.menugirls=QAction(self.tr('Girls'), self)
351         self.menugirls.triggered.connect(self.menuGirls)
352
353         self.menuabout=QAction(self.tr('About'), self)
354         self.menuabout.triggered.connect(self.menuAbout)
355
356         self.menuhelp=QAction(self.tr('Help'), self)
357         self.menuhelp.triggered.connect(self.menuHelp)
358
359         m=self.menuBar()
360         m.addAction(self.menureset)
361         m.addAction(self.menuconfig)
362         m.addAction(self.menugirls)
363         m.addAction(self.menuhelp)
364         m.addAction(self.menuabout)
365
366     def updateTitle(self):
367         txt="MaeGirls - %s" % (self.girl, )
368         self.setWindowTitle(txt)
369
370     def setAlgo(self, algo):
371         self.dg.setAlgo(algo)
372
373     def setGirl(self, name):
374         cfg=config.loadGirl(name)
375         self.girl=name
376         self.algo.setReference(cfg['day0'], cfg['cycle'])
377         self.update()
378         self.updateTitle()
379
380     def menuConfig(self):
381         if self.dlgConfig==None:
382             self.dlgConfig=ConfigDialog(self)
383
384         dt={
385             'name':     self.girl,
386             'cycle':    self.algo.cycleLength(),
387             'day0':     self.algo.currentDayInCycle()
388             }
389
390         self.dlgConfig.initValues(dt)
391
392         ret=self.dlgConfig.exec_()
393
394         if ret==self.dlgConfig.Accepted:
395             today=algo.today()
396
397             name=self.dlgConfig.name
398             day0=today-self.dlgConfig.current
399
400             dt={
401                 'cycle':        self.dlgConfig.cycle,
402                 'day0':         day0,
403                 }
404
405             config.storeGirl(name, dt)
406             config.setCurrentGirl(name)
407
408             # If this is a rename, remove the old one
409             if self.girl!=name:
410                 config.removeGirl(self.girl)
411
412             self.setGirl(name)
413
414             self.update()
415
416     def menuGirls(self):
417         if self.dlgGirls==None:
418             self.dlgGirls=GirlsDialog(self)
419
420         ret=self.dlgGirls.exec_(self.girl)
421
422         what=self.dlgGirls.what
423         which=self.dlgGirls.which
424         if what=='new':
425             # Determine a unique name
426             base="newgirl"
427             idx=0
428             name=base
429             while config.girlExists(name):
430                 idx+=1
431                 name="%s%d" % (base, idx)
432             # Store this
433             config.newGirl(name)
434             # Set it as current
435             config.setCurrentGirl(name)
436             self.setGirl(name)
437             # Edit it
438             self.menuConfig()
439         elif what=='delete' and which!=None:
440             if self.girl==which:
441                 msg=QMessageBox(self)
442                 msg.setText(self.tr('You cannot delete the current girl'))
443                 msg.exec_()
444             else:
445                 config.removeGirl(which)
446         elif what=='select' and which!=None:
447             config.setCurrentGirl(which)
448             self.setGirl(which)
449
450     def menuAbout(self):
451         if self.dlgAbout==None:
452             self.dlgAbout=AboutDialog(self)
453
454         ret=self.dlgAbout.exec_()
455
456     def menuHelp(self):
457         if self.dlgHelp==None:
458             self.dlgHelp=HelpDialog(self)
459
460         ret=self.dlgHelp.exec_()
461
462     def menuReset(self):
463         self.dg.reset()
464
465 def init(algo):
466     global app
467     global win
468     global qttr, maetr
469
470     # Create the application
471     app=QApplication(sys.argv)
472
473     # This returns the country and *NOT* the required locale
474     # This means that if you have'set language==en_GB and countr==Greece
475     # it will return el_GR.
476     # IOW: If you want english messages this will not work
477     #loc=QLocale.system().name()
478
479     loc=locale.setlocale(locale.LC_MESSAGES, '')
480
481     # Load translations
482     qttr=QTranslator()
483     qttr.load("qt_" + loc,
484         QLibraryInfo.location(QLibraryInfo.TranslationsPath))
485     app.installTranslator(qttr)
486
487     maetr=QTranslator()
488     maetr.load("maegirls_" + loc,
489         "/usr/share/maegirls/translations")
490
491     # Install the translation
492     app.installTranslator(maetr)
493
494     # One day support portrait mode
495     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
496
497     # Create the main window
498     win=MaeGirls(algo)
499     win.show()
500
501 def setAlgo(algo):
502     global win
503     win.setAlgo(algo)
504
505 def setGirl(name):
506     global win
507     win.setGirl(name)
508
509 def doit():
510     global app
511     app.exec_()
512
513 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
514