Added version information in about.
[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
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(self.tr(" days"))
49
50         self.editCycle.valueChanged.connect(self.slotEditCycleChanged)
51
52         self.l0=QHBoxLayout(self)
53
54         l1=QFormLayout()
55         l1.addRow(self.tr("Name:"), self.editName)
56         l1.addRow(self.tr("Cycle length:"), self.editCycle)
57         l1.addRow(self.tr("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(self.tr("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(self.tr("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=self.tr("""
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(self.tr("About MaeGirls"))
155
156         self.ltitle=QLabel("MaeGirls v" + config.version, 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=self.tr("""
181 <p> MaeGirls shows information about women's cycle using some generic
182 guidelines.  It assumes that the ovulation 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(self.tr("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(self.tr("New"))
226
227         self.buttonSelect=QPushButton(self)
228         self.buttonSelect.setText(self.tr("Select"))
229
230         self.buttonDelete=QPushButton(self)
231         self.buttonDelete.setText(self.tr("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.dlgConfig=None
304         self.dlgAbout=None
305         self.dlgHelp=None
306         self.dlgGirls=None
307
308         self.algo=algo
309
310     def setupUi(self, algo):
311         self.centralwidget=QWidget(self)
312         self.setCentralWidget(self.centralwidget)
313
314         self.l0=QVBoxLayout(self.centralwidget)
315
316         self.dg=DaysGraph(algo, self.centralwidget)
317         self.l0.addWidget(self.dg)
318
319         # Menu
320         self.menuconfig=QAction(self.tr('Configure'), self)
321         self.menuconfig.triggered.connect(self.menuConfig)
322
323         self.menureset=QAction(self.tr('Go to today'), self)
324         self.menureset.triggered.connect(self.menuReset)
325
326         self.menugirls=QAction(self.tr('Girls'), self)
327         self.menugirls.triggered.connect(self.menuGirls)
328
329         self.menuabout=QAction(self.tr('About'), self)
330         self.menuabout.triggered.connect(self.menuAbout)
331
332         self.menuhelp=QAction(self.tr('Help'), self)
333         self.menuhelp.triggered.connect(self.menuHelp)
334
335         m=self.menuBar()
336         m.addAction(self.menureset)
337         m.addAction(self.menuconfig)
338         m.addAction(self.menugirls)
339         m.addAction(self.menuhelp)
340         m.addAction(self.menuabout)
341
342         self.setWindowTitle("MaeGirls")
343
344     def setAlgo(self, algo):
345         self.dg.setAlgo(algo)
346
347     def setGirl(self, name):
348         cfg=config.loadGirl(name)
349         self.girl=name
350         self.algo.setReference(cfg['day0'], cfg['cycle'])
351         self.repaint()
352
353     def menuConfig(self):
354         if self.dlgConfig==None:
355             self.dlgConfig=ConfigDialog(self)
356
357         dt={
358             'name':     self.girl,
359             'cycle':    self.algo.cycleLength(),
360             'day0':     self.algo.currentDayInCycle()
361             }
362
363         self.dlgConfig.initValues(dt)
364
365         ret=self.dlgConfig.exec_()
366
367         if ret==self.dlgConfig.Accepted:
368             today=algo.today()
369
370             name=self.dlgConfig.name
371             day0=today-self.dlgConfig.current
372
373             dt={
374                 'cycle':        self.dlgConfig.cycle,
375                 'day0':         day0,
376                 }
377
378             config.storeGirl(name, dt)
379             config.setCurrentGirl(name)
380
381             # If this is a rename, remove the old one
382             if self.girl!=name:
383                 config.removeGirl(self.girl)
384
385             self.setGirl(name)
386
387             self.repaint()
388
389     def menuGirls(self):
390         if self.dlgGirls==None:
391             self.dlgGirls=GirlsDialog(self)
392
393         ret=self.dlgGirls.exec_(self.girl)
394
395         what=self.dlgGirls.what
396         which=self.dlgGirls.which
397         if what=='new':
398             # Determine a unique name
399             base="newgirl"
400             idx=0
401             name=base
402             while config.girlExists(name):
403                 idx+=1
404                 name="%s%d" % (base, idx)
405             # Store this
406             config.newGirl(name)
407             # Set it as current
408             config.setCurrentGirl(name)
409             self.setGirl(name)
410             # Edit it
411             self.menuConfig()
412         elif what=='delete' and which!=None:
413             if self.girl==which:
414                 msg=QMessageBox(self)
415                 msg.setText(self.tr('You cannot delete the current girl'))
416                 msg.exec_()
417             else:
418                 config.removeGirl(which)
419         elif what=='select' and which!=None:
420             config.setCurrentGirl(which)
421             self.setGirl(which)
422
423     def menuAbout(self):
424         if self.dlgAbout==None:
425             self.dlgAbout=AboutDialog(self)
426
427         ret=self.dlgAbout.exec_()
428
429     def menuHelp(self):
430         if self.dlgHelp==None:
431             self.dlgHelp=HelpDialog(self)
432
433         ret=self.dlgHelp.exec_()
434
435     def menuReset(self):
436         self.dg.reset()
437
438 def init(algo):
439     global app
440     global win
441     global qttr, maetr
442
443     # Create the application
444     app=QApplication(sys.argv)
445
446     # Load translations
447     qttr=QTranslator()
448     qttr.load("qt_" + QLocale.system().name(),
449         QLibraryInfo.location(QLibraryInfo.TranslationsPath))
450     app.installTranslator(qttr)
451
452     maetr=QTranslator()
453     maetr.load("maegirls_" + QLocale.system().name(),
454         "/usr/share/maegirls/translations")
455
456     # Install the translation
457     app.installTranslator(maetr)
458
459     # One day support portrait mode
460     #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
461
462     # Create the main window
463     win=MaeGirls(algo)
464     win.show()
465
466 def setAlgo(algo):
467     global win
468     win.setAlgo(algo)
469
470 def setGirl(name):
471     global win
472     win.setGirl(name)
473
474 def doit():
475     global app
476     app.exec_()
477
478 # vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
479