Skip to content

Commit 93dfce4

Browse files
committed
created a main menu widget as an entry point for the app when no data.json file exist + added verification and a messagebox for invalid json files
1 parent 75c670d commit 93dfce4

10 files changed

+627
-25
lines changed

app.py

+64-13
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from widgets.projects_tab import ProjectsTab
88
from widgets.sessions_tab import SessionsTab
99
from widgets.py_files.dialog_about import Ui_dialog_about
10+
from widgets.py_files.widget_options_menu import Ui_widget_options_menu
1011
from widgets.dialog_create_data_file import DialogCreateDataFile
11-
from utils import open_dialog, get_data_path, set_data_path
12+
from utils import open_dialog, get_data_path, set_data_path, is_valid_file
1213

1314
class App(QtWidgets.QMainWindow):
1415
def __init__(self):
@@ -17,32 +18,71 @@ def __init__(self):
1718
self.ui.setupUi(self)
1819
self.data_path = get_data_path()
1920
self.tabs = []
21+
self.options_menu_widget = None
22+
self.tab_widget = None
2023
self.setups()
2124

2225
def setups(self):
2326
# Setup UI
24-
self.ui.tab_widget = QtWidgets.QTabWidget()
25-
self.ui.verticalLayout.addWidget(self.ui.tab_widget)
2627
self.ui.action_about.triggered.connect(lambda: open_dialog(Ui_dialog_about))
2728
self.ui.action_new.triggered.connect(lambda: DialogCreateDataFile(parent=self).exec())
2829
self.ui.action_open.triggered.connect(self.open_existing_file)
29-
if not os.path.exists(self.data_path):
30-
DialogCreateDataFile(parent=self).exec()
31-
self.tabs = [ProjectsTab(self), SessionsTab(self)]
32-
self.ui.tab_widget.addTab(self.tabs[0], "Projects")
33-
self.ui.tab_widget.addTab(self.tabs[1], "Sessions")
34-
self.ui.tab_widget.currentChanged.connect(lambda: self.ui.tab_widget.currentWidget().tab_changed())
30+
self.ui.action_close.triggered.connect(self.close_current_file)
31+
self.ui.action_exit.triggered.connect(self.close_app)
32+
if self.data_path == "" or not os.path.exists(self.data_path):
33+
self.setup_options_menu()
34+
self.ui.action_close.setEnabled(False)
35+
else:
36+
self.setup_tabs()
37+
self.ui.action_close.setEnabled(True)
3538
self.update_window_title()
3639

40+
def setup_options_menu(self):
41+
if self.tab_widget:
42+
self.ui.verticalLayout.removeWidget(self.tab_widget)
43+
self.tab_widget.hide()
44+
self.ui.verticalLayout.update()
45+
if not self.options_menu_widget:
46+
self.options_menu_widget = QtWidgets.QWidget()
47+
self.options_menu_widget.ui = Ui_widget_options_menu()
48+
self.options_menu_widget.ui.setupUi(self.options_menu_widget)
49+
self.options_menu_widget.ui.push_button_create_file.clicked.connect(lambda: DialogCreateDataFile(parent=self).exec())
50+
self.options_menu_widget.ui.push_button_open_file.clicked.connect(self.open_existing_file)
51+
else:
52+
self.options_menu_widget.show()
53+
self.ui.verticalLayout.addWidget(self.options_menu_widget)
54+
55+
def setup_tabs(self):
56+
if self.options_menu_widget:
57+
self.ui.verticalLayout.removeWidget(self.options_menu_widget)
58+
self.options_menu_widget.hide()
59+
self.ui.verticalLayout.update()
60+
if not self.tab_widget:
61+
self.tab_widget = QtWidgets.QTabWidget()
62+
self.tabs = [ProjectsTab(self), SessionsTab(self)]
63+
self.tab_widget.addTab(self.tabs[0], "Projects")
64+
self.tab_widget.addTab(self.tabs[1], "Sessions")
65+
self.tab_widget.currentChanged.connect(lambda: self.tab_widget.currentWidget().tab_changed())
66+
else:
67+
self.tab_widget.show()
68+
self.ui.verticalLayout.addWidget(self.tab_widget)
69+
3770
def open_existing_file(self):
3871
# Display a QFileDialog to select the data file
3972
data_path = QtWidgets.QFileDialog.getOpenFileName(
4073
caption="Select your data file",
4174
filter="*.json")[0]
42-
if data_path:
75+
if data_path and is_valid_file(data_path):
4376
self.data_path = data_path
44-
set_data_path(self.data_path)
45-
self.update_data_path()
77+
set_data_path(self.data_path)
78+
self.update_data_path()
79+
self.setup_tabs()
80+
else:
81+
#launch invalid file format dialog.
82+
QtWidgets.QMessageBox.critical(self,
83+
"Couldn't read the file",
84+
"Please select a file with the valid format or create a new one."
85+
)
4686

