commit
043a8b8c30
@ -0,0 +1,4 @@ |
|||||||
|
app.build |
||||||
|
tabletoplibrary |
||||||
|
minified |
||||||
|
pdf.js |
@ -0,0 +1,20 @@ |
|||||||
|
ifeq ($(PREFIX),) |
||||||
|
PREFIX := /usr/local
|
||||||
|
endif |
||||||
|
ifeq ($(LIBDIR),) |
||||||
|
LIBDIR := /lib
|
||||||
|
endif |
||||||
|
|
||||||
|
|
||||||
|
all: build |
||||||
|
build: viewer |
||||||
|
nuitka3 --follow-imports app.py -o tabletoplibrary
|
||||||
|
clean: |
||||||
|
rm -f tabletoplibrary
|
||||||
|
rm -rf pdf.js minified app.build
|
||||||
|
viewer: |
||||||
|
@sh ./makeviewer.sh
|
||||||
|
install: |
||||||
|
install tabletoplibrary $(PREFIX)/bin/
|
||||||
|
mkdir -p $(LIBDIR)/tabletoplibrary
|
||||||
|
cp -r minified $(LIBDIR)/tabletoplibrary/
|
@ -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_()) |
@ -0,0 +1,23 @@ |
|||||||
|
#!/bin/sh |
||||||
|
for arg in "$@" |
||||||
|
do |
||||||
|
case $arg in |
||||||
|
# Install bin to PREFIX/bin |
||||||
|
# specify with --prefix "/whatever" |
||||||
|
--prefix) |
||||||
|
export PREFIX="$2" |
||||||
|
sed "s#PREFIX := /usr/local#PREFIX := $2#g" Makefile | tee Makefile |
||||||
|
shift # Remove argument name from processing |
||||||
|
shift # Remove argument value from processing |
||||||
|
;; |
||||||
|
# Install viewer files to LIBDIR/tabletoplibrary |
||||||
|
# specify with --libdir "/libwhatver" |
||||||
|
--libdir) |
||||||
|
export LIBDIR="$2" |
||||||
|
sed "s#LIBDIR := /lib#LIBDIR := $2#g" Makefile | tee Makefile |
||||||
|
sed "s#LIB_DIR = \'/lib\'#LIB_DIR = \'$2\'#g" app.py | tee app.py |
||||||
|
shift |
||||||
|
shift |
||||||
|
;; |
||||||
|
esac |
||||||
|
done |
@ -0,0 +1,9 @@ |
|||||||
|
#!/bin/sh |
||||||
|
if [[ -s ./minified/web/viewer.html ]]; then |
||||||
|
exit |
||||||
|
fi |
||||||
|
git clone https://github.com/mozilla/pdf.js/ |
||||||
|
cd pdf.js |
||||||
|
npm i |
||||||
|
./node_modules/.bin/gulp minified |
||||||
|
mv build/minified .. |
Loading…
Reference in new issue