Skip to content

Commit

Permalink
Merge pull request #12 from AndreWohnsland/dev
Browse files Browse the repository at this point in the history
Add proper plot alignement, migrate to qt6
  • Loading branch information
AndreWohnsland authored Jun 21, 2024
2 parents db22a8c + e290e1a commit 9588480
Show file tree
Hide file tree
Showing 21 changed files with 587 additions and 472 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ python = ">=3.10,<3.12"
GitPython = "^3.1.43"
matplotlib = "^3.9.0"
pandas = "^2.2.2"
PyQt5 = "5.15.7"
pyqt5-qt5 = "5.15.2"
xlsxwriter = "^3.2.0"
pyqtdarktheme = "^2.1.0"
qtawesome = "^1.3.1"
holidays = "^0.47"
dataclasses-json = "^0.6.4"
pyqt6 = "^6.4.2"

[tool.poetry.group.dev.dependencies]
pyinstaller = "^6.8.0"
pip-chill = "^1.0.3"
jupyterlab = "^4.2.2"
mypy = "^1.10.0"
ruff = "^0.4.8"
pyqt6-tools = "^6.4.2.3.3"

[build-system]
requires = ["poetry-core"]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
gitpython
matplotlib
pandas
pyqt5
pyqt6
xlsxwriter
pyqtdarktheme
qtawesome
Expand Down
6 changes: 3 additions & 3 deletions runme.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import logging
import sys

from PyQt5.QtWidgets import QApplication
from PyQt6.QtWidgets import QApplication

from src.ui_mainwindow import MainWindow
from src.utils import get_additional_run_args, sync_theme
Expand All @@ -19,12 +19,12 @@
app = QApplication(sys.argv + get_additional_run_args())
w = MainWindow()
# in case of active style change, also change theme, need to sync at start
app.paletteChanged.connect(sync_theme)
# app.paletteChanged.connect(sync_theme)
# still keep the app running, even if the main window is closed (we use tray for the app)
QApplication.setQuitOnLastWindowClosed(False)
sync_theme()
w.show()
sys.exit(app.exec_())
sys.exit(app.exec())
except Exception as e:
logger.exception(e)
raise
2 changes: 1 addition & 1 deletion scripts/compile_ui_to_python.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ $files = @(
)

foreach ($f in $files) {
pyuic5 -x .\$f.ui -o .\$f.py
pyuic6 -x .\$f.ui -o .\$f.py
}

cd ..
79 changes: 65 additions & 14 deletions scripts/plotting.ipynb

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/config_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@
"country": "US",
"subdiv": None,
"workdays": [0, 1, 2, 3, 4], # 0-6, 0=Monday, 6=Sunday
"plot_pause": True,
}
CONFIG_NAMES = Literal["name", "save_path", "country", "subdiv", "daily_hours", "weekly_hours", "workdays"]
CONFIG_NAMES = Literal[
"name", "save_path", "country", "subdiv", "daily_hours", "weekly_hours", "workdays", "plot_pause"
]


@dataclass
Expand All @@ -31,6 +34,7 @@ class Config:
country: str
subdiv: str | None
workdays: list[int]
plot_pause: bool

