v0.1.0
[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 <p> A program to monitor the women's cycle.  Good for planning (or acting ;-).
140 Inspired by "MyGirls" app which is (was?) available for Java ME capable phones.
141
142 <p style="color: orange;">
143 WARNING!!! This is not accurate nor correct! You cannot trust
144 this program (or any other program) for accurate predictions!
145 (after all, this is about women... how can one be sure :-).
146
147 <p> Copyright &copy; 2010, Stefanos Harhalakis &lt;v13@v13.gr&gt;
148
149 <p> Send comments and bug reports to the above address.
150
151 <p> This program can be distributed under the terms of the GNU public
152 license, version 3 or any later.
153         """)
154
155         self.setWindowTitle(self.tr("About MaeGirls"))
156
157         self.ltitle=QLabel("MaeGirls v" + config.version, self.w)
158         self.ltitle.setObjectName("title")
159         self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
160         self.ltitle.setAlignment(Qt.AlignCenter)
161         self.l.addWidget(self.ltitle)
162
163         self.label=QLabel(txt, self.w)
164         self.label.setWordWrap(True)
165         self.label.setTextFormat(Qt.RichText)
166         self.label.setAlignment(Qt.AlignJustify)
167         self.l.addWidget(self.label)
168
169         self.ltitle.setStyleSheet("""
170         QLabel {
171             font-size:      25pt;
172             color:          rgb(192,192,192);
173             margin-bottom:  0.5ex;
174             }
175         """)
176
177 class HelpDialog(MyMsgDialog):
178     def __init__(self, *args, **kwargs):
179         MyMsgDialog.__init__(self, *args, **kwargs)
180
181         txt=self.tr("""
182 <p> MaeGirls shows information about women's cycle using some generic
183 guidelines.  It assumes that the ovulation happens 14 days before the start
184 of the next period and that the period cycle is constant. Also, it assumes
185 that sperm can live for 4 days, while an egg can live for 2 days.
186
187 <p style="color: orange;">
188 WARNING!!! This is not always correct. There are FAR TOO MANY exceptions
189 to the above rules!!!
190
191 <p> Assuming that you understand the risk of being wrong, you become
192 entitled to read the graph as follows:
193 <p> <span style="color: red">In red:</span> The days that menstruation
194 happens.
195 <p> <span style="color: green">In green:</span> The fertile days.
196 <p> <span style="color: blue">In blue:</span> The days of PMS
197 (Premenstrual Syndrome).
198
199 <p> Navigation is easy: Use left-right finger movement to move the calendar
200 view. Use up-down finger movement to zoom in/out.
201         """)
202
203         self.setWindowTitle(self.tr("Help"))
204
205         self.label=QLabel(txt, self.w)
206         self.label.setWordWrap(True)
207         self.label.setTextFormat(Qt.RichText)
208         self.label.setAlignment(Qt.AlignJustify)
209         self.l.addWidget(self.label)
210
211 class GirlsDialog(QDialog):
212     def __init__(self, *args, **kwargs):
213         QDialog.__init__(self, *args, **kwargs)
214
215         self.l0=QHBoxLayout(self)
216
217         self.lstm=QStringListModel()
218         self.lst=QListView(self)
219         self.lst.setModel(self.lstm)
220
221         self.lst.setProperty("FingerScrollable", True)
222
223         self.l0.addWidget(self.lst)
224
225         self.buttonNew=QPushButton(self)
226         self.buttonNew.setText(self.tr("New"))
227
228         self.buttonSelect=QPushButton(self)
229         self.buttonSelect.setText(self.tr("Select"))
230
231         self.buttonDelete=QPushButton(self)
232         self.buttonDelete.setText(self.tr("Delete"))
233
234         spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
235
236         self.l1=QVBoxLayout()
237         self.l0.addLayout(self.l1)
238         self.l1.addWidget(self.buttonNew)
239         self.l1.addWidget(self.buttonSelect)
240         self.l1.addWidget(self.buttonDelete)
241         self.l1.addItem(spacer)
242
243         self.buttonNew.clicked.connect(self.slotNew)
244         self.buttonDelete.clicked.connect(self.slotDelete)
245         self.buttonSelect.clicked.connect(self.slotSelect)
246
247     def _get_selection(self):
248         sel=self.lst.selectedIndexes()
249         if len(sel)==1:
250             d=sel[0]
251             ret=str(d.data().toString())
252         else:
253             ret=None
254
255         return(ret)
256
257     def exec_(self, current):
258         # Set data
259         girls=config.loadGirls()
260         dt=girls.keys()
261         dt.sort()
262         self.lstm.setStringList(dt)
263
264         self.what=""
265         self.which=None
266
267         # Set current selection
268         idx=dt.index(current)
269
270         # Either I'm doing something stupid, or this is a QT bug
271         # The selection works but isn't shown
272         idx2=self.lstm.index(idx, 0)
273         self.lst.setCurrentIndex(idx2)
274         # Give if focus to show the current selection - is this normal?
275         self.lst.setFocus(Qt.OtherFocusReason)
276
277         # Run
278         QDialog.exec_(self)
279
280     def slotNew(self):
281         self.what="new"
282         self.which=None
283         self.accept()
284
285     def slotDelete(self):
286         self.what="delete"
287         self.which=self._get_selection()
288         self.accept()
289         
290     def slotSelect(self):
291         self.what="select"
292         self.which=self._get_selection()
293         self.accept()
294
295 class MaeGirls(QMainWindow):
296     def __init__(self, algo):
297         QMainWindow.__init__(self)
298
299         self.setupUi(algo)
300
301 #       self.dlgConfig=ConfigDialog(self)
302 #       self.dlgAbout=AboutDialog(self)
303 #       self.dlgHelp=HelpDialog(self)
304         self.dlgConfig=None
305         self.dlgAbout=None
306         self.dlgHelp=None
307         self.dlgGirls=None
308
309         self.algo=algo
310
311     def setupUi(self, algo):
312         self.centralwidget=QWidget(self)
313         self.setCentralWidget(self.centralwidget)
314
315         self.l0=QVBoxLayout(self.centralwidget)
316
317         self.dg=DaysGraph(algo, self.centralwidget)
318         self.l0.addWidget(self.dg)
319
320         # Menu
321         self.menuconfig=QAction(self.tr('Configure'), self)
322         self.menuconfig.triggered.connect(self.menuConfig)
323
324         self.menureset=QAction(self.tr('Go to today'), self)
325         self.menureset.triggered.connect(self.menuReset)
326
327         self.menugirls=QAction(self.tr('Girls'), self)
328         self.menugirls.triggered.connect(self.menuGirls)
329
330         self.menuabout=QAction(self.tr('About'), self)
331         self.menuabout.triggered.connect(self.menuAbout)
332
333         self.menuhelp=QAction(self.tr('Help'), self)
334         self.menuhelp.triggered.connect(self.menuHelp)
335
336         m=self.menuBar()
337         m.addAction(self.menureset)
338         m.addAction(self.menuconfig)
339         m.addAction(self.menugirls)
340         m.addAction(self.menuhelp)
341         m.addAction(self.menuabout)
342
343         self.setWindowTitle("MaeGirls")
344
345     def setAlgo(self, algo):
346         self.dg.setAlgo(algo)
347
348     def setGirl(self, name):
349         cfg=config.loadGirl(name)
350         self.girl=name
351         self.algo.setReference(cfg['day0'], cfg['cycle'])
352         self.repaint()
353
354     def menuConfig(self):
355         if self.dlgConfig==None:
356             self.dlgConfig=ConfigDialog(self)
357
358         dt={
359             'name':     self.girl,
360             'cycle':    self.algo.cycleLength(),
361             'day0':     self.algo.currentDayInCycle()
362             }
363
364         self.dlgConfig.initValues(dt)
365
366         ret=self.dlgConfig.exec_()
367
368         if ret==self.dlgConfig.Accepted:
369             today=algo.today()
370
371             name=self.dlgConfig.name
372             day0=today-self.dlgConfig.current
373
374             dt={
375                 'cycle':        self.dlgConfig.cycle,
376                 'day0':         day0,
377                 }
378
379             config.storeGirl(name, dt)
380             config.setCurrentGirl(name)
381
382             # If this is a rename, remove the old one
383             if self.girl!=name:
384                 config.removeGirl(self.girl)
385
386             self.setGirl(name)
387
388             self.repaint()
389
390     def menuGirls(self):
391         if self.dlgGirls==None:
392             self.dlgGirls=GirlsDialog(self)
393
394         ret=self.dlgGirls.exec_(self.girl)
395
396         what=self.dlgGirls.what
397         which=self.dlgGirls.which
398         if what=='new':
399             # Determine a unique name
400             base="newgirl"
401             idx=0
402             name=base
403             while config.girlExists(name):
404                 idx+=1
405                 name="%s%d" % (base, idx)
406             # Store this
407             config.newGirl(name)
408             # Set it as current
409             config.setCurrentGirl(name)
410             self.setGirl(name)
411             # Edit it
412             self.menuConfig()
413         elif what=='delete' and which!=None:
414             if self.girl==which:
415                 msg=QMessageBox(self)
416                 msg.setText(self.tr('You cannot delete the current girl'))
417                 msg.exec_()
418             else:
419                 config.removeGirl(which)
420         elif what=='select' and which!=None:
421             config.setCurrentGirl(which)
422             self.setGirl(which)
423
424     def menuAbout(self):
425         if self.dlgAbout==None:
426             self.dlgAbout=AboutDialog(self)
427
428         ret=self.dlgAbout.exec_()
429
430     def menuHelp(self):
431         if self.dlgHelp==None:
432             self.dlgHelp=HelpDialog(self)
433
434         ret=self.dlgHelp.exec_()
435
436     def menuReset(self):
437         self.dg.reset()
438
439 def init(algo):
440     global app
441     global win
442     global qttr, maetr
443
444     # Create the application
445     app=QApplication(sys.argv)
446
447     # This returns the country and *NOT* the required locale
448     # This means that if you have'set language==en_GB and countr==Greece
449     # it will return el_GR.
450     # IOW: If you want english messages this will not work
451     #loc=QLocale.system().name()
452
453     loc=locale.setlocale(locale.LC_MESSAGES, '')
454
455     print "loc:", loc
456
457     # Load translations
458     qttr=QTranslator()
459     qttr.load("qt_" + loc,
460         QLibraryInfo.location(QLibraryInfo.TranslationsPath))
461     app.installTranslator(qttr)
462
463     maetr=QTranslator()
464     maetr.load("maegirls_" + loc,
465         "/usr/share/maegirls/translations")
466
467     # Install the translation
468     app.installTranslator(maetr)
469
470     # One day support portrait mode
471     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
472
473     # Create the main window
474     win=MaeGirls(algo)
475     win.show()
476
477 def setAlgo(algo):
478     global win
479     win.setAlgo(algo)
480
481 def setGirl(name):
482     global win
483     win.setGirl(name)
484
485 def doit():
486     global app
487     app.exec_()
488
489 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
490