Wizard
Posts: 1,118
Karma: 1954138
Join Date: Aug 2015
Device: Kindle
|
Switch to VL view
This action allows the user to assign View Manager views to virtual libraries. When used with "VL Tab Changed" event, it will automatically apply the corresponding view whenever the user switches VL tabs.
Code:
import copy
from functools import partial
from qt.core import (QApplication, Qt, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
QGroupBox, QAbstractTableModel, QModelIndex, QSizePolicy,
QToolButton, QSpacerItem, QIcon, QBrush, pyqtSignal)
from calibre import prints
from calibre.constants import DEBUG
from calibre.gui2 import error_dialog
from calibre.gui2.widgets2 import Dialog
from calibre_plugins.action_chains.actions.base import ChainAction
from calibre_plugins.action_chains.common_utils import get_icon, ViewLog
from calibre_plugins.action_chains.database import get_valid_vls
from calibre_plugins.action_chains.gui.delegates import ComboDelegate
from calibre_plugins.action_chains.gui.models import UP, DOWN
from calibre_plugins.action_chains.gui.views import TableView
import calibre_plugins.action_chains.config as cfg
ALL_BOOKS = '_ALL_BOOKS'
KEY_TABS_VIEWS_TABLE_STATE = 'tabsViewsTableStates'
def get_vls(db):
vls = get_valid_vls(db)
vls.insert(0, ALL_BOOKS)
return vls
def view_manager_views(gui):
try:
import calibre_plugins.view_manager.config as vm_cfg
views = vm_cfg.get_library_config(gui.current_db)[vm_cfg.KEY_VIEWS]
return views
except:
import traceback
print(traceback.format_exc())
return []
class TabsModel(QAbstractTableModel):
error = pyqtSignal(str, str)
def __init__(self, plugin_action, tabs_config=[]):
QAbstractTableModel.__init__(self)
self.tabs_config = tabs_config
self.plugin_action = plugin_action
self.gui = self.plugin_action.gui
self.db = self.gui.current_db
self.col_map = ['tab_name','view_name','errors']
self.editable_columns = ['tab_name','view_name']
#self.hidden_cols = ['errors']
self.hidden_cols = []
self.col_min_width = {
'tab_name': 300,
'view_name': 300
}
all_headers = [_('VL'), _('View'), _('errors')]
self.headers = all_headers
def rowCount(self, parent):
if parent and parent.isValid():
return 0
return len(self.tabs_config)
def columnCount(self, parent):
if parent and parent.isValid():
return 0
return len(self.headers)
def headerData(self, section, orientation, role):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.headers[section]
elif role == Qt.DisplayRole and orientation == Qt.Vertical:
return section + 1
return None
def data(self, index, role):
if not index.isValid():
return None;
row, col = index.row(), index.column()
if row < 0 or row >= len(self.tabs_config):
return None
tab_config = self.tabs_config[row]
col_name = self.col_map[col]
value = tab_config.get(col_name, '')
error = tab_config.get('errors', '')
if role in [Qt.DisplayRole, Qt.UserRole, Qt.EditRole]:
if col_name == 'errors':
if error:
return error
else:
return value
elif role == Qt.DecorationRole:
if col_name == 'errors':
if error:
return QIcon(get_icon('dialog_error.png'))
elif role == Qt.ToolTipRole:
if col_name == 'errors':
if error:
return error
elif role == Qt.ForegroundRole:
color = None
if error:
color = Qt.red
if color is not None:
return QBrush(color)
return None
def setData(self, index, value, role):
done = False
row, col = index.row(), index.column()
tab_config = self.tabs_config[row]
val = str(value).strip()
col_name = self.col_map[col]
if role == Qt.EditRole:
# make sure no duplicate event entries
if col_name == 'tab_name':
old_name = self.data(index, Qt.DisplayRole)
names = self.get_names()
if old_name in names:
names.remove(old_name)
if val in names:
msg = _('Duplicate vls')
details = _('Name ({}) is used in more than one entry'.format(val))
self.error.emit(msg, details)
else:
tab_config[col_name] = val
else:
tab_config[col_name] = val
done = True
return done
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
tab_config = self.tabs_config[index.row()]
col_name = self.col_map[index.column()]
if col_name in self.editable_columns:
flags |= Qt.ItemIsEditable
return flags
def insertRows(self, row, count, idx):
self.beginInsertRows(QModelIndex(), row, row + count - 1)
for i in range(0, count):
tab_config = {}
tab_config['tab_name'] = ''
tab_config['view_name'] = ''
self.tabs_config.insert(row + i, tab_config)
self.endInsertRows()
return True
def removeRows(self, row, count, idx):
self.beginRemoveRows(QModelIndex(), row, row + count - 1)
for i in range(0, count):
self.tabs_config.pop(row + i)
self.endRemoveRows()
return True
def move_rows(self, rows, direction=DOWN):
srows = sorted(rows, reverse=direction == DOWN)
for row in srows:
pop = self.tabs_config.pop(row)
self.tabs_config.insert(row+direction, pop)
self.layoutChanged.emit()
def get_names(self):
names = []
col = self.col_map.index('tab_name')
for row in range(self.rowCount(QModelIndex())):
index = self.index(row, col, QModelIndex())
name = self.data(index, Qt.DisplayRole)
# empty name belong to separators, dont include
if name:
names.append(name)
return names
def validate(self):
for tab_config in self.tabs_config:
errors = []
tab_name = tab_config['tab_name']
view_name = tab_config['view_name']
if tab_name not in get_vls(self.db):
errors.append(_('VL is not available'))
if view_name not in view_manager_views(self.gui):
errors.append(_('View is not available'))
if errors:
tab_config['errors'] = ' ::: '.join(errors)
class TabsTable(TableView):
def __init__(self, parent):
TableView.__init__(self, parent)
self.plugin_action = parent.plugin_action
self.doubleClicked.connect(self._on_double_clicked)
self.gui = self.plugin_action.gui
self.db = self.gui.current_db
self.horizontalHeader().setStretchLastSection(False)
#self.setShowGrid(False)
def set_model(self, _model):
self.setModel(_model)
_model.error.connect(lambda *args: error_dialog(self, *args, show=True))
self.col_map = _model.col_map
# Hide columns
for col_name in _model.hidden_cols:
col = self.col_map.index(col_name)
self.setColumnHidden(col, True)
self.tabs_delegate = ComboDelegate(self, get_vls(self.db))
self.setItemDelegateForColumn(self.col_map.index('tab_name'), self.tabs_delegate)
self.views_delegate = ComboDelegate(self, view_manager_views(self.gui))
self.setItemDelegateForColumn(self.col_map.index('view_name'), self.views_delegate)
self.resizeColumnsToContents()
# Make sure every other column has a minimum width
for col_name, width in _model.col_min_width.items():
col = self.col_map.index(col_name)
self._set_minimum_column_width(col, width)
def _on_double_clicked(self, index):
m = self.model()
col_name = m.col_map[index.column()]
if col_name == 'errors':
tab_config = m.tabs_config[index.row()]
details = tab_config.get('errors', '')
self._view_error_details(details)
def _view_error_details(self, details):
ViewLog(_('Errors details'), details, self)
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.tabs_config = tabs_config
self._init_controls()
def _init_controls(self):
self.setWindowTitle(_('Configure views'))
self.l = l = QVBoxLayout()
self.setLayout(l)
settings_l = QGridLayout()
l.addLayout(settings_l)
_table_gb = QGroupBox(_('Views'))
_table_l = QHBoxLayout()
_table_gb.setLayout(_table_l)
l.addWidget(_table_gb)
self._table = TabsTable(self)
_table_l.addWidget(self._table)
_model = self._model = TabsModel(self.plugin_action)
_model.validate()
self._table.set_model(_model)
self._table.selectionModel().selectionChanged.connect(self._on_table_selection_change)
# restore table state
state = cfg.plugin_prefs.get(KEY_TABS_VIEWS_TABLE_STATE, None)
if state:
self._table.apply_state(state)
# Add a vertical layout containing the the buttons to move up/down etc.
button_layout = QVBoxLayout()
_table_l.addLayout(button_layout)
move_up_button = self.move_up_button = QToolButton(self)
move_up_button.setToolTip(_('Move row up'))
move_up_button.setIcon(QIcon(I('arrow-up.png')))
button_layout.addWidget(move_up_button)
spacerItem1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
button_layout.addItem(spacerItem1)
add_button = self.add_button = QToolButton(self)
add_button.setToolTip(_('Add row'))
add_button.setIcon(QIcon(I('plus.png')))
button_layout.addWidget(add_button)
spacerItem2 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
button_layout.addItem(spacerItem2)
delete_button = self.delete_button = QToolButton(self)
delete_button.setToolTip(_('Delete row'))
delete_button.setIcon(QIcon(I('minus.png')))
button_layout.addWidget(delete_button)
spacerItem4 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
button_layout.addItem(spacerItem4)
move_down_button = self.move_down_button = QToolButton(self)
move_down_button.setToolTip(_('Move row down'))
move_down_button.setIcon(QIcon(I('arrow-down.png')))
button_layout.addWidget(move_down_button)
move_up_button.clicked.connect(partial(self._table.move_rows,UP))
move_down_button.clicked.connect(partial(self._table.move_rows,DOWN))
add_button.clicked.connect(self._table.add_row)
delete_button.clicked.connect(self._table.delete_rows)
self._on_table_selection_change()
self.setMinimumSize(400, 300)
l.addStretch(1)
def _on_table_selection_change(self):
sm = self._table.selectionModel()
selection_count = len(sm.selectedRows())
self.delete_button.setEnabled(selection_count > 0)
self.move_up_button.setEnabled(selection_count > 0)
self.move_down_button.setEnabled(selection_count > 0)
def save_table_state(self):
# save table state
cfg.plugin_prefs[KEY_TABS_VIEWS_TABLE_STATE] = self._table.get_state()
def load_settings(self, settings):
self._model.tabs_config = settings['tabs_config']
self._model.validate()
self._model.layoutChanged.emit()
def save_settings(self):
self.save_table_state()
settings = {}
tabs_config = self._table.model().tabs_config
# remove error keys from event_members
for tab_config in tabs_config:
try:
del tab_config['errors']
except:
pass
settings['tabs_config'] = tabs_config
return settings
class SwitchToVLView(ChainAction):
name = 'Switch To VL View'
def run(self, gui, settings, chain):
idx = gui.vl_tabs.currentIndex()
vl = str(gui.vl_tabs.tabData(idx) or '').strip() or ALL_BOOKS
print('debug1: vl: {}'.format(vl))
if vl:
view = self.vl_view_lookup(vl, settings['tabs_config'])
print('debug2: view: {}'.format(view))
if view:
if not view in view_manager_views(gui):
if DEBUG:
prints('Action Chains: Switch To VL View: view ({}) is not available'.format(view))
return
self.switch_view(gui, view, vl)
else:
if DEBUG:
prints('Action Chains: Switch To VL View: VL Tab ({}) has no configured view'.format(vl))
def vl_view_lookup(self, vl, tabs_config):
for tab_config in tabs_config:
if vl == tab_config['tab_name']:
return tab_config['view_name']
def switch_view(self, gui, view, vl):
view_manager = gui.iactions.get('View Manager')
if view_manager:
import calibre_plugins.view_manager.config as vm_cfg
library_config = vm_cfg.get_library_config(gui.current_db)
view_info = copy.deepcopy(library_config[vm_cfg.KEY_VIEWS][view])
####
view_info[vm_cfg.KEY_APPLY_VIRTLIB] = False
####
selected_ids = gui.library_view.get_selected_ids()
# Persist this as the last selected view
if library_config.get(vm_cfg.KEY_LAST_VIEW, None) != view:
library_config[vm_cfg.KEY_LAST_VIEW] = view
vm_cfg.set_library_config(gui.current_db, library_config)
# if view_info.get(vm_cfg.KEY_APPLY_VIRTLIB,False):
# view_manager.apply_virtlib(view_info[vm_cfg.KEY_VIRTLIB])
if view_info[vm_cfg.KEY_APPLY_RESTRICTION]:
view_manager.apply_restriction(view_info[vm_cfg.KEY_RESTRICTION])
if view_info[vm_cfg.KEY_APPLY_SEARCH]:
view_manager.apply_search(view_info[vm_cfg.KEY_SEARCH])
view_manager.apply_column_and_sort(view_info)
gui.library_view.select_rows(selected_ids)
view_manager.current_view = view
view_manager.rebuild_menus()
else:
if DEBUG:
prints('Action Chains: Switch To VL View: View Manager Plugin not available')
def validate(self, settings):
if not settings:
return (_('Settings Error'), _('You must configure this action before running it'))
return True
def config_widget(self):
return ConfigWidget
Last edited by capink; 01-07-2022 at 06:16 AM.
|