Skip to content

Commit

Permalink
Add possibility to rename deck and reset time info
Browse files Browse the repository at this point in the history
Added rename deck dialog when double click deck name
Added reset deck to clear all spaced repetition time info of a deck
General bug fixes and other minor additions
  • Loading branch information
lucabindini committed Dec 24, 2021
1 parent db15d42 commit bab7dc5
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 92 deletions.
10 changes: 5 additions & 5 deletions src/model/card.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import dataclasses

from PyQt5 import QtCore
import config


@dataclasses.dataclass
class Card:
identifier: int
deck_name: str
question: str
_answer: str

def set_answer(self, answer: str) -> None:
self._answer = answer.replace(QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppDataLocation), '<//>')
self._answer = answer.replace(
f'{config.DECKS_DIR}{self.deck_name}', '<//>')

def get_answer(self) -> str:
return self._answer.replace('<//>',
QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppDataLocation))
f'{config.DECKS_DIR}{self.deck_name}')
22 changes: 20 additions & 2 deletions src/model/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import pickle
import shutil
import tarfile
import typing

from model import card
import config


DeckT = typing.TypeVar('DeckT', bound='Deck')


class Deck:

def __init__(self, name: str) -> None:
Expand All @@ -26,7 +30,7 @@ def __init__(self, name: str) -> None:
os.makedirs(f'{config.DECKS_DIR}{name}/{config.IMG_DIR}')

@staticmethod
def load_deck(filepath: str) -> Deck:
def load(filepath: str) -> DeckT:
with open(filepath, 'rb') as f:
d: Deck = pickle.load(f)
n = min((datetime.date.today() - d.last_study_day).days,
Expand All @@ -38,7 +42,8 @@ def load_deck(filepath: str) -> Deck:
return d

def add_card(self, question: str, answer: str) -> None:
self.cards.append(card.Card(self._current_id, question, answer))
self.cards.append(card.Card(self._current_id,
self.name, question, answer))
self._cards_strengths[self._current_id] = 1
self._new_queue.append(self.cards[-1])
self._current_id += 1
Expand Down Expand Up @@ -110,6 +115,19 @@ def dump(self) -> None:
'wb') as f:
pickle.dump(self, f)

def rename(self, name: str) -> None:
os.rename(f'{config.DECKS_DIR}{self.name}',
f'{config.DECKS_DIR}{name}')
self.name = name
for c in self.cards:
c.deck_name = name
self.dump()

def reset(self) -> None:
self._new_queue = collections.deque(self.cards)
self._queues = [collections.deque()]
self._cards_strengths = [1 for _ in self.cards]

def delete(self) -> None:
shutil.rmtree(f'{config.DECKS_DIR}{self.name}')

Expand Down
12 changes: 9 additions & 3 deletions src/view/editor_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def __init__(self, *args, **kwargs) -> None:
question_label = QtWidgets.QLabel('Question:')
right_layout.addWidget(question_label)
self._question_edit = QtWidgets.QLineEdit()
self._question_edit.textEdited.connect(self.show_card)
right_layout.addWidget(self._question_edit)
answer_label = QtWidgets.QLabel('Answer:')
right_layout.addWidget(answer_label)
Expand All @@ -70,6 +71,9 @@ def __init__(self, *args, **kwargs) -> None:
self.question_prefix + c.question
for i, c in enumerate(self._deck.cards))

self._cards_list.setFocus()
self._cards_list.setCurrentRow(0)

def remove_card(self) -> None:
current_row = self._cards_list.currentRow()
self._cards_list.takeItem(current_row)
Expand All @@ -95,6 +99,8 @@ def create_card(self) -> None:
self._up_btn.setEnabled(True)
self._down_btn.setDisabled(True)

self._question_edit.setFocus()

