d270aae36b6f212ac716c3a9503839cc5727d96e
[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 mydays.
7 #
8 # mydays 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 # mydays 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 mydays.  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
31 from graph import DaysGraph
32 import config
33 import algo
34
35 app=None
36 win=None
37
38 class ConfigDialog(QDialog):
39     def __init__(self, *args, **kwargs):
40         QDialog.__init__(self, *args, **kwargs)
41
42         self.editName=QLineEdit(self)
43         self.editCycle=QSpinBox(self)
44         self.editCurrent=QSpinBox(self)
45         self.editCycle.setRange(10,50)
46         self.editCurrent.setRange(1,50)
47         self.editCurrent.setWrapping(True)
48         self.editCycle.setSuffix(" days")
49
50         self.editCycle.valueChanged.connect(self.slotEditCycleChanged)
51
52         self.l0=QHBoxLayout(self)
53
54         l1=QFormLayout()
55         l1.addRow("Name:", self.editName)
56         l1.addRow("Cycle length:", self.editCycle)
57         l1.addRow("Current day in cycle:", self.editCurrent)
58
59         self.l0.addLayout(l1)
60
61         spacer=QSpacerItem(20, 20, QSizePolicy.Expanding)
62         self.l0.addItem(spacer)
63
64         l2=QVBoxLayout()
65         self.l0.addLayout(l2)
66
67         self.buttonOk=QPushButton(self)
68         self.buttonOk.setText("OK")
69         self.buttonOk.clicked.connect(self.slotButOk)
70         l2.addWidget(self.buttonOk)
71
72         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
73         l2.addItem(spacer)
74
75         self.setWindowTitle("Configuration")
76
77     def slotButOk(self):
78         self.name=str(self.editName.text())
79         self.cycle=self.editCycle.value()
80         self.current=self.editCurrent.value()-1
81
82         self.accept()
83
84     def slotEditCycleChanged(self, value):
85         self.editCurrent.setMaximum(value)
86
87     # current starts from 0
88     def initValues(self, dt):
89         self.dt=dt
90         self.editName.setText(dt['name'])
91         self.editCycle.setValue(dt['cycle'])
92         self.editCurrent.setValue(dt['day0']+1)
93
94 class MyMsgDialog(QDialog):
95     """
96     A Dialog to show a finger-scrollable message
97
98     Typical usage:
99
100     class Koko(MyMsgDialog):
101         def __init__(....)
102             MyMsgDialog.__init__(....)
103
104
105             self.setWindowTitle("My title")
106     
107             l1=QLabel("koko", self.w)
108             self.l.addWidget(l1)
109             ...
110
111             self.l is a QVBoxLayout. Add everything there.
112             self.w is a QWidget. Use it as parent.
113
114     """
115     def __init__(self, *args, **kwargs):
116         QDialog.__init__(self, *args, **kwargs)
117
118         # This freaking thing is hard
119         # It needs two layouts, one extra widget, the fingerscrollable
120         # property set to true *and* setWidgetResizable(True)
121         self._mm_l0=QVBoxLayout(self)
122
123         self._mm_q=QScrollArea(self)
124         self._mm_q.setWidgetResizable(True)
125         self._mm_q.setProperty('FingerScrollable', True)
126
127         self.w=QWidget(self._mm_q)
128
129         self.l=QVBoxLayout(self.w)
130         self._mm_q.setWidget(self.w)
131         self._mm_l0.addWidget(self._mm_q)
132
133 class AboutDialog(MyMsgDialog):
134     def __init__(self, *args, **kwargs):
135         MyMsgDialog.__init__(self, *args, **kwargs)
136
137         txt="""
138 <p> A program to monitor the women's cycle.  Good for planning (or acting ;-).
139 Inspired by "MyGirls" app which is (was?) available for Java ME capable phones.
140
141 <p style="color: orange;">
142 WARNING!!! This is not accurate nor correct! You cannot trust
143 this program (or any other program) for accurate predictions!
144 (after all, this is about women... how can one be sure :-).
145
146 <p> Copyright &copy; 2010, Stefanos Harhalakis &lt;v13@v13.gr&gt;
147
148 <p> Send comments and bug reports to the above address.
149
150 <p> This program can be distributed under the terms of the GNU public
151 license, version 3 or any later.
152         """
153
154         self.setWindowTitle("About MaeGirls")
155
156         self.ltitle=QLabel("MaeGirls", self.w)
157         self.ltitle.setObjectName("title")
158         self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
159         self.ltitle.setAlignment(Qt.AlignCenter)
160         self.l.addWidget(self.ltitle)
161
162         self.label=QLabel(txt, self.w)
163         self.label.setWordWrap(True)
164         self.label.setTextFormat(Qt.RichText)
165         self.label.setAlignment(Qt.AlignJustify)
166         self.l.addWidget(self.label)
167
168         self.ltitle.setStyleSheet("""
169         QLabel {
170             font-size:      25pt;
171             color:          rgb(192,192,192);
172             margin-bottom:  0.5ex;
173             }
174         """)
175
176 class HelpDialog(MyMsgDialog):
177     def __init__(self, *args, **kwargs):
178         MyMsgDialog.__init__(self, *args, **kwargs)
179
180         txt="""
181 <p> MaeGirls shows information about women's cycle using some generic
182 guidelines.  It assumes that the ovolution happens 14 days before the start
183 of the next period and that the period cycle is constant. Also, it assumes
184 that sperm can live for 4 days, while an egg can live for 2 days.
185
186 <p style="color: orange;">
187 WARNING!!! This is not always correct. There are FAR TOO MANY exceptions
188 to the above rules!!!
189
190 <p> Assuming that you understand the risk of being wrong, you become
191 entitled to read the graph as follows:
192 <p> <span style="color: red">In red:</span> The days that menstruation
193 happens.
194 <p> <span style="color: green">In green:</span> The fertile days.
195 <p> <span style="color: blue">In blue:</span> The days of PMS
196 (Premenstrual Syndrome).
197
198 <p> Navigation is easy: Use left-right finger movement to move the calendar
199 view. Use up-down finger movement to zoom in/out.
200         """
201
202         self.setWindowTitle("Help")
203
204         self.label=QLabel(txt, self.w)
205         self.label.setWordWrap(True)
206         self.label.setTextFormat(Qt.RichText)
207         self.label.setAlignment(Qt.AlignJustify)
208         self.l.addWidget(self.label)
209
210 class GirlsDialog(QDialog):
211     def __init__(self, *args, **kwargs):
212         QDialog.__init__(self, *args, **kwargs)
213
214         self.l0=QHBoxLayout(self)
215
216         self.lstm=QStringListModel()
217         self.lst=QListView(self)
218         self.lst.setModel(self.lstm)
219
220         self.lst.setProperty("FingerScrollable", True)
221
222         self.l0.addWidget(self.lst)
223
224         self.buttonNew=QPushButton(self)
225         self.buttonNew.setText("New")
226
227         self.buttonSelect=QPushButton(self)
228         self.buttonSelect.setText("Select")
229
230         self.buttonDelete=QPushButton(self)
231         self.buttonDelete.setText("Delete")
232
233         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
234
235         self.l1=QVBoxLayout()
236         self.l0.addLayout(self.l1)
237         self.l1.addWidget(self.buttonNew)
238         self.l1.addWidget(self.buttonSelect)
239         self.l1.addWidget(self.buttonDelete)
240         self.l1.addItem(spacer)
241
242         self.buttonNew.clicked.connect(self.slotNew)
243         self.buttonDelete.clicked.connect(self.slotDelete)
244         self.buttonSelect.clicked.connect(self.slotSelect)
245
246     def _get_selection(self):
247         sel=self.lst.selectedIndexes()
248         if len(sel)==1:
249             d=sel[0]
250             ret=str(d.data().toString())
251         else:
252             ret=None
253
254         return(ret)
255
256     def exec_(self, current):
257         # Set data
258         girls=config.loadGirls()
259         dt=girls.keys()
260         dt.sort()
261         self.lstm.setStringList(dt)
262
263         self.what=""
264         self.which=None
265
266         # Set current selection
267         idx=dt.index(current)
268
269         # Either I'm doing something stupid, or this is a QT bug
270         # The selection works but isn't shown
271         idx2=self.lstm.index(idx, 0)
272         self.lst.setCurrentIndex(idx2)
273         # Give if focus to show the current selection - is this normal?
274         self.lst.setFocus(Qt.OtherFocusReason)
275
276         # Run
277         QDialog.exec_(self)
278
279     def slotNew(self):
280         self.what="new"
281         self.which=None
282         self.accept()
283
284     def slotDelete(self):
285         self.what="delete"
286         self.which=self._get_selection()
287         self.accept()
288         
289     def slotSelect(self):
290         self.what="select"
291         self.which=self._get_selection()
292         self.accept()
293
294 class MaeGirls(QMainWindow):
295     def __init__(self, algo):
296         QMainWindow.__init__(self)
297
298         self.setupUi(algo)
299
300         self.dlgConfig=ConfigDialog(self)
301         self.dlgAbout=AboutDialog(self)
302         self.dlgHelp=HelpDialog(self)
303         self.dlgGirls=None
304
305         self.algo=algo
306
307     def setupUi(self, algo):
308         self.centralwidget=QWidget(self)
309         self.setCentralWidget(self.centralwidget)
310
311         self.l0=QVBoxLayout(self.centralwidget)
312
313         self.dg=DaysGraph(algo, self.centralwidget)
314         self.l0.addWidget(self.dg)
315
316         # Menu
317         self.menuconfig=QAction('Configure', self)
318         self.menuconfig.triggered.connect(self.menuConfig)
319
320         self.menureset=QAction('Go to today', self)
321         self.menureset.triggered.connect(self.menuReset)
322
323         self.menugirls=QAction('Girls', self)
324         self.menugirls.triggered.connect(self.menuGirls)
325
326         self.menuabout=QAction('About', self)
327         self.menuabout.triggered.connect(self.menuAbout)
328
329         self.menuhelp=QAction('Help', self)
330         self.menuhelp.triggered.connect(self.menuHelp)
331
332         m=self.menuBar()
333         m.addAction(self.menuconfig)
334         m.addAction(self.menureset)
335         m.addAction(self.menugirls)
336         m.addAction(self.menuhelp)
337         m.addAction(self.menuabout)
338
339         self.setWindowTitle("MaeGirls")
340
341     def setAlgo(self, algo):
342         self.dg.setAlgo(algo)
343
344     def setGirl(self, name):
345         cfg=config.loadGirl(name)
346         self.girl=name
347         self.algo.setReference(cfg['day0'], cfg['cycle'])
348         self.repaint()
349
350     def menuConfig(self):
351         if self.dlgConfig==None:
352             self.dlgConfig=ConfigDialog(self)
353
354         dt={
355             'name':     self.girl,
356             'cycle':    self.algo.cycleLength(),
357             'day0':     self.algo.currentDayInCycle()
358             }
359
360         self.dlgConfig.initValues(dt)
361
362         ret=self.dlgConfig.exec_()
363
364         if ret==self.dlgConfig.Accepted:
365             today=algo.today()
366
367             name=self.dlgConfig.name
368             day0=today-self.dlgConfig.current
369
370             dt={
371                 'cycle':        self.dlgConfig.cycle,
372                 'day0':         day0,
373                 }
374
375             config.storeGirl(name, dt)
376             config.setCurrentGirl(name)
377
378             # If this is a rename, remove the old one
379             if self.girl!=name:
380                 config.removeGirl(self.girl)
381
382             self.setGirl(name)
383
384             self.repaint()
385
386     def menuGirls(self):
387         if self.dlgGirls==None:
388             self.dlgGirls=GirlsDialog(self)
389
390         ret=self.dlgGirls.exec_(self.girl)
391
392         what=self.dlgGirls.what
393         which=self.dlgGirls.which
394         if what=='new':
395             # Determine a unique name
396             base="newgirl"
397             idx=0
398             name=base
399             while config.girlExists(name):
400                 idx+=1
401                 name="%s%d" % (base, idx)
402             # Store this
403             config.newGirl(name)
404             # Set it as current
405             config.setCurrentGirl(name)
406             self.setGirl(name)
407             # Edit it
408             self.menuConfig()
409         elif what=='delete' and which!=None:
410             if self.girl==which:
411                 msg=QMessageBox(self)
412                 msg.setText("You cannot delete the current girl")
413                 msg.exec_()
414             else:
415                 config.removeGirl(which)
416         elif what=='select' and which!=None:
417             config.setCurrentGirl(which)
418             self.setGirl(which)
419
420     def menuAbout(self):
421         if self.dlgAbout==None:
422             self.dlgAbout=AboutDialog(self)
423
424         ret=self.dlgAbout.exec_()
425
426     def menuHelp(self):
427         if self.dlgHelp==None:
428             self.dlgHelp=HelpDialog(self)
429
430         ret=self.dlgHelp.exec_()
431
432     def menuReset(self):
433         self.dg.reset()
434
435 def init(algo):
436     global app
437     global win
438
439     app=QApplication(sys.argv)
440     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
441     win=MaeGirls(algo)
442     win.show()
443
444 def setAlgo(algo):
445     global win
446     win.setAlgo(algo)
447
448 def setGirl(name):
449     global win
450     win.setGirl(name)
451
452 def doit():
453     global app
454     app.exec_()
455
456 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
457