Register Guidelines E-Books Today's Posts Search

Go Back   MobileRead Forums > E-Book Software > Calibre > Plugins

Notices

Reply
 
Thread Tools Search this Thread
Old 09-25-2022, 08:38 PM   #31
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,139
Karma: 1954142
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by Comfy.n View Post
Would it be possible to add to this Download Metadata Action some checkboxes for each metadata provider?

I'm thinking it could be useful to have different shortcut keys for quick one-click metadata downloads like, one for Amazon, another for Goodreads, another one for BN... and so on. Or perhaps this could be another action script.
That would be too much work for something I have not interest in.
capink is offline   Reply With Quote
Old 09-25-2022, 08:57 PM   #32
Comfy.n
want to learn what I want
Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.
 
Posts: 1,284
Karma: 6433040
Join Date: Sep 2020
Device: Calibre E-book viewer
Quote:
Originally Posted by capink View Post
That would be too much work for something I have not interest in.
Maybe just one action for Amazon?

I get tired of switching "engines" to get proper metadata, ratings, comments...
Comfy.n is offline   Reply With Quote
Advert
Old 09-26-2022, 05:57 AM   #33
georgemk
Addict
georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.georgemk ought to be getting tired of karma fortunes by now.
 
Posts: 237
Karma: 291844
Join Date: Oct 2019
Device: Kobo Nia
@Comfy.n The Quick Preferences plugin allows for swapping metadata sources for Download Metadata via a menu option. Could an action chain take advantage of that?
georgemk is offline   Reply With Quote
Old 09-26-2022, 06:43 AM   #34
Comfy.n
want to learn what I want
Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.
 
Posts: 1,284
Karma: 6433040
Join Date: Sep 2020
Device: Calibre E-book viewer
Quote:
Originally Posted by georgemk View Post
@Comfy.n The Quick Preferences plugin allows for swapping metadata sources for Download Metadata via a menu option. Could an action chain take advantage of that?
Damn. That's exactly what I was looking for, thank you so much.
Now I'm able to take full benefit of the one-click action Capink provided.

Comfy.n is offline   Reply With Quote
Old 09-27-2022, 02:57 PM   #35
tamaracks
Connoisseur
tamaracks began at the beginning.
 
tamaracks's Avatar
 
Posts: 53
Karma: 10
Join Date: Jun 2021
Device: Onyx Boox Nova3
I'm trying to use this, but for some reason, after reviewing all the changes, it only applies changes to the last book in the list I have selected. Any clues?

Quote:
Originally Posted by capink View Post
Code:
#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~

__license__ = 'GPL v3'
__copyright__ = '2022, Ahmed Zaki <azaki00.dev@gmail.com>'
__docformat__ = 'restructuredtext en'

from qt.core import (QApplication, Qt, QWidget, QVBoxLayout, QCheckBox,
                     QGroupBox, QRadioButton)

import copy
import types
from functools import partial


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

from calibre.ptempfile import PersistentTemporaryFile
from calibre.gui2.metadata.bulk_download import Job, download
from calibre.gui2.actions.edit_metadata import EditMetadataAction
from polyglot.builtins import iteritems

from calibre_plugins.action_chains.actions.base import ChainAction
from calibre_plugins.action_chains.common_utils import responsive_wait, responsive_wait_until

def unfinished_job_ids(gui):
    return set([job.id for job in gui.job_manager.unfinished_jobs()])



