--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of mydays.
+#
+# mydays is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# mydays is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with mydays. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+
+import time
+
+import algo
+
+class DaysGraph(QWidget):
+ def __init__(self, algo_, *args, **kwargs):
+ QWidget.__init__(self, *args, **kwargs)
+
+ self.min_day_width=20
+ self.max_day_width=50
+ self.day_width=50
+ self.algo=algo_
+
+ self.first_day=algo.today()
+
+ self.last_x=None
+ self.last_y=None
+
+ def setAlgo(self, algo_):
+ self.algo=algo_
+
+ def reset(self):
+ self.first_day=algo.today()
+ self.repaint()
+
+ def paintEvent(self, event):
+ w0=self.width()
+ h=self.height()
+
+ gray=0x22
+
+ days=int(w0/self.day_width)
+ days=int((days)/2)*2 + 3
+ w=days*self.day_width
+
+ # Now calculate the x0
+ x0=int((w0-w)/2)
+
+ painter=QPainter(self)
+
+ # This makes it sloooooooooooow
+# painter.setRenderHints(painter.Antialiasing \
+# | painter.TextAntialiasing \
+# | painter.HighQualityAntialiasing
+# )
+
+ # common day outline
+ colol=QColor(0x66,0x66,0x66)
+ penol=QPen(colol)
+
+ # outline of the small block
+ colblockol=Qt.white
+ penblockol=QPen(colblockol)
+
+ # Current day outline
+ colcur=Qt.white
+ pencur=QPen(colcur, 3)
+
+ # Text color
+ coltxt=Qt.white
+ pentxt=QPen(coltxt)
+
+ # Font
+ fontdate=QFont()
+ fontdate.setPointSizeF(fontdate.pointSizeF()*0.5)
+ fontdate_m=QFontMetrics(fontdate)
+
+ # Last bounding rectagle of the text
+ last_br=QRect(0,0,4,4)
+
+ today=algo.today()
+
+ for d in xrange(days):
+ # This will cause the first_day to be at the center
+ curday=self.first_day+d - int(days/2)
+ x=x0+d*self.day_width
+ y=0
+
+ h2=h-50
+
+ st=self.algo.status(curday)
+
+ if st['status']=='ok':
+ colbg=QColor(gray, gray, gray)
+ brbg=QBrush(colbg)
+
+ painter.setPen(penol)
+ painter.fillRect(x, y, self.day_width-1, h2-1, brbg)
+ elif st['status']=='red' or \
+ st['status']=='blue' or \
+ st['status']=='green':
+ if st['status']=='red':
+ perc=1.0*st['day'] / (st['len'])
+ colbg=QColor(
+ 0xff - int((0xff - gray - gray) * perc),
+ int(gray * perc),
+ int(gray * perc))
+ colblock=Qt.red
+ elif st['status']=='blue':
+ perc=1.0*(st['day']+1) / (st['len'])
+ colbg=QColor(
+ gray,
+ gray,
+ gray + ((0xff-gray) * perc))
+ colblock=Qt.blue
+ elif st['status']=='green':
+ perc=1.0*(st['day']+1) / (st['len'])
+ colbg=QColor(
+ gray,
+ gray + ((0xff-gray) * perc),
+ gray)
+ colblock=Qt.green
+ brbg=QBrush(colbg)
+ painter.fillRect(x, y, self.day_width-1, h2-1, brbg)
+
+ # Show a nice small box for interesting days
+ boxw=20
+ boxh=10
+
+ # Ensure that boxw is sane - e.g. not larger than
+ # the current day_width
+ boxw2=self.day_width-10
+ boxw=min(boxw, boxw2)
+
+ x1=x+int((self.day_width-boxw)/2)
+
+ if st['status']=='red':
+ y1=y + int(perc * h2*2/3) + 20
+ elif st['status']=='blue' or st['status']=='green':
+ y1=y + int(h2*2/3) - int(perc * h2*2/3) + 20
+
+ brbg=QBrush(colblock)
+ painter.fillRect(x1, y1, boxw, boxh, brbg)
+ painter.setPen(colblockol)
+ painter.drawRect(x1, y1, boxw, boxh)
+
+ painter.setPen(colol)
+ painter.drawRect(x, y, self.day_width, h2-1)
+
+ # Highlight today
+ if curday==today:
+ painter.setPen(pencur)
+ painter.drawRect(x+2,y+2,self.day_width-4, h2-5)
+
+ date=time.localtime(curday*86400+30000)
+
+ y2=h2
+
+ txt="%d.%02d\n%d" % (date.tm_mday, date.tm_mon, date.tm_year)
+
+ # br is used to determine overlaps
+ br=fontdate_m.boundingRect(txt)
+ br.translate(x,y2)
+
+ colnum=1
+ while colnum*self.day_width < br.width():
+ colnum+=2
+
+ # If this intersects with the previous day then it is ok
+ if br.intersects(last_br):
+ continue
+ last_br=br
+
+ # br2 is the true bounding rectangle
+ # The width calculation helps with alignment
+ br2=QRect(x-((colnum-1)/2 * self.day_width), y2,
+ self.day_width*colnum, fontdate_m.height()*2)
+
+ # Draw it
+ painter.setFont(fontdate)
+ painter.setPen(pentxt)
+ painter.drawText(br2, Qt.AlignCenter | Qt.AlignTop, txt)
+
+ # Handle both horizontal and vertical movement
+ def mouseMoveEvent(self, event):
+ pos=event.pos()
+ x=pos.x()
+ y=pos.y()
+
+ torepaint=False
+
+ if self.last_x==None:
+ self.last_x=x
+ self.last_y=y
+
+ # Vertical movement - that much pixels account for one zoom
+ y_step=50
+
+ # First look horizontal movement
+ # If there was movement the reset vertical movement
+ dx=x-self.last_x
+ if dx>0:
+ days=int(dx/self.day_width)
+ else:
+ days=-int(abs(dx)/self.day_width)
+
+ if days!=0:
+ self.first_day-=days
+ self.last_x+=(days*self.day_width)
+ self.last_y=y # Reset vertical movement
+ torepaint=True
+
+ # Now look for vertical movement
+ dy=y-self.last_y
+ if dy>0:
+ zoom=int(dy/y_step)
+ else:
+ zoom=-int(abs(dy)/y_step)
+
+ if zoom!=0:
+ t=self.day_width+zoom
+ if t<self.min_day_width:
+ t=self.min_day_width
+ elif t>self.max_day_width:
+ t=self.max_day_width
+ #self.day_width-=zoom
+ self.day_width=t
+
+ self.last_y+=zoom*y_step
+ self.last_x=x # Reset horizontal movement
+ torepaint=True
+
+ if torepaint:
+ self.repaint()
+
+ def mousePressEvent(self, event):
+ pos=event.pos()
+ self.last_x=pos.x()
+ self.last_y=pos.y()
+
+ def sizeHint(self):
+ return(QSize(100,50))
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+
--- /dev/null
+#!/usr/bin/env python
+# coding=UTF-8
+#
+# Copyright (C) 2010 Stefanos Harhalakis
+#
+# This file is part of mydays.
+#
+# mydays is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# mydays is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with mydays. If not, see <http://www.gnu.org/licenses/>.
+#
+# $Id: 0.py 2265 2010-02-21 19:16:26Z v13 $
+
+__version__ = "$Id: 0.py 2265 2010-02-21 19:16:26Z v13 $"
+
+from PyQt4.QtGui import *
+from PyQt4.QtCore import *
+
+import sys
+import time
+
+from graph import DaysGraph
+import config
+import algo
+
+app=None
+win=None
+
+class ConfigDialog(QDialog):
+ def __init__(self, *args, **kwargs):
+ QDialog.__init__(self, *args, **kwargs)
+
+ self.editCycle=QSpinBox(self)
+ self.editCurrent=QSpinBox(self)
+ self.editCycle.setRange(10,50)
+ self.editCurrent.setRange(1,50)
+ self.editCurrent.setWrapping(True)
+ self.editCycle.setSuffix(" days")
+
+ self.editCycle.valueChanged.connect(self.slotEditCycleChanged)
+
+ self.l0=QHBoxLayout(self)
+
+ l1=QFormLayout()
+ l1.addRow("Cycle length:", self.editCycle)
+ l1.addRow("Current day in cycle:", self.editCurrent)
+
+ self.l0.addLayout(l1)
+
+ spacer=QSpacerItem(20, 20, QSizePolicy.Expanding)
+ self.l0.addItem(spacer)
+
+ l2=QVBoxLayout()
+ self.l0.addLayout(l2)
+
+ self.buttonOk=QPushButton(self)
+ self.buttonOk.setText("OK")
+ self.buttonOk.clicked.connect(self.slotButOk)
+ l2.addWidget(self.buttonOk)
+
+ spacer=QSpacerItem(20, 20, QSizePolicy.Minimum,QSizePolicy.Expanding)
+ l2.addItem(spacer)
+
+ self.setWindowTitle("Configuration")
+
+ def slotButOk(self):
+ self.cycle=self.editCycle.value()
+ self.current=self.editCurrent.value()-1
+
+ self.accept()
+
+ def slotEditCycleChanged(self, value):
+ self.editCurrent.setMaximum(value)
+
+ # current starts from 0
+ def initValues(self, cycle, current):
+ self.cycle=cycle
+ self.current=current
+ self.editCycle.setValue(cycle)
+ self.editCurrent.setValue(current+1)
+
+class MyMsgDialog(QDialog):
+ """
+ A Dialog to show a finger-scrollable message
+
+ Typical usage:
+
+ class Koko(MyMsgDialog):
+ def __init__(....)
+ MyMsgDialog.__init__(....)
+
+
+ self.setWindowTitle("My title")
+
+ l1=QLabel("koko", self.w)
+ self.l.addWidget(l1)
+ ...
+
+ self.l is a QVBoxLayout. Add everything there.
+ self.w is a QWidget. Use it as parent.
+
+ """
+ def __init__(self, *args, **kwargs):
+ QDialog.__init__(self, *args, **kwargs)
+
+ # This freaking thing is hard
+ # It needs two layouts, one extra widget, the fingerscrollable
+ # property set to true *and* setWidgetResizable(True)
+ self._mm_l0=QVBoxLayout(self)
+
+ self._mm_q=QScrollArea(self)
+ self._mm_q.setWidgetResizable(True)
+ self._mm_q.setProperty('FingerScrollable', True)
+
+ self.w=QWidget(self._mm_q)
+
+ self.l=QVBoxLayout(self.w)
+ self._mm_q.setWidget(self.w)
+ self._mm_l0.addWidget(self._mm_q)
+
+class AboutDialog2(QDialog):
+ def __init__(self, *args, **kwargs):
+ QDialog.__init__(self, *args, **kwargs)
+
+ txt="""
+<p> A program to monitor the women's cycle. Good for planning (or acting ;-).
+Inspired by "MyDays" app which is (was?) available for Java ME capable phones.
+
+<p> WARNING!!! This is not accurate nor correct! You cannot trust
+this program (or any other program) for accurate predictions!
+(after all, this is about women... how can one be sure :-).
+
+<p> Copyright © 2010, Stefanos Harhalakis <v13@v13.gr>
+
+<p> Send comments and bug reports to the above address.
+
+<p> This program can be distributed under the terms of the GNU public
+license, version 3 or any later.
+ """
+
+ self.setWindowTitle("About MaeDays")
+
+ # This freaking thing is hard
+ # It needs two layouts, one extra widget, the fingerscrollable
+ # property set to true *and* setWidgetResizable(True)
+ self.l0=QVBoxLayout(self)
+
+ self.q=QScrollArea(self)
+ self.q.setWidgetResizable(True)
+ self.q.setProperty('FingerScrollable', True)
+ self.w1=QWidget(self.q)
+
+ self.l=QVBoxLayout(self.w1)
+
+ self.ltitle=QLabel("MaeDays", self.w1)
+ self.ltitle.setObjectName("title")
+ self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ self.ltitle.setAlignment(Qt.AlignCenter)
+ self.l.addWidget(self.ltitle)
+
+ self.label=QLabel(txt, self.w1)
+ self.label.setWordWrap(True)
+ self.label.setTextFormat(Qt.RichText)
+ self.label.setAlignment(Qt.AlignJustify)
+
+ self.l.addWidget(self.label)
+ self.q.setWidget(self.w1)
+ self.l0.addWidget(self.q)
+
+ self.ltitle.setStyleSheet("""
+ QLabel {
+ font-size: 25pt;
+ color: rgb(192,192,192);
+ margin-bottom: 0.5ex;
+ }
+ """)
+
+class AboutDialog(MyMsgDialog):
+ def __init__(self, *args, **kwargs):
+ MyMsgDialog.__init__(self, *args, **kwargs)
+
+ txt="""
+<p> A program to monitor the women's cycle. Good for planning (or acting ;-).
+Inspired by "MyDays" app which is (was?) available for Java ME capable phones.
+
+<p style="color: orange;">
+WARNING!!! This is not accurate nor correct! You cannot trust
+this program (or any other program) for accurate predictions!
+(after all, this is about women... how can one be sure :-).
+
+<p> Copyright © 2010, Stefanos Harhalakis <v13@v13.gr>
+
+<p> Send comments and bug reports to the above address.
+
+<p> This program can be distributed under the terms of the GNU public
+license, version 3 or any later.
+ """
+
+ self.setWindowTitle("About MaeDays")
+
+ self.ltitle=QLabel("MaeDays", self.w)
+ self.ltitle.setObjectName("title")
+ self.ltitle.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ self.ltitle.setAlignment(Qt.AlignCenter)
+ self.l.addWidget(self.ltitle)
+
+ self.label=QLabel(txt, self.w)
+ self.label.setWordWrap(True)
+ self.label.setTextFormat(Qt.RichText)
+ self.label.setAlignment(Qt.AlignJustify)
+ self.l.addWidget(self.label)
+
+ self.ltitle.setStyleSheet("""
+ QLabel {
+ font-size: 25pt;
+ color: rgb(192,192,192);
+ margin-bottom: 0.5ex;
+ }
+ """)
+
+class HelpDialog(MyMsgDialog):
+ def __init__(self, *args, **kwargs):
+ MyMsgDialog.__init__(self, *args, **kwargs)
+
+ txt="""
+<p> MaeDays shows information about women's cycle using some generic
+guidelines. It assumes that the ovolution happens 14 days before the start
+of the next period and that the period cycle is constant. Also, it assumes
+that sperm can live for 4 days, while an egg can live for 2 days.
+
+<p style="color: orange;">
+WARNING!!! This is not always correct. There are FAR TOO MANY exceptions
+to the above rules!!!
+
+<p> Assuming that you understand the risk of being wrong, you become
+entitle to read the graph as follows:
+<p> <span style="color: red">In red:</span> The days that menstruation
+happens.
+<p> <span style="color: green">In green:</span> The fertile days.
+<p> <span style="color: blue">In blue:</span> The days of PMS
+(Premenstrual Syndrome)
+
+<p> Navigation is easy: Use left-right finger movement to move the calendar
+view. Use up-down finger movement to zoom in/out.
+ """
+
+ self.setWindowTitle("Help")
+
+ self.label=QLabel(txt, self.w)
+ self.label.setWordWrap(True)
+ self.label.setTextFormat(Qt.RichText)
+ self.label.setAlignment(Qt.AlignJustify)
+ self.l.addWidget(self.label)
+
+class MyDays(QMainWindow):
+ def __init__(self, algo):
+ QMainWindow.__init__(self)
+
+ self.setupUi(algo)
+
+ self.dlgConfig=ConfigDialog(self)
+ self.dlgAbout=AboutDialog(self)
+ self.dlgHelp=HelpDialog(self)
+
+ self.algo=algo
+
+ def setupUi(self, algo):
+ self.centralwidget=QWidget(self)
+ self.setCentralWidget(self.centralwidget)
+
+ self.l0=QVBoxLayout(self.centralwidget)
+
+ self.dg=DaysGraph(algo, self.centralwidget)
+ self.l0.addWidget(self.dg)
+
+ # Menu
+ self.menuconfig=QAction('Configure', self)
+ self.menuconfig.triggered.connect(self.menuConfig)
+
+ self.menureset=QAction('Go to today', self)
+ self.menureset.triggered.connect(self.menuReset)
+
+ self.menuabout=QAction('About', self)
+ self.menuabout.triggered.connect(self.menuAbout)
+
+ self.menuhelp=QAction('Help', self)
+ self.menuhelp.triggered.connect(self.menuHelp)
+
+ m=self.menuBar()
+ m.addAction(self.menuconfig)
+ m.addAction(self.menureset)
+ m.addAction(self.menuabout)
+ m.addAction(self.menuhelp)
+
+ self.setWindowTitle("MaeDays")
+
+ def setAlgo(self, algo):
+ self.dg.setAlgo(algo)
+
+ def menuConfig(self):
+ if self.dlgConfig==None:
+ self.dlgConfig=ConfigDialog(self)
+
+ self.dlgConfig.initValues(self.algo.cycleLength(),
+ self.algo.currentDayInCycle())
+
+ ret=self.dlgConfig.exec_()
+
+ if ret==self.dlgConfig.Accepted:
+ today=algo.today()
+ day0=today-self.dlgConfig.current
+ self.algo.setReference(day0, self.dlgConfig.cycle)
+ config.store("default", self.dlgConfig.cycle, day0)
+ self.repaint()
+
+ def menuAbout(self):
+ if self.dlgAbout==None:
+ self.dlgAbout=AboutDialog(self)
+
+ ret=self.dlgAbout.exec_()
+
+ def menuHelp(self):
+ if self.dlgHelp==None:
+ self.dlgHelp=HelpDialog(self)
+
+ ret=self.dlgHelp.exec_()
+
+ def menuReset(self):
+ self.dg.reset()
+
+def init(algo):
+ global app
+ global win
+
+ app=QApplication(sys.argv)
+ #app.setAttribute(Qt.WA_Maemo5PortraitOrientation, True);
+ win=MyDays(algo)
+ win.show()
+
+def setAlgo(algo):
+ global win
+ win.setAlgo(algo)
+
+def doit():
+ global app
+ app.exec_()
+
+# vim: set ts=8 sts=4 sw=4 noet formatoptions=r ai nocindent:
+