def move_card(self, up: bool) -> None:
current_row = self._cards_list.currentRow()
self._deck.move_card(current_row, up)
Expand Down Expand Up @@ -135,7 +141,7 @@ def show_card(self) -> None:
self._question_edit.setText(
self._deck.cards[self._cards_list.currentRow()].question)
self._answer_edit.setText(
self._deck.cards[self._cards_list.currentRow()]._answer)
self._deck.cards[self._cards_list.currentRow()].get_answer())
self._old_select = self._cards_list.currentRow()

def add_image(self) -> None:
Expand Down Expand Up @@ -164,8 +170,8 @@ def exit(self) -> None:
try:
self._deck.cards[self._old_select].question \
= self._question_edit.text()
self._deck.cards[self._old_select]._answer \
= self._answer_edit.toHtml()
self._deck.cards[self._old_select].set_answer(
self._answer_edit.toHtml())
except TypeError:
pass
self._deck.dump()
106 changes: 95 additions & 11 deletions src/view/home_widget.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import re
import tarfile

from PyQt5 import QtWidgets, QtCore, QtGui
Expand Down Expand Up @@ -31,13 +32,14 @@ def __init__(self, *args, **kwargs) -> None:

new_deck_btn = QtWidgets.QPushButton(
QtGui.QIcon(f'{config.ICONS_DIR}plus.png'), 'Create new deck')
new_deck_btn.released.connect(self.window().create_deck)
new_deck_btn.released.connect(self.create_deck)
outer_layout.addWidget(new_deck_btn)

self.window().action_new_deck.setVisible(True)
self.window().action_export_deck.setVisible(True)
self.window().action_import_deck.setVisible(True)
self.window().action_reset_deck.setVisible(True)
self.window().action_delete_deck.setVisible(True)
self.window().action_import.setVisible(True)
self.window().action_export.setVisible(True)

def populate_list(self) -> None:
self._scroll_area_widget_contents = QtWidgets.QWidget()
Expand All @@ -51,11 +53,12 @@ def populate_list(self) -> None:
frame.setFrameStyle(QtWidgets.QFrame.Box)
horizontal_layout = QtWidgets.QHBoxLayout(frame)
horizontal_layout.setAlignment(QtCore.Qt.AlignCenter)
label = QtWidgets.QLabel(deck_name, frame)
label = DoubleClickedQLabel(deck_name, frame)
label.setFixedWidth(400)
font = label.font()
font.setPointSize(12)
label.setFont(font)
label.setToolTip('Double click to rename')
horizontal_layout.addWidget(label)
study_btn = QtWidgets.QPushButton(
QtGui.QIcon(f'{config.ICONS_DIR}book-open.png'), '')
Expand All @@ -80,27 +83,50 @@ def populate_list(self) -> None:
self._inner_layout.addWidget(frame)
self._scroll_area.setWidget(self._scroll_area_widget_contents)

def create_deck(self) -> None:
dlg = NameDeckDialog(is_new_deck=True, parent=self.window())
if dlg.exec():
d = deck.Deck(dlg.deck_name)
editor_widget.EditorWidget(d, parent=self.window())

def edit_deck(self, name: str) -> None:
d = deck.Deck.load_deck(f'{config.DECKS_DIR}{name}/'
+ config.DECK_FILE)
d = deck.Deck.load(f'{config.DECKS_DIR}{name}/'
+ config.DECK_FILE)
editor_widget.EditorWidget(d, parent=self.window())

