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) 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 ad83913328..e8f3068d35 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 @@ -408,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'), @@ -439,7 +459,12 @@ 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 + 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/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 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