Skip to content

Commit

Permalink
Add import/export capability and other changes
Browse files Browse the repository at this point in the history
Added error messages for deck creation when using the name of an already
existing deck
  • Loading branch information
MarcoBenelli committed Dec 23, 2021
1 parent 6393559 commit db15d42
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 60 deletions.
13 changes: 12 additions & 1 deletion src/model/card.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import dataclasses

from PyQt5 import QtCore


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

def set_answer(self, answer: str) -> None:
self._answer = answer.replace(QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppDataLocation), '<//>')

def get_answer(self) -> str:
return self._answer.replace('<//>',
QtCore.QStandardPaths.writableLocation(
QtCore.QStandardPaths.AppDataLocation))
26 changes: 16 additions & 10 deletions src/model/deck.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import os
import pickle
import shutil
import tarfile

from model import card
import config
Expand All @@ -24,6 +25,18 @@ def __init__(self, name: str) -> None:
self._img_id = 0
os.makedirs(f'{config.DECKS_DIR}{name}/{config.IMG_DIR}')

@staticmethod
def load_deck(filepath: str) -> Deck:
with open(filepath, 'rb') as f:
d: Deck = pickle.load(f)
n = min((datetime.date.today() - d.last_study_day).days,
len(d._queues)-1)
for i in range(n):
d._queues[0].extend(d._queues[i+1])
del d._queues[1:n+1]
d.last_study_day = datetime.date.today()
return d

def add_card(self, question: str, answer: str) -> None:
self.cards.append(card.Card(self._current_id, question, answer))
self._cards_strengths[self._current_id] = 1
Expand Down Expand Up @@ -100,16 +113,9 @@ def dump(self) -> None:
def delete(self) -> None:
shutil.rmtree(f'{config.DECKS_DIR}{self.name}')


def load_deck(filepath: str) -> Deck:
with open(filepath, 'rb') as f:
d: Deck = pickle.load(f)
n = min((datetime.date.today() - d.last_study_day).days, len(d._queues)-1)
for i in range(n):
d._queues[0].extend(d._queues[i+1])
del d._queues[1:n+1]
d.last_study_day = datetime.date.today()
return d
def export(self, file_name: str) -> None:
with tarfile.open(file_name, 'w') as tar:
tar.add(f'{config.DECKS_DIR}{self.name}', self.name)


class EmptyQueuesException(Exception):
Expand Down
15 changes: 8 additions & 7 deletions src/view/editor_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ def move_card(self, up: bool) -> None:

def show_card(self) -> None:
try:
self._deck.cards[self._old_select].question\
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())
self._cards_list.item(self._old_select).setText(
self.question_prefix
+ self._deck.cards[self._old_select].question)
Expand All @@ -135,11 +135,12 @@ 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()]._answer)
self._old_select = self._cards_list.currentRow()

