Make the girl selection finger-scrollable.
[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
274         # Run
275         QDialog.exec_(self)
276
277     def slotNew(self):
278         self.what="new"
279         self.which=None
280         self.accept()
281
282     def slotDelete(self):
283         self.what="delete"
284         self.which=self._get_selection()
285         self.accept()
286         
287     def slotSelect(self):
288         self.what="select"
289         self.which=self._get_selection()
290         self.accept()
291
292 class MaeGirls(QMainWindow):
293     def __init__(self, algo):
294         QMainWindow.__init__(self)
295
296         self.setupUi(algo)
297
298         self.dlgConfig=ConfigDialog(self)
299         self.dlgAbout=AboutDialog(self)
300         self.dlgHelp=HelpDialog(self)
301         self.dlgGirls=None
302
303         self.algo=algo
304
305     def setupUi(self, algo):
306         self.centralwidget=QWidget(self)
307         self.setCentralWidget(self.centralwidget)
308
309         self.l0=QVBoxLayout(self.centralwidget)
310
311         self.dg=DaysGraph(algo, self.centralwidget)
312         self.l0.addWidget(self.dg)
313
314         # Menu
315         self.menuconfig=QAction('Configure', self)
316         self.menuconfig.triggered.connect(self.menuConfig)
317
318         self.menureset=QAction('Go to today', self)
319         self.menureset.triggered.connect(self.menuReset)
320
321         self.menugirls=QAction('Girls', self)
322         self.menugirls.triggered.connect(self.menuGirls)
323
324         self.menuabout=QAction('About', self)
325         self.menuabout.triggered.connect(self.menuAbout)
326
327         self.menuhelp=QAction('Help', self)
328         self.menuhelp.triggered.connect(self.menuHelp)
329
330         m=self.menuBar()
331         m.addAction(self.menuconfig)
332         m.addAction(self.menureset)
333         m.addAction(self.menugirls)
334         m.addAction(self.menuhelp)
335         m.addAction(self.menuabout)
336
337         self.setWindowTitle("MaeGirls")
338
339     def setAlgo(self, algo):
340         self.dg.setAlgo(algo)
341
342     def setGirl(self, name):
343         cfg=config.loadGirl(name)
344         self.girl=name
345         self.algo.setReference(cfg['day0'], cfg['cycle'])
346         self.repaint()
347
348     def menuConfig(self):
349         if self.dlgConfig==None:
350             self.dlgConfig=ConfigDialog(self)
351
352         dt={
353             'name':     self.girl,
354             'cycle':    self.algo.cycleLength(),
355             'day0':     self.algo.currentDayInCycle()
356             }
357
358         self.dlgConfig.initValues(dt)
359
360         ret=self.dlgConfig.exec_()
361
362         if ret==self.dlgConfig.Accepted:
363             today=algo.today()
364
365             name=self.dlgConfig.name
366             day0=today-self.dlgConfig.current
367
368             dt={
369                 'cycle':        self.dlgConfig.cycle,
370                 'day0':         day0,
371                 }
372
373             config.storeGirl(name, dt)
374             config.setCurrentGirl(name)
375
376             # If this is a rename, remove the old one
377             if self.girl!=name:
378                 config.removeGirl(self.girl)
379
380             self.setGirl(name)
381
382             self.repaint()
383
384     def menuGirls(self):
385         if self.dlgGirls==None:
386             self.dlgGirls=GirlsDialog(self)
387
388         ret=self.dlgGirls.exec_(self.girl)
389
390         what=self.dlgGirls.what
391         which=self.dlgGirls.which
392         if what=='new':
393             # Determine a unique name
394             base="newgirl"
395             idx=0
396             name=base
397             while config.girlExists(name):
398                 idx+=1
399                 name="%s%d" % (base, idx)
400             # Store this
401             config.newGirl(name)
402             # Set it as current
403             config.setCurrentGirl(name)
404             self.setGirl(name)
405             # Edit it
406             self.menuConfig()
407         elif what=='delete' and which!=None:
408             if self.girl==which:
409                 msg=QMessageBox(self)
410                 msg.setText("You cannot delete the current girl")
411                 msg.exec_()
412             else:
413                 config.removeGirl(which)
414         elif what=='select' and which!=None:
415             config.setCurrentGirl(which)
416             self.setGirl(which)
417
418     def menuAbout(self):
419         if self.dlgAbout==None:
420             self.dlgAbout=AboutDialog(self)
421
422         ret=self.dlgAbout.exec_()
423
424     def menuHelp(self):
425         if self.dlgHelp==None:
426             self.dlgHelp=HelpDialog(self)
427
428         ret=self.dlgHelp.exec_()
429
430     def menuReset(self):
431         self.dg.reset()
432
433 def init(algo):
434     global app
435     global win
436
437     app=QApplication(sys.argv)
438     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
439     win=MaeGirls(algo)
440     win.show()
441
442 def setAlgo(algo):
443     global win
444     win.setAlgo(algo)
445
446 def setGirl(name):
447     global win
448     win.setGirl(name)
449
450 def doit():
451     global app
452     app.exec_()
453
454 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
455