design.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # @Author: Bachir Soussi Chiadmi <bach>
  4. # @Date: 23-05-2017
  5. # @Email: bachir@figureslibres.io
  6. # @Filename: design.py
  7. # @Last modified by: bach
  8. # @Last modified time: 03-06-2017
  9. # @License: GPL-V3
  10. from __future__ import absolute_import, print_function, division, unicode_literals
  11. import os, re
  12. from PyQt5 import QtCore
  13. from PyQt5.QtCore import QUrl, QSettings, QSizeF, Qt
  14. from PyQt5.QtGui import QKeySequence, QFont
  15. from PyQt5.QtWidgets import (QWidget, QTabWidget,
  16. QVBoxLayout, QHBoxLayout, QSplitter,
  17. QPlainTextEdit, QShortcut,
  18. QPushButton, QCheckBox, QSpinBox, QLabel)
  19. from PyQt5.QtWebKit import QWebSettings
  20. # TODO import own custom build lib of QtWebKit with ctypes
  21. # see aur package qt5-webkit-print for options
  22. # https://stackoverflow.com/questions/5081875/ctypes-beginner
  23. from PyQt5.QtWebKitWidgets import QWebView, QWebInspector
  24. from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
  25. from . import highlighter
  26. # _ __ __ _ ___
  27. # | | / /__ / /_| | / (_)__ _ __
  28. # | | /| / / _ \/ __ \ | / / / _ \ | /| / /
  29. # | |/ |/ / __/ /_/ / |/ / / __/ |/ |/ /
  30. # |__/|__/\___/_.___/|___/_/\___/|__/|__/
  31. class WebkitView(QWebView):
  32. def __init__(self, parent, core):
  33. self.parent = parent
  34. self.core = core
  35. self.port = core.server.port
  36. self.view = QWebView.__init__(self, parent)
  37. self.setZoomFactor(1)
  38. self.loadFinished.connect(self.onLoaded)
  39. self.load(QUrl('http://localhost:'+str(self.port)))
  40. self.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
  41. # self.settings().setAttribute(QWebSettings.PluginsEnabled, True)
  42. self.initPDF()
  43. # self.mainframe = self.page.mainFrame()
  44. # print(self.mainframe)
  45. def onLoaded(self):
  46. print("WebView : onLoaded")
  47. self.parent.webviewtoolbar.onRefresh()
  48. def initPDF(self):
  49. # from https://gist.github.com/gciotta/7766803
  50. self.printer = QPrinter(QPrinter.HighResolution)
  51. self.printer.setFullPage(True)
  52. self.printer.setPageMargins(0,0,0,0,QPrinter.Millimeter)
  53. self.printer.setFontEmbeddingEnabled(True)
  54. self.printer.setColorMode(QPrinter.Color)
  55. # TODO: set the page size and orientation from doc settings
  56. # (need to do doc settings before that)
  57. # self.printer.setPageSize(QPrinter.A4)
  58. # self.printer.setResolution(96)
  59. self.printer.setPaperSize(QSizeF(210, 298), QPrinter.Millimeter)
  60. # TODO: change paper size from docsettings
  61. # self.printer.setOrientation(QPrinter.Portrait)
  62. self.printer.setOutputFormat(QPrinter.PdfFormat)
  63. self.printer.setCreator('Libriis')
  64. self.printer.setDocName(self.core.projectname)
  65. self.printer.setOutputFileName(self.core.projectname+".pdf")
  66. # self.setFixedWidth(1000)
  67. def ongenPDF(self):
  68. # QPrinter::Custom
  69. # dialog = QPrintPreviewDialog(self.printer)
  70. # dialog.setWindowState(Qt.WindowMaximized)
  71. # dialog.paintRequested.connect(self.print_)
  72. # dialog.setWindowFlags(Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowMinMaxButtonsHint | Qt.WindowCloseButtonHint | Qt.WindowContextHelpButtonHint)
  73. # dialog.exec()
  74. # TODO: open a dialogue to ask where to save the pdf
  75. # TODO: reload webview and wait for it before printing
  76. # TODO: addd a progress bar
  77. # self.webview.
  78. self.print_(self.printer)
  79. def refresh(self):
  80. self.initPDF()
  81. self.reload()
  82. def toggleDocClass(self, c="",a=True):
  83. if a :
  84. togg = "add"
  85. else :
  86. togg = "remove"
  87. command = """document.documentElement.classList."""+togg+"""('"""+c+"""')"""
  88. self.evaluateJS(command)
  89. def zoom(self,z):
  90. self.setZoomFactor(z/100)
  91. # command = """
  92. # var zoomLevel = """+str(z)+""" / 100;
  93. # var elt = document.documentElement.querySelector("#pages");
  94. # elt.style.webkitTransform = "scale(" + zoomLevel + ")";
  95. # elt.style.webkitTransformOrigin = "0 0";
  96. # """
  97. # self.evaluateJS(command)
  98. def changePage(self,p=0):
  99. command = """
  100. var pageNumber = """+str(p-1)+""";
  101. var target = document.documentElement.querySelectorAll('.paper')[pageNumber];
  102. var offsetTop = target.offsetTop;
  103. var offsetLeft = target.offsetLeft;
  104. document.documentElement.querySelector('body').scrollTop = offsetTop;
  105. document.documentElement.querySelector('body').scrollLeft = offsetLeft;
  106. """
  107. self.evaluateJS(command)
  108. def evaluateJS(self, command):
  109. self.page().mainFrame().evaluateJavaScript(command)
  110. # ____ __
  111. # / _/___ _________ ___ _____/ /_____ _____
  112. # / // __ \/ ___/ __ \/ _ \/ ___/ __/ __ \/ ___/
  113. # _/ // / / (__ ) /_/ / __/ /__/ /_/ /_/ / /
  114. # /___/_/ /_/____/ .___/\___/\___/\__/\____/_/
  115. # /_/
  116. class WebkitInspector(QWebInspector):
  117. def __init__(self, parent, webkitview):
  118. super(WebkitInspector, self).__init__(parent)
  119. self.webkitview = webkitview
  120. self.setPage(self.webkitview.page())
  121. self.showMaximized()
  122. # TODO: webkitinspector is disappearing when chaging tabs
  123. # ______ ______
  124. # /_ __/___ ____ / / __ )____ ______
  125. # / / / __ \/ __ \/ / __ / __ `/ ___/
  126. # / / / /_/ / /_/ / / /_/ / /_/ / /
  127. # /_/ \____/\____/_/_____/\__,_/_/
  128. class WebViewToolBar(QWidget):
  129. def __init__(self, parent):
  130. super(WebViewToolBar, self).__init__(parent)
  131. self.parent = parent
  132. font = QFont()
  133. # font.setFamily("Droid Sans Mono")
  134. # font.setFixedPitch(True)
  135. font.setPointSize(8)
  136. self.setFont(font)
  137. self.hbox = QHBoxLayout()
  138. self.hbox.setContentsMargins(0,0,0,0)
  139. self.preview = QCheckBox('Prev&iew', self)
  140. self.preview.stateChanged.connect(self.onPreview)
  141. self.hbox.addWidget(self.preview)
  142. self.debug = QCheckBox('Deb&ug', self)
  143. self.debug.stateChanged.connect(self.onDebug)
  144. self.hbox.addWidget(self.debug)
  145. self.grid = QCheckBox('&Grid', self)
  146. self.grid.stateChanged.connect(self.onGrid)
  147. self.hbox.addWidget(self.grid)
  148. self.spread = QCheckBox('&Spread', self)
  149. self.spread.stateChanged.connect(self.onSpread)
  150. self.hbox.addWidget(self.spread)
  151. self.facing = QCheckBox('Fa&cing', self)
  152. self.facing.stateChanged.connect(self.onFacing)
  153. self.hbox.addWidget(self.facing)
  154. #
  155. self.hbox.addStretch()
  156. #
  157. # zoom
  158. self.hbox.addWidget(QLabel("Zoom:"))
  159. self.zoom = QSpinBox(self)
  160. self.zoom.setMinimum(-100)
  161. self.zoom.setMaximum(200)
  162. self.zoom.setSingleStep(10)
  163. self.zoom.setValue(90)
  164. self.zoom.valueChanged.connect(self.onZoomChanged)
  165. self.hbox.addWidget(self.zoom)
  166. # page
  167. self.gotopage = QLabel("Go to Page: /"+str(self.parent.core.docsettings['np']))
  168. self.hbox.addWidget(self.gotopage)
  169. self.page = QSpinBox(self)
  170. self.page.setMinimum(1)
  171. self.page.setMaximum(int(self.parent.core.docsettings['np']))
  172. self.page.valueChanged.connect(self.onChangePage)
  173. self.hbox.addWidget(self.page)
  174. self.addpage = QPushButton("&Add Page", self)
  175. self.addpage.clicked.connect(self.onAddPage)
  176. self.hbox.addWidget(self.addpage)
  177. self.rmpage = QPushButton("Re&move Page", self)
  178. self.rmpage.clicked.connect(self.onRmPage)
  179. self.hbox.addWidget(self.rmpage)
  180. #
  181. self.hbox.addStretch()
  182. #
  183. self.reload = QPushButton("&Reload", self)
  184. # self.reload.setShortcut('Ctrl+Shift+r')
  185. # TODO: how to define same shortcut in different places
  186. # self.reload.setIcon(Icon(ico)))
  187. self.reload.clicked.connect(self.onReload)
  188. self.hbox.addWidget(self.reload)
  189. self.genpdf = QPushButton("&PDF", self)
  190. # self.genpdf.setShortcut('Ctrl+Shift+r')
  191. # TODO: how to define same shortcut in different places
  192. # self.genpdf.setIcon(Icon(ico)))
  193. self.genpdf.clicked.connect(self.onGenPDF)
  194. self.hbox.addWidget(self.genpdf)
  195. self.setLayout(self.hbox)
  196. # def onCheckboxAction(self, box):
  197. # self.parent.webkitview.toggleDocClass(box, self[box].isChecked())
  198. # self.recToolbarState(box, self[box].isChecked())
  199. def onPreview(self):
  200. print('Toolbar : onPreview', self.preview.isChecked())
  201. self.parent.webkitview.toggleDocClass('preview', self.preview.isChecked())
  202. self.recToolbarState('preview', self.preview.isChecked())
  203. def onDebug(self):
  204. self.parent.webkitview.toggleDocClass('debug', self.debug.isChecked())
  205. self.recToolbarState('debug', self.debug.isChecked())
  206. def onGrid(self):
  207. self.parent.webkitview.toggleDocClass('grid', self.grid.isChecked())
  208. self.recToolbarState('grid', self.grid.isChecked())
  209. def onSpread(self):
  210. self.parent.webkitview.toggleDocClass('spread', self.spread.isChecked())
  211. self.recToolbarState('spread', self.spread.isChecked())
  212. def onFacing(self):
  213. self.parent.webkitview.toggleDocClass('facing', self.facing.isChecked())
  214. self.recToolbarState('facing', self.facing.isChecked())
  215. def onZoomChanged(self,i):
  216. # print("onZoomChanged : "+str(i))
  217. self.parent.webkitview.zoom(i)
  218. def onZoomOn(self):
  219. # print("onZoomOn")
  220. self.zoom.setValue(self.zoom.value()+self.zoom.singleStep())
  221. def onZoomOut(self):
  222. # print("onZoomOut")
  223. self.zoom.setValue(self.zoom.value()-self.zoom.singleStep())
  224. def onChangePage(self, i):
  225. # print("onChangePage : "+str(i))
  226. self.parent.webkitview.changePage(i)
  227. def onNextPage(self):
  228. # print('onNextPage')
  229. self.page.setValue(self.page.value()+self.page.singleStep())
  230. def onPrevPage(self):
  231. # print('onPrevPage')
  232. self.page.setValue(self.page.value()-self.page.singleStep())
  233. def onAddPage(self):
  234. # print("onAddPage")
  235. self.parent.core.addPage()
  236. def onRmPage(self):
  237. # print("onAddPage")
  238. self.parent.core.rmPage()
  239. def onReload(self):
  240. # print("onReload")
  241. self.parent.webkitview.reload()
  242. def onGenPDF(self):
  243. print("onGenPDF")
  244. self.parent.webkitview.ongenPDF()
  245. def recToolbarState(self, prop, val):
  246. # print('recToolbarState : '+prop, val)
  247. settings = QSettings('FiguresLibres', 'Libriis')
  248. settings.setValue('design/toolbar/'+prop, val)
  249. # print('recToolbarState after : '+prop, settings.value('design/toolbar/'+prop))
  250. def onRefresh(self):
  251. # apply precedent toolbar state
  252. settings = QSettings('FiguresLibres', 'Libriis')
  253. self.preview.setChecked(bool(settings.value('design/toolbar/preview', False, type=bool)))
  254. self.debug.setChecked(bool(settings.value('design/toolbar/debug', False, type=bool)))
  255. self.grid.setChecked(bool(settings.value('design/toolbar/grid', False, type=bool)))
  256. self.spread.setChecked(bool(settings.value('design/toolbar/spread', False, type=bool)))
  257. self.facing.setChecked(bool(settings.value('design/toolbar/facing', False, type=bool)))
  258. # trigger webview changes
  259. self.parent.webkitview.toggleDocClass('preview', self.preview.isChecked())
  260. self.parent.webkitview.toggleDocClass('debug', self.debug.isChecked())
  261. self.parent.webkitview.toggleDocClass('grid', self.grid.isChecked())
  262. self.parent.webkitview.toggleDocClass('spread', self.spread.isChecked())
  263. self.gotopage.setText("Go to Page: /"+str(self.parent.core.docsettings['np']))
  264. self.page.setMaximum(int(self.parent.core.docsettings['np']))
  265. self.parent.webkitview.changePage(self.page.value())
  266. # ______ ___ __
  267. # / ____/___/ (_) /_____ _____
  268. # / __/ / __ / / __/ __ \/ ___/
  269. # / /___/ /_/ / / /_/ /_/ / /
  270. # /_____/\__,_/_/\__/\____/_/
  271. class CodeEditor(QPlainTextEdit):
  272. def __init__(self, parent, core, tabs, file, mode):
  273. super(CodeEditor, self).__init__()
  274. self.parent = parent
  275. self.core = core
  276. self.tabs = tabs
  277. self.file = file
  278. self.mode = mode
  279. self.setStyle()
  280. self.setText()
  281. self.setTabStopWidth(15)
  282. self.textChanged.connect(self.onTextChanged)
  283. self.save_shortcut = QShortcut(QKeySequence("Ctrl+s"), self)
  284. self.save_shortcut.activated.connect(self.save)
  285. def setStyle(self):
  286. font = QFont()
  287. font.setFamily("Droid Sans Mono")
  288. font.setFixedPitch(True)
  289. font.setPointSize(12)
  290. self.setFont(font)
  291. self.hl= highlighter.Highlighter(self.document(),self.mode)
  292. self.setStyleSheet("QPlainTextEdit { background-color: #282828; }")
  293. def setText(self):
  294. # try:
  295. # self.textChanged.disconnect(self.onTextChanged)
  296. # except Exception as e:
  297. # print(e)
  298. self.filepath = os.path.join(self.core.cwd,self.file)
  299. self.clear()
  300. self.insertPlainText(open(self.filepath, 'r').read())
  301. self.changed = False
  302. def onTextChanged(self):
  303. print('textChanged')
  304. # print(self.toPlainText())
  305. # open(self.filepath, 'w').write(self.toPlainText())
  306. if not self.changed:
  307. self.changed = True
  308. i = self.tabs.currentIndex()
  309. # self.tabs.setTabText(i, re.sub(r'^\**\s', '', self.tabs.tabText(i)))
  310. self.tabs.setTabText(i, "* "+self.tabs.tabText(i))
  311. # TODO: indicate that webview needs to be reloaded
  312. def save(self):
  313. if self.changed:
  314. open(self.filepath, 'w').write(self.toPlainText())
  315. i = self.tabs.currentIndex()
  316. self.tabs.setTabText(i, re.sub(r'^\**\s', '', self.tabs.tabText(i)))
  317. self.parent.reloadView()
  318. self.changed = False
  319. # TODO: how to combine file save and project save
  320. class Editor(QWidget):
  321. def __init__(self, parent):
  322. super(Editor, self).__init__()
  323. self.parent = parent
  324. self.layout = QVBoxLayout(self)
  325. self.layout.setContentsMargins(0,0,0,0)
  326. # Initialize tab screen
  327. self.tabs = QTabWidget()
  328. self.scsstab = CodeEditor(self, self.parent.core, self.tabs, 'assets/css/styles.scss', "sass")
  329. self.jstab = CodeEditor(self, self.parent.core, self.tabs, 'assets/js/script.js', 'js')
  330. # Add tabs
  331. self.tabs.addTab(self.scsstab,"scss")
  332. self.tabs.addTab(self.jstab,"js")
  333. # Add tabs to widget
  334. self.layout.addWidget(self.tabs)
  335. self.setLayout(self.layout)
  336. def refresh(self):
  337. self.scsstab.setText()
  338. self.jstab.setText()
  339. def reloadView(self):
  340. self.parent.webkitview.reload()
  341. # _____ __ __
  342. # / ___// /_____ ______/ /__
  343. # \__ \/ __/ __ `/ ___/ //_/
  344. # ___/ / /_/ /_/ / /__/ ,<
  345. # /____/\__/\__,_/\___/_/|_|
  346. class DesignStack(QWidget):
  347. def __init__(self, core):
  348. super(DesignStack, self).__init__()
  349. self.core = core
  350. # self.grid = QGridLayout()
  351. self.hbox = QHBoxLayout()
  352. self.hbox.setContentsMargins(0,0,0,0)
  353. self.setLayout(self.hbox)
  354. self.webview = QWidget()
  355. self.webview.vbox = QVBoxLayout()
  356. self.webview.setLayout(self.webview.vbox)
  357. self.webview.vbox.setContentsMargins(0,0,0,0)
  358. # toolbar
  359. self.webviewtoolbar = WebViewToolBar(self)
  360. self.webview.vbox.addWidget(self.webviewtoolbar)
  361. # webkitview
  362. self.webkitview = WebkitView(self, core)
  363. # webkitinspector
  364. self.webkitinspector = WebkitInspector(self, self.webkitview)
  365. # V layout
  366. self.vsplitter = QSplitter(QtCore.Qt.Vertical)
  367. self.vsplitter.addWidget(self.webkitview)
  368. self.vsplitter.addWidget(self.webkitinspector)
  369. self.vsplitter.splitterMoved.connect(self.movedSplitter)
  370. self.webview.vbox.addWidget(self.vsplitter)
  371. # H layout
  372. self.hsplitter = QSplitter(QtCore.Qt.Horizontal)
  373. self.hsplitter.addWidget(self.webview)
  374. # editor
  375. self.editor = Editor(self)
  376. self.hsplitter.addWidget(self.editor)
  377. self.hsplitter.splitterMoved.connect(self.movedSplitter)
  378. self.hbox.addWidget(self.hsplitter)
  379. self.restorePrefs()
  380. def toggleInspector(self):
  381. self.webkitinspector.setVisible(not self.webkitinspector.isVisible())
  382. def initShortcuts(self):
  383. # inspector
  384. shortcut = QShortcut(self)
  385. shortcut.setKey("F12")
  386. shortcut.activated.connect(self.toggleInspector)
  387. self.webkitinspector.setVisible(False)
  388. # pages
  389. # self.pagenext_shortcut = QShortcut(QKeySequence(Qt.ControlModifier+Qt.ShiftModifier+Qt.Key_Right), self)
  390. # self.pagenext_shortcut.activated.connect(self.webviewtoolbar.onNextPage)
  391. # self.pageprev_shortcut = QShortcut(QKeySequence(Qt.ControlModifier+Qt.ShiftModifier+Qt.Key_Left), self)
  392. # self.pageprev_shortcut.activated.connect(self.webviewtoolbar.onPrevPage)
  393. # zoom
  394. # self.zoomon_shortcut = QShortcut(QKeySequence(Qt.ControlModifier+Qt.Key_Plus), self)
  395. # self.zoomon_shortcut.activated.connect(self.webviewtoolbar.onZoomOn)
  396. # self.zoomout_shortcut = QShortcut(QKeySequence(Qt.ControlModifier+Qt.Key_Minus), self)
  397. # self.zoomout_shortcut.activated.connect(self.webviewtoolbar.onZoomOut)
  398. def restorePrefs(self):
  399. settings = QSettings('FiguresLibres', 'Libriis')
  400. print(settings.value('design/vsplitter/sizes', self.vsplitter.sizes()))
  401. vals = settings.value('design/vsplitter/sizes', None)
  402. if vals:
  403. sizes = []
  404. for size in vals: sizes.append(int(size))
  405. self.vsplitter.setSizes(sizes)
  406. vals = settings.value('design/hsplitter/sizes', None)
  407. if vals:
  408. sizes = []
  409. for size in vals: sizes.append(int(size))
  410. self.hsplitter.setSizes(sizes)
  411. def movedSplitter(self):
  412. settings = QSettings('FiguresLibres', 'Libriis')
  413. # print(self.vsplitter.sizes())
  414. settings.setValue('design/vsplitter/sizes', self.vsplitter.sizes())
  415. settings.setValue('design/hsplitter/sizes', self.hsplitter.sizes())
  416. def refresh(self):
  417. self.editor.refresh()
  418. self.webkitview.refresh()