4787
def update_data_path(self):
4888
# Notify the tabs (children widgets) that the data file has changed.
@@ -54,7 +94,18 @@ def update_data_path(self):
5494
def update_window_title(self):
5595
# Changes the window title according to the data file in use.
5696
settings_path = self.data_path.replace(os.path.expanduser("~"), "~")
57-
self.setWindowTitle(f"Time Tracker ({settings_path})")
97+
settings_path = "" if len(settings_path) == 0 else f"({settings_path})"
98+
self.setWindowTitle(f"Time Tracker {settings_path}")
99+
100+
def close_current_file(self):
101+
self.setup_options_menu()
102+
self.data_path = ""
103+
self.update_window_title()
104+
set_data_path(self.data_path)
105+
#self.update_data_path()
106+
107+
def close_app(self):
108+
sys.exit()
58109

59110
if __name__ == "__main__":
60111
import sys

resources_rc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@
369369
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
370370
\x00\x00\x00\x00\x00\x00\x00\x00\
371371
\x00\x00\x00\x12\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
372-
\x00\x00\x01\x6f\x7c\x39\xca\xec\
372+
\x00\x00\x01\x6f\xf1\xb8\xaa\x2f\
373373
"
374374

375375
qt_version = [int(v) for v in QtCore.qVersion().split('.')]

state.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,9 @@ def add_timestamp(self, project_name):
5959

6060
def notify_update(self):
6161
# Notifies data changes to every state listener
62-
for l in self.listeners:
63-
l.update_data(self.data)
62+
if len(self.data_path) > 0:
63+
for l in self.listeners:
64+
l.update_data(self.data)
6465

6566
# time-tracker wrapper methods
6667
def has_ongoing_sessions(self, project_name):

utils.py

+19-5
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,30 @@ def open_dialog(ui_dialog):
1212
dialog.ui.setupUi(dialog)
1313
dialog.exec()
1414

15+
def read_json(path):
16+
f = open(path, "r")
17+
content = json.loads(f.read())
18+
f.close()
19+
return content
20+
1521
def get_data_path():
1622
# Reads the settings file to get the data file path
1723
path = "settings.json"
1824
if os.path.exists(path):
19-
f = open(path, "r")
20-
settings = json.loads(f.read())
21-
f.close()
22-
return settings.get("data_path")
25+
settings = read_json(path)
26+
if "data_path" in settings.keys():
27+
return settings.get("data_path")
28+
else:
29+
return None
30+
else:
31+
return None
32+
33+
def is_valid_file(path):
34+
if os.path.exists(path):
35+
data = read_json(path)
36+
return "projects" in data.keys() and type(data.get("projects")) == list
2337
else:
24-
return "./data.json"
38+
return False
2539

2640
def set_data_path(path):
2741
# Writes the settings file with a new data file path

