View Single Post
Old 04-27-2024, 01:00 AM   #85
capink
Wizard
capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.capink ought to be getting tired of karma fortunes by now.
 
Posts: 1,118
Karma: 1954138
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by thiago.eec View Post
2) Call other plugin's action (like running Epubcheck or ACE).
There is a custom action you can add that would do what you want. Go to Editor Chains > Manage Modules > Create > Copy/Paste the following:

Code:
#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~
from __future__ import (unicode_literals, division, absolute_import,
                        print_function)

import re

# python 3 compatibility
from six import text_type as unicode

from PyQt5.Qt import (QApplication, Qt, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout,
                      QDialog, QDialogButtonBox, QMenu, QPainter, QPoint, QPixmap,
                      QSize, QIcon, QTreeWidgetItem, QTreeWidget, QAbstractItemView,
                      QGroupBox, QCheckBox, QLabel, QAction)

from calibre import prints
from calibre.constants import DEBUG
from calibre.gui2 import error_dialog

from calibre_plugins.editor_chains.actions.base import EditorAction
from calibre_plugins.editor_chains.common_utils import get_icon

try:
    load_translations()
except NameError:
    prints("EditorChains::actions/editor_actions.py - exception when loading translations")


EXCLUDED_GROUPS = [
    'Editor Chains',
    'Editor actions',
    'Windows',
    'Preview',
    'Search'
]

ICON_SIZE = 32


def group_unique_names(gui, group):
    menu_actions = [x for x in gui.__dict__.values() if isinstance(x, QAction)]
    menu_unique_names = [re.sub(r'^action\-', r'', x.objectName()) for x in menu_actions]
    if group == 'Plugins':
        unique_names = [x for x in gui.keyboard.groups['Plugins']]
    else:
        unique_names = [x for x in gui.keyboard.groups[group] if x in menu_unique_names]
    
    return unique_names

def get_qaction_from_unique_name(gui, unique_name):
    shortcut = gui.keyboard.shortcuts[unique_name]
    ac = shortcut['action']
    return ac


def allowed_unique_names(gui):
    unique_names = []
    for group in gui.keyboard.groups.keys():
        unique_names.extend(group_unique_names(gui, group))
    unique_names += [x for x in gui.keyboard.groups['Plugins']]
    return unique_names

class Item(QTreeWidgetItem):
    pass

class ConfigWidget(QWidget):
    def __init__(self, plugin_action):
        QWidget.__init__(self)
        self.plugin_action = plugin_action
        self.gui = plugin_action.gui
        self.all_nodes = {}
        # used to keep track of selected item and allow only one selection
        self.checked_item = None
        
        self._init_controls()

    def _init_controls(self):
        self.blank_icon = QIcon(I('blank.png'))
        l = self.l = QVBoxLayout()
        self.setLayout(l)

