From 6364ea1d39d50b2cafd2505b31661f09e8562c8a Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 8 Jun 2010 08:16:46 +0100
Subject: [PATCH 01/14] Fix bugs #5751 and #5752. Also distinguish between the
delete warning dialogs for the library and the device
---
.../dialogs/config/create_custom_column.py | 5 +-
src/calibre/gui2/tag_view.py | 58 ++++++++++++-------
src/calibre/gui2/ui.py | 4 +-
3 files changed, 43 insertions(+), 24 deletions(-)
diff --git a/src/calibre/gui2/dialogs/config/create_custom_column.py b/src/calibre/gui2/dialogs/config/create_custom_column.py
index 3d5cb8ba53..a66b7b6642 100644
--- a/src/calibre/gui2/dialogs/config/create_custom_column.py
+++ b/src/calibre/gui2/dialogs/config/create_custom_column.py
@@ -3,6 +3,7 @@ __copyright__ = '2010, Kovid Goyal '
'''Dialog to create a new custom column'''
+import re
from functools import partial
from PyQt4.QtCore import SIGNAL
@@ -94,8 +95,8 @@ class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
col = unicode(self.column_name_box.text()).lower()
if not col:
return self.simple_error('', _('No lookup name was provided'))
- if not col.isalnum() or not col[0].isalpha():
- return self.simple_error('', _('The label must contain only letters and digits, and start with a letter'))
+ if re.match('^\w*$', col) is None or not col[0].isalpha():
+ return self.simple_error('', _('The label must contain only letters, digits and underscores, and start with a letter'))
col_heading = unicode(self.column_heading_box.text())
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
if col_type == '*text':
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 24e83376aa..95b83b407c 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -63,8 +63,7 @@ class TagsView(QTreeView): # {{{
def sort_changed(self, state):
config.set('sort_by_popularity', state == Qt.Checked)
- self.model().refresh()
- # self.search_restriction_set()
+ self.recount()
def set_search_restriction(self, s):
if s:
@@ -197,7 +196,9 @@ class TagsView(QTreeView): # {{{
ci = self.indexAt(QPoint(10, 10))
path = self.model().path_for_index(ci) if self.is_visible(ci) else None
try:
- self.model().refresh()
+ if not self.model().refresh(): # categories changed!
+ self.set_new_model()
+ path = None
except: #Database connection could be closed if an integrity check is happening
pass
if path:
@@ -210,10 +211,16 @@ class TagsView(QTreeView): # {{{
# gone, or if columns have been hidden or restored, we must rebuild the
# model. Reason: it is much easier than reconstructing the browser tree.
def set_new_model(self):
- self._model = TagsModel(self.db, parent=self,
- hidden_categories=self.hidden_categories,
- search_restriction=self.search_restriction)
- self.setModel(self._model)
+ try:
+ self._model = TagsModel(self.db, parent=self,
+ hidden_categories=self.hidden_categories,
+ search_restriction=self.search_restriction)
+ self.setModel(self._model)
+ except:
+ # The DB must be gone. Set the model to None and hope that someone
+ # will call set_database later. I don't know if this in fact works
+ self._model = None
+ self.setModel(None)
# }}}
class TagTreeItem(object): # {{{
@@ -323,18 +330,9 @@ class TagsModel(QAbstractItemModel): # {{{
self.tags_view = parent
self.hidden_categories = hidden_categories
self.search_restriction = search_restriction
+ self.row_map = []
- # Reconstruct the user categories, putting them into metadata
- tb_cats = self.db.field_metadata
- for k in tb_cats.keys():
- if tb_cats[k]['kind'] in ['user', 'search']:
- del tb_cats[k]
- for user_cat in sorted(prefs['user_categories'].keys()):
- cat_name = user_cat+':' # add the ':' to avoid name collision
- tb_cats.add_user_category(label=cat_name, name=user_cat)
- if len(saved_searches.names()):
- tb_cats.add_search_category(label='search', name=_('Searches'))
-
+ # get_node_tree cannot return None here, because row_map is empty
data = self.get_node_tree(config['sort_by_popularity'])
self.root_item = TagTreeItem()
for i, r in enumerate(self.row_map):
@@ -355,9 +353,22 @@ class TagsModel(QAbstractItemModel): # {{{
self.search_restriction = s
def get_node_tree(self, sort):
+ old_row_map = self.row_map[:]
self.row_map = []
self.categories = []
+ # Reconstruct the user categories, putting them into metadata
+ tb_cats = self.db.field_metadata
+ for k in tb_cats.keys():
+ if tb_cats[k]['kind'] in ['user', 'search']:
+ del tb_cats[k]
+ for user_cat in sorted(prefs['user_categories'].keys()):
+ cat_name = user_cat+':' # add the ':' to avoid name collision
+ tb_cats.add_user_category(label=cat_name, name=user_cat)
+ if len(saved_searches.names()):
+ tb_cats.add_search_category(label='search', name=_('Searches'))
+
+ # Now get the categories
if self.search_restriction:
data = self.db.get_categories(sort_on_count=sort,
icon_map=self.category_icon_map,
@@ -367,13 +378,19 @@ class TagsModel(QAbstractItemModel): # {{{
tb_categories = self.db.field_metadata
for category in tb_categories:
- if category in data: # They should always be there, but ...
+ if category in data: # The search category can come and go
self.row_map.append(category)
self.categories.append(tb_categories[category]['name'])
+ if len(old_row_map) != 0 and len(old_row_map) != len(self.row_map):
+ # A category has been added or removed. We must force a rebuild of
+ # the model
+ return None
return data
def refresh(self):
data = self.get_node_tree(config['sort_by_popularity']) # get category data
+ if data is None:
+ return False
row_index = -1
for i, r in enumerate(self.row_map):
if self.hidden_categories and self.categories[i] in self.hidden_categories:
@@ -395,6 +412,7 @@ class TagsModel(QAbstractItemModel): # {{{
tag.state = state_map.get(tag.name, 0)
t = TagTreeItem(parent=category, data=tag, icon_map=self.icon_state_map)
self.endInsertRows()
+ return True
def columnCount(self, parent):
return 1
@@ -439,7 +457,7 @@ class TagsModel(QAbstractItemModel): # {{{
label=self.db.field_metadata[key]['label'])
self.tags_view.tag_item_renamed.emit()
item.tag.name = val
- self.refresh()
+ self.refresh() # Should work, because no categories can have disappeared
return True
def headerData(self, *args):
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 01f0cf6271..7d258608d0 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -854,7 +854,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
r = unicode(r)
if r is not None and r != '':
self.restriction_in_effect = True
- restriction = "search:%s"%(r)
+ restriction = 'search:"%s"'%(r)
else:
self.restriction_in_effect = False
restriction = ''
@@ -1557,7 +1557,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
if not confirm(''+_('The selected books will be '
'permanently deleted '
'from your device. Are you sure?')
- +'
', 'library_delete_books', self):
+ +'
', 'device_delete_books', self):
return
if self.stack.currentIndex() == 1:
view = self.memory_view
From db7474d6f64d47fa712ca825fb2050a220e4cae1 Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 8 Jun 2010 09:12:50 +0100
Subject: [PATCH 02/14] Fix bug #5754 - extra customization problems
---
src/calibre/devices/prs505/driver.py | 3 ++-
src/calibre/gui2/device_drivers/configwidget.py | 5 +++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/calibre/devices/prs505/driver.py b/src/calibre/devices/prs505/driver.py
index 071d651186..38fac8b266 100644
--- a/src/calibre/devices/prs505/driver.py
+++ b/src/calibre/devices/prs505/driver.py
@@ -143,10 +143,11 @@ class PRS505(USBMS):
if booklists[i] is not None:
blists[i] = booklists[i]
opts = self.settings()
- collections = ['series', 'tags']
if opts.extra_customization:
collections = [x.strip() for x in
opts.extra_customization.split(',')]
+ else:
+ collections = []
debug_print('PRS505: collection fields:', collections)
c.update(blists, collections)
c.write()
diff --git a/src/calibre/gui2/device_drivers/configwidget.py b/src/calibre/gui2/device_drivers/configwidget.py
index d1cebcb81d..585eed30df 100644
--- a/src/calibre/gui2/device_drivers/configwidget.py
+++ b/src/calibre/gui2/device_drivers/configwidget.py
@@ -38,9 +38,10 @@ class ConfigWidget(QWidget, Ui_ConfigWidget):
self.opt_read_metadata.setChecked(self.settings.read_metadata)
else:
self.opt_read_metadata.hide()
- if extra_customization_message and settings.extra_customization:
+ if extra_customization_message:
self.extra_customization_label.setText(extra_customization_message)
- self.opt_extra_customization.setText(settings.extra_customization)
+ if settings.extra_customization:
+ self.opt_extra_customization.setText(settings.extra_customization)
else:
self.extra_customization_label.setVisible(False)
self.opt_extra_customization.setVisible(False)
From 58bc3fbab3a747cd3a5c94502172e3596c79218c Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 8 Jun 2010 10:24:01 +0100
Subject: [PATCH 03/14] Fix problems renaming an item to itself
---
src/calibre/gui2/tag_view.py | 7 +++++
src/calibre/library/custom_columns.py | 2 +-
src/calibre/library/database2.py | 38 +++++++++++++++++++--------
3 files changed, 35 insertions(+), 12 deletions(-)
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index 056838750f..e8f3068d35 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -426,6 +426,8 @@ class TagsModel(QAbstractItemModel): # {{{
def setData(self, index, value, role=Qt.EditRole):
if not index.isValid():
return NONE
+ # set up to position at the category label
+ path = self.path_for_index(self.parent(index))
val = unicode(value.toString())
if not val:
error_dialog(self.tags_view, _('Item is blank'),
@@ -458,6 +460,11 @@ class TagsModel(QAbstractItemModel): # {{{
self.tags_view.tag_item_renamed.emit()
item.tag.name = val
self.refresh() # Should work, because no categories can have disappeared
+ if path:
+ idx = self.index_for_path(path)
+ if idx.isValid():
+ self.tags_view.setCurrentIndex(idx)
+ self.tags_view.scrollTo(idx, QTreeView.PositionAtCenter)
return True
def headerData(self, *args):
diff --git a/src/calibre/library/custom_columns.py b/src/calibre/library/custom_columns.py
index d9e16a5e32..23b78f38ae 100644
--- a/src/calibre/library/custom_columns.py
+++ b/src/calibre/library/custom_columns.py
@@ -192,7 +192,7 @@ class CustomColumns(object):
# check if item exists
new_id = self.conn.get(
'SELECT id FROM %s WHERE value=?'%table, (new_name,), all=False)
- if new_id is None:
+ if new_id is None or old_id == new_id:
self.conn.execute('UPDATE %s SET value=? WHERE id=?'%table, (new_name, old_id))
else:
# New id exists. If the column is_multiple, then process like
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index e639643e68..5868a782ad 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -1003,8 +1003,9 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
new_id = self.conn.get(
'''SELECT id from tags
WHERE name=?''', (new_name,), all=False)
- if new_id is None:
- # easy case. Simply rename the tag
+ if new_id is None or old_id == new_id:
+ # easy cases. Simply rename the tag. Do it even if equal, in case
+ # there is a change of case
self.conn.execute('''UPDATE tags SET name=?
WHERE id=?''', (new_name, old_id))
else:
@@ -1041,7 +1042,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
new_id = self.conn.get(
'''SELECT id from series
WHERE name=?''', (new_name,), all=False)
- if new_id is None:
+ if new_id is None or old_id == new_id:
self.conn.execute('UPDATE series SET name=? WHERE id=?',
(new_name, old_id))
else:
@@ -1086,7 +1087,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
new_id = self.conn.get(
'''SELECT id from publishers
WHERE name=?''', (new_name,), all=False)
- if new_id is None:
+ if new_id is None or old_id == new_id:
# New name doesn't exist. Simply change the old name
self.conn.execute('UPDATE publishers SET name=? WHERE id=?', \
(new_name, old_id))
@@ -1113,22 +1114,34 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
new_name = new_name.replace(',', '|')
# Get the list of books we must fix up, one way or the other
- books = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,))
+ # Save the list so we can use it twice
+ bks = self.conn.get('SELECT book from books_authors_link WHERE author=?', (old_id,))
+ books = []
+ for (book_id,) in bks:
+ books.append(book_id)
# check if the new author already exists
new_id = self.conn.get('SELECT id from authors WHERE name=?',
(new_name,), all=False)
- if new_id is None:
+ if new_id is None or old_id == new_id:
# No name clash. Go ahead and update the author's name
self.conn.execute('UPDATE authors SET name=? WHERE id=?',
(new_name, old_id))
else:
+ # First check for the degenerate case -- changing a value to itself.
+ # Update it in case there is a change of case, but do nothing else
+ if old_id == new_id:
+ self.conn.execute('UPDATE authors SET name=? WHERE id=?',
+ (new_name, old_id))
+ self.conn.commit()
+ return
# Author exists. To fix this, we must replace all the authors
# instead of replacing the one. Reason: db integrity checks can stop
# the rename process, which would leave everything half-done. We
# can't do it the same way as tags (delete and add) because author
# order is important.
- for (book_id,) in books:
+
+ for book_id in books:
# Get the existing list of authors
authors = self.conn.get('''
SELECT author from books_authors_link
@@ -1139,7 +1152,6 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# with the new one while we are at it
for i,aut in enumerate(authors):
authors[i] = aut[0] if aut[0] != old_id else new_id
-
# Delete the existing authors list
self.conn.execute('''DELETE FROM books_authors_link
WHERE book=?''',(book_id,))
@@ -1154,11 +1166,12 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
# metadata. Ignore it.
pass
# Now delete the old author from the DB
+ bks = self.conn.get('SELECT book FROM books_authors_link WHERE author=?', (old_id,))
self.conn.execute('DELETE FROM authors WHERE id=?', (old_id,))
- self.conn.commit()
+ self.conn.commit()
# the authors are now changed, either by changing the author's name
# or replacing the author in the list. Now must fix up the books.
- for (book_id,) in books:
+ for book_id in books:
# First, must refresh the cache to see the new authors
self.data.refresh_ids(self, [book_id])
# now fix the filesystem paths
@@ -1168,14 +1181,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
SELECT authors.name
FROM authors, books_authors_link as bl
WHERE bl.book = ? and bl.author = authors.id
+ ORDER BY bl.id
''' , (book_id,))
# unpack the double-list structure
for i,aut in enumerate(authors):
authors[i] = aut[0]
ss = authors_to_sort_string(authors)
+ # Change the '|'s to ','
+ ss = ss.replace('|', ',')
self.conn.execute('''UPDATE books
SET author_sort=?
- WHERE id=?''', (ss, old_id))
+ WHERE id=?''', (ss, book_id))
self.conn.commit()
# the caller will do a general refresh, so we don't need to
# do one here
From 4af32fc6f084c1c3f2754a46158ac1c881ad3992 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 8 Jun 2010 11:23:55 -0600
Subject: [PATCH 04/14] Fix #5750 (Default book cover position inconsistent)
---
src/calibre/gui2/dialogs/metadata_single.py | 8 +++++---
src/calibre/gui2/status.py | 5 +----
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py
index 1ea6743ae2..0241e1b542 100644
--- a/src/calibre/gui2/dialogs/metadata_single.py
+++ b/src/calibre/gui2/dialogs/metadata_single.py
@@ -403,12 +403,14 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
ag = QCoreApplication.instance().desktop().availableGeometry(self)
self.cover.MAX_HEIGHT = ag.height()-(25 if (islinux or isfreebsd) else 0)-height_of_rest
self.cover.MAX_WIDTH = ag.width()-(25 if (islinux or isfreebsd) else 0)-width_of_rest
+ pm = QPixmap()
if cover:
- pm = QPixmap()
pm.loadFromData(cover)
- if not pm.isNull():
- self.cover.setPixmap(pm)
+ if pm.isNull():
+ pm = QPixmap(I('book.svg'))
+ else:
self.cover_data = cover
+ self.cover.setPixmap(pm)
self.original_series_name = unicode(self.series.text()).strip()
if len(db.custom_column_label_map) == 0:
self.central_widget.tabBar().setVisible(False)
diff --git a/src/calibre/gui2/status.py b/src/calibre/gui2/status.py
index 2759c4074b..14c6131fb8 100644
--- a/src/calibre/gui2/status.py
+++ b/src/calibre/gui2/status.py
@@ -52,10 +52,7 @@ class BookInfoDisplay(QWidget):
QLabel.__init__(self)
self.setMaximumWidth(81)
self.setMaximumHeight(108)
- self.default_pixmap = QPixmap(coverpath).scaled(self.maximumWidth(),
- self.maximumHeight(),
- Qt.IgnoreAspectRatio,
- Qt.SmoothTransformation)
+ self.default_pixmap = QPixmap(coverpath)
self.setScaledContents(True)
self.statusbar_height = 120
self.setPixmap(self.default_pixmap)
From fb9d327cbf083e184d008fd7574bebb1247a9a6e Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 8 Jun 2010 15:04:11 -0600
Subject: [PATCH 05/14] fix #5696 (incorrect encoding for Epub output for
Sueddeutsche Zeitung generated in text)
---
resources/recipes/sueddeutsche.recipe | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/resources/recipes/sueddeutsche.recipe b/resources/recipes/sueddeutsche.recipe
index 8f4d7e5892..9a5b00fc6c 100644
--- a/resources/recipes/sueddeutsche.recipe
+++ b/resources/recipes/sueddeutsche.recipe
@@ -19,10 +19,10 @@ class Sueddeutsche(BasicNewsRecipe):
no_stylesheets = True
language = 'de'
- encoding = 'iso-8859-15'
+ encoding = 'utf-8'
remove_javascript = True
-
+
remove_tags = [ dict(name='link'), dict(name='iframe'),
dict(name='div', attrs={'id':["bookmarking","themenbox","artikelfoot","CAD_AD",
"SKY_AD","NT1_AD","navbar1","sdesiteheader"]}),
From 3d551ff1ab4ff8081296372014936e8a1c853cae Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 8 Jun 2010 15:35:10 -0600
Subject: [PATCH 06/14] Fix cover browser positioning and only open viewer when
doubleclicking on row numbers
---
src/calibre/gui2/main.ui | 70 ++++++++++++++++++++++------------------
src/calibre/gui2/ui.py | 4 +--
2 files changed, 40 insertions(+), 34 deletions(-)
diff --git a/src/calibre/gui2/main.ui b/src/calibre/gui2/main.ui
index 1abaf53d3c..b4bafc3b79 100644
--- a/src/calibre/gui2/main.ui
+++ b/src/calibre/gui2/main.ui
@@ -329,7 +329,7 @@
0
-
+
-
@@ -389,37 +389,43 @@
-
-
-
- 100
- 10
-
-
-
- true
-
-
- true
-
-
- false
-
-
- QAbstractItemView::DragDrop
-
-
- true
-
-
- QAbstractItemView::SelectRows
-
-
- false
-
-
- false
-
+
+
+ -
+
+
+
+ 100
+ 10
+
+
+
+ true
+
+
+ true
+
+
+ false
+
+
+ QAbstractItemView::DragDrop
+
+
+ true
+
+
+ QAbstractItemView::SelectRows
+
+
+ false
+
+
+ false
+
+
+
+
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 7d258608d0..6a1a508891 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -597,7 +597,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.cover_flow = CoverFlow(height=cfh, text_height=text_height)
self.cover_flow.setVisible(False)
if not config['separate_cover_flow']:
- self.library.layout().addWidget(self.cover_flow)
+ self.cb_layout.addWidget(self.cover_flow)
self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
self.library_view.selectionModel().currentRowChanged.connect(
self.sync_cf_to_listview)
@@ -638,7 +638,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.connect(self.scheduler,
SIGNAL('start_recipe_fetch(PyQt_PyObject)'),
self.download_scheduled_recipe, Qt.QueuedConnection)
- self.library_view.verticalHeader().sectionClicked.connect(self.view_specific_book)
for view in ('library', 'memory', 'card_a', 'card_b'):
view = getattr(self, view+'_view')
@@ -923,6 +922,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
index = m.index(row, 0)
if self.library_view.currentIndex().row() != row and index.isValid():
self.cover_flow_sync_flag = False
+ self.library_view.scrollTo(index)
sm = self.library_view.selectionModel()
sm.select(index, sm.ClearAndSelect|sm.Rows)
self.library_view.setCurrentIndex(index)
From e2c2067095f2920dff8e545c8923ef0a34e10863 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 8 Jun 2010 16:43:24 -0600
Subject: [PATCH 07/14] Fix regression that broke sync between the cover
browser and the book list
---
src/calibre/gui2/library/views.py | 8 ++++++++
src/calibre/gui2/ui.py | 6 +++---
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py
index 6c3f04828e..109b001925 100644
--- a/src/calibre/gui2/library/views.py
+++ b/src/calibre/gui2/library/views.py
@@ -426,6 +426,14 @@ class BooksView(QTableView): # {{{
if dy != 0:
self.column_header.update()
+ def scroll_to_row(self, row):
+ if row > -1:
+ h = self.horizontalHeader()
+ for i in range(h.count()):
+ if not h.isSectionHidden(i):
+ self.scrollTo(self.model().index(row, i))
+ break
+
def close(self):
self._model.close()
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 6a1a508891..af33b2e997 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -800,7 +800,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
d.layout().addWidget(self.cover_flow)
self.cover_flow.setVisible(True)
self.cover_flow.setFocus(Qt.OtherFocusReason)
- self.library_view.scrollTo(self.library_view.currentIndex())
+ self.library_view.scroll_to_row(self.library_view.currentIndex().row())
d.show()
d.finished.connect(self.sidebar.external_cover_flow_finished)
self.cf_dialog = d
@@ -824,7 +824,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.library_view.currentIndex())
self.cover_flow.setVisible(True)
self.cover_flow.setFocus(Qt.OtherFocusReason)
- self.library_view.scrollTo(self.library_view.currentIndex())
+ self.library_view.scroll_to_row(self.library_view.currentIndex().row())
self.cover_flow_sync_timer.start(500)
else:
self.cover_flow_sync_timer.stop()
@@ -922,7 +922,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
index = m.index(row, 0)
if self.library_view.currentIndex().row() != row and index.isValid():
self.cover_flow_sync_flag = False
- self.library_view.scrollTo(index)
+ self.library_view.scroll_to_row(index.row())
sm = self.library_view.selectionModel()
sm.select(index, sm.ClearAndSelect|sm.Rows)
self.library_view.setCurrentIndex(index)
From 8a88c2a6200272f6828335b849b73b5df1ad66d1 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 8 Jun 2010 23:20:58 -0600
Subject: [PATCH 08/14] Rationalize various pieces of GUI initialization,
making the jobs button a self contained widget.Note cover browser is
currently broken.
---
src/calibre/gui2/cover_flow.py | 122 ++++-
src/calibre/gui2/init.py | 275 +++++++++++
src/calibre/gui2/jobs.py | 113 ++++-
src/calibre/gui2/pictureflow/pictureflow.sip | 4 +
src/calibre/gui2/sidebar.py | 99 +---
src/calibre/gui2/tag_view.py | 51 +-
src/calibre/gui2/ui.py | 488 ++-----------------
7 files changed, 567 insertions(+), 585 deletions(-)
create mode 100644 src/calibre/gui2/init.py
diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py
index 81d67ff7dd..e762caf11a 100644
--- a/src/calibre/gui2/cover_flow.py
+++ b/src/calibre/gui2/cover_flow.py
@@ -7,16 +7,17 @@ __docformat__ = 'restructuredtext en'
Module to implement the Cover Flow feature
'''
-import sys, os
+import sys, os, time
-from PyQt4.QtGui import QImage, QSizePolicy
-from PyQt4.QtCore import Qt, QSize, SIGNAL, QObject
+from PyQt4.Qt import QImage, QSizePolicy, QTimer, QDialog, Qt, QSize, \
+ QStackedLayout
from calibre import plugins
-from calibre.gui2 import config
+from calibre.gui2 import config, available_height, available_width
pictureflow, pictureflowerror = plugins['pictureflow']
if pictureflow is not None:
+
class EmptyImageList(pictureflow.FlowImages):
def __init__(self):
pictureflow.FlowImages.__init__(self)
@@ -51,7 +52,7 @@ if pictureflow is not None:
def __init__(self, model, buffer=20):
pictureflow.FlowImages.__init__(self)
self.model = model
- QObject.connect(self.model, SIGNAL('modelReset()'), self.reset)
+ self.model.modelReset.connect(self.reset)
def count(self):
return self.model.count()
@@ -66,7 +67,7 @@ if pictureflow is not None:
return ans
def reset(self):
- self.emit(SIGNAL('dataChanged()'))
+ self.dataChanged.emit()
def image(self, index):
return self.model.cover(index)
@@ -74,13 +75,14 @@ if pictureflow is not None:
class CoverFlow(pictureflow.PictureFlow):
- def __init__(self, height=300, parent=None, text_height=25):
+ def __init__(self, parent=None):
pictureflow.PictureFlow.__init__(self, parent,
config['cover_flow_queue_length']+1)
- self.setSlideSize(QSize(int(2/3. * height), height))
- self.setMinimumSize(QSize(int(2.35*0.67*height), (5/3.)*height+text_height))
+ self.setSlideSize(QSize(int(2/3. * 10), 10))
+ self.setMinimumSize(QSize(10, 10))
self.setFocusPolicy(Qt.WheelFocus)
- self.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum))
+ self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
+ QSizePolicy.Expanding))
def wheelEvent(self, ev):
ev.accept()
@@ -95,6 +97,104 @@ else:
DatabaseImages = None
FileSystemImages = None
+class CoverFlowMixin(object):
+
+ def __init__(self):
+ self.cover_flow = None
+ if CoverFlow is not None:
+ self.cf_last_updated_at = None
+ self.cover_flow_sync_timer = QTimer(self)
+ self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync)
+ self.cover_flow_sync_flag = True
+ self.cover_flow = CoverFlow(parent=self)
+ self.cover_flow.setVisible(False)
+ if not config['separate_cover_flow']:
+ self.cb_layout.addWidget(self.cover_flow)
+ self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
+ self.library_view.selectionModel().currentRowChanged.connect(
+ self.sync_cf_to_listview)
+ self.db_images = DatabaseImages(self.library_view.model())
+ self.cover_flow.setImages(self.db_images)
+ ah, aw = available_height(), available_width()
+ self._cb_layout_is_horizontal = float(aw)/ah >= 1.4
+ self.cb_layout.setDirection(self.cb_layout.LeftToRight if
+ self._cb_layout_is_horizontal else
+ self.cb_layout.TopToBottom)
+
+ def toggle_cover_flow_visibility(self, show):
+ if config['separate_cover_flow']:
+ if show:
+ d = QDialog(self)
+ ah, aw = available_height(), available_width()
+ d.resize(int(aw/2.), ah-60)
+ d._layout = QStackedLayout()
+ d.setLayout(d._layout)
+ d.setWindowTitle(_('Browse by covers'))
+ d.layout().addWidget(self.cover_flow)
+ self.cover_flow.setVisible(True)
+ self.cover_flow.setFocus(Qt.OtherFocusReason)
+ d.show()
+ d.finished.connect(self.sidebar.external_cover_flow_finished)
+ self.cf_dialog = d
+ else:
+ cfd = getattr(self, 'cf_dialog', None)
+ if cfd is not None:
+ self.cover_flow.setVisible(False)
+ cfd.hide()
+ self.cf_dialog = None
+ else:
+ if show:
+ self.cover_flow.setVisible(True)
+ self.cover_flow.setFocus(Qt.OtherFocusReason)
+ else:
+ self.cover_flow.setVisible(False)
+
+ def toggle_cover_flow(self, show):
+ if show:
+ self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
+ self.library_view.setCurrentIndex(
+ self.library_view.currentIndex())
+ self.cover_flow_sync_timer.start(500)
+ self.library_view.scroll_to_row(self.library_view.currentIndex().row())
+ else:
+ self.cover_flow_sync_timer.stop()
+ idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
+ if idx.isValid():
+ sm = self.library_view.selectionModel()
+ sm.select(idx, sm.ClearAndSelect|sm.Rows)
+ self.library_view.setCurrentIndex(idx)
+ self.library_view.scroll_to_row(idx.row())
+ self.toggle_cover_flow_visibility(show)
+
+ def sync_cf_to_listview(self, current, previous):
+ if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
+ self.cover_flow.currentSlide() != current.row():
+ self.cover_flow.setCurrentSlide(current.row())
+ self.cover_flow_sync_flag = True
+
+ def cover_flow_do_sync(self):
+ self.cover_flow_sync_flag = True
+ try:
+ if self.cover_flow.isVisible() and self.cf_last_updated_at is not None and \
+ time.time() - self.cf_last_updated_at > 0.5:
+ self.cf_last_updated_at = None
+ row = self.cover_flow.currentSlide()
+ m = self.library_view.model()
+ index = m.index(row, 0)
+ if self.library_view.currentIndex().row() != row and index.isValid():
+ self.cover_flow_sync_flag = False
+ self.library_view.scroll_to_row(index.row())
+ sm = self.library_view.selectionModel()
+ sm.select(index, sm.ClearAndSelect|sm.Rows)
+ self.library_view.setCurrentIndex(index)
+ except:
+ pass
+
+
+ def sync_listview_to_cf(self, row):
+ self.cf_last_updated_at = time.time()
+
+
def main(args=sys.argv):
return 0
@@ -108,7 +208,7 @@ if __name__ == '__main__':
path = sys.argv[1]
model = FileSystemImages(sys.argv[1])
cf.setImages(model)
- cf.connect(cf, SIGNAL('currentChanged(int)'), model.currentChanged)
+ cf.currentChanged[int].connect(model.currentChanged)
w.setCentralWidget(cf)
w.show()
diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py
new file mode 100644
index 0000000000..e6fbc460ef
--- /dev/null
+++ b/src/calibre/gui2/init.py
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
+
+__license__ = 'GPL v3'
+__copyright__ = '2010, Kovid Goyal '
+__docformat__ = 'restructuredtext en'
+
+import functools
+
+from PyQt4.Qt import QMenu, Qt, pyqtSignal, QToolButton, QIcon
+
+from calibre.utils.config import prefs
+from calibre.ebooks import BOOK_EXTENSIONS
+from calibre.constants import isosx
+from calibre.gui2 import config
+
+_keep_refs = []
+
+def partial(*args, **kwargs):
+ ans = functools.partial(*args, **kwargs)
+ _keep_refs.append(ans)
+ return ans
+
+class SaveMenu(QMenu): # {{{
+
+ save_fmt = pyqtSignal(object)
+
+ def __init__(self, parent):
+ QMenu.__init__(self, _('Save single format to disk...'), parent)
+ for ext in sorted(BOOK_EXTENSIONS):
+ action = self.addAction(ext.upper())
+ setattr(self, 'do_'+ext, partial(self.do, ext))
+ action.triggered.connect(
+ getattr(self, 'do_'+ext))
+
+ def do(self, ext, *args):
+ self.save_fmt.emit(ext)
+
+# }}}
+
+class ToolbarMixin(object): # {{{
+
+ def __init__(self):
+ md = QMenu()
+ md.addAction(_('Edit metadata individually'),
+ partial(self.edit_metadata, False))
+ md.addSeparator()
+ md.addAction(_('Edit metadata in bulk'),
+ partial(self.edit_metadata, False, bulk=True))
+ md.addSeparator()
+ md.addAction(_('Download metadata and covers'),
+ partial(self.download_metadata, False, covers=True))
+ md.addAction(_('Download only metadata'),
+ partial(self.download_metadata, False, covers=False))
+ md.addAction(_('Download only covers'),
+ partial(self.download_metadata, False, covers=True,
+ set_metadata=False, set_social_metadata=False))
+ md.addAction(_('Download only social metadata'),
+ partial(self.download_metadata, False, covers=False,
+ set_metadata=False, set_social_metadata=True))
+ self.metadata_menu = md
+
+ mb = QMenu()
+ mb.addAction(_('Merge into first selected book - delete others'),
+ self.merge_books)
+ mb.addSeparator()
+ mb.addAction(_('Merge into first selected book - keep others'),
+ partial(self.merge_books, safe_merge=True))
+ self.merge_menu = mb
+ self.action_merge.setMenu(mb)
+ md.addSeparator()
+ md.addAction(self.action_merge)
+
+ self.add_menu = QMenu()
+ self.add_menu.addAction(_('Add books from a single directory'),
+ self.add_books)
+ self.add_menu.addAction(_('Add books from directories, including '
+ 'sub-directories (One book per directory, assumes every ebook '
+ 'file is the same book in a different format)'),
+ self.add_recursive_single)
+ self.add_menu.addAction(_('Add books from directories, including '
+ 'sub directories (Multiple books per directory, assumes every '
+ 'ebook file is a different book)'), self.add_recursive_multiple)
+ self.add_menu.addAction(_('Add Empty book. (Book entry with no '
+ 'formats)'), self.add_empty)
+ self.action_add.setMenu(self.add_menu)
+ self.action_add.triggered.connect(self.add_books)
+ self.action_del.triggered.connect(self.delete_books)
+ self.action_edit.triggered.connect(self.edit_metadata)
+ self.action_merge.triggered.connect(self.merge_books)
+
+ self.action_save.triggered.connect(self.save_to_disk)
+ self.save_menu = QMenu()
+ self.save_menu.addAction(_('Save to disk'), partial(self.save_to_disk,
+ False))
+ self.save_menu.addAction(_('Save to disk in a single directory'),
+ partial(self.save_to_single_dir, False))
+ self.save_menu.addAction(_('Save only %s format to disk')%
+ prefs['output_format'].upper(),
+ partial(self.save_single_format_to_disk, False))
+ self.save_menu.addAction(
+ _('Save only %s format to disk in a single directory')%
+ prefs['output_format'].upper(),
+ partial(self.save_single_fmt_to_single_dir, False))
+ self.save_sub_menu = SaveMenu(self)
+ self.save_menu.addMenu(self.save_sub_menu)
+ self.save_sub_menu.save_fmt.connect(self.save_specific_format_disk)
+
+ self.action_view.triggered.connect(self.view_book)
+ self.view_menu = QMenu()
+ self.view_menu.addAction(_('View'), partial(self.view_book, False))
+ ac = self.view_menu.addAction(_('View specific format'))
+ ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
+ self.action_view.setMenu(self.view_menu)
+ ac.triggered.connect(self.view_specific_format, type=Qt.QueuedConnection)
+
+ self.delete_menu = QMenu()
+ self.delete_menu.addAction(_('Remove selected books'), self.delete_books)
+ self.delete_menu.addAction(
+ _('Remove files of a specific format from selected books..'),
+ self.delete_selected_formats)
+ self.delete_menu.addAction(
+ _('Remove all formats from selected books, except...'),
+ self.delete_all_but_selected_formats)
+ self.delete_menu.addAction(
+ _('Remove covers from selected books'), self.delete_covers)
+ self.action_del.setMenu(self.delete_menu)
+
+ self.action_open_containing_folder.setShortcut(Qt.Key_O)
+ self.addAction(self.action_open_containing_folder)
+ self.action_sync.setShortcut(Qt.Key_D)
+ self.action_sync.setEnabled(True)
+ self.create_device_menu()
+ self.action_sync.triggered.connect(
+ self._sync_action_triggered)
+
+ self.action_edit.setMenu(md)
+ self.action_save.setMenu(self.save_menu)
+
+ cm = QMenu()
+ cm.addAction(_('Convert individually'), partial(self.convert_ebook,
+ False, bulk=False))
+ cm.addAction(_('Bulk convert'),
+ partial(self.convert_ebook, False, bulk=True))
+ cm.addSeparator()
+ ac = cm.addAction(
+ _('Create catalog of books in your calibre library'))
+ ac.triggered.connect(self.generate_catalog)
+ self.action_convert.setMenu(cm)
+ self.action_convert.triggered.connect(self.convert_ebook)
+ self.convert_menu = cm
+
+ pm = QMenu()
+ ap = self.action_preferences
+ pm.addAction(ap)
+ pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'),
+ self.run_wizard)
+ self.action_preferences.setMenu(pm)
+ self.preferences_menu = pm
+ for x in (self.preferences_action, self.action_preferences):
+ x.triggered.connect(self.do_config)
+
+ for x in ('news', 'edit', 'sync', 'convert', 'save', 'add', 'view',
+ 'del', 'preferences'):
+ w = self.tool_bar.widgetForAction(getattr(self, 'action_'+x))
+ w.setPopupMode(w.MenuButtonPopup)
+
+ self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
+
+ for ch in self.tool_bar.children():
+ if isinstance(ch, QToolButton):
+ ch.setCursor(Qt.PointingHandCursor)
+
+ self.tool_bar.contextMenuEvent = self.no_op
+
+ def read_toolbar_settings(self):
+ self.tool_bar.setIconSize(config['toolbar_icon_size'])
+ self.tool_bar.setToolButtonStyle(
+ Qt.ToolButtonTextUnderIcon if \
+ config['show_text_in_toolbar'] else \
+ Qt.ToolButtonIconOnly)
+
+# }}}
+
+class LibraryViewMixin(object): # {{{
+
+ def __init__(self, db):
+ similar_menu = QMenu(_('Similar books...'))
+ similar_menu.addAction(self.action_books_by_same_author)
+ similar_menu.addAction(self.action_books_in_this_series)
+ similar_menu.addAction(self.action_books_with_the_same_tags)
+ similar_menu.addAction(self.action_books_by_this_publisher)
+ self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
+ self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
+ self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
+ self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
+ self.addAction(self.action_books_by_same_author)
+ self.addAction(self.action_books_by_this_publisher)
+ self.addAction(self.action_books_in_this_series)
+ self.addAction(self.action_books_with_the_same_tags)
+ self.similar_menu = similar_menu
+ self.action_books_by_same_author.triggered.connect(
+ partial(self.show_similar_books, 'authors'))
+ self.action_books_in_this_series.triggered.connect(
+ partial(self.show_similar_books, 'series'))
+ self.action_books_with_the_same_tags.triggered.connect(
+ partial(self.show_similar_books, 'tag'))
+ self.action_books_by_this_publisher.triggered.connect(
+ partial(self.show_similar_books, 'publisher'))
+ self.library_view.set_context_menu(self.action_edit, self.action_sync,
+ self.action_convert, self.action_view,
+ self.action_save,
+ self.action_open_containing_folder,
+ self.action_show_book_details,
+ self.action_del,
+ similar_menu=similar_menu)
+
+ self.memory_view.set_context_menu(None, None, None,
+ self.action_view, self.action_save, None, None, self.action_del)
+ self.card_a_view.set_context_menu(None, None, None,
+ self.action_view, self.action_save, None, None, self.action_del)
+ self.card_b_view.set_context_menu(None, None, None,
+ self.action_view, self.action_save, None, None, self.action_del)
+
+ self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
+ for func, args in [
+ ('connect_to_search_box', (self.search,
+ self.search_done)),
+ ('connect_to_book_display',
+ (self.status_bar.book_info.show_data,)),
+ ]:
+ for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
+ getattr(view, func)(*args)
+
+ self.memory_view.connect_dirtied_signal(self.upload_booklists)
+ self.card_a_view.connect_dirtied_signal(self.upload_booklists)
+ self.card_b_view.connect_dirtied_signal(self.upload_booklists)
+
+ self.book_on_device(None, reset=True)
+ db.set_book_on_device_func(self.book_on_device)
+ self.library_view.set_database(db)
+ self.library_view.model().set_book_on_device_func(self.book_on_device)
+ prefs['library_path'] = self.library_path
+
+ def show_similar_books(self, type, *args):
+ search, join = [], ' '
+ idx = self.library_view.currentIndex()
+ if not idx.isValid():
+ return
+ row = idx.row()
+ if type == 'series':
+ series = idx.model().db.series(row)
+ if series:
+ search = ['series:"'+series+'"']
+ elif type == 'publisher':
+ publisher = idx.model().db.publisher(row)
+ if publisher:
+ search = ['publisher:"'+publisher+'"']
+ elif type == 'tag':
+ tags = idx.model().db.tags(row)
+ if tags:
+ search = ['tag:"='+t+'"' for t in tags.split(',')]
+ elif type in ('author', 'authors'):
+ authors = idx.model().db.authors(row)
+ if authors:
+ search = ['author:"='+a.strip().replace('|', ',')+'"' \
+ for a in authors.split(',')]
+ join = ' or '
+ if search:
+ self.search.set_search_string(join.join(search))
+
+
+
+ # }}}
+
diff --git a/src/calibre/gui2/jobs.py b/src/calibre/gui2/jobs.py
index 0b54ce2406..adf938c435 100644
--- a/src/calibre/gui2/jobs.py
+++ b/src/calibre/gui2/jobs.py
@@ -7,11 +7,14 @@ __docformat__ = 'restructuredtext en'
Job management.
'''
+import re
+
from Queue import Empty, Queue
from PyQt4.Qt import QAbstractTableModel, QVariant, QModelIndex, Qt, \
- QTimer, SIGNAL, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
- QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip
+ QTimer, pyqtSignal, QIcon, QDialog, QAbstractItemDelegate, QApplication, \
+ QSize, QStyleOptionProgressBarV2, QString, QStyle, QToolTip, QFrame, \
+ QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QCoreApplication
from calibre.utils.ipc.server import Server
from calibre.utils.ipc.job import ParallelJob
@@ -20,9 +23,13 @@ from calibre.gui2.device import DeviceJob
from calibre.gui2.dialogs.jobs_ui import Ui_JobsDialog
from calibre import __appname__
from calibre.gui2.dialogs.job_view_ui import Ui_Dialog
+from calibre.gui2.progress_indicator import ProgressIndicator
class JobManager(QAbstractTableModel):
+ job_added = pyqtSignal(int)
+ job_done = pyqtSignal(int)
+
def __init__(self):
QAbstractTableModel.__init__(self)
self.wait_icon = QVariant(QIcon(I('jobs.svg')))
@@ -37,8 +44,7 @@ class JobManager(QAbstractTableModel):
self.changed_queue = Queue()
self.timer = QTimer(self)
- self.connect(self.timer, SIGNAL('timeout()'), self.update,
- Qt.QueuedConnection)
+ self.timer.timeout.connect(self.update, type=Qt.QueuedConnection)
self.timer.start(1000)
def columnCount(self, parent=QModelIndex()):
@@ -130,8 +136,7 @@ class JobManager(QAbstractTableModel):
for i, j in enumerate(self.jobs):
if j.run_state == j.RUNNING:
idx = self.index(i, 3)
- self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
- idx, idx)
+ self.dataChanged.emit(idx, idx)
# Update parallel jobs
jobs = set([])
@@ -157,20 +162,19 @@ class JobManager(QAbstractTableModel):
self.jobs.sort()
self.reset()
if job.is_finished:
- self.emit(SIGNAL('job_done(int)'), len(self.unfinished_jobs()))
+ self.job_done.emit(len(self.unfinished_jobs()))
else:
for job in jobs:
idx = self.jobs.index(job)
- self.emit(SIGNAL('dataChanged(QModelIndex,QModelIndex)'),
+ self.dataChanged.emit(
self.index(idx, 0), self.index(idx, 3))
def _add_job(self, job):
- self.emit(SIGNAL('layoutAboutToBeChanged()'))
+ self.layoutAboutToBeChanged.emit()
self.jobs.append(job)
self.jobs.sort()
- self.emit(SIGNAL('job_added(int)'), len(self.unfinished_jobs()))
- self.emit(SIGNAL('layoutChanged()'))
+ self.job_added.emit(len(self.unfinished_jobs()))
def done_jobs(self):
return [j for j in self.jobs if j.is_finished]
@@ -266,6 +270,76 @@ class DetailView(QDialog, Ui_Dialog):
if more:
self.log.appendPlainText(more.decode('utf-8', 'replace'))
+class JobsButton(QFrame):
+
+ def __init__(self, horizontal=False, size=48, parent=None):
+ QFrame.__init__(self, parent)
+ self.pi = ProgressIndicator(self, size)
+ self._jobs = QLabel(''+_('Jobs:')+' 0')
+
+ if horizontal:
+ self.setLayout(QHBoxLayout())
+ else:
+ self.setLayout(QVBoxLayout())
+ self._jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
+
+ self.layout().addWidget(self.pi)
+ self.layout().addWidget(self._jobs)
+ if not horizontal:
+ self.layout().setAlignment(self._jobs, Qt.AlignHCenter)
+ self._jobs.setMargin(0)
+ self.layout().setMargin(0)
+ self._jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
+ self.setCursor(Qt.PointingHandCursor)
+ self.setToolTip(_('Click to see list of active jobs.'))
+
+ def initialize(self, jobs_dialog, job_manager):
+ self.jobs_dialog = jobs_dialog
+ job_manager.job_added.connect(self.job_added)
+ job_manager.job_done.connect(self.job_done)
+
+
+ def mouseReleaseEvent(self, event):
+ if self.jobs_dialog.isVisible():
+ self.jobs_dialog.hide()
+ else:
+ self.jobs_dialog.show()
+
+ @property
+ def is_running(self):
+ return self.pi.isAnimated()
+
+ def start(self):
+ self.pi.startAnimation()
+
+ def stop(self):
+ self.pi.stopAnimation()
+
+ def jobs(self):
+ src = unicode(self._jobs.text())
+ return int(re.search(r'\d+', src).group())
+
+ def job_added(self, nnum):
+ jobs = self._jobs
+ src = unicode(jobs.text())
+ num = self.jobs()
+ text = src.replace(str(num), str(nnum))
+ jobs.setText(text)
+ self.start()
+
+ def job_done(self, nnum):
+ jobs = self._jobs
+ src = unicode(jobs.text())
+ num = self.jobs()
+ text = src.replace(str(num), str(nnum))
+ jobs.setText(text)
+ if nnum == 0:
+ self.no_more_jobs()
+
+ def no_more_jobs(self):
+ if self.is_running:
+ self.stop()
+ QCoreApplication.instance().alert(self, 5000)
class JobsDialog(QDialog, Ui_JobsDialog):
@@ -278,14 +352,9 @@ class JobsDialog(QDialog, Ui_JobsDialog):
self.model = model
self.setWindowModality(Qt.NonModal)
self.setWindowTitle(__appname__ + _(' - Jobs'))
- self.connect(self.kill_button, SIGNAL('clicked()'),
- self.kill_job)
- self.connect(self.details_button, SIGNAL('clicked()'),
- self.show_details)
- self.connect(self.stop_all_jobs_button, SIGNAL('clicked()'),
- self.kill_all_jobs)
- self.connect(self, SIGNAL('kill_job(int, PyQt_PyObject)'),
- self.jobs_view.model().kill_job)
+ self.kill_button.clicked.connect(self.kill_job)
+ self.details_button.clicked.connect(self.show_details)
+ self.stop_all_jobs_button.clicked.connect(self.kill_all_jobs)
self.pb_delegate = ProgressBarDelegate(self)
self.jobs_view.setItemDelegateForColumn(2, self.pb_delegate)
self.jobs_view.doubleClicked.connect(self.show_job_details)
@@ -304,18 +373,18 @@ class JobsDialog(QDialog, Ui_JobsDialog):
d.exec_()
d.timer.stop()
- def kill_job(self):
+ def kill_job(self, *args):
for index in self.jobs_view.selectedIndexes():
row = index.row()
self.model.kill_job(row, self)
return
- def show_details(self):
+ def show_details(self, *args):
for index in self.jobs_view.selectedIndexes():
self.show_job_details(index)
return
- def kill_all_jobs(self):
+ def kill_all_jobs(self, *args):
self.model.kill_all_jobs()
def closeEvent(self, e):
diff --git a/src/calibre/gui2/pictureflow/pictureflow.sip b/src/calibre/gui2/pictureflow/pictureflow.sip
index c636529e05..9202dd8ad5 100644
--- a/src/calibre/gui2/pictureflow/pictureflow.sip
+++ b/src/calibre/gui2/pictureflow/pictureflow.sip
@@ -16,6 +16,10 @@ public:
virtual int count();
virtual QImage image(int index);
virtual QString caption(int index);
+
+signals:
+ void dataChanged();
+
};
diff --git a/src/calibre/gui2/sidebar.py b/src/calibre/gui2/sidebar.py
index bd305912a0..6710b5d471 100644
--- a/src/calibre/gui2/sidebar.py
+++ b/src/calibre/gui2/sidebar.py
@@ -5,78 +5,13 @@ __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal '
__docformat__ = 'restructuredtext en'
-import re
from functools import partial
from PyQt4.Qt import QToolBar, Qt, QIcon, QSizePolicy, QWidget, \
- QFrame, QVBoxLayout, QLabel, QSize, QCoreApplication, QToolButton
+ QSize, QToolButton
-from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2 import dynamic
-class JobsButton(QFrame):
-
- def __init__(self, parent):
- QFrame.__init__(self, parent)
- self.setLayout(QVBoxLayout())
- self.pi = ProgressIndicator(self)
- self.layout().addWidget(self.pi)
- self.jobs = QLabel(''+_('Jobs:')+' 0')
- self.jobs.setAlignment(Qt.AlignHCenter|Qt.AlignBottom)
- self.layout().addWidget(self.jobs)
- self.layout().setAlignment(self.jobs, Qt.AlignHCenter)
- self.jobs.setMargin(0)
- self.layout().setMargin(0)
- self.jobs.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
- self.setCursor(Qt.PointingHandCursor)
- self.setToolTip(_('Click to see list of active jobs.'))
-
- def initialize(self, jobs_dialog):
- self.jobs_dialog = jobs_dialog
-
- def mouseReleaseEvent(self, event):
- if self.jobs_dialog.isVisible():
- self.jobs_dialog.hide()
- else:
- self.jobs_dialog.show()
-
- @property
- def is_running(self):
- return self.pi.isAnimated()
-
- def start(self):
- self.pi.startAnimation()
-
- def stop(self):
- self.pi.stopAnimation()
-
-
-class Jobs(ProgressIndicator):
-
- def initialize(self, jobs_dialog):
- self.jobs_dialog = jobs_dialog
-
- def mouseClickEvent(self, event):
- if self.jobs_dialog.isVisible():
- self.jobs_dialog.jobs_view.write_settings()
- self.jobs_dialog.hide()
- else:
- self.jobs_dialog.jobs_view.read_settings()
- self.jobs_dialog.show()
- self.jobs_dialog.jobs_view.restore_column_widths()
-
- @property
- def is_running(self):
- return self.isAnimated()
-
- def start(self):
- self.startAnimation()
-
- def stop(self):
- self.stopAnimation()
-
-
-
class SideBar(QToolBar):
toggle_texts = {
@@ -114,8 +49,6 @@ class SideBar(QToolBar):
self.spacer = QWidget(self)
self.spacer.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Expanding)
self.addWidget(self.spacer)
- self.jobs_button = JobsButton(self)
- self.addWidget(self.jobs_button)
self.show_cover_browser = partial(self._toggle_cover_browser, show=True)
self.hide_cover_browser = partial(self._toggle_cover_browser,
@@ -124,9 +57,8 @@ class SideBar(QToolBar):
if isinstance(ch, QToolButton):
ch.setCursor(Qt.PointingHandCursor)
- def initialize(self, jobs_dialog, cover_browser, toggle_cover_browser,
+ def initialize(self, jobs_button, cover_browser, toggle_cover_browser,
cover_browser_error, vertical_splitter, horizontal_splitter):
- self.jobs_button.initialize(jobs_dialog)
self.cover_browser, self.do_toggle_cover_browser = cover_browser, \
toggle_cover_browser
if self.cover_browser is None:
@@ -166,6 +98,7 @@ class SideBar(QToolBar):
'book_info'), type=Qt.QueuedConnection)
self.horizontal_splitter.state_changed.connect(partial(self.view_status_changed,
'tag_browser'), type=Qt.QueuedConnection)
+ self.addWidget(jobs_button)
@@ -211,30 +144,4 @@ class SideBar(QToolBar):
def _toggle_book_info(self, show=None):
self.vertical_splitter.toggle_side_index()
- def jobs(self):
- src = unicode(self.jobs_button.jobs.text())
- return int(re.search(r'\d+', src).group())
-
- def job_added(self, nnum):
- jobs = self.jobs_button.jobs
- src = unicode(jobs.text())
- num = self.jobs()
- text = src.replace(str(num), str(nnum))
- jobs.setText(text)
- self.jobs_button.start()
-
- def job_done(self, nnum):
- jobs = self.jobs_button.jobs
- src = unicode(jobs.text())
- num = self.jobs()
- text = src.replace(str(num), str(nnum))
- jobs.setText(text)
- if nnum == 0:
- self.no_more_jobs()
-
- def no_more_jobs(self):
- if self.jobs_button.is_running:
- self.jobs_button.stop()
- QCoreApplication.instance().alert(self, 5000)
-
diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py
index e8f3068d35..b5ced8d626 100644
--- a/src/calibre/gui2/tag_view.py
+++ b/src/calibre/gui2/tag_view.py
@@ -18,6 +18,8 @@ from calibre.utils.config import prefs
from calibre.library.field_metadata import TagsIcons
from calibre.utils.search_query_parser import saved_searches
from calibre.gui2 import error_dialog
+from calibre.gui2.dialogs.tag_categories import TagCategories
+from calibre.gui2.dialogs.tag_list_editor import TagListEditor
class TagsView(QTreeView): # {{{
@@ -29,12 +31,16 @@ class TagsView(QTreeView): # {{{
tag_item_renamed = pyqtSignal()
search_item_renamed = pyqtSignal()
- def __init__(self, *args):
- QTreeView.__init__(self, *args)
+ def __init__(self, parent=None):
+ QTreeView.__init__(self, parent=None)
+ self.tag_match = None
self.setUniformRowHeights(True)
self.setCursor(Qt.PointingHandCursor)
self.setIconSize(QSize(30, 30))
- self.tag_match = None
+ self.setTabKeyNavigation(True)
+ self.setAlternatingRowColors(True)
+ self.setAnimated(True)
+ self.setHeaderHidden(True)
def set_database(self, db, tag_match, popularity):
self.hidden_categories = config['tag_browser_hidden_categories']
@@ -588,3 +594,42 @@ class TagsModel(QAbstractItemModel): # {{{
# }}}
+class TagBrowserMixin(object): # {{{
+
+ def __init__(self, db):
+ self.tags_view.set_database(self.library_view.model().db,
+ self.tag_match, self.popularity)
+ self.tags_view.tags_marked.connect(self.search.search_from_tags)
+ self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
+ self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
+ self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
+ self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
+ self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
+ self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
+
+ def do_user_categories_edit(self, on_category=None):
+ d = TagCategories(self, self.library_view.model().db, on_category)
+ d.exec_()
+ if d.result() == d.Accepted:
+ self.tags_view.set_new_model()
+ self.tags_view.recount()
+
+ def do_tags_list_edit(self, tag, category):
+ d = TagListEditor(self, self.library_view.model().db, tag, category)
+ d.exec_()
+ if d.result() == d.Accepted:
+ # Clean up everything, as information could have changed for many books.
+ self.library_view.model().refresh()
+ self.tags_view.set_new_model()
+ self.tags_view.recount()
+ self.saved_search.clear_to_help()
+ self.search.clear_to_help()
+
+ def do_tag_item_renamed(self):
+ # Clean up library view and search
+ self.library_view.model().refresh()
+ self.saved_search.clear_to_help()
+ self.search.clear_to_help()
+
+# }}}
+
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index af33b2e997..6db3fe217d 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -16,9 +16,9 @@ from threading import Thread
from functools import partial
from PyQt4.Qt import Qt, SIGNAL, QObject, QUrl, QTimer, \
QModelIndex, QPixmap, QColor, QPainter, QMenu, QIcon, \
- QToolButton, QDialog, QDesktopServices, \
+ QDialog, QDesktopServices, \
QSystemTrayIcon, QApplication, QKeySequence, QAction, \
- QMessageBox, QStackedLayout, QHelpEvent, QInputDialog,\
+ QMessageBox, QHelpEvent, QInputDialog,\
QThread, pyqtSignal
from PyQt4.QtSvg import QSvgRenderer
@@ -35,10 +35,9 @@ from calibre.gui2 import warning_dialog, choose_files, error_dialog, \
question_dialog,\
pixmap_to_data, choose_dir, \
Dispatcher, gprefs, \
- available_height, \
max_available_height, config, info_dialog, \
- available_width, GetMetadata
-from calibre.gui2.cover_flow import CoverFlow, DatabaseImages, pictureflowerror
+ GetMetadata
+from calibre.gui2.cover_flow import pictureflowerror, CoverFlowMixin
from calibre.gui2.widgets import ProgressIndicator, IMAGE_EXTENSIONS
from calibre.gui2.wizard import move_library
from calibre.gui2.dialogs.scheduler import Scheduler
@@ -46,7 +45,7 @@ from calibre.gui2.update import CheckForUpdates
from calibre.gui2.main_window import MainWindow
from calibre.gui2.main_ui import Ui_MainWindow
from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
-from calibre.gui2.jobs import JobManager, JobsDialog
+from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
from calibre.gui2.tools import convert_single_ebook, convert_bulk_ebook, \
@@ -60,24 +59,11 @@ from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag, NavigableString
from calibre.library.database2 import LibraryDatabase2
from calibre.library.caches import CoverCache
from calibre.gui2.dialogs.confirm_delete import confirm
-from calibre.gui2.dialogs.tag_categories import TagCategories
-from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
+from calibre.gui2.tag_view import TagBrowserMixin
+from calibre.gui2.init import ToolbarMixin, LibraryViewMixin
-class SaveMenu(QMenu):
-
- def __init__(self, parent):
- QMenu.__init__(self, _('Save single format to disk...'), parent)
- for ext in sorted(BOOK_EXTENSIONS):
- action = self.addAction(ext.upper())
- setattr(self, 'do_'+ext, partial(self.do, ext))
- self.connect(action, SIGNAL('triggered(bool)'),
- getattr(self, 'do_'+ext))
-
- def do(self, ext, *args):
- self.emit(SIGNAL('save_fmt(PyQt_PyObject)'), ext)
-
-class Listener(Thread):
+class Listener(Thread): # {{{
def __init__(self, listener):
Thread.__init__(self)
@@ -102,7 +88,9 @@ class Listener(Thread):
except:
pass
-class SystemTrayIcon(QSystemTrayIcon):
+# }}}
+
+class SystemTrayIcon(QSystemTrayIcon): # {{{
def __init__(self, icon, parent):
QSystemTrayIcon.__init__(self, icon, parent)
@@ -115,7 +103,10 @@ class SystemTrayIcon(QSystemTrayIcon):
return True
return QSystemTrayIcon.event(self, ev)
-class Main(MainWindow, Ui_MainWindow, DeviceGUI):
+# }}}
+
+class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
+ TagBrowserMixin, CoverFlowMixin, LibraryViewMixin):
'The main GUI'
def set_default_thumbnail(self, height):
@@ -234,7 +225,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.connect(self.system_tray_icon,
SIGNAL('activated(QSystemTrayIcon::ActivationReason)'),
self.system_tray_icon_activated)
- self.tool_bar.contextMenuEvent = self.no_op
+ QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
+ self.do_advanced_search)
####################### Start spare job server ########################
QTimer.singleShot(1000, self.add_spare_server)
@@ -277,281 +269,22 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.status_bar.files_dropped.connect(self.files_dropped_on_book)
####################### Setup Toolbar #####################
- md = QMenu()
- md.addAction(_('Edit metadata individually'))
- md.addSeparator()
- md.addAction(_('Edit metadata in bulk'))
- md.addSeparator()
- md.addAction(_('Download metadata and covers'))
- md.addAction(_('Download only metadata'))
- md.addAction(_('Download only covers'))
- md.addAction(_('Download only social metadata'))
- self.metadata_menu = md
-
- mb = QMenu()
- mb.addAction(_('Merge into first selected book - delete others'))
- mb.addSeparator()
- mb.addAction(_('Merge into first selected book - keep others'))
- self.merge_menu = mb
- self.action_merge.setMenu(mb)
- md.addSeparator()
- md.addAction(self.action_merge)
-
- self.add_menu = QMenu()
- self.add_menu.addAction(_('Add books from a single directory'))
- self.add_menu.addAction(_('Add books from directories, including '
- 'sub-directories (One book per directory, assumes every ebook '
- 'file is the same book in a different format)'))
- self.add_menu.addAction(_('Add books from directories, including '
- 'sub directories (Multiple books per directory, assumes every '
- 'ebook file is a different book)'))
- self.add_menu.addAction(_('Add Empty book. (Book entry with no '
- 'formats)'))
- self.action_add.setMenu(self.add_menu)
- QObject.connect(self.action_add, SIGNAL("triggered(bool)"),
- self.add_books)
- QObject.connect(self.add_menu.actions()[0], SIGNAL("triggered(bool)"),
- self.add_books)
- QObject.connect(self.add_menu.actions()[1], SIGNAL("triggered(bool)"),
- self.add_recursive_single)
- QObject.connect(self.add_menu.actions()[2], SIGNAL("triggered(bool)"),
- self.add_recursive_multiple)
- QObject.connect(self.add_menu.actions()[3], SIGNAL('triggered(bool)'),
- self.add_empty)
- QObject.connect(self.action_del, SIGNAL("triggered(bool)"),
- self.delete_books)
- QObject.connect(self.action_edit, SIGNAL("triggered(bool)"),
- self.edit_metadata)
- self.__em1__ = partial(self.edit_metadata, bulk=False)
- QObject.connect(md.actions()[0], SIGNAL('triggered(bool)'),
- self.__em1__)
- self.__em2__ = partial(self.edit_metadata, bulk=True)
- QObject.connect(md.actions()[2], SIGNAL('triggered(bool)'),
- self.__em2__)
- self.__em3__ = partial(self.download_metadata, covers=True)
- QObject.connect(md.actions()[4], SIGNAL('triggered(bool)'),
- self.__em3__)
- self.__em4__ = partial(self.download_metadata, covers=False)
- QObject.connect(md.actions()[5], SIGNAL('triggered(bool)'),
- self.__em4__)
- self.__em5__ = partial(self.download_metadata, covers=True,
- set_metadata=False, set_social_metadata=False)
- QObject.connect(md.actions()[6], SIGNAL('triggered(bool)'),
- self.__em5__)
- self.__em6__ = partial(self.download_metadata, covers=False,
- set_metadata=False, set_social_metadata=True)
- QObject.connect(md.actions()[7], SIGNAL('triggered(bool)'),
- self.__em6__)
-
- QObject.connect(self.action_merge, SIGNAL("triggered(bool)"),
- self.merge_books)
- QObject.connect(mb.actions()[0], SIGNAL('triggered(bool)'),
- self.merge_books)
- self.__mb1__ = partial(self.merge_books, safe_merge=True)
- QObject.connect(mb.actions()[2], SIGNAL('triggered(bool)'),
- self.__mb1__)
-
- self.save_menu = QMenu()
- self.save_menu.addAction(_('Save to disk'))
- self.save_menu.addAction(_('Save to disk in a single directory'))
- self.save_menu.addAction(_('Save only %s format to disk')%
- prefs['output_format'].upper())
- self.save_menu.addAction(
- _('Save only %s format to disk in a single directory')%
- prefs['output_format'].upper())
-
- self.save_sub_menu = SaveMenu(self)
- self.save_menu.addMenu(self.save_sub_menu)
- self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'),
- self.save_specific_format_disk)
-
- self.view_menu = QMenu()
- self.view_menu.addAction(_('View'))
- ac = self.view_menu.addAction(_('View specific format'))
- ac.setShortcut((Qt.ControlModifier if isosx else Qt.AltModifier)+Qt.Key_V)
- self.action_view.setMenu(self.view_menu)
-
- self.delete_menu = QMenu()
- self.delete_menu.addAction(_('Remove selected books'))
- self.delete_menu.addAction(
- _('Remove files of a specific format from selected books..'))
- self.delete_menu.addAction(
- _('Remove all formats from selected books, except...'))
- self.delete_menu.addAction(
- _('Remove covers from selected books'))
- self.action_del.setMenu(self.delete_menu)
- QObject.connect(self.action_save, SIGNAL("triggered(bool)"),
- self.save_to_disk)
- QObject.connect(self.save_menu.actions()[0], SIGNAL("triggered(bool)"),
- self.save_to_disk)
- QObject.connect(self.save_menu.actions()[1], SIGNAL("triggered(bool)"),
- self.save_to_single_dir)
- QObject.connect(self.save_menu.actions()[2], SIGNAL("triggered(bool)"),
- self.save_single_format_to_disk)
- QObject.connect(self.save_menu.actions()[3], SIGNAL("triggered(bool)"),
- self.save_single_fmt_to_single_dir)
- QObject.connect(self.action_view, SIGNAL("triggered(bool)"),
- self.view_book)
- QObject.connect(self.view_menu.actions()[0],
- SIGNAL("triggered(bool)"), self.view_book)
- QObject.connect(self.view_menu.actions()[1],
- SIGNAL("triggered(bool)"), self.view_specific_format,
- Qt.QueuedConnection)
- self.connect(self.action_open_containing_folder,
- SIGNAL('triggered(bool)'), self.view_folder)
-
- self.delete_menu.actions()[0].triggered.connect(self.delete_books)
- self.delete_menu.actions()[1].triggered.connect(self.delete_selected_formats)
- self.delete_menu.actions()[2].triggered.connect(self.delete_all_but_selected_formats)
- self.delete_menu.actions()[3].triggered.connect(self.delete_covers)
-
- self.action_open_containing_folder.setShortcut(Qt.Key_O)
- self.addAction(self.action_open_containing_folder)
- self.action_sync.setShortcut(Qt.Key_D)
- self.action_sync.setEnabled(True)
- self.create_device_menu()
- self.connect(self.action_sync, SIGNAL('triggered(bool)'),
- self._sync_action_triggered)
-
- self.action_edit.setMenu(md)
- self.action_save.setMenu(self.save_menu)
-
- cm = QMenu()
- cm.addAction(_('Convert individually'))
- cm.addAction(_('Bulk convert'))
- cm.addSeparator()
- ac = cm.addAction(
- _('Create catalog of books in your calibre library'))
- ac.triggered.connect(self.generate_catalog)
- self.action_convert.setMenu(cm)
- self._convert_single_hook = partial(self.convert_ebook, bulk=False)
- QObject.connect(cm.actions()[0],
- SIGNAL('triggered(bool)'), self._convert_single_hook)
- self._convert_bulk_hook = partial(self.convert_ebook, bulk=True)
- QObject.connect(cm.actions()[1],
- SIGNAL('triggered(bool)'), self._convert_bulk_hook)
- QObject.connect(self.action_convert,
- SIGNAL('triggered(bool)'), self.convert_ebook)
- self.convert_menu = cm
-
- pm = QMenu()
- ap = self.action_preferences
- pm.addAction(ap.icon(), ap.text())
- pm.addAction(QIcon(I('wizard.svg')), _('Run welcome wizard'))
- self.connect(pm.actions()[0], SIGNAL('triggered(bool)'),
- self.do_config)
- self.connect(pm.actions()[1], SIGNAL('triggered(bool)'),
- self.run_wizard)
- self.action_preferences.setMenu(pm)
- self.preferences_menu = pm
-
- self.tool_bar.widgetForAction(self.action_news).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_edit).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_sync).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_convert).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_save).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_add).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_view).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_del).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.widgetForAction(self.action_preferences).\
- setPopupMode(QToolButton.MenuButtonPopup)
- self.tool_bar.setContextMenuPolicy(Qt.PreventContextMenu)
-
- self.connect(self.preferences_action, SIGNAL('triggered(bool)'),
- self.do_config)
- self.connect(self.action_preferences, SIGNAL('triggered(bool)'),
- self.do_config)
- QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
- self.do_advanced_search)
-
- for ch in self.tool_bar.children():
- if isinstance(ch, QToolButton):
- ch.setCursor(Qt.PointingHandCursor)
+ ToolbarMixin.__init__(self)
####################### Library view ########################
- similar_menu = QMenu(_('Similar books...'))
- similar_menu.addAction(self.action_books_by_same_author)
- similar_menu.addAction(self.action_books_in_this_series)
- similar_menu.addAction(self.action_books_with_the_same_tags)
- similar_menu.addAction(self.action_books_by_this_publisher)
- self.action_books_by_same_author.setShortcut(Qt.ALT + Qt.Key_A)
- self.action_books_in_this_series.setShortcut(Qt.ALT + Qt.Key_S)
- self.action_books_by_this_publisher.setShortcut(Qt.ALT + Qt.Key_P)
- self.action_books_with_the_same_tags.setShortcut(Qt.ALT+Qt.Key_T)
- self.addAction(self.action_books_by_same_author)
- self.addAction(self.action_books_by_this_publisher)
- self.addAction(self.action_books_in_this_series)
- self.addAction(self.action_books_with_the_same_tags)
- self.similar_menu = similar_menu
- self.connect(self.action_books_by_same_author, SIGNAL('triggered()'),
- lambda : self.show_similar_books('author'))
- self.connect(self.action_books_in_this_series, SIGNAL('triggered()'),
- lambda : self.show_similar_books('series'))
- self.connect(self.action_books_with_the_same_tags,
- SIGNAL('triggered()'),
- lambda : self.show_similar_books('tag'))
- self.connect(self.action_books_by_this_publisher, SIGNAL('triggered()'),
- lambda : self.show_similar_books('publisher'))
- self.library_view.set_context_menu(self.action_edit, self.action_sync,
- self.action_convert, self.action_view,
- self.action_save,
- self.action_open_containing_folder,
- self.action_show_book_details,
- self.action_del,
- similar_menu=similar_menu)
-
- self.memory_view.set_context_menu(None, None, None,
- self.action_view, self.action_save, None, None, self.action_del)
- self.card_a_view.set_context_menu(None, None, None,
- self.action_view, self.action_save, None, None, self.action_del)
- self.card_b_view.set_context_menu(None, None, None,
- self.action_view, self.action_save, None, None, self.action_del)
-
- self.library_view.files_dropped.connect(self.files_dropped, type=Qt.QueuedConnection)
- for func, args in [
- ('connect_to_search_box', (self.search,
- self.search_done)),
- ('connect_to_book_display',
- (self.status_bar.book_info.show_data,)),
- ]:
- for view in (self.library_view, self.memory_view, self.card_a_view, self.card_b_view):
- getattr(view, func)(*args)
-
- self.memory_view.connect_dirtied_signal(self.upload_booklists)
- self.card_a_view.connect_dirtied_signal(self.upload_booklists)
- self.card_b_view.connect_dirtied_signal(self.upload_booklists)
+ LibraryViewMixin.__init__(self, db)
self.show()
+
if self.system_tray_icon.isVisible() and opts.start_in_tray:
self.hide_windows()
self.stack.setCurrentIndex(0)
- self.book_on_device(None, reset=True)
- db.set_book_on_device_func(self.book_on_device)
- self.library_view.set_database(db)
- self.library_view.model().set_book_on_device_func(self.book_on_device)
- prefs['library_path'] = self.library_path
self.search.setFocus(Qt.OtherFocusReason)
self.cover_cache = CoverCache(self.library_path)
self.cover_cache.start()
self.library_view.model().cover_cache = self.cover_cache
self.connect(self.edit_categories, SIGNAL('clicked()'), self.do_user_categories_edit)
self.search_restriction.activated[str].connect(self.apply_search_restriction)
- self.tags_view.set_database(db, self.tag_match, self.popularity)
- self.tags_view.tags_marked.connect(self.search.search_from_tags)
- self.tags_view.tags_marked.connect(self.saved_search.clear_to_help)
- self.tags_view.tag_list_edit.connect(self.do_tags_list_edit)
- self.tags_view.user_category_edit.connect(self.do_user_categories_edit)
- self.tags_view.saved_search_edit.connect(self.do_saved_search_edit)
- self.tags_view.tag_item_renamed.connect(self.do_tag_item_renamed)
- self.tags_view.search_item_renamed.connect(self.saved_search.clear_to_help)
for x in (self.location_view.count_changed, self.tags_view.recount,
self.restriction_count_changed):
self.library_view.model().count_changed_signal.connect(x)
@@ -578,46 +311,29 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
type=Qt.QueuedConnection)
########################### Tags Browser ##############################
+ TagBrowserMixin.__init__(self, db)
self.search_restriction.setSizeAdjustPolicy(self.search_restriction.AdjustToMinimumContentsLengthWithIcon)
self.search_restriction.setMinimumContentsLength(10)
########################### Cover Flow ################################
- self.cover_flow = None
- if CoverFlow is not None:
- self.cf_last_updated_at = None
- self.cover_flow_sync_timer = QTimer(self)
- self.cover_flow_sync_timer.timeout.connect(self.cover_flow_do_sync)
- self.cover_flow_sync_flag = True
- text_height = 40 if config['separate_cover_flow'] else 25
- ah = available_height()
- cfh = ah-100
- cfh = 3./5 * cfh - text_height
- if not config['separate_cover_flow']:
- cfh = 220 if ah > 950 else 170 if ah > 850 else 140
- self.cover_flow = CoverFlow(height=cfh, text_height=text_height)
- self.cover_flow.setVisible(False)
- if not config['separate_cover_flow']:
- self.cb_layout.addWidget(self.cover_flow)
- self.cover_flow.currentChanged.connect(self.sync_listview_to_cf)
- self.library_view.selectionModel().currentRowChanged.connect(
- self.sync_cf_to_listview)
- self.db_images = DatabaseImages(self.library_view.model())
- self.cover_flow.setImages(self.db_images)
+
+ CoverFlowMixin.__init__(self)
self._calculated_available_height = min(max_available_height()-15,
self.height())
self.resize(self.width(), self._calculated_available_height)
self.search.setMaximumWidth(self.width()-150)
+ # Jobs Button {{{
+ self.jobs_button = JobsButton()
+ self.jobs_button.initialize(self.jobs_dialog, self.job_manager)
+ # }}}
+
####################### Side Bar ###############################
- self.sidebar.initialize(self.jobs_dialog, self.cover_flow,
+ self.sidebar.initialize(self.jobs_button, self.cover_flow,
self.toggle_cover_flow, pictureflowerror,
self.vertical_splitter, self.horizontal_splitter)
- QObject.connect(self.job_manager, SIGNAL('job_added(int)'),
- self.sidebar.job_added, Qt.QueuedConnection)
- QObject.connect(self.job_manager, SIGNAL('job_done(int)'),
- self.sidebar.job_done, Qt.QueuedConnection)
if config['autolaunch_server']:
@@ -648,29 +364,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
self.keyboard_interrupt.connect(self.quit, type=Qt.QueuedConnection)
- def do_user_categories_edit(self, on_category=None):
- d = TagCategories(self, self.library_view.model().db, on_category)
- d.exec_()
- if d.result() == d.Accepted:
- self.tags_view.set_new_model()
- self.tags_view.recount()
-
- def do_tags_list_edit(self, tag, category):
- d = TagListEditor(self, self.library_view.model().db, tag, category)
- d.exec_()
- if d.result() == d.Accepted:
- # Clean up everything, as information could have changed for many books.
- self.library_view.model().refresh()
- self.tags_view.set_new_model()
- self.tags_view.recount()
- self.saved_search.clear_to_help()
- self.search.clear_to_help()
-
- def do_tag_item_renamed(self):
- # Clean up library view and search
- self.library_view.model().refresh()
- self.saved_search.clear_to_help()
- self.search.clear_to_help()
def do_saved_search_edit(self, search):
d = SavedSearchEditor(self, search)
@@ -760,81 +453,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
error_dialog(self, _('Failed to start content server'),
unicode(self.content_server.exception)).exec_()
- def show_similar_books(self, type):
- search, join = [], ' '
- idx = self.library_view.currentIndex()
- if not idx.isValid():
- return
- row = idx.row()
- if type == 'series':
- series = idx.model().db.series(row)
- if series:
- search = ['series:"'+series+'"']
- elif type == 'publisher':
- publisher = idx.model().db.publisher(row)
- if publisher:
- search = ['publisher:"'+publisher+'"']
- elif type == 'tag':
- tags = idx.model().db.tags(row)
- if tags:
- search = ['tag:"='+t+'"' for t in tags.split(',')]
- elif type == 'author':
- authors = idx.model().db.authors(row)
- if authors:
- search = ['author:"='+a.strip().replace('|', ',')+'"' \
- for a in authors.split(',')]
- join = ' or '
- if search:
- self.search.set_search_string(join.join(search))
-
- def toggle_cover_flow(self, show):
- if config['separate_cover_flow']:
- if show:
- self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
- d = QDialog(self)
- ah, aw = available_height(), available_width()
- d.resize(int(aw/2.), ah-60)
- d._layout = QStackedLayout()
- d.setLayout(d._layout)
- d.setWindowTitle(_('Browse by covers'))
- d.layout().addWidget(self.cover_flow)
- self.cover_flow.setVisible(True)
- self.cover_flow.setFocus(Qt.OtherFocusReason)
- self.library_view.scroll_to_row(self.library_view.currentIndex().row())
- d.show()
- d.finished.connect(self.sidebar.external_cover_flow_finished)
- self.cf_dialog = d
- self.cover_flow_sync_timer.start(500)
- else:
- self.cover_flow_sync_timer.stop()
- idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
- if idx.isValid():
- sm = self.library_view.selectionModel()
- sm.select(idx, sm.ClearAndSelect|sm.Rows)
- self.library_view.setCurrentIndex(idx)
- cfd = getattr(self, 'cf_dialog', None)
- if cfd is not None:
- self.cover_flow.setVisible(False)
- cfd.hide()
- self.cf_dialog = None
- else:
- if show:
- self.cover_flow.setCurrentSlide(self.library_view.currentIndex().row())
- self.library_view.setCurrentIndex(
- self.library_view.currentIndex())
- self.cover_flow.setVisible(True)
- self.cover_flow.setFocus(Qt.OtherFocusReason)
- self.library_view.scroll_to_row(self.library_view.currentIndex().row())
- self.cover_flow_sync_timer.start(500)
- else:
- self.cover_flow_sync_timer.stop()
- self.cover_flow.setVisible(False)
- idx = self.library_view.model().index(self.cover_flow.currentSlide(), 0)
- if idx.isValid():
- sm = self.library_view.selectionModel()
- sm.select(idx, sm.ClearAndSelect|sm.Rows)
- self.library_view.setCurrentIndex(idx)
-
'''
Restrictions.
Adding and deleting books creates a complexity. When added, they are
@@ -905,33 +523,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
self.search_restriction.setCurrentIndex(0)
self.apply_search_restriction('')
- def sync_cf_to_listview(self, current, previous):
- if self.cover_flow_sync_flag and self.cover_flow.isVisible() and \
- self.cover_flow.currentSlide() != current.row():
- self.cover_flow.setCurrentSlide(current.row())
- self.cover_flow_sync_flag = True
-
- def cover_flow_do_sync(self):
- self.cover_flow_sync_flag = True
- try:
- if self.cover_flow.isVisible() and self.cf_last_updated_at is not None and \
- time.time() - self.cf_last_updated_at > 0.5:
- self.cf_last_updated_at = None
- row = self.cover_flow.currentSlide()
- m = self.library_view.model()
- index = m.index(row, 0)
- if self.library_view.currentIndex().row() != row and index.isValid():
- self.cover_flow_sync_flag = False
- self.library_view.scroll_to_row(index.row())
- sm = self.library_view.selectionModel()
- sm.select(index, sm.ClearAndSelect|sm.Rows)
- self.library_view.setCurrentIndex(index)
- except:
- pass
-
-
- def sync_listview_to_cf(self, row):
- self.cf_last_updated_at = time.time()
def another_instance_wants_to_talk(self):
try:
@@ -1309,21 +900,21 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
Dispatcher(self._files_added), spare_server=self.spare_server)
self._adder.add_recursive(root, single)
- def add_recursive_single(self, checked):
+ def add_recursive_single(self, *args):
'''
Add books from the local filesystem to either the library or the device
recursively assuming one book per folder.
'''
self.add_recursive(True)
- def add_recursive_multiple(self, checked):
+ def add_recursive_multiple(self, *args):
'''
Add books from the local filesystem to either the library or the device
recursively assuming multiple books per folder.
'''
self.add_recursive(False)
- def add_empty(self, checked):
+ def add_empty(self, *args):
'''
Add an empty book item to the library. This does not import any formats
from a book file.
@@ -1383,7 +974,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
def add_filesystem_book(self, paths, allow_device=True):
self._add_filesystem_book(paths, allow_device=allow_device)
- def add_books(self, checked):
+ def add_books(self, *args):
'''
Add books from the local filesystem to either the library or the device.
'''
@@ -2289,12 +1880,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
d.exec_()
self.content_server = d.server
if d.result() == d.Accepted:
- self.tool_bar.setIconSize(config['toolbar_icon_size'])
+ self.read_toolbar_settings()
self.search.search_as_you_type(config['search_as_you_type'])
- self.tool_bar.setToolButtonStyle(
- Qt.ToolButtonTextUnderIcon if \
- config['show_text_in_toolbar'] else \
- Qt.ToolButtonIconOnly)
self.save_menu.actions()[2].setText(
_('Save only %s format to disk')%
prefs['output_format'].upper())
@@ -2451,12 +2038,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
geometry = config['main_window_geometry']
if geometry is not None:
self.restoreGeometry(geometry)
- self.tool_bar.setIconSize(config['toolbar_icon_size'])
- self.tool_bar.setToolButtonStyle(
- Qt.ToolButtonTextUnderIcon if \
- config['show_text_in_toolbar'] else \
- Qt.ToolButtonIconOnly)
-
+ self.read_toolbar_settings()
def write_settings(self):
config.set('main_window_geometry', self.saveGeometry())
From 5979d46fd2a53dccbbcc8bf41c4205a275c18317 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 00:02:37 -0600
Subject: [PATCH 09/14] ...
---
src/calibre/gui2/cover_flow.py | 12 +++++++++---
src/calibre/gui2/init.py | 6 ++++++
src/calibre/gui2/ui.py | 4 ----
3 files changed, 15 insertions(+), 7 deletions(-)
diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py
index e762caf11a..5254ad501c 100644
--- a/src/calibre/gui2/cover_flow.py
+++ b/src/calibre/gui2/cover_flow.py
@@ -91,6 +91,12 @@ if pictureflow is not None:
elif ev.delta() > 0:
self.showPrevious()
+ def resizeEvent(self, *args):
+ ans = pictureflow.PictureFlow.resizeEvent(self, *args)
+ height = self.height()/2.
+ self.setSlideSize(QSize(int(2/3. * height), height))
+ return ans
+
else:
CoverFlow = None
@@ -126,7 +132,7 @@ class CoverFlowMixin(object):
if show:
d = QDialog(self)
ah, aw = available_height(), available_width()
- d.resize(int(aw/2.), ah-60)
+ d.resize(int(aw/1.5), ah-60)
d._layout = QStackedLayout()
d.setLayout(d._layout)
d.setWindowTitle(_('Browse by covers'))
@@ -203,8 +209,8 @@ if __name__ == '__main__':
app = QApplication([])
w = QMainWindow()
cf = CoverFlow()
- cf.resize(cf.minimumSize())
- w.resize(cf.minimumSize()+QSize(30, 20))
+ cf.resize(int(available_width()/1.5), available_height()-60)
+ w.resize(cf.size()+QSize(30, 20))
path = sys.argv[1]
model = FileSystemImages(sys.argv[1])
cf.setImages(model)
diff --git a/src/calibre/gui2/init.py b/src/calibre/gui2/init.py
index e6fbc460ef..a991c4d1f8 100644
--- a/src/calibre/gui2/init.py
+++ b/src/calibre/gui2/init.py
@@ -242,6 +242,12 @@ class LibraryViewMixin(object): # {{{
self.library_view.model().set_book_on_device_func(self.book_on_device)
prefs['library_path'] = self.library_path
+ for view in ('library', 'memory', 'card_a', 'card_b'):
+ view = getattr(self, view+'_view')
+ view.verticalHeader().sectionDoubleClicked.connect(self.view_specific_book)
+
+
+
def show_similar_books(self, type, *args):
search, join = [], ' '
idx = self.library_view.currentIndex()
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 6db3fe217d..29a09d6a1d 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -355,10 +355,6 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
SIGNAL('start_recipe_fetch(PyQt_PyObject)'),
self.download_scheduled_recipe, Qt.QueuedConnection)
- for view in ('library', 'memory', 'card_a', 'card_b'):
- view = getattr(self, view+'_view')
- view.verticalHeader().sectionDoubleClicked.connect(self.view_specific_book)
-
self.location_view.setCurrentIndex(self.location_view.model().index(0))
self._add_filesystem_book = Dispatcher(self.__add_filesystem_book)
From 589ea3b113dd580d7edd17ef7650667c482b33c5 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 01:16:14 -0600
Subject: [PATCH 10/14] Handle resizinf of caover browser correctly
---
src/calibre/gui2/cover_flow.py | 9 +--------
src/calibre/gui2/pictureflow/pictureflow.cpp | 2 ++
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py
index 5254ad501c..7ca475e8f3 100644
--- a/src/calibre/gui2/cover_flow.py
+++ b/src/calibre/gui2/cover_flow.py
@@ -78,7 +78,6 @@ if pictureflow is not None:
def __init__(self, parent=None):
pictureflow.PictureFlow.__init__(self, parent,
config['cover_flow_queue_length']+1)
- self.setSlideSize(QSize(int(2/3. * 10), 10))
self.setMinimumSize(QSize(10, 10))
self.setFocusPolicy(Qt.WheelFocus)
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
@@ -91,12 +90,6 @@ if pictureflow is not None:
elif ev.delta() > 0:
self.showPrevious()
- def resizeEvent(self, *args):
- ans = pictureflow.PictureFlow.resizeEvent(self, *args)
- height = self.height()/2.
- self.setSlideSize(QSize(int(2/3. * height), height))
- return ans
-
else:
CoverFlow = None
@@ -213,8 +206,8 @@ if __name__ == '__main__':
w.resize(cf.size()+QSize(30, 20))
path = sys.argv[1]
model = FileSystemImages(sys.argv[1])
- cf.setImages(model)
cf.currentChanged[int].connect(model.currentChanged)
+ cf.setImages(model)
w.setCentralWidget(cf)
w.show()
diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp
index 1b8d3300f4..140d918ff9 100644
--- a/src/calibre/gui2/pictureflow/pictureflow.cpp
+++ b/src/calibre/gui2/pictureflow/pictureflow.cpp
@@ -540,6 +540,8 @@ void PictureFlowPrivate::showSlide(int index)
void PictureFlowPrivate::resize(int w, int h)
{
+ slideHeight = int(float(h)/2.);
+ slideWidth = int(float(slideHeight) * 2/3.);
recalc(w, h);
resetSlides();
triggerRender();
From a185a0771e30133c1d053cb82af8db9e38782fd4 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 01:35:23 -0600
Subject: [PATCH 11/14] Fix #2345 (Covers in browser differ in size to enhance
browsing)
---
src/calibre/gui2/cover_flow.py | 1 +
src/calibre/gui2/pictureflow/pictureflow.cpp | 12 +++++++-----
src/calibre/gui2/pictureflow/pictureflow.h | 3 ++-
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/src/calibre/gui2/cover_flow.py b/src/calibre/gui2/cover_flow.py
index 7ca475e8f3..3bd554e891 100644
--- a/src/calibre/gui2/cover_flow.py
+++ b/src/calibre/gui2/cover_flow.py
@@ -82,6 +82,7 @@ if pictureflow is not None:
self.setFocusPolicy(Qt.WheelFocus)
self.setSizePolicy(QSizePolicy(QSizePolicy.Expanding,
QSizePolicy.Expanding))
+ self.setZoomFactor(150)
def wheelEvent(self, ev):
ev.accept()
diff --git a/src/calibre/gui2/pictureflow/pictureflow.cpp b/src/calibre/gui2/pictureflow/pictureflow.cpp
index 140d918ff9..9bb9a0954c 100644
--- a/src/calibre/gui2/pictureflow/pictureflow.cpp
+++ b/src/calibre/gui2/pictureflow/pictureflow.cpp
@@ -85,6 +85,8 @@ typedef long PFreal;
typedef unsigned short QRgb565;
+#define FONT_SIZE 18
+
#define RGB565_RED_MASK 0xF800
#define RGB565_GREEN_MASK 0x07E0
#define RGB565_BLUE_MASK 0x001F
@@ -711,14 +713,14 @@ void PictureFlowPrivate::render()
QPainter painter;
painter.begin(&buffer);
- QFont font("Arial", 14);
+ QFont font("Arial", FONT_SIZE);
font.setBold(true);
painter.setFont(font);
painter.setPen(Qt::white);
//painter.setPen(QColor(255,255,255,127));
if (centerIndex < slideCount() && centerIndex > -1)
- painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
+ painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2-FONT_SIZE*3),
Qt::AlignCenter, slideImages->caption(centerIndex));
painter.end();
@@ -761,7 +763,7 @@ void PictureFlowPrivate::render()
QPainter painter;
painter.begin(&buffer);
- QFont font("Arial", 14);
+ QFont font("Arial", FONT_SIZE);
font.setBold(true);
painter.setFont(font);
@@ -770,12 +772,12 @@ void PictureFlowPrivate::render()
painter.setPen(QColor(255,255,255, (255-fade) ));
if (leftTextIndex < sc && leftTextIndex > -1)
- painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
+ painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
Qt::AlignCenter, slideImages->caption(leftTextIndex));
painter.setPen(QColor(255,255,255, fade));
if (leftTextIndex+1 < sc && leftTextIndex > -2)
- painter.drawText( QRect(0,0, buffer.width(), (buffer.height() - slideSize().height())/2),
+ painter.drawText( QRect(0,0, buffer.width(), buffer.height()*2 - FONT_SIZE*3),
Qt::AlignCenter, slideImages->caption(leftTextIndex+1));
diff --git a/src/calibre/gui2/pictureflow/pictureflow.h b/src/calibre/gui2/pictureflow/pictureflow.h
index 3e0b606d8a..8cce025180 100644
--- a/src/calibre/gui2/pictureflow/pictureflow.h
+++ b/src/calibre/gui2/pictureflow/pictureflow.h
@@ -115,7 +115,8 @@ public:
QSize slideSize() const;
/*!
- Sets the dimension of each slide (in pixels).
+ Sets the dimension of each slide (in pixels). Do not use this method directly
+ instead use resize which automatically sets an appropriate slide size.
*/
void setSlideSize(QSize size);
From 1b828d5f33700d4755b26a80e5d9dcfd0cdd1830 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 09:45:02 -0600
Subject: [PATCH 12/14] Fix #5146 (/etc/bash_completion.d/calibre has errors)
---
src/calibre/linux.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/calibre/linux.py b/src/calibre/linux.py
index 26bbe0837b..51711b5b0f 100644
--- a/src/calibre/linux.py
+++ b/src/calibre/linux.py
@@ -411,7 +411,8 @@ def options(option_parser):
def opts_and_words(name, op, words):
opts = '|'.join(options(op))
words = '|'.join([w.replace("'", "\\'") for w in words])
- return ('_'+name+'()'+\
+ fname = name.replace('-', '_')
+ return ('_'+fname+'()'+\
'''
{
local cur opts
@@ -435,7 +436,7 @@ def opts_and_words(name, op, words):
esac
}
-complete -F _'''%(opts, words) + name + ' ' + name +"\n\n").encode('utf-8')
+complete -F _'''%(opts, words) + fname + ' ' + name +"\n\n").encode('utf-8')
def opts_and_exts(name, op, exts):
From 80fae845bab7b15ab91544aec4e2a469ede79165 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 09:52:20 -0600
Subject: [PATCH 13/14] ...
---
src/calibre/gui2/device.py | 5 ++++-
src/calibre/gui2/ui.py | 6 ++++--
2 files changed, 8 insertions(+), 3 deletions(-)
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 378c585efb..0e18929d81 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -599,7 +599,10 @@ class Emailer(Thread): # {{{
# }}}
-class DeviceGUI(object):
+class DeviceMixin(object):
+
+ def __init__(self):
+ self.db_book_uuid_cache = set()
def dispatch_sync_event(self, dest, delete, specific):
rows = self.library_view.selectionModel().selectedRows()
diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py
index 29a09d6a1d..9ff30aa768 100644
--- a/src/calibre/gui2/ui.py
+++ b/src/calibre/gui2/ui.py
@@ -44,7 +44,7 @@ from calibre.gui2.dialogs.scheduler import Scheduler
from calibre.gui2.update import CheckForUpdates
from calibre.gui2.main_window import MainWindow
from calibre.gui2.main_ui import Ui_MainWindow
-from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceGUI, Emailer
+from calibre.gui2.device import DeviceManager, DeviceMenu, DeviceMixin, Emailer
from calibre.gui2.jobs import JobManager, JobsDialog, JobsButton
from calibre.gui2.dialogs.metadata_single import MetadataSingleDialog
from calibre.gui2.dialogs.metadata_bulk import MetadataBulkDialog
@@ -105,7 +105,7 @@ class SystemTrayIcon(QSystemTrayIcon): # {{{
# }}}
-class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
+class Main(MainWindow, Ui_MainWindow, DeviceMixin, ToolbarMixin,
TagBrowserMixin, CoverFlowMixin, LibraryViewMixin):
'The main GUI'
@@ -228,6 +228,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI, ToolbarMixin,
QObject.connect(self.advanced_search_button, SIGNAL('clicked(bool)'),
self.do_advanced_search)
+ DeviceMixin.__init__(self)
+
####################### Start spare job server ########################
QTimer.singleShot(1000, self.add_spare_server)
From ff6ac7f3529dbccd13d1d98598f842cd4b6639e9 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Wed, 9 Jun 2010 10:11:13 -0600
Subject: [PATCH 14/14] Add a deleted_after_upload attribute to file paths sent
to the device driver from the GUI
---
src/calibre/devices/interface.py | 5 ++++-
src/calibre/gui2/device.py | 6 ++++--
src/calibre/gui2/library/models.py | 1 +
3 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/src/calibre/devices/interface.py b/src/calibre/devices/interface.py
index 2d82bf4563..da8a2cb978 100644
--- a/src/calibre/devices/interface.py
+++ b/src/calibre/devices/interface.py
@@ -289,7 +289,10 @@ class DevicePlugin(Plugin):
word "card" if C{on_card} is not None otherwise it must contain the word "memory".
:files: A list of paths and/or file-like objects. If they are paths and
the paths point to temporary files, they may have an additional
- attribute, original_file_path pointing to the originals.
+ attribute, original_file_path pointing to the originals. They may have
+ another optional attribute, deleted_after_upload which if True means
+ that the file pointed to by original_file_path will be deleted after
+ being uploaded to the device.
:names: A list of file names that the books should have
once uploaded to the device. len(names) == len(files)
:return: A list of 3-element tuples. The list is meant to be passed
diff --git a/src/calibre/gui2/device.py b/src/calibre/gui2/device.py
index 0e18929d81..1445b0f36e 100644
--- a/src/calibre/gui2/device.py
+++ b/src/calibre/gui2/device.py
@@ -854,6 +854,7 @@ class DeviceMixin(object):
def sync_news(self, send_ids=None, do_auto_convert=True):
if self.device_connected:
+ del_on_upload = config['delete_news_from_library_on_upload']
settings = self.device_manager.device.settings()
ids = list(dynamic.get('news_to_be_synced', set([]))) if send_ids is None else send_ids
ids = [id for id in ids if self.library_view.model().db.has_id(id)]
@@ -883,6 +884,8 @@ class DeviceMixin(object):
'the device?'), det_msg=autos):
self.auto_convert_news(auto, format)
files = [f for f in files if f is not None]
+ for f in files:
+ f.deleted_after_upload = del_on_upload
if not files:
dynamic.set('news_to_be_synced', set([]))
return
@@ -900,8 +903,7 @@ class DeviceMixin(object):
'rb').read())
dynamic.set('news_to_be_synced', set([]))
if config['upload_news_to_device'] and files:
- remove = ids if \
- config['delete_news_from_library_on_upload'] else []
+ remove = ids if del_on_upload else []
space = { self.location_view.model().free[0] : None,
self.location_view.model().free[1] : 'carda',
self.location_view.model().free[2] : 'cardb' }
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index abd80aaa8f..71f41eab45 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -39,6 +39,7 @@ class FormatPath(unicode):
def __new__(cls, path, orig_file_path):
ans = unicode.__new__(cls, path)
ans.orig_file_path = orig_file_path
+ ans.deleted_after_upload = False
return ans
class BooksModel(QAbstractTableModel): # {{{