class ModifiedEditMetadataAction(EditMetadataAction):
    def __init__(self, gui):
        self.gui = gui

    def metadata_downloaded(self, job):
        if job.failed:
            self.gui.job_exception(job, dialog_title=_('Failed to download metadata'))
            return
        from calibre.gui2.metadata.bulk_download import get_job_details
        (aborted, id_map, tdir, log_file, failed_ids, failed_covers, all_failed,
                det_msg, lm_map) = get_job_details(job)
        if aborted:
            return self.cleanup_bulk_download(tdir)
        if all_failed:
            num = len(failed_ids | failed_covers)
            self.cleanup_bulk_download(tdir)
            return error_dialog(self.gui, _('Download failed'), ngettext(
                'Failed to download metadata or cover for the selected book.',
                'Failed to download metadata or covers for any of the {} books.', num
            ).format(num), det_msg=det_msg, show=True)

        self.gui.status_bar.show_message(_('Metadata download completed'), 3000)

        msg = '<p>' + ngettext(
            'Finished downloading metadata for the selected book.',
            'Finished downloading metadata for <b>{} books</b>.', len(id_map)).format(len(id_map)) + ' ' + \
            _('Proceed with updating the metadata in your library?')

        show_copy_button = False
        checkbox_msg = None
        if failed_ids or failed_covers:
            show_copy_button = True
            num = len(failed_ids.union(failed_covers))
            msg += '<p>'+_('Could not download metadata and/or covers for %d of the books. Click'
                    ' "Show details" to see which books.')%num
            checkbox_msg = _('Show the &failed books in the main book list '
                    'after updating metadata')

        if getattr(job, 'metadata_and_covers', None) == (False, True):
            # Only covers, remove failed cover downloads from id_map
            for book_id in failed_covers:
                if hasattr(id_map, 'discard'):
                    id_map.discard(book_id)
        payload = (id_map, tdir, log_file, lm_map,
                failed_ids.union(failed_covers))

        if self.do_review:
            self.apply_downloaded_metadata(True, payload, self.restrict_to_failed)
        else:
            self.apply_downloaded_metadata(False, payload, self.restrict_to_failed)


    def apply_metadata_changes(self, id_map, title=None, msg='', callback=None,
            merge_tags=True, merge_comments=False, icon=None):
        '''
        Apply the metadata changes in id_map to the database synchronously
        id_map must be a mapping of ids to Metadata objects. Set any fields you
        do not want updated in the Metadata object to null. An easy way to do
        that is to create a metadata object as Metadata(_('Unknown')) and then
        only set the fields you want changed on this object.

        callback can be either None or a function accepting a single argument,
        in which case it is called after applying is complete with the list of
        changed ids.

        id_map can also be a mapping of ids to 2-tuple's where each 2-tuple
        contains the absolute paths to an OPF and cover file respectively. If
        either of the paths is None, then the corresponding metadata is not
        updated.
        '''
        if title is None:
            title = _('Applying changed metadata')
        self.apply_id_map = list(iteritems(id_map))
        self.apply_current_idx = 0
        self.apply_failures = []
        self.applied_ids = set()
        self.apply_pd = None
        self.apply_callback = callback
        #if len(self.apply_id_map) > 1:
            #from calibre.gui2.dialogs.progress import ProgressDialog
            #self.apply_pd = ProgressDialog(title, msg, min=0,
                    #max=len(self.apply_id_map)-1, parent=self.gui,
                    #cancelable=False, icon=icon)
            #self.apply_pd.setModal(True)
            #self.apply_pd.show()
        self._am_merge_tags = merge_tags
        self._am_merge_comments = merge_comments
        self.do_one_apply()

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):

        l = self.l = QVBoxLayout()
        self.setLayout(l)

        opt_gb = QGroupBox(_('Options'))
        opt_gb_l = QVBoxLayout()
        opt_gb.setLayout(opt_gb_l)
        l.addWidget(opt_gb)

        self.metadata_opt = QRadioButton(_('Download Metadata'))
        self.covers_opt = QRadioButton(_('Download Covers'))
        self.both_opt = QRadioButton(_('Download Both'))
        self.both_opt.setChecked(True)

        opt_gb_l.addWidget(self.metadata_opt)
        opt_gb_l.addWidget(self.covers_opt)
        opt_gb_l.addWidget(self.both_opt)

        self.review_chk = QCheckBox(_('Review downloaded metadata before applying them'))
        self.wait_chk = QCheckBox(_('Wait for metadata download jobs to finish'))
        self.wait_chk.setToolTip(_('Check this if this action in not the last action in the chain.'))
        l.addWidget(self.review_chk)
        l.addWidget(self.wait_chk)

        l.addStretch(1)

        self.setMinimumSize(500,300)

    def load_settings(self, settings):
        if settings:
            self.metadata_opt.setChecked(settings.get('download_metadata', False))
            self.covers_opt.setChecked(settings.get('download_covers', False))
            self.both_opt.setChecked(settings.get('download_both', True))
            self.review_chk.setChecked(settings.get('review', True))
            self.wait_chk.setChecked(settings.get('wait_jobs', False))

    def save_settings(self):
        settings = {}
        settings['download_metadata'] = self.metadata_opt.isChecked()
        settings['download_covers'] = self.covers_opt.isChecked()
        settings['download_both'] = self.both_opt.isChecked()
        settings['review'] = self.review_chk.isChecked()
        settings['wait_jobs'] = self.wait_chk.isChecked()
        return settings


