b90d3e9b5aad9969ff79fb65190c0d3a1fb28941
[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.l0.addWidget(self.lst)
221
222         self.buttonNew=QPushButton(self)
223         self.buttonNew.setText("New")
224
225         self.buttonSelect=QPushButton(self)
226         self.buttonSelect.setText("Select")
227
228         self.buttonDelete=QPushButton(self)
229         self.buttonDelete.setText("Delete")
230
231         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
232
233         self.l1=QVBoxLayout()
234         self.l0.addLayout(self.l1)
235         self.l1.addWidget(self.buttonNew)
236         self.l1.addWidget(self.buttonSelect)
237         self.l1.addWidget(self.buttonDelete)
238         self.l1.addItem(spacer)
239
240         self.buttonNew.clicked.connect(self.slotNew)
241         self.buttonDelete.clicked.connect(self.slotDelete)
242         self.buttonSelect.clicked.connect(self.slotSelect)
243
244     def _get_selection(self):
245         sel=self.lst.selectedIndexes()
246         if len(sel)==1:
247             d=sel[0]
248             ret=str(d.data().toString())
249         else:
250             ret=None
251
252         return(ret)
253
254     def exec_(self, current):
255         # Set data
256         girls=config.loadGirls()
257         dt=girls.keys()
258         dt.sort()
259         self.lstm.setStringList(dt)
260
261         self.what=""
262         self.which=None
263
264         # Set current selection
265         idx=dt.index(current)
266         self.lst.setCurrentIndex(self.lstm.index(idx))
267
268         # Run
269         QDialog.exec_(self)
270
271     def slotNew(self):
272         self.what="new"
273         self.which=None
274         self.accept()
275
276     def slotDelete(self):
277         self.what="delete"
278         self.which=self._get_selection()
279         self.accept()
280         
281     def slotSelect(self):
282         self.what="select"
283         self.which=self._get_selection()
284         self.accept()
285
286 class MaeGirls(QMainWindow):
287     def __init__(self, algo):
288         QMainWindow.__init__(self)
289
290         self.setupUi(algo)
291
292         self.dlgConfig=ConfigDialog(self)
293         self.dlgAbout=AboutDialog(self)
294         self.dlgHelp=HelpDialog(self)
295         self.dlgGirls=None
296
297         self.algo=algo
298
299     def setupUi(self, algo):
300         self.centralwidget=QWidget(self)
301         self.setCentralWidget(self.centralwidget)
302
303         self.l0=QVBoxLayout(self.centralwidget)
304
305         self.dg=DaysGraph(algo, self.centralwidget)
306         self.l0.addWidget(self.dg)
307
308         # Menu
309         self.menuconfig=QAction('Configure', self)
310         self.menuconfig.triggered.connect(self.menuConfig)
311
312         self.menureset=QAction('Go to today', self)
313         self.menureset.triggered.connect(self.menuReset)
314
315         self.menugirls=QAction('Girls', self)
316         self.menugirls.triggered.connect(self.menuGirls)
317
318         self.menuabout=QAction('About', self)
319         self.menuabout.triggered.connect(self.menuAbout)
320
321         self.menuhelp=QAction('Help', self)
322         self.menuhelp.triggered.connect(self.menuHelp)
323
324         m=self.menuBar()
325         m.addAction(self.menuconfig)
326         m.addAction(self.menureset)
327         m.addAction(self.menugirls)
328         m.addAction(self.menuhelp)
329         m.addAction(self.menuabout)
330
331         self.setWindowTitle("MaeGirls")
332
333     def setAlgo(self, algo):
334         self.dg.setAlgo(algo)
335
336     def setGirl(self, name):
337         cfg=config.loadGirl(name)
338         self.girl=name
339         self.algo.setReference(cfg['day0'], cfg['cycle'])
340         self.repaint()
341
342     def menuConfig(self):
343         if self.dlgConfig==None:
344             self.dlgConfig=ConfigDialog(self)
345
346         dt={
347             'name':     self.girl,
348             'cycle':    self.algo.cycleLength(),
349             'day0':     self.algo.currentDayInCycle()
350             }
351
352         self.dlgConfig.initValues(dt)
353
354         ret=self.dlgConfig.exec_()
355
356         if ret==self.dlgConfig.Accepted:
357             today=algo.today()
358
359             name=self.dlgConfig.name
360             day0=today-self.dlgConfig.current
361
362             dt={
363                 'cycle':        self.dlgConfig.cycle,
364                 'day0':         day0,
365                 }
366
367             config.storeGirl(name, dt)
368             config.setCurrentGirl(name)
369
370             # If this is a rename, remove the old one
371             if self.girl!=name:
372                 config.removeGirl(self.girl)
373
374             self.setGirl(name)
375
376             self.repaint()
377
378     def menuGirls(self):
379         if self.dlgGirls==None:
380             self.dlgGirls=GirlsDialog(self)
381
382         ret=self.dlgGirls.exec_(self.girl)
383
384         what=self.dlgGirls.what
385         which=self.dlgGirls.which
386         if what=='new':
387             # Determine a unique name
388             base="newgirl"
389             idx=0
390             name=base
391             while config.girlExists(name):
392                 idx+=1
393                 name="%s%d" % (base, idx)
394             # Store this
395             config.newGirl(name)
396             # Set it as current
397             config.setCurrentGirl(name)
398             self.setGirl(name)
399             # Edit it
400             self.menuConfig()
401         elif what=='delete' and which!=None:
402             if self.girl==which:
403                 msg=QMessageBox(self)
404                 msg.setText("You cannot delete the current girl")
405                 msg.exec_()
406             else:
407                 config.removeGirl(which)
408         elif what=='select' and which!=None:
409             config.setCurrentGirl(which)
410             self.setGirl(which)
411
412     def menuAbout(self):
413         if self.dlgAbout==None:
414             self.dlgAbout=AboutDialog(self)
415
416         ret=self.dlgAbout.exec_()
417
418     def menuHelp(self):
419         if self.dlgHelp==None:
420             self.dlgHelp=HelpDialog(self)
421
422         ret=self.dlgHelp.exec_()
423
424     def menuReset(self):
425         self.dg.reset()
426
427 def init(algo):
428     global app
429     global win
430
431     app=QApplication(sys.argv)
432     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
433     win=MaeGirls(algo)
434     win.show()
435
436 def setAlgo(algo):
437     global win
438     win.setAlgo(algo)
439
440 def setGirl(name):
441     global win
442     win.setGirl(name)
443
444 def doit():
445     global app
446     app.exec_()
447
448 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
449