Fixed link in about box.
[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 the 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