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'})] keep_only_tags = [dict(name='div', attrs={'id': 'story'})]
#remove_tags = [dict(name=['object','link'])] # remove_tags = [dict(name=['object','link'])]
remove_tags = [dict(name ='div', attrs = {'class': 'story-info'}), remove_tags = [dict(name='div', attrs={'class': 'story-info'}),
dict(name ='div', attrs = {'class': 'story-header-tools'}), dict(name='div', attrs={'class': 'story-header-tools'}),
dict(name ='div', attrs = {'class': 'story-sidebar'}), dict(name='div', attrs={'class': 'story-sidebar'}),
dict(name ='div', attrs = {'class': 'story-footer'}), dict(name='div', attrs={'class': 'story-footer'}),
dict(name ='div', attrs = {'id': 'comments'}), dict(name='div', attrs={'id': 'comments'}),
dict(name ='div', attrs = {'class': 'story-extras story-extras-2'}), dict(name='div', attrs={'class': 'story-extras story-extras-2'}),
dict(name ='div', attrs = {'class': 'group item-count-1 story-related'}) dict(name='div', attrs={'class': 'group item-count-1 story-related'})
] ]
extra_css = ''' extra_css = '''
@ -45,30 +45,31 @@ class DailyTelegraph(BasicNewsRecipe):
.caption{font-family:Trebuchet MS,Trebuchet,Helvetica,sans-serif; font-size: xx-small;} .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'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'), (u'News', u'http://feeds.news.com.au/public/rss/2.0/aus_news_807.xml'),
(u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'), (u'Opinion', u'http://feeds.news.com.au/public/rss/2.0/aus_opinion_58.xml'),
(u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'), (u'The Nation', u'http://feeds.news.com.au/public/rss/2.0/aus_the_nation_62.xml'),
(u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'), (u'World News', u'http://feeds.news.com.au/public/rss/2.0/aus_world_808.xml'),
(u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'), (u'US Election', u'http://feeds.news.com.au/public/rss/2.0/aus_uselection_687.xml'),
(u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'), (u'Climate', u'http://feeds.news.com.au/public/rss/2.0/aus_climate_809.xml'),
(u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'), (u'Media', u'http://feeds.news.com.au/public/rss/2.0/aus_media_57.xml'),
(u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'), (u'IT', u'http://feeds.news.com.au/public/rss/2.0/ausit_itnews_topstories_367.xml'),
(u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'), (u'Exec Tech', u'http://feeds.news.com.au/public/rss/2.0/ausit_exec_topstories_385.xml'),
(u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'), (u'Higher Education', u'http://feeds.news.com.au/public/rss/2.0/aus_higher_education_56.xml'),
(u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'), (u'Arts', u'http://feeds.news.com.au/public/rss/2.0/aus_arts_51.xml'),
(u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'), (u'Travel', u'http://feeds.news.com.au/public/rss/2.0/aus_travel_and_indulgence_63.xml'),
(u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'), (u'Property', u'http://feeds.news.com.au/public/rss/2.0/aus_property_59.xml'),
(u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'), (u'Sport', u'http://feeds.news.com.au/public/rss/2.0/aus_sport_61.xml'),
(u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'), (u'Business', u'http://feeds.news.com.au/public/rss/2.0/aus_business_811.xml'),
(u'Commercial Property', u'http://feeds.news.com.au/public/rss/2.0/aus_business_commercial_property_708.xml'), (u'Aviation', u'http://feeds.news.com.au/public/rss/2.0/aus_business_aviation_706.xml'),
(u'Mining', u'http://feeds.news.com.au/public/rss/2.0/aus_business_mining_704.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): def get_browser(self):
br = BasicNewsRecipe.get_browser(self) br = BasicNewsRecipe.get_browser(self)
if self.username and self.password: if self.username and self.password:
br.open('http://www.theaustralian.com.au') br.open('http://www.theaustralian.com.au')
br.select_form(nr=0) br.select_form(nr=1)
br['username'] = self.username br['username'] = self.username
br['password'] = self.password br['password'] = self.password
raw = br.submit().read() raw = br.submit().read()
@ -80,10 +81,11 @@ class DailyTelegraph(BasicNewsRecipe):
def get_article_url(self, article): def get_article_url(self, article):
return article.id return article.id
#br = self.get_browser() # br = self.get_browser()
#br.open(article.link).read() # br.open(article.link).read()
#print br.geturl() # print br.geturl()
# return br.geturl()
#return br.geturl()

View File

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

View File

@ -25,7 +25,7 @@ class ANDROID(USBMS):
VENDOR_ID = { VENDOR_ID = {
# HTC # HTC
0x0bb4 : { 0xc02 : HTC_BCDS, 0x0bb4 : {0xc02 : HTC_BCDS,
0xc01 : HTC_BCDS, 0xc01 : HTC_BCDS,
0xff9 : HTC_BCDS, 0xff9 : HTC_BCDS,
0xc86 : HTC_BCDS, 0xc86 : HTC_BCDS,
@ -52,13 +52,13 @@ class ANDROID(USBMS):
}, },
# Eken # Eken
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] }, 0x040d : {0x8510 : [0x0001], 0x0851 : [0x1]},
# Trekstor # Trekstor
0x1e68 : { 0x006a : [0x0231] }, 0x1e68 : {0x006a : [0x0231]},
# Motorola # Motorola
0x22b8 : { 0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100], 0x22b8 : {0x41d9 : [0x216], 0x2d61 : [0x100], 0x2d67 : [0x100],
0x2de8 : [0x229], 0x2de8 : [0x229],
0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216], 0x41db : [0x216], 0x4285 : [0x216], 0x42a3 : [0x216],
0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216], 0x4286 : [0x216], 0x42b3 : [0x216], 0x42b4 : [0x216],
@ -111,7 +111,7 @@ class ANDROID(USBMS):
}, },
# Samsung # Samsung
0x04e8 : { 0x681d : [0x0222, 0x0223, 0x0224, 0x0400], 0x04e8 : {0x681d : [0x0222, 0x0223, 0x0224, 0x0400],
0x681c : [0x0222, 0x0223, 0x0224, 0x0400], 0x681c : [0x0222, 0x0223, 0x0224, 0x0400],
0x6640 : [0x0100], 0x6640 : [0x0100],
0x685b : [0x0400, 0x0226], 0x685b : [0x0400, 0x0226],
@ -130,7 +130,7 @@ class ANDROID(USBMS):
0xc001 : [0x0226], 0xc001 : [0x0226],
0xc004 : [0x0226], 0xc004 : [0x0226],
0x8801 : [0x0226, 0x0227], 0x8801 : [0x0226, 0x0227],
0xe115 : [0x0216], # PocketBook A10 0xe115 : [0x0216], # PocketBook A10
}, },
# Another Viewsonic # Another Viewsonic
@ -139,10 +139,10 @@ class ANDROID(USBMS):
}, },
# Acer # Acer
0x502 : { 0x3203 : [0x0100, 0x224]}, 0x502 : {0x3203 : [0x0100, 0x224]},
# Dell # Dell
0x413c : { 0xb007 : [0x0100, 0x0224, 0x0226]}, 0x413c : {0xb007 : [0x0100, 0x0224, 0x0226]},
# LG # LG
0x1004 : { 0x1004 : {
@ -166,25 +166,25 @@ class ANDROID(USBMS):
# Huawei # Huawei
# Disabled as this USB id is used by various USB flash drives # Disabled as this USB id is used by various USB flash drives
#0x45e : { 0x00e1 : [0x007], }, # 0x45e : { 0x00e1 : [0x007], },
# T-Mobile # T-Mobile
0x0408 : { 0x03ba : [0x0109], }, 0x0408 : {0x03ba : [0x0109], },
# Xperia # Xperia
0x13d3 : { 0x3304 : [0x0001, 0x0002] }, 0x13d3 : {0x3304 : [0x0001, 0x0002]},
# CREEL?? Also Nextbook and Wayteq # CREEL?? Also Nextbook and Wayteq
0x5e3 : { 0x726 : [0x222] }, 0x5e3 : {0x726 : [0x222]},
# ZTE # ZTE
0x19d2 : { 0x1353 : [0x226], 0x1351 : [0x227] }, 0x19d2 : {0x1353 : [0x226], 0x1351 : [0x227]},
# Advent # 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 # Kobo
0x2237: { 0x2208 : [0x0226] }, 0x2237: {0x2208 : [0x0226]},
# Lenovo # Lenovo
0x17ef : { 0x17ef : {
@ -193,10 +193,10 @@ class ANDROID(USBMS):
}, },
# Pantech # Pantech
0x10a9 : { 0x6050 : [0x227] }, 0x10a9 : {0x6050 : [0x227]},
# Prestigio and Teclast # Prestigio and Teclast
0x2207 : { 0 : [0x222], 0x10 : [0x222] }, 0x2207 : {0 : [0x222], 0x10 : [0x222]},
} }
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books', EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books',
@ -219,7 +219,7 @@ class ANDROID(USBMS):
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD', 'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0', 'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0',
'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703', 'NEXT8D12', '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', WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'A953', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', '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', 'S5830I_CARD', 'MID7042', 'LINK-CREATE', '7035', 'VIEWPAD_7E',
'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS', 'NOVO7', 'MB526', '_USB#WYK7MSF8KE', 'TABLET_PC', 'F', 'MT65XX_MS',
'ICS', 'E400', '__FILE-STOR_GADG', 'ST80208-1', 'GT-S5660M_CARD', 'XT894', '_USB', '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', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', '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', 'UMS_COMPOSITE', 'PRO', '.KOBO_VOX', 'SGH-T989_CARD', 'SGH-I727',
'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E', 'USB_FLASH_DRIVER', 'ANDROID', 'MID7042', '7035', 'VIEWPAD_7E',
'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1', 'XT894', 'NOVO7', 'ADVANCED', 'TABLET_PC', 'F', 'E400_SD_CARD', 'ST80208-1', 'XT894',
'_USB', '_USB', 'PROD_TAB13-201',
] ]
OSX_MAIN_MEM = 'Android Device Main Memory' OSX_MAIN_MEM = 'Android Device Main Memory'
@ -369,7 +370,6 @@ class WEBOS(USBMS):
except ImportError: except ImportError:
import Image, ImageDraw import Image, ImageDraw
coverdata = getattr(metadata, 'thumbnail', None) coverdata = getattr(metadata, 'thumbnail', None)
if coverdata and coverdata[2]: if coverdata and coverdata[2]:
cover = Image.open(cStringIO.StringIO(coverdata[2])) cover = Image.open(cStringIO.StringIO(coverdata[2]))
@ -418,3 +418,4 @@ class WEBOS(USBMS):
coverfile.write(coverdata) coverfile.write(coverdata)

View File

@ -560,7 +560,9 @@ class OPF(object): # {{{
self.package_version = 0 self.package_version = 0
self.metadata = self.metadata_path(self.root) self.metadata = self.metadata_path(self.root)
if not self.metadata: 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] self.metadata = self.metadata[0]
if unquote_urls: if unquote_urls:
self.unquote_urls() self.unquote_urls()

View File

@ -7,10 +7,10 @@ __docformat__ = 'restructuredtext en'
from functools import partial from functools import partial
from PyQt4.Qt import QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit, \ from PyQt4.Qt import (QComboBox, QLabel, QSpinBox, QDoubleSpinBox, QDateTimeEdit,
QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout, \ QDateTime, QGroupBox, QVBoxLayout, QSizePolicy, QGridLayout,
QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL, \ QSpacerItem, QIcon, QCheckBox, QWidget, QHBoxLayout, SIGNAL,
QPushButton, QMessageBox, QToolButton QPushButton, QMessageBox, QToolButton, Qt)
from calibre.utils.date import qt_to_dt, now from calibre.utils.date import qt_to_dt, now
from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.complete2 import EditWithComplete
@ -39,7 +39,6 @@ class Base(object):
def gui_val(self): def gui_val(self):
return self.getter() return self.getter()
def commit(self, book_id, notify=False): def commit(self, book_id, notify=False):
val = self.gui_val val = self.gui_val
val = self.normalize_ui_val(val) val = self.normalize_ui_val(val)
@ -159,6 +158,17 @@ class DateTimeEdit(QDateTimeEdit):
def set_to_clear(self): def set_to_clear(self):
self.setDateTime(UNDEFINED_QDATETIME) 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): class DateTime(Base):
def setup_ui(self, parent): def setup_ui(self, parent):
@ -211,7 +221,7 @@ class Comments(Base):
self._layout = QVBoxLayout() self._layout = QVBoxLayout()
self._tb = CommentsEditor(self._box) self._tb = CommentsEditor(self._box)
self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) self._tb.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
#self._tb.setTabChangesFocus(True) # self._tb.setTabChangesFocus(True)
self._layout.addWidget(self._tb) self._layout.addWidget(self._tb)
self._box.setLayout(self._layout) self._box.setLayout(self._layout)
self.widgets = [self._box] self.widgets = [self._box]
@ -534,7 +544,7 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
column = row = base_row = max_row = 0 column = row = base_row = max_row = 0
for key in cols: for key in cols:
if not fm[key]['is_editable']: if not fm[key]['is_editable']:
continue # this almost never happens continue # this almost never happens
dt = fm[key]['datatype'] dt = fm[key]['datatype']
if dt == 'composite' or (bulk and dt == 'comments'): if dt == 'composite' or (bulk and dt == 'comments'):
continue continue
@ -595,7 +605,6 @@ class BulkBase(Base):
self._cached_gui_val_ = self.getter() self._cached_gui_val_ = self.getter()
return self._cached_gui_val_ return self._cached_gui_val_
def get_initial_value(self, book_ids): def get_initial_value(self, book_ids):
values = set([]) values = set([])
for book_id in book_ids: for book_id in book_ids:
@ -633,7 +642,7 @@ class BulkBase(Base):
self.main_widget = main_widget_class(w) self.main_widget = main_widget_class(w)
l.addWidget(self.main_widget) l.addWidget(self.main_widget)
l.setStretchFactor(self.main_widget, 10) 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) l.addWidget(self.a_c_checkbox)
self.ignore_change_signals = True self.ignore_change_signals = True
@ -1054,3 +1063,5 @@ bulk_widgets = {
'series': BulkSeries, 'series': BulkSeries,
'enumeration': BulkEnumeration, 'enumeration': BulkEnumeration,
} }

View File

@ -27,7 +27,7 @@ def partial(*args, **kwargs):
_keep_refs.append(ans) _keep_refs.append(ans)
return ans return ans
class LibraryViewMixin(object): # {{{ class LibraryViewMixin(object): # {{{
def __init__(self, db): def __init__(self, db):
self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection) self.library_view.files_dropped.connect(self.iactions['Add Books'].files_dropped, type=Qt.QueuedConnection)
@ -100,7 +100,7 @@ class LibraryViewMixin(object): # {{{
# }}} # }}}
class LibraryWidget(Splitter): # {{{ class LibraryWidget(Splitter): # {{{
def __init__(self, parent): def __init__(self, parent):
orientation = Qt.Vertical orientation = Qt.Vertical
@ -119,7 +119,7 @@ class LibraryWidget(Splitter): # {{{
self.addWidget(parent.library_view) self.addWidget(parent.library_view)
# }}} # }}}
class Stack(QStackedWidget): # {{{ class Stack(QStackedWidget): # {{{
def __init__(self, parent): def __init__(self, parent):
QStackedWidget.__init__(self, parent) QStackedWidget.__init__(self, parent)
@ -147,7 +147,7 @@ class Stack(QStackedWidget): # {{{
# }}} # }}}
class UpdateLabel(QLabel): # {{{ class UpdateLabel(QLabel): # {{{
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
QLabel.__init__(self, *args, **kwargs) QLabel.__init__(self, *args, **kwargs)
@ -157,22 +157,22 @@ class UpdateLabel(QLabel): # {{{
pass pass
# }}} # }}}
class StatusBar(QStatusBar): # {{{ class StatusBar(QStatusBar): # {{{
def __init__(self, parent=None): def __init__(self, parent=None):
QStatusBar.__init__(self, parent) QStatusBar.__init__(self, parent)
self.default_message = __appname__ + ' ' + _('version') + ' ' + \
self.get_version() + ' ' + _('created by Kovid Goyal')
self.device_string = '' self.device_string = ''
self.update_label = UpdateLabel('') self.update_label = UpdateLabel('')
self.total = self.current = self.selected = 0
self.addPermanentWidget(self.update_label) self.addPermanentWidget(self.update_label)
self.update_label.setVisible(False) self.update_label.setVisible(False)
self._font = QFont() self._font = QFont()
self._font.setBold(True) self._font.setBold(True)
self.setFont(self._font) self.setFont(self._font)
self.defmsg = QLabel(self.default_message) self.defmsg = QLabel('')
self.defmsg.setFont(self._font) self.defmsg.setFont(self._font)
self.addWidget(self.defmsg) self.addWidget(self.defmsg)
self.set_label()
def initialize(self, systray=None): def initialize(self, systray=None):
self.systray = systray self.systray = systray
@ -180,17 +180,39 @@ class StatusBar(QStatusBar): # {{{
def device_connected(self, devname): def device_connected(self, devname):
self.device_string = _('Connected ') + devname self.device_string = _('Connected ') + devname
self.defmsg.setText(self.default_message + ' ..::.. ' + self.set_label()
self.device_string)
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() self.clearMessage()
def device_disconnected(self): def device_disconnected(self):
self.device_string = '' self.device_string = ''
self.defmsg.setText(self.default_message) self.set_label()
self.clearMessage()
def get_version(self):
return get_version()
def show_message(self, msg, timeout=0): def show_message(self, msg, timeout=0):
self.showMessage(msg, timeout) self.showMessage(msg, timeout)
@ -207,11 +229,11 @@ class StatusBar(QStatusBar): # {{{
# }}} # }}}
class LayoutMixin(object): # {{{ class LayoutMixin(object): # {{{
def __init__(self): def __init__(self):
if config['gui_layout'] == 'narrow': # narrow {{{ if config['gui_layout'] == 'narrow': # narrow {{{
self.book_details = BookDetails(False, self) self.book_details = BookDetails(False, self)
self.stack = Stack(self) self.stack = Stack(self)
self.bd_splitter = Splitter('book_details_splitter', self.bd_splitter = Splitter('book_details_splitter',
@ -224,7 +246,7 @@ class LayoutMixin(object): # {{{
self.centralwidget.layout().addWidget(self.bd_splitter) self.centralwidget.layout().addWidget(self.bd_splitter)
button_order = ('tb', 'bd', 'cb') button_order = ('tb', 'bd', 'cb')
# }}} # }}}
else: # wide {{{ else: # wide {{{
self.bd_splitter = Splitter('book_details_splitter', self.bd_splitter = Splitter('book_details_splitter',
_('Book Details'), I('book.png'), initial_side_size=200, _('Book Details'), I('book.png'), initial_side_size=200,
orientation=Qt.Horizontal, parent=self, side_index=1, orientation=Qt.Horizontal, parent=self, side_index=1,
@ -312,9 +334,15 @@ class LayoutMixin(object): # {{{
def read_layout_settings(self): def read_layout_settings(self):
# View states are restored automatically when set_database is called # View states are restored automatically when set_database is called
for x in ('cb', 'tb', 'bd'): for x in ('cb', 'tb', 'bd'):
getattr(self, x+'_splitter').restore_state() 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' __docformat__ = 'restructuredtext en'
import functools, re, os, traceback, errno, time 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, from PyQt4.Qt import (QAbstractTableModel, Qt, pyqtSignal, QIcon, QImage,
QModelIndex, QVariant, QDateTime, QColor, QPixmap) 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.utils.localization import calibre_langcode_to_name
from calibre.library.coloring import color_row_key from calibre.library.coloring import color_row_key
Counts = namedtuple('Counts', 'total current')
def human_readable(size, precision=1): def human_readable(size, precision=1):
""" Convert a size in bytes into megabytes """ """ Convert a size in bytes into megabytes """
return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),) return ('%.'+str(precision)+'f') % ((size/(1024.*1024.)),)
@ -46,7 +48,7 @@ def default_image():
_default_image = QImage(I('default_cover.png')) _default_image = QImage(I('default_cover.png'))
return _default_image return _default_image
class ColumnColor(object): class ColumnColor(object): # {{{
def __init__(self, formatter, colors): def __init__(self, formatter, colors):
self.mi = None self.mi = None
@ -70,9 +72,9 @@ class ColumnColor(object):
return color return color
except: except:
pass pass
# }}}
class ColumnIcon(object): # {{{
class ColumnIcon(object):
def __init__(self, formatter): def __init__(self, formatter):
self.mi = None self.mi = None
@ -108,8 +110,9 @@ class ColumnIcon(object):
return icon_bitmap return icon_bitmap
except: except:
pass pass
# }}}
class BooksModel(QAbstractTableModel): # {{{ class BooksModel(QAbstractTableModel): # {{{
about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted') about_to_be_sorted = pyqtSignal(object, name='aboutToBeSorted')
sorting_done = pyqtSignal(object, name='sortingDone') sorting_done = pyqtSignal(object, name='sortingDone')
@ -150,7 +153,7 @@ class BooksModel(QAbstractTableModel): # {{{
self.default_image = default_image() self.default_image = default_image()
self.sorted_on = DEFAULT_SORT self.sorted_on = DEFAULT_SORT
self.sort_history = [self.sorted_on] 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.column_map = []
self.headers = {} self.headers = {}
self.alignment_map = {} self.alignment_map = {}
@ -240,7 +243,6 @@ class BooksModel(QAbstractTableModel): # {{{
# Would like to to a join here, but the thread might be waiting to # Would like to to a join here, but the thread might be waiting to
# do something on the GUI thread. Deadlock. # do something on the GUI thread. Deadlock.
def refresh_ids(self, ids, current_row=-1): def refresh_ids(self, ids, current_row=-1):
self._clear_caches() self._clear_caches()
rows = self.db.refresh_ids(ids) rows = self.db.refresh_ids(ids)
@ -282,9 +284,16 @@ class BooksModel(QAbstractTableModel): # {{{
self._clear_caches() self._clear_caches()
self.count_changed_signal.emit(self.db.count()) 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): def row_indices(self, index):
''' Return list indices of all cells in index.row()''' ''' 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 @property
def by_author(self): def by_author(self):
@ -332,7 +341,7 @@ class BooksModel(QAbstractTableModel): # {{{
while True: while True:
row_ += 1 if forward else -1 row_ += 1 if forward else -1
if row_ < 0: if row_ < 0:
row_ = self.count() - 1; row_ = self.count() - 1
elif row_ >= self.count(): elif row_ >= self.count():
row_ = 0 row_ = 0
if self.id(row_) in self.ids_to_highlight_set: if self.id(row_) in self.ids_to_highlight_set:
@ -611,7 +620,7 @@ class BooksModel(QAbstractTableModel): # {{{
data = None data = None
try: try:
data = self.db.cover(row_number) 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 pass
if not data: if not data:
@ -673,7 +682,7 @@ class BooksModel(QAbstractTableModel): # {{{
return QVariant(UNDEFINED_QDATETIME) return QVariant(UNDEFINED_QDATETIME)
def bool_type(r, idx=-1): 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): def bool_type_decorator(r, idx=-1, bool_cols_are_tristate=True):
val = force_to_bool(self.db.data[r][idx]) val = force_to_bool(self.db.data[r][idx])
@ -884,18 +893,18 @@ class BooksModel(QAbstractTableModel): # {{{
ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname, ans = Qt.AlignVCenter | ALIGNMENT_MAP[self.alignment_map.get(cname,
'left')] 'left')]
return QVariant(ans) 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: # if self.column_map[index.column()] in self.editable_cols:
# return QVariant(_("Double click to <b>edit</b> me<br><br>")) # return QVariant(_("Double click to <b>edit</b> me<br><br>"))
return NONE return NONE
def headerData(self, section, orientation, role): def headerData(self, section, orientation, role):
if orientation == Qt.Horizontal: 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 return None
if role == Qt.ToolTipRole: if role == Qt.ToolTipRole:
ht = self.column_map[section] 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' ht = 'date'
if self.db.field_metadata[self.column_map[section]]['is_category']: if self.db.field_metadata[self.column_map[section]]['is_category']:
is_cat = _('. This column can be Quickview\'ed') is_cat = _('. This column can be Quickview\'ed')
@ -909,11 +918,10 @@ class BooksModel(QAbstractTableModel): # {{{
col = self.db.field_metadata['uuid']['rec_index'] col = self.db.field_metadata['uuid']['rec_index']
return QVariant(_('This book\'s UUID is "{0}"').format(self.db.data[section][col])) 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 QVariant(section+1)
return NONE return NONE
def flags(self, index): def flags(self, index):
flags = QAbstractTableModel.flags(self, index) flags = QAbstractTableModel.flags(self, index)
if index.isValid(): if index.isValid():
@ -973,7 +981,7 @@ class BooksModel(QAbstractTableModel): # {{{
tmpl = unicode(value.toString()).strip() tmpl = unicode(value.toString()).strip()
disp = cc['display'] disp = cc['display']
disp['composite_template'] = tmpl 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) self.refresh(reset=True)
return True return True
@ -991,7 +999,7 @@ class BooksModel(QAbstractTableModel): # {{{
return self._set_data(index, value) return self._set_data(index, value)
except (IOError, OSError) as err: except (IOError, OSError) as err:
import traceback 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) fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else '' p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(get_gui(), _('Permission denied'), error_dialog(get_gui(), _('Permission denied'),
@ -1069,7 +1077,7 @@ class BooksModel(QAbstractTableModel): # {{{
# }}} # }}}
class OnDeviceSearch(SearchQueryParser): # {{{ class OnDeviceSearch(SearchQueryParser): # {{{
USABLE_LOCATIONS = [ USABLE_LOCATIONS = [
'all', 'all',
@ -1082,7 +1090,6 @@ class OnDeviceSearch(SearchQueryParser): # {{{
'inlibrary' 'inlibrary'
] ]
def __init__(self, model): def __init__(self, model):
SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS) SearchQueryParser.__init__(self, locations=self.USABLE_LOCATIONS)
self.model = model self.model = model
@ -1105,7 +1112,7 @@ class OnDeviceSearch(SearchQueryParser): # {{{
elif query.startswith('~'): elif query.startswith('~'):
matchkind = REGEXP_MATCH matchkind = REGEXP_MATCH
query = query[1:] 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() query = query.lower()
if location not in self.USABLE_LOCATIONS: if location not in self.USABLE_LOCATIONS:
@ -1137,9 +1144,9 @@ class OnDeviceSearch(SearchQueryParser): # {{{
if locvalue == 'inlibrary': if locvalue == 'inlibrary':
continue # this is bool, so can't match below continue # this is bool, so can't match below
try: try:
### Can't separate authors because comma is used for name sep and author sep # 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 # Exact match might not get what you want. For that reason, turn author
### exactmatch searches into contains searches. # exactmatch searches into contains searches.
if locvalue == 'author' and matchkind == EQUALS_MATCH: if locvalue == 'author' and matchkind == EQUALS_MATCH:
m = CONTAINS_MATCH m = CONTAINS_MATCH
else: else:
@ -1152,13 +1159,13 @@ class OnDeviceSearch(SearchQueryParser): # {{{
if _match(query, vals, m, use_primary_find_in_search=upf): if _match(query, vals, m, use_primary_find_in_search=upf):
matches.add(index) matches.add(index)
break break
except ValueError: # Unicode errors except ValueError: # Unicode errors
traceback.print_exc() traceback.print_exc()
return matches return matches
# }}} # }}}
class DeviceDBSortKeyGen(object): # {{{ class DeviceDBSortKeyGen(object): # {{{
def __init__(self, attr, keyfunc, db): def __init__(self, attr, keyfunc, db):
self.attr = attr self.attr = attr
@ -1173,7 +1180,7 @@ class DeviceDBSortKeyGen(object): # {{{
return ans return ans
# }}} # }}}
class DeviceBooksModel(BooksModel): # {{{ class DeviceBooksModel(BooksModel): # {{{
booklist_dirtied = pyqtSignal() booklist_dirtied = pyqtSignal()
upload_collections = pyqtSignal(object) upload_collections = pyqtSignal(object)
@ -1202,6 +1209,12 @@ class DeviceBooksModel(BooksModel): # {{{
self.editable = ['title', 'authors', 'collections'] self.editable = ['title', 'authors', 'collections']
self.book_in_library = None 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): def mark_for_deletion(self, job, rows, rows_are_ids=False):
db_indices = rows if rows_are_ids else self.indices(rows) 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)] 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: if not succeeded:
indices = self.row_indices(self.index(row, 0)) indices = self.row_indices(self.index(row, 0))
self.dataChanged.emit(indices[0], indices[-1]) self.dataChanged.emit(indices[0], indices[-1])
self.count_changed()
def paths_deleted(self, paths): def paths_deleted(self, paths):
self.map = list(range(0, len(self.db))) self.map = list(range(0, len(self.db)))
self.resort(False) self.resort(False)
self.research(True) self.research(True)
self.count_changed()
def is_row_marked_for_deletion(self, row): def is_row_marked_for_deletion(self, row):
try: try:
@ -1276,9 +1291,9 @@ class DeviceBooksModel(BooksModel): # {{{
if index.isValid(): if index.isValid():
cname = self.column_map[index.column()] cname = self.column_map[index.column()]
if cname in self.editable and \ if cname in self.editable and \
(cname != 'collections' or \ (cname != 'collections' or
(callable(getattr(self.db, 'supports_collections', None)) and \ (callable(getattr(self.db, 'supports_collections', None)) and
self.db.supports_collections() and \ self.db.supports_collections() and
device_prefs['manage_device_metadata']=='manual')): device_prefs['manage_device_metadata']=='manual')):
flags |= Qt.ItemIsEditable flags |= Qt.ItemIsEditable
return flags return flags
@ -1308,6 +1323,7 @@ class DeviceBooksModel(BooksModel): # {{{
self.last_search = text self.last_search = text
if self.last_search: if self.last_search:
self.searched.emit(True) self.searched.emit(True)
self.count_changed()
def research(self, reset=True): def research(self, reset=True):
self.search(self.last_search, reset) self.search(self.last_search, reset)
@ -1377,6 +1393,7 @@ class DeviceBooksModel(BooksModel): # {{{
self.map = list(range(0, len(db))) self.map = list(range(0, len(db)))
self.research(reset=False) self.research(reset=False)
self.resort() self.resort()
self.count_changed()
def cover(self, row): def cover(self, row):
item = self.db[self.map[row]] item = self.db[self.map[row]]
@ -1436,7 +1453,7 @@ class DeviceBooksModel(BooksModel): # {{{
return data return data
def paths(self, rows): 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): def paths_for_db_ids(self, db_ids, as_map=False):
res = defaultdict(list) if as_map else [] res = defaultdict(list) if as_map else []
@ -1521,7 +1538,7 @@ class DeviceBooksModel(BooksModel): # {{{
elif role == Qt.ToolTipRole and index.isValid(): elif role == Qt.ToolTipRole and index.isValid():
if self.is_row_marked_for_deletion(row): if self.is_row_marked_for_deletion(row):
return QVariant(_('Marked for deletion')) 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()): self.db.supports_collections()):
return QVariant(_("Double click to <b>edit</b> me<br><br>")) return QVariant(_("Double click to <b>edit</b> me<br><br>"))
elif role == Qt.DecorationRole and cname == 'inlibrary': elif role == Qt.DecorationRole and cname == 'inlibrary':
@ -1590,3 +1607,4 @@ class DeviceBooksModel(BooksModel): # {{{
# }}} # }}}

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function) 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' __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>' __copyright__ = '2011, John Schember <john@nachtimwald.com>'

View File

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

View File

@ -325,6 +325,11 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
if self.library_view.model().rowCount(None) < 3: if self.library_view.model().rowCount(None) < 3:
self.library_view.resizeColumnsToContents() 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.library_view.model().count_changed()
self.bars_manager.database_changed(self.library_view.model().db) self.bars_manager.database_changed(self.library_view.model().db)
self.library_view.model().database_changed.connect(self.bars_manager.database_changed, 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 # Reset the view in case something changed while it was invisible
self.current_view().reset() self.current_view().reset()
self.set_number_of_books_shown() self.set_number_of_books_shown()
self.update_status_bar()
def job_exception(self, job, dialog_title=_('Conversion Error')): def job_exception(self, job, dialog_title=_('Conversion Error')):
if not hasattr(self, '_modeless_dialogs'): if not hasattr(self, '_modeless_dialogs'):

View File

@ -24,7 +24,7 @@ from calibre.gui2.dnd import (dnd_has_image, dnd_get_image, dnd_get_files,
history = XMLConfig('history') history = XMLConfig('history')
class ProgressIndicator(QWidget): # {{{ class ProgressIndicator(QWidget): # {{{
def __init__(self, *args): def __init__(self, *args):
QWidget.__init__(self, *args) QWidget.__init__(self, *args)
@ -57,7 +57,7 @@ class ProgressIndicator(QWidget): # {{{
self.setVisible(False) self.setVisible(False)
# }}} # }}}
class FilenamePattern(QWidget, Ui_Form): # {{{ class FilenamePattern(QWidget, Ui_Form): # {{{
changed_signal = pyqtSignal() changed_signal = pyqtSignal()
@ -82,7 +82,8 @@ class FilenamePattern(QWidget, Ui_Form): # {{{
val = prefs['filename_pattern'] val = prefs['filename_pattern']
self.re.lineEdit().setText(val) 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: if val in val_hist:
del val_hist[val_hist.index(val)] del val_hist[val_hist.index(val)]
val_hist.insert(0, 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)) self.isbn.setText(_('No match') if mi.isbn is None else str(mi.isbn))
def pattern(self): def pattern(self):
pat = unicode(self.re.lineEdit().text()) pat = unicode(self.re.lineEdit().text())
return re.compile(pat) return re.compile(pat)
@ -157,7 +157,7 @@ class FilenamePattern(QWidget, Ui_Form): # {{{
# }}} # }}}
class FormatList(QListWidget): # {{{ class FormatList(QListWidget): # {{{
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
formats_dropped = pyqtSignal(object, object) formats_dropped = pyqtSignal(object, object)
delete_format = pyqtSignal() delete_format = pyqtSignal()
@ -186,7 +186,6 @@ class FormatList(QListWidget): # {{{
if d.err is None: if d.err is None:
self.formats_dropped.emit(event, [d.fpath]) self.formats_dropped.emit(event, [d.fpath])
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
event.acceptProposedAction() event.acceptProposedAction()
@ -198,7 +197,7 @@ class FormatList(QListWidget): # {{{
# }}} # }}}
class ImageDropMixin(object): # {{{ class ImageDropMixin(object): # {{{
''' '''
Adds support for dropping images onto widgets and a context menu for Adds support for dropping images onto widgets and a context menu for
copy/pasting images. copy/pasting images.
@ -272,7 +271,7 @@ class ImageDropMixin(object): # {{{
pixmap_to_data(pmap)) pixmap_to_data(pmap))
# }}} # }}}
class ImageView(QWidget, ImageDropMixin): # {{{ class ImageView(QWidget, ImageDropMixin): # {{{
BORDER_WIDTH = 1 BORDER_WIDTH = 1
cover_changed = pyqtSignal(object) cover_changed = pyqtSignal(object)
@ -338,7 +337,7 @@ class ImageView(QWidget, ImageDropMixin): # {{{
p.end() p.end()
# }}} # }}}
class CoverView(QGraphicsView, ImageDropMixin): # {{{ class CoverView(QGraphicsView, ImageDropMixin): # {{{
cover_changed = pyqtSignal(object) cover_changed = pyqtSignal(object)
@ -393,7 +392,7 @@ class BasicList(QListWidget):
yield self.item(i) yield self.item(i)
# }}} # }}}
class LineEditECM(object): # {{{ class LineEditECM(object): # {{{
''' '''
Extend the context menu of a QLineEdit to include more actions. Extend the context menu of a QLineEdit to include more actions.
@ -438,7 +437,7 @@ class LineEditECM(object): # {{{
# }}} # }}}
class EnLineEdit(LineEditECM, QLineEdit): # {{{ class EnLineEdit(LineEditECM, QLineEdit): # {{{
''' '''
Enhanced QLineEdit. Enhanced QLineEdit.
@ -449,7 +448,7 @@ class EnLineEdit(LineEditECM, QLineEdit): # {{{
pass pass
# }}} # }}}
class ItemsCompleter(QCompleter): # {{{ class ItemsCompleter(QCompleter): # {{{
''' '''
A completer object that completes a list of tags. It is used in conjunction A completer object that completes a list of tags. It is used in conjunction
@ -541,7 +540,7 @@ class CompleteLineEdit(EnLineEdit): # {{{
# }}} # }}}
class EnComboBox(QComboBox): # {{{ class EnComboBox(QComboBox): # {{{
''' '''
Enhanced QComboBox. Enhanced QComboBox.
@ -567,7 +566,7 @@ class EnComboBox(QComboBox): # {{{
# }}} # }}}
class CompleteComboBox(EnComboBox): # {{{ class CompleteComboBox(EnComboBox): # {{{
def __init__(self, *args): def __init__(self, *args):
EnComboBox.__init__(self, *args) EnComboBox.__init__(self, *args)
@ -584,7 +583,7 @@ class CompleteComboBox(EnComboBox): # {{{
# }}} # }}}
class HistoryLineEdit(QComboBox): # {{{ class HistoryLineEdit(QComboBox): # {{{
lost_focus = pyqtSignal() lost_focus = pyqtSignal()
@ -637,7 +636,7 @@ class HistoryLineEdit(QComboBox): # {{{
# }}} # }}}
class ComboBoxWithHelp(QComboBox): # {{{ class ComboBoxWithHelp(QComboBox): # {{{
''' '''
A combobox where item 0 is help text. CurrentText will return '' for item 0. 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 Be sure to always fetch the text with currentText. Don't use the signals
@ -686,7 +685,7 @@ class ComboBoxWithHelp(QComboBox): # {{{
# }}} # }}}
class EncodingComboBox(QComboBox): # {{{ class EncodingComboBox(QComboBox): # {{{
''' '''
A combobox that holds text encodings support A combobox that holds text encodings support
by Python. This is only populated with the most by Python. This is only populated with the most
@ -711,7 +710,7 @@ class EncodingComboBox(QComboBox): # {{{
# }}} # }}}
class PythonHighlighter(QSyntaxHighlighter): # {{{ class PythonHighlighter(QSyntaxHighlighter): # {{{
Rules = [] Rules = []
Formats = {} Formats = {}
@ -736,13 +735,11 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
CONSTANTS = ["False", "True", "None", "NotImplemented", "Ellipsis"] CONSTANTS = ["False", "True", "None", "NotImplemented", "Ellipsis"]
def __init__(self, parent=None): def __init__(self, parent=None):
super(PythonHighlighter, self).__init__(parent) super(PythonHighlighter, self).__init__(parent)
if not self.Config: if not self.Config:
self.loadConfig() self.loadConfig()
self.initializeFormats() self.initializeFormats()
PythonHighlighter.Rules.append((QRegExp( PythonHighlighter.Rules.append((QRegExp(
@ -752,7 +749,7 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
"|".join([r"\b%s\b" % builtin for builtin in self.BUILTINS])), "|".join([r"\b%s\b" % builtin for builtin in self.BUILTINS])),
"builtin")) "builtin"))
PythonHighlighter.Rules.append((QRegExp( PythonHighlighter.Rules.append((QRegExp(
"|".join([r"\b%s\b" % constant \ "|".join([r"\b%s\b" % constant
for constant in self.CONSTANTS])), "constant")) for constant in self.CONSTANTS])), "constant"))
PythonHighlighter.Rules.append((QRegExp( PythonHighlighter.Rules.append((QRegExp(
r"\b[+-]?[0-9]+[lL]?\b" r"\b[+-]?[0-9]+[lL]?\b"
@ -812,7 +809,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
Config["%sfontbold" % name] = QVariant(bold).toBool() Config["%sfontbold" % name] = QVariant(bold).toBool()
Config["%sfontitalic" % name] = QVariant(italic).toBool() Config["%sfontitalic" % name] = QVariant(italic).toBool()
@classmethod @classmethod
def initializeFormats(cls): def initializeFormats(cls):
Config = cls.Config Config = cls.Config
@ -829,7 +825,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
format.setFontItalic(Config["%sfontitalic" % name]) format.setFontItalic(Config["%sfontitalic" % name])
PythonHighlighter.Formats[name] = format PythonHighlighter.Formats[name] = format
def highlightBlock(self, text): def highlightBlock(self, text):
NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4) NORMAL, TRIPLESINGLE, TRIPLEDOUBLE, ERROR = range(4)
@ -861,7 +856,7 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
# Slow but good quality highlighting for comments. For more # Slow but good quality highlighting for comments. For more
# speed, comment this out and add the following to __init__: # 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(): if text.isEmpty():
pass pass
elif text[0] == "#": elif text[0] == "#":
@ -900,7 +895,6 @@ class PythonHighlighter(QSyntaxHighlighter): # {{{
self.setFormat(i, text.length(), self.setFormat(i, text.length(),
PythonHighlighter.Formats["string"]) PythonHighlighter.Formats["string"])
def rehighlight(self): def rehighlight(self):
QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
QSyntaxHighlighter.rehighlight(self) QSyntaxHighlighter.rehighlight(self)
@ -955,8 +949,8 @@ class LayoutButton(QToolButton):
def set_state_to_hide(self, *args): def set_state_to_hide(self, *args):
self.setChecked(True) self.setChecked(True)
self.setText(_('Hide %(label)s %(shortcut)s'%dict( self.setText(_('Hide %(label)s %(shortcut)s')%dict(
label=self.label, shortcut=self.shortcut))) label=self.label, shortcut=self.shortcut))
self.setToolTip(self.text()) self.setToolTip(self.text())
self.setStatusTip(self.text()) self.setStatusTip(self.text())
@ -1045,11 +1039,13 @@ class Splitter(QSplitter):
@dynamic_property @dynamic_property
def side_index_size(self): def side_index_size(self):
def fget(self): def fget(self):
if self.count() < 2: return 0 if self.count() < 2:
return 0
return self.sizes()[self.side_index] return self.sizes()[self.side_index]
def fset(self, val): def fset(self, val):
if self.count() < 2: return if self.count() < 2:
return
if val == 0 and not self.is_side_index_hidden: if val == 0 and not self.is_side_index_hidden:
self.save_state() self.save_state()
sizes = list(self.sizes()) sizes = list(self.sizes())
@ -1081,7 +1077,8 @@ class Splitter(QSplitter):
self.resize_timer.start() self.resize_timer.start()
def get_state(self): 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) return (self.desired_show, self.desired_side_size)
def apply_state(self, state, save_desired=True): def apply_state(self, state, save_desired=True):
@ -1142,3 +1139,4 @@ class Splitter(QSplitter):
# }}} # }}}