コード例 #1
0
ファイル: custom_recipes.py プロジェクト: j-howell/calibre
class AdvancedRecipe(QWidget):  # {{{

    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)

        self.la = la = QLabel(_(
            'For help with writing advanced news recipes, see the <a href="%s">User Manual</a>'
        ) % localize_user_manual_link('https://manual.calibre-ebook.com/news.html'))
        l.addWidget(la)

        self.editor = TextEdit(self)
        l.addWidget(self.editor)

    def validate(self):
        src = self.recipe_source
        try:
            compile_recipe(src)
        except Exception as err:
            error_dialog(self, _('Invalid recipe'), _(
                'Failed to compile the recipe, with syntax error: %s' % err), show=True)
            return False
        return True

    @property
    def recipe_source(self):
        return self.editor.toPlainText()

    @recipe_source.setter
    def recipe_source(self, src):
        self.editor.load_text(src, syntax='python', doc_name='<recipe>')

    def sizeHint(self):
        return QSize(800, 500)
コード例 #2
0
ファイル: widget.py プロジェクト: BorviX/calibre
class Editor(QMainWindow):

    modification_state_changed = pyqtSignal(object)

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.setCentralWidget(self.editor)
        self.editor.modificationChanged.connect(self.modification_state_changed.emit)
        self.create_toolbars()

    def load_text(self, raw):
        self.editor.load_text(raw, syntax=self.syntax)

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified
        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('Edit actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
コード例 #3
0
ファイル: function_replace.py プロジェクト: davidfor/calibre
    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.h = h = QHBoxLayout()
        l.addLayout(h)

        self.la1 = la = QLabel(_('F&unction name:'))
        h.addWidget(la)
        self.fb = fb = FunctionBox(self)
        la.setBuddy(fb)
        h.addWidget(fb, stretch=10)

        self.la3 = la = QLabel(_('&Code:'))
        self.source_code = TextEdit(self)
        self.source_code.load_text('', 'python')
        la.setBuddy(self.source_code)
        l.addWidget(la), l.addWidget(self.source_code)

        if self._func_name:
            self.fb.setText(self._func_name)
            func = functions().get(self._func_name)
            if func is not None:
                self.source_code.setPlainText(func.source or ('\n' + EMPTY_FUNC))
        else:
            self.source_code.setPlainText('\n' + EMPTY_FUNC)

        self.la2 = la = QLabel(_(
            'For help with creating functions, see the <a href="%s">User Manual</a>') %
            localize_user_manual_link('https://manual.calibre-ebook.com/function_mode.html'))
        la.setOpenExternalLinks(True)
        l.addWidget(la)

        l.addWidget(self.bb)
コード例 #4
0
ファイル: widget.py プロジェクト: BorviX/calibre
 def __init__(self, syntax, parent=None):
     QMainWindow.__init__(self, parent)
     if parent is None:
         self.setWindowFlags(Qt.Widget)
     self.syntax = syntax
     self.editor = TextEdit(self)
     self.setCentralWidget(self.editor)
     self.editor.modificationChanged.connect(self.modification_state_changed.emit)
     self.create_toolbars()
コード例 #5
0
ファイル: custom_recipes.py プロジェクト: Farb/calibre
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)

        self.la = la = QLabel(_(
            'For help with writing advanced news recipes, see the <a href="%s">User Manual</a>'
        ) % localize_user_manual_link('https://manual.calibre-ebook.com/news.html'))
        l.addWidget(la)

        self.editor = TextEdit(self)
        l.addWidget(self.editor)
コード例 #6
0
class AdvancedRecipe(QWidget):  # {{{
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        self.l = l = QVBoxLayout(self)

        self.la = la = QLabel(
            _('For help with writing advanced news recipes, see the <a href="%s">User Manual</a>'
              ) % localize_user_manual_link(
                  'https://manual.calibre-ebook.com/news.html'))
        l.addWidget(la)

        self.editor = TextEdit(self)
        l.addWidget(self.editor)

    def validate(self):
        src = self.recipe_source
        try:
            compile_recipe(src)
        except Exception as err:
            error_dialog(
                self,
                _('Invalid recipe'),
                _('Failed to compile the recipe, with syntax error: %s' % err),
                show=True)
            return False
        return True

    @property
    def recipe_source(self):
        return self.editor.toPlainText()

    @recipe_source.setter
    def recipe_source(self, src):
        self.editor.load_text(src, syntax='python', doc_name='<recipe>')

    def sizeHint(self):
        return QSize(800, 500)
コード例 #7
0
 def setup_ui(self):
     from calibre.gui2.tweak_book.editor.text import TextEdit
     self.l = l = QVBoxLayout(self)
     self.bb.setStandardButtons(QDialogButtonBox.StandardButton.Close)
     self.la = la = QLabel(self.LABEL)
     l.addWidget(la)
     self.css = t = TextEdit(self)
     t.load_text(
         '/* %s */\n' %
         _('Enter CSS rules below and click the "Test" button'), 'css')
     la.setBuddy(t)
     c = t.textCursor()
     c.movePosition(QTextCursor.MoveOperation.End)
     t.setTextCursor(c)
     self.h = h = QHBoxLayout()
     l.addLayout(h)
     h.addWidget(t)
     self.test_button = b = QPushButton(_('&Test'), self)
     b.clicked.connect(self.do_test)
     h.addWidget(b)
     self.result = la = TextEdit(self)
     la.setReadOnly(True)
     l.addWidget(la)
     l.addWidget(self.bb)
コード例 #8
0
ファイル: widget.py プロジェクト: randy1/calibre
 def __init__(self, syntax, parent=None):
     QMainWindow.__init__(self, parent)
     if parent is None:
         self.setWindowFlags(Qt.Widget)
     self.syntax = syntax
     self.editor = TextEdit(self)
     self.setCentralWidget(self.editor)
     self.editor.modificationChanged.connect(self.modification_state_changed.emit)
     self.create_toolbars()
     self.undo_available = False
     self.redo_available = False
     self.copy_available = self.cut_available = False
     self.editor.undoAvailable.connect(self._undo_available)
     self.editor.redoAvailable.connect(self._redo_available)
     self.editor.textChanged.connect(self._data_changed)
     self.editor.copyAvailable.connect(self._copy_available)
コード例 #9
0
    def setup_ui(self):
        from calibre.gui2.tweak_book.templates import DEFAULT_TEMPLATES
        from calibre.gui2.tweak_book.editor.text import TextEdit
        # Cannot use QFormLayout as it does not play nice with TextEdit on windows
        self.l = l = QVBoxLayout(self)

        self.syntaxes = s = QComboBox(self)
        s.addItems(sorted(DEFAULT_TEMPLATES))
        s.setCurrentIndex(s.findText('html'))
        h = QHBoxLayout()
        l.addLayout(h)
        la = QLabel(_('Choose the &type of template to edit:'))
        la.setBuddy(s)
        h.addWidget(la), h.addWidget(s), h.addStretch(10)
        s.currentIndexChanged.connect(self.show_template)

        self.helpl = la = QLabel(
            _('The variables {0} and {1} will be replaced with the title and author of the book. {2}'
              ' is where the cursor will be positioned. If you want to include braces in your template,'
              ' for example for CSS rules, you have to escape them, like this: {3}'
              ).format(*[
                  '<code>%s</code>' % x for x in
                  ['{TITLE}', '{AUTHOR}', '%CURSOR%', 'body {{ color: red }}']
              ]))
        la.setWordWrap(True)
        l.addWidget(la)

        self.save_timer = t = QTimer(self)
        t.setSingleShot(True), t.setInterval(100)
        t.timeout.connect(self._save_syntax)

        self.editor = e = TextEdit(self)
        l.addWidget(e)
        e.textChanged.connect(self.save_syntax)

        self.show_template()

        self.bb.clear()
        self.bb.addButton(QDialogButtonBox.StandardButton.Close)
        self.rd = b = self.bb.addButton(
            QDialogButtonBox.StandardButton.RestoreDefaults)
        b.clicked.connect(self.restore_defaults)
        l.addWidget(self.bb)
コード例 #10
0
ファイル: widget.py プロジェクト: BatteringRam/calibre
 def __init__(self, syntax, parent=None):
     QMainWindow.__init__(self, parent)
     if parent is None:
         self.setWindowFlags(Qt.Widget)
     self.is_synced_to_container = False
     self.syntax = syntax
     self.editor = TextEdit(self)
     self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
     self.editor.customContextMenuRequested.connect(self.show_context_menu)
     self.setCentralWidget(self.editor)
     self.create_toolbars()
     self.undo_available = False
     self.redo_available = False
     self.copy_available = self.cut_available = False
     self.editor.modificationChanged.connect(self._modification_state_changed)
     self.editor.undoAvailable.connect(self._undo_available)
     self.editor.redoAvailable.connect(self._redo_available)
     self.editor.textChanged.connect(self._data_changed)
     self.editor.copyAvailable.connect(self._copy_available)
     self.editor.cursorPositionChanged.connect(self._cursor_position_changed)