class DownloadMetadata(ChainAction):

    name = 'Download Metadata'
    support_scopes = True

    def run(self, gui, settings, chain):
        identify = settings.get('download_metadata') or settings.get('download_both', True)
        covers = settings.get('download_covers') or settings.get('download_both', True)
        wait_jobs = settings.get('wait_jobs', False)
        ensure_fields = None

        edit_metadata = ModifiedEditMetadataAction(gui)
        edit_metadata.do_review = settings.get('review', True)
        edit_metadata.restrict_to_failed = settings.get('restrict_to_failed', True)
        callback = Dispatcher(edit_metadata.metadata_downloaded)

        ids = chain.scope().get_book_ids()
        if len(ids) == 0:
            return error_dialog(gui, _('Cannot download metadata'),
                        _('No books selected'), show=True)

        jobs_before_ids = unfinished_job_ids(gui)

        tf = PersistentTemporaryFile('_metadata_bulk.log')
        tf.close()
        job = Job('metadata bulk download',
            ngettext(
                'Download metadata for one book',
                'Download metadata for {} books', len(ids)).format(len(ids)),
            download, (ids, tf.name, gui.current_db, identify, covers,
                ensure_fields), {}, callback)
        job.metadata_and_covers = (identify, covers)
        job.download_debug_log = tf.name
        gui.job_manager.run_threaded_job(job)
        gui.status_bar.show_message(_('Metadata download started'), 3000)

        if wait_jobs:
            # wait for jobs spawned by action to kick in
            responsive_wait(1)
            # save ids of jobs started after running the action
            ids_jobs_by_action = unfinished_job_ids(gui).difference(jobs_before_ids)

            # wait for jobs to finish
            responsive_wait_until(lambda: ids_jobs_by_action.intersection(unfinished_job_ids(gui)) == set())

    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
tamaracks is offline   Reply With Quote
Advert
Old 09-27-2022, 03:05 PM   #36
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,139
Karma: 1954142
Join Date: Aug 2015
Device: Kindle
I've had a PM reporting a similar problem. I don't have the time right now to debug this issue. Will revisit in the future.
capink is offline   Reply With Quote
Old 09-29-2022, 05:53 PM   #37
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,139
Karma: 1954142
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by tamaracks View Post
I'm trying to use this, but for some reason, after reviewing all the changes, it only applies changes to the last book in the list I have selected. Any clues?
I updated the action with a fix. I tested a couple of times and the fix seems to work.
capink is offline   Reply With Quote
Old 09-29-2022, 07:17 PM   #38
Comfy.n
want to learn what I want
Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.
 
Posts: 1,284
Karma: 6433040
Join Date: Sep 2020
Device: Calibre E-book viewer
Quote:
Originally Posted by capink View Post
I updated the action with a fix. I tested a couple of times and the fix seems to work.
thanks so much for the update, I just did some tests and it is applying the changes to all books selected
Comfy.n is offline   Reply With Quote
Old 09-29-2022, 08:52 PM   #39
Comfy.n
want to learn what I want
Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.Comfy.n ought to be getting tired of karma fortunes by now.
 
Posts: 1,284
Karma: 6433040
Join Date: Sep 2020
Device: Calibre E-book viewer
I just noticed that, when no meta is available for some titles in the job, action will mark those.

That way, one is able to visualize instantly what books should need extra meta queries.
Comfy.n is offline   Reply With Quote
Old 09-29-2022, 11:02 PM   #40
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,139
Karma: 1954142
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by Comfy.n View Post
I just noticed that, when no meta is available for some titles in the job, action will mark those.

That way, one is able to visualize instantly what books should need extra meta queries.
That is the default behavior of the underlying calibre action. I have not noticed this when testing because I have not come across similar case.