def add_image(self) -> None:
file_name = QtWidgets.QFileDialog.getOpenFileName(parent=self.window(),
file_name = QtWidgets.QFileDialog.getOpenFileName(
parent=self.window(),
filter='Images (*.png *.jpeg *.jpg)')
try:
path = self._deck.add_image(
Expand All @@ -161,9 +162,9 @@ def disable(self) -> None:

def exit(self) -> None:
try:
self._deck.cards[self._old_select].question\
self._deck.cards[self._old_select].question \
= self._question_edit.text()
self._deck.cards[self._old_select].answer\
self._deck.cards[self._old_select]._answer \
= self._answer_edit.toHtml()
except TypeError:
pass
Expand Down
72 changes: 57 additions & 15 deletions src/view/home_widget.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
import tarfile

from PyQt5 import QtWidgets, QtCore, QtGui

Expand Down Expand Up @@ -29,11 +30,14 @@ def __init__(self, *args, **kwargs) -> None:
outer_layout.addWidget(self._scroll_area)

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

self.window().action_new_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 Down Expand Up @@ -68,20 +72,21 @@ def populate_list(self) -> None:
QtGui.QIcon(f'{config.ICONS_DIR}cross.png'), '')
delete_btn.setFixedWidth(32)
delete_btn.setToolTip('Delete deck')
delete_btn.released.connect(lambda d=deck_name: self.delete_deck(d))
delete_btn.released.connect(
lambda d=deck_name: self.delete_deck(d))
horizontal_layout.addWidget(study_btn)
horizontal_layout.addWidget(edit_btn)
horizontal_layout.addWidget(delete_btn)
self._inner_layout.addWidget(frame)
self._scroll_area.setWidget(self._scroll_area_widget_contents)

def edit_deck(self, name: str) -> None:
d = deck.load_deck(f'{config.DECKS_DIR}{name}/'
+ config.DECK_FILE)
d = deck.Deck.load_deck(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.load_deck(
study_widget.StudyWidget(deck.Deck.load_deck(
f'{config.DECKS_DIR}{name}/{config.DECK_FILE}'),
parent=self.window())

Expand All @@ -90,15 +95,51 @@ def delete_deck(self, deck_name: str = None) -> None:
dlg = SelectDeckDialog('delete', self)
if dlg.exec():
deck_name = dlg.deck_name
if deck_name is not None:
dialog = QtWidgets.QMessageBox(parent=self.window())
dialog.setText(f'Are you sure you want delete {deck_name} deck?')
dialog.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
if dialog.exec() == QtWidgets.QMessageBox.Ok:
d = deck.load_deck(
f'{config.DECKS_DIR}{deck_name}/'
+ config.DECK_FILE)
d.delete()
if deck_name is None or QtWidgets.QMessageBox.question(
self.window(), 'Delete deck',
f'Are you sure you want delete {repr(deck_name)} deck?') \
== QtWidgets.QMessageBox.No:
return
d = deck.Deck.load_deck(
f'{config.DECKS_DIR}{deck_name}/{config.DECK_FILE}')
d.delete()
self.populate_list()

def export_deck(self) -> None:
dialog = SelectDeckDialog('export', self)
if not dialog.exec():
return
deck_name = dialog.deck_name
directory = QtWidgets.QFileDialog.getExistingDirectory(self.window())
path = f'{directory}/{deck_name}.tar'
if os.access(path, os.F_OK) and QtWidgets.QMessageBox.question(
self.window(), 'File already exists',
f'A file called "{deck_name}.tar" already exists in the '
+ 'selected directory. Do you want to overwrite it?') \
== QtWidgets.QMessageBox.No:
return
d = deck.Deck.load_deck(
f'{config.DECKS_DIR}{deck_name}/{config.DECK_FILE}')
d.export(path)

def import_deck(self) -> None:
file_name = QtWidgets.QFileDialog.getOpenFileName(
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(
self.window(), 'Deck already exists',
f'A deck called {repr(tar.getnames()[0])} already '
+ 'exists. Do you want to overwrite it?') \
== QtWidgets.QMessageBox.No:
return
tar.extractall(config.DECKS_DIR)
except ValueError:
pass
else:
self.populate_list()

def exit(self) -> None:
Expand All @@ -122,7 +163,8 @@ def __init__(self, action: str, *args, **kwargs) -> None:
self._combo_box.addItem(deck_name)
layout.addWidget(self._combo_box)

q_btn = QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
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)
Expand Down
49 changes: 27 additions & 22 deletions src/view/main_window.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import re

from PyQt5 import QtWidgets, QtGui, QtCore
Expand All @@ -20,19 +21,15 @@ def __init__(self, *args, **kwargs) -> None:
menubar = QtWidgets.QMenuBar(self)

menu_file = QtWidgets.QMenu(menubar)
# menu_export_deck = QtWidgets.QMenu(menu_file)
self.setMenuBar(menubar)
self.action_new_deck = QtWidgets.QAction(self)
action_quit = QtWidgets.QAction(self)
# action_import_deck = QtWidgets.QAction(self)
# action_local_export = QtWidgets.QAction(self)
# action_remote_export = QtWidgets.QAction(self)
self.action_import = QtWidgets.QAction(self)
self.action_export = QtWidgets.QAction(self)
self.action_delete_deck = QtWidgets.QAction(self)
# menu_export_deck.addAction(action_local_export)
# menu_export_deck.addAction(action_remote_export)
menu_file.addAction(self.action_new_deck)
# menu_file.addAction(action_import_deck)
# menu_file.addAction(menu_export_deck.menuAction())
menu_file.addAction(self.action_export)
menu_file.addAction(self.action_import)
menu_file.addAction(self.action_delete_deck)
menu_file.addAction(action_quit)
menubar.addAction(menu_file.menuAction())
Expand All @@ -43,8 +40,6 @@ def __init__(self, *args, **kwargs) -> None:

menu_file.setTitle(QtCore.QCoreApplication.translate(
'Study and Repeat', 'File'))
# menu_export_deck.setTitle(QtCore.QCoreApplication.translate(
# 'Study and Repeat', 'Export deck'))
# menu_help.setTitle(QtCore.QCoreApplication.translate(
# 'Study and Repeat', 'Help'))
self.action_new_deck.setText(QtCore.QCoreApplication.translate(
Expand All @@ -53,19 +48,21 @@ def __init__(self, *args, **kwargs) -> None:
# 'Study and Repeat', 'About'))
action_quit.setText(QtCore.QCoreApplication.translate(
'Study and Repeat', 'Quit'))
# action_import_deck.setText(QtCore.QCoreApplication.translate(
# 'Study and Repeat', 'Import deck'))
# action_local_export.setText(QtCore.QCoreApplication.translate(
# 'Study and Repeat', 'Local export'))
# action_remote_export.setText(QtCore.QCoreApplication.translate(
# 'Study and Repeat', 'Remote export'))
self.action_import.setText(QtCore.QCoreApplication.translate(
'Study and Repeat', 'Import deck'))
self.action_export.setText(QtCore.QCoreApplication.translate(
'Study and Repeat', 'Export deck'))
self.action_delete_deck.setText(QtCore.QCoreApplication.translate(
'Study and Repeat', 'Delete deck'))

self.action_new_deck.triggered.connect(self.create_deck)
action_quit.triggered.connect(self.close)
self.action_delete_deck.triggered.connect(
lambda: self.centralWidget().delete_deck())
self.action_export.triggered.connect(
lambda: self.centralWidget().export_deck())
self.action_import.triggered.connect(
lambda: self.centralWidget().import_deck())

self.setCentralWidget(home_widget.HomeWidget(parent=self))

Expand All @@ -89,11 +86,17 @@ def __init__(self, *args, **kwargs) -> None:
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)

self._error_label = QtWidgets.QLabel('The only permitted characters '
+ 'are a-z, A-Z, 0-9 and _')
self._error_label.setStyleSheet("color: #ff0000")
layout.addWidget(self._error_label)
self._error_label.hide()
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')
Expand All @@ -108,7 +111,9 @@ def __init__(self, *args, **kwargs) -> None:

def accept(self, *args, **kwargs) -> None:
if re.fullmatch('[a-zA-Z0-9_]+', self._line_edit.text()) is None:
self._error_label.show()
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 = deck.Deck(self._line_edit.text())
8 changes: 4 additions & 4 deletions src/view/secondary_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ def __init__(self, d: deck.Deck, *args, **kwargs) -> None:
back_btn.released.connect(self.back_home)
self._central_widget = QtWidgets.QWidget()
self._layout.addWidget(self._central_widget)

self.window().setCentralWidget(self)

self.window().action_new_deck.setVisible(False)
self.window().action_delete_deck.setVisible(False)
self.window().action_import.setVisible(False)
self.window().action_export.setVisible(False)

def back_home(self) -> None:
self.window().action_new_deck.setVisible(True)
self.window().action_delete_deck.setVisible(True)
self.window().setCentralWidget(home_widget.HomeWidget(
parent=self.window()))
self.exit()

2 changes: 1 addition & 1 deletion src/view/study_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def show_question(self) -> None:
parent=self.window()))
else:
self._question_text.setText(self._current_card.question)
self._answer_text.setText(self._current_card.answer)
self._answer_text.setText(self._current_card.get_answer())

def show_answer(self) -> None:
self._show_btn.hide()
Expand Down

0 comments on commit db15d42

Please sign in to comment.