|
|
|
@ -0,0 +1,231 @@ |
|
|
|
|
#!/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_()) |