mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-06-23 15:30:45 -04:00
Work on replacing webkit in the comments editor
This commit is contained in:
parent
371e5f9813
commit
25eb65e156
@ -1,151 +1,103 @@
|
||||
#!/usr/bin/env python2
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
|
||||
# License: GPLv3 Copyright: 2010, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import weakref
|
||||
|
||||
import re, os, json, weakref
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt5.Qt import (QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit,
|
||||
QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, QFormLayout,
|
||||
QSyntaxHighlighter, QColor, QColorDialog, QMenu, QDialog, QLabel,
|
||||
QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox, QPushButton,
|
||||
pyqtSignal, QCheckBox)
|
||||
from PyQt5.QtWebKitWidgets import QWebView, QWebPage
|
||||
try:
|
||||
from PyQt5 import sip
|
||||
except ImportError:
|
||||
import sip
|
||||
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre import xml_replace_entities, prepare_string_for_xml
|
||||
from calibre.gui2 import open_url, error_dialog, choose_files, gprefs, NO_URL_FORMATTING, secure_web_page
|
||||
from calibre.gui2.widgets import LineEditECM
|
||||
from html5_parser import parse
|
||||
from lxml import html
|
||||
from PyQt5.Qt import (
|
||||
QAction, QApplication, QByteArray, QCheckBox, QColor, QColorDialog, QDialog,
|
||||
QDialogButtonBox, QFontInfo, QFormLayout, QHBoxLayout, QIcon, QKeySequence,
|
||||
QLabel, QLineEdit, QMenu, QPlainTextEdit, QPushButton, QSize, QSyntaxHighlighter,
|
||||
Qt, QTabWidget, QTextEdit, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal,
|
||||
pyqtSlot
|
||||
)
|
||||
|
||||
from calibre import prepare_string_for_xml, xml_replace_entities
|
||||
from calibre.ebooks.chardet import xml_to_unicode
|
||||
from calibre.gui2 import NO_URL_FORMATTING, choose_files, error_dialog, gprefs
|
||||
from calibre.gui2.widgets import LineEditECM
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.imghdr import what
|
||||
from polyglot.builtins import unicode_type
|
||||
|
||||
|
||||
class PageAction(QAction): # {{{
|
||||
|
||||
def __init__(self, wac, icon, text, checkable, view):
|
||||
QAction.__init__(self, QIcon(I(icon+'.png')), text, view)
|
||||
self._page_action = getattr(QWebPage, wac)
|
||||
self.setCheckable(checkable)
|
||||
self.triggered.connect(self.trigger_page_action)
|
||||
view.selectionChanged.connect(self.update_state,
|
||||
type=Qt.QueuedConnection)
|
||||
self.page_action.changed.connect(self.update_state,
|
||||
type=Qt.QueuedConnection)
|
||||
self.update_state()
|
||||
|
||||
@property
|
||||
def page_action(self):
|
||||
return self.parent().pageAction(self._page_action)
|
||||
|
||||
def trigger_page_action(self, *args):
|
||||
self.page_action.trigger()
|
||||
|
||||
def update_state(self, *args):
|
||||
if sip.isdeleted(self) or sip.isdeleted(self.page_action):
|
||||
return
|
||||
if self.isCheckable():
|
||||
self.setChecked(self.page_action.isChecked())
|
||||
self.setEnabled(self.page_action.isEnabled())
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class BlockStyleAction(QAction): # {{{
|
||||
|
||||
def __init__(self, text, name, view):
|
||||
QAction.__init__(self, text, view)
|
||||
self._name = name
|
||||
self.triggered.connect(self.apply_style)
|
||||
|
||||
def apply_style(self, *args):
|
||||
self.parent().exec_command('formatBlock', self._name)
|
||||
|
||||
# }}}
|
||||
|
||||
|
||||
class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
class EditorWidget(QTextEdit, LineEditECM): # {{{
|
||||
|
||||
data_changed = pyqtSignal()
|
||||
|
||||
@property
|
||||
def readonly(self):
|
||||
return self.isReadOnly()
|
||||
|
||||
@readonly.setter
|
||||
def readonly(self, val):
|
||||
self.setReadOnly(bool(val))
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWebView.__init__(self, parent)
|
||||
QTextEdit.__init__(self, parent)
|
||||
self.setTabChangesFocus(True)
|
||||
font = self.font()
|
||||
f = QFontInfo(font)
|
||||
delta = tweaks['change_book_details_font_size_by'] + 1
|
||||
if delta:
|
||||
font.setPixelSize(f.pixelSize() + delta)
|
||||
self.setFont(font)
|
||||
self.base_url = None
|
||||
self._parent = weakref.ref(parent)
|
||||
self.readonly = False
|
||||
|
||||
self.comments_pat = re.compile(r'<!--.*?-->', re.DOTALL)
|
||||
|
||||
extra_shortcuts = {
|
||||
'ToggleBold': 'Bold',
|
||||
'ToggleItalic': 'Italic',
|
||||
'ToggleUnderline': 'Underline',
|
||||
'bold': 'Bold',
|
||||
'italic': 'Italic',
|
||||
'underline': 'Underline',
|
||||
}
|
||||
|
||||
for wac, name, icon, text, checkable in [
|
||||
('ToggleBold', 'bold', 'format-text-bold', _('Bold'), True),
|
||||
('ToggleItalic', 'italic', 'format-text-italic', _('Italic'),
|
||||
True),
|
||||
('ToggleUnderline', 'underline', 'format-text-underline',
|
||||
_('Underline'), True),
|
||||
('ToggleStrikethrough', 'strikethrough', 'format-text-strikethrough',
|
||||
_('Strikethrough'), True),
|
||||
('ToggleSuperscript', 'superscript', 'format-text-superscript',
|
||||
_('Superscript'), True),
|
||||
('ToggleSubscript', 'subscript', 'format-text-subscript',
|
||||
_('Subscript'), True),
|
||||
('InsertOrderedList', 'ordered_list', 'format-list-ordered',
|
||||
_('Ordered list'), True),
|
||||
('InsertUnorderedList', 'unordered_list', 'format-list-unordered',
|
||||
_('Unordered list'), True),
|
||||
for rec in (
|
||||
('bold', 'format-text-bold', _('Bold'), True),
|
||||
('italic', 'format-text-italic', _('Italic'), True),
|
||||
('underline', 'format-text-underline', _('Underline'), True),
|
||||
('strikethrough', 'format-text-strikethrough', _('Strikethrough'), True),
|
||||
('superscript', 'format-text-superscript', _('Superscript'), True),
|
||||
('subscript', 'format-text-subscript', _('Subscript'), True),
|
||||
('ordered_list', 'format-list-ordered', _('Ordered list'), True),
|
||||
('unordered_list', 'format-list-unordered', _('Unordered list'), True),
|
||||
|
||||
('AlignLeft', 'align_left', 'format-justify-left',
|
||||
_('Align left'), False),
|
||||
('AlignCenter', 'align_center', 'format-justify-center',
|
||||
_('Align center'), False),
|
||||
('AlignRight', 'align_right', 'format-justify-right',
|
||||
_('Align right'), False),
|
||||
('AlignJustified', 'align_justified', 'format-justify-fill',
|
||||
_('Align justified'), False),
|
||||
('Undo', 'undo', 'edit-undo', _('Undo'), False),
|
||||
('Redo', 'redo', 'edit-redo', _('Redo'), False),
|
||||
('RemoveFormat', 'remove_format', 'edit-clear', _('Remove formatting'), False),
|
||||
('Copy', 'copy', 'edit-copy', _('Copy'), False),
|
||||
('Paste', 'paste', 'edit-paste', _('Paste'), False),
|
||||
('Cut', 'cut', 'edit-cut', _('Cut'), False),
|
||||
('Indent', 'indent', 'format-indent-more',
|
||||
_('Increase indentation'), False),
|
||||
('Outdent', 'outdent', 'format-indent-less',
|
||||
_('Decrease indentation'), False),
|
||||
('SelectAll', 'select_all', 'edit-select-all',
|
||||
_('Select all'), False),
|
||||
]:
|
||||
ac = PageAction(wac, icon, text, checkable, self)
|
||||
('align_left', 'format-justify-left', _('Align left'), ),
|
||||
('align_center', 'format-justify-center', _('Align center'), ),
|
||||
('align_right', 'format-justify-right', _('Align right'), ),
|
||||
('align_justified', 'format-justify-fill', _('Align justified'), ),
|
||||
('undo', 'edit-undo', _('Undo'), ),
|
||||
('redo', 'edit-redo', _('Redo'), ),
|
||||
('remove_format', 'edit-clear', _('Remove formatting'), ),
|
||||
('copy', 'edit-copy', _('Copy'), ),
|
||||
('paste', 'edit-paste', _('Paste'), ),
|
||||
('cut', 'edit-cut', _('Cut'), ),
|
||||
('indent', 'format-indent-more', _('Increase indentation'), ),
|
||||
('outdent', 'format-indent-less', _('Decrease indentation'), ),
|
||||
('select_all', 'edit-select-all', _('Select all'), ),
|
||||
|
||||
('color', 'format-text-color', _('Foreground color')),
|
||||
('background', 'format-fill-color', _('Background color')),
|
||||
('insert_link', 'insert-link', _('Insert link or image'),),
|
||||
('insert_hr', 'format-text-hr.png', _('Insert separator'),),
|
||||
('clear', 'trash.png', _('Clear')),
|
||||
):
|
||||
name, icon, text = rec[:3]
|
||||
checkable = len(rec) == 4
|
||||
ac = QAction(QIcon(I(icon + '.png')), text, self)
|
||||
if checkable:
|
||||
ac.setCheckable(checkable)
|
||||
setattr(self, 'action_'+name, ac)
|
||||
ss = extra_shortcuts.get(wac, None)
|
||||
if ss:
|
||||
ss = extra_shortcuts.get(name)
|
||||
if ss is not None:
|
||||
ac.setShortcut(QKeySequence(getattr(QKeySequence, ss)))
|
||||
if wac == 'RemoveFormat':
|
||||
ac.triggered.connect(self.remove_format_cleanup,
|
||||
type=Qt.QueuedConnection)
|
||||
|
||||
self.action_color = QAction(QIcon(I('format-text-color.png')), _('Foreground color'),
|
||||
self)
|
||||
self.action_color.triggered.connect(self.foreground_color)
|
||||
|
||||
self.action_background = QAction(QIcon(I('format-fill-color.png')),
|
||||
_('Background color'), self)
|
||||
self.action_background.triggered.connect(self.background_color)
|
||||
ac.triggered.connect(getattr(self, 'do_' + name))
|
||||
|
||||
self.action_block_style = QAction(QIcon(I('format-text-heading.png')),
|
||||
_('Style text block'), self)
|
||||
@ -153,81 +105,124 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
_('Style the selected text block'))
|
||||
self.block_style_menu = QMenu(self)
|
||||
self.action_block_style.setMenu(self.block_style_menu)
|
||||
self.block_style_actions = []
|
||||
for text, name in [
|
||||
h = _('Heading {0}')
|
||||
for text, name in (
|
||||
(_('Normal'), 'p'),
|
||||
(_('Heading') +' 1', 'h1'),
|
||||
(_('Heading') +' 2', 'h2'),
|
||||
(_('Heading') +' 3', 'h3'),
|
||||
(_('Heading') +' 4', 'h4'),
|
||||
(_('Heading') +' 5', 'h5'),
|
||||
(_('Heading') +' 6', 'h6'),
|
||||
(h.format(1), 'h1'),
|
||||
(h.format(2), 'h2'),
|
||||
(h.format(3), 'h3'),
|
||||
(h.format(4), 'h4'),
|
||||
(h.format(5), 'h5'),
|
||||
(h.format(6), 'h6'),
|
||||
(_('Pre-formatted'), 'pre'),
|
||||
(_('Blockquote'), 'blockquote'),
|
||||
(_('Address'), 'address'),
|
||||
]:
|
||||
ac = BlockStyleAction(text, name, self)
|
||||
):
|
||||
ac = QAction(text, self)
|
||||
self.block_style_menu.addAction(ac)
|
||||
self.block_style_actions.append(ac)
|
||||
|
||||
self.action_insert_link = QAction(QIcon(I('insert-link.png')),
|
||||
_('Insert link or image'), self)
|
||||
self.action_insert_hr = QAction(QIcon(I('format-text-hr.png')),
|
||||
_('Insert separator'), self)
|
||||
self.action_insert_link.triggered.connect(self.insert_link)
|
||||
self.action_insert_hr.triggered.connect(self.insert_hr)
|
||||
self.pageAction(QWebPage.ToggleBold).changed.connect(self.update_link_action)
|
||||
self.action_insert_link.setEnabled(False)
|
||||
self.action_insert_hr.setEnabled(False)
|
||||
self.action_clear = QAction(QIcon(I('trash.png')), _('Clear'), self)
|
||||
self.action_clear.triggered.connect(self.clear_text)
|
||||
|
||||
self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
|
||||
self.page().linkClicked.connect(self.link_clicked)
|
||||
secure_web_page(self.page().settings())
|
||||
connect_lambda(ac.triggered, self, lambda self: self.do_format_block(name))
|
||||
|
||||
self.setHtml('')
|
||||
self.set_readonly(False)
|
||||
self.page().contentsChanged.connect(self.data_changed)
|
||||
|
||||
def update_link_action(self):
|
||||
wac = self.pageAction(QWebPage.ToggleBold).isEnabled()
|
||||
self.action_insert_link.setEnabled(wac)
|
||||
self.action_insert_hr.setEnabled(wac)
|
||||
self.textChanged.connect(self.data_changed)
|
||||
|
||||
def set_readonly(self, what):
|
||||
self.readonly = what
|
||||
self.page().setContentEditable(not self.readonly)
|
||||
|
||||
def clear_text(self, *args):
|
||||
def do_clear(self, *args):
|
||||
raise NotImplementedError('TODO')
|
||||
us = self.page().undoStack()
|
||||
us.beginMacro('clear all text')
|
||||
self.action_select_all.trigger()
|
||||
self.action_remove_format.trigger()
|
||||
self.exec_command('delete')
|
||||
us.endMacro()
|
||||
self.set_font_style()
|
||||
self.setFocus(Qt.OtherFocusReason)
|
||||
clear_text = do_clear
|
||||
|
||||
def link_clicked(self, url):
|
||||
open_url(url)
|
||||
def do_bold(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def foreground_color(self):
|
||||
def do_italic(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_underline(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_strikethrough(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_superscript(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_subscript(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_ordered_list(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_unordered_list(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_align_left(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_align_center(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_align_right(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_align_justified(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_undo(self):
|
||||
self.undo()
|
||||
|
||||
def do_redo(self):
|
||||
self.redo()
|
||||
|
||||
def do_remove_format(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_copy(self):
|
||||
self.copy()
|
||||
|
||||
def do_paste(self):
|
||||
self.paste()
|
||||
|
||||
def do_cut(self):
|
||||
self.cut()
|
||||
|
||||
def do_indent(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_outdent(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_select_all(self):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_format_block(self, name):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def do_color(self):
|
||||
col = QColorDialog.getColor(Qt.black, self,
|
||||
_('Choose foreground color'), QColorDialog.ShowAlphaChannel)
|
||||
if col.isValid():
|
||||
raise NotImplementedError('TODO')
|
||||
self.exec_command('foreColor', unicode_type(col.name()))
|
||||
|
||||
def background_color(self):
|
||||
def do_background(self):
|
||||
col = QColorDialog.getColor(Qt.white, self,
|
||||
_('Choose background color'), QColorDialog.ShowAlphaChannel)
|
||||
if col.isValid():
|
||||
raise NotImplementedError('TODO')
|
||||
self.exec_command('hiliteColor', unicode_type(col.name()))
|
||||
|
||||
def insert_hr(self, *args):
|
||||
self.exec_command('insertHTML', '<hr>')
|
||||
def do_insert_hr(self, *args):
|
||||
raise NotImplementedError('TODO')
|
||||
|
||||
def insert_link(self, *args):
|
||||
def do_insert_link(self, *args):
|
||||
link, name, is_image = self.ask_link()
|
||||
if not link:
|
||||
return
|
||||
@ -235,6 +230,7 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
if url.isValid():
|
||||
url = unicode_type(url.toString(NO_URL_FORMATTING))
|
||||
self.setFocus(Qt.OtherFocusReason)
|
||||
raise NotImplementedError('TODO')
|
||||
if is_image:
|
||||
self.exec_command('insertHTML',
|
||||
'<img src="%s" alt="%s"></img>'%(prepare_string_for_xml(url, True),
|
||||
@ -325,18 +321,6 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
def sizeHint(self):
|
||||
return QSize(150, 150)
|
||||
|
||||
def exec_command(self, cmd, arg=None):
|
||||
frame = self.page().mainFrame()
|
||||
if arg is not None:
|
||||
js = 'document.execCommand("%s", false, %s);' % (cmd,
|
||||
json.dumps(unicode_type(arg)))
|
||||
else:
|
||||
js = 'document.execCommand("%s", false, null);' % cmd
|
||||
frame.evaluateJavaScript(js)
|
||||
|
||||
def remove_format_cleanup(self):
|
||||
self.html = self.html
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
ans = ''
|
||||
@ -371,7 +355,7 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
if not ans.startswith('<'):
|
||||
ans = '<p>%s</p>'%ans
|
||||
ans = xml_replace_entities(ans)
|
||||
except:
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@ -379,15 +363,25 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
|
||||
@html.setter
|
||||
def html(self, val):
|
||||
if self.base_url is None:
|
||||
self.setHtml(val)
|
||||
else:
|
||||
self.setHtml(val, self.base_url)
|
||||
self.set_font_style()
|
||||
|
||||
def set_base_url(self, qurl):
|
||||
self.base_url = qurl
|
||||
self.setHtml('', self.base_url)
|
||||
|
||||
@pyqtSlot(int, 'QUrl', result='QVariant')
|
||||
def loadResource(self, rtype, qurl):
|
||||
if self.base_url:
|
||||
if qurl.isRelative():
|
||||
qurl = self.base_url.resolved(qurl)
|
||||
if qurl.isLocalFile():
|
||||
path = qurl.toLocalFile()
|
||||
try:
|
||||
with lopen(path, 'rb') as f:
|
||||
data = f.read()
|
||||
except EnvironmentError:
|
||||
pass
|
||||
else:
|
||||
return QByteArray(data)
|
||||
|
||||
def set_html(self, val, allow_undo=True):
|
||||
if not allow_undo or self.readonly:
|
||||
@ -396,44 +390,22 @@ class EditorWidget(QWebView, LineEditECM): # {{{
|
||||
mf = self.page().mainFrame()
|
||||
mf.evaluateJavaScript('document.execCommand("selectAll", false, null)')
|
||||
mf.evaluateJavaScript('document.execCommand("insertHTML", false, %s)' % json.dumps(unicode_type(val)))
|
||||
self.set_font_style()
|
||||
|
||||
def set_font_style(self):
|
||||
fi = QFontInfo(QApplication.font(self))
|
||||
f = fi.pixelSize() + 1 + int(tweaks['change_book_details_font_size_by'])
|
||||
fam = unicode_type(fi.family()).strip().replace('"', '')
|
||||
if not fam:
|
||||
fam = 'sans-serif'
|
||||
style = 'font-size: %fpx; font-family:"%s",sans-serif;' % (f, fam)
|
||||
|
||||
# toList() is needed because PyQt on Debian is old/broken
|
||||
for body in self.page().mainFrame().documentElement().findAll('body').toList():
|
||||
body.setAttribute('style', style)
|
||||
self.page().setContentEditable(not self.readonly)
|
||||
|
||||
def event(self, ev):
|
||||
if ev.type() in (ev.KeyPress, ev.KeyRelease, ev.ShortcutOverride) and hasattr(ev, 'key') and ev.key() in (
|
||||
Qt.Key_Tab, Qt.Key_Escape, Qt.Key_Backtab):
|
||||
if (ev.key() == Qt.Key_Tab and ev.modifiers() & Qt.ControlModifier and ev.type() == ev.KeyPress):
|
||||
self.exec_command('insertHTML', '<span style="white-space:pre">\t</span>')
|
||||
ev.accept()
|
||||
return True
|
||||
ev.ignore()
|
||||
return False
|
||||
return QWebView.event(self, ev)
|
||||
|
||||
def text(self):
|
||||
return self.page().selectedText()
|
||||
return self.textCursor().selectedText()
|
||||
|
||||
def setText(self, text):
|
||||
self.exec_command('insertText', text)
|
||||
c = self.textCursor()
|
||||
c.insertText(text)
|
||||
self.setTextCursor(c)
|
||||
|
||||
def contextMenuEvent(self, ev):
|
||||
menu = self.page().createStandardContextMenu()
|
||||
paste = self.pageAction(QWebPage.Paste)
|
||||
menu = self.createStandardContextMenu()
|
||||
# paste = self.pageAction(QWebPage.Paste)
|
||||
for action in menu.actions():
|
||||
if action == paste:
|
||||
menu.insertAction(action, self.pageAction(QWebPage.PasteAndMatchStyle))
|
||||
pass
|
||||
# if action == paste:
|
||||
# menu.insertAction(action, self.pageAction(QWebPage.PasteAndMatchStyle))
|
||||
st = self.text()
|
||||
if st and st.strip():
|
||||
self.create_change_case_menu(menu)
|
||||
@ -757,7 +729,7 @@ class Editor(QWidget): # {{{
|
||||
# }}}
|
||||
|
||||
self.code_edit.textChanged.connect(self.code_dirtied)
|
||||
self.editor.page().contentsChanged.connect(self.wyswyg_dirtied)
|
||||
self.editor.data_changed.connect(self.wyswyg_dirtied)
|
||||
|
||||
def set_minimum_height_for_editor(self, val):
|
||||
self.editor.setMinimumHeight(val)
|
||||
|
Loading…
x
Reference in New Issue
Block a user