43 self.__vm = viewmanager |
43 self.__vm = viewmanager |
44 self.__splitter = splitter |
44 self.__splitter = splitter |
45 |
45 |
46 self.__firstShow = True |
46 self.__firstShow = True |
47 |
47 |
48 self.previewView.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) |
48 self.previewView.page().setLinkDelegationPolicy( |
|
49 QWebPage.DelegateAllLinks) |
49 |
50 |
50 # Don't update too often because the UI might become sluggish |
51 # Don't update too often because the UI might become sluggish |
51 self.__typingTimer = QTimer() |
52 self.__typingTimer = QTimer() |
52 self.__typingTimer.setInterval(500) # 500ms |
53 self.__typingTimer.setInterval(500) # 500ms |
53 self.__typingTimer.timeout.connect(self.__runProcessingThread) |
54 self.__typingTimer.timeout.connect(self.__runProcessingThread) |
75 """ |
76 """ |
76 Public method to show the preview widget. |
77 Public method to show the preview widget. |
77 """ |
78 """ |
78 super(Previewer, self).show() |
79 super(Previewer, self).show() |
79 if self.__firstShow: |
80 if self.__firstShow: |
80 self.__splitter.restoreState(Preferences.getUI("PreviewSplitterState")) |
81 self.__splitter.restoreState( |
81 self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS")) |
82 Preferences.getUI("PreviewSplitterState")) |
82 self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI")) |
83 self.jsCheckBox.setChecked( |
|
84 Preferences.getUI("ShowFilePreviewJS")) |
|
85 self.ssiCheckBox.setChecked( |
|
86 Preferences.getUI("ShowFilePreviewSSI")) |
83 self.__firstShow = False |
87 self.__firstShow = False |
84 self.__typingTimer.start() |
88 self.__typingTimer.start() |
85 |
89 |
86 def hide(self): |
90 def hide(self): |
87 """ |
91 """ |
183 else: |
188 else: |
184 self.hide() |
189 self.hide() |
185 |
190 |
186 def __isPreviewable(self, editor): |
191 def __isPreviewable(self, editor): |
187 """ |
192 """ |
188 Private method to check, if a preview can be shown for the given editor. |
193 Private method to check, if a preview can be shown for the given |
|
194 editor. |
189 |
195 |
190 @param editor reference to an editor (Editor) |
196 @param editor reference to an editor (Editor) |
191 @return flag indicating if a preview can be shown (boolean) |
197 @return flag indicating if a preview can be shown (boolean) |
192 """ |
198 """ |
193 if editor: |
199 if editor: |
194 if editor.getFileName() is not None: |
200 if editor.getFileName() is not None: |
195 extension = os.path.normcase( |
201 extension = os.path.normcase( |
196 os.path.splitext(editor.getFileName())[1][1:]) |
202 os.path.splitext(editor.getFileName())[1][1:]) |
197 return extension in \ |
203 return extension in \ |
198 Preferences.getEditor("PreviewHtmlFileNameExtensions") + \ |
204 Preferences.getEditor("PreviewHtmlFileNameExtensions") + \ |
199 Preferences.getEditor("PreviewMarkdownFileNameExtensions") + \ |
205 Preferences.getEditor( |
|
206 "PreviewMarkdownFileNameExtensions") + \ |
200 Preferences.getEditor("PreviewRestFileNameExtensions") |
207 Preferences.getEditor("PreviewRestFileNameExtensions") |
201 elif editor.getLanguage() == "HTML": |
208 elif editor.getLanguage() == "HTML": |
202 return True |
209 return True |
203 |
210 |
204 return False |
211 return False |
245 fn, language, editor.text(), |
252 fn, language, editor.text(), |
246 self.ssiCheckBox.isChecked(), rootPath) |
253 self.ssiCheckBox.isChecked(), rootPath) |
247 |
254 |
248 def __setHtml(self, filePath, html): |
255 def __setHtml(self, filePath, html): |
249 """ |
256 """ |
250 Private method to set the HTML to the view and restore the scroll bars positions. |
257 Private method to set the HTML to the view and restore the scroll bars |
|
258 positions. |
251 |
259 |
252 @param filePath file path of the previewed editor (string) |
260 @param filePath file path of the previewed editor (string) |
253 @param html processed HTML text ready to be shown (string) |
261 @param html processed HTML text ready to be shown (string) |
254 """ |
262 """ |
255 self.__saveScrollBarPositions() |
263 self.__saveScrollBarPositions() |
299 |
307 |
300 if self.__previewedPath not in self.__scrollBarPositions: |
308 if self.__previewedPath not in self.__scrollBarPositions: |
301 return |
309 return |
302 |
310 |
303 frame = self.previewView.page().mainFrame() |
311 frame = self.previewView.page().mainFrame() |
304 frame.setScrollPosition(self.__scrollBarPositions[self.__previewedPath]) |
312 frame.setScrollPosition( |
|
313 self.__scrollBarPositions[self.__previewedPath]) |
305 |
314 |
306 if self.__hScrollBarAtEnd[self.__previewedPath]: |
315 if self.__hScrollBarAtEnd[self.__previewedPath]: |
307 frame.setScrollBarValue(Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) |
316 frame.setScrollBarValue( |
|
317 Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) |
308 |
318 |
309 if self.__vScrollBarAtEnd[self.__previewedPath]: |
319 if self.__vScrollBarAtEnd[self.__previewedPath]: |
310 frame.setScrollBarValue(Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) |
320 frame.setScrollBarValue( |
|
321 Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) |
311 |
322 |
312 @pyqtSlot(QUrl) |
323 @pyqtSlot(QUrl) |
313 def on_previewView_linkClicked(self, url): |
324 def on_previewView_linkClicked(self, url): |
314 """ |
325 """ |
315 Private slot handling the clicking of a link. |
326 Private slot handling the clicking of a link. |
322 class PreviewProcessingThread(QThread): |
333 class PreviewProcessingThread(QThread): |
323 """ |
334 """ |
324 Class implementing a thread to process some text into HTML usable by the |
335 Class implementing a thread to process some text into HTML usable by the |
325 previewer view. |
336 previewer view. |
326 |
337 |
327 @signal htmlReady(str,str) emitted with the file name and processed HTML to signal |
338 @signal htmlReady(str,str) emitted with the file name and processed HTML |
328 the availability of the processed HTML |
339 to signal the availability of the processed HTML |
329 """ |
340 """ |
330 htmlReady = pyqtSignal(str, str) |
341 htmlReady = pyqtSignal(str, str) |
331 |
342 |
332 def __init__(self, parent=None): |
343 def __init__(self, parent=None): |
333 """ |
344 """ |
344 Convert the given text to HTML. |
355 Convert the given text to HTML. |
345 |
356 |
346 @param filePath file path of the text (string) |
357 @param filePath file path of the text (string) |
347 @param language language of the text (string) |
358 @param language language of the text (string) |
348 @param text text to be processed (string) |
359 @param text text to be processed (string) |
349 @param ssiEnabled flag indicating to do some (limited) SSI processing (boolean) |
360 @param ssiEnabled flag indicating to do some (limited) SSI processing |
|
361 (boolean) |
350 @param rootPath root path to be used for SSI processing (str) |
362 @param rootPath root path to be used for SSI processing (str) |
351 """ |
363 """ |
352 with self.__lock: |
364 with self.__lock: |
353 self.__filePath = filePath |
365 self.__filePath = filePath |
354 self.__language = language |
366 self.__language = language |
371 text = self.__text |
383 text = self.__text |
372 ssiEnabled = self.__ssiEnabled |
384 ssiEnabled = self.__ssiEnabled |
373 rootPath = self.__rootPath |
385 rootPath = self.__rootPath |
374 self.__haveData = False |
386 self.__haveData = False |
375 |
387 |
376 html = self.__getHtml(language, text, ssiEnabled, filePath, rootPath) |
388 html = self.__getHtml(language, text, ssiEnabled, filePath, |
|
389 rootPath) |
377 |
390 |
378 with self.__lock: |
391 with self.__lock: |
379 if not self.__haveData: |
392 if not self.__haveData: |
380 self.htmlReady.emit(filePath, html) |
393 self.htmlReady.emit(filePath, html) |
381 break |
394 break |
382 # else - next iteration |
395 # else - next iteration |
383 |
396 |
384 def __getHtml(self, language, text, ssiEnabled, filePath, rootPath): |
397 def __getHtml(self, language, text, ssiEnabled, filePath, rootPath): |
385 """ |
398 """ |
386 Private method to process the given text depending upon the given language. |
399 Private method to process the given text depending upon the given |
|
400 language. |
387 |
401 |
388 @param language language of the text (string) |
402 @param language language of the text (string) |
389 @param text to be processed (string) |
403 @param text to be processed (string) |
390 @param ssiEnabled flag indicating to do some (limited) SSI processing (boolean) |
404 @param ssiEnabled flag indicating to do some (limited) SSI processing |
|
405 (boolean) |
391 @param filePath file path of the text (string) |
406 @param filePath file path of the text (string) |
392 @param rootPath root path to be used for SSI processing (str) |
407 @param rootPath root path to be used for SSI processing (str) |
|
408 @return processed HTML text (string) |
393 """ |
409 """ |
394 if language == "HTML": |
410 if language == "HTML": |
395 if ssiEnabled: |
411 if ssiEnabled: |
396 return self.__processSSI(text, filePath, rootPath) |
412 return self.__processSSI(text, filePath, rootPath) |
397 else: |
413 else: |
399 elif language == "Markdown": |
415 elif language == "Markdown": |
400 return self.__convertMarkdown(text) |
416 return self.__convertMarkdown(text) |
401 elif language == "ReST": |
417 elif language == "ReST": |
402 return self.__convertReST(text) |
418 return self.__convertReST(text) |
403 else: |
419 else: |
404 return self.trUtf8("<p>No preview available for this type of file.</p>") |
420 return self.trUtf8( |
|
421 "<p>No preview available for this type of file.</p>") |
405 |
422 |
406 def __processSSI(self, txt, filename, root): |
423 def __processSSI(self, txt, filename, root): |
407 """ |
424 """ |
408 Private method to process the given text for SSI statements. |
425 Private method to process the given text for SSI statements. |
409 |
426 |
410 Note: Only a limited subset of SSI statements are supported. |
427 Note: Only a limited subset of SSI statements are supported. |
411 |
428 |
412 @param txt text to be processed (string) |
429 @param txt text to be processed (string) |
413 @param filename name of the file associated with the given text (string) |
430 @param filename name of the file associated with the given text |
|
431 (string) |
414 @param root directory of the document root (string) |
432 @param root directory of the document root (string) |
415 @return processed HTML (string) |
433 @return processed HTML (string) |
416 """ |
434 """ |
417 if not filename: |
435 if not filename: |
418 return txt |
436 return txt |
458 """ |
476 """ |
459 try: |
477 try: |
460 import docutils.core # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
478 import docutils.core # __IGNORE_EXCEPTION__ __IGNORE_WARNING__ |
461 except ImportError: |
479 except ImportError: |
462 return self.trUtf8( |
480 return self.trUtf8( |
463 """<p>ReStructuredText preview requires the <b>python-docutils</b> """ |
481 """<p>ReStructuredText preview requires the""" |
464 """package.<br/>Install it with your package manager or see """ |
482 """ <b>python-docutils</b> package.<br/>Install it with""" |
465 """<a href="http://pypi.python.org/pypi/docutils">this page.</a></p>""") |
483 """ your package manager or see""" |
466 |
484 """ <a href="http://pypi.python.org/pypi/docutils">""" |
467 return docutils.core.publish_string(text, writer_name='html').decode("utf-8") |
485 """this page.</a></p>""") |
|
486 |
|
487 return docutils.core.publish_string(text, writer_name='html')\ |
|
488 .decode("utf-8") |
468 |
489 |
469 def __convertMarkdown(self, text): |
490 def __convertMarkdown(self, text): |
470 """ |
491 """ |
471 Private method to convert Markdown text into HTML. |
492 Private method to convert Markdown text into HTML. |
472 |
493 |
490 |
511 |
491 extensions = ['fenced_code', 'nl2br', 'extra'] |
512 extensions = ['fenced_code', 'nl2br', 'extra'] |
492 |
513 |
493 # version 2.0 supports only extension names, not instances |
514 # version 2.0 supports only extension names, not instances |
494 if markdown.version_info[0] > 2 or \ |
515 if markdown.version_info[0] > 2 or \ |
495 (markdown.version_info[0] == 2 and markdown.version_info[1] > 0): |
516 (markdown.version_info[0] == 2 and |
496 |
517 markdown.version_info[1] > 0): |
497 class _StrikeThroughExtension(markdown.Extension): |
518 class _StrikeThroughExtension(markdown.Extension): |
498 """ |
519 """ |
499 Class is placed here, because it depends on imported markdown, |
520 Class is placed here, because it depends on imported markdown, |
500 and markdown import is lazy. |
521 and markdown import is lazy. |
501 |
522 |
502 (see |
523 (see http://achinghead.com/ |
503 <a href="http://achinghead.com/python-markdown-adding-insert-delete.html"> |
524 python-markdown-adding-insert-delete.html this page for |
504 this page for details</a>) |
525 details) |
505 """ |
526 """ |
506 DEL_RE = r'(~~)(.*?)~~' |
527 DEL_RE = r'(~~)(.*?)~~' |
507 |
528 |
508 def extendMarkdown(self, md, md_globals): |
529 def extendMarkdown(self, md, md_globals): |
509 # Create the del pattern |
530 # Create the del pattern |
510 del_tag = markdown.inlinepatterns.SimpleTagPattern(self.DEL_RE, 'del') |
531 del_tag = markdown.inlinepatterns.SimpleTagPattern( |
|
532 self.DEL_RE, 'del') |
511 # Insert del pattern into markdown parser |
533 # Insert del pattern into markdown parser |
512 md.inlinePatterns.add('del', del_tag, '>not_strong') |
534 md.inlinePatterns.add('del', del_tag, '>not_strong') |
513 |
535 |
514 extensions.append(_StrikeThroughExtension()) |
536 extensions.append(_StrikeThroughExtension()) |
515 |
537 |
516 try: |
538 try: |
517 return markdown.markdown(text, extensions + ['mathjax']) |
539 return markdown.markdown(text, extensions + ['mathjax']) |
518 except (ImportError, ValueError): |
540 except (ImportError, ValueError): |
519 # markdown raises ValueError or ImportError, depends on version |
541 # markdown raises ValueError or ImportError, depends on version |
520 # It is not clear, how to distinguish missing mathjax from other errors. |
542 # It is not clear, how to distinguish missing mathjax from other |
521 # So keep going without mathjax. |
543 # errors. So keep going without mathjax. |
522 return markdown.markdown(text, extensions) |
544 return markdown.markdown(text, extensions) |