diff --git a/.gitignore b/.gitignore index 3d9fc1c..c63b1ef 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,6 @@ __pycache__ *.pyc src/default/ *.json -*.csv \ No newline at end of file +*.csv +dist/ +build/ \ No newline at end of file diff --git a/Timer.spec b/Timer.spec new file mode 100644 index 0000000..6fd0ef9 --- /dev/null +++ b/Timer.spec @@ -0,0 +1,63 @@ +# -*- mode: python ; coding: utf-8 -*- + + +a = Analysis( + ['src/main.py'], + pathex=[], + binaries=[], + datas=[ + ('resources/close.svg', 'resources'), + ('resources/dropdown.svg', 'resources'), + ('resources/fullscreen.svg', 'resources'), + ('resources/icon.icns', 'resources'), + ('resources/minimize.svg', 'resources'), + ('resources/fonts/satoshi_bold.otf', 'resources/fonts'), + ('resources/fonts/satoshi_medium.otf', 'resources/fonts'), + ('resources/fonts/satoshi_regular.otf', 'resources/fonts'), + ('resources/fonts/license.txt', 'resources/fonts'), + ('src/default/settings.json', 'src/default'), + ('src/default/timesheet.csv', 'src/default'), + ], + hiddenimports=[], + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=[], + noarchive=False, + optimize=0, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name='Timer', + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=True, + console=False, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon=['resources/icon.icns'], +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=True, + upx_exclude=[], + name='Timer', +) +app = BUNDLE( + coll, + name='Timer.app', + icon='resources/icon.icns', + bundle_identifier=None, +) diff --git a/readme.txt b/readme.txt index 11cd558..323a3fd 100644 --- a/readme.txt +++ b/readme.txt @@ -1,8 +1,8 @@ -Time Tracker +Timer -The application allows you to track time for different projects. -The data is stored locally in a csv file. The location of the -csv file can be changed from the settings. +A desktop application that allows you to track time for multiple different +projects. The data is stored locally in a csv file. The location of the csv file +can be changed from the settings menu. --- @@ -16,7 +16,8 @@ Project Setup 2. Create a virtual environment and activate it python3 venv .venv - source .venv/bin/activate + source .venv/bin/activate # For Linux and MacOS + .venv\Scripts\activate # For Windows 3. Install the required packages @@ -30,3 +31,19 @@ Project Setup cd src python3 main.py + + +--- + +Creating Executables + +1. Install PyInstaller + + pip3 install pyinstaller + +2. Bundle the application + + pyinstaller Timer.spec + + +It will create a `dist` directory with the executable files. \ No newline at end of file diff --git a/resources/icon.icns b/resources/icon.icns new file mode 100644 index 0000000..9a202c6 Binary files /dev/null and b/resources/icon.icns differ diff --git a/src/interface.py b/src/interface.py index 53621a0..dbf00ae 100644 --- a/src/interface.py +++ b/src/interface.py @@ -27,7 +27,7 @@ class MainWindow(QMainWindow): """ - Main application window for the Time Tracker. + Main application window for the Timer. """ def __init__(self): @@ -46,7 +46,7 @@ def __init__(self): self.setMaximumHeight(210) self.setMinimumHeight(210) - self.setWindowTitle("Time Tracker") + self.setWindowTitle("Timer") # Remove the default title bar self.setWindowFlags(Qt.WindowType.FramelessWindowHint) @@ -70,6 +70,10 @@ def __init__(self): self.project_combo.currentTextChanged.connect(self.project_changed) self.project_combo.setFont(self.custom_style.regular_font) + initial_project = self.project_combo.currentText() + if initial_project: + self.project_changed(initial_project) + self.settings_button = QPushButton("Settings") self.view_log_button = QPushButton("Timesheet") @@ -110,8 +114,6 @@ def __init__(self): self.settings_button.clicked.connect(self.open_settings) self.view_log_button.clicked.connect(self.view_log) - self.refresh_ui() - def toggle_timer(self): if self.timer.start_time is None: diff --git a/src/project_manager.py b/src/project_manager.py index 7b369d0..ba3fa31 100644 --- a/src/project_manager.py +++ b/src/project_manager.py @@ -1,17 +1,29 @@ import json import os +import sys + + +if getattr(sys, 'frozen', False): + script_dir = sys._MEIPASS + settings_dir = os.path.join(script_dir, 'src/default/settings.json') + timesheet_dir = os.path.join(script_dir, 'src/default/timesheet.csv') +else: + script_dir = os.path.dirname(os.path.abspath(__file__)) + settings_dir = os.path.join(script_dir, 'default/settings.json') + timesheet_dir = os.path.join(script_dir, 'default/timesheet.csv') + class ProjectManager: """ Class to manage projects and settings """ - def __init__(self, settings_file='default/settings.json'): + def __init__(self, settings_file=settings_dir): self.settings_file = settings_file self.settings = self.load_settings() self.version = self.settings.get('version', '1.0.0') self.projects = self.settings.get('projects', []) - self.csv_file_path = self.settings.get('csv_file_path', 'default/timesheet.csv') + self.csv_file_path = self.settings.get('csv_file_path', timesheet_dir) def load_settings(self): @@ -21,7 +33,7 @@ def load_settings(self): return { "version": "1.0.0", "projects": [], - "csv_file_path": "default/timesheet.csv" + "csv_file_path": timesheet_dir } diff --git a/src/style.py b/src/style.py index 58ea603..b1243f3 100644 --- a/src/style.py +++ b/src/style.py @@ -1,6 +1,6 @@ from PyQt6.QtGui import QFont, QFontDatabase import os - +import sys class Style: """ @@ -14,8 +14,15 @@ class Style: def __init__(self): super().__init__() - script_dir = os.path.dirname(os.path.abspath(__file__)) - font_dir = os.path.join(script_dir, '../resources/fonts') + # Determine if the application is running as a bundled executable + if getattr(sys, 'frozen', False): + script_dir = sys._MEIPASS + font_dir = os.path.join(script_dir, 'resources/fonts') + dropdown_icon = os.path.join(script_dir, 'resources/dropdown.svg') + else: + script_dir = os.path.dirname(os.path.abspath(__file__)) + font_dir = os.path.join(script_dir, '../resources/fonts') + dropdown_icon = os.path.join(script_dir, '../resources/dropdown.svg') self.regular_font_id = QFontDatabase.addApplicationFont(os.path.join(font_dir, "satoshi_regular.otf")) self.medium_font_id = QFontDatabase.addApplicationFont(os.path.join(font_dir, "satoshi_medium.otf")) @@ -23,7 +30,6 @@ def __init__(self): if self.regular_font_id == -1: print("Failed to load font.") - exit() font_family = QFontDatabase.applicationFontFamilies(self.regular_font_id)[0] self.regular_font = QFont(font_family) @@ -81,31 +87,31 @@ def __init__(self): text-align: right; } """ - self.combo_box = """ - QComboBox { + self.combo_box = f""" + QComboBox {{ background-color: #383838; color: #CACACA; border: none; padding: 5px 8px; border-radius: 4px; font-size: 14px; - } - QComboBox QAbstractItemView { + }} + QComboBox QAbstractItemView {{ background-color: #383838; color: #ffffff; padding: 5px 1px; border: 1px solid #4F4F4F; border-radius: 4px; margin: 0px; - } - QComboBox::drop-down { + }} + QComboBox::drop-down {{ padding-right: 8px; border: none; border-radius: 4px; - } - QComboBox::down-arrow { - image: url(../resources/dropdown.svg); - } + }} + QComboBox::down-arrow {{ + image: url({dropdown_icon}); + }} """ self.widget = """ QWidget { diff --git a/src/title_bar.py b/src/title_bar.py index 0db8ad0..2f1c299 100644 --- a/src/title_bar.py +++ b/src/title_bar.py @@ -2,6 +2,8 @@ from PyQt6.QtCore import Qt, QSize, QPoint from PyQt6.QtGui import QIcon from style import Style +import sys +import os class CustomTitleBar(QWidget): @@ -10,6 +12,17 @@ def __init__(self, parent=None, allow_fullscreen=True): self.parent = parent self.custom_style = Style() + if getattr(sys, 'frozen', False): + script_dir = sys._MEIPASS + close_icon = os.path.join(script_dir, 'resources/close.svg') + minimize_icon = os.path.join(script_dir, 'resources/minimize.svg') + fullscreen_icon = os.path.join(script_dir, 'resources/fullscreen.svg') + else: + script_dir = os.path.dirname(os.path.abspath(__file__)) + close_icon = os.path.join(script_dir, '../resources/close.svg') + minimize_icon = os.path.join(script_dir, '../resources/minimize.svg') + fullscreen_icon = os.path.join(script_dir, '../resources/fullscreen.svg') + # Variables to track dragging self.dragging = False self.drag_start_position = QPoint() @@ -32,14 +45,14 @@ def __init__(self, parent=None, allow_fullscreen=True): # Add close button to the title bar close_button = QPushButton() - close_button.setIcon(QIcon("../resources/close.svg")) + close_button.setIcon(QIcon(close_icon)) close_button.setIconSize(QSize(10, 10)) close_button.setStyleSheet(self.custom_style.button_title_bar + "background-color:#BF616A;}") close_button.setFont(self.custom_style.bold_font) # Add minimize button to the title bar minimize_button = QPushButton() - minimize_button.setIcon(QIcon("../resources/minimize.svg")) + minimize_button.setIcon(QIcon(minimize_icon)) minimize_button.setIconSize(QSize(10, 10)) minimize_button.setStyleSheet(self.custom_style.button_title_bar + "background-color:#EBCB8B;}") minimize_button.setFont(self.custom_style.bold_font) @@ -57,7 +70,7 @@ def __init__(self, parent=None, allow_fullscreen=True): # Add fullscreen button to the title bar if allow_fullscreen: fullscreen_button = QPushButton() - fullscreen_button.setIcon(QIcon("../resources/fullscreen.svg")) + fullscreen_button.setIcon(QIcon(fullscreen_icon)) fullscreen_button.setIconSize(QSize(10, 10)) fullscreen_button.setStyleSheet(self.custom_style.button_title_bar + "background-color:#A3BE8C;}") fullscreen_button.setFont(self.custom_style.bold_font)