@classmethod
def from_kwargs(cls, **kwargs):
Expand Down
2 changes: 1 addition & 1 deletion src/datastore.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def _generate_monthly_time(
daily_minutes = CONFIG_HANDLER.config.daily_hours * 60
for _day in full_month:
days_data = df[df["date"] == _day.date()]
calculated_time = 1.0
calculated_time = 0.0
# when we got a free day, we get the working time for this day
if _day.date() in free_days:
calculated_time += daily_minutes
Expand Down
2 changes: 1 addition & 1 deletion src/icons.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import dataclass

import qtawesome as qta
from PyQt5.QtGui import QIcon
from PyQt6.QtGui import QIcon

from src.utils import get_background_color, get_font_color

Expand Down
15 changes: 11 additions & 4 deletions src/ui_config_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
from typing import TYPE_CHECKING

import holidays
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QWidget

from src.config_handler import CONFIG_HANDLER
from src.icons import get_app_icon
Expand All @@ -21,8 +21,13 @@ def __init__(self, main_window: MainWindow):
self.main_window = main_window
self.setupUi(self)
self.setWindowIcon(get_app_icon())
self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint) # type: ignore
self.setAttribute(Qt.WA_DeleteOnClose) # type: ignore
self.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.CustomizeWindowHint
| Qt.WindowType.WindowTitleHint
| Qt.WindowType.WindowCloseButtonHint
)
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)
self.country_list = holidays.list_supported_countries()
self._update_country_list()
self.set_config_values()
Expand Down Expand Up @@ -71,6 +76,7 @@ def set_config_values(self):
self.input_name.setText(CONFIG_HANDLER.config.name)
self.input_daily_hours.setValue(CONFIG_HANDLER.config.daily_hours)
self.input_weekly_hours.setValue(CONFIG_HANDLER.config.weekly_hours)
self.input_plot_pause.setChecked(CONFIG_HANDLER.config.plot_pause)
for day in CONFIG_HANDLER.config.workdays:
getattr(self, f"radio_weekday_{day}").setChecked(True)

Expand All @@ -81,6 +87,7 @@ def apply_config(self):
CONFIG_HANDLER.config.name = self.input_name.text()
CONFIG_HANDLER.config.daily_hours = self.input_daily_hours.value()
CONFIG_HANDLER.config.weekly_hours = self.input_daily_hours.value()
CONFIG_HANDLER.config.plot_pause = self.input_plot_pause.isChecked()
selected_days: list[int] = []
for day in range(7):
if getattr(self, f"radio_weekday_{day}").isChecked():
Expand Down
39 changes: 14 additions & 25 deletions src/ui_controller.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QDialog, QFileDialog, QInputDialog, QLayout, QMessageBox, QTableWidget, QTableWidgetItem
from PyQt6.QtWidgets import QDialog, QFileDialog, QInputDialog, QLayout, QMessageBox, QTableWidget, QTableWidgetItem

from src import __version__
from src.config_handler import CONFIG_HANDLER, CONFIG_NAMES
from src.config_handler import CONFIG_HANDLER
from src.filepath import HOME_PATH
from src.icons import get_app_icon

Expand All @@ -13,12 +13,12 @@ def __init__(self):
def show_message(self, message: str):
"""Prompt default messagebox, use a QMessageBox with OK-Button."""
message_box = QMessageBox()
message_box.setStandardButtons(QMessageBox.Ok) # type: ignore
message_box.setStandardButtons(QMessageBox.StandardButton.Ok)
message_box.setText(str(message))
message_box.setWindowIcon(get_app_icon())
message_box.setWindowTitle("Information")
message_box.show()
message_box.exec_()
message_box.exec()

def report_choice(self):
message_box = QMessageBox()
Expand All @@ -27,11 +27,11 @@ def report_choice(self):
)
message_box.setWindowTitle("Report Generation")
message_box.setWindowIcon(get_app_icon())
overtime_button = message_box.addButton("Overtime", QMessageBox.YesRole) # type: ignore
time_button = message_box.addButton("Time", QMessageBox.NoRole) # type: ignore
message_box.addButton("Cancel", QMessageBox.RejectRole) # type: ignore
overtime_button = message_box.addButton("Overtime", QMessageBox.ButtonRole.YesRole)
time_button = message_box.addButton("Time", QMessageBox.ButtonRole.NoRole)
message_box.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)

message_box.exec_()
message_box.exec()
if message_box.clickedButton() == overtime_button:
return True
if message_box.clickedButton() == time_button:
Expand All @@ -43,9 +43,9 @@ def user_okay(self, text: str):
message_box.setText(text)
message_box.setWindowTitle("Confirmation required")
message_box.setWindowIcon(get_app_icon())
yes_button = message_box.addButton("Yes", QMessageBox.YesRole) # type: ignore
message_box.addButton("No", QMessageBox.NoRole) # type: ignore
message_box.exec_()
yes_button = message_box.addButton("Yes", QMessageBox.ButtonRole.YesRole)
message_box.addButton("No", QMessageBox.ButtonRole.NoRole)
message_box.exec()
if message_box.clickedButton() == yes_button:
return True
return False
Expand All @@ -66,11 +66,11 @@ def get_folder(self, current_path: str, parent=None):
current_path = str(HOME_PATH)

