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