Mobileread
How to use progress dialog?
#1  Phssthpok 08-14-2018, 01:47 PM
I'm trying to create a UI plugin which processes selected books. All is well, but I end up with a loop of the form:
Code
for book in book_list: ... do stuff ...
which does the work successfully enough, but tells me nothing until it finishes.

What I'd like is to have a blocking progress dialog that goes from 0 to len(book_list), updating each time around the loop. The only code sample I can find uses ProgressDialog and seems to involve lots of complication with WorkerThreads and suchlike. Maybe this is the easiest way to do it, maybe not... but can anyone give me some pointers on how to write minimal code to do what I want?
Reply 

#2  jackie_w 08-14-2018, 04:00 PM
I remember going through this exact same learning process. Here's some minimal boiler-plate code for running a modal QProgressDialog with a Cancel button in a calibre User Interface plugin:

Spoiler Warning below






Code
from PyQt5.Qt import (QProgressDialog, QTimer)
class MyProgressDialog(QProgressDialog): def __init__(self, gui, book_ids, arg1, arg2, ...): self.total_books = len(book_ids) QProgressDialog.__init__(self, '', 'Cancel', 0, self.total_books, gui) self.gui = gui self.book_ids = book_ids self.db = self.gui.current_db.new_api # ... etc etc ... self.i = 0 self.setWindowTitle('blah, blah') icon = get_icons('images/plugin_icon.png') self.setWindowIcon(icon) QTimer.singleShot(0, self.do_book_action) self.exec_() def do_book_action(self): # Process a single book if self.wasCanceled(): return self.do_close() if self.i >= self.total_books: return self.do_close() # get data for current book, e.g. book_id = self.book_ids[self.i] title = self.db.field_for('title', book_id) pathtoepub = self.db.format(book_id, 'EPUB', as_path=True) # ... do stuff ... self.i += 1 self.setLabelText('%d of %d' % (self.i, self.total_books)) self.setValue(self.i) QTimer.singleShot(0, self.do_book_action) def do_close(self): self.hide() self.gui = None


Then to run the QProgressDialog from your main process:
Code
dlg = MyProgressDialog(self.gui, self.book_ids, arg1, arg2, ...)
if dlg.wasCanceled(): # do cancelled stuff
else: # do ran-to-completion stuff
Reply 

#3  Phssthpok 08-15-2018, 05:59 AM
Quote jackie_w
I remember going through this exact same learning process. Here's some minimal boiler-plate code for running a modal QProgressDialog with a Cancel button in a calibre User Interface plugin:
Wow. If that's minimal, I'd hate to see something really complex!

Your help is much appreciated. A bit of expert guidance is much better than burrowing around in the code base looking for relevant snippets. Many thanks.
Reply 

#4  davidfor 08-15-2018, 09:16 AM
An alternative is to grab the common_utils.py from one of kiwidudes plugins, or one of mine. This has a ProgressBar implementation. Here is how it is called:

Code
 pb = ProgressBar(parent=self.gui, window_title=_("Removing annotations"), on_top=True) total_books = len(db.data) pb.set_maximum(total_books) pb.set_value(0) pb.set_label('{:^100}'.format(_("Scanning {0} of {1}").format(0, total_books))) pb.show() for i, record in enumerate(db.data.iterall()): pb.set_label('{:^100}'.format(_("Scanning {0} of {1}").format(i, total_books))) pb.increment() # Do whatever # Hide the progress bar pb.hide()
This is modal and can't be cancelled. Overall, probably no simpler as it relies on another class, but it feels simpler when actually using it.
Reply 

#5  Phssthpok 08-15-2018, 01:52 PM
Quote davidfor
An alternative is to grab the common_utils.py from one of kiwidudes plugins, or one of mine. This has a ProgressBar implementation. Here is how it is called:
Many thanks, this looks much simpler! I'll do it next time (if there is one)...

