Initial Commit

master
knotteye 2021-03-01 12:57:27 -06:00
commit 043a8b8c30
5 changed files with 287 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
app.build
tabletoplibrary
minified
pdf.js

20
Makefile Normal file
View File

@ -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/

231
app.py Normal file
View File

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

23
configure vendored Executable file
View File

@ -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

9
makeviewer.sh Executable file
View File

@ -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 ..