widgets/dialog_create_data_file.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import json
55
import os
6-
from PyQt5 import QtWidgets
6+
from PyQt5 import QtWidgets, QtCore
77
from widgets.py_files.dialog_create_data_file import Ui_dialog_create_data_file
88
from widgets.py_files.dialog_file_exist import Ui_dialog_file_exist
99
from utils import set_data_path
@@ -23,6 +23,8 @@ def __init__(self, parent=None, filename="data", directory=None):
2323

2424
def setups(self):
2525
# Setup UI
26+
self.setWindowFlags(self.windowFlags() | QtCore.Qt.CustomizeWindowHint)
27+
#self.toggle_close_button_state()
2628
self.ui.label_directory_not_selected.hide()
2729
self.ui.push_btn_directory_selector.clicked.connect(self.open_file_selector)
2830
self.ui.push_btn_save.clicked.connect(self.create_data_file)
@@ -35,7 +37,8 @@ def open_file_selector(self):
3537
os.path.expanduser("~"),
3638
QtWidgets.QFileDialog.ShowDirsOnly
3739
)
38-
self.ui.label_selected_directory.setText(self.directory)
40+
if len(self.directory) > 0:
41+
self.ui.label_selected_directory.setText(self.directory)
3942

4043
def create_empty_data_file(self, filename):
4144
# Creates an empty JSON file with a data structure template
@@ -56,6 +59,7 @@ def create_data_file(self):
5659
self.filename = self.ui.line_edit_filename.text()
5760
if self.directory is not None:
5861
self.ui.label_directory_not_selected.hide()
62+
#self.toggle_close_button_state()
5963
self.filename = f"{self.directory}/{self.filename}.json"
6064
if os.path.exists(self.filename):
6165
dialog_file_exist = QtWidgets.QDialog()
@@ -67,3 +71,9 @@ def create_data_file(self):
6771
self.override_confirmed()
6872
else:
6973
self.ui.label_directory_not_selected.show()
74+
75+
def toggle_close_button_state(self):
76+
if not self.directory:
77+
self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, False)
78+
else:
79+
self.setWindowFlag(QtCore.Qt.WindowCloseButtonHint, True)

widgets/py_files/main.py

