-
Notifications
You must be signed in to change notification settings - Fork 1
/
ManageFlashcardsFrame.py
652 lines (567 loc) · 29.9 KB
/
ManageFlashcardsFrame.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
# Program FlashCards
#
# Copyright 2020 Ertugrul Harman
#
# E-mail : harmancode@gmail.com
# Twitter : https://twitter.com/harmancode
# Web : https://harman.page
#
# This file is part of Flashcards.
#
# Flashcards is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import tkinter as tk
from typing import Optional
from Deck import Deck
from Flashcard import Flashcard
class ManageFlashcardsFrame(tk.Frame):
def __init__(self, parent, controller):
"""
ManageFlashcardsFrame is the class that provides the view and controller for editing flashcards scene.
:param tk.Frame parent: Container frame in Program class that acts as the parent view, holding all the main views
(scenes) of the Program as child frames
:param Program.Program controller: Program class that acts as the parent controller, provides access to model methods
and properties.
"""
tk.Frame.__init__(self, parent)
# Provides direct access to the main controller (Program) and indirect access to the model (DatabaseManager).
self.controller = controller
# The parent frame of the treeview_frame. It will provide a label and a visual frame to the treeview.
self.flashcards_frame = tk.LabelFrame(self, text="Flashcards")
self.flashcards_frame.grid(row=0, column=0, padx=10, pady=10, sticky="nsew")
# Set up Treeview
# Create a new frame specific to Treeview and its scrollbar to easily use scrollbar in there
self.treeview_frame = tk.Frame(self.flashcards_frame)
self.treeview_frame.grid(row=0, column=0, pady=10, padx=10, sticky="nsew")
self.treeview = tk.ttk.Treeview(self.treeview_frame, columns=("Question", "Answer"))
self.treeview.grid(row=0, column=0, pady=2, ipady=2, sticky="nsew")
self.yscrollbar = tk.ttk.Scrollbar(self.treeview_frame, orient='vertical', command=self.treeview.yview)
self.treeview.configure(yscrollcommand=self.yscrollbar.set)
self.yscrollbar.grid(row=0, column=1, sticky='nse')
self.yscrollbar.configure(command=self.treeview.yview)
# Format columns
# We set width as 0 because we will not use parent-children rows
self.treeview.column("#0", width=0, minwidth=0, stretch=tk.NO)
# self.treeview.column("#1", anchor="w", width="364")
# self.treeview.column("#2", anchor="e", width="80")
self.treeview.column("#1", anchor="w")
self.treeview.column("#2", anchor="e")
# Create headings to the columns
self.treeview.heading("#0", text="")
self.treeview.heading("#1", text="Question", anchor="w", )
self.treeview.heading("#2", text="Answer", anchor="e")
# Add binding to the treeview
self.treeview.bind("<ButtonRelease-1>", self.row_selected)
# Add edit flashcard frame
self.edit_flashcard_frame = tk.LabelFrame(self, text="Edit Flashcard...")
self.edit_flashcard_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
self.question_label = tk.Label(self.edit_flashcard_frame, text="Question:")
self.question_label.grid(row=0, rowspan=2, column=0, sticky="w")
self.answer_label = tk.Label(self.edit_flashcard_frame, text="Answer:")
self.answer_label.grid(row=2, rowspan=2, column=0, sticky="w")
# self.question_entry = tk.Entry(self.edit_flashcard_frame)
# self.question_entry.grid(row=0, column=1, padx=10, pady=5, sticky="nsew")
self.question_textentry = tk.Text(self.edit_flashcard_frame, height=2, width=50, wrap="word")
self.question_textentry.grid(row=0, column=1, ipadx=10, ipady=10, pady=4, sticky="nsew")
self.question_textentry.configure(font=("Sans", 9))
# self.answer_entry = tk.Entry(self.edit_flashcard_frame)
# self.answer_entry.grid(row=1, column=1, padx=10, pady=5, sticky="nsew")
self.answer_textentry = tk.Text(self.edit_flashcard_frame, height=2, wrap="word")
self.answer_textentry.grid(row=2, column=1, ipadx=10, ipady=10, pady=4, sticky="nsew")
self.answer_textentry.configure(font=("Sans", 9))
# Save Flashcard
self.save_flashcard_button = tk.Button(self.edit_flashcard_frame, text="Save flashcard",
command=self.save_flashcard_button_pressed)
self.save_flashcard_button.grid(row=0, column=2, rowspan=2, padx=10, pady=10, sticky="we")
self.cancel_button = tk.Button(self.edit_flashcard_frame, text="Cancel",
command=self.cancel_button_pressed)
self.cancel_button.grid(row=1, column=2, rowspan=2, padx=10, pady=10, sticky="we")
# Stretch the entry fields
self.edit_flashcard_frame.grid_columnconfigure(0, weight=0)
self.edit_flashcard_frame.grid_columnconfigure(1, weight=1)
self.edit_flashcard_frame.grid_columnconfigure(2, weight=0)
self.edit_flashcard_frame.grid_rowconfigure(0, weight=1)
self.edit_flashcard_frame.grid_rowconfigure(1, weight=1)
self.edit_flashcard_frame.grid_rowconfigure(2, weight=1)
self.edit_flashcard_frame.grid_rowconfigure(3, weight=1)
# Set up bottom frame
self.bottom_frame = tk.Frame(self)
self.bottom_frame.grid(row=2, column=0, sticky="nsew")
# Add buttons to bottom frame
self.add_flashcard_button = tk.Button(self.bottom_frame, text="Add flashcard", width=14,
command=self.add_new_flashcard)
self.add_flashcard_button.grid(row=0, column=1, padx=10, pady=10)
self.remove_flashcard_button = tk.Button(self.bottom_frame, text="Remove flashcard", width=14,
command=self.remove_flashcard)
self.remove_flashcard_button.grid(row=0, column=3, padx=10, pady=10)
self.start_studying_button = tk.Button(self.bottom_frame, text="Start studying", width=14,
command=self.start_studying)
self.start_studying_button.grid(row=0, column=2, padx=10, pady=10)
# Center group of buttons horizontally by creating empty columns on the left and right side,
# and giving them a weight so that they consume all extra space
# https://stackoverflow.com/a/48934682/3780985
self.bottom_frame.grid_columnconfigure(0, weight=1)
self.bottom_frame.grid_columnconfigure(4, weight=1)
self.flashcards_frame.grid_rowconfigure(0, weight=1)
self.flashcards_frame.grid_columnconfigure(0, weight=1)
self.treeview_frame.grid_rowconfigure(0, weight=1)
self.treeview_frame.grid_columnconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.grid_rowconfigure(1, weight=0)
self.grid_rowconfigure(2, weight=0)
# All GUI setup is complete. Now prepare the view contents.
self.prepare_manage_flashcards_view()
def load_deck(self) -> None:
"""
Make the view ready for user interaction by loading the deck, flashcards, and configuring GUI elements.
"""
deck = self.controller.database_manager.deck
# Safety check
if deck is not None:
deck_title_string = "Deck: " + deck.get_truncated_title()
self.flashcards_frame.config(text=deck_title_string)
# self.refresh_treeview()
else:
pass
# print("Deck is None in load_deck()")
def add_data_to_treeview(self) -> None:
"""
Fill the treeview with data.
"""
try:
deck = self.controller.database_manager.deck
if hasattr(deck, "flashcards"):
flashcards = self.controller.database_manager.deck.flashcards
flashcards_count = len(flashcards)
for index in range(flashcards_count):
question = flashcards[index].question
answer = flashcards[index].answer
self.treeview.insert(parent='', index='end', iid=index, text="", values=(question, answer))
except AttributeError:
tk.messagebox.showwarning("Info", "You should create a deck first.")
except Exception as error:
print("Error in add_data_to_treeview(): ", error)
def refresh_treeview(self) -> None:
"""
Remove all contents from treeview, fill it with data and give it focus.
"""
self.remove_all_data_from_treeview()
self.clear_entry_boxes()
self.add_data_to_treeview()
self.treeview.focus_set()
# flashcards = self.controller.database_manager.deck.flashcards
# if len(flashcards) > 0:
# # self.question_entry.config(state="normal")
# # self.answer_entry.config(state="normal")
# self.question_textentry.config(state="normal")
# self.answer_textentry.config(state="normal")
# self.save_flashcard_button.config(state="normal")
# else:
# # self.question_entry.config(state="disabled")
# # self.answer_entry.config(state="disabled")
# self.question_textentry.config(state="disabled")
# self.answer_textentry.config(state="disabled")
# self.save_flashcard_button.config(state="disabled")
def remove_all_data_from_treeview(self) -> None:
"""
Remove all contents from treeview
"""
for i in self.treeview.get_children():
self.treeview.delete(i)
def index_of_last_selection_in_treeview(self) -> int:
"""
Returns the index of the item that is last selected in treeview.
:return: Index of the item in the treeview. -1 if there is no item.
"""
# Details: https://stackoverflow.com/a/30615520/3780985
result = -1
if self.treeview.focus() != '':
row_index = int(self.treeview.focus())
# print("item: ", self.treeview.item(row_index))
result = row_index
return result
def get_flashcards_when_multiple_selection_in_treeview(self) -> [Flashcard]:
"""
Returns selected flashcards in treeview as a list.
:return: [Flashcard]
"""
row_indexes = self.treeview.selection()
selected_flashcards = []
for row_index in row_indexes:
selected_flashcard = self.controller.database_manager.deck.flashcards[int(row_index)]
selected_flashcards.append(selected_flashcard)
return selected_flashcards
def select_first_flashcard(self) -> None:
"""
Calls select_flashcard_at_index(self, index) for the first row in treeview.
"""
# Select first row if there is any
if self.controller.database_manager.deck is not None:
if len(self.controller.database_manager.deck.flashcards) > 0:
self.select_flashcard_at_index(0)
else:
pass
# print("Error: No flashcard for select_first_flashcard()")
def select_last_flashcard_in_treeview(self) -> None:
"""
Calls select_flashcard_at_index(self, index) for the last row in treeview.
"""
# Select last flashcard item in treeview, i.e. give it focus
flashcards = self.controller.database_manager.deck.flashcards
count = len(flashcards)
if count > 0:
self.select_flashcard_at_index(count - 1)
else:
print("Error: No flashcard for select_last_flashcard_in_treeview()")
def select_flashcard_at_index(self, index) -> None:
"""
Give selection to the given index in the treeview. Assign it to the self.current.flashcard. Fill edit boxes.
:param index:
"""
flashcards = self.controller.database_manager.deck.flashcards
count = len(flashcards)
# Safety check
if 0 <= index <= count - 1:
self.treeview.selection_set(index)
self.treeview.focus(index)
self.fill_entry_boxes_based_on_selected_row_index(index)
else:
print("Error: No flashcard for select_flashcard_at_index(), index: ", index)
def selected_flashcard(self) -> Optional[Flashcard]:
"""
Returns if there is a flashcard selected in the treeview.
:return: Checks the treeview if there is an item selected. If yes, returns selected Flashcard. If no,
returns None.
:rtype: Flashcard or None
"""
result = None
index = self.index_of_last_selection_in_treeview()
if (len(self.controller.database_manager.deck.flashcards) > 0) and (index >= 0):
if self.controller.database_manager.deck.flashcards[index] is not None:
selected_flashcard = self.controller.database_manager.deck.flashcards[index]
result = selected_flashcard
return result
def fill_entry_boxes_based_on_selected_row_index(self, index) -> None:
"""
Fill text entry boxes based on the selected row index in the treeview.
:param int index: Index of selected row in treeview.
"""
if (len(self.controller.database_manager.deck.flashcards) > 0) and (index >= 0):
if self.controller.database_manager.deck.flashcards[index] is not None:
current_flashcard = self.controller.database_manager.deck.flashcards[index]
self.clear_entry_boxes()
# self.question_entry.insert(0, current_flashcard.question)
# self.answer_entry.insert(0, current_flashcard.answer)
self.question_textentry.insert(1.0, current_flashcard.question)
self.answer_textentry.insert(1.0, current_flashcard.answer)
else:
print("There is not any flashcard at index", index)
# Binding function for treeview selection event
def row_selected(self, event):
"""
Event handler for row selection in treeview.
:param event: Treeview button release
"""
# Check if there are any flashcards in the deck
if len(self.controller.database_manager.deck.flashcards) > 0:
# Set add_mode_switch to False. This will automatically enable entry boxes for the selected flashcard
# if any.
self.add_mode_switch(status=False)
def start_studying(self) -> None:
"""
Event handler for start_studying button click. It finds the deck index and calls self.controller.open_deck(
index=index) with that index.
"""
index = self.controller.database_manager.decks.index(self.controller.database_manager.deck)
self.controller.open_deck(index=index)
def remove_selection_from_treeview(self) -> None:
"""
Removes any selection from the treeview.
"""
# See: https://stackoverflow.com/a/48593195
if len(self.treeview.selection()) > 0:
self.treeview.selection_remove(self.treeview.selection()[0])
def add_mode_switch(self, status) -> None:
"""
There are two modes: self.adding_new_flashcard = True or False
When it is True, user is adding a new flashcard by using the entry boxes.
When it is False, user is editing an existing flashcard by using the entry boxes.
This functions makes this switch, configures the view, and variables accordingly.
:param status: True when user is adding a new Flashcard. Otherwise, by default, False.
:type status: bool
"""
if status:
self.adding_new_flashcard = True
self.edit_flashcard_frame.config(text="Add new flashcard... ")
self.clear_entry_boxes()
self.question_textentry.focus_set()
self.remove_selection_from_treeview()
else:
self.adding_new_flashcard = False
index = self.index_of_last_selection_in_treeview()
# Safety check
if index >= 0:
self.fill_entry_boxes_based_on_selected_row_index(index)
self.edit_flashcard_frame.config(text="Edit flashcard... ")
# Set the status of the buttons depending on the add/edit mode/status
self.configure_buttons()
def cancel_button_pressed(self) -> None:
"""
Event handler for cancel button click.
"""
if self.adding_new_flashcard:
self.clear_entry_boxes()
self.add_mode_switch(status=False)
self.treeview.focus_set()
index = self.index_of_last_selection_in_treeview()
if index >= 0:
self.treeview.selection_set(index)
else:
self.add_flashcard_button.focus_set()
def add_new_flashcard(self) -> None:
"""
Just calls self.add_mode_switch by passing True as status parameter. This function is called when user clicks
on the "Add new flashcard..." button.
"""
if len(self.controller.database_manager.deck.flashcards) < Deck.MAXIMUM_AMOUNT_OF_FLASHCARDS:
self.add_mode_switch(status=True)
else:
self.show_too_many_flashcards_warning()
def remove_flashcard(self) -> None:
"""
Deletes a flashcard from database, from model, and from the view. And then refreshes the view and selects
first flashcard in the treeview, if there is any.
"""
flashcards = self.get_flashcards_when_multiple_selection_in_treeview()
for flashcard in flashcards:
self.controller.database_manager.delete_flashcard_from_db(flashcard.flashcard_id)
self.controller.database_manager.deck.flashcards.remove(flashcard)
self.clear_entry_boxes()
self.refresh_treeview()
self.select_first_flashcard()
self.activate_adding_flashcard_mode_if_no_flashcard()
def activate_adding_flashcard_mode_if_no_flashcard(self):
"""
Activate adding flashcard mode if there is no flashcard in the deck. This will make the program more
easy-to-use, because user will not have to click on "Add flashcard" button to add a new flashcard.
"""
if self.controller.database_manager.deck is not None:
if len(self.controller.database_manager.deck.flashcards) == 0:
self.add_mode_switch(True)
else:
self.add_mode_switch(False)
def clear_entry_boxes(self) -> None:
"""
Clear text the text entry boxes in the frame.
"""
# self.question_entry.delete(0, tk.END)
# self.answer_entry.delete(0, tk.END)
self.question_textentry.delete(1.0, tk.END)
self.answer_textentry.delete(1.0, tk.END)
def update_existing_flashcard(self) -> None:
"""
Updates flashcard by using texts in entry boxes.
"""
selected_treeview_index = self.index_of_last_selection_in_treeview()
# Safety check
if selected_treeview_index >= 0:
flashcard = self.controller.database_manager.deck.flashcards[selected_treeview_index]
# question = str(self.question_entry.get())
# answer = str(self.answer_entry.get())
question = str(self.question_textentry.get("1.0", tk.END))
answer = str(self.answer_textentry.get("1.0", tk.END))
question = question.strip()
answer = answer.strip()
if len(question) == 0 or len(answer) == 0:
tk.messagebox.showwarning("Info", "Question or answer cannot be empty.")
elif len(question) > Flashcard.MAX_LENGTH_OF_QUESTION:
warning_message = "Question cannot be longer than {} characters.".format(
Flashcard.MAX_LENGTH_OF_QUESTION)
tk.messagebox.showwarning("Too long question", warning_message)
elif len(answer) > Flashcard.MAX_LENGTH_OF_ANSWER:
warning_message = "Answer cannot be longer than {} characters.".format(
Flashcard.MAX_LENGTH_OF_ANSWER)
tk.messagebox.showwarning("Too long answer", warning_message)
else:
flashcard.question = question
flashcard.answer = answer
self.controller.database_manager.update_flashcard_in_db(flashcard_id=flashcard.flashcard_id,
question=question,
answer=answer,
last_study_date=flashcard.last_study_date,
due_date_string=flashcard.due_date_string,
inter_repetition_interval=flashcard.inter_repetition_interval,
easiness_factor=flashcard.easiness_factor,
repetition_number=flashcard.repetition_number
)
self.refresh_treeview()
self.select_flashcard_at_index(selected_treeview_index)
def are_entry_box_entries_valid_to_save(self, show_warnings: bool = False) -> bool:
"""
Checks if entry boxes have valid entries to be saved as Flashcard properties.
:param show_warnings: If True, user will be warned about the issue.
:type show_warnings: bool
:return: True if entries are valid. False if not.
:rtype: bool
"""
result = False
question_is_valid = True
answer_is_valid = True
question = self.question_textentry.get("1.0", tk.END).strip()
answer = self.answer_textentry.get("1.0", tk.END).strip()
if len(question) <= 0 or len(answer) <= 0:
if show_warnings:
tk.messagebox.showwarning("Info", "Question or answer cannot be empty.")
question_is_valid = False
elif len(question) > Flashcard.MAX_LENGTH_OF_QUESTION:
if show_warnings:
warning_message = "Question cannot be longer than {} characters.".format(
Flashcard.MAX_LENGTH_OF_QUESTION)
tk.messagebox.showwarning("Too long question", warning_message)
question_is_valid = False
elif len(answer) > Flashcard.MAX_LENGTH_OF_ANSWER:
if show_warnings:
warning_message = "Answer cannot be longer than {} characters.".format(
Flashcard.MAX_LENGTH_OF_ANSWER)
tk.messagebox.showwarning("Too long answer", warning_message)
answer_is_valid = False
if question_is_valid and answer_is_valid:
result = True
else:
result = False
return result
def create_new_flashcard(self) -> None:
"""
Creates and adds a new flashcard to the current deck. It saves it to the database by calling necessary
functions.
"""
if self.are_entry_box_entries_valid_to_save(show_warnings=True):
deck_id = self.controller.database_manager.deck.deck_id
due_date_string = self.controller.database_manager.today_as_string()
# It is safe to take data from textentry boxes because their validity has just been checked in
# self.are_entry_box_entries_valid_to_save()
question = self.question_textentry.get("1.0", tk.END).strip()
answer = self.answer_textentry.get("1.0", tk.END).strip()
# Add flashcard to the database and obtain a unique flashcard_id
new_flashcard_id = self.controller.database_manager.add_new_flashcard_to_db(
deck_id=deck_id,
question=question,
answer=answer,
last_study_date=None,
due_date_string=due_date_string)
# Initialize new Flashcard object with the just-obtained flashcard_id
new_flashcard = Flashcard(flashcard_id=new_flashcard_id,
deck_id=deck_id,
question=question,
answer=answer,
last_study_date=None,
due_date_string=due_date_string,
inter_repetition_interval=0,
easiness_factor=0,
repetition_number=0)
# Add initialized Flashcard object to the deck.flashcard list
self.controller.database_manager.deck.flashcards.append(new_flashcard)
self.refresh_treeview()
self.select_last_flashcard_in_treeview()
self.scroll_to_the_bottom_of_treeview()
self.add_mode_switch(False)
def scroll_to_the_bottom_of_treeview(self) -> None:
"""
Scroll to the bottom of the treeview.
"You can use tree.yview_moveto(1) to display the bottom of the table. The yview_moveto method takes as
argument the fraction of the total (scrollable) widget height that you want to be off-screen to the top.
So, yview_moveto(0) will display the top part of the table, yview_moveto(1) the bottom part and
yview_moveto(0.5) will adjust the display so that the top half of the widget is hidden."
Source: https://stackoverflow.com/a/42161808
"""
self.treeview.yview_moveto(1)
def save_flashcard_button_pressed(self) -> None:
"""
Called when "Save flashcard" buttons pressed. It calls relevant functions based on the status of the
self.adding_new_flashcard flag.
"""
# Set self.adding_new_flashcard = True when there is no flashcard in the deck. Because when there is no
# flashcard, user will probably trying to add a new flashcard by using the entry boxes, instead of trying to
# edit something.
if len(self.controller.database_manager.deck.flashcards) < Deck.MAXIMUM_AMOUNT_OF_FLASHCARDS:
if len(self.controller.database_manager.deck.flashcards) == 0:
self.adding_new_flashcard = True
if self.adding_new_flashcard:
self.create_new_flashcard()
else:
self.update_existing_flashcard()
else:
self.show_too_many_flashcards_warning()
def show_too_many_flashcards_warning(self) -> None:
"""
Shows warning message when user tries to add a new flashcard when the deck is full.
"""
warning_message = "A deck can not contain more than {} flashcards.".format(
Deck.MAXIMUM_AMOUNT_OF_FLASHCARDS)
tk.messagebox.showwarning("Too many flashcards", warning_message)
def configure_buttons(self) -> None:
"""
Set the status of the buttons depending on the adding/editing flashcard status, and on the amount of flashcards.
"""
if self.adding_new_flashcard:
self.add_flashcard_button.config(state="disabled")
self.remove_flashcard_button.config(state="disabled")
self.start_studying_button.config(state="disabled")
else:
self.add_flashcard_button.config(state="normal")
self.remove_flashcard_button.config(state="normal")
self.start_studying_button.config(state="normal")
if len(self.controller.database_manager.deck.flashcards) == 0:
self.start_studying_button.config(state="disabled")
self.remove_flashcard_button.config(state="disabled")
else:
self.start_studying_button.config(state="normal")
self.remove_flashcard_button.config(state="normal")
def is_there_unsaved_changes_in_flashcard(self) -> bool:
"""
Check if there is any changes made in the flashcard's question or answer.
:return: True if there is any change. False if there is not.
:rtype: bool
"""
result = False
question_in_entrybox = self.question_textentry.get("1.0", tk.END).strip()
answer_in_entrybox = self.answer_textentry.get("1.0", tk.END).strip()
selected_flashcard = self.selected_flashcard()
if selected_flashcard != None:
if (question_in_entrybox != selected_flashcard.question or
answer_in_entrybox != selected_flashcard.answer):
result = True
return result
def ask_save_question_if_necessary(self):
"""
Asks user if flashcard should be saved or not by showing a messagebox.
"""
asking_is_necessary = False
adding_new_flashcard = self.adding_new_flashcard
unsaved_changes = self.is_there_unsaved_changes_in_flashcard()
valid_entries = self.are_entry_box_entries_valid_to_save()
if (adding_new_flashcard or unsaved_changes) and valid_entries:
response = tk.messagebox.askquestion('Save changes?', 'Would you like to save this flashcard?')
if response == 'yes':
self.save_flashcard_button_pressed()
def prepare_manage_flashcards_view(self) -> None:
"""
Prepare the view before bringing it to the front (displaying it to the user), by refreshing the treeview and
selecting the first flashcard in the treeview.
"""
# done: refresh_treeview() is called here and in load_deck(). Eliminate duplicate calls.
self.load_deck()
self.refresh_treeview()
self.select_first_flashcard()
self.activate_adding_flashcard_mode_if_no_flashcard()