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()