#        opts_groupbox = QGroupBox()
#        l.addWidget(opts_groupbox)
#        opts_groupbox_layout = QVBoxLayout()
#        opts_groupbox.setLayout(opts_groupbox_layout)
#        cursor_chk = self.cursor_chk = QCheckBox(_('Disable wait cursor for this action.'))
#        opts_groupbox_layout.addWidget(cursor_chk)
#        cursor_chk.setChecked(False)
        
        self.tv = QTreeWidget(self.gui)
        self.tv.setIconSize(QSize(ICON_SIZE, ICON_SIZE))
        self.tv.header().hide()
        self.tv.setSelectionMode(QAbstractItemView.SingleSelection)
        l.addWidget(self.tv, 1)
        self.tv.itemChanged.connect(self._tree_item_changed)

        self._populate_actions_tree()

    def _get_scaled_icon(self, icon):
        if icon.isNull():
            return self.blank_icon
        # We need the icon scaled to 16x16
        src = icon.pixmap(ICON_SIZE, ICON_SIZE)
        if src.width() == ICON_SIZE and src.height() == ICON_SIZE:
            return icon
        # Need a new version of the icon
        pm = QPixmap(ICON_SIZE, ICON_SIZE)
        pm.fill(Qt.transparent)
        p = QPainter(pm)
        p.drawPixmap(QPoint(int((ICON_SIZE - src.width()) / 2), int((ICON_SIZE - src.height()) / 2)), src)
        p.end()
        return QIcon(pm)

    def set_checked_state(self, item, col, state):
        item.setCheckState(col, state)
        if state == Qt.Checked:
            self.checked_item = item
        else:
            self.checked_item = None

    def _populate_actions_tree(self):

        self.top_level_items_map = {}
        for group in sorted(self.gui.keyboard.groups.keys()):
            if group in EXCLUDED_GROUPS: continue
            
            # Create a node for our top level plugin name
            tl = Item()
            tl.setText(0, group)
          
            # Normal top-level checkable plugin iaction handling
            tl.setFlags(Qt.ItemIsEnabled)
            
            #unique_names = self.gui.keyboard.groups[group]
            unique_names = group_unique_names(self.gui, group)
            
            # Iterate through all the children of this node
            self._populate_action_children(unique_names, tl)
            self.tv.addTopLevelItem(tl)

    def _populate_action_children(self, unique_names, parent):
        for unique_name in unique_names:
            ac = get_qaction_from_unique_name(self.gui, unique_name)
            text = ac.text().replace('&', '')

            it = Item(parent)
            it.setText(0, text)
            it.setData(0, Qt.UserRole, unique_name)
            it.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
            it.setCheckState(0, Qt.Unchecked)
            it.setIcon(0, self._get_scaled_icon(ac.icon()))

            self.all_nodes[unique_name] = it

    def _tree_item_changed(self, item, column):
        # Checkstate has been changed

        is_checked = item.checkState(column) == Qt.Checked
        #text = unicode(item.text(column)).replace('&', '')
        unique_name = item.data(column, Qt.UserRole)

        if is_checked:
            # un-check previously checked item if any
            if self.checked_item:
                self.tv.blockSignals(True)
                self.checked_item.setCheckState(0, Qt.Unchecked)
                self.tv.blockSignals(False)
            
            self.checked_item = item
        else:
            self.checked_item = None

    def _repopulate_actions_tree(self):
        self.tv.clear()
        self._populate_actions_tree()

    def load_settings(self, settings):
        if settings:
            #self.cursor_chk.setChecked(settings.get('disable_busy_cursor', False))
            #self._repopulate_actions_tree()
            self.set_unique_name_checked(settings['unique_name'])

    def save_settings(self):
        settings = {}
        settings['unique_name'] = self.checked_item.data(0, Qt.UserRole)
        #settings['disable_busy_cursor'] = self.cursor_chk.isChecked()
        return settings

    def set_unique_name_checked(self, unique_name):
        it = self.all_nodes.get(unique_name)
        if it:
            self.set_checked_state(it, 0, Qt.Checked)
            self.tv.setCurrentItem(it)

class EditorActions(EditorAction):

    name = 'Editor Actions'
    #_is_builtin = True

    def run(self, chain, settings):
        
        ac = get_qaction_from_unique_name(chain.gui, settings['unique_name'])
        if ac:
            if DEBUG:
                prints('Editor Chains: Editor Actions: Found action: {}'.format(ac.text().replace('&', ''))) 

            cursor = Qt.WaitCursor
            if settings.get('disable_busy_cursor'):
                cursor = Qt.ArrowCursor
            
            QApplication.setOverrideCursor(cursor)
            try:
                ac.trigger()
                   
            finally:
                QApplication.restoreOverrideCursor()
            
        if DEBUG:
            prints('Editor Chains: Editor Actions: Finishing run action') 

    def validate(self, settings):
        gui = self.plugin_action.gui
        if not settings:
            return (_('Settings Error'), _('You must configure this action before running it'))
        if settings['unique_name'] not in allowed_unique_names(gui):
            return (_('Settings Error'), _('Action cannot be found: {}'.format(settings['unique_name'])))
        return True

    def config_widget(self):
        return ConfigWidget
Notes:
  • If any of the plugins/actions you call require user interaction, you'll have to do it. There is no way to automate this.
  • If you are familiar with Action Chains, this action is similar to an action called "Calibre Actions" in that plugin. I did not include this action in Editor Chains by default, nor I have plans to so. This is because in retrospect, including "Calibre Actions" in Actions Chains plugin is the main thing I regret, for several reasons.

Quote:
Originally Posted by thiago.eec View Post
1) Add the 'Check book' action
The action provided above should enable you to call check books, but it will not allow for automatic repair, unless you click the hyperlink to do so.

A more sophisticated version with options and automatic repair without clicking should be possible, I guess. But I am not familiar with that part of calibre code. If you are interested, you can add it as a custom action, through modules as in the action provided above.

Hope that helps.

Edit: Similar to Action Chains, this plugin provides an API for other plugins to provide actions for Editor Chains. A plugin developer needs the following to add actions:
  1. Create a file called editor_chains.py in the root of the plugin folder.
  2. Include all the actions he want in the above file.
  3. Now in Editor Chains, the actions will appear under "Imported From Other Plugins" node.
The file editor_chains/actions/base.py provides instructions on how to write actions. You can also examine any of the actions in the editor_chain/actions for examples. The main method you need to implement is run(). If the you want the action to provide options for the user, also take a look at config_widget().

Last edited by capink; 04-27-2024 at 05:12 AM.
capink is offline   Reply With Quote