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

@ -26,14 +26,14 @@ class DailyTelegraph(BasicNewsRecipe):
keep_only_tags = [dict(name='div', attrs={'id': 'story'})]
#remove_tags = [dict(name=['object','link'])]
remove_tags = [dict(name ='div', attrs = {'class': 'story-info'}),
dict(name ='div', attrs = {'class': 'story-header-tools'}),
dict(name ='div', attrs = {'class': 'story-sidebar'}),
dict(name ='div', attrs = {'class': 'story-footer'}),
dict(name ='div', attrs = {'id': 'comments'}),
dict(name ='div', attrs = {'class': 'story-extras story-extras-2'}),
dict(name ='div', attrs = {'class': 'group item-count-1 story-related'})
# remove_tags = [dict(name=['object','link'])]
remove_tags = [dict(name='div', attrs={'class': 'story-info'}),
dict(name='div', attrs={'class': 'story-header-tools'}),
dict(name='div', attrs={'class': 'story-sidebar'}),
dict(name='div', attrs={'class': 'story-footer'}),
dict(name='div', attrs={'id': 'comments'}),
dict(name='div', attrs={'class': 'story-extras story-extras-2'}),
dict(name='div', attrs={'class': 'group item-count-1 story-related'})
]
extra_css = '''
@ -45,30 +45,31 @@ 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'),
(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'),
(u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'),
(u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'),
(u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'),
(u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'),
(u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'),
(u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'),
(u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'),
(u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'),
(u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'),
(u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'),
(u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'),
(u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'),
(u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'),
(u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.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'),
(u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'),
(u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'),
(u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'),
(u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'),
(u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'),
(u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'),
(u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'),
(u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'),
(u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'),
(u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'),
(u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'),
(u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'),
(u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'),
(u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.xml')]
def get_browser(self):
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()
@ -80,10 +81,11 @@ def get_browser(self):
def get_article_url(self, article):
return article.id
#br = self.get_browser()
#br.open(article.link).read()
#print br.geturl()
# br = self.get_browser()
# br.open(article.link).read()
# print br.geturl()
# return br.geturl()
#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

@ -25,7 +25,7 @@ class ANDROID(USBMS):
VENDOR_ID = {
# HTC
0x0bb4 : { 0xc02 : HTC_BCDS,
0x0bb4 : {0xc02 : HTC_BCDS,
0xc01 : HTC_BCDS,
0xff9 : HTC_BCDS,
0xc86 : HTC_BCDS,
@ -52,13 +52,13 @@ class ANDROID(USBMS):
},
# Eken
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
0x040d : {0x8510 : [0x0001], 0x0851 : [0x1]},
# Trekstor
0x1e68 : { 0x006a : [0x0231] },
0x1e68 : {0x006a : [0x0231]},
# Motorola
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
0x22b8 : {0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
0x2de8 : [0x229],
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
@ -111,7 +111,7 @@ class ANDROID(USBMS):
},
# Samsung
0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400],
0x04e8 : {0x681d : [0x0222, 0x0223, 0x0224, 0x0400],
0x681c : [0x0222, 0x0223, 0x0224, 0x0400],
0x6640 : [0x0100],
0x685b : [0x0400, 0x0226],
@ -130,7 +130,7 @@ class ANDROID(USBMS):
0xc001 : [0x0226],
0xc004 : [0x0226],
0x8801 : [0x0226, 0x0227],
0xe115 : [0x0216], # PocketBook A10
0xe115 : [0x0216], # PocketBook A10
},
# Another Viewsonic
@ -139,10 +139,10 @@ class ANDROID(USBMS):
},
# Acer
0x502 : { 0x3203 : [0x0100, 0x224]},
0x502 : {0x3203 : [0x0100, 0x224]},
# Dell
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]},
0x413c : {0xb007 : [0x0100, 0x0224, 0x0226]},
# LG
0x1004 : {
@ -166,25 +166,25 @@ class ANDROID(USBMS):
# Huawei
# Disabled as this USB id is used by various USB flash drives
#0x45e : { 0x00e1 : [0x007], },
# 0x45e : { 0x00e1 : [0x007], },
# T-Mobile
0x0408 : { 0x03ba : [0x0109], },
0x0408 : {0x03ba : [0x0109], },
# Xperia
0x13d3 : { 0x3304 : [0x0001, 0x0002] },
0x13d3 : {0x3304 : [0x0001, 0x0002]},
# CREEL?? Also Nextbook and Wayteq
0x5e3 : { 0x726 : [0x222] },
0x5e3 : {0x726 : [0x222]},
# ZTE
0x19d2 : { 0x1353 : [0x226], 0x1351 : [0x227] },
0x19d2 : {0x1353 : [0x226], 0x1351 : [0x227]},
# Advent
0x0955 : { 0x7100 : [0x9999] }, # This is the same as the Notion Ink Adam
0x0955 : {0x7100 : [0x9999]}, # This is the same as the Notion Ink Adam
# Kobo
0x2237: { 0x2208 : [0x0226] },
0x2237: {0x2208 : [0x0226]},
# Lenovo
0x17ef : {
@ -193,10 +193,10 @@ class ANDROID(USBMS):
},
# Pantech
0x10a9 : { 0x6050 : [0x227] },
0x10a9 : {0x6050 : [0x227]},
# Prestigio and Teclast
0x2207 : { 0 : [0x222], 0x10 : [0x222] },
0x2207 : {0 : [0x222], 0x10 : [0x222]},
}
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books',
@ -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 @@ def upload_cover(self, path, filename, metadata, filepath):
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 @@ def upload_cover(self, path, filename, metadata, filepath):
coverfile.write(coverdata)

View file

@ -560,7 +560,9 @@ def __init__(self, stream, basedir=os.getcwdu(), unquote_urls=True,
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 @@
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 @@ def initialize(self, book_id):
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 @@ def set_to_today(self):
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):
@ -211,7 +221,7 @@ def setup_ui(self, parent):
self._layout = QVBoxLayout()
self._tb = CommentsEditor(self._box)
self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
#self._tb.setTabChangesFocus(True)
# self._tb.setTabChangesFocus(True)
self._layout.addWidget(self._tb)
self._box.setLayout(self._layout)
self.widgets = [self._box]
@ -534,7 +544,7 @@ def widget_factory(typ, key):
column = row = base_row = max_row = 0
for key in cols:
if not fm[key]['is_editable']:
continue # this almost never happens
continue # this almost never happens
dt = fm[key]['datatype']
if dt == 'composite' or (bulk and dt == 'comments'):
continue
@ -595,7 +605,6 @@ def gui_val(self):
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:
@ -633,7 +642,7 @@ def make_widgets(self, parent, main_widget_class, extra_label_text=''):
self.main_widget = main_widget_class(w)
l.addWidget(self.main_widget)
l.setStretchFactor(self.main_widget, 10)
self.a_c_checkbox = QCheckBox( _('Apply changes'), w)
self.a_c_checkbox = QCheckBox(_('Apply changes'), w)
l.addWidget(self.a_c_checkbox)
self.ignore_change_signals = True
@ -1054,3 +1063,5 @@ def getter(self):
'series': BulkSeries,
'enumeration': BulkEnumeration,
}

View file

@ -27,7 +27,7 @@ def partial(*args, **kwargs):
_keep_refs.append(ans)
return ans
class LibraryViewMixin(object): # {{{
class LibraryViewMixin(object): # {{{
def __init__(self, db):
self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection)
@ -100,7 +100,7 @@ def search_done(self, view, ok):
# }}}
class LibraryWidget(Splitter): # {{{
class LibraryWidget(Splitter): # {{{
def __init__(self, parent):
orientation = Qt.Vertical
@ -119,7 +119,7 @@ def __init__(self, parent):
self.addWidget(parent.library_view)
# }}}
class Stack(QStackedWidget): # {{{
class Stack(QStackedWidget): # {{{
def __init__(self, parent):
QStackedWidget.__init__(self, parent)
@ -147,7 +147,7 @@ def __init__(self, parent):
# }}}
class UpdateLabel(QLabel): # {{{
class UpdateLabel(QLabel): # {{{
def __init__(self, *args, **kwargs):
QLabel.__init__(self, *args, **kwargs)
@ -157,22 +157,22 @@ def contextMenuEvent(self, e):
pass
# }}}
class StatusBar(QStatusBar): # {{{
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 @@ def initialize(self, systray=None):
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)
@ -207,11 +229,11 @@ def clear_message(self):
# }}}
class LayoutMixin(object): # {{{
class LayoutMixin(object): # {{{
def __init__(self):
if config['gui_layout'] == 'narrow': # narrow {{{
if config['gui_layout'] == 'narrow': # narrow {{{
self.book_details = BookDetails(False, self)
self.stack = Stack(self)
self.bd_splitter = Splitter('book_details_splitter',
@ -224,7 +246,7 @@ def __init__(self):
self.centralwidget.layout().addWidget(self.bd_splitter)
button_order = ('tb', 'bd', 'cb')
# }}}
else: # wide {{{
else: # wide {{{
self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.png'), initial_side_size=200,
orientation=Qt.Horizontal, parent=self, side_index=1,
@ -312,9 +334,15 @@ def save_layout_state(self):
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 @@
__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.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 @@ def __call__(self, id_, key, fmt, db, color_cache):
return color
except:
pass
# }}}
class ColumnIcon(object):
class ColumnIcon(object): # {{{
def __init__(self, formatter):
self.mi = None
@ -108,8 +110,9 @@ def __call__(self, id_, key, fmt, kind, db, icon_cache, icon_bitmap_cache):
return icon_bitmap
except:
pass
# }}}
class BooksModel(QAbstractTableModel): # {{{
class BooksModel(QAbstractTableModel): # {{{
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
sorting_done = pyqtSignal(object, name='sortingDone')
@ -150,7 +153,7 @@ def __init__(self, parent=None, buffer=40):
self.default_image = default_image()
self.sorted_on = DEFAULT_SORT
self.sort_history = [self.sorted_on]
self.last_search = '' # The last search performed on this model
self.last_search = '' # The last search performed on this model
self.column_map = []
self.headers = {}
self.alignment_map = {}
@ -240,7 +243,6 @@ def stop_metadata_backup(self):
# 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,9 +284,16 @@ def count_changed(self, *args):
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))]
return [self.index(index.row(), c) for c in range(self.columnCount(None))]
@property
def by_author(self):
@ -332,7 +341,7 @@ def get_next_highlighted_id(self, current_row, forward):
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:
@ -611,7 +620,7 @@ def cover(self, row_number):
data = None
try:
data = self.db.cover(row_number)
except IndexError: # Happens if database has not yet been refreshed
except IndexError: # Happens if database has not yet been refreshed
pass
if not data:
@ -673,7 +682,7 @@ def datetime_type(r, idx=-1):
return QVariant(UNDEFINED_QDATETIME)
def bool_type(r, idx=-1):
return None # displayed using a decorator
return None # displayed using a decorator
def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True):
val = force_to_bool(self.db.data[r][idx])
@ -884,18 +893,18 @@ def data(self, index, role):
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,
'left')]
return QVariant(ans)
#elif role == Qt.ToolTipRole and index.isValid():
# elif role == Qt.ToolTipRole and index.isValid():
# if self.column_map[index.column()] in self.editable_cols:
# return QVariant(_("Double click to <b>edit</b> me<br><br>"))
return NONE
def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal:
if section >= len(self.column_map): # same problem as in data, the column_map can be wrong
if section >= len(self.column_map): # same problem as in data, the column_map can be wrong
return None
if role == Qt.ToolTipRole:
ht = self.column_map[section]
if ht == 'timestamp': # change help text because users know this field as 'date'
if ht == 'timestamp': # change help text because users know this field as 'date'
ht = 'date'
if self.db.field_metadata[self.column_map[section]]['is_category']:
is_cat = _('. This column can be Quickview\'ed')
@ -909,11 +918,10 @@ def headerData(self, section, orientation, role):
col = self.db.field_metadata['uuid']['rec_index']
return QVariant(_('This book\'s UUID is "{0}"').format(self.db.data[section][col]))
if role == Qt.DisplayRole: # orientation is vertical
if role == Qt.DisplayRole: # orientation is vertical
return QVariant(section+1)
return NONE
def flags(self, index):
flags = QAbstractTableModel.flags(self, index)
if index.isValid():
@ -973,7 +981,7 @@ def set_custom_column_data(self, row, colhead, value):
tmpl = unicode(value.toString()).strip()
disp = cc['display']
disp['composite_template'] = tmpl
self.db.set_custom_column_metadata(cc['colnum'], display = disp)
self.db.set_custom_column_metadata(cc['colnum'], display=disp)
self.refresh(reset=True)
return True
@ -991,7 +999,7 @@ def setData(self, index, value, role):
return self._set_data(index, value)
except (IOError, OSError) as err:
import traceback
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(get_gui(), _('Permission denied'),
@ -1069,7 +1077,7 @@ def _set_data(self, index, value):
# }}}
class OnDeviceSearch(SearchQueryParser): # {{{
class OnDeviceSearch(SearchQueryParser): # {{{
USABLE_LOCATIONS = [
'all',
@ -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 @@ def get_matches(self, location, query):
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 @@ def get_matches(self, location, query):
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:
@ -1152,13 +1159,13 @@ def get_matches(self, location, query):
if _match(query, vals, m, use_primary_find_in_search=upf):
matches.add(index)
break
except ValueError: # Unicode errors
except ValueError: # Unicode errors
traceback.print_exc()
return matches
# }}}
class DeviceDBSortKeyGen(object): # {{{
class DeviceDBSortKeyGen(object): # {{{
def __init__(self, attr, keyfunc, db):
self.attr = attr
@ -1173,7 +1180,7 @@ def __call__(self, x):
return ans
# }}}
class DeviceBooksModel(BooksModel): # {{{
class DeviceBooksModel(BooksModel): # {{{
booklist_dirtied = pyqtSignal()
upload_collections = pyqtSignal(object)
@ -1202,6 +1209,12 @@ def __init__(self, parent):
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 @@ def deletion_done(self, job, succeeded=True):
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 @@ def flags(self, index):
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 @@ def search(self, text, reset=True):
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 @@ def set_database(self, db):
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]]
@ -1436,7 +1453,7 @@ def current_changed(self, current, previous, emit_signal=True):
return data
def paths(self, rows):
return [self.db[self.map[r.row()]].path for r in rows ]
return [self.db[self.map[r.row()]].path for r in rows]
def paths_for_db_ids(self, db_ids, as_map=False):
res = defaultdict(list) if as_map else []
@ -1521,7 +1538,7 @@ def data(self, index, role):
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 @@ def set_editable(self, editable):
# }}}

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 @@
import re
import urllib
from base64 import b64encode
from contextlib import closing
from lxml import html
@ -25,21 +26,19 @@
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 @@ def initialize(self, library_path, db, listener, actions, show_gui=True):
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 @@ def location_selected(self, location):
# 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

@ -24,7 +24,7 @@
history = XMLConfig('history')
class ProgressIndicator(QWidget): # {{{
class ProgressIndicator(QWidget): # {{{
def __init__(self, *args):
QWidget.__init__(self, *args)
@ -57,7 +57,7 @@ def stop(self):
self.setVisible(False)
# }}}
class FilenamePattern(QWidget, Ui_Form): # {{{
class FilenamePattern(QWidget, Ui_Form): # {{{
changed_signal = pyqtSignal()
@ -82,7 +82,8 @@ def initialize(self, defaults=False):
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 @@ def do_test(self):
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)
@ -157,7 +157,7 @@ def commit(self):
# }}}
class FormatList(QListWidget): # {{{
class FormatList(QListWidget): # {{{
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
formats_dropped = pyqtSignal(object, object)
delete_format = pyqtSignal()
@ -186,7 +186,6 @@ def dropEvent(self, event):
if d.err is None:
self.formats_dropped.emit(event, [d.fpath])
def dragMoveEvent(self, event):
event.acceptProposedAction()
@ -198,7 +197,7 @@ def keyPressEvent(self, event):
# }}}
class ImageDropMixin(object): # {{{
class ImageDropMixin(object): # {{{
'''
Adds support for dropping images onto widgets and a context menu for
copy/pasting images.
@ -272,7 +271,7 @@ def paste_from_clipboard(self):
pixmap_to_data(pmap))
# }}}
class ImageView(QWidget, ImageDropMixin): # {{{
class ImageView(QWidget, ImageDropMixin): # {{{
BORDER_WIDTH = 1
cover_changed = pyqtSignal(object)
@ -338,7 +337,7 @@ def paintEvent(self, event):
p.end()
# }}}
class CoverView(QGraphicsView, ImageDropMixin): # {{{
class CoverView(QGraphicsView, ImageDropMixin): # {{{
cover_changed = pyqtSignal(object)
@ -393,7 +392,7 @@ def items(self):
yield self.item(i)
# }}}
class LineEditECM(object): # {{{
class LineEditECM(object): # {{{
'''
Extend the context menu of a QLineEdit to include more actions.
@ -438,7 +437,7 @@ def capitalize(self):
# }}}
class EnLineEdit(LineEditECM, QLineEdit): # {{{
class EnLineEdit(LineEditECM, QLineEdit): # {{{
'''
Enhanced QLineEdit.
@ -449,7 +448,7 @@ class EnLineEdit(LineEditECM, QLineEdit): # {{{
pass
# }}}
class ItemsCompleter(QCompleter): # {{{
class ItemsCompleter(QCompleter): # {{{
'''
A completer object that completes a list of tags. It is used in conjunction
@ -541,7 +540,7 @@ def complete_text(self, text):
# }}}
class EnComboBox(QComboBox): # {{{
class EnComboBox(QComboBox): # {{{
'''
Enhanced QComboBox.
@ -567,7 +566,7 @@ def setText(self, text):
# }}}
class CompleteComboBox(EnComboBox): # {{{
class CompleteComboBox(EnComboBox): # {{{
def __init__(self, *args):
EnComboBox.__init__(self, *args)
@ -584,7 +583,7 @@ def set_space_before_sep(self, space_before):
# }}}
class HistoryLineEdit(QComboBox): # {{{
class HistoryLineEdit(QComboBox): # {{{
lost_focus = pyqtSignal()
@ -637,7 +636,7 @@ def focusOutEvent(self, e):
# }}}
class ComboBoxWithHelp(QComboBox): # {{{
class ComboBoxWithHelp(QComboBox): # {{{
'''
A combobox where item 0 is help text. CurrentText will return '' for item 0.
Be sure to always fetch the text with currentText. Don't use the signals
@ -686,7 +685,7 @@ def hidePopup(self):
# }}}
class EncodingComboBox(QComboBox): # {{{
class EncodingComboBox(QComboBox): # {{{
'''
A combobox that holds text encodings support
by Python. This is only populated with the most
@ -711,7 +710,7 @@ def __init__(self, parent=None):
# }}}
class PythonHighlighter(QSyntaxHighlighter): # {{{
class PythonHighlighter(QSyntaxHighlighter): # {{{
Rules = []
Formats = {}
@ -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 @@ def __init__(self, parent=None):
"|".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 @@ def loadConfig(cls):
Config["%sfontbold" % name] = QVariant(bold).toBool()
Config["%sfontitalic" % name] = QVariant(italic).toBool()
@classmethod
def initializeFormats(cls):
Config = cls.Config
@ -829,7 +825,6 @@ def initializeFormats(cls):
format.setFontItalic(Config["%sfontitalic" % name])
PythonHighlighter.Formats[name] = format
def highlightBlock(self, text):
NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4)
@ -861,7 +856,7 @@ def highlightBlock(self, text):
# Slow but good quality highlighting for comments. For more
# speed, comment this out and add the following to __init__:
# PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment"))
# PythonHighlighter.Rules.append((QRegExp(r"#.*"), "comment"))
if text.isEmpty():
pass
elif text[0] == "#":
@ -900,7 +895,6 @@ def highlightBlock(self, text):
self.setFormat(i, text.length(),
PythonHighlighter.Formats["string"])
def rehighlight(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
QSyntaxHighlighter.rehighlight(self)
@ -955,8 +949,8 @@ def set_state_to_show(self, *args):
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 @@ def print_sizes(self):
@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 @@ def resizeEvent(self, ev):
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 @@ def double_clicked(self, *args):
# }}}