View Single Post
Old 09-24-2021, 04:47 AM   #20
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
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.
capink is offline   Reply With Quote