![]() |
#406 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
I've played with gui.tags_view.context_menu_handler(action='hide', category='#genre') and it seems to working. Is there something that makes calling this non desirable? |
|
![]() |
![]() |
![]() |
#407 | |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
If you want to play with it then this might work. It uses "recount()' which is what calibre uses when the tag browser data has changed. Code:
tag_browser = gui.tags_view cats = tag_browser.hidden_categories # modify cats as needed, adding or removing categories. It is a set() self.db.new_api.set_pref('tag_browser_hidden_categories', list(cats)) tag_browser.recount() Code:
change_hidden_categories(operation, category_set) Last edited by chaley; 03-18-2021 at 10:02 AM. |
|
![]() |
![]() |
Advert | |
|
![]() |
#408 |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
This custom action seems to be working even when the tag browser has no focus and also when it is hidden
Code:
# python3 compatibility from six.moves import range from six import text_type as unicode from PyQt5.Qt import (QApplication, Qt, QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QTableWidget, QComboBox, QToolButton, QIcon, QSpacerItem, QSizePolicy, QAbstractItemView) from calibre import prints from calibre.constants import DEBUG, numeric_version from calibre.gui2 import question_dialog from calibre_plugins.action_chains.actions.base import ChainAction class CategoryComboBox(QComboBox): def __init__(self, parent, categories, selected_text=None): QComboBox.__init__(self, parent) self.populate_combo(categories, selected_text) def populate_combo(self, categories, selected_text=None): self.blockSignals(True) self.clear() for category in categories: self.addItem(category) self.select_cateogry(selected_text) def select_cateogry(self, selected_text): self.blockSignals(True) if selected_text: idx = self.findText(selected_text) if idx == -1: self.addItem(selected_text) idx = self.findText(selected_text) self.setCurrentIndex(idx) else: self.setCurrentIndex(-1) self.blockSignals(False) class CategoriesTable(QTableWidget): def __init__(self, plugin_action, data_items=[]): QTableWidget.__init__(self) self.plugin_action = plugin_action self.db = self.plugin_action.gui.current_db tag_browser = self.plugin_action.gui.tags_view self.all_categories = [ category.category_key for category \ in tag_browser._model.category_nodes if category.category_key.find('.') == -1 ] self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOn) self.setAlternatingRowColors(True) self.populate_table(data_items) def populate_table(self, data_items): self.clear() self.setRowCount(len(data_items)) header_labels = [_('Category'), _('Operation'), ''] self.setColumnCount(len(header_labels)) self.setHorizontalHeaderLabels(header_labels) self.verticalHeader().setDefaultSectionSize(24) for row, data in enumerate(data_items): self.populate_table_row(row, data) self.resizeColumnsToContents() for col in range(self.columnCount()): self.setMinimumColumnWidth(col, 150) self.setSortingEnabled(False) self.setMinimumSize(800, 0) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.selectRow(0) def populate_table_row(self, row, data): self.blockSignals(True) category_combo = CategoryComboBox(self, self.all_categories, data['category_key']) operation_combo = CategoryComboBox(self, ['toggle','show','hide'], data['operation']) self.setCellWidget(row, 0, category_combo) self.setCellWidget(row, 1, operation_combo) self.blockSignals(False) def setMinimumColumnWidth(self, col, minimum): if self.columnWidth(col) < minimum: self.setColumnWidth(col, minimum) def add_data(self, data_items, append=False): if not append: self.setRowCount(0) for data in reversed(data_items): row = self.currentRow() + 1 self.insertRow(row) self.populate_table_row(row, data) def get_data(self): data_items = [] for row in range(self.rowCount()): data_item = self.convert_row_to_data(row) # filter out empty or incomplete rows if not (data_item['category_key'] and data_item['operation']): continue data_items.append(data_item) return data_items def convert_row_to_data(self, row): data = {} category_combo = self.cellWidget(row, 0) data['category_key'] = unicode(category_combo.currentText()).strip() operation_combo = self.cellWidget(row, 1) data['operation'] = unicode(operation_combo.currentText()).strip() return data def create_blank_row_data(self): data = {} data['category_key'] = '' data['operation'] = '' return data def select_and_scroll_to_row(self, row): self.selectRow(row) self.scrollToItem(self.currentItem()) def add_row(self): self.setFocus() # We will insert a blank row below the currently selected row row = self.currentRow() + 1 self.insertRow(row) self.populate_table_row(row, self.create_blank_row_data()) self.select_and_scroll_to_row(row) def delete_rows(self): self.setFocus() rows = self.selectionModel().selectedRows() rows = sorted(rows, key=lambda x: x.row()) if len(rows) == 0: return message = '<p>Are you sure you want to delete this row?' if len(rows) > 1: message = '<p>Are you sure you want to delete the selected %d rows?'%len(rows) if not question_dialog(self, _('Are you sure?'), message, show_copy_button=False): return first_sel_row = self.currentRow() for selrow in reversed(rows): self.removeRow(selrow.row()) if first_sel_row < self.rowCount(): self.select_and_scroll_to_row(first_sel_row) elif self.rowCount() > 0: self.select_and_scroll_to_row(first_sel_row - 1) class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action self.gui = plugin_action.gui self.db = self.gui.current_db self._init_controls() def _init_controls(self): layout = QVBoxLayout() self.setLayout(layout) categories_group_box = QGroupBox(_('Category Operations')) layout.addWidget(categories_group_box) categories_group_box_layout = QHBoxLayout() categories_group_box.setLayout(categories_group_box_layout) self.table = CategoriesTable(self) categories_group_box_layout.addWidget(self.table) # Add a vertical layout containing the the buttons to move up/down etc. button_layout = QVBoxLayout() categories_group_box_layout.addLayout(button_layout) add_button = QToolButton(self) add_button.setToolTip(_('Add row')) add_button.setIcon(QIcon(I('plus.png'))) button_layout.addWidget(add_button) spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) button_layout.addItem(spacerItem1) delete_button = QToolButton(self) delete_button.setToolTip(_('Delete row')) delete_button.setIcon(QIcon(I('minus.png'))) button_layout.addWidget(delete_button) add_button.clicked.connect(self.table.add_row) delete_button.clicked.connect(self.table.delete_rows) layout.addStretch(1) self.setMinimumSize(600,300) def load_settings(self, settings): categories_config = settings.get('categories_config', []) self.table.add_data(categories_config) def save_settings(self): settings = {} settings['categories_config'] = self.table.get_data() return settings class CategoryVisibility(ChainAction): name = 'Category Visibility' def apply_category_operations(self, category_operations): db = self.plugin_action.gui.current_db tag_browser = self.plugin_action.gui.tags_view all_categories = [ category.category_key for category \ in tag_browser._model.category_nodes if category.category_key.find('.') == -1 ] hidden_categories = tag_browser.hidden_categories for category_operation in category_operations: category_key = category_operation['category_key'] operation = category_operation['operation'] if category_key not in all_categories: continue if numeric_version < (5,14,0): is_category_hidden = category_key in hidden_categories if operation == 'toggle': if is_category_hidden: operation = 'show' else: operation = 'hide' tag_browser.context_menu_handler(action=operation, category=category_key) else: gui.tb_category_visibility(category_key, operation) def run(self, gui, settings, chain): category_operations = settings.get('categories_config', []) self.apply_category_operations(category_operations) def validate(self, settings): try: if not settings.get('categories_config'): raise Exception('Empty settings') except: return (_('Settings Error'), _('You must configure this action before running it')) for category_config in settings.get('categories_config'): category_key = category_config.get('category_key') operation = category_config.get('operation') if not (category_key and operation): return (_('Missing values'), _('Empty values not allowed for name or action')) gui = self.plugin_action.gui categories_config = settings['categories_config'] available_category_keys = set([category.category_key for category in gui.tags_view._model.category_nodes]) saved_category_keys = set([x['category_key'] for x in categories_config]) missing_keys = saved_category_keys.difference(available_category_keys) if missing_keys: return (_('Missing Category(s)'), _('Catetory(s) ({}) not available in current library'.format(missing_keys))) return True def config_widget(self): return ConfigWidget Last edited by capink; 03-23-2021 at 01:34 PM. Reason: Updated to include latest changes introduced by chaley |
![]() |
![]() |
![]() |
#409 | |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
Quote:
Add this at line 438 of gui2.tag_browser.ui Code:
def change_tb_category_visibility(self, category, operation): ''' Hide or show categories in the tag browser. 'category' is the lookup key to show or hide. Set operation == 'show' or 'hide' as needed. ''' if category not in self.tags_view.model().categories: raise ValueError(_('change_tb_category_visibility: category %s does not exist') % category) cats = self.tags_view.hidden_categories if operation == 'hide': cats.add(category) elif operation == 'show': cats.discard(category) else: raise ValueError(_('change_tb_category_visibility: invalid operation %s') % operation) self.library_view.model().db.new_api.set_pref('tag_browser_hidden_categories', list(cats)) self.tags_view.recount() Code:
#tag_browser.context_menu_handler(action=visibility, category=category_key) # if visibility == 'show': # hidden_categories.discard(category_key) # else: # hidden_categories.add(category_key) # db.new_api.set_pref('tag_browser_hidden_categories', list(hidden_categories)) # tag_browser.recount() gui.change_tb_category_visibility(category_key, visibility) Code:
gui.change_tb_category_visibility(category_key, visibility) |
|
![]() |
![]() |
![]() |
#410 |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
I tested it and it is working.
![]() |
![]() |
![]() |
Advert | |
|
![]() |
#411 |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
|
![]() |
![]() |
![]() |
#412 |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
The API method is now in source, with a new name and more functionality. Details are:
Code:
def tb_category_visibility(self, category, operation): ''' Hide or show categories in the tag browser. 'category' is the lookup key. Operation can be: - 'show' to show the category in the tag browser - 'hide' to hide the category - 'toggle' to invert its visibility - 'is_visible' returns True if the category is currently visible, False otherwise ''' |
![]() |
![]() |
![]() |
#413 |
Custom User Title
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 11,007
Karma: 75555555
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
|
Thank you!
|
![]() |
![]() |
![]() |
#414 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
![]() |
|
![]() |
![]() |
![]() |
#415 |
Custom User Title
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 11,007
Karma: 75555555
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
|
|
![]() |
![]() |
![]() |
#416 |
Custom User Title
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 11,007
Karma: 75555555
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
|
This might've been asked before, but is there a way in Action Chains to "add file to selected book records" with a specified filepath? I keep a dummy.paperbook file in a folder for, well, paper books.
This is a bit of a corner case though and probably not practical to add something specifically for it. |
![]() |
![]() |
![]() |
#417 | |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
Quote:
Code:
from __future__ import (unicode_literals, division, absolute_import, print_function) import subprocess import os from PyQt5.Qt import (QApplication, Qt, QWidget, QHBoxLayout, QVBoxLayout, QGroupBox, QToolButton) from calibre import prints from calibre.constants import DEBUG, iswindows from calibre.gui2 import error_dialog, choose_files from calibre_plugins.action_chains.actions.base import ChainAction from calibre_plugins.action_chains.common_utils import DragDropComboBox, get_icon class ConfigWidget(QWidget): def __init__(self, plugin_action): QWidget.__init__(self) self.plugin_action = plugin_action self.gui = plugin_action.gui self.db = self.gui.current_db self.template = '' self._init_controls() def _init_controls(self): l = QVBoxLayout() self.setLayout(l) self.format_box = QGroupBox(_('&Choose format:')) l.addWidget(self.format_box) format_layout = QVBoxLayout() self.format_box.setLayout(format_layout) self.format_combo = DragDropComboBox(self, drop_mode='file') format_layout.addWidget(self.format_combo) hl1 = QHBoxLayout() format_layout.addLayout(hl1) hl1.addWidget(self.format_combo, 1) self.choose_format_button = QToolButton(self) self.choose_format_button.setToolTip(_('Choose format')) self.choose_format_button.setIcon(get_icon('document_open.png')) self.choose_format_button.clicked.connect(self._choose_file) hl1.addWidget(self.choose_format_button) def _choose_file(self): files = choose_files(None, _('Select format dialog'), _('Select a format'), all_files=True, select_only_single_file=True) if not files: return format_path = files[0] if iswindows: format_path = os.path.normpath(format_path) self.block_events = True existing_index = self.format_combo.findText(format_path, Qt.MatchExactly) if existing_index >= 0: self.format_combo.setCurrentIndex(existing_index) else: self.format_combo.insertItem(0, format_path) self.format_combo.setCurrentIndex(0) self.block_events = False def load_settings(self, settings): if settings: self.format_combo.setCurrentText(settings['path_to_format']) def save_settings(self): settings = {} settings['path_to_format'] = self.format_combo.currentText().strip() return settings class AddFormatAction(ChainAction): name = 'Add Format' support_scopes = True def run(self, gui, settings, chain): book_ids = chain.scope().get_book_ids() db = gui.current_db path = settings['path_to_format'] try: fmt = os.path.splitext(path)[-1].lower().replace('.', '').upper() except: fmt = '' for book_id in book_ids: db.new_api.add_format(book_id, fmt, path) def validate(self, settings): if not settings: return (_('Settings Error'), _('You must configure this action before running it')) if not settings['path_to_format']: return (_('No Format'), _('You must specify a path to valid format')) return True def config_widget(self): return ConfigWidget Last edited by capink; 07-16-2021 at 03:44 AM. |
|
![]() |
![]() |
![]() |
#418 |
Grand Sorcerer
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 12,447
Karma: 8012886
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
|
@capink: the changes to the template dialog broke action chains. Opening the template editor now silently dies.
The problem is in these lines of code at (I think) line 431 in templates.py: Code:
try: self.gridLayout.setRowStretch(self.getWidgetRow(self.textbox), 2) self.gridLayout.setRowStretch(self.getWidgetRow(self.source_code), 1) except: if DEBUG: prints('Action chains: TemplateBox: Not able to modify stretch') Code:
self.gridLayout.setRowStretch(self.getWidgetRow(self.source_code), 1) I don't know why these lines are there. While making the changes to add more result lines and ability to change the font, I cleaned up the layout of the dialog. In particular, the function reference stuff is now in its own unnamed grid layout put into a cell in the parent layout. My recommendation is that unless there is a compelling reason you should remove the try/except block changing the stretch factor. You shouldn't need it. If you do then I can look at why and see if it is fixable in the TemplateDialog class itself. |
![]() |
![]() |
![]() |
#419 |
Wizard
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 1,196
Karma: 1995558
Join Date: Aug 2015
Device: Kindle
|
They are unnecessary. I was experimenting with modifying stretch values and left them there. I will remove them before the next version of calibre is released.
|
![]() |
![]() |
![]() |
#420 |
Custom User Title
![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() ![]() Posts: 11,007
Karma: 75555555
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
|
Question: Some of my chains have Kobo Utilities actions that disappear when device is not plugged in, so they appear as red errors in the editor. Is it safe to modify that chain without plugging in the device?
|
![]() |
![]() |
![]() |
|
![]() |
||||
Thread | Thread Starter | Forum | Replies | Last Post |
[Editor Plugin] Editor Chains | capink | Plugins | 106 | 06-17-2025 05:36 PM |
Action Chains Resources | capink | Plugins | 77 | 06-16-2025 12:45 PM |
[GUI Plugin] Noosfere_util, a companion plugin to noosfere DB | lrpirlet | Plugins | 2 | 08-18-2022 03:15 PM |
[GUI Plugin] Save Virtual Libraries To Column (GUI) | chaley | Plugins | 14 | 04-04-2021 05:25 AM |