section_profile_editor.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. import section_calendar
  2. from widgets import *
  3. from typing import *
  4. import profiles
  5. from copy import copy
  6. _parent_layout: QLayout
  7. _style: QStyle
  8. def _clear_layout(layout: QLayout) -> None:
  9. for _ in range(layout.count()):
  10. layout.takeAt(0).widget().deleteLater()
  11. def initialize(parent_layout: QVBoxLayout, application_style: QStyle) -> None:
  12. global _parent_layout, _style
  13. _parent_layout = parent_layout
  14. _style = application_style
  15. def _show_profile_deletion_warning(widget: QWidget, profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]) -> None:
  16. icon = _style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxWarning)
  17. title = 'Удаление профиля'
  18. text = \
  19. 'Внимание! ' + \
  20. 'Удаление профиля также отвяжет его от всех дней, на которые он был присвоен. ' + \
  21. 'Вы уверены, что хотите удалить профиль, а отвязать его от всех соответствующих дней?'
  22. message_box = QMessageBox(QMessageBox.Icon.Warning, title, text, QMessageBox.Yes | QMessageBox.No, widget)
  23. button_yes = message_box.button(QMessageBox.Yes)
  24. button_yes.setText('Да')
  25. button_no = message_box.button(QMessageBox.No)
  26. button_no.setText('Нет')
  27. message_box.setDefaultButton(button_no)
  28. message_box.exec()
  29. if message_box.clickedButton() == button_yes:
  30. _delete_profile(profile)
  31. def _delete_profile(profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]) -> None:
  32. profiles.remove(profile)
  33. section_calendar.update()
  34. def _select_profile(date: QDate, profile_id: int) -> None:
  35. timetable_calendar.set_profile(date, profile_id)
  36. section_calendar.update()
  37. def _create_profile(date: QDate) -> None:
  38. profile_id = profiles.add_profile(
  39. 'Безымянный профиль',
  40. QColor('#3F3F3F'),
  41. {QTime(7, 0, 0, 0): 'melody.wav'}
  42. )
  43. timetable_calendar.set_profile(date, profile_id)
  44. section_calendar.update()
  45. def _create_profile_entry_widget(profile: Dict[str, Union[str, QColor, Dict[QTime, str]]], date: QDate) -> QWidget:
  46. global _style
  47. widget = HighlightableWidget(profile['name'])
  48. widget: QPushButton
  49. widget.setClickCallback(lambda: _select_profile(date, profile['id']))
  50. layout = QHBoxLayout(widget)
  51. indicator = QWidget()
  52. indicator.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
  53. indicator.setFixedWidth(2)
  54. indicator.setAutoFillBackground(True)
  55. palette = indicator.palette()
  56. palette.setColor(indicator.backgroundRole(), profile['color'])
  57. indicator.setPalette(palette)
  58. layout.addWidget(indicator)
  59. label = QLabel(profile['name'])
  60. label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Maximum)
  61. layout.addWidget(label)
  62. icon_delete = _style.standardIcon(QStyle.StandardPixmap.SP_DialogDiscardButton)
  63. button_delete = QPushButton(icon=icon_delete)
  64. button_delete.clicked.connect(lambda: _show_profile_deletion_warning(widget, profile))
  65. layout.addWidget(button_delete)
  66. return widget
  67. def _reflect_no_profile(date: QDate) -> None:
  68. global _parent_layout, _style
  69. all_profiles = profiles.get_all()
  70. if len(all_profiles) == 0:
  71. _parent_layout.addWidget(Spacer())
  72. label = QLabel("Профилей не найдено")
  73. label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
  74. _parent_layout.addWidget(label)
  75. _parent_layout.setAlignment(label, Qt.AlignmentFlag.AlignCenter)
  76. icon = _style.standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)
  77. text = "Создать профиль"
  78. button = QPushButton(icon, text)
  79. button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Minimum)
  80. button.clicked.connect(lambda: _create_profile(date))
  81. _parent_layout.addWidget(button)
  82. _parent_layout.setAlignment(button, Qt.AlignmentFlag.AlignCenter)
  83. _parent_layout.addWidget(Spacer())
  84. else:
  85. _parent_layout.setContentsMargins(0, 0, 0, 0)
  86. _parent_layout.setSpacing(0)
  87. _parent_layout.addWidget(Header('**Выберите профиль**'))
  88. list_widget = QWidget()
  89. list_layout = QVBoxLayout(list_widget)
  90. list_layout.setContentsMargins(0, 0, 0, 0)
  91. list_layout.setSpacing(0)
  92. for profile in profiles.get_all():
  93. list_layout.addWidget(_create_profile_entry_widget(profile, date))
  94. list_layout.addWidget(Spacer())
  95. scroll_area = VerticalScrollArea()
  96. scroll_area.setWidget(list_widget)
  97. scroll_area.setWidgetResizable(True)
  98. scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
  99. scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
  100. scroll_area.setFrameShape(QFrame.Shape.NoFrame)
  101. _parent_layout.addWidget(scroll_area)
  102. helper_widget = QWidget()
  103. helper_layout = QVBoxLayout(helper_widget)
  104. icon = _style.standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)
  105. text = 'Создать новый профиль'
  106. button_create_profile = QPushButton(icon=icon, text=text)
  107. button_create_profile.clicked.connect(lambda: _create_profile(date))
  108. helper_layout.addWidget(button_create_profile)
  109. _parent_layout.addWidget(helper_widget)
  110. def _on_profile_color_selected(profile_id: int, color_dialog: QColorDialog) -> None:
  111. profile = profiles.get(profile_id)
  112. old_profile = copy(profile)
  113. profile['color'] = color_dialog.selectedColor()
  114. profiles.replace(old_profile, profile)
  115. section_calendar.update()
  116. def _pick_profile_color(profile_id: int) -> None:
  117. profile = profiles.get(profile_id)
  118. dialog = QColorDialog(profile['color'])
  119. dialog.colorSelected.connect(lambda: _on_profile_color_selected(profile_id, dialog))
  120. dialog.show()
  121. def _on_profile_name_changed(profile_id: int, line_edit: QLineEdit) -> None:
  122. profile = profiles.get(profile_id)
  123. old_profile = copy(profile)
  124. profile['name'] = line_edit.text()
  125. profiles.replace(old_profile, profile)
  126. def _on_clear_profile(date: QDate) -> None:
  127. timetable_calendar.clear_profile(date)
  128. section_calendar.update()
  129. def _create_profile_info_widget(date: QDate, profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]) -> QWidget:
  130. widget = SectionFrame()
  131. layout = QVBoxLayout(widget)
  132. layout.setContentsMargins(0, 0, 0, 0)
  133. layout.setSpacing(0)
  134. layout.addWidget(Header('**Свойства профиля**', profile['color']))
  135. options_widget = QWidget()
  136. options_layout = QGridLayout(options_widget)
  137. description_profile_name = QLabel(text='Имя:')
  138. description_profile_name.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignCenter)
  139. options_layout.addWidget(description_profile_name, 0, 0)
  140. profile_name_edit = QLineEdit(profile['name'])
  141. profile_name_edit.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
  142. profile_name_edit.textChanged.connect(lambda: _on_profile_name_changed(profile['id'], profile_name_edit))
  143. options_layout.addWidget(profile_name_edit, 0, 1)
  144. description_profile_color = QLabel(text='Цвет:')
  145. description_profile_color.setAlignment(Qt.AlignmentFlag.AlignCenter | Qt.AlignmentFlag.AlignRight)
  146. options_layout.addWidget(description_profile_color, 1, 0)
  147. change_color_button = QPushButton('something')
  148. change_color_button.clicked.connect(lambda: _pick_profile_color(profile['id']))
  149. change_color_helper_layout = QHBoxLayout(change_color_button)
  150. change_color_helper_layout.setContentsMargins(4, 4, 4, 4)
  151. color_name = profile['color'].name()
  152. color_swatch = QWidget()
  153. color_swatch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
  154. color_swatch.setAutoFillBackground(True)
  155. color_swatch.setStyleSheet(f'background-color: {color_name}')
  156. change_color_helper_layout.addWidget(color_swatch)
  157. options_layout.addWidget(change_color_button, 1, 1)
  158. layout.addWidget(options_widget)
  159. icon = _style.standardIcon(QStyle.StandardPixmap.SP_BrowserReload)
  160. clear_profile_button = QPushButton(icon, 'Сменить профиль')
  161. clear_profile_button.clicked.connect(lambda: _on_clear_profile(date))
  162. clear_profile_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
  163. options_layout.addWidget(clear_profile_button, 2, 0, 1, 2)
  164. return widget
  165. def _on_edit_melody_name(
  166. time: QTime,
  167. profile_id: int,
  168. line_edit: DisconnectableLineEdit
  169. ) -> None:
  170. profile = profiles.get(profile_id)
  171. old_profile = profile.copy()
  172. profile['timetable'][time] = line_edit.text()
  173. profiles.replace(old_profile, profile)
  174. def _on_edit_time(
  175. time: QTime,
  176. profile_id: int,
  177. time_line_edit: CachingDisconnectableLineEdit,
  178. melody_edit: DisconnectableLineEdit
  179. ) -> None:
  180. profile = profiles.get(profile_id)
  181. old_profile = profile.copy()
  182. split_up_text = time_line_edit.text().split(':')
  183. correct_formatting = \
  184. len(split_up_text) == 2 and \
  185. split_up_text[0].isdigit() and \
  186. split_up_text[1].isdigit() and \
  187. 0 <= int(split_up_text[0]) <= 23 and \
  188. 0 <= int(split_up_text[1]) <= 59 and \
  189. len(split_up_text[0]) <= 2 and \
  190. len(split_up_text[1]) <= 2
  191. if not correct_formatting:
  192. print("Неверное форматирование времени!")
  193. time_line_edit.set_icon(_style.standardIcon(QStyle.StandardPixmap.SP_MessageBoxWarning))
  194. return
  195. new_time = QTime(int(split_up_text[0]), int(split_up_text[1]), 0)
  196. if time != new_time and profile['timetable'].get(new_time) is not None:
  197. print('Звонок на это время уже существует!')
  198. time_line_edit.set_icon(QIcon('icons/override.png'))
  199. return
  200. if time == new_time:
  201. print('Новое введенное время идентично предыдущему!')
  202. time_line_edit.set_icon(None)
  203. return
  204. profile['timetable'][new_time] = profile['timetable'][time]
  205. del(profile['timetable'][time])
  206. profiles.replace(old_profile, profile)
  207. time_line_edit.disconnect_on_text_changed()
  208. time_line_edit.connect_on_text_changed(lambda: _on_edit_time(new_time, profile_id, time_line_edit, melody_edit))
  209. melody_edit.disconnect_on_text_changed()
  210. melody_edit.connect_on_text_changed(lambda: _on_edit_melody_name(new_time, profile_id, melody_edit))
  211. time_line_edit.set_icon(_style.standardIcon(QStyle.StandardPixmap.SP_DialogSaveButton), True)
  212. def _delete_alarm(widget: QWidget, profile_id: int, time: QTime) -> None:
  213. profile = profiles.get(profile_id)
  214. new_profile = copy(profile)
  215. del (new_profile['timetable'][time])
  216. profiles.replace(profile, new_profile)
  217. widget.setParent(None)
  218. def _pick_alarm_melody(line_edit: DisconnectableLineEdit) -> None:
  219. file_name = QFileDialog.getOpenFileName(caption='Выберите мелодию звонка', filter='*.wav')[0]
  220. if file_name == '':
  221. return
  222. line_edit.setText(file_name)
  223. def _create_profile_timetable_entry_widget(
  224. time: QTime,
  225. melody_name: str,
  226. profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]
  227. ) -> QWidget:
  228. global _style
  229. widget = HighlightableWidget()
  230. layout = QGridLayout(widget)
  231. melody_edit = DisconnectableLineEdit(melody_name)
  232. melody_edit.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
  233. melody_edit.connect_on_text_changed(lambda: _on_edit_melody_name(time, profile['id'], melody_edit))
  234. time_line_edit = CachingDisconnectableLineEdit(f'{time.hour():02d}:{time.minute():02d}')
  235. time_line_edit.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Minimum)
  236. time_line_edit.connect_on_text_changed(lambda: _on_edit_time(time, profile['id'], time_line_edit, melody_edit))
  237. layout.addWidget(time_line_edit, 0, 0)
  238. layout.addWidget(melody_edit, 1, 0)
  239. delete_alarm_button = QPushButton()
  240. delete_alarm_button.setIcon(_style.standardIcon(QStyle.StandardPixmap.SP_TrashIcon))
  241. delete_alarm_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
  242. delete_alarm_button.clicked.connect(lambda: _delete_alarm(widget, profile['id'], time))
  243. layout.addWidget(delete_alarm_button, 0, 1)
  244. pick_melody_button = QPushButton()
  245. pick_melody_button.setIcon(_style.standardIcon(QStyle.StandardPixmap.SP_DirOpenIcon))
  246. pick_melody_button.setSizePolicy(QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Minimum)
  247. pick_melody_button.clicked.connect(lambda: _pick_alarm_melody(melody_edit))
  248. layout.addWidget(pick_melody_button, 1, 1)
  249. return widget
  250. def _add_alarm(profile_id: int, layout: QLayout) -> None:
  251. old_profile = profiles.get(profile_id)
  252. keys = list(old_profile['timetable'].keys())
  253. if len(keys) == 0:
  254. last_alarm = QTime(6, 59)
  255. melody_name = 'melody.wav'
  256. else:
  257. last_alarm = keys[-1]
  258. melody_name = old_profile['timetable'][last_alarm]
  259. last_alarm = last_alarm.addSecs(60)
  260. new_profile = copy(old_profile)
  261. new_profile['timetable'][last_alarm] = melody_name
  262. profiles.replace(old_profile, new_profile)
  263. layout.addWidget(_create_profile_timetable_entry_widget(last_alarm, melody_name, new_profile))
  264. def _create_profile_timetable_widget(profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]) -> QWidget:
  265. widget = SectionFrame()
  266. layout = QVBoxLayout(widget)
  267. layout.setContentsMargins(0, 0, 0, 0)
  268. layout.setSpacing(0)
  269. layout.addWidget(Header('**Расписание**', profile['color']))
  270. list_widget = QWidget()
  271. list_layout = QVBoxLayout(list_widget)
  272. list_layout.setSpacing(0)
  273. list_layout.setContentsMargins(0, 0, 0, 0)
  274. for time, melody_name in sorted(profile['timetable'].items()):
  275. list_layout.addWidget(_create_profile_timetable_entry_widget(time, melody_name, profile))
  276. layout.addWidget(list_widget)
  277. bottom_section = QWidget()
  278. bottom_layout = QVBoxLayout(bottom_section)
  279. text = "Добавить звонок"
  280. icon = _style.standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)
  281. add_alarm_button = QPushButton(icon, text)
  282. add_alarm_button.clicked.connect(lambda: _add_alarm(profile['id'], list_layout))
  283. bottom_layout.addWidget(add_alarm_button)
  284. layout.addWidget(bottom_section)
  285. return widget
  286. def _reflect_profile(date: QDate, profile: Dict[str, Union[str, QColor, Dict[QTime, str]]]) -> None:
  287. global _parent_layout
  288. _parent_layout.setContentsMargins(0, 0, 0, 0)
  289. _parent_layout.setSpacing(0)
  290. _parent_layout.addWidget(Header('**Настройка профиля**'))
  291. list_widget = QWidget()
  292. list_layout = QVBoxLayout(list_widget)
  293. list_layout.addWidget(_create_profile_info_widget(date, profile))
  294. list_layout.addWidget(_create_profile_timetable_widget(profile))
  295. list_layout.addWidget(Spacer())
  296. scroll_area = VerticalScrollArea()
  297. scroll_area.setWidget(list_widget)
  298. scroll_area.setWidgetResizable(True)
  299. scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
  300. scroll_area.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
  301. scroll_area.setFrameShape(QFrame.Shape.NoFrame)
  302. _parent_layout.addWidget(scroll_area)
  303. def update(profile_id: int, date: QDate) -> None:
  304. global _parent_layout
  305. _clear_layout(_parent_layout)
  306. _parent_layout.setContentsMargins(-1, -1, -1, -1)
  307. if profile_id is not None:
  308. profile = profiles.get(profile_id)
  309. else:
  310. profile = None
  311. if profile is not None:
  312. _reflect_profile(date, profile)
  313. else:
  314. _reflect_no_profile(date)