A tool for viewing and editing collections of pdfs
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tabletoplibrary/app.py

232 lines
8.8 KiB

3 years ago
#!/bin/env python
import os, sys, urllib.parse, re, magic, shutil
from appdirs import AppDirs
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
from PyQt5.QtCore import QSettings, QCoreApplication, QUrl, QTimer
from PyQt5.QtWidgets import QApplication, QWidget, qApp, QMainWindow, QStyle, QAction, QTabWidget, QInputDialog, QFileDialog, QLineEdit
from PyQt5.QtGui import QIcon
from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineDownloadItem
from PyQt5.QtWebChannel import QWebChannel
DATA_DIR = AppDirs("TabletopLibrary", "knotteye").user_data_dir
LIB_DIR = '/lib'
def _mkdir(_dir):
if os.path.isdir(_dir): pass
elif os.path.isfile(_dir):
raise OSError("%s exists as a regular file." % _dir)
else:
parent, directory = os.path.split(_dir)
if parent and not os.path.isdir(parent): _mkdir(parent)
if directory: os.mkdir(_dir)
class TabBar(QtWidgets.QTabBar):
def tabSizeHint(self, index):
s = QtWidgets.QTabBar.tabSizeHint(self, index)
s.transpose()
return s
def paintEvent(self, event):
painter = QtWidgets.QStylePainter(self)
opt = QtWidgets.QStyleOptionTab()
for i in range(self.count()):
self.initStyleOption(opt, i)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt)
painter.save()
s = opt.rect.size()
s.transpose()
r = QtCore.QRect(QtCore.QPoint(), s)
r.moveCenter(opt.rect.center())
opt.rect = r
c = self.tabRect(i).center()
painter.translate(c)
painter.rotate(90)
painter.translate(-c)
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt);
painter.restore()
class TabWidget(QtWidgets.QTabWidget):
def __init__(self, *args, **kwargs):
QtWidgets.QTabWidget.__init__(self, *args, **kwargs)
self.setTabBar(TabBar(self))
self.setTabPosition(QtWidgets.QTabWidget.West)
class App(QMainWindow):
settings = QSettings()
currentDownloads = []
def __init__(self):
self.vertTabDict = {}
super().__init__()
self.title = 'Tabletop Library'
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.settings.value('left', type=int) or 10, self.settings.value('top', type=int) or 10, self.settings.value('width', type=int) or 640, self.settings.value('height', type=int) or 480)
exitAction = QAction('&Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
exitAction.triggered.connect(self.close)
saveAction = QAction('Save', self, triggered=self.saveCurrent)
saveAction.setShortcut('Ctrl+S')
saveAction.setStatusTip('Save current tab')
saveAllAction = QAction('Save All', self, triggered=self.saveAll)
saveAllAction.setShortcut('Ctrl+Shift+S')
saveAllAction.setStatusTip('Save all tabs')
newCharAction = QAction("New Collection", self, triggered=self.newCharDialog)
newCharAction.setShortcut('Ctrl+Shift+N')
newFileAction = QAction("New File", self, triggered=self.newFileDialog)
newFileAction.setShortcut('Ctrl+N')
menubar = self.menuBar()
filemenu = menubar.addMenu("File")
filemenu.addAction(newFileAction)
filemenu.addAction(newCharAction)
filemenu.insertSeparator(QAction())
filemenu.addAction(saveAction)
filemenu.addAction(saveAllAction)
filemenu.insertSeparator(QAction())
filemenu.addAction(exitAction)
self.saveOnExitPref = QAction('Save On Exit', self, checkable=True)
self.saveOnExitPref.setChecked(self.settings.value('saveOnExit', type=bool))
self.saveOnExitPref.setToolTip('May cause the app to be sluggish when closing')
editmenu = menubar.addMenu("Edit")
editmenu.setToolTipsVisible(True)
editmenu.addAction(self.saveOnExitPref)
self.vtabs = TabWidget()
self.getChars()
self.setCentralWidget(self.vtabs)
self.show()
QtWebEngineWidgets.QWebEngineProfile.defaultProfile().downloadRequested.connect( self.on_downloadRequested )
def closeEvent(self, event):
if self.saveOnExitPref.isChecked():
self.saveAll()
if not self.currentDownloads and self.saveOnExitPref.isChecked():
QTimer.singleShot(200, self.close)
event.ignore()
return
self.settings.setValue("left", self.x())
self.settings.setValue("top", self.y())
self.settings.setValue("width", self.width())
self.settings.setValue("height", self.height())
self.settings.setValue('saveOnExit', self.saveOnExitPref.isChecked())
event.accept()
def getChars(self):
_mkdir(DATA_DIR+"/collections")
for _, dirs, _ in os.walk(DATA_DIR+"/collections"):
for direc in dirs:
self.addChar(direc)
break
def addChar(self, name):
_mkdir(DATA_DIR+"/collections/"+name)
self.vertTabDict[name] = CTab(QTabWidget(), name)
self.vtabs.addTab(self.vertTabDict[name].qobject, name)
for _, _, files in os.walk(DATA_DIR+"/collections/"+name):
for file in files:
mime = magic.from_file(DATA_DIR+"/collections/"+name+"/"+file, mime=True)
if(mime == "application/pdf"):
self.vertTabDict[name].makeChild(file)
return
break
def saveCurrent(self):
self.vtabs.currentWidget().currentWidget().page().runJavaScript('document.getElementById("download").click()')
@QtCore.pyqtSlot("QWebEngineDownloadItem*")
def on_downloadRequested(self, download):
regex = re.compile('^file://[\\/\w:]+viewer\.html\?file=')
#strip out the file://whatever/viewer.html?file= and we're left with the file path
download.setPath(regex.sub('', download.page().url().toString()))
download.accept()
self.currentDownloads.append(download)
download.finished.connect(self.finishDownload)
def finishDownload(self):
for d in self.currentDownloads:
if d.isFinished():
del d
return
def saveAll(self):
for vt in self.vertTabDict:
for t in self.vertTabDict[vt].children:
self.vertTabDict[vt].children[t].page().runJavaScript('document.getElementById("download").click()')
def newCharDialog(self):
text, ok = QInputDialog.getText(self, "New Collection", "Name:", QLineEdit.Normal, "")
if ok and text != '':
self.addChar(text)
def newFileDialog(self):
qfd = QFileDialog(self)
qfd.setFileMode(QFileDialog.ExistingFile)
qfd.setNameFilters(["TabletopLibrary Documents (*.pdf)", "All Files (*)"])
item, ok = QInputDialog.getItem(self, "Add to which collection?","Collection:", self.vertTabDict.keys(), self.vtabs.currentIndex(), False)
if not ok:
return
qfd.filesSelected.connect(self.vertTabDict[item].addFile)
qfd.show()
class CTab(object):
def __init__(self, qobject, name, children={}):
self.qobject = qobject
self.children = children
self.name = name
def makeChild(self, filename):
print('makechild')
self.children[filename] = QWebEngineView()
self.qobject.addTab(self.children[filename], filename)
try:
viewerfile = sys.argv[1]
except:
if os.path.isfile(DATA_DIR+'/minified/web/viewer.html'):
viewerfile = DATA_DIR+'/minified/web/viewer.html'
elif os.path.isfile(LIB_DIR+'/tabletoplibrary/minified/web/viewer.html'):
viewerfile = LIB_DIR+'/tabletoplibrary/minified/web/viewer.html'
else:
print("No viewer file")
exit(1)
self.children[filename].load(QUrl('file://'+viewerfile+'?file='+urllib.parse.quote(DATA_DIR+'/collections/'+self.name+'/'+filename)))
self.children[filename].show()
def addFile(self, pathlist):
# Check for duplicates because of this bug
# https://forum.qt.io/topic/41356/qfiledialog-signal-fileselected-is-triggered-twice-for-one-user-choice-changed-in-recent-qt/
for path in pathlist:
_mkdir(DATA_DIR+"/collections/"+self.name)
if os.path.isfile(DATA_DIR+'/collections/'+self.name+'/'+os.path.basename(path)):
return
shutil.copyfile(path, DATA_DIR+'/collections/'+self.name+'/'+os.path.basename(path))
self.makeChild(os.path.basename(path))
if __name__ == '__main__':
app = QApplication(sys.argv)
app.setOrganizationName("knotteye")
app.setApplicationName("Tabletop Library")
ex = App()
sys.exit(app.exec_())