dialog = QFileDialog(parent)
dialog.setFileMode(QFileDialog.DirectoryOnly) # type: ignore
dialog.setFileMode(QFileDialog.FileMode.Directory)
dialog.setDirectory(current_path)

if dialog.exec_() == QDialog.Accepted: # type: ignore
return dialog.selectedFiles()[0] # returns a list
if dialog.exec() == QDialog.DialogCode.Accepted:
return dialog.selectedFiles()[0]
return ""

def fill_table(self, table: QTableWidget, entry):
Expand All @@ -91,17 +91,6 @@ def set_header_names(self, table: QTableWidget, name1: str, name2: str):
if header_2 is not None:
header_2.setText(name2)

def get_user_data(self, parent):
needed_keys: list[CONFIG_NAMES] = ["name"]
# todo adjust to new class logic
for data in needed_keys:
text, ok = self.get_text(data, parent)
if not ok:
return
if text != "":
CONFIG_HANDLER.set_config_value(data, text, write=False)
CONFIG_HANDLER.write_config_file()

def get_save_folder(self):
user_path = CONFIG_HANDLER.config.save_path
returned_path = self.get_folder(user_path)
Expand Down
22 changes: 16 additions & 6 deletions src/ui_data_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from PyQt5.QtCore import QDate, QDateTime, Qt
from PyQt5.QtWidgets import QTableWidgetItem, QWidget
from PyQt6.QtCore import QDate, QDateTime, Qt
from PyQt6.QtWidgets import QTableWidgetItem, QWidget

from src.config_handler import CONFIG_HANDLER
from src.data_exporter import EXPORTER
Expand Down Expand Up @@ -44,7 +44,10 @@ def __init__(self, main_window: MainWindow):
self.main_window = main_window
self.setWindowIcon(get_app_icon())
self.setWindowFlags(
Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint # type: ignore
Qt.WindowType.Window
| Qt.WindowType.CustomizeWindowHint
| Qt.WindowType.WindowTitleHint
| Qt.WindowType.WindowCloseButtonHint
)
# setting all the params
self.date_edit.setDateTime(QDateTime.currentDateTime())
Expand Down Expand Up @@ -159,11 +162,15 @@ def plot(self):

# Add numbers above the bars
for i, (_, row) in enumerate(plot_df.iterrows()):
total_time = row["work"] + row["overtime"]
if total_time <= 0:
total_time = sum(row)
if total_time <= 0.0:
continue
# put small offset for the numbers to not overlap with the bar
position = (i, total_time + 0.01 * needed_hours)
# last 3% will collide with the line, in this case just put it above the line already
line_collide = 0.97 * needed_hours <= total_time <= needed_hours
if line_collide:
position = (i, 1.01 * needed_hours)
ax.annotate(f"{total_time:.1f}", position, ha="center", va="bottom", fontsize=8, weight="bold")

if self.radio_month.isChecked():
Expand All @@ -184,7 +191,10 @@ def adjust_df_for_plot(self, df: pd.DataFrame, needed_hours: float) -> pd.DataFr
df["overtime"] = df["work"] - needed_hours
df["overtime"] = df["overtime"].clip(lower=0)
df["work"] = df["work"].clip(upper=needed_hours, lower=0)
return df[["work", "overtime", "pause"]]
to_keep = ["work", "overtime"]
if CONFIG_HANDLER.config.plot_pause:
to_keep.append("pause")
return df[to_keep]

def _create_dummy_df(self) -> pd.DataFrame:
"""Create a dummy dataframe if no data is available."""
Expand Down
14 changes: 7 additions & 7 deletions src/ui_mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging
from collections.abc import Callable

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QAction, QApplication, QMainWindow, QMenu, QSystemTrayIcon
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import QApplication, QMainWindow, QMenu, QSystemTrayIcon