コード例 #11
0
ファイル: widget.py プロジェクト: randy1/calibre
class Editor(QMainWindow):

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.setCentralWidget(self.editor)
        self.editor.modificationChanged.connect(self.modification_state_changed.emit)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)

    @dynamic_property
    def data(self):
        def fget(self):
            ans = unicode(self.editor.toPlainText())
            if self.syntax == 'html':
                ans = xml_replace_entities(ans)
            return ans.encode('utf-8')
        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)
        return property(fget=fget, fset=fset)

    def get_raw_data(self):
        return unicode(self.editor.toPlainText())

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified
        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('save', 'undo', 'redo'):
            try:
                b.addAction(actions['editor-%s' % x])
            except KeyError:
                pass
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            try:
                b.addAction(actions['editor-%s' % x])
            except KeyError:
                pass

    def break_cycles(self):
        self.modification_state_changed.disconnect()
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.setPlainText('')

    def _data_changed(self):
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(self, _('No text'), _(
                'There is no suitable text in the clipboard to paste.'), show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()
コード例 #12
0
ファイル: widget.py プロジェクト: davidfor/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()
    word_ignored = pyqtSignal(object, object)
    link_clicked = pyqtSignal(object)
    smart_highlighting_updated = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.editor.customContextMenuRequested.connect(self.show_context_menu)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(self._cursor_position_changed)
        self.editor.link_clicked.connect(self.link_clicked)
        self.editor.smart_highlighting_updated.connect(self.smart_highlighting_updated)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()

        def fset(self, val):
            self.editor.go_to_line(val)
        return property(fget=fget, fset=fset)

    @dynamic_property
    def current_editing_state(self):
        def fget(self):
            c = self.editor.textCursor()
            return {'cursor':(c.anchor(), c.position())}

        def fset(self, val):
            anchor, position = val.get('cursor', (None, None))
            if anchor is not None and position is not None:
                c = self.editor.textCursor()
                c.setPosition(anchor), c.setPosition(position, c.KeepAnchor)
                self.editor.setTextCursor(c)
        return property(fget=fget, fset=fset)

    def current_tag(self, for_position_sync=True):
        return self.editor.current_tag(for_position_sync=for_position_sync)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            ans, changed = replace_encoding_declarations(ans, enc='utf-8', limit=4*1024)
            if changed:
                self.data = ans
            return ans.encode('utf-8')

        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax, doc_name=editor_name(self))
        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template, syntax=self.syntax, process_template=True, doc_name=editor_name(self))

    def change_document_name(self, newname):
        self.editor.change_document_name(newname)
        self.editor.completion_doc_name = newname

    def get_raw_data(self):
        # The EPUB spec requires NFC normalization, see section 1.3.6 of
        # http://www.idpf.org/epub/20/spec/OPS_2.0.1_draft.htm
        return unicodedata.normalize('NFC', unicode(self.editor.toPlainText()).rstrip('\0'))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None, dictionaries_changed=False):
        self.editor.apply_settings(prefs=None, dictionaries_changed=dictionaries_changed)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def action_triggered(self, action):
        action, args = action[0], action[1:]
        func = getattr(self.editor, action)
        func(*args)

    def insert_image(self, href, fullpage=False, preserve_aspect_ratio=False):
        self.editor.insert_image(href, fullpage=fullpage, preserve_aspect_ratio=preserve_aspect_ratio)

    def insert_hyperlink(self, href, text):
        self.editor.insert_hyperlink(href, text)

    def _build_insert_tag_button_menu(self):
        m = self.insert_tag_menu
        m.clear()
        names = tprefs['insert_tag_mru']
        for name in names:
            m.addAction(name, partial(self.insert_tag, name))
        if names:
            m.addSeparator()
            m = m.addMenu(_('Remove from this menu'))
            for name in names:
                m.addAction(name, partial(self.remove_insert_tag, name))

    def insert_tag(self, name):
        self.editor.insert_tag(name)
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        mru.insert(0, name)
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def remove_insert_tag(self, name):
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def set_request_completion(self, callback=None, doc_name=None):
        self.editor.request_completion = callback
        self.editor.completion_doc_name = doc_name

    def handle_completion_result(self, result):
        return self.editor.handle_completion_result(result)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    def get_smart_selection(self, update=True):
        return self.editor.smarts.get_smart_selection(self.editor, update=update)

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def find_text(self, *args, **kwargs):
        return self.editor.find_text(*args, **kwargs)

    def find_spell_word(self, *args, **kwargs):
        return self.editor.find_spell_word(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)
    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified

        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('Edit actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        b.setObjectName('tools_bar')
        self.bars = [self.action_bar, self.tools_bar]
        if self.syntax == 'html':
            self.format_bar = b = self.addToolBar(_('Format text'))
            b.setObjectName('html_format_bar')
            self.bars.append(self.format_bar)
        self.insert_tag_menu = QMenu(self)
        self.populate_toolbars()
        for x in self.bars:
            x.setFloatable(False)
            x.topLevelChanged.connect(self.toolbar_floated)
            x.setIconSize(QSize(tprefs['toolbar_icon_size'], tprefs['toolbar_icon_size']))

    def toolbar_floated(self, floating):
        if not floating:
            self.save_state()
            for ed in editors.itervalues():
                if ed is not self:
                    ed.restore_state()

    def save_state(self):
        for bar in self.bars:
            if bar.isFloating():
                return
        tprefs['%s-editor-state' % self.syntax] = bytearray(self.saveState())

    def restore_state(self):
        state = tprefs.get('%s-editor-state' % self.syntax, None)
        if state is not None:
            self.restoreState(state)

    def populate_toolbars(self):
        self.action_bar.clear(), self.tools_bar.clear()

        def add_action(name, bar):
            if name is None:
                bar.addSeparator()
                return
            try:
                ac = actions[name]
            except KeyError:
                if DEBUG:
                    prints('Unknown editor tool: %r' % name)
                return
            bar.addAction(ac)
            if name == 'insert-tag':
                w = bar.widgetForAction(ac)
                w.setPopupMode(QToolButton.MenuButtonPopup)
                w.setMenu(self.insert_tag_menu)
                w.setContextMenuPolicy(Qt.CustomContextMenu)
                w.customContextMenuRequested.connect(w.showMenu)
                self._build_insert_tag_button_menu()
            elif name == 'change-paragraph':
                m = ac.m = QMenu()
                ac.setMenu(m)
                ch = bar.widgetForAction(ac)
                ch.setPopupMode(QToolButton.InstantPopup)
                for name in tuple('h%d' % d for d in range(1, 7)) + ('p',):
                    m.addAction(actions['rename-block-tag-%s' % name])

        for name in tprefs.get('editor_common_toolbar', ()):
            add_action(name, self.action_bar)

        for name in tprefs.get('editor_%s_toolbar' % self.syntax, ()):
            add_action(name, self.tools_bar)

        if self.syntax == 'html':
            self.format_bar.clear()
            for name in tprefs['editor_format_toolbar']:
                add_action(name, self.format_bar)
        self.restore_state()

    def break_cycles(self):
        for x in ('modification_state_changed', 'word_ignored', 'link_clicked', 'smart_highlighting_updated'):
            try:
                getattr(self, x).disconnect()
            except TypeError:
                pass  # in case this signal was never connected
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.link_clicked.disconnect()
        self.editor.smart_highlighting_updated.disconnect()
        self.editor.setPlainText('')
        self.editor.smarts = None
        self.editor.request_completion = None

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        char = ''
        col = c.positionInBlock()
        if not c.atStart():
            c.clearSelection()
            c.movePosition(c.PreviousCharacter, c.KeepAnchor)
            char = unicode(c.selectedText()).rstrip('\0')
        return (c.blockNumber() + 1, col, char)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(self, _('No text'), _(
                'There is no suitable text in the clipboard to paste.'), show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(fix_html(current_container(), unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {'css':pretty_css, 'xml':pretty_xml}.get(self.syntax, pretty_html)
            original_text = unicode(self.editor.toPlainText())
            prettied_text = func(current_container(), name, original_text).decode('utf-8')
            if original_text != prettied_text:
                self.editor.replace_text(prettied_text)
            return True
        return False

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        c = self.editor.cursorForPosition(pos)
        origc = QTextCursor(c)
        current_cursor = self.editor.textCursor()
        r = origr = self.editor.syntax_range_for_cursor(c)
        if (r is None or not r.format.property(SPELL_PROPERTY)) and c.positionInBlock() > 0 and not current_cursor.hasSelection():
            c.setPosition(c.position() - 1)
            r = self.editor.syntax_range_for_cursor(c)

        if r is not None and r.format.property(SPELL_PROPERTY):
            word = self.editor.text_for_range(c.block(), r)
            locale = self.editor.spellcheck_locale_for_cursor(c)
            orig_pos = c.position()
            c.setPosition(orig_pos - utf16_length(word))
            found = False
            self.editor.setTextCursor(c)
            if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False):
                found = True
                fc = self.editor.textCursor()
                if fc.position() < c.position():
                    self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False)
            spell_cursor = self.editor.textCursor()
            if current_cursor.hasSelection():
                # Restore the current cursor so that any selection is preserved
                # for the change case actions
                self.editor.setTextCursor(current_cursor)
            if found:
                suggestions = dictionaries.suggestions(word, locale)[:7]
                if suggestions:
                    for suggestion in suggestions:
                        ac = m.addAction(suggestion, partial(self.editor.simple_replace, suggestion, cursor=spell_cursor))
                        f = ac.font()
                        f.setBold(True), ac.setFont(f)
                    m.addSeparator()
                m.addAction(actions['spell-next'])
                m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale))
                dics = dictionaries.active_user_dictionaries
                if len(dics) > 0:
                    if len(dics) == 1:
                        m.addAction(_('Add this word to the dictionary: {0}').format(dics[0].name), partial(
                            self._nuke_word, dics[0].name, word, locale))
                    else:
                        ac = m.addAction(_('Add this word to the dictionary'))
                        dmenu = QMenu(m)
                        ac.setMenu(dmenu)
                        for dic in dics:
                            dmenu.addAction(dic.name, partial(self._nuke_word, dic.name, word, locale))
                m.addSeparator()

        if origr is not None and origr.format.property(LINK_PROPERTY):
            href = self.editor.text_for_range(origc.block(), origr)
            m.addAction(_('Open %s') % href, partial(self.link_clicked.emit, href))

        if origr is not None and (origr.format.property(TAG_NAME_PROPERTY) or origr.format.property(CSS_PROPERTY)):
            word = self.editor.text_for_range(origc.block(), origr)
            item_type = 'tag_name' if origr.format.property(TAG_NAME_PROPERTY) else 'css_property'
            url = help_url(word, item_type, self.editor.highlighter.doc_name, extra_data=current_container().opf_version)
            if url is not None:
                m.addAction(_('Show help for: %s') % word, partial(open_url, url))

        for x in ('undo', 'redo'):
            ac = actions['editor-%s' % x]
            if ac.isEnabled():
                a(ac)
        m.addSeparator()
        for x in ('cut', 'copy', 'paste'):
            ac = actions['editor-' + x]
            if ac.isEnabled():
                a(ac)
        m.addSeparator()
        m.addAction(_('&Select all'), self.editor.select_all)
        if self.selected_text or self.has_marked_text:
            update_mark_text_action(self)
            m.addAction(actions['mark-selected-text'])
        if self.syntax != 'css' and actions['editor-cut'].isEnabled():
            cm = QMenu(_('Change &case'), m)
            for ac in 'upper lower swap title capitalize'.split():
                cm.addAction(actions['transform-case-' + ac])
            m.addMenu(cm)
        if self.syntax == 'html':
            m.addAction(actions['multisplit'])
        m.exec_(self.editor.viewport().mapToGlobal(pos))

    def goto_sourceline(self, *args, **kwargs):
        return self.editor.goto_sourceline(*args, **kwargs)

    def goto_css_rule(self, *args, **kwargs):
        return self.editor.goto_css_rule(*args, **kwargs)

    def get_tag_contents(self, *args, **kwargs):
        return self.editor.get_tag_contents(*args, **kwargs)

    def _nuke_word(self, dic, word, locale):
        if dic is None:
            dictionaries.ignore_word(word, locale)
        else:
            dictionaries.add_to_user_dictionary(dic, word, locale)
        self.word_ignored.emit(word, locale)
