mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add progress dialog for saving to disk. Also Fix #1624 (Reading meta-data dialogue box doesn't close in windows XP)
This commit is contained in:
parent
8d6bc29107
commit
edd7ef00b1
@ -10,11 +10,6 @@ Based on ideas from comiclrf created by FangornUK.
|
|||||||
import os, sys, shutil, traceback, textwrap
|
import os, sys, shutil, traceback, textwrap
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
try:
|
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
_reportlab = True
|
|
||||||
except:
|
|
||||||
_reportlab = False
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -397,8 +392,7 @@ def create_lrf(pages, profile, opts, thumbnail=None):
|
|||||||
def create_pdf(pages, profile, opts, thumbnail=None):
|
def create_pdf(pages, profile, opts, thumbnail=None):
|
||||||
width, height = PROFILES[profile]
|
width, height = PROFILES[profile]
|
||||||
|
|
||||||
if not _reportlab:
|
from reportlab.pdfgen import canvas
|
||||||
raise RuntimeError('Failed to load reportlab')
|
|
||||||
|
|
||||||
pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15))
|
pdf = canvas.Canvas(filename=opts.output, pagesize=(width,height+15))
|
||||||
pdf.setAuthor(opts.author)
|
pdf.setAuthor(opts.author)
|
||||||
|
48
src/calibre/gui2/dialogs/progress.py
Normal file
48
src/calibre/gui2/dialogs/progress.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
__license__ = 'GPL v3'
|
||||||
|
__copyright__ = '2009, Kovid Goyal kovid@kovidgoyal.net'
|
||||||
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
|
''''''
|
||||||
|
|
||||||
|
from PyQt4.Qt import QDialog, SIGNAL, Qt
|
||||||
|
|
||||||
|
from calibre.gui2.dialogs.progress_ui import Ui_Dialog
|
||||||
|
|
||||||
|
class ProgressDialog(QDialog, Ui_Dialog):
|
||||||
|
|
||||||
|
def __init__(self, title, msg='', min=0, max=99, parent=None):
|
||||||
|
QDialog.__init__(self, parent)
|
||||||
|
self.setupUi(self)
|
||||||
|
self.setWindowTitle(title)
|
||||||
|
self.title.setText(title)
|
||||||
|
self.message.setText(msg)
|
||||||
|
self.setWindowModality(Qt.ApplicationModal)
|
||||||
|
self.set_min(min)
|
||||||
|
self.set_max(max)
|
||||||
|
self.canceled = False
|
||||||
|
|
||||||
|
self.connect(self.button_box, SIGNAL('rejected()'), self._canceled)
|
||||||
|
|
||||||
|
def set_msg(self, msg=''):
|
||||||
|
self.message.setText(msg)
|
||||||
|
|
||||||
|
def set_value(self, val):
|
||||||
|
self.bar.setValue(val)
|
||||||
|
|
||||||
|
def set_min(self, min):
|
||||||
|
self.bar.setMinimum(min)
|
||||||
|
|
||||||
|
def set_max(self, max):
|
||||||
|
self.bar.setMaximum(max)
|
||||||
|
|
||||||
|
def _canceled(self, *args):
|
||||||
|
self.canceled = True
|
||||||
|
self.button_box.setDisabled(True)
|
||||||
|
self.title.setText(_('Aborting...'))
|
||||||
|
|
||||||
|
def keyPressEvent(self, ev):
|
||||||
|
if ev.key() == Qt.Key_Escape:
|
||||||
|
self._canceled()
|
||||||
|
else:
|
||||||
|
QDialog.keyPressEvent(self, ev)
|
72
src/calibre/gui2/dialogs/progress.ui
Normal file
72
src/calibre/gui2/dialogs/progress.ui
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<ui version="4.0" >
|
||||||
|
<class>Dialog</class>
|
||||||
|
<widget class="QDialog" name="Dialog" >
|
||||||
|
<property name="geometry" >
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>712</width>
|
||||||
|
<height>308</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle" >
|
||||||
|
<string>Dialog</string>
|
||||||
|
</property>
|
||||||
|
<property name="windowIcon" >
|
||||||
|
<iconset resource="../images.qrc" >
|
||||||
|
<normaloff>:/images/jobs.svg</normaloff>:/images/jobs.svg</iconset>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout" >
|
||||||
|
<item row="0" column="0" >
|
||||||
|
<widget class="QLabel" name="title" >
|
||||||
|
<property name="font" >
|
||||||
|
<font>
|
||||||
|
<weight>75</weight>
|
||||||
|
<bold>true</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text" >
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment" >
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0" >
|
||||||
|
<widget class="QProgressBar" name="bar" >
|
||||||
|
<property name="value" >
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0" >
|
||||||
|
<widget class="QLabel" name="message" >
|
||||||
|
<property name="text" >
|
||||||
|
<string>TextLabel</string>
|
||||||
|
</property>
|
||||||
|
<property name="alignment" >
|
||||||
|
<set>Qt::AlignCenter</set>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap" >
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" >
|
||||||
|
<widget class="QDialogButtonBox" name="button_box" >
|
||||||
|
<property name="standardButtons" >
|
||||||
|
<set>QDialogButtonBox::Abort</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources>
|
||||||
|
<include location="../images.qrc" />
|
||||||
|
</resources>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
@ -198,13 +198,18 @@ class BooksModel(QAbstractTableModel):
|
|||||||
''' Return list indices of all cells in index.row()'''
|
''' Return list indices of all cells in index.row()'''
|
||||||
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
return [ self.index(index.row(), c) for c in range(self.columnCount(None))]
|
||||||
|
|
||||||
def save_to_disk(self, rows, path, single_dir=False, single_format=None):
|
def save_to_disk(self, rows, path, single_dir=False, single_format=None,
|
||||||
|
callback=None):
|
||||||
rows = [row.row() for row in rows]
|
rows = [row.row() for row in rows]
|
||||||
if single_format is None:
|
if single_format is None:
|
||||||
return self.db.export_to_dir(path, rows, self.sorted_on[0] == 'authors',
|
return self.db.export_to_dir(path, rows,
|
||||||
single_dir=single_dir)
|
self.sorted_on[0] == 'authors',
|
||||||
|
single_dir=single_dir,
|
||||||
|
callback=callback)
|
||||||
else:
|
else:
|
||||||
return self.db.export_single_format_to_dir(path, rows, single_format)
|
return self.db.export_single_format_to_dir(path, rows,
|
||||||
|
single_format,
|
||||||
|
callback=callback)
|
||||||
|
|
||||||
|
|
||||||
def delete_books(self, indices):
|
def delete_books(self, indices):
|
||||||
|
@ -28,6 +28,7 @@ from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
|
|||||||
from calibre.library.database import LibraryDatabase
|
from calibre.library.database import LibraryDatabase
|
||||||
from calibre.gui2.dialogs.scheduler import Scheduler
|
from calibre.gui2.dialogs.scheduler import Scheduler
|
||||||
from calibre.gui2.update import CheckForUpdates
|
from calibre.gui2.update import CheckForUpdates
|
||||||
|
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||||
from calibre.gui2.main_window import MainWindow, option_parser as _option_parser
|
from calibre.gui2.main_window import MainWindow, option_parser as _option_parser
|
||||||
from calibre.gui2.main_ui import Ui_MainWindow
|
from calibre.gui2.main_ui import Ui_MainWindow
|
||||||
from calibre.gui2.device import DeviceManager
|
from calibre.gui2.device import DeviceManager
|
||||||
@ -598,29 +599,26 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder')
|
root = choose_dir(self, 'recursive book import root dir dialog', 'Select root folder')
|
||||||
if not root:
|
if not root:
|
||||||
return
|
return
|
||||||
progress = QProgressDialog('', '&'+_('Stop'),
|
progress = ProgressDialog(_('Adding books recursively...'),
|
||||||
0, 0, self)
|
min=0, max=0, parent=self)
|
||||||
progress.setWindowModality(Qt.ApplicationModal)
|
|
||||||
progress.setWindowTitle(_('Adding books recursively...'))
|
|
||||||
progress.show()
|
progress.show()
|
||||||
def callback(msg):
|
def callback(msg):
|
||||||
if msg != '.':
|
if msg != '.':
|
||||||
progress.setLabelText((_('Added ')+msg) if msg else _('Searching...'))
|
progress.set_msg((_('Added ')+msg) if msg else _('Searching...'))
|
||||||
stop = progress.wasCanceled()
|
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
QApplication.sendPostedEvents()
|
QApplication.sendPostedEvents()
|
||||||
QApplication.flush()
|
QApplication.flush()
|
||||||
return stop
|
return progress.canceled
|
||||||
try:
|
try:
|
||||||
duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback)
|
duplicates = self.library_view.model().db.recursive_import(root, single, callback=callback)
|
||||||
finally:
|
finally:
|
||||||
progress.hide()
|
progress.hide()
|
||||||
progress.close()
|
|
||||||
if duplicates:
|
if duplicates:
|
||||||
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
||||||
for mi, formats in duplicates:
|
for mi, formats in duplicates:
|
||||||
files += '<li>'+mi.title+'</li>\n'
|
files += '<li>'+mi.title+'</li>\n'
|
||||||
d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'), files+'</ul></p>', self)
|
d = WarningDialog(_('Duplicates found!'), _('Duplicates found!'),
|
||||||
|
files+'</ul></p>', self)
|
||||||
if d.exec_() == QDialog.Accepted:
|
if d.exec_() == QDialog.Accepted:
|
||||||
for mi, formats in duplicates:
|
for mi, formats in duplicates:
|
||||||
self.library_view.model().db.import_book(mi, formats )
|
self.library_view.model().db.import_book(mi, formats )
|
||||||
@ -686,15 +684,13 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
return
|
return
|
||||||
# Get format and metadata information
|
# Get format and metadata information
|
||||||
formats, metadata, names, infos = [], [], [], []
|
formats, metadata, names, infos = [], [], [], []
|
||||||
progress = QProgressDialog(_('Reading metadata...'), _('Stop'), 0, len(paths), self)
|
progress = ProgressDialog(_('Adding books...'), _('Reading metadata...'),
|
||||||
progress.setWindowTitle(_('Adding books...'))
|
min=0, max=len(paths), parent=self)
|
||||||
progress.setWindowModality(Qt.ApplicationModal)
|
|
||||||
progress.setLabelText(_('Reading metadata...'))
|
|
||||||
progress.show()
|
progress.show()
|
||||||
try:
|
try:
|
||||||
for c, book in enumerate(paths):
|
for c, book in enumerate(paths):
|
||||||
progress.setValue(c)
|
progress.set_value(c)
|
||||||
if progress.wasCanceled():
|
if progress.canceled:
|
||||||
return
|
return
|
||||||
format = os.path.splitext(book)[1]
|
format = os.path.splitext(book)[1]
|
||||||
format = format[1:] if format else None
|
format = format[1:] if format else None
|
||||||
@ -713,15 +709,14 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
infos.append({'title':mi.title, 'authors':', '.join(mi.authors),
|
infos.append({'title':mi.title, 'authors':', '.join(mi.authors),
|
||||||
'cover':self.default_thumbnail, 'tags':[]})
|
'cover':self.default_thumbnail, 'tags':[]})
|
||||||
title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace')
|
title = mi.title if isinstance(mi.title, unicode) else mi.title.decode(preferred_encoding, 'replace')
|
||||||
progress.setLabelText(_('Read metadata from ')+title)
|
progress.set_msg(_('Read metadata from ')+title)
|
||||||
|
|
||||||
if not to_device:
|
if not to_device:
|
||||||
progress.setLabelText(_('Adding books to database...'))
|
progress.set_msg(_('Adding books to database...'))
|
||||||
model = self.library_view.model()
|
model = self.library_view.model()
|
||||||
|
|
||||||
paths = list(paths)
|
paths = list(paths)
|
||||||
duplicates, number_added = model.add_books(paths, formats, metadata)
|
duplicates, number_added = model.add_books(paths, formats, metadata)
|
||||||
progress.cancel()
|
|
||||||
if duplicates:
|
if duplicates:
|
||||||
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
files = _('<p>Books with the same title as the following already exist in the database. Add them anyway?<ul>')
|
||||||
for mi in duplicates[2]:
|
for mi in duplicates[2]:
|
||||||
@ -734,9 +729,7 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card)
|
self.upload_books(paths, list(map(sanitize_file_name, names)), infos, on_card=on_card)
|
||||||
finally:
|
finally:
|
||||||
progress.setValue(progress.maximum())
|
|
||||||
progress.hide()
|
progress.hide()
|
||||||
progress.close()
|
|
||||||
|
|
||||||
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
def upload_books(self, files, names, metadata, on_card=False, memory=None):
|
||||||
'''
|
'''
|
||||||
@ -979,18 +972,37 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
self.save_to_disk(checked, True)
|
self.save_to_disk(checked, True)
|
||||||
|
|
||||||
def save_to_disk(self, checked, single_dir=False, single_format=None):
|
def save_to_disk(self, checked, single_dir=False, single_format=None):
|
||||||
|
|
||||||
rows = self.current_view().selectionModel().selectedRows()
|
rows = self.current_view().selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
d = error_dialog(self, _('Cannot save to disk'), _('No books selected'))
|
d = error_dialog(self, _('Cannot save to disk'), _('No books selected'))
|
||||||
d.exec_()
|
d.exec_()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
||||||
|
parent=self)
|
||||||
|
|
||||||
|
def callback(count, msg):
|
||||||
|
progress.set_value(count)
|
||||||
|
progress.set_msg(_('Saved')+' '+msg)
|
||||||
|
QApplication.processEvents()
|
||||||
|
QApplication.sendPostedEvents()
|
||||||
|
QApplication.flush()
|
||||||
|
return not progress.canceled
|
||||||
|
|
||||||
dir = choose_dir(self, 'save to disk dialog', _('Choose destination directory'))
|
dir = choose_dir(self, 'save to disk dialog', _('Choose destination directory'))
|
||||||
if not dir:
|
if not dir:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
progress.show()
|
||||||
|
QApplication.processEvents()
|
||||||
|
QApplication.sendPostedEvents()
|
||||||
|
QApplication.flush()
|
||||||
|
try:
|
||||||
if self.current_view() == self.library_view:
|
if self.current_view() == self.library_view:
|
||||||
failures = self.current_view().model().save_to_disk(rows, dir,
|
failures = self.current_view().model().save_to_disk(rows, dir,
|
||||||
single_dir=single_dir, single_format=single_format)
|
single_dir=single_dir, callback=callback,
|
||||||
|
single_format=single_format)
|
||||||
if failures and single_format is not None:
|
if failures and single_format is not None:
|
||||||
msg = _('<p>Could not save the following books to disk, because the %s format is not available for them:<ul>')%single_format.upper()
|
msg = _('<p>Could not save the following books to disk, because the %s format is not available for them:<ul>')%single_format.upper()
|
||||||
for f in failures:
|
for f in failures:
|
||||||
@ -1001,6 +1013,8 @@ class Main(MainWindow, Ui_MainWindow):
|
|||||||
else:
|
else:
|
||||||
paths = self.current_view().model().paths(rows)
|
paths = self.current_view().model().paths(rows)
|
||||||
self.device_manager.save_books(Dispatcher(self.books_saved), paths, dir)
|
self.device_manager.save_books(Dispatcher(self.books_saved), paths, dir)
|
||||||
|
finally:
|
||||||
|
progress.hide()
|
||||||
|
|
||||||
def books_saved(self, job):
|
def books_saved(self, job):
|
||||||
if job.exception is not None:
|
if job.exception is not None:
|
||||||
|
@ -1390,10 +1390,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
return [i[0] for i in self.conn.get('SELECT id FROM books')]
|
return [i[0] for i in self.conn.get('SELECT id FROM books')]
|
||||||
|
|
||||||
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
def export_to_dir(self, dir, indices, byauthor=False, single_dir=False,
|
||||||
index_is_id=False):
|
index_is_id=False, callback=None):
|
||||||
if not os.path.exists(dir):
|
if not os.path.exists(dir):
|
||||||
raise IOError('Target directory does not exist: '+dir)
|
raise IOError('Target directory does not exist: '+dir)
|
||||||
by_author = {}
|
by_author = {}
|
||||||
|
count = 0
|
||||||
for index in indices:
|
for index in indices:
|
||||||
id = index if index_is_id else self.id(index)
|
id = index if index_is_id else self.id(index)
|
||||||
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
au = self.conn.get('SELECT author_sort FROM books WHERE id=?',
|
||||||
@ -1403,8 +1404,6 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
if not au:
|
if not au:
|
||||||
au = _('Unknown')
|
au = _('Unknown')
|
||||||
au = au.split(',')[0]
|
au = au.split(',')[0]
|
||||||
else:
|
|
||||||
au = au.replace(',', ';')
|
|
||||||
if not by_author.has_key(au):
|
if not by_author.has_key(au):
|
||||||
by_author[au] = []
|
by_author[au] = []
|
||||||
by_author[au].append(index)
|
by_author[au].append(index)
|
||||||
@ -1456,6 +1455,11 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
print 'Error setting metadata for book:', mi.title
|
print 'Error setting metadata for book:', mi.title
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
f.close()
|
f.close()
|
||||||
|
count += 1
|
||||||
|
if callable(callback):
|
||||||
|
if not callback(count, mi.title):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def import_book(self, mi, formats):
|
def import_book(self, mi, formats):
|
||||||
@ -1569,12 +1573,13 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
|
|
||||||
return duplicates
|
return duplicates
|
||||||
|
|
||||||
def export_single_format_to_dir(self, dir, indices, format, index_is_id=False):
|
def export_single_format_to_dir(self, dir, indices, format,
|
||||||
|
index_is_id=False, callback=None):
|
||||||
dir = os.path.abspath(dir)
|
dir = os.path.abspath(dir)
|
||||||
if not index_is_id:
|
if not index_is_id:
|
||||||
indices = map(self.id, indices)
|
indices = map(self.id, indices)
|
||||||
failures = []
|
failures = []
|
||||||
for id in indices:
|
for count, id in enumerate(indices):
|
||||||
try:
|
try:
|
||||||
data = self.format(id, format, index_is_id=True)
|
data = self.format(id, format, index_is_id=True)
|
||||||
if not data:
|
if not data:
|
||||||
@ -1599,6 +1604,9 @@ ALTER TABLE books ADD COLUMN isbn TEXT DEFAULT "" COLLATE NOCASE;
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
f.close()
|
f.close()
|
||||||
|
if callable(callback):
|
||||||
|
if not callback(count, title):
|
||||||
|
break
|
||||||
return failures
|
return failures
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ class cmd_commit(_cmd_commit):
|
|||||||
print attributes['summary']
|
print attributes['summary']
|
||||||
return attributes['summary']
|
return attributes['summary']
|
||||||
|
|
||||||
|
|
||||||
def expand_bug(self, msg, nick, config, bug_tracker, type='trac'):
|
def expand_bug(self, msg, nick, config, bug_tracker, type='trac'):
|
||||||
prefix = '%s_%s_'%(type, nick)
|
prefix = '%s_%s_'%(type, nick)
|
||||||
username = config.get_user_option(prefix+'username')
|
username = config.get_user_option(prefix+'username')
|
||||||
|
@ -7,6 +7,7 @@ clarin.com
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class Clarin(BasicNewsRecipe):
|
class Clarin(BasicNewsRecipe):
|
||||||
title = 'Clarin'
|
title = 'Clarin'
|
||||||
|
Loading…
x
Reference in New Issue
Block a user