Meanwhile I got my plugin working, but I don't have the time to polish it to make it more professional or error-resistant or to invent an icon for it. I'm attaching it here in case anyone wants to pick it up. What it does is to extract the tables of contents from selected ePub files to generate a CSV file. The idea was to make it easier for me to create a list of the short stories in my collection of anthologies. It incorporates a copy of https://pypi.org/project/Unidecode/ which is used to flatten all strings to ASCII, since Microsoft Excel seems to be allergic to UTF-8. The authors, titles, series and ToC entry are all converted to title case to make it easier to sort the output file. Hope someone will find a use for it besides me.
[zip] Extract_ToCs.zip (208.7 KB, 143 views)
Reply 

#6  Terisa de morgan 08-16-2018, 06:42 AM
Quote Phssthpok
Many thanks, this looks much simpler! I'll do it next time (if there is one)...

Meanwhile I got my plugin working, but I don't have the time to polish it to make it more professional or error-resistant or to invent an icon for it. I'm attaching it here in case anyone wants to pick it up. What it does is to extract the tables of contents from selected ePub files to generate a CSV file. The idea was to make it easier for me to create a list of the short stories in my collection of anthologies. It incorporates a copy of https://pypi.org/project/Unidecode/ which is used to flatten all strings to ASCII, since Microsoft Excel seems to be allergic to UTF-8. The authors, titles, series and ToC entry are all converted to title case to make it easier to sort the output file. Hope someone will find a use for it besides me.
Thank you very much, it's useful and comfortable for anthologies. It's in my calibre now If I was able to link it with ePubSplit....
Reply 

#7  un_pogaz 10-16-2020, 07:16 PM
Sorry for the Necro-post, but the solution presented here is not the most optimal.
(Recursive call of a QTimer object in the function)
Too bad, but excepted this, Thanks to this thread, I was able to make my plugin, so I return the elevator.

My optimised version:
Code
class CustomProgressDialog(QProgressDialog): def __init__(self, gui, book_ids): # DB API self.dbA = gui.current_db; # list of books id self.book_ids = book_ids; # Books count self.book_count = len(self.book_ids); # Output book dic self.books_dic = {}; QProgressDialog.__init__(self, '', _('Cancel'), 0, self.book_count, gui); self.gui = gui; self.setWindowTitle(_('Progress Bar Name')); self.setWindowIcon(get_icon('images/plugin_icon.png')); self.setValue(0); self.setMinimumWidth(500); self.setMinimumDuration(100); self.setAutoClose(True); self.setAutoReset(False); self.hide(); debug_print('Launch cleaning for {0} book.'.format(self.book_count)); QTimer.singleShot(0, self._do_book_action); self.exec_(); def close(self): self.dbA = None; self.books_dic = None; super(CleanerProgressDialog, self).close(); def _do_book_action(self): self.setValue(0); for num, book_id in enumerate(self.book_ids, start=1): # update ProgressBar self.setValue(num); self.setLabelText(_('Book {0} of {1}').format(num, self.book_count)); if self.book_count < 100: self.hide(); else: self.show(); if self.wasCanceled(): self.close(); return; # # Write your code here # # # Example miA = self.dbA.get_metadata(book_id, index_is_id=True, get_cover=False); comment = miA.get('comments'); if comment is not None: self.books_dic[book_id] = CleanHTML(comment); # # end of your code if not canceled # # update the database library debug_print('ProgressDialog - Finish') debug_print('Update the database for {0} books...\n'.format(len(self.books_dic))); self.setLabelText(_('Update the library for {0} books...').format(len(self.books_dic))); self.dbA.new_api.set_field('comments', {id:self.books_dic[id] for id in self.books_dic.keys()}); self.gui.iactions['Edit Metadata'].refresh_gui(self.books_dic.keys(), covers_changed=False); self.hide(); return;
Reply 

Today's Posts | Search this Thread | Login | Register