コード例 #13
0
ファイル: widget.py プロジェクト: BatteringRam/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.editor.customContextMenuRequested.connect(self.show_context_menu)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(self._cursor_position_changed)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()
        def fset(self, val):
            self.editor.go_to_line(val)
        return property(fget=fget, fset=fset)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            return ans.encode('utf-8')
        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)
        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template, syntax=self.syntax, process_template=True)

    def get_raw_data(self):
        return unicodedata.normalize('NFC', unicode(self.editor.toPlainText()).rstrip('\0'))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None):
        self.editor.apply_settings(prefs=None)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def action_triggered(self, action):
        action, args = action[0], action[1:]
        func = getattr(self.editor, action)
        func(*args)

    def insert_image(self, href):
        self.editor.insert_image(href)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)
    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified
        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('undo', 'redo'):
            b.addAction(actions['editor-%s' % x])
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            b.addAction(actions['editor-%s' % x])
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        if self.syntax == 'html':
            b.addAction(actions['fix-html-current'])
        if self.syntax in {'xml', 'html', 'css'}:
            b.addAction(actions['pretty-current'])
        if self.syntax in {'html', 'css'}:
            b.addAction(actions['insert-image'])
        if self.syntax == 'html':
            self.format_bar = b = self.addToolBar(_('Format text'))
            for x in ('bold', 'italic', 'underline', 'strikethrough', 'subscript', 'superscript', 'color', 'background-color'):
                b.addAction(actions['format-text-%s' % x])
            ac = b.addAction(QIcon(I('format-text-heading.png')), _('Change paragraph to heading'))
            m = QMenu()
            ac.setMenu(m)
            b.widgetForAction(ac).setPopupMode(QToolButton.InstantPopup)
            for name in tuple('h%d' % d for d in range(1, 7)) + ('p',):
                m.addAction(actions['rename-block-tag-%s' % name])

    def break_cycles(self):
        self.modification_state_changed.disconnect()
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.setPlainText('')
        self.editor.smarts = None

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        char = ''
        if not c.atStart():
            c.clearSelection()
            c.setPosition(c.position()-1, c.KeepAnchor)
            char = unicode(c.selectedText()).rstrip('\0')
        return (c.blockNumber() + 1, c.positionInBlock(), char)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(self, _('No text'), _(
                'There is no suitable text in the clipboard to paste.'), show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(fix_html(current_container(), unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {'css':pretty_css, 'xml':pretty_xml}.get(self.syntax, pretty_html)
            self.editor.replace_text(func(current_container(), name, unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        for x in ('undo', 'redo'):
            a(actions['editor-%s' % x])
        m.addSeparator()
        for x in ('cut', 'copy', 'paste'):
            a(actions['editor-' + x])
        m.addSeparator()
        m.addAction(_('&Select all'), self.editor.select_all)
        m.addAction(actions['mark-selected-text'])
        if self.syntax == 'html':
            m.addAction(actions['multisplit'])
        m.exec_(self.editor.mapToGlobal(pos))
コード例 #14
0
ファイル: widget.py プロジェクト: kmshi/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(
            self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(
            self._cursor_position_changed)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()

        def fset(self, val):
            self.editor.go_to_line(val)

        return property(fget=fget, fset=fset)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            return ans.encode('utf-8')

        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)

        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template,
                              syntax=self.syntax,
                              process_template=True)

    def get_raw_data(self):
        return unicodedata.normalize('NFC', unicode(self.editor.toPlainText()))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None):
        self.editor.apply_settings(prefs=None)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)

    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified

        def fset(self, val):
            self.editor.is_modified = val

        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('undo', 'redo'):
            b.addAction(actions['editor-%s' % x])
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            b.addAction(actions['editor-%s' % x])
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        if self.syntax == 'html':
            b.addAction(actions['fix-html-current'])
        if self.syntax in {'xml', 'html', 'css'}:
            b.addAction(actions['pretty-current'])

    def break_cycles(self):
        self.modification_state_changed.disconnect()
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.setPlainText('')

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        return (c.blockNumber() + 1, c.positionInBlock())

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(
                self,
                _('No text'),
                _('There is no suitable text in the clipboard to paste.'),
                show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(
                fix_html(current_container(),
                         unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {
                'css': pretty_css,
                'xml': pretty_xml
            }.get(self.syntax, pretty_html)
            self.editor.replace_text(
                func(current_container(), name,
                     unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False
コード例 #15
0
ファイル: function_replace.py プロジェクト: davidfor/calibre
class FunctionEditor(Dialog):

    def __init__(self, func_name='', parent=None):
        self._func_name = func_name
        Dialog.__init__(self, _('Create/edit a function'), 'edit-sr-func', parent=parent)

    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.h = h = QHBoxLayout()
        l.addLayout(h)

        self.la1 = la = QLabel(_('F&unction name:'))
        h.addWidget(la)
        self.fb = fb = FunctionBox(self)
        la.setBuddy(fb)
        h.addWidget(fb, stretch=10)

        self.la3 = la = QLabel(_('&Code:'))
        self.source_code = TextEdit(self)
        self.source_code.load_text('', 'python')
        la.setBuddy(self.source_code)
        l.addWidget(la), l.addWidget(self.source_code)

        if self._func_name:
            self.fb.setText(self._func_name)
            func = functions().get(self._func_name)
            if func is not None:
                self.source_code.setPlainText(func.source or ('\n' + EMPTY_FUNC))
        else:
            self.source_code.setPlainText('\n' + EMPTY_FUNC)

        self.la2 = la = QLabel(_(
            'For help with creating functions, see the <a href="%s">User Manual</a>') %
            localize_user_manual_link('https://manual.calibre-ebook.com/function_mode.html'))
        la.setOpenExternalLinks(True)
        l.addWidget(la)

        l.addWidget(self.bb)

    def sizeHint(self):
        fm = QFontMetrics(self.font())
        return QSize(fm.averageCharWidth() * 120, 600)

    @property
    def func_name(self):
        return self.fb.text().strip()

    @property
    def source(self):
        return self.source_code.toPlainText()

    def accept(self):
        if not self.func_name:
            return error_dialog(self, _('Must specify name'), _(
                'You must specify a name for this function.'), show=True)
        source = self.source
        try:
            mod = compile_code(source, self.func_name)
        except Exception as err:
            return error_dialog(self, _('Invalid python code'), _(
                'The code you created is not valid python code, with error: %s') % err, show=True)
        if not callable(mod.get('replace')):
            return error_dialog(self, _('No replace function'), _(
                'You must create a python function named replace in your code'), show=True)
        user_functions[self.func_name] = source
        functions(refresh=True)
        refresh_boxes()

        Dialog.accept(self)
コード例 #16
0
class FunctionEditor(Dialog):

    def __init__(self, func_name='', parent=None):
        self._func_name = func_name
        Dialog.__init__(self, _('Create/edit a function'), 'edit-sr-func', parent=parent)

    def setup_ui(self):
        self.l = l = QVBoxLayout(self)
        self.h = h = QHBoxLayout()
        l.addLayout(h)

        self.la1 = la = QLabel(_('F&unction name:'))
        h.addWidget(la)
        self.fb = fb = FunctionBox(self)
        la.setBuddy(fb)
        h.addWidget(fb, stretch=10)

        self.la3 = la = QLabel(_('&Code:'))
        self.source_code = TextEdit(self)
        self.source_code.load_text('', 'python')
        la.setBuddy(self.source_code)
        l.addWidget(la), l.addWidget(self.source_code)

        if self._func_name:
            self.fb.setText(self._func_name)
            func = functions().get(self._func_name)
            if func is not None:
                self.source_code.setPlainText(func.source or ('\n' + EMPTY_FUNC))
        else:
            self.source_code.setPlainText('\n' + EMPTY_FUNC)

        self.la2 = la = QLabel(_(
            'For help with creating functions, see the <a href="%s">User Manual</a>') %
            localize_user_manual_link('https://manual.calibre-ebook.com/function_mode.html'))
        la.setOpenExternalLinks(True)
        l.addWidget(la)

        l.addWidget(self.bb)

    def sizeHint(self):
        fm = QFontMetrics(self.font())
        return QSize(fm.averageCharWidth() * 120, 600)

    @property
    def func_name(self):
        return self.fb.text().strip()

    @property
    def source(self):
        return self.source_code.toPlainText()

    def accept(self):
        if not self.func_name:
            return error_dialog(self, _('Must specify name'), _(
                'You must specify a name for this function.'), show=True)
        source = self.source
        try:
            mod = compile_code(source, self.func_name)
        except Exception as err:
            return error_dialog(self, _('Invalid Python code'), _(
                'The code you created is not valid Python code, with error: %s') % err, show=True)
        if not callable(mod.get('replace')):
            return error_dialog(self, _('No replace function'), _(
                'You must create a Python function named replace in your code'), show=True)
        user_functions[self.func_name] = source
        functions(refresh=True)
        refresh_boxes()

        Dialog.accept(self)
コード例 #17
0
class Ui_Form(object):
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(955, 672)
        self.gridLayout = QtWidgets.QGridLayout(Form)
        self.gridLayout.setObjectName("gridLayout")
        self.label_4 = QtWidgets.QLabel(Form)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem)
        self.execute_button = QtWidgets.QPushButton(Form)
        self.execute_button.setObjectName("execute_button")
        self.horizontalLayout_3.addWidget(self.execute_button)
        self.gridLayout.addLayout(self.horizontalLayout_3, 6, 0, 1, 1)
        self.line = QtWidgets.QFrame(Form)
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.gridLayout.addWidget(self.line, 9, 0, 1, 2)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.gridLayout_3 = QtWidgets.QGridLayout()
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.documentation = QtWidgets.QTextEdit(Form)
        self.documentation.setObjectName("documentation")
        self.gridLayout_3.addWidget(self.documentation, 5, 1, 1, 1)
        self.function_name = QtWidgets.QComboBox(Form)
        self.function_name.setEditable(True)
        self.function_name.setObjectName("function_name")
        self.gridLayout_3.addWidget(self.function_name, 0, 1, 1, 1)
        self.label_2 = QtWidgets.QLabel(Form)
        self.label_2.setObjectName("label_2")
        self.gridLayout_3.addWidget(self.label_2, 0, 0, 1, 1)
        self.label_3 = QtWidgets.QLabel(Form)
        self.label_3.setToolTip("")
        self.label_3.setObjectName("label_3")
        self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1)
        self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_5.setObjectName("horizontalLayout_5")
        self.fromfile_checkbox = QtWidgets.QCheckBox(Form)
        self.fromfile_checkbox.setObjectName("fromfile_checkbox")
        self.horizontalLayout_5.addWidget(self.fromfile_checkbox)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_5.addItem(spacerItem1)
        self.save_file_button = QtWidgets.QPushButton(Form)
        self.save_file_button.setObjectName("save_file_button")
        self.horizontalLayout_5.addWidget(self.save_file_button)
        self.load_file_button = QtWidgets.QPushButton(Form)
        self.load_file_button.setObjectName("load_file_button")
        self.horizontalLayout_5.addWidget(self.load_file_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_5, 4, 1, 1, 1)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.delete_button = QtWidgets.QPushButton(Form)
        self.delete_button.setObjectName("delete_button")
        self.horizontalLayout_2.addWidget(self.delete_button)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem2)
        self.save_button = QtWidgets.QPushButton(Form)
        self.save_button.setObjectName("save_button")
        self.horizontalLayout_2.addWidget(self.save_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_2, 6, 1, 1, 1)
        self.label_41 = QtWidgets.QLabel(Form)
        self.label_41.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.label_41.setObjectName("label_41")
        self.gridLayout_3.addWidget(self.label_41, 5, 0, 1, 1)
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.macrofile = QtWidgets.QLineEdit(Form)
        self.macrofile.setObjectName("macrofile")
        self.horizontalLayout_6.addWidget(self.macrofile)
        self.filebrowse_button = QtWidgets.QToolButton(Form)
        self.filebrowse_button.setObjectName("filebrowse_button")
        self.horizontalLayout_6.addWidget(self.filebrowse_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_6, 2, 1, 1, 1)
        self.horizontalLayout.addLayout(self.gridLayout_3)
        self.horizontalLayout1 = QtWidgets.QVBoxLayout()
        self.horizontalLayout1.setObjectName("horizontalLayout1")
        self.textBrowser = QtWidgets.QTextBrowser(Form)
        self.textBrowser.setObjectName("textBrowser")
        self.horizontalLayout1.addWidget(self.textBrowser)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_4.addItem(spacerItem3)
        self.import_button = QtWidgets.QPushButton(Form)
        self.import_button.setObjectName("import_button")
        self.horizontalLayout_4.addWidget(self.import_button)
        self.export_button = QtWidgets.QPushButton(Form)
        self.export_button.setObjectName("export_button")
        self.horizontalLayout_4.addWidget(self.export_button)
        self.horizontalLayout1.addLayout(self.horizontalLayout_4)
        self.horizontalLayout.addLayout(self.horizontalLayout1)
        self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1)
        from calibre.gui2.tweak_book.editor.text import TextEdit
        self.program = TextEdit(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.program.sizePolicy().hasHeightForWidth())
        self.program.setSizePolicy(sizePolicy)
        self.program.setMinimumSize(QtCore.QSize(400, 0))
        self.program.setDocumentTitle("")
        self.program.setTabStopWidth(30)
        self.program.setObjectName("program")
        self.gridLayout.addWidget(self.program, 2, 0, 1, 1)
        self.label_4.setBuddy(self.program)
        self.label_2.setBuddy(self.function_name)
        self.label_3.setBuddy(self.macrofile)
        self.label_41.setBuddy(self.documentation)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):

        Form.setWindowTitle(_("Form"))
        self.label_4.setText(_("&Program Code: (be sure to follow python indenting rules)"))
        self.execute_button.setText(_("&Execute"))
        self.function_name.setToolTip(_("Select or enter the name of the macro to create."))
        self.label_2.setText(_("&Macro:"))
        self.label_3.setText(_("Macro &file:"))
        self.fromfile_checkbox.setToolTip(_("If checked, macro\'s program code will be loaded from specified file everytime before execute. So, you can edit program code using external editor and execute it directly. Othewise, program code is saved inside calibre\'s preference file."))
        self.fromfile_checkbox.setText(_("Execute from a file"))
        self.save_file_button.setToolTip(_("Save current program code to a file"))
        self.save_file_button.setText(_("&Save to file"))
        self.load_file_button.setToolTip(_("Load macro from a file for current macro"))
        self.load_file_button.setText(_("&Load from file"))
        self.delete_button.setToolTip(_("Delete current macro."))
        self.delete_button.setText(_("&Delete macro"))
        self.save_button.setToolTip(_("Save current macro."))
        self.save_button.setText(_("&Save macro"))
        self.label_41.setText(_("&Documentation:"))
        self.macrofile.setToolTip(_("File path to program code saved to, loaded from, and executed from if \"Execute from a file\" is checked."))
        self.filebrowse_button.setText(_("..."))
        self.import_button.setToolTip(_("Import previously exported macros."))
        self.import_button.setText(_("&Import previously exported macros"))
        self.export_button.setToolTip(_("Export all macros\'s program code and settings into single json file. Useful for moving macros to another computer or making a backup."))
        self.export_button.setText(_("&Export all macros"))
コード例 #18
0
    def setupUi(self, Form):
        Form.setObjectName("Form")
        Form.resize(955, 672)
        self.gridLayout = QtWidgets.QGridLayout(Form)
        self.gridLayout.setObjectName("gridLayout")
        self.label_4 = QtWidgets.QLabel(Form)
        self.label_4.setObjectName("label_4")
        self.gridLayout.addWidget(self.label_4, 1, 0, 1, 1)
        self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_3.setObjectName("horizontalLayout_3")
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_3.addItem(spacerItem)
        self.execute_button = QtWidgets.QPushButton(Form)
        self.execute_button.setObjectName("execute_button")
        self.horizontalLayout_3.addWidget(self.execute_button)
        self.gridLayout.addLayout(self.horizontalLayout_3, 6, 0, 1, 1)
        self.line = QtWidgets.QFrame(Form)
        self.line.setFrameShape(QtWidgets.QFrame.HLine)
        self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
        self.line.setObjectName("line")
        self.gridLayout.addWidget(self.line, 9, 0, 1, 2)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.gridLayout_3 = QtWidgets.QGridLayout()
        self.gridLayout_3.setObjectName("gridLayout_3")
        self.documentation = QtWidgets.QTextEdit(Form)
        self.documentation.setObjectName("documentation")
        self.gridLayout_3.addWidget(self.documentation, 5, 1, 1, 1)
        self.function_name = QtWidgets.QComboBox(Form)
        self.function_name.setEditable(True)
        self.function_name.setObjectName("function_name")
        self.gridLayout_3.addWidget(self.function_name, 0, 1, 1, 1)
        self.label_2 = QtWidgets.QLabel(Form)
        self.label_2.setObjectName("label_2")
        self.gridLayout_3.addWidget(self.label_2, 0, 0, 1, 1)
        self.label_3 = QtWidgets.QLabel(Form)
        self.label_3.setToolTip("")
        self.label_3.setObjectName("label_3")
        self.gridLayout_3.addWidget(self.label_3, 2, 0, 1, 1)
        self.horizontalLayout_5 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_5.setObjectName("horizontalLayout_5")
        self.fromfile_checkbox = QtWidgets.QCheckBox(Form)
        self.fromfile_checkbox.setObjectName("fromfile_checkbox")
        self.horizontalLayout_5.addWidget(self.fromfile_checkbox)
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_5.addItem(spacerItem1)
        self.save_file_button = QtWidgets.QPushButton(Form)
        self.save_file_button.setObjectName("save_file_button")
        self.horizontalLayout_5.addWidget(self.save_file_button)
        self.load_file_button = QtWidgets.QPushButton(Form)
        self.load_file_button.setObjectName("load_file_button")
        self.horizontalLayout_5.addWidget(self.load_file_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_5, 4, 1, 1, 1)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        self.delete_button = QtWidgets.QPushButton(Form)
        self.delete_button.setObjectName("delete_button")
        self.horizontalLayout_2.addWidget(self.delete_button)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem2)
        self.save_button = QtWidgets.QPushButton(Form)
        self.save_button.setObjectName("save_button")
        self.horizontalLayout_2.addWidget(self.save_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_2, 6, 1, 1, 1)
        self.label_41 = QtWidgets.QLabel(Form)
        self.label_41.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
        self.label_41.setObjectName("label_41")
        self.gridLayout_3.addWidget(self.label_41, 5, 0, 1, 1)
        self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_6.setObjectName("horizontalLayout_6")
        self.macrofile = QtWidgets.QLineEdit(Form)
        self.macrofile.setObjectName("macrofile")
        self.horizontalLayout_6.addWidget(self.macrofile)
        self.filebrowse_button = QtWidgets.QToolButton(Form)
        self.filebrowse_button.setObjectName("filebrowse_button")
        self.horizontalLayout_6.addWidget(self.filebrowse_button)
        self.gridLayout_3.addLayout(self.horizontalLayout_6, 2, 1, 1, 1)
        self.horizontalLayout.addLayout(self.gridLayout_3)
        self.horizontalLayout1 = QtWidgets.QVBoxLayout()
        self.horizontalLayout1.setObjectName("horizontalLayout1")
        self.textBrowser = QtWidgets.QTextBrowser(Form)
        self.textBrowser.setObjectName("textBrowser")
        self.horizontalLayout1.addWidget(self.textBrowser)
        self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_4.setObjectName("horizontalLayout_4")
        spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_4.addItem(spacerItem3)
        self.import_button = QtWidgets.QPushButton(Form)
        self.import_button.setObjectName("import_button")
        self.horizontalLayout_4.addWidget(self.import_button)
        self.export_button = QtWidgets.QPushButton(Form)
        self.export_button.setObjectName("export_button")
        self.horizontalLayout_4.addWidget(self.export_button)
        self.horizontalLayout1.addLayout(self.horizontalLayout_4)
        self.horizontalLayout.addLayout(self.horizontalLayout1)
        self.gridLayout.addLayout(self.horizontalLayout, 10, 0, 1, 1)
        from calibre.gui2.tweak_book.editor.text import TextEdit
        self.program = TextEdit(Form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(1)
        sizePolicy.setHeightForWidth(self.program.sizePolicy().hasHeightForWidth())
        self.program.setSizePolicy(sizePolicy)
        self.program.setMinimumSize(QtCore.QSize(400, 0))
        self.program.setDocumentTitle("")
        self.program.setTabStopWidth(30)
        self.program.setObjectName("program")
        self.gridLayout.addWidget(self.program, 2, 0, 1, 1)
        self.label_4.setBuddy(self.program)
        self.label_2.setBuddy(self.function_name)
        self.label_3.setBuddy(self.macrofile)
        self.label_41.setBuddy(self.documentation)

        self.retranslateUi(Form)
        QtCore.QMetaObject.connectSlotsByName(Form)
コード例 #19
0
ファイル: themes.py プロジェクト: dusual/calibre
    def setup_ui(self):
        self.block_show = False
        self.properties = []
        self.l = l = QVBoxLayout(self)
        self.setLayout(l)
        h = QHBoxLayout()
        l.addLayout(h)
        self.la = la = QLabel(_('&Edit theme:'))
        h.addWidget(la)
        self.theme = t = QComboBox(self)
        la.setBuddy(t)
        t.addItems(sorted(custom_theme_names()))
        t.setMinimumWidth(200)
        if t.count() > 0:
            t.setCurrentIndex(0)
        t.currentIndexChanged[int].connect(self.show_theme)
        h.addWidget(t)

        self.add_button = b = QPushButton(QIcon(I('plus.png')),
                                          _('Add &new theme'), self)
        b.clicked.connect(self.create_new_theme)
        h.addWidget(b)

        self.remove_button = b = QPushButton(QIcon(I('minus.png')),
                                             _('&Remove theme'), self)
        b.clicked.connect(self.remove_theme)
        h.addWidget(b)
        h.addStretch(1)

        self.scroll = s = QScrollArea(self)
        self.w = w = QWidget(self)
        s.setWidget(w), s.setWidgetResizable(True)
        self.cl = cl = QVBoxLayout()
        w.setLayout(cl)

        from calibre.gui2.tweak_book.editor.text import TextEdit
        self.preview = p = TextEdit(self, expected_geometry=(73, 50))
        p.load_text(
            textwrap.dedent(
                _('''\
            <h2>Creating a custom theme</h2>

            <p id="attribute" lang="und">You can create a custom syntax highlighting
            theme, with your own colors and font styles. The most important
            types of highlighting rules are described below. Note that not
            every rule supports every kind of customization, for example,
            changing font or underline styles for the <code>Cursor</code> rule
            does not have any effect as that rule is used only for the color of
            the blinking cursor.</p>

            <p>As you make changes to your them on the left, the changes will
            be reflected live in this panel.</p>

            <p xml:lang="und">
            {0}
                The most important rule. Sets the
                foreground and background colors for the editor as well as the
                style of "normal" text, that is, text that does not match any
                special syntax.

            {1}
                Defines the colors for text selected by the mouse.

            {2}
                Defines the color for the line containing the cursor.

            {3}
                Defines the colors for the line numbers on the left.

            {4}
                Defines the colors for matching tags in HTML and matching
                braces in CSS.

            {5}
                Used for highlighting tags in HTML

            {6}
                Used for highlighting attributes in HTML

            {7}
                Tag names in HTML

            {8}
                Namespace prefixes in XML and constants in CSS

            {9}
                Non-breaking spaces/hyphens in HTML

            {10}
                Syntax errors such as <this <>

            {11}
                Misspelled words such as <span lang="en">thisword</span>

            {12}
                Comments like <!-- this one -->
            </p>

            <style type="text/css">
            /* Some CSS so you can see how the highlighting rules affect it */

            p.someclass {{
                font-family: serif;
                font-size: 12px;
                line-height: 1.2;
            }}
            </style>
            ''')).format(*[
                    '<b>%s</b>' % x
                    for x in ('Normal', 'Visual', 'CursorLine', 'LineNr',
                              'MatchParen', 'Function', 'Type', 'Statement',
                              'Constant', 'SpecialCharacter', 'Error',
                              'SpellError', 'Comment')
                ]))
        p.setMaximumWidth(p.size_hint.width() + 5)
        s.setMinimumWidth(600)
        self.splitter = sp = QSplitter(self)
        l.addWidget(sp)
        sp.addWidget(s), sp.addWidget(p)

        self.bb.clear()
        self.bb.addButton(self.bb.Close)
        l.addWidget(self.bb)

        if self.theme.count() > 0:
            self.show_theme()
コード例 #20
0
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.editor.customContextMenuRequested.connect(self.show_context_menu)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(
            self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(
            self._cursor_position_changed)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()

        def fset(self, val):
            self.editor.go_to_line(val)

        return property(fget=fget, fset=fset)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            return ans.encode('utf-8')

        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)

        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template,
                              syntax=self.syntax,
                              process_template=True)

    def get_raw_data(self):
        return unicodedata.normalize(
            'NFC',
            unicode(self.editor.toPlainText()).rstrip('\0'))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None, dictionaries_changed=False):
        self.editor.apply_settings(prefs=None,
                                   dictionaries_changed=dictionaries_changed)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def action_triggered(self, action):
        action, args = action[0], action[1:]
        func = getattr(self.editor, action)
        func(*args)

    def insert_image(self, href):
        self.editor.insert_image(href)

    def insert_hyperlink(self, href, text):
        self.editor.insert_hyperlink(href, text)

    def _build_insert_tag_button_menu(self):
        m = self.insert_tag_button.menu()
        m.clear()
        for name in tprefs['insert_tag_mru']:
            m.addAction(name, partial(self.insert_tag, name))

    def insert_tag(self, name):
        self.editor.insert_tag(name)
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        mru.insert(0, name)
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    def get_smart_selection(self, update=True):
        return self.editor.smarts.get_smart_selection(self.editor,
                                                      update=update)

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def find_spell_word(self, *args, **kwargs):
        return self.editor.find_spell_word(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)

    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified

        def fset(self, val):
            self.editor.is_modified = val

        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('undo', 'redo'):
            b.addAction(actions['editor-%s' % x])
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            b.addAction(actions['editor-%s' % x])
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        b.setObjectName('tools_bar')
        if self.syntax == 'html':
            b.addAction(actions['fix-html-current'])
        if self.syntax in {'xml', 'html', 'css'}:
            b.addAction(actions['pretty-current'])
        if self.syntax in {'html', 'css'}:
            b.addAction(actions['insert-image'])
        if self.syntax == 'html':
            b.addAction(actions['insert-hyperlink'])
        if self.syntax in {'xml', 'html'}:
            b.addAction(actions['insert-tag'])
            w = self.insert_tag_button = b.widgetForAction(
                actions['insert-tag'])
            w.setPopupMode(QToolButton.MenuButtonPopup)
            w.m = m = QMenu()
            w.setMenu(m)
            w.setContextMenuPolicy(Qt.CustomContextMenu)
            w.customContextMenuRequested.connect(
                self.insert_tag_button.showMenu)
            self._build_insert_tag_button_menu()
        if self.syntax == 'html':
            self.format_bar = b = self.addToolBar(_('Format text'))
            b.setObjectName('html_format_bar')
            for x in ('bold', 'italic', 'underline', 'strikethrough',
                      'subscript', 'superscript', 'color', 'background-color'):
                b.addAction(actions['format-text-%s' % x])
            ac = b.addAction(QIcon(I('format-text-heading.png')),
                             _('Change paragraph to heading'))
            m = QMenu()
            ac.setMenu(m)
            b.widgetForAction(ac).setPopupMode(QToolButton.InstantPopup)
            for name in tuple('h%d' % d for d in range(1, 7)) + ('p', ):
                m.addAction(actions['rename-block-tag-%s' % name])

    def break_cycles(self):
        self.modification_state_changed.disconnect()
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.setPlainText('')
        self.editor.smarts = None

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        char = ''
        col = c.positionInBlock()
        if not c.atStart():
            c.clearSelection()
            c.movePosition(c.PreviousCharacter, c.KeepAnchor)
            char = unicode(c.selectedText()).rstrip('\0')
        return (c.blockNumber() + 1, col, char)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(
                self,
                _('No text'),
                _('There is no suitable text in the clipboard to paste.'),
                show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(
                fix_html(current_container(),
                         unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {
                'css': pretty_css,
                'xml': pretty_xml
            }.get(self.syntax, pretty_html)
            original_text = unicode(self.editor.toPlainText())
            prettied_text = func(current_container(), name,
                                 original_text).decode('utf-8')
            if original_text != prettied_text:
                self.editor.replace_text(prettied_text)
            return True
        return False

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        for x in ('undo', 'redo'):
            a(actions['editor-%s' % x])
        m.addSeparator()
        for x in ('cut', 'copy', 'paste'):
            a(actions['editor-' + x])
        m.addSeparator()
        m.addAction(_('&Select all'), self.editor.select_all)
        m.addAction(actions['mark-selected-text'])
        if self.syntax == 'html':
            m.addAction(actions['multisplit'])
        m.exec_(self.editor.mapToGlobal(pos))
コード例 #21
0
ファイル: widget.py プロジェクト: IvoNet/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()
    word_ignored = pyqtSignal(object, object)

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.editor.customContextMenuRequested.connect(self.show_context_menu)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(self._cursor_position_changed)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()
        def fset(self, val):
            self.editor.go_to_line(val)
        return property(fget=fget, fset=fset)

    def current_tag(self):
        return self.editor.current_tag()

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            ans, changed = replace_encoding_declarations(ans, enc='utf-8', limit=4*1024)
            if changed:
                self.data = ans
            return ans.encode('utf-8')
        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)
        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template, syntax=self.syntax, process_template=True)

    def get_raw_data(self):
        # The EPUB spec requires NFC normalization, see section 1.3.6 of
        # http://www.idpf.org/epub/20/spec/OPS_2.0.1_draft.htm
        return unicodedata.normalize('NFC', unicode(self.editor.toPlainText()).rstrip('\0'))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None, dictionaries_changed=False):
        self.editor.apply_settings(prefs=None, dictionaries_changed=dictionaries_changed)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def action_triggered(self, action):
        action, args = action[0], action[1:]
        func = getattr(self.editor, action)
        func(*args)

    def insert_image(self, href):
        self.editor.insert_image(href)

    def insert_hyperlink(self, href, text):
        self.editor.insert_hyperlink(href, text)

    def _build_insert_tag_button_menu(self):
        m = self.insert_tag_menu
        m.clear()
        for name in tprefs['insert_tag_mru']:
            m.addAction(name, partial(self.insert_tag, name))

    def insert_tag(self, name):
        self.editor.insert_tag(name)
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        mru.insert(0, name)
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    def get_smart_selection(self, update=True):
        return self.editor.smarts.get_smart_selection(self.editor, update=update)

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def find_spell_word(self, *args, **kwargs):
        return self.editor.find_spell_word(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)
    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified
        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('undo', 'redo'):
            b.addAction(actions['editor-%s' % x])
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            b.addAction(actions['editor-%s' % x])
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        b.setObjectName('tools_bar')
        if self.syntax == 'html':
            self.format_bar = b = self.addToolBar(_('Format text'))
            b.setObjectName('html_format_bar')
        self.insert_tag_menu = QMenu(self)
        self.populate_toolbars()

    def populate_toolbars(self):
        self.tools_bar.clear()
        def add_action(name, bar):
            try:
                ac = actions[name]
            except KeyError:
                if DEBUG:
                    prints('Unknown editor tool: %r' % name)
                return
            bar.addAction(ac)
            if name == 'insert-tag':
                w = bar.widgetForAction(ac)
                w.setPopupMode(QToolButton.MenuButtonPopup)
                w.setMenu(self.insert_tag_menu)
                w.setContextMenuPolicy(Qt.CustomContextMenu)
                w.customContextMenuRequested.connect(w.showMenu)
                self._build_insert_tag_button_menu()
            elif name == 'change-paragraph':
                m = ac.m = QMenu()
                ac.setMenu(m)
                bar.widgetForAction(ac).setPopupMode(QToolButton.InstantPopup)
                for name in tuple('h%d' % d for d in range(1, 7)) + ('p',):
                    m.addAction(actions['rename-block-tag-%s' % name])

        for name in tprefs.get('editor_%s_toolbar' % self.syntax, ()):
            add_action(name, self.tools_bar)

        if self.syntax == 'html':
            self.format_bar.clear()
            for name in tprefs['editor_format_toolbar']:
                add_action(name, self.format_bar)

    def break_cycles(self):
        try:
            self.modification_state_changed.disconnect()
        except TypeError:
            pass  # in case this signal was never connected
        try:
            self.word_ignored.disconnect()
        except TypeError:
            pass  # in case this signal was never connected
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.setPlainText('')
        self.editor.smarts = None

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        char = ''
        col = c.positionInBlock()
        if not c.atStart():
            c.clearSelection()
            c.movePosition(c.PreviousCharacter, c.KeepAnchor)
            char = unicode(c.selectedText()).rstrip('\0')
        return (c.blockNumber() + 1, col, char)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(self, _('No text'), _(
                'There is no suitable text in the clipboard to paste.'), show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(fix_html(current_container(), unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {'css':pretty_css, 'xml':pretty_xml}.get(self.syntax, pretty_html)
            original_text = unicode(self.editor.toPlainText())
            prettied_text = func(current_container(), name, original_text).decode('utf-8')
            if original_text != prettied_text:
                self.editor.replace_text(prettied_text)
            return True
        return False

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        c = self.editor.cursorForPosition(pos)
        r = self.editor.syntax_range_for_cursor(c)
        if r is not None and r.format.property(SPELL_PROPERTY).toBool():
            word = self.editor.text_for_range(c.block(), r)
            locale = self.editor.spellcheck_locale_for_cursor(c)
            orig_pos = c.position()
            c.setPosition(orig_pos - utf16_length(word))
            found = False
            self.editor.setTextCursor(c)
            if self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False):
                found = True
                fc = self.editor.textCursor()
                if fc.position() < c.position():
                    self.editor.find_spell_word([word], locale.langcode, center_on_cursor=False)
            if found:
                suggestions = dictionaries.suggestions(word, locale)[:7]
                if suggestions:
                    for suggestion in suggestions:
                        ac = m.addAction(suggestion, partial(self.editor.simple_replace, suggestion))
                        f = ac.font()
                        f.setBold(True), ac.setFont(f)
                    m.addSeparator()
                m.addAction(actions['spell-next'])
                m.addAction(_('Ignore this word'), partial(self._nuke_word, None, word, locale))
                dics = dictionaries.active_user_dictionaries
                if len(dics) > 0:
                    if len(dics) == 1:
                        m.addAction(_('Add this word to the dictionary: {0}').format(dics[0].name), partial(
                            self._nuke_word, dics[0].name, word, locale))
                    else:
                        ac = m.addAction(_('Add this word to the dictionary'))
                        dmenu = QMenu(m)
                        ac.setMenu(dmenu)
                        for dic in dics:
                            dmenu.addAction(dic.name, partial(self._nuke_word, dic.name, word, locale))
                m.addSeparator()

        for x in ('undo', 'redo'):
            a(actions['editor-%s' % x])
        m.addSeparator()
        for x in ('cut', 'copy', 'paste'):
            a(actions['editor-' + x])
        m.addSeparator()
        m.addAction(_('&Select all'), self.editor.select_all)
        m.addAction(actions['mark-selected-text'])
        if self.syntax == 'html':
            m.addAction(actions['multisplit'])
        m.exec_(self.editor.mapToGlobal(pos))

    def goto_sourceline(self, *args, **kwargs):
        return self.editor.goto_sourceline(*args, **kwargs)

    def goto_css_rule(self, *args, **kwargs):
        return self.editor.goto_css_rule(*args, **kwargs)

    def get_tag_contents(self, *args, **kwargs):
        return self.editor.get_tag_contents(*args, **kwargs)

    def _nuke_word(self, dic, word, locale):
        if dic is None:
            dictionaries.ignore_word(word, locale)
        else:
            dictionaries.add_to_user_dictionary(dic, word, locale)
        self.word_ignored.emit(word, locale)
コード例 #22
0
ファイル: widget.py プロジェクト: elonchen/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()
    word_ignored = pyqtSignal(object, object)
    link_clicked = pyqtSignal(object)
    smart_highlighting_updated = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.editor.setContextMenuPolicy(Qt.CustomContextMenu)
        self.editor.customContextMenuRequested.connect(self.show_context_menu)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(
            self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(
            self._cursor_position_changed)
        self.editor.link_clicked.connect(self.link_clicked)
        self.editor.smart_highlighting_updated.connect(
            self.smart_highlighting_updated)

    @property
    def current_line(self):
        return self.editor.textCursor().blockNumber()

    @current_line.setter
    def current_line(self, val):
        self.editor.go_to_line(val)

    @property
    def current_editing_state(self):
        c = self.editor.textCursor()
        return {'cursor': (c.anchor(), c.position())}

    @current_editing_state.setter
    def current_editing_state(self, val):
        anchor, position = val.get('cursor', (None, None))
        if anchor is not None and position is not None:
            c = self.editor.textCursor()
            c.setPosition(anchor), c.setPosition(position, c.KeepAnchor)
            self.editor.setTextCursor(c)

    def current_tag(self, for_position_sync=True):
        return self.editor.current_tag(for_position_sync=for_position_sync)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @property
    def data(self):
        ans = self.get_raw_data()
        ans, changed = replace_encoding_declarations(ans,
                                                     enc='utf-8',
                                                     limit=4 * 1024)
        if changed:
            self.data = ans
        return ans.encode('utf-8')

    @data.setter
    def data(self, val):
        self.editor.load_text(val,
                              syntax=self.syntax,
                              doc_name=editor_name(self))

    def init_from_template(self, template):
        self.editor.load_text(template,
                              syntax=self.syntax,
                              process_template=True,
                              doc_name=editor_name(self))

    def change_document_name(self, newname):
        self.editor.change_document_name(newname)
        self.editor.completion_doc_name = newname

    def get_raw_data(self):
        # The EPUB spec requires NFC normalization, see section 1.3.6 of
        # http://www.idpf.org/epub/20/spec/OPS_2.0.1_draft.htm
        return unicodedata.normalize(
            'NFC',
            unicode_type(self.editor.toPlainText()).rstrip('\0'))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None, dictionaries_changed=False):
        self.editor.apply_settings(prefs=None,
                                   dictionaries_changed=dictionaries_changed)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def action_triggered(self, action):
        action, args = action[0], action[1:]
        func = getattr(self.editor, action)
        func(*args)

    def insert_image(self,
                     href,
                     fullpage=False,
                     preserve_aspect_ratio=False,
                     width=-1,
                     height=-1):
        self.editor.insert_image(href,
                                 fullpage=fullpage,
                                 preserve_aspect_ratio=preserve_aspect_ratio,
                                 width=width,
                                 height=height)

    def insert_hyperlink(self, href, text, template=None):
        self.editor.insert_hyperlink(href, text, template=template)

    def _build_insert_tag_button_menu(self):
        m = self.insert_tag_menu
        m.clear()
        names = tprefs['insert_tag_mru']
        for name in names:
            m.addAction(name, partial(self.insert_tag, name))
        if names:
            m.addSeparator()
            m = m.addMenu(_('Remove from this menu'))
            for name in names:
                m.addAction(name, partial(self.remove_insert_tag, name))

    def insert_tag(self, name):
        self.editor.insert_tag(name)
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        mru.insert(0, name)
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def remove_insert_tag(self, name):
        mru = tprefs['insert_tag_mru']
        try:
            mru.remove(name)
        except ValueError:
            pass
        tprefs['insert_tag_mru'] = mru
        self._build_insert_tag_button_menu()

    def set_request_completion(self, callback=None, doc_name=None):
        self.editor.request_completion = callback
        self.editor.completion_doc_name = doc_name

    def handle_completion_result(self, result):
        return self.editor.handle_completion_result(result)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    def get_smart_selection(self, update=True):
        return self.editor.smarts.get_smart_selection(self.editor,
                                                      update=update)

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def find_text(self, *args, **kwargs):
        return self.editor.find_text(*args, **kwargs)

    def find_spell_word(self, *args, **kwargs):
        return self.editor.find_spell_word(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)

    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @property
    def is_modified(self):
        return self.editor.is_modified

    @is_modified.setter
    def is_modified(self, val):
        self.editor.is_modified = val

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('Edit actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        b.setObjectName('tools_bar')
        self.bars = [self.action_bar, self.tools_bar]
        if self.syntax == 'html':
            self.format_bar = b = self.addToolBar(_('Format text'))
            b.setObjectName('html_format_bar')
            self.bars.append(self.format_bar)
        self.insert_tag_menu = QMenu(self)
        self.populate_toolbars()
        for x in self.bars:
            x.setFloatable(False)
            x.topLevelChanged.connect(self.toolbar_floated)
            x.setIconSize(
                QSize(tprefs['toolbar_icon_size'],
                      tprefs['toolbar_icon_size']))

    def toolbar_floated(self, floating):
        if not floating:
            self.save_state()
            for ed in itervalues(editors):
                if ed is not self:
                    ed.restore_state()

    def save_state(self):
        for bar in self.bars:
            if bar.isFloating():
                return
        tprefs['%s-editor-state' % self.syntax] = bytearray(self.saveState())

    def restore_state(self):
        state = tprefs.get('%s-editor-state' % self.syntax, None)
        if state is not None:
            self.restoreState(state)
        for bar in self.bars:
            bar.setVisible(len(bar.actions()) > 0)

    def populate_toolbars(self):
        self.action_bar.clear(), self.tools_bar.clear()

        def add_action(name, bar):
            if name is None:
                bar.addSeparator()
                return
            try:
                ac = actions[name]
            except KeyError:
                if DEBUG:
                    prints('Unknown editor tool: %r' % name)
                return
            bar.addAction(ac)
            if name == 'insert-tag':
                w = bar.widgetForAction(ac)
                if hasattr(w, 'setPopupMode'):
                    # For some unknown reason this button is occassionally a
                    # QPushButton instead of a QToolButton
                    w.setPopupMode(QToolButton.MenuButtonPopup)
                w.setMenu(self.insert_tag_menu)
                w.setContextMenuPolicy(Qt.CustomContextMenu)
                w.customContextMenuRequested.connect(w.showMenu)
                self._build_insert_tag_button_menu()
            elif name == 'change-paragraph':
                m = ac.m = QMenu()
                ac.setMenu(m)
                ch = bar.widgetForAction(ac)
                if hasattr(ch, 'setPopupMode'):
                    # For some unknown reason this button is occassionally a
                    # QPushButton instead of a QToolButton
                    ch.setPopupMode(QToolButton.InstantPopup)
                for name in tuple('h%d' % d for d in range(1, 7)) + ('p', ):
                    m.addAction(actions['rename-block-tag-%s' % name])

        for name in tprefs.get('editor_common_toolbar', ()):
            add_action(name, self.action_bar)

        for name in tprefs.get('editor_%s_toolbar' % self.syntax, ()):
            add_action(name, self.tools_bar)

        if self.syntax == 'html':
            self.format_bar.clear()
            for name in tprefs['editor_format_toolbar']:
                add_action(name, self.format_bar)
        self.restore_state()

    def break_cycles(self):
        for x in ('modification_state_changed', 'word_ignored', 'link_clicked',
                  'smart_highlighting_updated'):
            try:
                getattr(self, x).disconnect()
            except TypeError:
                pass  # in case this signal was never connected
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.link_clicked.disconnect()
        self.editor.smart_highlighting_updated.disconnect()
        self.editor.setPlainText('')
        self.editor.smarts = None
        self.editor.request_completion = None

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available,
                                          self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        char = ''
        col = c.positionInBlock()
        if not c.atStart():
            c.clearSelection()
            c.movePosition(c.PreviousCharacter, c.KeepAnchor)
            char = unicode_type(c.selectedText()).rstrip('\0')
        return (c.blockNumber() + 1, col, char)

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(
                self,
                _('No text'),
                _('There is no suitable text in the clipboard to paste.'),
                show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(
                fix_html(current_container(),
                         unicode_type(
                             self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {
                'css': pretty_css,
                'xml': pretty_xml
            }.get(self.syntax, pretty_html)
            original_text = unicode_type(self.editor.toPlainText())
            prettied_text = func(current_container(), name,
                                 original_text).decode('utf-8')
            if original_text != prettied_text:
                self.editor.replace_text(prettied_text)
            return True
        return False

    def show_context_menu(self, pos):
        m = QMenu(self)
        a = m.addAction
        c = self.editor.cursorForPosition(pos)
        origc = QTextCursor(c)
        current_cursor = self.editor.textCursor()
        r = origr = self.editor.syntax_range_for_cursor(c)
        if (
                r is None or not r.format.property(SPELL_PROPERTY)
        ) and c.positionInBlock() > 0 and not current_cursor.hasSelection():
            c.setPosition(c.position() - 1)
            r = self.editor.syntax_range_for_cursor(c)

        if r is not None and r.format.property(SPELL_PROPERTY):
            word = self.editor.text_for_range(c.block(), r)
            locale = self.editor.spellcheck_locale_for_cursor(c)
            orig_pos = c.position()
            c.setPosition(orig_pos - utf16_length(word))
            found = False
            self.editor.setTextCursor(c)
            if self.editor.find_spell_word([word],
                                           locale.langcode,
                                           center_on_cursor=False):
                found = True
                fc = self.editor.textCursor()
                if fc.position() < c.position():
                    self.editor.find_spell_word([word],
                                                locale.langcode,
                                                center_on_cursor=False)
            spell_cursor = self.editor.textCursor()
            if current_cursor.hasSelection():
                # Restore the current cursor so that any selection is preserved
                # for the change case actions
                self.editor.setTextCursor(current_cursor)
            if found:
                suggestions = dictionaries.suggestions(word, locale)[:7]
                if suggestions:
                    for suggestion in suggestions:
                        ac = m.addAction(
                            suggestion,
                            partial(self.editor.simple_replace,
                                    suggestion,
                                    cursor=spell_cursor))
                        f = ac.font()
                        f.setBold(True), ac.setFont(f)
                    m.addSeparator()
                m.addAction(actions['spell-next'])
                m.addAction(_('Ignore this word'),
                            partial(self._nuke_word, None, word, locale))
                dics = dictionaries.active_user_dictionaries
                if len(dics) > 0:
                    if len(dics) == 1:
                        m.addAction(
                            _('Add this word to the dictionary: {0}').format(
                                dics[0].name),
                            partial(self._nuke_word, dics[0].name, word,
                                    locale))
                    else:
                        ac = m.addAction(_('Add this word to the dictionary'))
                        dmenu = QMenu(m)
                        ac.setMenu(dmenu)
                        for dic in dics:
                            dmenu.addAction(
                                dic.name,
                                partial(self._nuke_word, dic.name, word,
                                        locale))
                m.addSeparator()

        if origr is not None and origr.format.property(LINK_PROPERTY):
            href = self.editor.text_for_range(origc.block(), origr)
            m.addAction(
                _('Open %s') % href, partial(self.link_clicked.emit, href))

        if origr is not None and (origr.format.property(TAG_NAME_PROPERTY)
                                  or origr.format.property(CSS_PROPERTY)):
            word = self.editor.text_for_range(origc.block(), origr)
            item_type = 'tag_name' if origr.format.property(
                TAG_NAME_PROPERTY) else 'css_property'
            url = help_url(word,
                           item_type,
                           self.editor.highlighter.doc_name,
                           extra_data=current_container().opf_version)
            if url is not None:
                m.addAction(
                    _('Show help for: %s') % word, partial(open_url, url))

        for x in ('undo', 'redo'):
            ac = actions['editor-%s' % x]
            if ac.isEnabled():
                a(ac)
        m.addSeparator()
        for x in ('cut', 'copy', 'paste'):
            ac = actions['editor-' + x]
            if ac.isEnabled():
                a(ac)
        m.addSeparator()
        m.addAction(_('&Select all'), self.editor.select_all)
        if self.selected_text or self.has_marked_text:
            update_mark_text_action(self)
            m.addAction(actions['mark-selected-text'])
        if self.syntax != 'css' and actions['editor-cut'].isEnabled():
            cm = QMenu(_('Change &case'), m)
            for ac in 'upper lower swap title capitalize'.split():
                cm.addAction(actions['transform-case-' + ac])
            m.addMenu(cm)
        if self.syntax == 'html':
            m.addAction(actions['multisplit'])
        m.exec_(self.editor.viewport().mapToGlobal(pos))

    def goto_sourceline(self, *args, **kwargs):
        return self.editor.goto_sourceline(*args, **kwargs)

    def goto_css_rule(self, *args, **kwargs):
        return self.editor.goto_css_rule(*args, **kwargs)

    def get_tag_contents(self, *args, **kwargs):
        return self.editor.get_tag_contents(*args, **kwargs)

    def _nuke_word(self, dic, word, locale):
        if dic is None:
            dictionaries.ignore_word(word, locale)
        else:
            dictionaries.add_to_user_dictionary(dic, word, locale)
        self.word_ignored.emit(word, locale)
コード例 #23
0
ファイル: widget.py プロジェクト: kmshi/calibre
class Editor(QMainWindow):

    has_line_numbers = True

    modification_state_changed = pyqtSignal(object)
    undo_redo_state_changed = pyqtSignal(object, object)
    copy_available_state_changed = pyqtSignal(object)
    data_changed = pyqtSignal(object)
    cursor_position_changed = pyqtSignal()

    def __init__(self, syntax, parent=None):
        QMainWindow.__init__(self, parent)
        if parent is None:
            self.setWindowFlags(Qt.Widget)
        self.is_synced_to_container = False
        self.syntax = syntax
        self.editor = TextEdit(self)
        self.setCentralWidget(self.editor)
        self.create_toolbars()
        self.undo_available = False
        self.redo_available = False
        self.copy_available = self.cut_available = False
        self.editor.modificationChanged.connect(self._modification_state_changed)
        self.editor.undoAvailable.connect(self._undo_available)
        self.editor.redoAvailable.connect(self._redo_available)
        self.editor.textChanged.connect(self._data_changed)
        self.editor.copyAvailable.connect(self._copy_available)
        self.editor.cursorPositionChanged.connect(self._cursor_position_changed)

    @dynamic_property
    def current_line(self):
        def fget(self):
            return self.editor.textCursor().blockNumber()
        def fset(self, val):
            self.editor.go_to_line(val)
        return property(fget=fget, fset=fset)

    @property
    def number_of_lines(self):
        return self.editor.blockCount()

    @dynamic_property
    def data(self):
        def fget(self):
            ans = self.get_raw_data()
            return ans.encode('utf-8')
        def fset(self, val):
            self.editor.load_text(val, syntax=self.syntax)
        return property(fget=fget, fset=fset)

    def init_from_template(self, template):
        self.editor.load_text(template, syntax=self.syntax, process_template=True)

    def get_raw_data(self):
        return unicodedata.normalize('NFC', unicode(self.editor.toPlainText()))

    def replace_data(self, raw, only_if_different=True):
        if isinstance(raw, bytes):
            raw = raw.decode('utf-8')
        current = self.get_raw_data() if only_if_different else False
        if current != raw:
            self.editor.replace_text(raw)

    def apply_settings(self, prefs=None):
        self.editor.apply_settings(prefs=None)

    def set_focus(self):
        self.editor.setFocus(Qt.OtherFocusReason)

    def undo(self):
        self.editor.undo()

    def redo(self):
        self.editor.redo()

    @property
    def selected_text(self):
        return self.editor.selected_text

    # Search and replace {{{
    def mark_selected_text(self):
        self.editor.mark_selected_text()

    def find(self, *args, **kwargs):
        return self.editor.find(*args, **kwargs)

    def replace(self, *args, **kwargs):
        return self.editor.replace(*args, **kwargs)

    def all_in_marked(self, *args, **kwargs):
        return self.editor.all_in_marked(*args, **kwargs)

    def go_to_anchor(self, *args, **kwargs):
        return self.editor.go_to_anchor(*args, **kwargs)
    # }}}

    @property
    def has_marked_text(self):
        return self.editor.current_search_mark is not None

    @dynamic_property
    def is_modified(self):
        def fget(self):
            return self.editor.is_modified
        def fset(self, val):
            self.editor.is_modified = val
        return property(fget=fget, fset=fset)

    def create_toolbars(self):
        self.action_bar = b = self.addToolBar(_('File actions tool bar'))
        b.setObjectName('action_bar')  # Needed for saveState
        for x in ('undo', 'redo'):
            b.addAction(actions['editor-%s' % x])
        self.edit_bar = b = self.addToolBar(_('Edit actions tool bar'))
        for x in ('cut', 'copy', 'paste'):
            b.addAction(actions['editor-%s' % x])
        self.tools_bar = b = self.addToolBar(_('Editor tools'))
        if self.syntax == 'html':
            b.addAction(actions['fix-html-current'])
        if self.syntax in {'xml', 'html', 'css'}:
            b.addAction(actions['pretty-current'])

    def break_cycles(self):
        self.modification_state_changed.disconnect()
        self.undo_redo_state_changed.disconnect()
        self.copy_available_state_changed.disconnect()
        self.cursor_position_changed.disconnect()
        self.data_changed.disconnect()
        self.editor.undoAvailable.disconnect()
        self.editor.redoAvailable.disconnect()
        self.editor.modificationChanged.disconnect()
        self.editor.textChanged.disconnect()
        self.editor.copyAvailable.disconnect()
        self.editor.cursorPositionChanged.disconnect()
        self.editor.setPlainText('')

    def _modification_state_changed(self):
        self.is_synced_to_container = self.is_modified
        self.modification_state_changed.emit(self.is_modified)

    def _data_changed(self):
        self.is_synced_to_container = False
        self.data_changed.emit(self)

    def _undo_available(self, available):
        self.undo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _redo_available(self, available):
        self.redo_available = available
        self.undo_redo_state_changed.emit(self.undo_available, self.redo_available)

    def _copy_available(self, available):
        self.copy_available = self.cut_available = available
        self.copy_available_state_changed.emit(available)

    def _cursor_position_changed(self, *args):
        self.cursor_position_changed.emit()

    @property
    def cursor_position(self):
        c = self.editor.textCursor()
        return (c.blockNumber() + 1, c.positionInBlock())

    def cut(self):
        self.editor.cut()

    def copy(self):
        self.editor.copy()

    def go_to_line(self, line, col=None):
        self.editor.go_to_line(line, col=col)

    def paste(self):
        if not self.editor.canPaste():
            return error_dialog(self, _('No text'), _(
                'There is no suitable text in the clipboard to paste.'), show=True)
        self.editor.paste()

    def contextMenuEvent(self, ev):
        ev.ignore()

    def fix_html(self):
        if self.syntax == 'html':
            from calibre.ebooks.oeb.polish.pretty import fix_html
            self.editor.replace_text(fix_html(current_container(), unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False

    def pretty_print(self, name):
        from calibre.ebooks.oeb.polish.pretty import pretty_html, pretty_css, pretty_xml
        if self.syntax in {'css', 'html', 'xml'}:
            func = {'css':pretty_css, 'xml':pretty_xml}.get(self.syntax, pretty_html)
            self.editor.replace_text(func(current_container(), name, unicode(self.editor.toPlainText())).decode('utf-8'))
            return True
        return False