Merge from trunk

This commit is contained in:
Charles Haley 2013-05-19 11:54:46 +02:00
commit 7d4d42ea91
11 changed files with 217 additions and 151 deletions

View File

@ -45,7 +45,8 @@ class DailyTelegraph(BasicNewsRecipe):
.caption{font-family:Trebuchet MS,Trebuchet,Helvetica,sans-serif; font-size: xx-small;}
'''
feeds = [ (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'),
feeds = [
(u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'),
(u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'),
(u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'),
(u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'),
@ -68,7 +69,7 @@ class DailyTelegraph(BasicNewsRecipe):
br = BasicNewsRecipe.get_browser(self)
if self.username and self.password:
br.open('http://www.theaustralian.com.au')
br.select_form(nr=0)
br.select_form(nr=1)
br['username'] = self.username
br['password'] = self.password
raw = br.submit().read()
@ -87,3 +88,4 @@ class DailyTelegraph(BasicNewsRecipe):
# return br.geturl()

View File

@ -1661,6 +1661,7 @@ class StoreWoblinkStore(StoreBase):
headquarters = 'PL'
formats = ['EPUB', 'MOBI', 'PDF', 'WOBLINK']
affiliate = True
class XinXiiStore(StoreBase):
name = 'XinXii'

View File

@ -219,7 +219,7 @@ class ANDROID(USBMS):
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0',
'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703', 'NEXT8D12',
'MEDIATEK', 'KEENHI', 'TECLAST', 'SURFTAB']
'MEDIATEK', 'KEENHI', 'TECLAST', 'SURFTAB', 'XENTA',]
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'A953', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',
@ -241,6 +241,7 @@ class ANDROID(USBMS):
'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD', 'XT894', '_USB',
'PROD_TAB13-201',
]
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
@ -253,7 +254,7 @@ class ANDROID(USBMS):
'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1', 'XT894',
'_USB',
'_USB', 'PROD_TAB13-201',
]
OSX_MAIN_MEM = 'Android Device Main Memory'
@ -369,7 +370,6 @@ class WEBOS(USBMS):
except ImportError:
import Image, ImageDraw
coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]:
cover = Image.open(cStringIO.StringIO(coverdata[2]))
@ -418,3 +418,4 @@ class WEBOS(USBMS):
coverfile.write(coverdata)

View File

@ -560,7 +560,9 @@ class OPF(object): # {{{
self.package_version = 0
self.metadata = self.metadata_path(self.root)
if not self.metadata:
raise ValueError('Malformed OPF file: No <metadata> element')
self.metadata = [self.root.makeelement('{http://www.idpf.org/2007/opf}metadata')]
self.root.insert(0, self.metadata[0])
self.metadata[0].tail = '\n'
self.metadata = self.metadata[0]
if unquote_urls:
self.unquote_urls()

View File

@ -7,10 +7,10 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \
QPushButton, QMessageBox, QToolButton
from PyQt4.Qt import (QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit,
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout,
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL,
QPushButton, QMessageBox, QToolButton, Qt)
from calibre.utils.date import qt_to_dt, now
from calibre.gui2.complete2 import EditWithComplete
@ -39,7 +39,6 @@ class Base(object):
def gui_val(self):
return self.getter()
def commit(self, book_id, notify=False):
val = self.gui_val
val = self.normalize_ui_val(val)
@ -159,6 +158,17 @@ class DateTimeEdit(QDateTimeEdit):
def set_to_clear(self):
self.setDateTime(UNDEFINED_QDATETIME)
def keyPressEvent(self, ev):
if ev.key() == Qt.Key_Minus:
ev.accept()
self.setDateTime(self.minimumDateTime())
elif ev.key() == Qt.Key_Equal:
ev.accept()
self.setDateTime(QDateTime.currentDateTime())
else:
return QDateTimeEdit.keyPressEvent(self, ev)
class DateTime(Base):
def setup_ui(self, parent):
@ -595,7 +605,6 @@ class BulkBase(Base):
self._cached_gui_val_ = self.getter()
return self._cached_gui_val_
def get_initial_value(self, book_ids):
values = set([])
for book_id in book_ids:
@ -1054,3 +1063,5 @@ bulk_widgets = {
'series': BulkSeries,
'enumeration': BulkEnumeration,
}

View File

@ -161,18 +161,18 @@ class StatusBar(QStatusBar): # {{{
def __init__(self, parent=None):
QStatusBar.__init__(self, parent)
self.default_message = __appname__ + ' ' + _('version') + ' ' + \
self.get_version() + ' ' + _('created by Kovid Goyal')
self.device_string = ''
self.update_label = UpdateLabel('')
self.total = self.current = self.selected = 0
self.addPermanentWidget(self.update_label)
self.update_label.setVisible(False)
self._font = QFont()
self._font.setBold(True)
self.setFont(self._font)
self.defmsg = QLabel(self.default_message)
self.defmsg = QLabel('')
self.defmsg.setFont(self._font)
self.addWidget(self.defmsg)
self.set_label()
def initialize(self, systray=None):
self.systray = systray
@ -180,17 +180,39 @@ class StatusBar(QStatusBar): # {{{
def device_connected(self, devname):
self.device_string = _('Connected ') + devname
self.defmsg.setText(self.default_message + ' ..::.. ' +
self.device_string)
self.set_label()
def update_state(self, total, current, selected):
self.total, self.current, self.selected = total, current, selected
self.set_label()
def set_label(self):
try:
self._set_label()
except:
import traceback
traceback.print_exc()
def _set_label(self):
msg = '%s %s %s' % (__appname__, _('version'), get_version())
if self.device_string:
msg += ' ..::.. ' + self.device_string
else:
msg += _(' %(created)s %(name)s') % dict(created=_('created by'), name='Kovid Goyal')
if self.total != self.current:
base = _('%(num)d of %(total)d books') % dict(num=self.current, total=self.total)
else:
base = _('%d books') % self.total
if self.selected > 0:
base = _('%(num)s, %(sel)d selected') % dict(num=base, sel=self.selected)
self.defmsg.setText('%s [%s]' % (msg, base))
self.clearMessage()
def device_disconnected(self):
self.device_string = ''
self.defmsg.setText(self.default_message)
self.clearMessage()
def get_version(self):
return get_version()
self.set_label()
def show_message(self, msg, timeout=0):
self.showMessage(msg, timeout)
@ -312,9 +334,15 @@ class LayoutMixin(object): # {{{
def read_layout_settings(self):
# View states are restored automatically when set_database is called
for x in ('cb', 'tb', 'bd'):
getattr(self, x+'_splitter').restore_state()
def update_status_bar(self, *args):
v = self.current_view()
selected = len(v.selectionModel().selectedRows())
total, current = v.model().counts()
self.status_bar.update_state(total, current, selected)
# }}}

View File

@ -6,7 +6,7 @@ __copyright__ = '2010, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import functools, re, os, traceback, errno, time
from collections import defaultdict
from collections import defaultdict, namedtuple
from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDateTime, QColor, QPixmap)
@ -29,6 +29,8 @@ from calibre.gui2.library import DEFAULT_SORT
from calibre.utils.localization import calibre_langcode_to_name
from calibre.library.coloring import color_row_key
Counts = namedtuple('Counts', 'total current')
def human_readable(size, precision=1):
""" Convert a size in bytes into megabytes """
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
@ -46,7 +48,7 @@ def default_image():
_default_image = QImage(I('default_cover.png'))
return _default_image
class ColumnColor(object):
class ColumnColor(object): # {{{
def __init__(self, formatter, colors):
self.mi = None
@ -70,9 +72,9 @@ class ColumnColor(object):
return color
except:
pass
# }}}
class ColumnIcon(object):
class ColumnIcon(object): # {{{
def __init__(self, formatter):
self.mi = None
@ -108,6 +110,7 @@ class ColumnIcon(object):
return icon_bitmap
except:
pass
# }}}
class BooksModel(QAbstractTableModel): # {{{
@ -240,7 +243,6 @@ class BooksModel(QAbstractTableModel): # {{{
# Would like to to a join here, but the thread might be waiting to
# do something on the GUI thread. Deadlock.
def refresh_ids(self, ids, current_row=-1):
self._clear_caches()
rows = self.db.refresh_ids(ids)
@ -282,6 +284,13 @@ class BooksModel(QAbstractTableModel): # {{{
self._clear_caches()
self.count_changed_signal.emit(self.db.count())
def counts(self):
if self.db.data.search_restriction_applied():
total = self.db.data.get_search_restriction_book_count()
else:
total = self.db.count()
return Counts(total, self.count())
def row_indices(self, index):
''' Return list indices of all cells in index.row()'''
return [self.index(index.row(), c) for c in range(self.columnCount(None))]
@ -332,7 +341,7 @@ class BooksModel(QAbstractTableModel): # {{{
while True:
row_ += 1 if forward else -1
if row_ < 0:
row_ = self.count() - 1;
row_ = self.count() - 1
elif row_ >= self.count():
row_ = 0
if self.id(row_) in self.ids_to_highlight_set:
@ -913,7 +922,6 @@ class BooksModel(QAbstractTableModel): # {{{
return QVariant(section+1)
return NONE
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
@ -1082,7 +1090,6 @@ class OnDeviceSearch(SearchQueryParser): # {{{
'inlibrary'
]
def __init__(self, model):
SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS)
self.model = model
@ -1105,7 +1112,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{
elif query.startswith('~'):
matchkind = REGEXP_MATCH
query = query[1:]
if matchkind != REGEXP_MATCH: ### leave case in regexps because it can be significant e.g. \S \W \D
if matchkind != REGEXP_MATCH: # leave case in regexps because it can be significant e.g. \S \W \D
query = query.lower()
if location not in self.USABLE_LOCATIONS:
@ -1137,9 +1144,9 @@ class OnDeviceSearch(SearchQueryParser): # {{{
if locvalue == 'inlibrary':
continue # this is bool, so can't match below
try:
### Can't separate authors because comma is used for name sep and author sep
### Exact match might not get what you want. For that reason, turn author
### exactmatch searches into contains searches.
# Can't separate authors because comma is used for name sep and author sep
# Exact match might not get what you want. For that reason, turn author
# exactmatch searches into contains searches.
if locvalue == 'author' and matchkind == EQUALS_MATCH:
m = CONTAINS_MATCH
else:
@ -1202,6 +1209,12 @@ class DeviceBooksModel(BooksModel): # {{{
self.editable = ['title', 'authors', 'collections']
self.book_in_library = None
def counts(self):
return Counts(len(self.db), len(self.map))
def count_changed(self, *args):
self.count_changed_signal.emit(len(self.db))
def mark_for_deletion(self, job, rows, rows_are_ids=False):
db_indices = rows if rows_are_ids else self.indices(rows)
db_items = [self.db[i] for i in db_indices if -1 < i < len(self.db)]
@ -1241,11 +1254,13 @@ class DeviceBooksModel(BooksModel): # {{{
if not succeeded:
indices = self.row_indices(self.index(row, 0))
self.dataChanged.emit(indices[0], indices[-1])
self.count_changed()
def paths_deleted(self, paths):
self.map = list(range(0, len(self.db)))
self.resort(False)
self.research(True)
self.count_changed()
def is_row_marked_for_deletion(self, row):
try:
@ -1276,9 +1291,9 @@ class DeviceBooksModel(BooksModel): # {{{
if index.isValid():
cname = self.column_map[index.column()]
if cname in self.editable and \
(cname != 'collections' or \
(callable(getattr(self.db, 'supports_collections', None)) and \
self.db.supports_collections() and \
(cname != 'collections' or
(callable(getattr(self.db, 'supports_collections', None)) and
self.db.supports_collections() and
device_prefs['manage_device_metadata']=='manual')):
flags |= Qt.ItemIsEditable
return flags
@ -1308,6 +1323,7 @@ class DeviceBooksModel(BooksModel): # {{{
self.last_search = text
if self.last_search:
self.searched.emit(True)
self.count_changed()
def research(self, reset=True):
self.search(self.last_search, reset)
@ -1377,6 +1393,7 @@ class DeviceBooksModel(BooksModel): # {{{
self.map = list(range(0, len(db)))
self.research(reset=False)
self.resort()
self.count_changed()
def cover(self, row):
item = self.db[self.map[row]]
@ -1521,7 +1538,7 @@ class DeviceBooksModel(BooksModel): # {{{
elif role == Qt.ToolTipRole and index.isValid():
if self.is_row_marked_for_deletion(row):
return QVariant(_('Marked for deletion'))
if cname in ['title', 'authors'] or (cname == 'collections' and \
if cname in ['title', 'authors'] or (cname == 'collections' and
self.db.supports_collections()):
return QVariant(_("Double click to <b>edit</b> me<br><br>"))
elif role == Qt.DecorationRole and cname == 'inlibrary':
@ -1590,3 +1607,4 @@ class DeviceBooksModel(BooksModel): # {{{
# }}}

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
store_version = 1 # Needed for dynamic plugin loading
store_version = 2 # Needed for dynamic plugin loading
__license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
store_version = 3 # Needed for dynamic plugin loading
store_version = 4 # Needed for dynamic plugin loading
__license__ = 'GPL 3'
__copyright__ = '2011-2013, Tomasz Długosz <tomek3d@gmail.com>'
@ -9,6 +9,7 @@ __docformat__ = 'restructuredtext en'
import re
import urllib
from base64 import b64encode
from contextlib import closing
from lxml import html
@ -25,21 +26,19 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
class WoblinkStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
#aff_root = 'https://www.a4b-tracking.com/pl/stat-click-text-link/16/58/'
aff_root = 'https://www.a4b-tracking.com/pl/stat-click-text-link/16/58/'
url = 'http://woblink.com/publication'
#aff_url = aff_root + str(b64encode(url))
aff_url = aff_root + str(b64encode(url))
detail_url = None
if detail_item:
detail_url = 'http://woblink.com' + detail_item #aff_root + str(b64encode('http://woblink.com' + detail_item))
detail_url = aff_root + str(b64encode('http://woblink.com' + detail_item))
if external or self.config.get('open_external', False):
#open_url(QUrl(url_slash_cleaner(detail_url if detail_url else aff_url)))
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else aff_url)))
else:
#d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else aff_url)
d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else url)
d = WebStoreDialog(self.gui, url, parent, detail_url if detail_url else aff_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()

View File

@ -325,6 +325,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if self.library_view.model().rowCount(None) < 3:
self.library_view.resizeColumnsToContents()
for view in ('library', 'memory', 'card_a', 'card_b'):
v = getattr(self, '%s_view' % view)
v.selectionModel().selectionChanged.connect(self.update_status_bar)
v.model().count_changed_signal.connect(self.update_status_bar)
self.library_view.model().count_changed()
self.bars_manager.database_changed(self.library_view.model().db)
self.library_view.model().database_changed.connect(self.bars_manager.database_changed,
@ -661,6 +666,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
# Reset the view in case something changed while it was invisible
self.current_view().reset()
self.set_number_of_books_shown()
self.update_status_bar()
def job_exception(self, job, dialog_title=_('Conversion Error')):
if not hasattr(self, '_modeless_dialogs'):

View File

@ -82,7 +82,8 @@ class FilenamePattern(QWidget, Ui_Form): # {{{
val = prefs['filename_pattern']
self.re.lineEdit().setText(val)
val_hist += gprefs.get('filename_pattern_history', ['(?P<title>.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?'])
val_hist += gprefs.get('filename_pattern_history', [
'(?P<title>.+)', '(?P<author>[^_-]+) -?\s*(?P<series>[^_0-9-]*)(?P<series_index>[0-9]*)\s*-\s*(?P<title>[^_].+) ?'])
if val in val_hist:
del val_hist[val_hist.index(val)]
val_hist.insert(0, val)
@ -136,7 +137,6 @@ class FilenamePattern(QWidget, Ui_Form): # {{{
self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn))
def pattern(self):
pat = unicode(self.re.lineEdit().text())
return re.compile(pat)
@ -186,7 +186,6 @@ class FormatList(QListWidget): # {{{
if d.err is None:
self.formats_dropped.emit(event, [d.fpath])
def dragMoveEvent(self, event):
event.acceptProposedAction()
@ -736,13 +735,11 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
CONSTANTS = ["False", "True", "None", "NotImplemented", "Ellipsis"]
def __init__(self, parent=None):
super(PythonHighlighter, self).__init__(parent)
if not self.Config:
self.loadConfig()
self.initializeFormats()
PythonHighlighter.Rules.append((QRegExp(
@ -752,7 +749,7 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
"|".join([r"\b%s\b" % builtin for builtin in self.BUILTINS])),
"builtin"))
PythonHighlighter.Rules.append((QRegExp(
"|".join([r"\b%s\b" % constant \
"|".join([r"\b%s\b" % constant
for constant in self.CONSTANTS])), "constant"))
PythonHighlighter.Rules.append((QRegExp(
r"\b[+-]?[0-9]+[lL]?\b"
@ -812,7 +809,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
Config["%sfontbold" % name] = QVariant(bold).toBool()
Config["%sfontitalic" % name] = QVariant(italic).toBool()
@classmethod
def initializeFormats(cls):
Config = cls.Config
@ -829,7 +825,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
format.setFontItalic(Config["%sfontitalic" % name])
PythonHighlighter.Formats[name] = format
def highlightBlock(self, text):
NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4)
@ -900,7 +895,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
self.setFormat(i, text.length(),
PythonHighlighter.Formats["string"])
def rehighlight(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
QSyntaxHighlighter.rehighlight(self)
@ -955,8 +949,8 @@ class LayoutButton(QToolButton):
def set_state_to_hide(self, *args):
self.setChecked(True)
self.setText(_('Hide %(label)s %(shortcut)s'%dict(
label=self.label, shortcut=self.shortcut)))
self.setText(_('Hide %(label)s %(shortcut)s')%dict(
label=self.label, shortcut=self.shortcut))
self.setToolTip(self.text())
self.setStatusTip(self.text())
@ -1045,11 +1039,13 @@ class Splitter(QSplitter):
@dynamic_property
def side_index_size(self):
def fget(self):
if self.count() < 2: return 0
if self.count() < 2:
return 0
return self.sizes()[self.side_index]
def fset(self, val):
if self.count() < 2: return
if self.count() < 2:
return
if val == 0 and not self.is_side_index_hidden:
self.save_state()
sizes = list(self.sizes())
@ -1081,7 +1077,8 @@ class Splitter(QSplitter):
self.resize_timer.start()
def get_state(self):
if self.count() < 2: return (False, 200)
if self.count() < 2:
return (False, 200)
return (self.desired_show, self.desired_side_size)
def apply_state(self, state, save_desired=True):
@ -1142,3 +1139,4 @@ class Splitter(QSplitter):
# }}}