design.py 17 KB

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