diff --git a/resources/images/swap.png b/resources/images/swap.png index 60b8803d95..e5aeb60e22 100644 Binary files a/resources/images/swap.png and b/resources/images/swap.png differ diff --git a/src/calibre/customize/builtins.py b/src/calibre/customize/builtins.py index d95a7d1c8a..4c87236e71 100644 --- a/src/calibre/customize/builtins.py +++ b/src/calibre/customize/builtins.py @@ -461,7 +461,7 @@ from calibre.devices.hanvon.driver import N516, EB511, ALEX, AZBOOKA, THEBOOK from calibre.devices.edge.driver import EDGE from calibre.devices.teclast.driver import TECLAST_K3, NEWSMY, IPAPYRUS from calibre.devices.sne.driver import SNE -from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN +from calibre.devices.misc import PALMPRE, AVANT, SWEEX, PDNOVEL, KOGAN, GEMEI from calibre.devices.folder_device.driver import FOLDER_DEVICE_FOR_CONFIG from calibre.devices.kobo.driver import KOBO @@ -570,6 +570,7 @@ plugins += [ KOGAN, PDNOVEL, SPECTRA, + GEMEI, ITUNES, ] plugins += [x for x in list(locals().values()) if isinstance(x, type) and \ diff --git a/src/calibre/devices/apple/driver.py b/src/calibre/devices/apple/driver.py index 848d3351f3..e318d368ff 100644 --- a/src/calibre/devices/apple/driver.py +++ b/src/calibre/devices/apple/driver.py @@ -82,7 +82,7 @@ class ITUNES(DriverBase): ''' name = 'Apple device interface' - gui_name = 'Apple device' + gui_name = _('Apple device') icon = I('devices/ipad.png') description = _('Communicate with iTunes/iBooks.') supported_platforms = ['osx','windows'] diff --git a/src/calibre/devices/misc.py b/src/calibre/devices/misc.py index 7e8f5fbdd3..bb75ceabd1 100644 --- a/src/calibre/devices/misc.py +++ b/src/calibre/devices/misc.py @@ -108,4 +108,23 @@ class PDNOVEL(USBMS): with open('%s.jpg' % os.path.join(path, filename), 'wb') as coverfile: coverfile.write(coverdata[2]) +class GEMEI(USBMS): + name = 'Gemei Device Interface' + gui_name = 'GM2000' + description = _('Communicate with the GM2000') + author = 'Kovid Goyal' + supported_platforms = ['windows', 'osx', 'linux'] + + # Ordered list of supported formats + FORMATS = ['epub', 'chm', 'html', 'pdb', 'pdf', 'txt'] + + VENDOR_ID = [0x07c4] + PRODUCT_ID = [0xa4a5] + BCD = None + + VENDOR_NAME = 'CHINA' + WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = 'CHIP' + + EBOOK_DIR_MAIN = 'eBooks' + SUPPORTS_SUB_DIRS = True diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py index 6596e9b1a2..624b277e61 100644 --- a/src/calibre/ebooks/__init__.py +++ b/src/calibre/ebooks/__init__.py @@ -138,3 +138,11 @@ def check_ebook_format(stream, current_guess): stream.seek(0) return ans +def calibre_cover(title, author_string, series_string=None, + output_format='jpg', title_size=46, author_size=36): + from calibre.utils.magick.draw import create_cover_page, TextLine + lines = [TextLine(title, title_size), TextLine(author_string, author_size)] + if series_string: + lines.append(TextLine(series_string, author_size)) + return create_cover_page(lines, I('library.png'), output_format='jpg') + diff --git a/src/calibre/ebooks/metadata/archive.py b/src/calibre/ebooks/metadata/archive.py index 624c2ad5e5..f5982406ea 100644 --- a/src/calibre/ebooks/metadata/archive.py +++ b/src/calibre/ebooks/metadata/archive.py @@ -70,7 +70,7 @@ class ArchiveExtract(FileTypePlugin): fname = fnames[0] ext = os.path.splitext(fname)[1][1:] if ext.lower() not in ('lit', 'epub', 'mobi', 'prc', 'rtf', 'pdf', - 'mp3', 'pdb', 'azw', 'azw1'): + 'mp3', 'pdb', 'azw', 'azw1', 'fb2'): return archive of = self.temporary_file('_archive_extract.'+ext) diff --git a/src/calibre/ebooks/oeb/transforms/cover.py b/src/calibre/ebooks/oeb/transforms/cover.py index 83b7b5d3c1..59b42df68a 100644 --- a/src/calibre/ebooks/oeb/transforms/cover.py +++ b/src/calibre/ebooks/oeb/transforms/cover.py @@ -89,19 +89,22 @@ class CoverManager(object): ''' Create a generic cover for books that dont have a cover ''' - from calibre.ebooks.metadata import authors_to_string + from calibre.ebooks.metadata import authors_to_string, fmt_sidx if self.no_default_cover: return None self.log('Generating default cover') m = self.oeb.metadata title = unicode(m.title[0]) authors = [unicode(x) for x in m.creator if x.role == 'aut'] + series_string = None + if m.series and m.series_index: + series_string = _('Book %s of %s')%( + fmt_sidx(m.series_index[0], use_roman=True), m.series[0]) try: - from calibre.utils.magick.draw import create_cover_page, TextLine - lines = [TextLine(title, 44), TextLine(authors_to_string(authors), - 32)] - img_data = create_cover_page(lines, I('library.png')) + from calibre.ebooks import calibre_cover + img_data = calibre_cover(title, authors_to_string(authors), + series_string=series_string) id, href = self.oeb.manifest.generate('cover_image', 'cover_image.jpg') item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0], diff --git a/src/calibre/gui2/convert/bulk.py b/src/calibre/gui2/convert/bulk.py index f0fdf9a9a1..1affea4e2a 100644 --- a/src/calibre/gui2/convert/bulk.py +++ b/src/calibre/gui2/convert/bulk.py @@ -58,7 +58,8 @@ class BulkConfig(Config): output_path = 'dummy.'+output_format log = Log() log.outputs = [] - self.plumber = Plumber(input_path, output_path, log) + self.plumber = Plumber(input_path, output_path, log, + merge_plugin_recs=False) def widget_factory(cls): return cls(self.stack, self.plumber.get_option_by_name, diff --git a/src/calibre/gui2/dialogs/metadata_single.py b/src/calibre/gui2/dialogs/metadata_single.py index 1393a50738..3d79b01c14 100644 --- a/src/calibre/gui2/dialogs/metadata_single.py +++ b/src/calibre/gui2/dialogs/metadata_single.py @@ -144,15 +144,23 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog): self.cover_data = cover def generate_cover(self, *args): - from calibre.utils.magick.draw import create_cover_page, TextLine + from calibre.ebooks import calibre_cover + from calibre.ebooks.metadata import fmt_sidx + from calibre.gui2 import config title = unicode(self.title.text()).strip() author = unicode(self.authors.text()).strip() if not title or not author: return error_dialog(self, _('Specify title and author'), _('You must specify a title and author before generating ' 'a cover'), show=True) - lines = [TextLine(title, 44), TextLine(author, 32)] - self.cover_data = create_cover_page(lines, I('library.png')) + series = unicode(self.series.text()).strip() + series_string = None + if series: + series_string = _('Book %s of %s')%( + fmt_sidx(self.series_index.value(), + use_roman=config['use_roman_numerals_for_series_number']), series) + self.cover_data = calibre_cover(title, author, + series_string=series_string) pix = QPixmap() pix.loadFromData(self.cover_data) self.cover.setPixmap(pix) diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py index 68f093da95..58d5267c8e 100644 --- a/src/calibre/gui2/layout.py +++ b/src/calibre/gui2/layout.py @@ -215,6 +215,7 @@ class ToolBar(QToolBar): # {{{ self.location_manager.locations_changed.connect(self.build_bar) donate.setAutoRaise(True) donate.setCursor(Qt.PointingHandCursor) + self.added_actions = [] self.build_bar() self.preferred_width = self.sizeHint().width() @@ -237,7 +238,13 @@ class ToolBar(QToolBar): # {{{ actions = '-device' if showing_device else '' actions = gprefs['action-layout-toolbar'+actions] + for ac in self.added_actions: + m = ac.menu() + if m is not None: + m.setVisible(False) + self.clear() + self.added_actions = [] for what in actions: if what is None: @@ -245,6 +252,7 @@ class ToolBar(QToolBar): # {{{ elif what == 'Location Manager': for ac in self.location_manager.available_actions: self.addAction(ac) + self.added_actions.append(ac) self.setup_tool_button(ac, QToolButton.MenuButtonPopup) elif what == 'Donate': self.d_widget = QWidget() @@ -255,6 +263,7 @@ class ToolBar(QToolBar): # {{{ elif what in self.gui.iactions: action = self.gui.iactions[what] self.addAction(action.qaction) + self.added_actions.append(action.qaction) self.setup_tool_button(action.qaction, action.popup_type) def setup_tool_button(self, ac, menu_mode=None): diff --git a/src/calibre/gui2/tag_view.py b/src/calibre/gui2/tag_view.py index 72f0efd9bf..a64eb2eb9a 100644 --- a/src/calibre/gui2/tag_view.py +++ b/src/calibre/gui2/tag_view.py @@ -424,10 +424,8 @@ class TagsModel(QAbstractItemModel): # {{{ self.categories = [] # Reconstruct the user categories, putting them into metadata + self.db.field_metadata.remove_dynamic_categories() 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(self.db.prefs.get('user_categories', {}).keys()): cat_name = user_cat+':' # add the ':' to avoid name collision tb_cats.add_user_category(label=cat_name, name=user_cat) diff --git a/src/calibre/gui2/ui.py b/src/calibre/gui2/ui.py index 624935f279..f40eed1fcd 100644 --- a/src/calibre/gui2/ui.py +++ b/src/calibre/gui2/ui.py @@ -523,11 +523,15 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, # {{{ def shutdown(self, write_settings=True): try: - cf = self.library_view.model().db.clean + db = self.library_view.model().db + cf = db.clean except: pass else: cf() + # Save the current field_metadata for applications like calibre2opds + # Goes here, because if cf is valid, db is valid. + db.prefs['field_metadata'] = db.field_metadata.all_metadata() for action in self.iactions.values(): if not action.shutting_down(): return diff --git a/src/calibre/gui2/viewer/main.py b/src/calibre/gui2/viewer/main.py index c8f1f62856..79f4c29998 100644 --- a/src/calibre/gui2/viewer/main.py +++ b/src/calibre/gui2/viewer/main.py @@ -173,6 +173,7 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.pending_anchor = None self.pending_reference = None self.pending_bookmark = None + self.existing_bookmarks= [] self.selected_text = None self.read_settings() self.dictionary_box.hide() @@ -415,15 +416,6 @@ class EbookViewer(MainWindow, Ui_EbookViewer): self.action_font_size_smaller.setEnabled(self.view.multiplier() > 0.2) self.set_page_number(frac) - def bookmark(self, *args): - title, ok = QInputDialog.getText(self, _('Add bookmark'), _('Enter title for bookmark:')) - title = unicode(title).strip() - if ok and title: - pos = self.view.bookmark() - bookmark = '%d#%s'%(self.current_index, pos) - self.iterator.add_bookmark((title, bookmark)) - self.set_bookmarks(self.iterator.bookmarks) - def find(self, text, repeat=False, backwards=False): if not text: @@ -539,15 +531,34 @@ class EbookViewer(MainWindow, Ui_EbookViewer): getattr(self, o).setEnabled(False) self.setCursor(Qt.BusyCursor) + def bookmark(self, *args): + num = 1 + bm = None + while True: + bm = _('Bookmark #%d')%num + if bm not in self.existing_bookmarks: + break + num += 1 + title, ok = QInputDialog.getText(self, _('Add bookmark'), + _('Enter title for bookmark:'), text=bm) + title = unicode(title).strip() + if ok and title: + pos = self.view.bookmark() + bookmark = '%d#%s'%(self.current_index, pos) + self.iterator.add_bookmark((title, bookmark)) + self.set_bookmarks(self.iterator.bookmarks) + def set_bookmarks(self, bookmarks): self.bookmarks_menu.clear() self.bookmarks_menu.addAction(_("Manage Bookmarks"), self.manage_bookmarks) self.bookmarks_menu.addSeparator() current_page = None + self.existing_bookmarks = [] for bm in bookmarks: if bm[0] == 'calibre_current_page_bookmark': current_page = bm else: + self.existing_bookmarks.append(bm[0]) self.bookmarks_menu.addAction(bm[0], partial(self.goto_bookmark, bm)) return current_page diff --git a/src/calibre/library/catalog.py b/src/calibre/library/catalog.py index a02766c9b4..bd2160aff1 100644 --- a/src/calibre/library/catalog.py +++ b/src/calibre/library/catalog.py @@ -2248,7 +2248,6 @@ class EPUB_MOBI(CatalogPlugin): date_range_list = [] today_time = nowf().replace(hour=23, minute=59, second=59) - books_added_in_date_range = False for (i, date) in enumerate(self.DATE_RANGE): date_range_limit = self.DATE_RANGE[i] if i: @@ -2261,7 +2260,6 @@ class EPUB_MOBI(CatalogPlugin): delta = today_time-book_time if delta.days <= date_range_limit: date_range_list.append(book) - books_added_in_date_range = True else: break diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py index 0bd0610ab0..9a2d0b0a62 100644 --- a/src/calibre/library/cli.py +++ b/src/calibre/library/cli.py @@ -576,6 +576,9 @@ def command_add_custom_column(args, dbpath): return 1 do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2], opts.is_multiple, json.loads(opts.display)) + # Re-open the DB so that field_metadata is reflects the column changes + db = get_db(dbpath, opts) + db.prefs['field_metadata'] = db.field_metadata.all_metadata() return 0 def catalog_option_parser(args): @@ -806,6 +809,9 @@ def command_remove_custom_column(args, dbpath): return 1 do_remove_custom_column(get_db(dbpath, opts), args[0], opts.force) + # Re-open the DB so that field_metadata is reflects the column changes + db = get_db(dbpath, opts) + db.prefs['field_metadata'] = db.field_metadata.all_metadata() return 0 def saved_searches_option_parser(): diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py index 52b5f2d4e6..cc4ddb1c17 100644 --- a/src/calibre/library/database2.py +++ b/src/calibre/library/database2.py @@ -290,10 +290,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns): # Reconstruct the user categories, putting them into field_metadata # Assumption is that someone else will fix them if they change. + self.field_metadata.remove_dynamic_categories() tb_cats = self.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(self.prefs.get('user_categories', {}).keys()): cat_name = user_cat+':' # add the ':' to avoid name collision tb_cats.add_user_category(label=cat_name, name=user_cat) diff --git a/src/calibre/library/field_metadata.py b/src/calibre/library/field_metadata.py index 09dd024b66..66cdee51f0 100644 --- a/src/calibre/library/field_metadata.py +++ b/src/calibre/library/field_metadata.py @@ -371,6 +371,12 @@ class FieldMetadata(dict): def get_custom_fields(self): return [l for l in self._tb_cats if self._tb_cats[l]['is_custom']] + def all_metadata(self): + l = {} + for k in self._tb_cats: + l[k] = self._tb_cats[k] + return l + def get_custom_field_metadata(self): l = {} for k in self._tb_cats: @@ -408,6 +414,12 @@ class FieldMetadata(dict): self._add_search_terms_to_map(key, [key]) self.custom_label_to_key_map[label+'_index'] = key + def remove_dynamic_categories(self): + for key in list(self._tb_cats.keys()): + val = self._tb_cats[key] + if val['is_category'] and val['kind'] in ('user', 'search'): + del self._tb_cats[key] + def cc_series_index_column_for(self, key): return self._tb_cats[key]['rec_index'] + 1 diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py index 17bf1c273a..82a0237b8d 100644 --- a/src/calibre/utils/magick/draw.py +++ b/src/calibre/utils/magick/draw.py @@ -165,12 +165,17 @@ def create_cover_page(top_lines, logo_path, width=590, height=750, top = height - lheight - 10 canvas.compose(vanity, left, top) - logo = Image() - logo.open(logo_path) - lwidth, lheight = logo.size - left = int(max(0, (width - lwidth)/2.)) - top = max(int((height - lheight)/2.), bottom+20) - canvas.compose(logo, left, top) + available = (width, int(top - bottom)-20) + if available[1] > 40: + logo = Image() + logo.open(logo_path) + lwidth, lheight = logo.size + scaled, lwidth, lheight = fit_image(lwidth, lheight, *available) + if scaled: + logo.size = (lwidth, lheight) + left = int(max(0, (width - lwidth)/2.)) + top = bottom+10 + canvas.compose(logo, left, top) return canvas.export(output_format) diff --git a/src/calibre/web/feeds/news.py b/src/calibre/web/feeds/news.py index 6df73487ed..9ba9583c73 100644 --- a/src/calibre/web/feeds/news.py +++ b/src/calibre/web/feeds/news.py @@ -1018,12 +1018,11 @@ class BasicNewsRecipe(Recipe): Create a generic cover for recipes that dont have a cover ''' try: - from calibre.utils.magick.draw import create_cover_page, TextLine + from calibre.ebooks import calibre_cover title = self.title if isinstance(self.title, unicode) else \ self.title.decode(preferred_encoding, 'replace') date = strftime(self.timefmt) - lines = [TextLine(title, 44), TextLine(date, 32)] - img_data = create_cover_page(lines, I('library.png'), output_format='jpg') + img_data = calibre_cover(title, date) cover_file.write(img_data) cover_file.flush() except: