#!/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_())