mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 10:44:09 -04:00
merge from trunk
This commit is contained in:
commit
a89d168ad8
@ -54,7 +54,7 @@ class ANDROID(USBMS):
|
||||
0x1004 : { 0x61cc : [0x100] },
|
||||
|
||||
# Archos
|
||||
0x0e79 : { 0x1420 : [0x0216]},
|
||||
0x0e79 : { 0x1419: [0x0216], 0x1420 : [0x0216]},
|
||||
|
||||
}
|
||||
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
|
||||
@ -70,10 +70,10 @@ class ANDROID(USBMS):
|
||||
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
|
||||
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
|
||||
'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE',
|
||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID']
|
||||
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT']
|
||||
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
|
||||
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
|
||||
'A70S']
|
||||
'A70S', 'A101IT']
|
||||
|
||||
OSX_MAIN_MEM = 'Android Device Main Memory'
|
||||
|
||||
|
@ -8,11 +8,12 @@ __docformat__ = 'restructuredtext en'
|
||||
import os
|
||||
from functools import partial
|
||||
|
||||
from PyQt4.Qt import QInputDialog, QPixmap, QMenu
|
||||
from PyQt4.Qt import QPixmap, QMenu
|
||||
|
||||
|
||||
from calibre.gui2 import error_dialog, choose_files, \
|
||||
choose_dir, warning_dialog, info_dialog
|
||||
from calibre.gui2.dialogs.add_empty_book import AddEmptyBookDialog
|
||||
from calibre.gui2.widgets import IMAGE_EXTENSIONS
|
||||
from calibre.ebooks import BOOK_EXTENSIONS
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
@ -42,7 +43,7 @@ class AddAction(InterfaceAction):
|
||||
'ebook file is a different book)'), self.add_recursive_multiple)
|
||||
self.add_menu.addSeparator()
|
||||
self.add_menu.addAction(_('Add Empty book. (Book entry with no '
|
||||
'formats)'), self.add_empty)
|
||||
'formats)'), self.add_empty, _('Shift+Ctrl+E'))
|
||||
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
|
||||
self.qaction.setMenu(self.add_menu)
|
||||
self.qaction.triggered.connect(self.add_books)
|
||||
@ -83,12 +84,21 @@ class AddAction(InterfaceAction):
|
||||
Add an empty book item to the library. This does not import any formats
|
||||
from a book file.
|
||||
'''
|
||||
num, ok = QInputDialog.getInt(self.gui, _('How many empty books?'),
|
||||
_('How many empty books should be added?'), 1, 1, 100)
|
||||
if ok:
|
||||
author = None
|
||||
index = self.gui.library_view.currentIndex()
|
||||
if index.isValid():
|
||||
raw = index.model().db.authors(index.row())
|
||||
if raw:
|
||||
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
|
||||
if authors:
|
||||
author = authors[0]
|
||||
dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db, author)
|
||||
if dlg.exec_() == dlg.Accepted:
|
||||
num = dlg.qty_to_add
|
||||
from calibre.ebooks.metadata import MetaInformation
|
||||
for x in xrange(num):
|
||||
self.gui.library_view.model().db.import_book(MetaInformation(None), [])
|
||||
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
||||
self.gui.library_view.model().db.import_book(mi, [])
|
||||
self.gui.library_view.model().books_added(num)
|
||||
|
||||
def add_isbns(self, books, add_tags=[]):
|
||||
|
@ -32,7 +32,7 @@ class LibraryUsageStats(object): # {{{
|
||||
locs = list(self.stats.keys())
|
||||
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
|
||||
reverse=True)
|
||||
for key in locs[15:]:
|
||||
for key in locs[25:]:
|
||||
self.stats.pop(key)
|
||||
gprefs.set('library_usage_stats', self.stats)
|
||||
|
||||
|
@ -379,7 +379,8 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
|
||||
w = bulk_widgets[type](db, col, parent)
|
||||
else:
|
||||
w = widgets[type](db, col, parent)
|
||||
w.initialize(book_id)
|
||||
if book_id is not None:
|
||||
w.initialize(book_id)
|
||||
return w
|
||||
x = db.custom_column_num_map
|
||||
cols = list(x)
|
||||
|
85
src/calibre/gui2/dialogs/add_empty_book.py
Normal file
85
src/calibre/gui2/dialogs/add_empty_book.py
Normal file
@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__license__ = 'GPL v3'
|
||||
|
||||
|
||||
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
|
||||
QApplication, QSpinBox, QToolButton, QIcon
|
||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||
from calibre.gui2.widgets import CompleteComboBox
|
||||
from calibre.utils.icu import sort_key
|
||||
|
||||
class AddEmptyBookDialog(QDialog):
|
||||
|
||||
def __init__(self, parent, db, author):
|
||||
QDialog.__init__(self, parent)
|
||||
self.db = db
|
||||
|
||||
self.setWindowTitle(_('How many empty books?'))
|
||||
|
||||
self._layout = QGridLayout(self)
|
||||
self.setLayout(self._layout)
|
||||
|
||||
self.qty_label = QLabel(_('How many empty books should be added?'))
|
||||
self._layout.addWidget(self.qty_label, 0, 0, 1, 2)
|
||||
|
||||
self.qty_spinbox = QSpinBox(self)
|
||||
self.qty_spinbox.setRange(1, 10000)
|
||||
self.qty_spinbox.setValue(1)
|
||||
self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)
|
||||
|
||||
self.author_label = QLabel(_('Set the author of the new books to:'))
|
||||
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
|
||||
|
||||
self.authors_combo = CompleteComboBox(self)
|
||||
self.authors_combo.setSizeAdjustPolicy(
|
||||
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.authors_combo.setEditable(True)
|
||||
self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
|
||||
self.initialize_authors(db, author)
|
||||
|
||||
self.clear_button = QToolButton(self)
|
||||
self.clear_button.setIcon(QIcon(I('trash.png')))
|
||||
self.clear_button.setToolTip(_('Reset author to Unknown'))
|
||||
self.clear_button.clicked.connect(self.reset_author)
|
||||
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
|
||||
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
self._layout.addWidget(button_box)
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def reset_author(self, *args):
|
||||
self.authors_combo.setEditText(_('Unknown'))
|
||||
|
||||
def initialize_authors(self, db, author):
|
||||
all_authors = db.all_authors()
|
||||
all_authors.sort(key=lambda x : sort_key(x[1]))
|
||||
for i in all_authors:
|
||||
id, name = i
|
||||
name = [name.strip().replace('|', ',') for n in name.split(',')]
|
||||
self.authors_combo.addItem(authors_to_string(name))
|
||||
|
||||
au = author
|
||||
if not au:
|
||||
au = _('Unknown')
|
||||
self.authors_combo.setEditText(au.replace('|', ','))
|
||||
|
||||
self.authors_combo.set_separator('&')
|
||||
self.authors_combo.set_space_before_sep(True)
|
||||
self.authors_combo.update_items_cache(db.all_author_names())
|
||||
|
||||
@property
|
||||
def qty_to_add(self):
|
||||
return self.qty_spinbox.value()
|
||||
|
||||
@property
|
||||
def selected_authors(self):
|
||||
return string_to_authors(unicode(self.authors_combo.text()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
d = AddEmptyBookDialog()
|
||||
d.exec_()
|
@ -15,9 +15,10 @@ from PyQt4.Qt import Qt, QDateEdit, QDate, \
|
||||
from calibre.gui2.widgets import EnLineEdit, CompleteComboBox, \
|
||||
EnComboBox, FormatList, ImageView, CompleteLineEdit
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre.utils.config import tweaks, prefs
|
||||
from calibre.ebooks.metadata import title_sort, authors_to_string, \
|
||||
string_to_authors, check_isbn
|
||||
from calibre.ebooks.metadata.meta import get_metadata
|
||||
from calibre.gui2 import file_icon_provider, UNDEFINED_QDATE, UNDEFINED_DATE, \
|
||||
choose_files, error_dialog, choose_images, question_dialog
|
||||
from calibre.utils.date import local_tz, qt_to_dt
|
||||
@ -440,7 +441,6 @@ class FormatsManager(QWidget): # {{{
|
||||
self.metadata_from_format_button = QToolButton(self)
|
||||
self.metadata_from_format_button.setIcon(QIcon(I('edit_input.png')))
|
||||
self.metadata_from_format_button.setIconSize(QSize(32, 32))
|
||||
# TODO: Implement the *_from_format buttons
|
||||
|
||||
self.add_format_button = QToolButton(self)
|
||||
self.add_format_button.setIcon(QIcon(I('add_book.png')))
|
||||
@ -565,6 +565,36 @@ class FormatsManager(QWidget): # {{{
|
||||
fmt = item.ext
|
||||
self.dialog.view_format.emit(fmt)
|
||||
|
||||
def get_selected_format_metadata(self, db, id_):
|
||||
old = prefs['read_file_metadata']
|
||||
if not old:
|
||||
prefs['read_file_metadata'] = True
|
||||
try:
|
||||
row = self.formats.currentRow()
|
||||
fmt = self.formats.item(row)
|
||||
if fmt is None:
|
||||
if self.formats.count() == 1:
|
||||
fmt = self.formats.item(0)
|
||||
if fmt is None:
|
||||
error_dialog(self, _('No format selected'),
|
||||
_('No format selected')).exec_()
|
||||
return None, None
|
||||
ext = fmt.ext.lower()
|
||||
if fmt.path is None:
|
||||
stream = db.format(id_, ext, as_file=True, index_is_id=True)
|
||||
else:
|
||||
stream = open(fmt.path, 'r+b')
|
||||
try:
|
||||
mi = get_metadata(stream, ext)
|
||||
return mi, ext
|
||||
except:
|
||||
error_dialog(self, _('Could not read metadata'),
|
||||
_('Could not read metadata from %s format')%ext).exec_()
|
||||
return None, None
|
||||
finally:
|
||||
if old != prefs['read_file_metadata']:
|
||||
prefs['read_file_metadata'] = old
|
||||
|
||||
# }}}
|
||||
|
||||
class Cover(ImageView): # {{{
|
||||
|
@ -5,6 +5,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
|
||||
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \
|
||||
@ -12,11 +13,13 @@ from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
|
||||
QSizePolicy
|
||||
|
||||
from calibre.ebooks.metadata import authors_to_string, string_to_authors
|
||||
from calibre.gui2 import ResizableDialog
|
||||
from calibre.gui2 import ResizableDialog, error_dialog, gprefs
|
||||
from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \
|
||||
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \
|
||||
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \
|
||||
BuddyLabel, DateEdit, PubdateEdit
|
||||
from calibre.gui2.custom_column_widgets import populate_metadata_page
|
||||
from calibre.utils.config import tweaks
|
||||
|
||||
class MetadataSingleDialog(ResizableDialog):
|
||||
|
||||
@ -52,13 +55,23 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
|
||||
self.create_basic_metadata_widgets()
|
||||
|
||||
if len(self.db.custom_column_label_map) == 0:
|
||||
self.central_widget.tabBar().setVisible(False)
|
||||
else:
|
||||
self.create_custom_metadata_widgets()
|
||||
|
||||
|
||||
self.do_layout()
|
||||
geom = gprefs.get('metasingle_window_geometry3', None)
|
||||
if geom is not None:
|
||||
self.restoreGeometry(bytes(geom))
|
||||
# }}}
|
||||
|
||||
def create_basic_metadata_widgets(self): # {{{
|
||||
self.basic_metadata_widgets = []
|
||||
|
||||
self.title = TitleEdit(self)
|
||||
self.title.textChanged.connect(self.update_window_title)
|
||||
self.deduce_title_sort_button = QToolButton(self)
|
||||
self.deduce_title_sort_button.setToolTip(
|
||||
_('Automatically create the title sort entry based on the current '
|
||||
@ -97,7 +110,10 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
|
||||
self.formats_manager = FormatsManager(self)
|
||||
self.basic_metadata_widgets.append(self.formats_manager)
|
||||
|
||||
self.formats_manager.metadata_from_format_button.clicked.connect(
|
||||
self.metadata_from_format)
|
||||
self.formats_manager.cover_from_format_button.clicked.connect(
|
||||
self.cover_from_format)
|
||||
self.cover = Cover(self)
|
||||
self.basic_metadata_widgets.append(self.cover)
|
||||
|
||||
@ -131,6 +147,25 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
font.setBold(True)
|
||||
self.fetch_metadata_button.setFont(font)
|
||||
|
||||
|
||||
# }}}
|
||||
|
||||
def create_custom_metadata_widgets(self): # {{{
|
||||
self.custom_metadata_widgets_parent = w = QWidget(self)
|
||||
layout = QGridLayout()
|
||||
w.setLayout(layout)
|
||||
self.custom_metadata_widgets, self.__cc_spacers = \
|
||||
populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
|
||||
two_column=tweaks['metadata_single_use_2_cols_for_custom_fields'])
|
||||
self.__custom_col_layouts = [layout]
|
||||
ans = self.custom_metadata_widgets
|
||||
for i in range(len(ans)-1):
|
||||
if len(ans[i+1].widgets) == 2:
|
||||
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1])
|
||||
else:
|
||||
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0])
|
||||
for c in range(2, len(ans[i].widgets), 2):
|
||||
w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1])
|
||||
# }}}
|
||||
|
||||
def do_layout(self): # {{{
|
||||
@ -142,8 +177,15 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.tabs[0].l = l = QVBoxLayout()
|
||||
self.tabs[0].tl = tl = QGridLayout()
|
||||
self.tabs[0].setLayout(l)
|
||||
w = getattr(self, 'custom_metadata_widgets_parent', None)
|
||||
if w is not None:
|
||||
self.tabs.append(w)
|
||||
self.central_widget.addTab(w, _('&Custom metadata'))
|
||||
l.addLayout(tl)
|
||||
|
||||
sto = QWidget.setTabOrder
|
||||
sto(self.fetch_metadata_button, self.title)
|
||||
|
||||
def create_row(row, one, two, three, col=1, icon='forward.png'):
|
||||
ql = BuddyLabel(one)
|
||||
tl.addWidget(ql, row, col+0, 1, 1)
|
||||
@ -156,13 +198,18 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
tl.addWidget(ql, row, col+3, 1, 1)
|
||||
self.labels.append(ql)
|
||||
tl.addWidget(three, row, col+4, 1, 1)
|
||||
sto(one, two)
|
||||
sto(two, three)
|
||||
|
||||
tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
|
||||
|
||||
create_row(0, self.title, self.deduce_title_sort_button, self.title_sort)
|
||||
sto(self.title_sort, self.authors)
|
||||
create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort)
|
||||
sto(self.author_sort, self.series)
|
||||
create_row(2, self.series, self.remove_unused_series_button,
|
||||
self.series_index, icon='trash.png')
|
||||
sto(self.series_index, self.swap_title_author_button)
|
||||
|
||||
tl.addWidget(self.formats_manager, 0, 6, 3, 1)
|
||||
|
||||
@ -219,6 +266,18 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
self.book_id = id_
|
||||
for widget in self.basic_metadata_widgets:
|
||||
widget.initialize(self.db, id_)
|
||||
for widget in self.custom_metadata_widgets:
|
||||
widget.initialize(id_)
|
||||
self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)
|
||||
|
||||
|
||||
def update_window_title(self, *args):
|
||||
title = self.title.current_val
|
||||
if len(title) > 50:
|
||||
title = title[:50] + u'\u2026'
|
||||
self.setWindowTitle(_('Edit Meta Information') + ' - ' +
|
||||
title)
|
||||
|
||||
|
||||
def swap_title_author(self, *args):
|
||||
title = self.title.current_val
|
||||
@ -241,9 +300,99 @@ class MetadataSingleDialog(ResizableDialog):
|
||||
def tags_editor(self, *args):
|
||||
self.tags.edit(self.db, self.book_id)
|
||||
|
||||
def metadata_from_format(self, *args):
|
||||
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||
self.book_id)
|
||||
if mi is not None:
|
||||
self.update_from_mi(mi)
|
||||
|
||||
def cover_from_format(self, *args):
|
||||
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
|
||||
self.book_id)
|
||||
if mi is None:
|
||||
return
|
||||
cdata = None
|
||||
if mi.cover and os.access(mi.cover, os.R_OK):
|
||||
cdata = open(mi.cover).read()
|
||||
elif mi.cover_data[1] is not None:
|
||||
cdata = mi.cover_data[1]
|
||||
if cdata is None:
|
||||
error_dialog(self, _('Could not read cover'),
|
||||
_('Could not read cover from %s format')%ext).exec_()
|
||||
return
|
||||
orig = self.cover.current_val
|
||||
self.cover.current_val = cdata
|
||||
if self.cover.current_val is None:
|
||||
self.cover.current_val = orig
|
||||
return error_dialog(self, _('Could not read cover'),
|
||||
_('The cover in the %s format is invalid')%ext,
|
||||
show=True)
|
||||
return
|
||||
|
||||
def update_from_mi(self, mi):
|
||||
if not mi.is_null('title'):
|
||||
self.title.current_val = mi.title
|
||||
if not mi.is_null('authors'):
|
||||
self.authors.current_val = mi.authors
|
||||
if not mi.is_null('author_sort'):
|
||||
self.author_sort.current_val = mi.author_sort
|
||||
if not mi.is_null('rating'):
|
||||
try:
|
||||
self.rating.current_val = mi.rating
|
||||
except:
|
||||
pass
|
||||
if not mi.is_null('publisher'):
|
||||
self.publisher.current_val = mi.publisher
|
||||
if not mi.is_null('tags'):
|
||||
self.tags.current_val = mi.tags
|
||||
if not mi.is_null('isbn'):
|
||||
self.isbn.current_val = mi.isbn
|
||||
if not mi.is_null('pubdate'):
|
||||
self.pubdate.current_val = mi.pubdate
|
||||
if not mi.is_null('series') and mi.series.strip():
|
||||
self.series.current_val = mi.series
|
||||
if mi.series_index is not None:
|
||||
self.series_index.current_val = float(mi.series_index)
|
||||
if mi.comments and mi.comments.strip():
|
||||
self.comments.current_val = mi.comments
|
||||
|
||||
def fetch_metadata(self, *args):
|
||||
pass # TODO: fetch metadata
|
||||
|
||||
def apply_changes(self):
|
||||
for widget in self.basic_metadata_widgets:
|
||||
try:
|
||||
if not widget.commit(self.db, self.book_id):
|
||||
return False
|
||||
except IOError, err:
|
||||
if err.errno == 13: # Permission denied
|
||||
import traceback
|
||||
fname = err.filename if err.filename else 'file'
|
||||
error_dialog(self, _('Permission denied'),
|
||||
_('Could not open %s. Is it being used by another'
|
||||
' program?')%fname, det_msg=traceback.format_exc(),
|
||||
show=True)
|
||||
return False
|
||||
raise
|
||||
for widget in getattr(self, 'custom_metadata_widgets', []):
|
||||
widget.commit(self.book_id)
|
||||
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
def accept(self):
|
||||
self.save_state()
|
||||
if not self.apply_changes():
|
||||
return
|
||||
ResizableDialog.accept(self)
|
||||
|
||||
def reject(self):
|
||||
self.save_state()
|
||||
ResizableDialog.reject(self)
|
||||
|
||||
def save_state(self):
|
||||
gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import QApplication
|
||||
|
Loading…
x
Reference in New Issue
Block a user