from src.database_controller import DB_CONTROLLER
from src.icons import get_preset_icons
Expand All @@ -24,7 +24,7 @@ def __init__(self):
"""Init. Many of the button and List connects are in pass_setup."""
super().__init__()
self.setupUi(self)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowMaximizeButtonHint) # type: ignore
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowMaximizeButtonHint)
self.icons = get_preset_icons()
self.clock_icon = self.icons.clock
self.connect_buttons()
Expand Down Expand Up @@ -77,7 +77,7 @@ def set_tray(self):

def add_tray_menu_option(self, tray_menu: QMenu, icon: QIcon, text: str, action: Callable[[], None]):
start_action = QAction(icon, text, self)
start_action.triggered.connect(action) # type: ignore
start_action.triggered.connect(action)
tray_menu.addAction(start_action)

def close_app(self):
Expand Down Expand Up @@ -155,13 +155,13 @@ def add_pause(self, check_past_entry: bool = True):

def get_past_date(self):
"""Return the date from the past datetime edit."""
qt_object = self.past_datetime_edit.dateTime() # type: ignore
qt_object = self.past_datetime_edit.dateTime()
qt_date = qt_object.date()
return datetime.date(qt_date.year(), qt_date.month(), qt_date.day())

def get_past_datetime(self):
"""Return the datetime from the past datetime edit."""
qt_object = self.past_datetime_edit.dateTime() # type: ignore
qt_object = self.past_datetime_edit.dateTime()
qt_date = qt_object.date()
qt_time = qt_object.time()
return datetime.datetime(
Expand Down
17 changes: 11 additions & 6 deletions src/ui_vacation_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import datetime
from typing import TYPE_CHECKING

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QHBoxLayout, QLabel, QListWidgetItem, QPushButton, QSizePolicy, QWidget
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QHBoxLayout, QLabel, QListWidgetItem, QPushButton, QSizePolicy, QWidget

from src.database_controller import DB_CONTROLLER
from src.icons import get_app_icon, get_preset_icons
Expand All @@ -22,8 +22,13 @@ def __init__(self, main_window: MainWindow):
self.main_window = main_window
self.setupUi(self)
self.setWindowIcon(get_app_icon())
self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint) # type: ignore
self.setAttribute(Qt.WA_DeleteOnClose) # type: ignore
self.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.CustomizeWindowHint
| Qt.WindowType.WindowTitleHint
| Qt.WindowType.WindowCloseButtonHint
)
self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose)

# setting dates to today
today = datetime.date.today()
Expand Down Expand Up @@ -54,13 +59,13 @@ def add_date_item(self, date: datetime.date):
line_text = QLabel(f"{date.strftime('%m-%d | %B')} {date.day}{suffix}")
line_text.setStyleSheet("font-size: 16px;")
delete_button = QPushButton("")
delete_button.clicked.connect(lambda: self.delete_date_item(item, date)) # type: ignore
delete_button.clicked.connect(lambda: self.delete_date_item(item, date))
delete_button.setIcon(get_preset_icons().delete_inverted)
# set red border and add a padding top bottom of 5px
delete_button.setStyleSheet("border: 1px solid red; border-radius: 5px; padding: 5px; background-color: red;")
# always shrink the button to the minimum size
delete_button.setMaximumWidth(30)
delete_button.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) # type: ignore
delete_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
item_layout = QHBoxLayout()
item_layout.setContentsMargins(0, 0, 0, 0) # Set margins to 0
item_layout.addWidget(line_text)
Expand Down
4 changes: 2 additions & 2 deletions src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import darkdetect
import qdarktheme
from PyQt5.QtWidgets import QApplication
from PyQt6.QtWidgets import QApplication

from src.filepath import (
CONFIG_PATH,
Expand Down Expand Up @@ -51,7 +51,7 @@ def get_additional_run_args() -> list[str]:
system = platform.system()
# windows need some extra love for the window header to be dark
if system == "Windows" and not is_light():
return ["-platform", "windows:darkmode=1"]
return ["-platform", "windows:darkmode=2"]
return []


Expand Down
Loading

0 comments on commit 9588480

Please sign in to comment.