This can cause problems if this action is followed by other actions in the same chain, as it changes the view and the selected books on which the later actions in the chain are going to act on.

To solve it, this behavior will be changed in future iterations of this action. i.e. marking books will be optional, and displaying them will be left to the user. (which can be achieved by adding a Selection Modifier action at the end of the chain)

Last edited by capink; 09-29-2022 at 11:04 PM.
capink is offline   Reply With Quote
Old 01-26-2023, 01:17 PM   #41
chaley
Grand Sorcerer
chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.chaley ought to be getting tired of karma fortunes by now.
 
Posts: 12,043
Karma: 7548549
Join Date: Jan 2010
Location: Notts, England
Device: Kobo Libra 2
Action chain to to sum/average arbitrary numeric columns

The attached chain computes the sum, average, and median of numeric and rating columns for selected books.

Usage:
  • Install the chain. Give it a shortcut if you want.
  • Select a cell in a numeric or rating column in the booklist.
  • Extend the selection to include the books you want.
  • Run the action.

Requires calibre 6.7 or later.
Attached Thumbnails
Click image for larger version

Name:	Clipboard01.jpg
Views:	352
Size:	29.1 KB
ID:	199260  
Attached Files
File Type: zip sum_selected_column_action.zip (863 Bytes, 245 views)
chaley is offline   Reply With Quote
Old 03-16-2023, 02:57 PM   #42
rjwse@aol.com
Addict
rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.
 
rjwse@aol.com's Avatar
 
Posts: 304
Karma: 2228060
Join Date: Dec 2013
Location: LaVernia, Texas
Device: kindle epub readers on android
I am hoping for help on creating chains. While in the editor I like to keep open several tabbed dockables, such as File Browser, Saved Searches, Check Book, File Preview, Table of Contents, etc. I was hoping to make a single command that would toggle all of them on and off. I am lost in the procedures of doing this. Best regards, Pop
rjwse@aol.com is offline   Reply With Quote
Old 03-19-2023, 03:17 PM   #43
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,139
Karma: 1954142
Join Date: Aug 2015
Device: Kindle
Quote:
Originally Posted by rjwse@aol.com View Post
I am hoping for help on creating chains. While in the editor I like to keep open several tabbed dockables, such as File Browser, Saved Searches, Check Book, File Preview, Table of Contents, etc. I was hoping to make a single command that would toggle all of them on and off. I am lost in the procedures of doing this. Best regards, Pop
This plugin does not work in the editor, only in calibre main program. There is another plugin that works in the editor called Editor Chains, but it cannot do what you want.
capink is offline   Reply With Quote
Old 03-20-2023, 06:54 AM   #44
rjwse@aol.com
Addict
rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.rjwse@aol.com ought to be getting tired of karma fortunes by now.
 
rjwse@aol.com's Avatar
 
Posts: 304
Karma: 2228060
Join Date: Dec 2013
Location: LaVernia, Texas
Device: kindle epub readers on android
This plugin does not work in the editor, only in calibre main program. There is another plugin that works in the editor called Editor Chains, but it cannot do what you want.
-----------------------
Thanks for the clarification
rjwse@aol.com is offline   Reply With Quote
Old 04-27-2023, 07:27 PM   #45
ownedbycats
Custom User Title
ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.ownedbycats ought to be getting tired of karma fortunes by now.
 
ownedbycats's Avatar
 
Posts: 9,576
Karma: 64960983
Join Date: Oct 2018
Location: Canada
Device: Kobo Libra H2O, formerly Aura HD
Quote:
Originally Posted by capink View Post
Code:
#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~

__license__ = 'GPL v3'
__copyright__ = '2021, Ahmed Zaki <azaki00.dev@gmail.com>'
__docformat__ = 'restructuredtext en'

from qt.core import (QApplication, Qt, QWidget, QGridLayout, QHBoxLayout, QVBoxLayout,
                     QLabel, QGroupBox, QToolButton, QPushButton, QScrollArea, QComboBox,
                     QRadioButton, QCheckBox, QSizePolicy)

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

from calibre_plugins.action_chains.actions.base import ChainAction
from calibre_plugins.action_chains.common_utils import get_icon

try:
    load_translations()
