pytodo-qt 0.2.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.

Potentially problematic release.


This version of pytodo-qt might be problematic. Click here for more details.

todo/gui/MainWindow.py ADDED
@@ -0,0 +1,925 @@
1
+ """MainWindow.py
2
+
3
+ This module implements the GUI for To-Do.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+
9
+ from PyQt5 import QtCore, QtGui, QtWidgets, QtPrintSupport
10
+
11
+ from todo.core import error_on_none_db, settings, json_helpers
12
+ from todo.core.Logger import Logger
13
+ from todo.gui.AddTodoDialog import AddTodoDialog
14
+ from todo.gui.SyncDialog import SyncDialog
15
+ from todo.net.sync_operations import sync_operations
16
+
17
+
18
+ logger = Logger(__name__)
19
+
20
+
21
+ class CreateMainWindow(QtWidgets.QMainWindow):
22
+ """This class implements the bulk of the gui functionality in To-Do.
23
+
24
+ It creates the main window, and helps to facilitate management of the
25
+ list database for the user.
26
+ """
27
+
28
+ def __init__(self, *args):
29
+ """Create the window, make a table, fill table with to-do data."""
30
+ logger.log.info("Creating the main window")
31
+
32
+ # create the window, set title and tooltip, resize and center window
33
+ QtWidgets.QMainWindow.__init__(self)
34
+ self.setWindowIcon(QtGui.QIcon("gui/icons/todo.png"))
35
+ self.setWindowTitle("To-Do")
36
+ self.setToolTip("Python3 + Qt5 = Happy <u>To-Do</u> Programmer!")
37
+ QtWidgets.QToolTip.setFont(QtGui.QFont("Helvetica", 10))
38
+ self.resize(800, 500)
39
+ self.center()
40
+
41
+ # create our special font
42
+ self.complete_font = QtGui.QFont("Helvetica", 10, QtGui.QFont.Bold)
43
+ self.complete_font.setStrikeOut(True)
44
+
45
+ # create our normal font
46
+ self.normal_font = QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal)
47
+ self.normal_font.setStrikeOut(False)
48
+
49
+ # create some actions
50
+ printer = QtWidgets.QAction(QtGui.QIcon(), "Print", self)
51
+ printer.setShortcut("Ctrl+P")
52
+ printer.triggered.connect(self.print_list)
53
+
54
+ export = QtWidgets.QAction(QtGui.QIcon(), "Export to Text File", self)
55
+ export.setShortcut("Ctrl+E")
56
+ export.triggered.connect(self.export_list)
57
+
58
+ _quit = QtWidgets.QAction(QtGui.QIcon(), "Exit", self)
59
+ _quit.setShortcut("Ctrl+Q")
60
+ _quit.triggered.connect(self.close)
61
+
62
+ # to-do actions
63
+ add = QtWidgets.QAction(
64
+ QtGui.QIcon("gui/icons/plus.png"), "Add new to-do", self
65
+ )
66
+ add.setShortcut("+")
67
+ add.triggered.connect(self.add_todo)
68
+
69
+ delete = QtWidgets.QAction(
70
+ QtGui.QIcon("gui/icons/minus.png"), "Delete to-do", self
71
+ )
72
+ delete.setShortcut("-")
73
+ delete.triggered.connect(self.delete_todo)
74
+
75
+ toggle = QtWidgets.QAction(
76
+ QtGui.QIcon("gui/icons/todo.png"), "Toggle to-do Status", self
77
+ )
78
+ toggle.setShortcut("%")
79
+ toggle.triggered.connect(self.toggle_todo)
80
+
81
+ # list actions
82
+ list_add = QtWidgets.QAction(QtGui.QIcon(), "Add new list", self)
83
+ list_add.setShortcut("Ctrl++")
84
+ list_add.triggered.connect(self.add_list)
85
+
86
+ list_delete = QtWidgets.QAction(
87
+ QtGui.QIcon("gui/icons/minus.png"), "Delete list", self
88
+ )
89
+
90
+ list_delete.setShortcut("Ctrl+-")
91
+ list_delete.triggered.connect(self.delete_list)
92
+
93
+ list_rename = QtWidgets.QAction(QtGui.QIcon(), "Rename list", self)
94
+ list_rename.setShortcut("Ctrl+R")
95
+ list_rename.triggered.connect(self.rename_list)
96
+
97
+ list_switch = QtWidgets.QAction(QtGui.QIcon(), "Switch List", self)
98
+ list_switch.setShortcut("Ctrl+L")
99
+ list_switch.triggered.connect(self.switch_list)
100
+
101
+ sync_pull = QtWidgets.QAction(
102
+ QtGui.QIcon(), "Get lists from another computer", self
103
+ )
104
+ sync_pull.setShortcut("F6")
105
+ sync_pull.triggered.connect(self.db_sync_pull)
106
+
107
+ sync_push = QtWidgets.QAction(
108
+ QtGui.QIcon(), "Send lists to another computer", self
109
+ )
110
+ sync_push.setShortcut("F7")
111
+ sync_push.triggered.connect(self.db_sync_push)
112
+
113
+ # network server actions
114
+ start_server = QtWidgets.QAction(
115
+ QtGui.QIcon(), "Start the network server", self
116
+ )
117
+ start_server.triggered.connect(self.db_start_server)
118
+
119
+ stop_server = QtWidgets.QAction(QtGui.QIcon(), "Stop the network server", self)
120
+ stop_server.triggered.connect(self.db_stop_server)
121
+
122
+ change_port = QtWidgets.QAction(
123
+ QtGui.QIcon(), "Change network server port", self
124
+ )
125
+ change_port.triggered.connect(self.db_server_port)
126
+
127
+ change_bind_address = QtWidgets.QAction(
128
+ QtGui.QIcon(), "Change network server address", self
129
+ )
130
+ change_bind_address.triggered.connect(self.db_server_bind_address)
131
+
132
+ # fanfare
133
+ about = QtWidgets.QAction(QtGui.QIcon(), "About To-Do", self)
134
+ about.triggered.connect(self.about_todo)
135
+
136
+ about_qt = QtWidgets.QAction(QtGui.QIcon(), "About Qt", self)
137
+ about_qt.triggered.connect(self.about_qt)
138
+
139
+ # create a menu bar
140
+ menu_bar = self.menuBar()
141
+ if menu_bar is not None:
142
+ main_menu = menu_bar.addMenu("&Menu")
143
+ if main_menu is not None:
144
+ main_menu.addAction(printer)
145
+ main_menu.addAction(export)
146
+ main_menu.addAction(_quit)
147
+ else:
148
+ msg = "Could not populate main menu, exiting"
149
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
150
+ logger.log.exception(msg)
151
+ else:
152
+ msg = "Could not create menu bar, exiting"
153
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
154
+ logger.log.exception(msg)
155
+ sys.exit(1)
156
+
157
+ if menu_bar is not None:
158
+ todo_menu = menu_bar.addMenu("&To-Do")
159
+ if todo_menu is not None:
160
+ todo_menu.addAction(add)
161
+ todo_menu.addAction(delete)
162
+ todo_menu.addAction(toggle)
163
+ else:
164
+ msg = "Could not populate to-do menu, exiting"
165
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
166
+ logger.log.exception(msg)
167
+ sys.exit(1)
168
+ else:
169
+ msg = "Menu bar error, exiting"
170
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
171
+ logger.log.exception(msg)
172
+ sys.exit(1)
173
+
174
+ if menu_bar is not None:
175
+ list_menu = menu_bar.addMenu("&List")
176
+ if list_menu is not None:
177
+ list_menu.addAction(list_add)
178
+ list_menu.addAction(list_delete)
179
+ list_menu.addAction(list_rename)
180
+ list_menu.addAction(list_switch)
181
+ else:
182
+ msg = "Could not populate list menu, exiting"
183
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
184
+ logger.log.exception(msg)
185
+ sys.exit(1)
186
+ else:
187
+ msg = "Menu bar error, exiting"
188
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
189
+ logger.log.exception(msg)
190
+ sys.exit(1)
191
+
192
+ if menu_bar is not None:
193
+ sync_menu = menu_bar.addMenu("&Sync")
194
+ if sync_menu is not None:
195
+ sync_menu.addAction(sync_pull)
196
+ sync_menu.addAction(sync_push)
197
+ else:
198
+ msg = "Could not populate sync menu, exiting"
199
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
200
+ logger.log.exception(msg)
201
+ sys.exit(1)
202
+ else:
203
+ msg = "Menu bar error, exiting"
204
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
205
+ logger.log.exception(msg)
206
+ sys.exit(1)
207
+
208
+ if menu_bar is not None:
209
+ server_menu = menu_bar.addMenu("&Server")
210
+ if server_menu is not None:
211
+ server_menu.addAction(start_server)
212
+ server_menu.addAction(stop_server)
213
+ server_menu.addAction(change_port)
214
+ server_menu.addAction(change_bind_address)
215
+ else:
216
+ msg = "Could not populate server menu, exiting"
217
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
218
+ logger.log.exception(msg)
219
+ sys.exit(1)
220
+ else:
221
+ msg = "Menu bar error, exiting"
222
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
223
+ logger.log.exception(msg)
224
+ sys.exit(1)
225
+
226
+ if menu_bar is not None:
227
+ help_menu = menu_bar.addMenu("&Help")
228
+ if help_menu is not None:
229
+ help_menu.addAction(about)
230
+ help_menu.addAction(about_qt)
231
+ else:
232
+ msg = "Could not populate help menu, exiting"
233
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
234
+ logger.log.exception(msg)
235
+ sys.exit(1)
236
+ else:
237
+ msg = "Menu bar error, exiting"
238
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
239
+ logger.log.exception(msg)
240
+ sys.exit(1)
241
+
242
+ # create action toolbar
243
+ toolbar = self.addToolBar("To-Do Actions")
244
+ if toolbar is not None:
245
+ toolbar.addAction(add)
246
+ toolbar.addAction(delete)
247
+ toolbar.addAction(toggle)
248
+ toolbar.addAction(_quit)
249
+ else:
250
+ msg = "Could not create toolbar, exiting"
251
+ QtWidgets.QMessageBox.warning(self, "Creation Error", msg)
252
+ logger.log.exception(msg)
253
+ sys.exit(1)
254
+
255
+ # create table, set it as central widget
256
+ self.table = QtWidgets.QTableWidget(self)
257
+ if self.table is not None:
258
+ self.table.insertColumn(0)
259
+ self.table.insertColumn(1)
260
+ self.table.setHorizontalHeaderLabels(["Priority", "Reminder"])
261
+ self.table.horizontalHeader().setStretchLastSection(True)
262
+ self.table.setToolTip("This is your <u>list</u> of to-do's.")
263
+ self.setCentralWidget(self.table)
264
+ else:
265
+ msg = "Could not create to-do list table, exiting"
266
+ QtWidgets.QMessageBox.critical(self, "Creation Error", msg)
267
+ logger.log.exception(msg)
268
+ sys.exit(1)
269
+
270
+ # create a status bar
271
+ self.progressBar = QtWidgets.QProgressBar()
272
+ self.statusBarLabel = QtWidgets.QLabel()
273
+ self.statusBar = self.statusBar()
274
+ if self.statusBar is not None:
275
+ self.statusBar.addPermanentWidget(self.progressBar)
276
+ self.statusBar.addPermanentWidget(self.statusBarLabel, 1)
277
+ else:
278
+ msg = "Could not create status bar, exiting"
279
+ QtWidgets.QMessageBox.critical(self, "Creation Error", msg)
280
+ logger.log.exception(msg)
281
+ sys.exit(1)
282
+
283
+ # create a system tray icon
284
+ self.tray_icon = QtWidgets.QSystemTrayIcon(
285
+ QtGui.QIcon("gui/icons/todo.png"), self
286
+ )
287
+ self.tray_icon.activated.connect(self.tray_event)
288
+
289
+ tray_exit = QtWidgets.QAction(QtGui.QIcon(), "Exit", self.tray_icon)
290
+ tray_exit.triggered.connect(self.close)
291
+
292
+ tray_menu = QtWidgets.QMenu("&To-Do", self)
293
+ if tray_menu is not None:
294
+ tray_menu.addAction(tray_exit)
295
+ self.tray_icon.setContextMenu(tray_menu)
296
+ self.tray_icon.show()
297
+ else:
298
+ msg = "Could not create status tray menu, exiting"
299
+ QtWidgets.QMessageBox.critical(self, "Creation Error", msg)
300
+ logger.log.exception(msg)
301
+ sys.exit(1)
302
+
303
+ # create a printer if we can
304
+ self.printer = QtPrintSupport.QPrinter()
305
+
306
+ # show the window
307
+ self.show()
308
+
309
+ # read in any saved to-do data
310
+ self.read_todo_data()
311
+
312
+ # draw the table
313
+ self.refresh()
314
+
315
+ logger.log.info("Main window created")
316
+
317
+ def read_todo_data(self):
318
+ """Read lists of to-dos from database."""
319
+ if os.path.exists(settings.lists_fn):
320
+ self.update_progress_bar(0)
321
+ self.update_status_bar("Reading in JSON data")
322
+ result, msg = json_helpers.read_json_data()
323
+ if not result:
324
+ QtWidgets.QMessageBox.warning(self, "Read Error", str(msg))
325
+ self.update_progress_bar()
326
+ self.update_status_bar()
327
+ else:
328
+ return
329
+
330
+ self.refresh()
331
+
332
+ @error_on_none_db
333
+ def write_todo_data(self, *args, **kwargs):
334
+ """Write to-do lists to a JSON file."""
335
+ if len(settings.db.todo_lists.keys()) == 0:
336
+ msg = "No to-do information, aborting write"
337
+ QtWidgets.QMessageBox.warning(self, "Write Error", msg)
338
+ return
339
+
340
+ self.update_progress_bar(0)
341
+ self.update_status_bar("Writing JSON data")
342
+
343
+ result, msg = json_helpers.write_json_data()
344
+ if not result:
345
+ QtWidgets.QMessageBox.warning(self, "Write Error", msg)
346
+ self.update_progress_bar()
347
+ self.update_status_bar()
348
+
349
+ def db_sync_pull(self):
350
+ """Pull lists from another network server."""
351
+ self.update_progress_bar(0)
352
+ self.update_status_bar("Sync Pull")
353
+ SyncDialog(sync_operations["PULL_REQUEST"].name).exec()
354
+ self.write_todo_data()
355
+ self.refresh()
356
+
357
+ def db_sync_push(self):
358
+ """Push lists to another computer."""
359
+ self.update_progress_bar(0)
360
+ self.update_status_bar("Waiting for input")
361
+ SyncDialog(sync_operations["PUSH_REQUEST"].name).exec()
362
+ self.refresh()
363
+
364
+ @error_on_none_db
365
+ def db_update_active_list(self, list_name, *args, **kwargs):
366
+ """Update the active list, and save the configuration."""
367
+ settings.options["active_list"] = list_name
368
+
369
+ settings.db.active_list = list_name
370
+ result, msg = settings.db.write_config()
371
+ if not result:
372
+ QtWidgets.QMessageBox.critical(self, "Write Error", msg)
373
+ sys.exit(1)
374
+ else:
375
+ QtWidgets.QMessageBox.information(self, "Write Success", msg)
376
+
377
+ @error_on_none_db
378
+ def db_start_server(self, *args, **kwargs):
379
+ """Start the database server."""
380
+ if settings.db.server_running():
381
+ QtWidgets.QMessageBox.information(
382
+ self, "Info", "The database server is already running."
383
+ )
384
+ else:
385
+ settings.db.start_server()
386
+ QtWidgets.QMessageBox.information(
387
+ self, "Info", "The database server was started."
388
+ )
389
+ self.refresh()
390
+
391
+ @error_on_none_db
392
+ def db_stop_server(self, *args, **kwargs):
393
+ """Stop the database server."""
394
+ if not settings.db.server_running():
395
+ QtWidgets.QMessageBox.information(
396
+ self, "Info", "The database server is not running."
397
+ )
398
+ else:
399
+ settings.db.stop_server()
400
+ QtWidgets.QMessageBox.information(
401
+ self, "Info", "The database server was stopped."
402
+ )
403
+ self.refresh()
404
+
405
+ @error_on_none_db
406
+ def db_server_port(self, *args, **kwargs):
407
+ """Change the port the database server listens too."""
408
+ port, ok = QtWidgets.QInputDialog.getInt(
409
+ self, "Change database server port", "Port: "
410
+ )
411
+ if not ok:
412
+ return
413
+
414
+ if settings.options["port"] == port:
415
+ QtWidgets.QMessageBox.information(
416
+ self, "Info", f"Server is already using port {port}"
417
+ )
418
+ else:
419
+ settings.options["port"] = port
420
+ if settings.db.server_running():
421
+ reply = QtWidgets.QMessageBox.question(
422
+ self,
423
+ "Restart Database Server?",
424
+ "The server needs to be restarted for changes to take effect, would you like to do that now?",
425
+ QtWidgets.QMessageBox.Yes,
426
+ QtWidgets.QMessageBox.No,
427
+ )
428
+ if reply == QtWidgets.QMessageBox.Yes:
429
+ settings.db.restart_server()
430
+ settings.db.write_config()
431
+ self.refresh()
432
+
433
+ @error_on_none_db
434
+ def db_server_bind_address(self, *args, **kwargs):
435
+ """Bind database server to an ip address."""
436
+ address, ok = QtWidgets.QInputDialog.getText(
437
+ self, "Change server ip address", "IP Address: "
438
+ )
439
+ if not ok:
440
+ return
441
+
442
+ if settings.options["address"] == address:
443
+ QtWidgets.QMessageBox.information(
444
+ self, "Info", f"Server is already bound to {address}"
445
+ )
446
+ else:
447
+ settings.options["address"] = address
448
+ if settings.db.server_running():
449
+ reply = QtWidgets.QMessageBox.question(
450
+ self,
451
+ "Restart Database Server?",
452
+ "The server needs to be restarted for changes to take effect, would you like to do that now?",
453
+ QtWidgets.QMessageBox.Yes,
454
+ QtWidgets.QMessageBox.No,
455
+ )
456
+ if reply == QtWidgets.QMessageBox.Yes:
457
+ settings.db.restart_server()
458
+ settings.db.write_config()
459
+ self.refresh()
460
+
461
+ @error_on_none_db
462
+ def add_list(self, *args, **kwargs):
463
+ """Add a new list of to-dos."""
464
+ self.update_progress_bar(0)
465
+ self.update_status_bar("Waiting for input")
466
+
467
+ list_name, ok = QtWidgets.QInputDialog.getText(
468
+ self, "Add New List", "Enter a name for the new list: "
469
+ )
470
+ if ok:
471
+ if len(list_name) == 0:
472
+ self.update_progress_bar()
473
+ self.update_status_bar()
474
+ return
475
+
476
+ if list_name not in settings.db.todo_lists.keys():
477
+ settings.db.todo_lists[list_name] = []
478
+ settings.options["active_list"] = list_name
479
+ settings.db.active_list = list_name
480
+ settings.db.write_config()
481
+ self.write_todo_data()
482
+ else:
483
+ QtWidgets.QMessageBox.warning(
484
+ self,
485
+ "Duplicate List",
486
+ f'A list named "{list_name}" already exists.',
487
+ )
488
+
489
+ self.refresh()
490
+
491
+ @error_on_none_db
492
+ def delete_list(self, *args, **kwargs):
493
+ """Delete user selected list."""
494
+ self.update_progress_bar(0)
495
+
496
+ # if there is more than one list, ask user which one to delete
497
+ if len(settings.db.todo_lists.keys()) > 1:
498
+ list_entry, ok = QtWidgets.QInputDialog.getItem(
499
+ self,
500
+ "Select List",
501
+ "To-Do Lists: ",
502
+ list(settings.db.todo_lists.keys()),
503
+ )
504
+ if not ok:
505
+ self.update_progress_bar()
506
+ return
507
+
508
+ settings.db.todo_total -= len(settings.db.todo_lists[list_entry])
509
+ del settings.db.todo_lists[list_entry]
510
+
511
+ # use list switcher if there is still more than one list
512
+ if len(settings.db.todo_lists) > 1:
513
+ self.switch_list()
514
+ else:
515
+ for list_entry in settings.db.todo_lists.keys():
516
+ self.db_update_active_list(list_entry)
517
+ self.write_todo_data()
518
+ break
519
+ else:
520
+ reply = QtWidgets.QMessageBox.question(
521
+ self,
522
+ "Confirm deletion",
523
+ f'Really delete list "{settings.db.active_list}"?',
524
+ QtWidgets.QMessageBox.Yes,
525
+ QtWidgets.QMessageBox.No,
526
+ )
527
+ if reply == QtWidgets.QMessageBox.No:
528
+ self.update_progress_bar()
529
+ return
530
+
531
+ settings.db.todo_total -= settings.db.todo_count
532
+ del settings.db.todo_lists[settings.db.active_list]
533
+ settings.db.list_count -= 1
534
+
535
+ # reset database
536
+ settings.db.active_list = ""
537
+ settings.options["active_list"] = settings.db.active_list
538
+ settings.db.todo_count = 0
539
+ settings.db.write_config()
540
+ if os.path.exists(settings.lists_fn):
541
+ os.remove(settings.lists_fn)
542
+
543
+ self.refresh()
544
+
545
+ @error_on_none_db
546
+ def rename_list(self, *args, **kwargs):
547
+ """Rename the active list"""
548
+ self.update_progress_bar(0)
549
+
550
+ list_name, ok = QtWidgets.QInputDialog.getText(
551
+ self, "Rename to-do list", "Enter new name:"
552
+ )
553
+ if ok:
554
+ reply = QtWidgets.QMessageBox.question(
555
+ self,
556
+ "Confirm rename",
557
+ f"Are you sure you want to rename list {settings.db.active_list} to {list_name}",
558
+ QtWidgets.QMessageBox.Yes,
559
+ QtWidgets.QMessageBox.No,
560
+ )
561
+
562
+ if reply == QtWidgets.QMessageBox.No:
563
+ return
564
+
565
+ settings.db.todo_lists[list_name] = settings.db.todo_lists[
566
+ settings.db.active_list
567
+ ]
568
+ del settings.db.todo_lists[settings.db.active_list]
569
+ settings.db.active_list = settings.db.todo_lists[list_name]
570
+ settings.db.write_config()
571
+ self.write_todo_data()
572
+ self.refresh()
573
+
574
+ @error_on_none_db
575
+ def switch_list(self, *args, **kwargs):
576
+ """Switch the active list.
577
+
578
+ Present user with a drop-down dialog of lists,
579
+ set their choice as the active list.
580
+ """
581
+ self.update_progress_bar(0)
582
+
583
+ if len(settings.db.todo_lists.keys()) == 0:
584
+ return
585
+
586
+ list_entry, ok = QtWidgets.QInputDialog.getItem(
587
+ self, "Select List", "To-Do Lists: ", list(settings.db.todo_lists.keys())
588
+ )
589
+ if ok:
590
+ self.db_update_active_list(list_entry)
591
+ self.write_todo_data()
592
+
593
+ self.refresh()
594
+
595
+ @error_on_none_db
596
+ def export_list(self, fn=None, *args, **kwargs):
597
+ """Export active list to text file."""
598
+ self.update_progress_bar(0)
599
+
600
+ if not settings.db.todo_count:
601
+ return
602
+
603
+ if fn is None:
604
+ # prompt user for fn
605
+ self.update_status_bar("Waiting for input")
606
+ fn, ok = QtWidgets.QInputDialog.getText(
607
+ self,
608
+ "Save list to text file",
609
+ "Enter name of file to write as text:",
610
+ )
611
+ if not ok:
612
+ return
613
+
614
+ self.update_status_bar(f"Exporting to text file {fn}.")
615
+ settings.db.write_text_file(fn)
616
+ self.update_status_bar(f"finished exporting to {fn}.")
617
+ self.update_progress_bar()
618
+
619
+ def print_list(self):
620
+ """Print the active list."""
621
+ self.update_progress_bar(0)
622
+
623
+ # check that we have a printer first
624
+ if not self.printer:
625
+ self.update_progress_bar()
626
+ return
627
+
628
+ tmp_fn = ".todo_printer.tmp"
629
+
630
+ accept = QtPrintSupport.QPrintDialog(self.printer).exec()
631
+ if accept:
632
+ self.update_status_bar("Printing")
633
+
634
+ # Write formatted list to temporary file
635
+ self.export_list(tmp_fn)
636
+ if not os.path.exists(tmp_fn):
637
+ return
638
+
639
+ # Read in formatted list as one big string
640
+ string = ""
641
+ with open(tmp_fn, encoding="utf-8") as f:
642
+ for line in f:
643
+ string += line
644
+
645
+ # Remove temporary file
646
+ os.remove(tmp_fn)
647
+
648
+ # Open string as a QTextDocument and then print it
649
+ doc = QtGui.QTextDocument(string)
650
+ doc.print_(self.printer)
651
+
652
+ self.update_progress_bar()
653
+ self.update_status_bar("finished printing")
654
+
655
+ @error_on_none_db
656
+ def add_todo(self, *args, **kwargs):
657
+ """Add a new to-do to active list."""
658
+ self.update_progress_bar(0)
659
+
660
+ if not settings.db.active_list:
661
+ if settings.db.list_count == 0:
662
+ QtWidgets.QMessageBox.information(
663
+ self,
664
+ "No list",
665
+ "You need to create a list before you add a reminder",
666
+ )
667
+ self.update_progress_bar()
668
+ self.update_status_bar()
669
+ return self.add_list()
670
+ else:
671
+ # ask the user if they would like to switch to an existing list
672
+ self.update_status_bar("Waiting for input")
673
+ reply = QtWidgets.QMessageBox.question(
674
+ self,
675
+ "Set an active list",
676
+ "There is currently no active list set, but lists do exist,"
677
+ "would you like to switch to one of them?",
678
+ QtWidgets.QMessageBox.Yes,
679
+ QtWidgets.QMessageBox.No,
680
+ )
681
+
682
+ # check the user response
683
+ if reply == QtWidgets.QMessageBox.Yes:
684
+ self.switch_list()
685
+ else:
686
+ return
687
+
688
+ # Get a new to-do from user
689
+ AddTodoDialog().exec()
690
+
691
+ self.refresh()
692
+
693
+ @error_on_none_db
694
+ def delete_todo(self, *args, **kwargs):
695
+ """Delete the currently selected to-do."""
696
+ self.update_progress_bar(0)
697
+
698
+ if self.table is None:
699
+ msg = "To-Do List Table is invalid, exiting"
700
+ QtWidgets.QMessageBox.critical(self, "To-Do List Error", msg)
701
+ logger.log.exception(msg)
702
+ sys.exit(1)
703
+ else:
704
+ if self.table.selectionModel().hasSelection():
705
+ indices = [
706
+ QtCore.QPersistentModelIndex(index)
707
+ for index in self.table.selectionModel().selectedIndexes()
708
+ ]
709
+ for index in indices:
710
+ item = self.table.cellWidget(index.row(), 1)
711
+ text = item.text()
712
+ todo = settings.db.todo_index(text)
713
+ del settings.db.todo_lists[settings.db.active_list][todo]
714
+ settings.db.todo_count -= 1
715
+ settings.db.todo_total -= 1
716
+ self.write_todo_data()
717
+ self.table.removeRow(index.row())
718
+ self.refresh()
719
+ else:
720
+ QtWidgets.QMessageBox.warning(
721
+ self, "Delete To-Do", "No reminders selected."
722
+ )
723
+
724
+ @error_on_none_db
725
+ def toggle_todo(self, *args, **kwargs):
726
+ """Toggle a to-do complete / incomplete."""
727
+ self.update_progress_bar(0)
728
+
729
+ for index in self.table.selectedIndexes():
730
+ item = self.table.cellWidget(index.row(), 1)
731
+ text = item.text()
732
+ todo = settings.db.todo_index(text)
733
+ if not settings.db.todo_lists[settings.db.active_list][todo]["complete"]:
734
+ settings.db.todo_lists[settings.db.active_list][todo]["complete"] = True
735
+ else:
736
+ settings.db.todo_lists[settings.db.active_list][todo][
737
+ "complete"
738
+ ] = False
739
+ self.write_todo_data()
740
+ self.refresh()
741
+
742
+ @error_on_none_db
743
+ def change_priority(self, *args, **kwargs):
744
+ """Change a to-do's priority."""
745
+ for index in self.table.selectedIndexes():
746
+ item_p = self.table.cellWidget(index.row(), 0)
747
+ item_r = self.table.cellWidget(index.row(), 1)
748
+
749
+ text = item_p.currentText()
750
+ if text == "Low":
751
+ priority = 3
752
+ elif text == "Normal":
753
+ priority = 2
754
+ else:
755
+ priority = 1
756
+
757
+ reminder = item_r.text()
758
+ todo = settings.db.todo_index(reminder)
759
+ settings.db.todo_lists[settings.db.active_list][todo]["priority"] = priority
760
+ self.write_todo_data()
761
+ self.refresh()
762
+
763
+ @error_on_none_db
764
+ def edit_reminder(self, *args, **kwargs):
765
+ """Edit the reminder of a to-do."""
766
+ for index in self.table.selectedIndexes():
767
+ item = self.table.cellWidget(index.row(), 1)
768
+ new_text = item.text()
769
+ settings.db.todo_lists[settings.db.active_list][index.row()][
770
+ "reminder"
771
+ ] = new_text
772
+ self.write_todo_data()
773
+
774
+ def about_todo(self):
775
+ """Display a message box with Program/Author information."""
776
+ text = """<b><u>To-Do v0.2.0</u></b>
777
+ <br><br>To-Do list program that works with multiple To-Do
778
+ lists locally and over a network.
779
+ <br><br>License: <a href="http://www.fsf.org/licenses/gpl.html">GPLv3</a>
780
+ <br><br><b>Copyright (C) 2024 Michael Berry</b>
781
+ """
782
+ QtWidgets.QMessageBox.about(self, "About To-Do", text)
783
+
784
+ def about_qt(self):
785
+ """Display information about Qt."""
786
+ QtWidgets.QMessageBox.aboutQt(self, "About Qt")
787
+
788
+ def center(self):
789
+ """Place the main window in the center of the screen."""
790
+ qt_rectangle = self.frameGeometry()
791
+ center_point = QtWidgets.QDesktopWidget().availableGeometry().center()
792
+ qt_rectangle.moveCenter(center_point)
793
+ self.move(qt_rectangle.topLeft())
794
+
795
+ @error_on_none_db
796
+ def update_progress_bar(self, value=None, *args, **kwargs):
797
+ """Update the progress bar.
798
+
799
+ Maximum value should be set to the total to-do count,
800
+ while value should be the number of completed todos.
801
+ This makes the progress bar show the total percentage
802
+ of completed to-dos.
803
+ """
804
+ if value is None:
805
+ return
806
+
807
+ i = 0
808
+
809
+ for list_entry in settings.db.todo_lists:
810
+ for todo in settings.db.todo_lists[list_entry]:
811
+ if todo["complete"]:
812
+ i += 1
813
+
814
+ value = i
815
+
816
+ self.progressBar.reset()
817
+ self.progressBar.setMaximum(settings.db.todo_total)
818
+ self.progressBar.setValue(value)
819
+
820
+ @error_on_none_db
821
+ def update_status_bar(self, msg="Ready", *args, **kwargs):
822
+ """Update the status bar, display some statistics."""
823
+ if settings.db.server_running():
824
+ server_status = (
825
+ f"Up on {settings.options['address']}: {settings.options['port']}"
826
+ )
827
+ else:
828
+ server_status = "Down"
829
+
830
+ # create our status bar text
831
+ text = f"Lists: {settings.db.list_count} Shown: {settings.db.active_list} To-Do's: {settings.db.todo_count} of {settings.db.todo_total} Status: {msg} Server: {server_status}"
832
+
833
+ self.statusBarLabel.setText(text)
834
+
835
+ @error_on_none_db
836
+ def refresh(self, *args, **kwargs):
837
+ """Redraw the table and update the progress and status bars."""
838
+ self.update_status_bar("Redrawing table")
839
+
840
+ # clear the table
841
+ self.table.setRowCount(0)
842
+
843
+ # set the table headers
844
+ self.table.setHorizontalHeaderLabels(["Priority", "Reminder"])
845
+
846
+ # make sure we have a valid active list
847
+ if settings.db.active_list not in settings.db.todo_lists:
848
+ self.update_status_bar()
849
+ return
850
+
851
+ # add each to-do in the list to the table, show a progress bar
852
+ i = 0
853
+ settings.db.sort_active_list()
854
+
855
+ # update the progress bar
856
+ self.update_progress_bar(0)
857
+
858
+ for todo in settings.db.todo_lists[settings.db.active_list]:
859
+ # create priority table item
860
+ item_p = QtWidgets.QComboBox(self)
861
+ item_p.addItems(["Low", "Normal", "High"])
862
+ item_p.currentIndexChanged.connect(self.change_priority)
863
+ if todo["priority"] == 1:
864
+ item_p.setCurrentIndex(2)
865
+ elif todo["priority"] == 2:
866
+ item_p.setCurrentIndex(1)
867
+ else:
868
+ item_p.setCurrentIndex(0)
869
+
870
+ # create reminder table item
871
+ item_r = QtWidgets.QLineEdit(todo["reminder"])
872
+ item_r.returnPressed.connect(self.edit_reminder)
873
+
874
+ # set the font
875
+ if todo["complete"] is True:
876
+ item_r.setFont(self.complete_font)
877
+ else:
878
+ item_r.setFont(self.normal_font)
879
+
880
+ # put the items in the table
881
+ row_count = self.table.rowCount()
882
+ assert row_count == i
883
+ self.table.insertRow(i)
884
+ self.table.setCellWidget(i, 0, item_p)
885
+ self.table.setCellWidget(i, 1, item_r)
886
+ i += 1
887
+
888
+ # update the database todo_count
889
+ settings.db.todo_count = len(settings.db.todo_lists[settings.db.active_list])
890
+ settings.db.list_count = len(settings.db.todo_lists.keys())
891
+
892
+ # update progress and status bars
893
+ self.update_progress_bar()
894
+ self.update_status_bar()
895
+
896
+ def tray_event(self, reason=QtWidgets.QSystemTrayIcon.activated, *args, **kwargs):
897
+ """Hide the main window when the system tray icon is clicked."""
898
+ if reason == QtWidgets.QSystemTrayIcon.activated:
899
+ if self.isVisible():
900
+ self.hide()
901
+ else:
902
+ self.show()
903
+
904
+ @error_on_none_db
905
+ def closeEvent(self, event, *args, **kwargs):
906
+ """Take care of clean up details."""
907
+ # save configuration
908
+ result, msg = settings.db.write_config()
909
+ if not result:
910
+ QtWidgets.QMessageBox.warning(self, "Write Error", msg)
911
+
912
+ # save todo lists
913
+ self.write_todo_data()
914
+
915
+ # shutdown database network server
916
+ if settings.db.server_running():
917
+ settings.db.net_server.shutdown()
918
+
919
+ # hide the tray icon
920
+ self.tray_icon.hide()
921
+
922
+ logger.log.info("Closing main window, quitting application")
923
+
924
+ # close the window
925
+ event.accept()