+25-1
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,13 @@ class Ui_MainWindow(object):
1414
def setupUi(self, MainWindow):
1515
MainWindow.setObjectName("MainWindow")
1616
MainWindow.resize(662, 392)
17-
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
17+
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)
1818
sizePolicy.setHorizontalStretch(0)
1919
sizePolicy.setVerticalStretch(0)
2020
sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
2121
MainWindow.setSizePolicy(sizePolicy)
2222
MainWindow.setMinimumSize(QtCore.QSize(662, 392))
23+
MainWindow.setMaximumSize(QtCore.QSize(662, 392))
2324
icon = QtGui.QIcon()
2425
icon.addPixmap(QtGui.QPixmap(":/images/icon_full.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
2526
MainWindow.setWindowIcon(icon)
@@ -40,13 +41,30 @@ def setupUi(self, MainWindow):
4041
self.menuHelp.setObjectName("menuHelp")
4142
MainWindow.setMenuBar(self.menubar)
4243
self.action_about = QtWidgets.QAction(MainWindow)
44+
icon = QtGui.QIcon.fromTheme("help")
45+
self.action_about.setIcon(icon)
4346
self.action_about.setObjectName("action_about")
4447
self.action_new = QtWidgets.QAction(MainWindow)
48+
icon = QtGui.QIcon.fromTheme("document-new")
49+
self.action_new.setIcon(icon)
50+
self.action_new.setShortcutVisibleInContextMenu(True)
4551
self.action_new.setObjectName("action_new")
4652
self.action_open = QtWidgets.QAction(MainWindow)
53+
icon = QtGui.QIcon.fromTheme("document-open")
54+
self.action_open.setIcon(icon)
4755
self.action_open.setObjectName("action_open")
56+
self.action_close = QtWidgets.QAction(MainWindow)
57+
icon = QtGui.QIcon.fromTheme("document-close")
58+
self.action_close.setIcon(icon)
59+
self.action_close.setObjectName("action_close")
60+
self.action_exit = QtWidgets.QAction(MainWindow)
61+
icon = QtGui.QIcon.fromTheme("application-exit")
62+
self.action_exit.setIcon(icon)
63+
self.action_exit.setObjectName("action_exit")
4864
self.menuFile.addAction(self.action_new)
4965
self.menuFile.addAction(self.action_open)
66+
self.menuFile.addAction(self.action_close)
67+
self.menuFile.addAction(self.action_exit)
5068
self.menuHelp.addAction(self.action_about)
5169
self.menubar.addAction(self.menuFile.menuAction())
5270
self.menubar.addAction(self.menuHelp.menuAction())
@@ -61,7 +79,13 @@ def retranslateUi(self, MainWindow):
6179
self.menuHelp.setTitle(_translate("MainWindow", "Help"))
6280
self.action_about.setText(_translate("MainWindow", "About"))
6381
self.action_new.setText(_translate("MainWindow", "New"))
82+
self.action_new.setShortcut(_translate("MainWindow", "Ctrl+N"))
6483
self.action_open.setText(_translate("MainWindow", "Open"))
84+
self.action_open.setShortcut(_translate("MainWindow", "Ctrl+O"))
85+
self.action_close.setText(_translate("MainWindow", "Close"))
86+
self.action_close.setShortcut(_translate("MainWindow", "Ctrl+W"))
87+
self.action_exit.setText(_translate("MainWindow", "Exit"))
88+
self.action_exit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
6589
import resources_rc
6690

6791

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Form implementation generated from reading ui file 'widgets/ui/options_menu_widget.ui'
4+
#
5+
# Created by: PyQt5 UI code generator 5.14.1
6+
#
7+
# WARNING! All changes made in this file will be lost!
8+
9+
10+
from PyQt5 import QtCore, QtGui, QtWidgets
11+
12+
13+
class Ui_options_menu_widget(object):
14+
def setupUi(self, options_menu_widget):
15+
options_menu_widget.setObjectName("options_menu_widget")
16+
options_menu_widget.resize(703, 356)
17+
font = QtGui.QFont()
18+
font.setFamily("Open Sans")
19+
options_menu_widget.setFont(font)
20+
self.verticalLayout_4 = QtWidgets.QVBoxLayout(options_menu_widget)
21+
self.verticalLayout_4.setObjectName("verticalLayout_4")
22+
self.verticalLayout_3 = QtWidgets.QVBoxLayout()
23+
self.verticalLayout_3.setObjectName("verticalLayout_3")
24+
spacerItem = QtWidgets.QSpacerItem(547, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
25+
self.verticalLayout_3.addItem(spacerItem)
26+
self.label = QtWidgets.QLabel(options_menu_widget)
27+
self.label.setMinimumSize(QtCore.QSize(0, 0))
28+
font = QtGui.QFont()
29+
font.setFamily("Open Sans")
30+
font.setPointSize(18)
31+
self.label.setFont(font)
32+
self.label.setScaledContents(True)
33+
self.label.setAlignment(QtCore.Qt.AlignCenter)
34+
self.label.setWordWrap(False)
35+
self.label.setObjectName("label")
36+
self.verticalLayout_3.addWidget(self.label)
37+
self.label_2 = QtWidgets.QLabel(options_menu_widget)
38+
font = QtGui.QFont()
39+
font.setPointSize(11)
40+
self.label_2.setFont(font)
41+
self.label_2.setStyleSheet("color:rgba(0,0,0,99)")
42+
self.label_2.setAlignment(QtCore.Qt.AlignCenter)
43+
self.label_2.setObjectName("label_2")
44+
self.verticalLayout_3.addWidget(self.label_2)
45+
self.verticalLayout_4.addLayout(self.verticalLayout_3)
46+
self.horizontalLayout = QtWidgets.QHBoxLayout()
47+
self.horizontalLayout.setObjectName("horizontalLayout")
48+
spacerItem1 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
49+
self.horizontalLayout.addItem(spacerItem1)
50+
self.verticalLayout = QtWidgets.QVBoxLayout()
51+
self.verticalLayout.setObjectName("verticalLayout")
52+
self.verticalLayout_2 = QtWidgets.QVBoxLayout()
53+
self.verticalLayout_2.setObjectName("verticalLayout_2")
54+
spacerItem2 = QtWidgets.QSpacerItem(547, 57, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
55+
self.verticalLayout_2.addItem(spacerItem2)
56+
self.push_button_create_file = QtWidgets.QPushButton(options_menu_widget)
57+
self.push_button_create_file.setLayoutDirection(QtCore.Qt.LeftToRight)
58+
self.push_button_create_file.setAutoFillBackground(False)
59+
self.push_button_create_file.setStyleSheet("padding:5")
60+
icon = QtGui.QIcon.fromTheme("document-new")
61+
self.push_button_create_file.setIcon(icon)
62+
self.push_button_create_file.setIconSize(QtCore.QSize(30, 30))
63+
self.push_button_create_file.setObjectName("push_button_create_file")
64+
self.verticalLayout_2.addWidget(self.push_button_create_file)
65+
spacerItem3 = QtWidgets.QSpacerItem(17, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
66+
self.verticalLayout_2.addItem(spacerItem3)
67+
self.push_button_open_file = QtWidgets.QPushButton(options_menu_widget)
68+
self.push_button_open_file.setLayoutDirection(QtCore.Qt.LeftToRight)
69+
self.push_button_open_file.setAutoFillBackground(False)
70+
self.push_button_open_file.setStyleSheet("padding:5")
71+
icon = QtGui.QIcon.fromTheme("document-open")
72+
self.push_button_open_file.setIcon(icon)
73+
self.push_button_open_file.setIconSize(QtCore.QSize(30, 30))
74+
self.push_button_open_file.setFlat(False)
75+
self.push_button_open_file.setObjectName("push_button_open_file")
76+
self.verticalLayout_2.addWidget(self.push_button_open_file)
77+
self.verticalLayout.addLayout(self.verticalLayout_2)
78+
spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
79+
self.verticalLayout.addItem(spacerItem4)
80+
self.horizontalLayout.addLayout(self.verticalLayout)
81+
spacerItem5 = QtWidgets.QSpacerItem(60, 20, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum)
82+
self.horizontalLayout.addItem(spacerItem5)
83+
self.verticalLayout_4.addLayout(self.horizontalLayout)
84+
85+
self.retranslateUi(options_menu_widget)
86+
QtCore.QMetaObject.connectSlotsByName(options_menu_widget)
87+
88+
def retranslateUi(self, options_menu_widget):
89+
_translate = QtCore.QCoreApplication.translate
90+
options_menu_widget.setWindowTitle(_translate("options_menu_widget", "Options Menu"))
91+
self.label.setText(_translate("options_menu_widget", "Track the time you spend working on your projects"))
92+
self.label_2.setText(_translate("options_menu_widget", "Start by creating a new file or opening a file to store your data"))
93+
self.push_button_create_file.setText(_translate("options_menu_widget", " Create new data file"))
94+
self.push_button_create_file.setShortcut(_translate("options_menu_widget", "Ctrl+N"))
95+
self.push_button_open_file.setText(_translate("options_menu_widget", " Open existing data file"))
96+
self.push_button_open_file.setShortcut(_translate("options_menu_widget", "Ctrl+O"))
97+
98+
99+
if __name__ == "__main__":
100+
import sys
101+
app = QtWidgets.QApplication(sys.argv)
102+
options_menu_widget = QtWidgets.QWidget()
103+
ui = Ui_options_menu_widget()
104+
ui.setupUi(options_menu_widget)
105+
options_menu_widget.show()
106+
sys.exit(app.exec_())

0 commit comments

Comments
 (0)