except NameError:
    prints("ActionChains::actions/sort.py - exception when loading translations")
    pass


def get_cols(db):
    standard = [
        'title',
        'authors',
        'tags',
        'series',
        'publisher',
        'pubdate',
        'rating',
        'languages',
        'last_modified',
        'timestamp',
        'comments',
        'author_sort',
        'sort',
        'marked',
        'identifiers',
        'cover',
        'formats'
    ]                
    custom = sorted([ k for k,v in db.field_metadata.custom_field_metadata().items() if v['datatype'] not in [None,'composite'] ])
    return standard + custom

class SortControl(QGroupBox):
    
    def __init__(self, plugin_action, possible_cols):
        self.plugin_action = plugin_action
        self.possible_cols = possible_cols
        self.gui = plugin_action.gui
        self.db = self.gui.current_db
        self._init_controls()

    def _init_controls(self):
        QGroupBox.__init__(self)
        
        l = QGridLayout()
        self.setLayout(l)

        row_idx = 0
        remove_label = QLabel('<a href="close">✕</a>')
        remove_label.setToolTip(_('Remove'))
        remove_label.linkActivated.connect(self._remove)
        l.addWidget(remove_label, row_idx, 1, 1, 1, Qt.AlignRight)
        row_idx += 1

        gb1 = QGroupBox('')
        gb1_l = QVBoxLayout()
        gb1.setLayout(gb1_l)

        gb1_text = _('Column:')
        self.col_combo_box = QComboBox()
        self.col_combo_box.addItems(self.possible_cols)
        self.col_combo_box.setCurrentIndex(-1)
        gb1_l.addWidget(self.col_combo_box)
            
        gb1.setTitle(gb1_text)
        l.addWidget(gb1, row_idx, 0, 1, 1)


        gb2 = QGroupBox(_('Sort direction'), self)
        gb2_l = QVBoxLayout()
        gb2.setLayout(gb2_l)

        self.button_ascend = QRadioButton(_('Ascending'), self)
        gb2_l.addWidget(self.button_ascend)
        self.button_ascend.setChecked(True)
        self.button_descend = QRadioButton(_('Descending'), self)
        gb2_l.addWidget(self.button_descend)
        self.button_descend.setChecked(False)

        l.addWidget(gb2, row_idx, 1, 1, 1)
        row_idx += 1

        l.setColumnStretch(0, 1)        
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum)

    def apply_sort_filter(self, sort_filter):
        field, is_ascending = sort_filter
        self.col_combo_box.setCurrentText(field)
        self.button_ascend.setChecked(is_ascending)
        self.button_descend.setChecked(not is_ascending)

    def _remove(self):
        self.setParent(None)
        self.deleteLater()

    def isComplete(self):
        '''returns True only if a field and direction are chosen'''
        if self.col_combo_box.currentText() == '':
            return False
        return True
    
    def get_sort_filter(self):
        field = self.col_combo_box.currentText()
        is_ascending = self.button_ascend.isChecked()
        return (field, is_ascending)

