bpappbuilder 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bpappbuilder/__init__.py +7 -0
- bpappbuilder/app/__init__.py +5 -0
- bpappbuilder/app/app.py +134 -0
- bpappbuilder/app/db.py +47 -0
- bpappbuilder/app/styles.qss +243 -0
- bpappbuilder/app/themes.py +72 -0
- bpappbuilder/fields/__init__.py +29 -0
- bpappbuilder/fields/combo_box_items.py +93 -0
- bpappbuilder/fields/field.py +1194 -0
- bpappbuilder/reports/__init__.py +21 -0
- bpappbuilder/reports/report.py +1075 -0
- bpappbuilder/tabs/__init__.py +24 -0
- bpappbuilder/tabs/group.py +31 -0
- bpappbuilder/tabs/tab.py +80 -0
- bpappbuilder/tabs/table.py +544 -0
- bpappbuilder/tabs/table_tab.py +290 -0
- bpappbuilder-0.1.0.dist-info/METADATA +21 -0
- bpappbuilder-0.1.0.dist-info/RECORD +20 -0
- bpappbuilder-0.1.0.dist-info/WHEEL +4 -0
- bpappbuilder-0.1.0.dist-info/licenses/LICENSE +21 -0
bpappbuilder/__init__.py
ADDED
bpappbuilder/app/app.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QApplication, QMainWindow, QTabWidget, QWidget, QVBoxLayout, QLabel
|
|
2
|
+
from PySide6.QtGui import QIcon
|
|
3
|
+
|
|
4
|
+
from ..tabs.tab import Tab
|
|
5
|
+
from ..tabs.group import TabGroup
|
|
6
|
+
from .db import DataBase
|
|
7
|
+
from .themes import apply_styles
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
import logging
|
|
11
|
+
import sys
|
|
12
|
+
import os
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logging.basicConfig(level=logging.DEBUG,
|
|
16
|
+
format="%(asctime)s %(module)s %(funcName)s: %(lineno)d - %(levelname)s - %(message)s",
|
|
17
|
+
datefmt='%H:%M:%S')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class App:
|
|
21
|
+
"""
|
|
22
|
+
Класс приложения с таблицами
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, name: str, db: DataBase, window_width: int, window_height: int, icon_path: Optional[str] = None):
|
|
26
|
+
"""
|
|
27
|
+
Args:
|
|
28
|
+
name (str): Отображаемое название приложения
|
|
29
|
+
db (DataBase): Объект базы данных
|
|
30
|
+
window_width (int): Начальная ширина окна приложения в пикселях
|
|
31
|
+
window_height (int): Начальная ширина окна приложения в пикселях
|
|
32
|
+
icon_path (str, optional): Путь к файлу изображения, который будет использоваться как иконка приложения
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
self.tabs: list[Tab] = []
|
|
36
|
+
self.groups: list[TabGroup] = []
|
|
37
|
+
self.icon_path = icon_path
|
|
38
|
+
|
|
39
|
+
self.db = db
|
|
40
|
+
self.name = name
|
|
41
|
+
self.window_width = window_width
|
|
42
|
+
self.window_height = window_height
|
|
43
|
+
|
|
44
|
+
logging._defaultLastResort
|
|
45
|
+
|
|
46
|
+
def add_tab(self, tab: Tab):
|
|
47
|
+
"""
|
|
48
|
+
Добавление вкладки в приложение
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
tab (Tab): Объект вкладки
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
tab.app_add_tab(self.db)
|
|
55
|
+
|
|
56
|
+
self.tabs.append(tab)
|
|
57
|
+
|
|
58
|
+
def add_group(self, group: TabGroup):
|
|
59
|
+
"""
|
|
60
|
+
Добавление группы вкладок в приложение.
|
|
61
|
+
После добавления хотя бы одной группы, добавленные вне групп вкладки отображаться не будут.
|
|
62
|
+
Args:
|
|
63
|
+
group (TabGroup): Объект группы вкладок
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
for tab in group.tabs:
|
|
67
|
+
tab.app_add_tab(self.db)
|
|
68
|
+
|
|
69
|
+
self.groups.append(group)
|
|
70
|
+
|
|
71
|
+
def run(self):
|
|
72
|
+
"""
|
|
73
|
+
Запуск приложения
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
# TODO: Добавить поддержку тёмной темы
|
|
77
|
+
|
|
78
|
+
# Создание базы данных
|
|
79
|
+
if len(self.groups) > 0: # Если есть группы, то используем только вкладки из них
|
|
80
|
+
self.tabs = []
|
|
81
|
+
for group in self.groups:
|
|
82
|
+
self.tabs.extend(group.tabs)
|
|
83
|
+
|
|
84
|
+
# Создание объекта приложения
|
|
85
|
+
app = QApplication(sys.argv)
|
|
86
|
+
|
|
87
|
+
# Создание главного окна
|
|
88
|
+
window_widget = QMainWindow()
|
|
89
|
+
window_widget.setWindowTitle(self.name)
|
|
90
|
+
if self.icon_path is not None:
|
|
91
|
+
window_widget.setWindowIcon(QIcon(self.icon_path))
|
|
92
|
+
window_widget.resize(self.window_width, self.window_height)
|
|
93
|
+
|
|
94
|
+
container = QWidget()
|
|
95
|
+
container.setProperty("class", "main-container")
|
|
96
|
+
container_layout = QVBoxLayout()
|
|
97
|
+
container_layout.setContentsMargins(0, 0, 0, 0)
|
|
98
|
+
container.setLayout(container_layout)
|
|
99
|
+
|
|
100
|
+
apply_styles(window_widget)
|
|
101
|
+
QApplication.instance().paletteChanged.connect(lambda: apply_styles(window_widget))
|
|
102
|
+
|
|
103
|
+
if len(self.groups) > 0:
|
|
104
|
+
groups_tab_widget = QTabWidget()
|
|
105
|
+
groups_tab_widget.setProperty("class", "groups-tab")
|
|
106
|
+
|
|
107
|
+
tab_bar = groups_tab_widget.tabBar()
|
|
108
|
+
tab_bar.setProperty("class", "groups-tab-bar")
|
|
109
|
+
|
|
110
|
+
for group in self.groups:
|
|
111
|
+
tabs_tab_widget = QTabWidget()
|
|
112
|
+
for tab in group.tabs:
|
|
113
|
+
tabs_tab_widget.addTab(tab.create_tab_widget(), tab.name)
|
|
114
|
+
tabs_tab_widget.setCurrentIndex(0)
|
|
115
|
+
|
|
116
|
+
groups_tab_widget.addTab(tabs_tab_widget, group.name)
|
|
117
|
+
|
|
118
|
+
groups_tab_widget.setCurrentIndex(0)
|
|
119
|
+
container_layout.addWidget(groups_tab_widget)
|
|
120
|
+
else:
|
|
121
|
+
tabs_widget = QTabWidget()
|
|
122
|
+
|
|
123
|
+
for tab in self.tabs:
|
|
124
|
+
tabs_widget.addTab(tab.create_tab_widget(), tab.name)
|
|
125
|
+
|
|
126
|
+
tabs_widget.setCurrentIndex(0)
|
|
127
|
+
container_layout.addWidget(tabs_widget)
|
|
128
|
+
|
|
129
|
+
window_widget.setCentralWidget(container)
|
|
130
|
+
|
|
131
|
+
window_widget.show()
|
|
132
|
+
|
|
133
|
+
app.exec()
|
|
134
|
+
self.db.close()
|
bpappbuilder/app/db.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import sqlite3
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
class DataBase(ABC):
|
|
7
|
+
"""
|
|
8
|
+
Базовый класс базы данных, имеющий поля conn и cur
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@abstractmethod
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.conn = None
|
|
14
|
+
self.cur = None
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def close(self):
|
|
18
|
+
"""
|
|
19
|
+
Закрытие соединения с БД при завершении работы приложения
|
|
20
|
+
"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
class SQLiteDB(DataBase):
|
|
24
|
+
"""
|
|
25
|
+
База данных SQLite
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, path: str):
|
|
29
|
+
"""
|
|
30
|
+
Args:
|
|
31
|
+
path (str): Путь к .db файлу
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
self.conn = sqlite3.connect(path, check_same_thread = False)
|
|
35
|
+
self.conn.row_factory = sqlite3.Row
|
|
36
|
+
self.conn.set_trace_callback(self.log) # Логирование всех запросов к БД
|
|
37
|
+
self.cur = self.conn.cursor()
|
|
38
|
+
|
|
39
|
+
self.cur.execute("PRAGMA foreign_keys = ON")
|
|
40
|
+
self.conn.commit()
|
|
41
|
+
|
|
42
|
+
def close(self):
|
|
43
|
+
self.cur.close()
|
|
44
|
+
self.conn.close()
|
|
45
|
+
|
|
46
|
+
def log(self, statement: str):
|
|
47
|
+
logging.info(f"SQlite: \033[2;32m{statement}\033[0m")
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
.main-container {
|
|
2
|
+
margin: 0;
|
|
3
|
+
padding: 0;
|
|
4
|
+
background-color: var(--groups-bar-bg);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
QTabWidget QGroupBox {
|
|
8
|
+
border: 1px solid var(--default-widget-border);
|
|
9
|
+
border-radius: 1px;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
QTabWidget::pane {
|
|
13
|
+
border-top: 1px solid var(--tab-border);
|
|
14
|
+
background-color: var(--app-bg);
|
|
15
|
+
margin: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
QTabWidget:tab-bar {
|
|
19
|
+
left: 1px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
QTabBar::tab {
|
|
23
|
+
background: var(--unselected-tab-bg);
|
|
24
|
+
border: 1px solid var(--tab-border);
|
|
25
|
+
border-bottom-color: var(--app-bg);
|
|
26
|
+
border-top-left-radius: 4px;
|
|
27
|
+
border-top-right-radius: 4px;
|
|
28
|
+
min-width: 8ex;
|
|
29
|
+
padding: 3px;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
QTabBar::tab:selected, QTabBar::tab:hover {
|
|
33
|
+
background: var(--app-bg);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
QTabBar::tab:selected {
|
|
37
|
+
border-width: 2px;
|
|
38
|
+
border-color: var(--selected-tab-border);
|
|
39
|
+
border-bottom-color: var(--app-bg);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.groups-tab::pane {
|
|
43
|
+
border: 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.groups-tab-bar::tab {
|
|
47
|
+
border: 0;
|
|
48
|
+
border-radius: 0;
|
|
49
|
+
padding: 15px 25px;
|
|
50
|
+
font-size: 14px;
|
|
51
|
+
background-color: var(--groups-bar-bg);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.groups-tab-bar::tab:selected, .groups-tab-bar::tab:hover {
|
|
55
|
+
background: var(--app-bg);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
QDateTimeEdit {
|
|
59
|
+
border: 1px solid var(--default-widget-border);
|
|
60
|
+
border-radius: 3px;
|
|
61
|
+
padding: 3px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
QDateTimeEdit:focus {
|
|
65
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
QTimeEdit {
|
|
69
|
+
border: 1px solid var(--default-widget-border);
|
|
70
|
+
border-radius: 3px;
|
|
71
|
+
padding: 3px;
|
|
72
|
+
padding-top: 2px;
|
|
73
|
+
height: 22px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
QTimeEdit:focus {
|
|
77
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
QComboBox {
|
|
81
|
+
/*background-color: white;*/
|
|
82
|
+
border: 1px solid var(--default-widget-border);
|
|
83
|
+
border-radius: 3px;
|
|
84
|
+
padding: 1px 5px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
QComboBox:on {
|
|
88
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
QSpinBox {
|
|
92
|
+
border: 1px solid var(--default-widget-border);
|
|
93
|
+
border-radius: 3px;
|
|
94
|
+
/*background-color: white;*/
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
QSpinBox:focus {
|
|
98
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
QDoubleSpinBox {
|
|
102
|
+
border: 1px solid var(--default-widget-border);
|
|
103
|
+
border-radius: 3px;
|
|
104
|
+
/*background-color: white;*/
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
QDoubleSpinBox:focus {
|
|
108
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
QTextEdit {
|
|
112
|
+
border: 1px solid var(--default-widget-border);
|
|
113
|
+
border-radius: 3px;
|
|
114
|
+
/*background-color: white;*/
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
QTextEdit:focus {
|
|
118
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
QLineEdit {
|
|
122
|
+
border: 1px solid var(--default-widget-border);
|
|
123
|
+
border-radius: 3px;
|
|
124
|
+
padding: 2px;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
QLineEdit:focus {
|
|
128
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
QPushButton {
|
|
132
|
+
border: 1px solid var(--default-widget-border);
|
|
133
|
+
border-radius: 4px;
|
|
134
|
+
background-color: var(--gray-btn-bg);
|
|
135
|
+
padding: 3px 8px 3px 8px;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
QPushButton:hover {
|
|
139
|
+
background-color: var(--hover-gray-btn-bg);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
QPushButton:focus {
|
|
143
|
+
border: 2px solid var(--default-selected-widget-border);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
QPushButton:pressed {
|
|
147
|
+
background-color: var(--pressed-gray-btn-bg);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.table-line-edit {
|
|
151
|
+
border: 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.form-add {
|
|
155
|
+
margin: 0px;
|
|
156
|
+
padding: 0px;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.add-button {
|
|
160
|
+
border: 0;
|
|
161
|
+
height: 25px;
|
|
162
|
+
background-color: var(--add-btn-bg);
|
|
163
|
+
color: white;
|
|
164
|
+
font-weight: 700;
|
|
165
|
+
font-size: 13px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.add-button:hover {
|
|
169
|
+
background-color: var(--hover-add-btn-bg);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.add-button:pressed {
|
|
173
|
+
background-color: var(--pressed-add-btn-bg);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.red-button {
|
|
177
|
+
background-color: var(--red-btn-bg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.red-button:hover {
|
|
181
|
+
background-color: var(--hover-red-btn);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.red-button:pressed {
|
|
185
|
+
background-color: var(--pressed-red-btn-bg);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/*.red-button::focus {
|
|
189
|
+
border-color: #d10000;
|
|
190
|
+
}*/
|
|
191
|
+
|
|
192
|
+
.green-button {
|
|
193
|
+
background-color: var(--green-btn-bg);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.green-button:hover {
|
|
197
|
+
background-color: var(--hover-green-btn-bg);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.green-button:pressed {
|
|
201
|
+
background-color: var(--pressed-green-btn-bg);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/*.green-button::focus {
|
|
205
|
+
border-color: #059100;
|
|
206
|
+
}*/
|
|
207
|
+
|
|
208
|
+
.gray-button {
|
|
209
|
+
background-color: var(--gray-btn-bg);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.gray-button:hover {
|
|
213
|
+
background-color: var(--hover-gray-btn-bg);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.gray-button:pressed {
|
|
217
|
+
background-color: var(--pressed-gray-btn-bg);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.gray-button::focus {
|
|
221
|
+
border-color: var(--default-selected-widget-border);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.form-label {
|
|
225
|
+
font-size: 17pt;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.rowdatadialog-label {
|
|
229
|
+
font-size: 13pt;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.submitdialog-label {
|
|
233
|
+
font-size: 10pt;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.report-section {
|
|
237
|
+
border: 1px solid var(--report-section-border);
|
|
238
|
+
border-radius: 5px;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.report-widget {
|
|
242
|
+
background-color: var(--report-widget-bg);
|
|
243
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QWidget, QApplication
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
THEMES = {
|
|
7
|
+
"light": {
|
|
8
|
+
"--app-bg": "#eee",
|
|
9
|
+
"--tab-border": "#C2C7CB",
|
|
10
|
+
"--selected-tab-border": "#679dcf",
|
|
11
|
+
"--unselected-tab-bg": "#e4e4e4",
|
|
12
|
+
"--groups-bar-bg": "#e3e3e3",
|
|
13
|
+
"--default-widget-border": "gray",
|
|
14
|
+
"--default-selected-widget-border": "#3484cf",
|
|
15
|
+
"--gray-btn-bg": "#e3e3e3",
|
|
16
|
+
"--hover-gray-btn-bg": "#dbdbdb",
|
|
17
|
+
"--pressed-gray-btn-bg": "#d4d4d4",
|
|
18
|
+
"--add-btn-bg": "#3484cf",
|
|
19
|
+
"--hover-add-btn-bg": "#2679c7",
|
|
20
|
+
"--pressed-add-btn-bg": "#1f6eb8",
|
|
21
|
+
"--red-btn-bg": "#ff4d4d",
|
|
22
|
+
"--hover-red-btn": "#f74444",
|
|
23
|
+
"--pressed-red-btn-bg": "#f13737",
|
|
24
|
+
"--green-btn-bg": "#30c72b",
|
|
25
|
+
"--hover-green-btn-bg": "#30be2b",
|
|
26
|
+
"--pressed-green-btn-bg": "#33af2e",
|
|
27
|
+
"--report-section-border": "#E6E6E6",
|
|
28
|
+
"--report-widget-bg": "white",
|
|
29
|
+
},
|
|
30
|
+
"dark": {
|
|
31
|
+
"--app-bg": "#111215",
|
|
32
|
+
"--tab-border": "#545454",
|
|
33
|
+
"--selected-tab-border": "#2472d1",
|
|
34
|
+
"--unselected-tab-bg": "#1c1e23",
|
|
35
|
+
"--groups-bar-bg": "#1c1e23",
|
|
36
|
+
"--default-widget-border": "#545454",
|
|
37
|
+
"--default-selected-widget-border": "#3484cf",
|
|
38
|
+
"--gray-btn-bg": "#24262c",
|
|
39
|
+
"--hover-gray-btn-bg": "#282a30",
|
|
40
|
+
"--pressed-gray-btn-bg": "#31333b",
|
|
41
|
+
"--add-btn-bg": "#3484cf",
|
|
42
|
+
"--hover-add-btn-bg": "#2679c7",
|
|
43
|
+
"--pressed-add-btn-bg": "#1f6eb8",
|
|
44
|
+
"--red-btn-bg": "#ff2b2f",
|
|
45
|
+
"--hover-red-btn": "#f74444",
|
|
46
|
+
"--pressed-red-btn-bg": "#e62026",
|
|
47
|
+
"--green-btn-bg": "#35b62e",
|
|
48
|
+
"--hover-green-btn-bg": "#30be2b",
|
|
49
|
+
"--pressed-green-btn-bg": "#33af2e",
|
|
50
|
+
"--report-section-border": "#414141",
|
|
51
|
+
"--report-widget-bg": "#15161a",
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
def apply_styles(widget: QWidget, previous_theme: Optional[str] = None) -> str:
|
|
56
|
+
app = QApplication.instance()
|
|
57
|
+
palette = app.palette()
|
|
58
|
+
|
|
59
|
+
is_dark = palette.color(palette.ColorRole.Window).lightness() < 128
|
|
60
|
+
theme = "dark" if is_dark else "light"
|
|
61
|
+
|
|
62
|
+
if previous_theme == theme:
|
|
63
|
+
return theme
|
|
64
|
+
|
|
65
|
+
with open(os.path.dirname(os.path.realpath(__file__)) + "/styles.qss", "r") as f:
|
|
66
|
+
style = f.read()
|
|
67
|
+
for var, data in THEMES[theme].items():
|
|
68
|
+
style = style.replace(f"var({var})", data)
|
|
69
|
+
widget.setStyleSheet(style)
|
|
70
|
+
|
|
71
|
+
return theme
|
|
72
|
+
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from .combo_box_items import (
|
|
2
|
+
NameGenerator,
|
|
3
|
+
StandardNameGenerators,
|
|
4
|
+
StaticItems,
|
|
5
|
+
ComboBoxItems,
|
|
6
|
+
TableFieldItems,
|
|
7
|
+
TableItems
|
|
8
|
+
)
|
|
9
|
+
from .field import (
|
|
10
|
+
OnChangeHandler,
|
|
11
|
+
Field,
|
|
12
|
+
CheckField,
|
|
13
|
+
IntField,
|
|
14
|
+
FloatField,
|
|
15
|
+
TextLineField,
|
|
16
|
+
TextAreaField,
|
|
17
|
+
ComboBoxField,
|
|
18
|
+
DateTimeField,
|
|
19
|
+
DateField,
|
|
20
|
+
TimeField,
|
|
21
|
+
FileField,
|
|
22
|
+
TableField,
|
|
23
|
+
LabelField
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__all__ = ["NameGenerator", "StandardNameGenerators", "ComboBoxItems", "StaticItems", "TableFieldItems",
|
|
27
|
+
"TableItems", "OnChangeHandler", "Field", "CheckField", "IntField", "FloatField",
|
|
28
|
+
"TextLineField", "TextAreaField", "ComboBoxField", "DateTimeField", "DateField",
|
|
29
|
+
"TimeField", "FileField", "TableField", "LabelField"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from ..app.db import DataBase
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Optional, Callable, Sequence, Any, Iterable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
NameGenerator = Callable[[Sequence[Any]], str]
|
|
8
|
+
|
|
9
|
+
class StandardNameGenerators():
|
|
10
|
+
def Id(data: dict[str, str]) -> str:
|
|
11
|
+
return "#" + data["id"]
|
|
12
|
+
def DateTime(data: dict[str, str]) -> str:
|
|
13
|
+
return "от " + data["date"]
|
|
14
|
+
def IdDateTime(data: dict[str, str]) -> str:
|
|
15
|
+
return "#" + data["id"] + " от " + data["date"]
|
|
16
|
+
|
|
17
|
+
class ComboBoxItems(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Класс данных, из которых будут формироваться элементы выбора в ComboBoxField
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def get_items(self) -> dict[int, str]:
|
|
24
|
+
"""
|
|
25
|
+
Получает списки с элементами для ComboBoxField
|
|
26
|
+
Returns:
|
|
27
|
+
dict Словарь, где ключи - данные элемента, которые будут сохранены в БД,
|
|
28
|
+
а значения - отображаемый текст элемента
|
|
29
|
+
"""
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
class StaticItems(ComboBoxItems):
|
|
33
|
+
"""
|
|
34
|
+
Статичный неизменяемый список элементов для ComboBox
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, items: list[str]):
|
|
38
|
+
"""
|
|
39
|
+
Args:
|
|
40
|
+
items (list[str]): Список строк для элементов
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
self.items = items
|
|
44
|
+
|
|
45
|
+
def get_items(self) -> dict[int, str]:
|
|
46
|
+
return {i: value for i, value in enumerate(self.items)}
|
|
47
|
+
|
|
48
|
+
class TableFieldItems(ComboBoxItems):
|
|
49
|
+
"""
|
|
50
|
+
Список элементов генерируется из конкретного поля одной из таблиц
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, table_sql_name: str, field_sql_name: str, db: DataBase) -> dict[int, str]:
|
|
54
|
+
"""
|
|
55
|
+
Args:
|
|
56
|
+
table_sql_name (str): SQL название таблицы, из которой будут браться данные
|
|
57
|
+
field_sql_name (str): SQL название поля, данные которого будут составлять элементы списка
|
|
58
|
+
db (DataBase): База данных
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
self.table = table_sql_name
|
|
62
|
+
self.field = field_sql_name
|
|
63
|
+
self.db = db
|
|
64
|
+
|
|
65
|
+
def get_items(self) -> tuple[list[int], list[str]]:
|
|
66
|
+
result = self.db.cur.execute(f"SELECT id, {self.field} FROM {self.table}").fetchall()
|
|
67
|
+
return {int(x["id"]): x[self.field] for x in result}
|
|
68
|
+
|
|
69
|
+
class TableItems(ComboBoxItems):
|
|
70
|
+
"""
|
|
71
|
+
Список элементов формируется по названиям строк определённой таблицы
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(self, table_sql_name: str, db: DataBase, name_generator: NameGenerator, columns: Optional[Iterable[str]] = None):
|
|
75
|
+
"""
|
|
76
|
+
Args:
|
|
77
|
+
table_sql_name (str): SQL название таблицы, из которой будут браться данные
|
|
78
|
+
db (DataBase): база данных
|
|
79
|
+
name_generator ((row_data) -> row_name): Функция создания названия строки таблицы, в которой:
|
|
80
|
+
- row_data (Sequence[Any]): Данные из строки таблицы в виде массива пар ключ-значения (ключи - названия полей)
|
|
81
|
+
- row_name (str): Название строки таблицы, которое будет добавлено в список элементов
|
|
82
|
+
columns (list[str], optional): Список SQL названий столбцов, которые будут получены из БД; если не указано,
|
|
83
|
+
будут извлекаться все столбцы
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
self.table = table_sql_name
|
|
87
|
+
self.name_generator = name_generator
|
|
88
|
+
self.columns = columns
|
|
89
|
+
self.db = db
|
|
90
|
+
|
|
91
|
+
def get_items(self) -> dict[int, str]:
|
|
92
|
+
result = self.db.cur.execute(f"SELECT {'*' if self.columns is None else ', '.join(self.columns)} FROM {self.table}").fetchall()
|
|
93
|
+
return {int(x["id"]): self.name_generator(x) for x in result}
|