From 2891c675d57a726f2a5387f52e0d0b701ca0ea20 Mon Sep 17 00:00:00 2001 From: BasioMeusPuga Date: Sun, 4 Mar 2018 19:43:05 +0530 Subject: [PATCH] Metadata dialog functional. Consolidate database metadata update function. --- TODO | 4 +- __main__.py | 3 + database.py | 46 +++++++------ metadatadialog.py | 135 +++++++++++++++++++++++++++++++++----- resources/metadata.py | 51 ++++++++++---- resources/raw/metadata.ui | 128 +++++++++++++++++++++++++++++------- threaded.py | 17 ++--- widgets.py | 32 +++++++++ 8 files changed, 329 insertions(+), 87 deletions(-) diff --git a/TODO b/TODO index ac47d3a..bed43ca 100644 --- a/TODO +++ b/TODO @@ -22,9 +22,10 @@ TODO ✓ Add capability to sort by new ✓ Table view ✓ Context menu: Cache, Read, Edit database, delete, Mark read/unread - Information dialog widget + ✓ Information dialog widget Allow editing of database data through the UI + for Bookmarks Set focus to newly added file + Change selection rectangle position Reading: ✓ Drop down for TOC ✓ Override the keypress event of the textedit @@ -64,6 +65,7 @@ TODO ✓ Define every widget in code Bugs: If there are files open and the database is deleted, TypeErrors result + Cover culling does not occur if some other tab has initial focus Secondary: Annotations diff --git a/__main__.py b/__main__.py index 990a739..097da1b 100755 --- a/__main__.py +++ b/__main__.py @@ -296,6 +296,8 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): def cull_covers(self, event=None): blank_pixmap = QtGui.QPixmap() + blank_pixmap.load(':/images/blank.png') # Keep this. Removing it causes the + # listView to go blank on a resize all_indexes = set() for i in range(self.lib_ref.item_proxy_model.rowCount()): @@ -1091,6 +1093,7 @@ class MainUI(QtWidgets.QMainWindow, mainwindow.Ui_MainWindow): event.ignore() self.hide() + self.metadataDialog.hide() self.settingsDialog.hide() self.temp_dir.remove() diff --git a/database.py b/database.py index 9e76f60..b36ba54 100644 --- a/database.py +++ b/database.py @@ -19,7 +19,7 @@ import os import pickle import sqlite3 -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore class DatabaseInit: @@ -163,7 +163,7 @@ class DatabaseFunctions: return None except (KeyError, sqlite3.OperationalError): - print('Commander, SQLite is in rebellion @ data fetching handling') + print('SQLite is in wretched rebellion @ data fetching handling') def fetch_covers_only(self, hash_list): parameter_marks = ','.join(['?' for i in hash_list]) @@ -172,27 +172,33 @@ class DatabaseFunctions: self.database.close() return data - def modify_positional_data(self, positional_data): - for i in positional_data: - file_hash = i[0] - position = i[1] - last_accessed = i[2] - bookmarks = i[3] + def modify_metadata(self, metadata_dict, book_hash): - position_bin = sqlite3.Binary(pickle.dumps(position)) - last_accessed_bin = sqlite3.Binary(pickle.dumps(last_accessed)) - bookmarks_bin = sqlite3.Binary(pickle.dumps(bookmarks)) + def generate_binary(column, data): + if column in ('Position', 'LastAccessed', 'Bookmarks'): + return sqlite3.Binary(pickle.dumps(data)) + elif column == 'CoverImage': + return sqlite3.Binary(data) + else: + return data - sql_command = ( - "UPDATE books SET Position = ?, LastAccessed = ?, Bookmarks = ? WHERE Hash = ?") + sql_command = 'UPDATE books SET ' + update_data = [] + for i in metadata_dict.items(): + if i[1]: + sql_command += i[0] + ' = ?, ' + bin_data = generate_binary(i[0], i[1]) + update_data.append(bin_data) - try: - self.database.execute( - sql_command, - [position_bin, last_accessed_bin, bookmarks_bin, file_hash]) - except sqlite3.OperationalError: - print('Commander, SQLite is in rebellion @ positional data handling') - return + sql_command = sql_command[:-2] + sql_command += ' WHERE Hash = ?' + update_data.append(book_hash) + + try: + self.database.execute( + sql_command, update_data) + except sqlite3.OperationalError: + print('SQLite is in wretched rebellion @ metadata handling') self.database.commit() self.database.close() diff --git a/metadatadialog.py b/metadatadialog.py index 718d3fb..c395547 100644 --- a/metadatadialog.py +++ b/metadatadialog.py @@ -17,7 +17,12 @@ # along with this program. If not, see . from PyQt5 import QtWidgets, QtCore, QtGui + +import database + from resources import metadata +from widgets import PliantQGraphicsScene + class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog): def __init__(self, parent): @@ -26,35 +31,129 @@ class MetadataUI(QtWidgets.QDialog, metadata.Ui_Dialog): self.parent = parent self.setWindowFlags( - QtCore.Qt.Window | - QtCore.Qt.WindowCloseButtonHint) - self.setFixedSize(self.width(), self.height()) + QtCore.Qt.Popup | + QtCore.Qt.FramelessWindowHint) + + self.database_path = self.parent.database_path + + self.book_index = None + self.book_year = None + self.previous_position = None + self.cover_for_database = None + + radius = 20.0 + path = QtGui.QPainterPath() + path.addRoundedRect(QtCore.QRectF(self.rect()), radius, radius) + mask = QtGui.QRegion(path.toFillPolygon().toPolygon()) + self.setMask(mask) + + foreground = QtGui.QColor().fromRgb(230, 230, 230) + background = QtGui.QColor().fromRgb(0, 0, 0) + self.setStyleSheet( + "QDialog {{color: {0}; background-color: {1}}}".format( + foreground.name(), background.name())) + self.coverView.setStyleSheet( + "QGraphicsView {{color: {0}; background-color: {1}}}".format( + foreground.name(), background.name())) + self.okButton.setStyleSheet( + "QToolButton {background-color: red}") self.coverView.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.coverView.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - def load_book(self, cover, title, author, year, tags, index): - image_pixmap = cover.pixmap(self.coverView.size()) - graphics_scene = QtWidgets.QGraphicsScene() - graphics_scene.addPixmap(image_pixmap) - self.coverView.setScene(graphics_scene) + self.okButton.clicked.connect(self.ok_pressed) + self.cancelButton.clicked.connect(self.cancel_pressed) + + self.titleLine.returnPressed.connect(self.ok_pressed) + self.authorLine.returnPressed.connect(self.ok_pressed) + self.yearLine.returnPressed.connect(self.ok_pressed) + self.tagsLine.returnPressed.connect(self.ok_pressed) + + def load_book(self, cover, title, author, year, tags, book_index): + self.previous_position = None + self.cover_for_database = None + + self.book_index = book_index + self.book_year = year + + self.load_cover(cover) self.titleLine.setText(title) self.authorLine.setText(author) self.yearLine.setText(year) self.tagsLine.setText(tags) - def showEvent(self, event): + def load_cover(self, cover, use_as_is=False): + if use_as_is: + image_pixmap = cover + else: + image_pixmap = cover.pixmap(QtCore.QSize(140, 205)) + + graphics_scene = PliantQGraphicsScene(self) + graphics_scene.addPixmap(image_pixmap) + self.coverView.setScene(graphics_scene) + + def ok_pressed(self, event): + book_item = self.parent.lib_ref.view_model.item(self.book_index.row()) + + title = self.titleLine.text() + author = self.authorLine.text() + tags = self.tagsLine.text() + + try: + year = int(self.yearLine.text()) + except ValueError: + year = self.book_year + + tooltip_string = title + '\nAuthor: ' + author + '\nYear: ' + str(year) + + book_item.setData(title, QtCore.Qt.UserRole) + book_item.setData(author, QtCore.Qt.UserRole + 1) + book_item.setData(year, QtCore.Qt.UserRole + 2) + book_item.setData(tags, QtCore.Qt.UserRole + 4) + book_item.setToolTip(tooltip_string) + + if self.cover_for_database: + self.parent.cover_loader( + book_item, self.cover_for_database) + + self.parent.lib_ref.update_proxymodels() + self.hide() + + book_hash = book_item.data(QtCore.Qt.UserRole + 6) + database_dict = { + 'Title': title, + 'Author': author, + 'Year': year, + 'Tags': tags, + 'CoverImage': self.cover_for_database} + + database.DatabaseFunctions(self.database_path).modify_metadata( + database_dict, book_hash) + + def cancel_pressed(self, event): + self.hide() + + def generate_display_position(self, mouse_cursor_position): size = self.size() desktop_size = QtWidgets.QDesktopWidget().screenGeometry() - top = (desktop_size.height() / 2) - (size.height() / 2) - left = (desktop_size.width() / 2) - (size.width() / 2) - self.move(left, top) - self.parent.setEnabled(False) - def hideEvent(self, event): - self.parent.setEnabled(True) + display_x = mouse_cursor_position.x() + display_y = mouse_cursor_position.y() - def closeEvent(self, event): - self.parent.setEnabled(True) - event.accept() + if display_x + size.width() > desktop_size.width(): + display_x = desktop_size.width() - size.width() + + if display_y + size.height() > desktop_size.height(): + display_y = desktop_size.height() - size.height() + + return QtCore.QPoint(display_x, display_y) + + def showEvent(self, event): + if self.previous_position: + self.move(self.previous_position) + else: + display_position = self.generate_display_position(QtGui.QCursor.pos()) + self.move(display_position) + + self.titleLine.setFocus() diff --git a/resources/metadata.py b/resources/metadata.py index 19dac70..ec19c05 100644 --- a/resources/metadata.py +++ b/resources/metadata.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'raw/metadata.ui' # -# Created by: PyQt5 UI code generator 5.10 +# Created by: PyQt5 UI code generator 5.10.1 # # WARNING! All changes made in this file will be lost! @@ -11,27 +11,21 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_Dialog(object): def setupUi(self, Dialog): Dialog.setObjectName("Dialog") - Dialog.resize(700, 230) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Expanding) + Dialog.resize(728, 231) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(Dialog.sizePolicy().hasHeightForWidth()) Dialog.setSizePolicy(sizePolicy) - Dialog.setMaximumSize(QtCore.QSize(700, 230)) + Dialog.setMaximumSize(QtCore.QSize(16777215, 16777215)) Dialog.setModal(True) self.gridLayout = QtWidgets.QGridLayout(Dialog) self.gridLayout.setObjectName("gridLayout") self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.coverView = QtWidgets.QGraphicsView(Dialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.coverView.sizePolicy().hasHeightForWidth()) - self.coverView.setSizePolicy(sizePolicy) - self.coverView.setMinimumSize(QtCore.QSize(140, 218)) - self.coverView.setMaximumSize(QtCore.QSize(140, 218)) - self.coverView.setBaseSize(QtCore.QSize(140, 200)) + self.coverView.setMaximumSize(QtCore.QSize(165, 16777215)) + self.coverView.setFrameShadow(QtWidgets.QFrame.Plain) self.coverView.setObjectName("coverView") self.horizontalLayout.addWidget(self.coverView) self.verticalLayout = QtWidgets.QVBoxLayout() @@ -49,6 +43,31 @@ class Ui_Dialog(object): self.tagsLine.setMinimumSize(QtCore.QSize(0, 0)) self.tagsLine.setObjectName("tagsLine") self.verticalLayout.addWidget(self.tagsLine) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem) + self.okButton = QtWidgets.QPushButton(Dialog) + self.okButton.setText("") + icon = QtGui.QIcon() + icon.addPixmap(QtGui.QPixmap(":/images/checkmark.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.okButton.setIcon(icon) + self.okButton.setIconSize(QtCore.QSize(24, 24)) + self.okButton.setFlat(True) + self.okButton.setObjectName("okButton") + self.horizontalLayout_2.addWidget(self.okButton) + self.cancelButton = QtWidgets.QPushButton(Dialog) + self.cancelButton.setText("") + icon1 = QtGui.QIcon() + icon1.addPixmap(QtGui.QPixmap(":/images/error.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off) + self.cancelButton.setIcon(icon1) + self.cancelButton.setIconSize(QtCore.QSize(24, 24)) + self.cancelButton.setFlat(True) + self.cancelButton.setObjectName("cancelButton") + self.horizontalLayout_2.addWidget(self.cancelButton) + spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) + self.horizontalLayout_2.addItem(spacerItem1) + self.verticalLayout.addLayout(self.horizontalLayout_2) self.horizontalLayout.addLayout(self.verticalLayout) self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1) @@ -58,8 +77,14 @@ class Ui_Dialog(object): def retranslateUi(self, Dialog): _translate = QtCore.QCoreApplication.translate Dialog.setWindowTitle(_translate("Dialog", "Edit metadata")) + self.coverView.setToolTip(_translate("Dialog", "Cover (click to change)")) + self.titleLine.setToolTip(_translate("Dialog", "Title")) self.titleLine.setPlaceholderText(_translate("Dialog", "Title")) + self.authorLine.setToolTip(_translate("Dialog", "Author")) self.authorLine.setPlaceholderText(_translate("Dialog", "Author")) + self.yearLine.setToolTip(_translate("Dialog", "Year")) self.yearLine.setPlaceholderText(_translate("Dialog", "Year")) + self.tagsLine.setToolTip(_translate("Dialog", "Tags (comma separated)")) self.tagsLine.setPlaceholderText(_translate("Dialog", "Tags")) - + self.okButton.setToolTip(_translate("Dialog", "OK")) + self.cancelButton.setToolTip(_translate("Dialog", "Cancel")) diff --git a/resources/raw/metadata.ui b/resources/raw/metadata.ui index 7b02218..26a13e2 100644 --- a/resources/raw/metadata.ui +++ b/resources/raw/metadata.ui @@ -6,20 +6,20 @@ 0 0 - 700 - 230 + 728 + 231 - + 0 0 - 700 - 230 + 16777215 + 16777215 @@ -33,29 +33,17 @@ - - - 0 - 0 - - - - - 140 - 218 - - - 140 - 218 + 165 + 16777215 - - - 140 - 200 - + + Cover (click to change) + + + QFrame::Plain @@ -63,6 +51,9 @@ + + Title + Title @@ -70,6 +61,9 @@ + + Author + Author @@ -77,6 +71,9 @@ + + Year + Year @@ -90,17 +87,98 @@ 0 + + Tags (comma separated) + Tags + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + :/images/checkmark.svg:/images/checkmark.svg + + + + 24 + 24 + + + + true + + + + + + + Cancel + + + + + + + :/images/error.svg:/images/error.svg + + + + 24 + 24 + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + - + + + diff --git a/threaded.py b/threaded.py index 10143c4..eabea34 100644 --- a/threaded.py +++ b/threaded.py @@ -32,18 +32,15 @@ class BackGroundTabUpdate(QtCore.QThread): self.all_metadata = all_metadata def run(self): - hash_position_pairs = [] for i in self.all_metadata: - file_hash = i['hash'] - position = i['position'] - last_accessed = i['last_accessed'] - bookmarks = i['bookmarks'] + book_hash = i['hash'] + database_dict = { + 'Position': i['position'], + 'LastAccessed': i['last_accessed'], + 'Bookmarks': i['bookmarks']} - hash_position_pairs.append( - [file_hash, position, last_accessed, bookmarks]) - - database.DatabaseFunctions( - self.database_path).modify_positional_data(hash_position_pairs) + database.DatabaseFunctions(self.database_path).modify_metadata( + database_dict, book_hash) class BackGroundBookAddition(QtCore.QThread): diff --git a/widgets.py b/widgets.py index 3676aab..0fe31d9 100644 --- a/widgets.py +++ b/widgets.py @@ -28,6 +28,7 @@ from PyQt5 import QtWidgets, QtGui, QtCore from resources import pie_chart from models import BookmarkProxyModel +from sorter import resize_image class Tab(QtWidgets.QWidget): @@ -723,3 +724,34 @@ class PliantDockWidget(QtWidgets.QDockWidget): def hideEvent(self, event): self.parent.window().bookToolBar.bookmarkButton.setChecked(False) + + +class PliantQGraphicsScene(QtWidgets.QGraphicsScene): + def __init__(self, parent=None): + super(PliantQGraphicsScene, self).__init__(parent) + self.parent = parent + + def mouseReleaseEvent(self, event): + self.parent.previous_position = self.parent.pos() + + image_files = '*.jpg *.png' + new_cover = QtWidgets.QFileDialog.getOpenFileName( + None, 'Select new cover', self.parent.parent.settings['last_open_path'], + f'Images ({image_files})')[0] + + if not new_cover: + self.parent.show() + return + + with open(new_cover, 'rb') as cover_ref: + cover_bytes = cover_ref.read() + resized_cover = resize_image(cover_bytes) + self.parent.cover_for_database = resized_cover + + cover_pixmap = QtGui.QPixmap() + cover_pixmap.load(new_cover) + cover_pixmap = cover_pixmap.scaled( + 140, 205, QtCore.Qt.IgnoreAspectRatio) + + self.parent.load_cover(cover_pixmap, True) + self.parent.show()