class SortControlsContainer(QWidget):
    
    def __init__(self, plugin_action, possible_cols):
        self.plugin_action = plugin_action
        self.gui = plugin_action.gui
        self.db = self.gui.current_db
        self.possible_cols = possible_cols
        self._init_controls()

    def _init_controls(self):
        QWidget.__init__(self)
        l = QVBoxLayout()
        self.setLayout(l)
        
        hl1 = QHBoxLayout()
        clear_button = QPushButton(_('Clear'))
        clear_button.setToolTip(_('Clear all filters'))
        clear_button.setIcon(get_icon('clear_left.png'))
        clear_button.clicked.connect(self.reset)
        hl1.addWidget(clear_button)
        hl1.addStretch(1)
        hl1.addStretch(1)
        add_button = QPushButton(_('Add Sort Filter'))
        add_button.setToolTip(_('Add a column to sort by'))
        add_button.setIcon(get_icon('plus.png'))
        add_button.clicked.connect(self.add_control)
        hl1.addWidget(add_button)
        
        l.addLayout(hl1)

        w = QWidget(self)
        self.controls_layout = QVBoxLayout()
        self.controls_layout.setSizeConstraint(self.controls_layout.SetMinAndMaxSize)
        w.setLayout(self.controls_layout)
        
        scroll = QScrollArea()
        scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        scroll.setWidgetResizable(True)
        scroll.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        scroll.setObjectName('myscrollarea')
        scroll.setStyleSheet('#myscrollarea {background-color: transparent}')
        scroll.setWidget(w)
         
        l.addWidget(scroll)
        
        self._add_control(sort_filter={})

    def isComplete(self):
        '''return True if all controls have fields and algorithms set'''
        for idx in range(self.controls_layout.count()):
            control = self.controls_layout.itemAt(idx).widget()
            if not control.isComplete():
                return False
        return True

    def _add_control(self, sort_filter=None):
        control = SortControl(self.plugin_action, self.possible_cols)
        if sort_filter:
            control.apply_sort_filter(sort_filter)
        self.controls_layout.addWidget(control)

    def add_control(self):
        if not self.isComplete():
            error_dialog(
                self,
                _('Incomplete Sort Filter'),
                _('You must complete the previous sort filter(s) to proceed.'),
                show=True
            )
            return
        self._add_control()

    def reset(self, add_empty_control=True):
        # remove controls in reverse order
        for idx in reversed(range(self.controls_layout.count())):
            control = self.controls_layout.itemAt(idx).widget()
            control.setParent(None)
            control.deleteLater()
        if add_empty_control:
            self._add_control()

    def get_sort_filters(self):
        all_filters = []
        for idx in range(self.controls_layout.count()):
            control = self.controls_layout.itemAt(idx).widget()
            all_filters.append(control.get_sort_filter())
        return all_filters

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.possible_cols = get_cols(self.db)
        self._init_controls()

    def _init_controls(self):
        l = QVBoxLayout()
        self.setLayout(l)

        self.container = SortControlsContainer(self.plugin_action, self.possible_cols)
        l.addWidget(self.container, 1)
        
        self.resize(500, 600)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)

    def load_settings(self, settings):
        sort_filters = settings['sort_filters']

        self.container.reset(add_empty_control=False)

        for sort_filter in sort_filters:
            self.container._add_control(sort_filter)

    def save_settings(self):
        settings = {'sort_filters': self.container.get_sort_filters()}
        return settings

class SortAction(ChainAction):

    name = 'Sort by field'
    #_is_builtin = True

    def run(self, gui, settings, chain):
        sort_filters = settings['sort_filters']

        gui.library_view.multisort(sort_filters)

    def validate(self, settings):
        gui = self.plugin_action.gui
        db = gui.current_db
        if not settings:
            return (_('Settings Error'), _('You must configure this action before running it'))
        for sort_filter in settings['sort_filters']:
            field, is_ascending = sort_filter
            if not field:
                return _('No field'), _('You must specify a field for all filters')
            elif not field in get_cols(db):
                return _('Field unavailabe'), _('Current library does not have a field called {}'.format(field))
        return True

    def config_widget(self):
        return ConfigWidget
Note: After one of the recent Calibre updates, 'id' and 'path' are available as standard columns. Adding them to the 'standard' list worked when I tested.

Question: This line prevents composite columns from showing:

Code:
    custom = sorted([ k for k,v in db.field_metadata.custom_field_metadata().items() if v['datatype'] not in [None,'composite'] ])
Removing ,'composite' shows them. But is there a technical reason to avoid this?

Last edited by ownedbycats; 04-27-2023 at 07:37 PM.
ownedbycats is online now   Reply With Quote
Reply


Forum Jump

Similar Threads
Thread Thread Starter Forum Replies Last Post
[GUI Plugin] Action Chains capink Plugins 1410 11-14-2024 01:49 AM
Book Scanning tool chains tomsem Workshop 17 12-03-2023 10:19 AM
Mystery and Crime Thorne, Guy: Chance in Chains (1914); v1 Pulpmeister Kindle Books 0 11-25-2018 10:09 PM
Mystery and Crime Thorne, Guy: Chance in Chains (1914); v1 Pulpmeister ePub Books 0 11-25-2018 10:08 PM
Could this be the last year for the big chains? Connallmac News 66 01-07-2011 05:11 PM


All times are GMT -4. The time now is 06:06 PM.


MobileRead.com is a privately owned, operated and funded community.