diff options
| author | Loïc Guégan <loic.guegan@mailbox.org> | 2024-09-16 12:25:41 +0200 |
|---|---|---|
| committer | Loïc Guégan <loic.guegan@mailbox.org> | 2024-09-16 12:25:41 +0200 |
| commit | 79ce6bc894120527729bb282773a77a3c9cbaa2b (patch) | |
| tree | 2ca8590a99d5f43f4838acd3b3abb5b5d98ddeed | |
Minor changes
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | notes.org | 6 | ||||
| -rw-r--r-- | tropical/__init__.py | 1 | ||||
| -rw-r--r-- | tropical/calstate.py | 49 | ||||
| -rw-r--r-- | tropical/db.py | 37 | ||||
| -rw-r--r-- | tropical/qt/__init__.py | 1 | ||||
| -rw-r--r-- | tropical/qt/caldrawer.py | 192 | ||||
| -rw-r--r-- | tropical/qt/designer/MainWindow.ui | 81 | ||||
| -rw-r--r-- | tropical/qt/eventdrawer.py | 61 | ||||
| -rw-r--r-- | tropical/qt/mainwindow.py | 41 | ||||
| -rwxr-xr-x | tropical/tropical.py | 19 |
11 files changed, 491 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a990e4e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +env/ +**/__pycache__ +**/*.db
\ No newline at end of file diff --git a/notes.org b/notes.org new file mode 100644 index 0000000..6c60d19 --- /dev/null +++ b/notes.org @@ -0,0 +1,6 @@ + + + + +- [[https://doc.qt.io/qtforpython-6/PySide6/QtGui/QPainterPath.html][Qt6 Python doc]] +- [[https://stackoverflow.com/questions/9249842/how-does-qt-draw-a-border-around-a-rectangle][How rectangle border drawn]] diff --git a/tropical/__init__.py b/tropical/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tropical/__init__.py @@ -0,0 +1 @@ + diff --git a/tropical/calstate.py b/tropical/calstate.py new file mode 100644 index 0000000..2a760a6 --- /dev/null +++ b/tropical/calstate.py @@ -0,0 +1,49 @@ + +import calendar +from datetime import date, timedelta + +class CalState: + + def __init__(self): + self.gotoToday() + self.firstWeekDay=0 + + def setFirstWeekDay(self, i): + self.firstWeekDay=i + + def goto(self, year, month, day): + self.year=year + self.month=month + self.day=day + + def gotoToday(self): + today = date.today() + self.goto(today.year, today.month, today.day) + + def gotoNextWeek(self): + day=date(self.year,self.month,self.day) + timedelta(weeks=1) + self.goto(day.year,day.month, day.day) + + def gotoPrevWeek(self): + day=date(self.year,self.month,self.day) - timedelta(weeks=1) + self.goto(day.year,day.month, day.day) + + def getMonthDays(self): + cal=calendar.Calendar() + days=list() + for day in cal.itermonthdays4(self.year, self.month): + days.append(day) + return days + + def getWeekDays(self): + daysMonth=self.getMonthDays() + daysWeek=list() + for year, month, day, count in daysMonth: + daysWeek.append((year,month,day,count)) + if len(daysWeek) >= 7: + # Check if day within the week + for yy, mm, dd, cc in daysWeek: + if (yy, mm, dd) == (self.year, self.month, self.day): + return daysWeek + daysWeek.clear() + return None diff --git a/tropical/db.py b/tropical/db.py new file mode 100644 index 0000000..15e083b --- /dev/null +++ b/tropical/db.py @@ -0,0 +1,37 @@ +import sqlite3, time, socket + + +class CalDB: + __DBVERSION__="1" + + def __init__(self, dbPath): + self.path=dbPath + self.con=sqlite3.connect(dbPath) + self.cur=self.con.cursor() + + # Init database + res=self.cur.execute('SELECT name FROM sqlite_master WHERE type="table" AND name="infos";') + if res.fetchone() is None: + self.initDB() + + def initDB(self): + # Infos table + self.cur.execute("CREATE TABLE infos(name UNIQUE, value TEXT)") + self.cur.execute('INSERT INTO infos VALUES("dbversion", "'+CalDB.__DBVERSION__+'")') + self.cur.execute('INSERT INTO infos VALUES("creation", "'+str(time.time())+'")') + self.cur.execute('INSERT INTO infos VALUES("created_on", "'+socket.gethostname()+'")') + # Calendars table + self.cur.execute("CREATE TABLE calendars(id INTEGER PRIMARY KEY, name TEXT, description TEXT, color TEXT)") + # Events table + self.cur.execute("CREATE TABLE events(id INTEGER PRIMARY KEY, name TEXT, calendar INTEGER, description TEXT, start REAL, end REAL, repeat TEXT, frequency INTEGER, FOREIGN KEY(calendar) REFERENCES calendars(id))") + self.con.commit() + + def keyExists(self, db, key): + res=self.cur.execute("SELECT * FROM {} WHERE id={}".format(db,key)) + return not res.fetchone() is None + + def addEvent(self, event): + """ + Event format: { name: str, calendar: int, desc: str, start: float, end: float, repeat: str, frequency: int } + """ + pass diff --git a/tropical/qt/__init__.py b/tropical/qt/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tropical/qt/__init__.py @@ -0,0 +1 @@ + diff --git a/tropical/qt/caldrawer.py b/tropical/qt/caldrawer.py new file mode 100644 index 0000000..7e6b3fc --- /dev/null +++ b/tropical/qt/caldrawer.py @@ -0,0 +1,192 @@ + +from PyQt6.QtWidgets import QGraphicsScene, QGraphicsView, QSizePolicy +from PyQt6 import uic, QtGui, QtCore +from PyQt6.QtCore import Qt, QRect +import math +#https://forum.qt.io/topic/93327/how-can-i-use-qpainter-to-paint-on-qgraphicsview/5 +#https://www.pythonguis.com/tutorials/pyqt6-bitmap-graphics/#qpainter + +# to solve the fit problem: https://stackoverflow.com/questions/61886358/qgraphicsview-fitinview-not-working-as-expected + +class CalQGraphicsView(QGraphicsView): + def __init__(self, scene): + super().__init__(None) + self.setScene(scene) + + def resizeEvent(self, event): + self.fitInView(self.sceneRect(), Qt.AspectRatioMode.IgnoreAspectRatio) + + +class CalDrawerScene(QGraphicsScene): + def __init__(self, calState): + self.gridWidth=2 + self.daysLabelBG="#dddddd" + self.eventsLabelBG="#36e364" + super().__init__(None) + self.showWeekends=True + self.daysRect=list() + self.eventsRect=list() + self.calState=calState + self.daysNames=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"] + + def drawForeground(self, painter, rect): + origXF, origYF, widthF, heightF = rect.getRect() + origXI, origYI, widthI, heightI = (int(origXF),int(origYF),int(widthF),int(heightF)) + self.defaultBrush=painter.brush() + self.daysRect.clear() + self.eventsRect.clear() + daysNamesH=self.drawDaysName(painter, origXI, origYI, widthI, heightI) + self.drawGrid(painter, origXI, origYI+daysNamesH, widthI, heightI-daysNamesH) + self.drawDaysLabel(painter) + self.drawEvents(painter) + + def drawDaysName(self, painter, x, y, width, height): + # Init Pen + pen=QtGui.QPen() + pen.setWidth(self.gridWidth) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) + po=int(self.gridWidth/2) # Pen offset + painter.setPen(pen) + # Init Brush + painter.setBrush(Qt.BrushStyle.NoBrush) + # Setup dimensions + weekLength=7 + font=painter.font() + metric=QtGui.QFontMetrics(font); + labelH=metric.boundingRect("".join(self.daysNames)).height() + margin=0 + offsetY=int(labelH/2)+int(labelH/4)+margin + w=int((width-po*2)/weekLength) + h=int(labelH) + for i in range(0, 7): + labelW=metric.boundingRect(self.daysNames[i]).width() + offsetX=int(w/2-labelW/2) +# painter.drawText(x+offsetX+w*i,y+offsetY,self.daysNames[i]) + painter.drawText(x+w*i,y,w,h,Qt.AlignmentFlag.AlignHCenter,self.daysNames[i]) + rect=QRect(x+po+w*i,y+po,w,h) # Draw grid + painter.drawRect(rect) + return labelH + + def drawGrid(self,painter, x, y, width, height): + # Init Pen + pen=QtGui.QPen() + pen.setWidth(self.gridWidth) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) + po=int(self.gridWidth/2) # Pen offset + painter.setPen(pen) + # Init Brush + painter.setBrush(Qt.BrushStyle.NoBrush) + # Setup dimensions + weekLength=7 + if not self.showWeekends: + weekLength=5 + daysCount=weekLength*6 + + + # brush=QtGui.QBrush() + # brush.setColor(QtGui.QColor("#0000FF")) + # brush.setStyle(Qt.BrushStyle.SolidPattern) + + + w=int((width-po*2)/weekLength) + h=int((height-po*2)/6) + row=0 + col=0 + self.daysRect.clear() + while row*weekLength+col<daysCount: + rect=QRect(x+po+w*col,y+po+row*h,w,h) # Draw grid + rectInside=QRect(rect.x()+po,rect.y()+po,rect.width()-po*2,rect.height()-po*2) # Area within grid (within stroke) + self.daysRect.append(rectInside) # Store this region for later drawing + # if col==0 and row == 0: + # painter.setBrush(brush) + # painter.setPen(Qt.PenStyle.NoPen) + # painter.drawRect(QRect(b.x(),b.y(),b.width(),b.height())) + # painter.setPen(self.gridPen) + # painter.setBrush(self.defaultBrush) + painter.drawRect(rect) + col+=1 + if col==weekLength: + col=0 + row+=1 + + def drawDaysLabel(self,painter): + # Init Pen + pen=QtGui.QPen() + pen.setWidth(self.gridWidth) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) + po=int(self.gridWidth/2) # Pen offset + # Init Brush + brush=QtGui.QBrush() + brush.setColor(QtGui.QColor(self.daysLabelBG)) + brush.setStyle(Qt.BrushStyle.SolidPattern) + painter.setBrush(brush) + # Init various things + days=self.calState.getMonthDays() + font=painter.font() + metric=QtGui.QFontMetrics(font); + labelH=metric.boundingRect("1234567890").height() + margin=0 + offsetY=int(labelH/2)+int(labelH/4)+margin + # Draw labels + for i in range(0,len(self.daysRect)): + r=self.daysRect[i] + d=days[i] + dayLabel=str(d[2]) + labelW=metric.boundingRect(dayLabel).width() + offsetX=int(r.width()/2-labelW/2) + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRect(r.x(),r.y(),r.width(),labelH) # Remember r is within grid stroke + painter.setPen(pen) +# painter.drawText(r.x()+offsetX,r.y()+offsetY,dayLabel) + painter.drawText(r,Qt.AlignmentFlag.AlignHCenter,dayLabel) + self.eventsRect.append(QRect(r.x(),r.y()+labelH+margin,r.width(),r.height()-(labelH+margin))) + + def drawEvents(self,painter): + # Init Pen + pen=QtGui.QPen() + pen.setWidth(self.gridWidth) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) + po=int(self.gridWidth/2) # Pen offset + # Init Brush + brush=QtGui.QBrush() + brush.setColor(QtGui.QColor(self.eventsLabelBG)) + brush.setStyle(Qt.BrushStyle.SolidPattern) + painter.setBrush(brush) + # Init various things + days=self.calState.getMonthDays() + font=painter.font() + metric=QtGui.QFontMetrics(font); + labelH=metric.height() + margin=0 + colMark=5 + colMarkPadding=2 + offsetY=int(labelH/2)+int(labelH/4)+margin + # Draw + po=int(self.gridWidth/2) # Pen offset + for r in self.eventsRect: + offsetX=colMark+colMarkPadding + painter.drawText(r.x()+po+offsetX,r.y(),r.width()-offsetX-po*2,r.height(),0,"event testddddddddddddd") + painter.setPen(Qt.PenStyle.NoPen) + painter.drawRect(r.x(),r.y(),colMark,labelH) # Remember r is within grid stroke + painter.setPen(pen) + +class CalDrawer(): + + def __init__(self, layout, calState): + self.gs=CalDrawerScene(calState) + self.gv=CalQGraphicsView(self.gs) + # Setup propertion + spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + spLeft.setHorizontalStretch(3); + self.gv.setSizePolicy(spLeft); + + # if jean: + # spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + # spLeft.setHorizontalStretch(2); + # self.gv.setSizePolicy(spLeft); + # else: + # spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + # spLeft.setHorizontalStretch(1); + # self.gv.setSizePolicy(spLeft); + layout.addWidget(self.gv) + diff --git a/tropical/qt/designer/MainWindow.ui b/tropical/qt/designer/MainWindow.ui new file mode 100644 index 0000000..656646c --- /dev/null +++ b/tropical/qt/designer/MainWindow.ui @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>448</width> + <height>623</height> + </rect> + </property> + <property name="windowTitle"> + <string>MainWindow</string> + </property> + <widget class="QWidget" name="centralwidget"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="layoutDirection"> + <enum>Qt::LeftToRight</enum> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QFrame" name="calContainer"> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_2"/> + </widget> + </item> + <item> + <widget class="QPushButton" name="pushButton"> + <property name="text"> + <string>PushButton</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menubar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>448</width> + <height>22</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + </widget> + <widget class="QMenu" name="menuAbout"> + <property name="title"> + <string>About</string> + </property> + </widget> + <addaction name="menuFile"/> + <addaction name="menuAbout"/> + </widget> + <widget class="QStatusBar" name="statusbar"/> + <widget class="QToolBar" name="toolBar"> + <property name="windowTitle"> + <string>toolBar</string> + </property> + <attribute name="toolBarArea"> + <enum>TopToolBarArea</enum> + </attribute> + <attribute name="toolBarBreak"> + <bool>false</bool> + </attribute> + </widget> + </widget> + <resources/> + <connections/> +</ui> diff --git a/tropical/qt/eventdrawer.py b/tropical/qt/eventdrawer.py new file mode 100644 index 0000000..4afcf55 --- /dev/null +++ b/tropical/qt/eventdrawer.py @@ -0,0 +1,61 @@ + +from PyQt6.QtWidgets import QGraphicsScene, QGraphicsView, QSizePolicy +from PyQt6 import uic, QtGui, QtCore +from PyQt6.QtCore import Qt, QRect +import math +#https://forum.qt.io/topic/93327/how-can-i-use-qpainter-to-paint-on-qgraphicsview/5 +#https://www.pythonguis.com/tutorials/pyqt6-bitmap-graphics/#qpainter + +# to solve the fit problem: https://stackoverflow.com/questions/61886358/qgraphicsview-fitinview-not-working-as-expected + +class EvtQGraphicsView(QGraphicsView): + def __init__(self, scene): + super().__init__(None) + self.setScene(scene) + + def resizeEvent(self, event): + self.fitInView(self.sceneRect(), Qt.AspectRatioMode.IgnoreAspectRatio) + + +class EvtDrawerScene(QGraphicsScene): + def __init__(self, calState): + self.gridWidth=2 + super().__init__(None) + self.calState=calState + + def drawForeground(self, painter, rect): + origXF, origYF, widthF, heightF = rect.getRect() + origXI, origYI, widthI, heightI = (int(origXF),int(origYF),int(widthF),int(heightF)) + self.drawEvents(painter,origXI, origYI, widthI, heightI) + + + def drawEvents(self,painter,x,y,width,height): + # Init Pen + pen=QtGui.QPen() + pen.setWidth(self.gridWidth) + pen.setJoinStyle(Qt.PenJoinStyle.MiterJoin) + po=int(self.gridWidth/2) # Pen offset + painter.setPen(pen) + + painter.drawRect(x+po,y+po,width-po*2,height-po*2) + +class EvtDrawer(): + + def __init__(self, layout, calState): + self.gs=EvtDrawerScene(calState) + self.gv=EvtQGraphicsView(self.gs) + # Setup propertion + spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + spLeft.setHorizontalStretch(1); + self.gv.setSizePolicy(spLeft); + + # if jean: + # spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + # spLeft.setHorizontalStretch(2); + # self.gv.setSizePolicy(spLeft); + # else: + # spLeft=QSizePolicy(QSizePolicy.Policy.Preferred,QSizePolicy.Policy.Preferred); + # spLeft.setHorizontalStretch(1); + # self.gv.setSizePolicy(spLeft); + layout.addWidget(self.gv) + diff --git a/tropical/qt/mainwindow.py b/tropical/qt/mainwindow.py new file mode 100644 index 0000000..48da072 --- /dev/null +++ b/tropical/qt/mainwindow.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 + + +from PyQt6.QtWidgets import QApplication, QWidget, QMainWindow +from PyQt6 import uic, QtGui +from PyQt6.QtCore import Qt +from .caldrawer import CalDrawer +from .eventdrawer import EvtDrawer + +# Only needed for access to command line arguments +import sys, os + + +class MainWindow(QMainWindow): + + def __init__(self,uipath, calState): + super(MainWindow,self).__init__() + uic.loadUi(uipath+"/MainWindow.ui",self) + self.calDrawer=CalDrawer(self.calContainer.layout(),calState) + self.evtDrawer=EvtDrawer(self.calContainer.layout(),calState) + self.calState=calState + self.show() + + def setVersion(self,version): + self.statusbar.showMessage("Calanus v"+version,0) + +def StartApplication(version,calState): + path = os.path.dirname(os.path.abspath(__file__))+"/designer" + # You need one (and only one) QApplication instance per application. + # Pass in sys.argv to allow command line arguments for your app. + # If you know you won't use command line arguments QApplication([]) works too. + app = QApplication(sys.argv) + + # Create a Qt widget, which will be our window. + window = MainWindow(path, calState) + window.setVersion(version) + window.show() # IMPORTANT!!!!! Windows are hidden by default. + + # Start the event loop. + app.exec() + return window diff --git a/tropical/tropical.py b/tropical/tropical.py new file mode 100755 index 0000000..bb1e7e2 --- /dev/null +++ b/tropical/tropical.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +#!/usr/bin/env python3 + +import qt.mainwindow as QtCalanus +from calstate import CalState +from db import CalDB + +__VERSION__ = "0.1" + + + + + +if __name__ == '__main__': + calState=CalState() + QtCalanus.StartApplication(__VERSION__,calState) + #print(db.keyExists("calendars",1)) +#db=CalDB("sqlite.db") |