def study_deck(self, name: str) -> None:
study_widget.StudyWidget(deck.Deck.load_deck(
study_widget.StudyWidget(deck.Deck.load(
f'{config.DECKS_DIR}{name}/{config.DECK_FILE}'),
parent=self.window())

def reset_deck(self) -> None:
dlg = SelectDeckDialog('reset', self)
deck_name = None
if dlg.exec():
deck_name = dlg.deck_name
if deck_name is None or QtWidgets.QMessageBox.question(
self.window(), 'Reset deck',
'Are you sure you want to reset all of the scheduling '
+ f'information for {repr(deck_name)} deck?') \
== QtWidgets.QMessageBox.No:
return
d = deck.Deck.load(
f'{config.DECKS_DIR}{deck_name}/{config.DECK_FILE}')
d.reset()
d.dump()
# self.populate_list()

def delete_deck(self, deck_name: str = None) -> None:
if deck_name is None:
dlg = SelectDeckDialog('delete', self)
if dlg.exec():
deck_name = dlg.deck_name
if deck_name is None or QtWidgets.QMessageBox.question(
self.window(), 'Delete deck',
f'Are you sure you want delete {repr(deck_name)} deck?') \
f'Are you sure you want to delete {repr(deck_name)} deck?') \
== QtWidgets.QMessageBox.No:
return
d = deck.Deck.load_deck(
d = deck.Deck.load(
f'{config.DECKS_DIR}{deck_name}/{config.DECK_FILE}')
d.delete()
self.populate_list()
Expand All @@ -118,7 +144,7 @@ def export_deck(self) -> None:
+ 'selected directory. Do you want to overwrite it?') \
== QtWidgets.QMessageBox.No:
return
d = deck.Deck.load_deck(
d = deck.Deck.load(
f'{config.DECKS_DIR}{deck_name}/{config.DECK_FILE}')
d.export(path)

Expand All @@ -127,7 +153,6 @@ def import_deck(self) -> None:
parent=self.window(), filter='Archive (*.tar)')[0]
try:
with tarfile.open(file_name) as tar:
print(tar.getnames()[0])
if os.access(f'{config.DECKS_DIR}{tar.getnames()[0]}',
os.F_OK) \
and QtWidgets.QMessageBox.question(
Expand All @@ -146,6 +171,65 @@ def exit(self) -> None:
pass


class DoubleClickedQLabel(QtWidgets.QLabel):

def mouseDoubleClickEvent(self, a0: QtGui.QMouseEvent) -> None:
dlg = NameDeckDialog(parent=self.window())
if dlg.exec():
deck.Deck.load(
f'{config.DECKS_DIR}{self.text()}/{config.DECK_FILE}')\
.rename(dlg.deck_name)
self.window().centralWidget().populate_list()


class NameDeckDialog(QtWidgets.QDialog):

def __init__(self, is_new_deck: bool = False, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

self._is_new_deck = is_new_deck
if self._is_new_deck:
self.setWindowTitle('Create deck')
else:
self.setWindowTitle('Rename deck')
self.resize(400, 150)
self.setMinimumSize(self.size())
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)

self._syntax_error_label = QtWidgets.QLabel(
'The only permitted characters are a-z, A-Z, 0-9 and _')
self._syntax_error_label.setStyleSheet("color: #ff0000")
layout.addWidget(self._syntax_error_label)
self._syntax_error_label.hide()

self._exist_error_label = QtWidgets.QLabel(
'Two decks cannot have the same name')
self._exist_error_label.setStyleSheet("color: #ff0000")
layout.addWidget(self._exist_error_label)
self._exist_error_label.hide()

self._line_edit = QtWidgets.QLineEdit(self)
self._line_edit.setPlaceholderText('Insert deck name')
layout.addWidget(self._line_edit)

q_btn = QtWidgets.QDialogButtonBox.Ok \
| QtWidgets.QDialogButtonBox.Cancel
button_box = QtWidgets.QDialogButtonBox(q_btn, parent=self)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)

def accept(self, *args, **kwargs) -> None:
if re.fullmatch('[a-zA-Z0-9_]+', self._line_edit.text()) is None:
self._syntax_error_label.show()
elif os.access(f'{config.DECKS_DIR}{self._line_edit.text()}', os.F_OK):
self._exist_error_label.show()
else:
super().accept()
self.deck_name = self._line_edit.text()


class SelectDeckDialog(QtWidgets.QDialog):

def __init__(self, action: str, *args, **kwargs) -> None:
Expand Down
Loading

0 comments on commit bab7dc5

Please sign in to comment.