Merge from custcol trunk

This commit is contained in:
Charles Haley 2010-05-01 22:59:01 +01:00
commit eda505f755
16 changed files with 570 additions and 586 deletions

View File

@ -4,6 +4,7 @@ __copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
from itertools import izip from itertools import izip
from xml.sax.saxutils import escape
from calibre.customize import Plugin as _Plugin from calibre.customize import Plugin as _Plugin
@ -238,7 +239,7 @@ class OutputProfile(Plugin):
@classmethod @classmethod
def tags_to_string(cls, tags): def tags_to_string(cls, tags):
return ', '.join(tags) return escape(', '.join(tags))
class iPadOutput(OutputProfile): class iPadOutput(OutputProfile):
@ -383,7 +384,8 @@ class KindleOutput(OutputProfile):
@classmethod @classmethod
def tags_to_string(cls, tags): def tags_to_string(cls, tags):
return 'ttt '.join(tags)+'ttt ' return u'%s <br/><span style="color: white">%s</span>' % (', '.join(tags),
'ttt '.join(tags)+'ttt ')
class KindleDXOutput(OutputProfile): class KindleDXOutput(OutputProfile):
@ -399,7 +401,8 @@ class KindleDXOutput(OutputProfile):
@classmethod @classmethod
def tags_to_string(cls, tags): def tags_to_string(cls, tags):
return 'ttt '.join(tags)+'ttt ' return u'%s <br/><span style="color: white">%s</span>' % (', '.join(tags),
'ttt '.join(tags)+'ttt ')
class IlliadOutput(OutputProfile): class IlliadOutput(OutputProfile):

View File

@ -10,7 +10,7 @@ import threading, Queue
class DeviceManager(object): class DeviceManager(object):
def __init__(self): def __init__(self):
self.devices = [] self.devices = []
self.device_jobs = Queue(0) self.device_jobs = Queue(0)
@ -21,19 +21,19 @@ class Job(object):
def __init__(self, func, args): def __init__(self, func, args):
self.completed = False self.completed = False
self.exception = None self.exception = None
class Worker(threading.Thread): class Worker(threading.Thread):
def __init__(self, jobs): def __init__(self, jobs):
self.jobs = jobs self.jobs = jobs
self.results = [] self.results = []
threading.Thread.__init__(self) threading.Thread.__init__(self)
self.setDaemon(True) self.setDaemon(True)
def run(self): def run(self):
'''Thread loops taking jobs from the queue as they become available''' '''Thread loops taking jobs from the queue as they become available'''
while True: while True:
job = self.jobs.get(True, None) self.jobs.get(True, None)
# Do job # Do job
self.jobs.task_done() self.jobs.task_done()

View File

@ -99,7 +99,7 @@ class Jacket(object):
except: except:
tags = [] tags = []
if tags: if tags:
tags = '<b>Tags: </b>' + escape(self.opts.dest.tags_to_string(tags)) tags = '<b>Tags: </b>' + self.opts.dest.tags_to_string(tags)
else: else:
tags = '' tags = ''
try: try:

View File

@ -354,7 +354,6 @@ if another paragraph_def is found, the state changes to collect_tokens.
def __tab_stop_func(self, line): def __tab_stop_func(self, line):
""" """
""" """
type = 'tabs-%s' % self.__tab_type
self.__att_val_dict['tabs'] += '%s:' % self.__tab_type self.__att_val_dict['tabs'] += '%s:' % self.__tab_type
self.__att_val_dict['tabs'] += '%s;' % line[20:-1] self.__att_val_dict['tabs'] += '%s;' % line[20:-1]
self.__tab_type = 'left' self.__tab_type = 'left'
@ -373,7 +372,6 @@ if another paragraph_def is found, the state changes to collect_tokens.
""" """
leader = self.__tab_type_dict.get(self.__token_info) leader = self.__tab_type_dict.get(self.__token_info)
if leader != None: if leader != None:
type = 'tabs-%s' % self.__tab_type
self.__att_val_dict['tabs'] += '%s^' % leader self.__att_val_dict['tabs'] += '%s^' % leader
else: else:
if self.__run_level > 3: if self.__run_level > 3:

View File

@ -318,7 +318,6 @@ class Styles:
Try to add the number to dictionary entry tabs-left, or tabs-right, etc. Try to add the number to dictionary entry tabs-left, or tabs-right, etc.
If the dictionary entry doesn't exist, create one. If the dictionary entry doesn't exist, create one.
""" """
type = 'tabs-%s' % self.__tab_type
try: try:
if self.__leader_found: if self.__leader_found:
self.__styles_dict['par'][self.__styles_num]['tabs']\ self.__styles_dict['par'][self.__styles_num]['tabs']\
@ -362,7 +361,6 @@ class Styles:
leader = self.__tab_type_dict.get(self.__token_info) leader = self.__tab_type_dict.get(self.__token_info)
if leader != None: if leader != None:
leader += '^' leader += '^'
type = 'tabs-%s' % self.__tab_type
try: try:
self.__styles_dict['par'][self.__styles_num]['tabs'] += ':%s;' % leader self.__styles_dict['par'][self.__styles_num]['tabs'] += ':%s;' % leader
except KeyError: except KeyError:

View File

@ -1,17 +1,16 @@
#!/usr/bin/env python #!/usr/bin/env python
__license__ = 'GPL v3' __copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net' __docformat__ = 'restructuredtext en'
__docformat__ = 'restructuredtext en' __license__ = 'GPL v3'
from PyQt4.Qt import QDialog from PyQt4.Qt import QDialog
from calibre.gui2 import ResizableDialog from calibre.gui2.dialogs.comments_dialog_ui import Ui_CommentsDialog
from calibre.gui2.dialogs.comments_dialog_ui import Ui_CommentsDialog
class CommentsDialog(QDialog, Ui_CommentsDialog):
class CommentsDialog(QDialog, Ui_CommentsDialog): def __init__(self, parent, text):
def __init__(self, parent, text): QDialog.__init__(self, parent)
QDialog.__init__(self, parent) Ui_CommentsDialog.__init__(self)
Ui_CommentsDialog.__init__(self) self.setupUi(self)
self.setupUi(self) if text is not None:
if text is not None: self.textbox.setPlainText(text)
self.textbox.setPlainText(text) self.textbox.setTabChangesFocus(True)
self.textbox.setTabChangesFocus(True)

View File

@ -1,83 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>CommentsDialog</class> <class>CommentsDialog</class>
<widget class="QDialog" name="CommentsDialog"> <widget class="QDialog" name="CommentsDialog">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>336</width> <width>336</width>
<height>235</height> <height>235</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Edit Comments</string> <string>Edit Comments</string>
</property> </property>
<widget class="QWidget" name="verticalLayoutWidget"> <widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>10</x>
<y>10</y> <y>10</y>
<width>311</width> <width>311</width>
<height>211</height> <height>211</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QPlainTextEdit" name="textbox"/> <widget class="QPlainTextEdit" name="textbox"/>
</item> </item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
<resources/> <resources/>
<connections> <connections>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>
<signal>accepted()</signal> <signal>accepted()</signal>
<receiver>CommentsDialog</receiver> <receiver>CommentsDialog</receiver>
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>229</x> <x>229</x>
<y>211</y> <y>211</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
<y>234</y> <y>234</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
<connection> <connection>
<sender>buttonBox</sender> <sender>buttonBox</sender>
<signal>rejected()</signal> <signal>rejected()</signal>
<receiver>CommentsDialog</receiver> <receiver>CommentsDialog</receiver>
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>297</x> <x>297</x>
<y>217</y> <y>217</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>
<y>234</y> <y>234</y>
</hint> </hint>
</hints> </hints>
</connection> </connection>
</connections> </connections>
</ui> </ui>

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, re, time, textwrap, sys, copy import os, re, time, textwrap, copy
from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \ from PyQt4.Qt import QDialog, QListWidgetItem, QIcon, \
QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \ QDesktopServices, QVBoxLayout, QLabel, QPlainTextEdit, \
@ -17,7 +17,6 @@ from calibre.gui2 import qstring_to_unicode, choose_dir, error_dialog, config, \
ALL_COLUMNS, NONE, info_dialog, choose_files, \ ALL_COLUMNS, NONE, info_dialog, choose_files, \
warning_dialog, ResizableDialog warning_dialog, ResizableDialog
from calibre.utils.config import prefs from calibre.utils.config import prefs
from calibre.gui2.library import BooksModel
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.ebooks.oeb.iterator import is_supported from calibre.ebooks.oeb.iterator import is_supported
from calibre.library import server_config from calibre.library import server_config
@ -666,10 +665,10 @@ class ConfigDialog(ResizableDialog, Ui_Dialog):
return return
def add_custcol(self): def add_custcol(self):
d = CreateCustomColumn(self, False, self.model.orig_headers, ALL_COLUMNS) CreateCustomColumn(self, False, self.model.orig_headers, ALL_COLUMNS)
def edit_custcol(self): def edit_custcol(self):
d = CreateCustomColumn(self, True, self.model.orig_headers, ALL_COLUMNS) CreateCustomColumn(self, True, self.model.orig_headers, ALL_COLUMNS)
def view_server_logs(self): def view_server_logs(self):
from calibre.library.server import log_access_file, log_error_file from calibre.library.server import log_access_file, log_error_file

View File

@ -1,123 +1,121 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2010, Kovid Goyal <kovid at kovidgoyal.net>'
'''Dialog to create a new custom column''' '''Dialog to create a new custom column'''
from PyQt4.QtCore import SIGNAL, QObject from PyQt4.QtCore import SIGNAL
from PyQt4.Qt import QDialog, Qt, QMessageBox, QListWidgetItem, QVariant from PyQt4.Qt import QDialog, Qt, QListWidgetItem, QVariant
from calibre.gui2.dialogs.config.create_custom_column_ui import Ui_QCreateCustomColumn from calibre.gui2.dialogs.config.create_custom_column_ui import Ui_QCreateCustomColumn
from calibre.gui2 import ALL_COLUMNS, qstring_to_unicode
class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn):
class CreateCustomColumn(QDialog, Ui_QCreateCustomColumn): column_types = {
column_types = { 0:{'datatype':'text', 'text':_('Text, column shown in tags browser'), 'is_multiple':False},
0:{'datatype':'text', 'text':_('Text, column shown in tags browser'), 'is_multiple':False}, 1:{'datatype':'*text', 'text':_('Comma separated text, shown in tags browser'), 'is_multiple':True},
1:{'datatype':'*text', 'text':_('Comma separated text, shown in tags browser'), 'is_multiple':True}, 2:{'datatype':'comments', 'text':_('Text, column not shown in tags browser'), 'is_multiple':False},
2:{'datatype':'comments', 'text':_('Text, column not shown in tags browser'), 'is_multiple':False}, 3:{'datatype':'datetime', 'text':_('Date'), 'is_multiple':False},
3:{'datatype':'datetime', 'text':_('Date'), 'is_multiple':False}, 4:{'datatype':'float', 'text':_('Float'), 'is_multiple':False},
4:{'datatype':'float', 'text':_('Float'), 'is_multiple':False}, 5:{'datatype':'int', 'text':_('Integer'), 'is_multiple':False},
5:{'datatype':'int', 'text':_('Integer'), 'is_multiple':False}, 6:{'datatype':'rating', 'text':_('Rating (stars)'), 'is_multiple':False},
6:{'datatype':'rating', 'text':_('Rating (stars)'), 'is_multiple':False}, 7:{'datatype':'bool', 'text':_('Yes/No'), 'is_multiple':False},
7:{'datatype':'bool', 'text':_('Yes/No'), 'is_multiple':False}, }
} def __init__(self, parent, editing, standard_colheads, standard_colnames):
def __init__(self, parent, editing, standard_colheads, standard_colnames): QDialog.__init__(self, parent)
QDialog.__init__(self, parent) Ui_QCreateCustomColumn.__init__(self)
Ui_QCreateCustomColumn.__init__(self) self.setupUi(self)
self.setupUi(self) self.connect(self.button_box, SIGNAL("accepted()"), self.accept)
self.connect(self.button_box, SIGNAL("accepted()"), self.accept) self.connect(self.button_box, SIGNAL("rejected()"), self.reject)
self.connect(self.button_box, SIGNAL("rejected()"), self.reject) self.parent = parent
self.parent = parent self.editing_col = editing
self.editing_col = editing self.standard_colheads = standard_colheads
self.standard_colheads = standard_colheads self.standard_colnames = standard_colnames
self.standard_colnames = standard_colnames if not self.editing_col:
if not self.editing_col: for t in self.column_types:
for t in self.column_types: self.column_type_box.addItem(self.column_types[t]['text'])
self.column_type_box.addItem(self.column_types[t]['text']) self.exec_()
self.exec_() return
return idx = parent.columns.currentRow()
idx = parent.columns.currentRow() if idx < 0:
if idx < 0: self.parent.messagebox(_('No column has been selected'))
self.parent.messagebox(_('No column has been selected')) return
return col = unicode(parent.columns.item(idx).data(Qt.UserRole).toString())
col = qstring_to_unicode(parent.columns.item(idx).data(Qt.UserRole).toString()) if col not in parent.custcols:
if col not in parent.custcols: self.parent.messagebox(_('Selected column is not a user-defined column'))
self.parent.messagebox(_('Selected column is not a user-defined column')) return
return
c = parent.custcols[col]
c = parent.custcols[col] self.column_name_box.setText(c['label'])
self.column_name_box.setText(c['label']) self.column_heading_box.setText(c['name'])
self.column_heading_box.setText(c['name']) ct = c['datatype'] if not c['is_multiple'] else '*text'
ct = c['datatype'] if not c['is_multiple'] else '*text' self.orig_column_number = c['num']
self.orig_column_number = c['num'] self.orig_column_name = col
self.orig_column_name = col column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), self.column_types))
column_numbers = dict(map(lambda x:(self.column_types[x]['datatype'], x), self.column_types)) self.column_type_box.addItem(self.column_types[column_numbers[ct]]['text'])
self.column_type_box.addItem(self.column_types[column_numbers[ct]]['text']) self.exec_()
self.exec_()
def accept(self):
def accept(self): col = unicode(self.column_name_box.text())
col = qstring_to_unicode(self.column_name_box.text()) col_heading = unicode(self.column_heading_box.text())
col_heading = qstring_to_unicode(self.column_heading_box.text()) col_type = self.column_types[self.column_type_box.currentIndex()]['datatype']
col_type = self.column_types[self.column_type_box.currentIndex()]['datatype'] if col_type == '*text':
if col_type == '*text': col_type='text'
col_type='text' is_multiple = True
is_multiple = True else:
else: is_multiple = False
is_multiple = False if not col:
if not col: self.parent.messagebox(_('No lookup name was provided'))
self.parent.messagebox(_('No lookup name was provided')) return
return if not col_heading:
if not col_heading: self.parent.messagebox(_('No column heading was provided'))
self.parent.messagebox(_('No column heading was provided')) return
return bad_col = False
bad_col = False if col in self.parent.custcols:
if col in self.parent.custcols: if not self.editing_col or self.parent.custcols[col]['num'] != self.orig_column_number:
if not self.editing_col or self.parent.custcols[col]['num'] != self.orig_column_number: bad_col = True
bad_col = True if col in self.standard_colnames:
if col in self.standard_colnames: bad_col = True
bad_col = True if bad_col:
if bad_col: self.parent.messagebox(_('The lookup name %s is already used')%col)
self.parent.messagebox(_('The lookup name %s is already used')%col) return
return bad_head = False
bad_head = False for t in self.parent.custcols:
for t in self.parent.custcols: if self.parent.custcols[t]['name'] == col_heading:
if self.parent.custcols[t]['name'] == col_heading: if not self.editing_col or self.parent.custcols[t]['num'] != self.orig_column_number:
if not self.editing_col or self.parent.custcols[t]['num'] != self.orig_column_number: bad_head = True
bad_head = True for t in self.standard_colheads:
for t in self.standard_colheads: if self.standard_colheads[t] == col_heading:
if self.standard_colheads[t] == col_heading: bad_head = True
bad_head = True if bad_head:
if bad_head: self.parent.messagebox(_('The heading %s is already used')%col_heading)
self.parent.messagebox(_('The heading %s is already used')%col_heading) return
return if ':' in col or ' ' in col or col.lower() != col:
if col.find(':') >= 0 or col.find(' ') >= 0 and \ self.parent.messagebox(_('The lookup name must be lower case and cannot contain ":"s or spaces'))
(not is_alpha(col) or is_lower(col)): return
self.parent.messagebox(_('The lookup name must be lower case and cannot contain ":"s or spaces'))
return if not self.editing_col:
self.parent.custcols[col] = {
if not self.editing_col: 'label':col,
self.parent.custcols[col] = { 'name':col_heading,
'label':col, 'datatype':col_type,
'name':col_heading, 'editable':True,
'datatype':col_type, 'display':None,
'editable':True, 'normalized':None,
'display':None, 'num':None,
'normalized':None, 'is_multiple':is_multiple,
'num':None, }
'is_multiple':is_multiple, item = QListWidgetItem(col_heading, self.parent.columns)
} item.setData(Qt.UserRole, QVariant(col))
item = QListWidgetItem(col_heading, self.parent.columns) item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable)
item.setData(Qt.UserRole, QVariant(col)) item.setCheckState(Qt.Checked)
item.setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable|Qt.ItemIsSelectable) else:
item.setCheckState(Qt.Checked) idx = self.parent.columns.currentRow()
else: item = self.parent.columns.item(idx)
idx = self.parent.columns.currentRow() item.setData(Qt.UserRole, QVariant(col))
item = self.parent.columns.item(idx) item.setText(col_heading)
item.setData(Qt.UserRole, QVariant(col)) self.parent.custcols[self.orig_column_name]['label'] = col
item.setText(col_heading) self.parent.custcols[self.orig_column_name]['name'] = col_heading
self.parent.custcols[self.orig_column_name]['label'] = col self.parent.custcols[self.orig_column_name]['*edited'] = True
self.parent.custcols[self.orig_column_name]['name'] = col_heading self.parent.custcols[self.orig_column_name]['*must_restart'] = True
self.parent.custcols[self.orig_column_name]['*edited'] = True QDialog.accept(self)
self.parent.custcols[self.orig_column_name]['*must_restart'] = True
QDialog.accept(self) def reject(self):
QDialog.reject(self)
def reject(self):
QDialog.reject(self)

View File

@ -1,142 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0"> <ui version="4.0">
<class>QCreateCustomColumn</class> <class>QCreateCustomColumn</class>
<widget class="QDialog" name="QCreateCustomColumn"> <widget class="QDialog" name="QCreateCustomColumn">
<property name="windowModality"> <property name="windowModality">
<enum>Qt::ApplicationModal</enum> <enum>Qt::ApplicationModal</enum>
</property> </property>
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>391</width> <width>391</width>
<height>157</height> <height>157</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Create Tag-based Column</string> <string>Create Tag-based Column</string>
</property> </property>
<widget class="QWidget" name="verticalLayoutWidget"> <widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>10</x> <x>10</x>
<y>0</y> <y>0</y>
<width>371</width> <width>371</width>
<height>141</height> <height>141</height>
</rect> </rect>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0"> <layout class="QGridLayout" name="gridLayout_2" rowstretch="0,0,0,0">
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum> <enum>QLayout::SetDefaultConstraint</enum>
</property> </property>
<property name="margin"> <property name="margin">
<number>5</number> <number>5</number>
</property> </property>
<item row="2" column="0"> <item row="2" column="0">
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>Lookup name</string> <string>Lookup name</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Column heading</string> <string>Column heading</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="column_name_box"> <widget class="QLineEdit" name="column_name_box">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>20</width> <width>20</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Used for searching the column. Must be lower case and not contain spaces or colons.</string> <string>Used for searching the column. Must be lower case and not contain spaces or colons.</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="1" column="1">
<widget class="QLineEdit" name="column_heading_box"> <widget class="QLineEdit" name="column_heading_box">
<property name="toolTip"> <property name="toolTip">
<string>Column heading in the library view and category name in tags browser</string> <string>Column heading in the library view and category name in tags browser</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
<string>Column type</string> <string>Column type</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QComboBox" name="column_type_box"> <widget class="QComboBox" name="column_type_box">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed"> <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>70</width> <width>70</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>What kind of information will be kept in the column.</string> <string>What kind of information will be kept in the column.</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QDialogButtonBox" name="button_box"> <widget class="QDialogButtonBox" name="button_box">
<property name="orientation"> <property name="orientation">
<enum>Qt::Horizontal</enum> <enum>Qt::Horizontal</enum>
</property> </property>
<property name="standardButtons"> <property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property> </property>
<property name="centerButtons"> <property name="centerButtons">
<bool>true</bool> <bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="font"> <property name="font">
<font> <font>
<weight>75</weight> <weight>75</weight>
<bold>true</bold> <bold>true</bold>
</font> </font>
</property> </property>
<property name="text"> <property name="text">
<string>Create and edit custom columns</string> <string>Create and edit custom columns</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>column_name_box</tabstop> <tabstop>column_name_box</tabstop>
<tabstop>column_heading_box</tabstop> <tabstop>column_heading_box</tabstop>
<tabstop>column_type_box</tabstop> <tabstop>column_type_box</tabstop>
<tabstop>button_box</tabstop> <tabstop>button_box</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections/> <connections/>
</ui> </ui>

View File

@ -1,186 +1,182 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
from copy import copy
from PyQt4.QtCore import SIGNAL, Qt
from PyQt4.QtCore import SIGNAL, Qt, QVariant from PyQt4.QtGui import QDialog, QIcon, QListWidgetItem
from PyQt4.QtGui import QDialog, QDialogButtonBox, QLineEdit, QComboBox, \
QIcon, QListWidgetItem from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories
from PyQt4.Qt import QString from calibre.gui2 import qstring_to_unicode, config
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.tag_categories_ui import Ui_TagCategories from calibre.constants import islinux
from calibre.gui2 import qstring_to_unicode, config
from calibre.gui2 import question_dialog, error_dialog class Item:
from calibre.gui2.dialogs.confirm_delete import confirm def __init__(self, name, label, index, icon, exists):
from calibre.constants import islinux self.name = name
self.label = label
class Item: self.index = index
def __init__(self, name, label, index, icon, exists): self.icon = icon
self.name = name self.exists = exists
self.label = label def __str__(self):
self.index = index return 'name=%s, label=%s, index=%s, exists='%(self.name, self.label, self.index, self.exists)
self.icon = icon
self.exists = exists class TagCategories(QDialog, Ui_TagCategories):
def __str__(self): category_labels = ['', 'author', 'series', 'publisher', 'tag']
return 'name=%s, label=%s, index=%s, exists='%(self.name, self.label, self.index, self.exists)
def __init__(self, window, db, index=None):
class TagCategories(QDialog, Ui_TagCategories): QDialog.__init__(self, window)
category_labels = ['', 'author', 'series', 'publisher', 'tag'] Ui_TagCategories.__init__(self)
self.setupUi(self)
def __init__(self, window, db, index=None):
QDialog.__init__(self, window) self.db = db
Ui_TagCategories.__init__(self) self.index = index
self.setupUi(self) self.applied_items = []
self.db = db category_icons = [None, QIcon(I('user_profile.svg')), QIcon(I('series.svg')),
self.index = index QIcon(I('publisher.png')), QIcon(I('tags.svg'))]
self.applied_items = [] category_values = [None,
lambda: [n for (id, n) in self.db.all_authors()],
category_icons = [None, QIcon(I('user_profile.svg')), QIcon(I('series.svg')), lambda: [n for (id, n) in self.db.all_series()],
QIcon(I('publisher.png')), QIcon(I('tags.svg'))] lambda: [n for (id, n) in self.db.all_publishers()],
category_values = [None, lambda: self.db.all_tags()
lambda: [n for (id, n) in self.db.all_authors()], ]
lambda: [n for (id, n) in self.db.all_series()], category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')]
lambda: [n for (id, n) in self.db.all_publishers()],
lambda: self.db.all_tags() self.all_items = []
] self.all_items_dict = {}
category_names = ['', _('Authors'), _('Series'), _('Publishers'), _('Tags')] for idx,label in enumerate(self.category_labels):
if idx == 0:
self.all_items = [] continue
self.all_items_dict = {} for n in category_values[idx]():
for idx,label in enumerate(self.category_labels): t = Item(name=n, label=label, index=len(self.all_items),icon=category_icons[idx], exists=True)
if idx == 0: self.all_items.append(t)
continue self.all_items_dict[label+':'+n] = t
for n in category_values[idx]():
t = Item(name=n, label=label, index=len(self.all_items),icon=category_icons[idx], exists=True) self.categories = dict.copy(config['user_categories'])
self.all_items.append(t) if self.categories is None:
self.all_items_dict[label+':'+n] = t self.categories = {}
for cat in self.categories:
self.categories = dict.copy(config['user_categories']) for item,l in enumerate(self.categories[cat]):
if self.categories is None: key = ':'.join([l[1], l[0]])
self.categories = {} t = self.all_items_dict.get(key, None)
for cat in self.categories: if t is None:
for item,l in enumerate(self.categories[cat]): t = Item(name=l[0], label=l[1], index=len(self.all_items),
key = ':'.join([l[1], l[0]]) icon=category_icons[self.category_labels.index(l[1])], exists=False)
t = self.all_items_dict.get(key, None) self.all_items.append(t)
if t is None: self.all_items_dict[key] = t
t = Item(name=l[0], label=l[1], index=len(self.all_items), l[2] = t.index
icon=category_icons[self.category_labels.index(l[1])], exists=False)
self.all_items.append(t) self.all_items_sorted = sorted(self.all_items, cmp=lambda x,y: cmp(x.name.lower(), y.name.lower()))
self.all_items_dict[key] = t self.display_filtered_categories(0)
l[2] = t.index
for v in category_names:
self.all_items_sorted = sorted(self.all_items, cmp=lambda x,y: cmp(x.name.lower(), y.name.lower())) self.category_filter_box.addItem(v)
self.display_filtered_categories(0) self.current_cat_name = None
for v in category_names: self.connect(self.apply_button, SIGNAL('clicked()'), self.apply_tags)
self.category_filter_box.addItem(v) self.connect(self.unapply_button, SIGNAL('clicked()'), self.unapply_tags)
self.current_cat_name = None self.connect(self.add_category_button, SIGNAL('clicked()'), self.add_category)
self.connect(self.category_box, SIGNAL('currentIndexChanged(int)'), self.select_category)
self.connect(self.apply_button, SIGNAL('clicked()'), self.apply_tags) self.connect(self.category_filter_box, SIGNAL('currentIndexChanged(int)'), self.display_filtered_categories)
self.connect(self.unapply_button, SIGNAL('clicked()'), self.unapply_tags) self.connect(self.delete_category_button, SIGNAL('clicked()'), self.del_category)
self.connect(self.add_category_button, SIGNAL('clicked()'), self.add_category) if islinux:
self.connect(self.category_box, SIGNAL('currentIndexChanged(int)'), self.select_category) self.available_items_box.itemDoubleClicked.connect(self.apply_tags)
self.connect(self.category_filter_box, SIGNAL('currentIndexChanged(int)'), self.display_filtered_categories) else:
self.connect(self.delete_category_button, SIGNAL('clicked()'), self.del_category) self.connect(self.available_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags)
if islinux: self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags)
self.available_items_box.itemDoubleClicked.connect(self.apply_tags)
else: self.populate_category_list()
self.connect(self.available_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.apply_tags) return
self.connect(self.applied_items_box, SIGNAL('itemActivated(QListWidgetItem*)'), self.unapply_tags) self.select_category(0)
self.populate_category_list() def make_list_widget(self, item):
return n = item.name if item.exists else item.name + _(' (not on any book)')
self.select_category(0) w = QListWidgetItem(item.icon, n)
w.setData(Qt.UserRole, item.index)
def make_list_widget(self, item): return w
n = item.name if item.exists else item.name + _(' (not on any book)')
w = QListWidgetItem(item.icon, n) def display_filtered_categories(self, idx):
w.setData(Qt.UserRole, item.index) idx = idx if idx is not None else self.category_filter_box.currentIndex()
return w self.available_items_box.clear()
self.applied_items_box.clear()
def display_filtered_categories(self, idx): for item in self.all_items_sorted:
idx = idx if idx is not None else self.category_filter_box.currentIndex() if idx == 0 or item.label == self.category_labels[idx]:
self.available_items_box.clear() if item.index not in self.applied_items and item.exists:
self.applied_items_box.clear() self.available_items_box.addItem(self.make_list_widget(item))
for item in self.all_items_sorted: for index in self.applied_items:
if idx == 0 or item.label == self.category_labels[idx]: self.applied_items_box.addItem(self.make_list_widget(self.all_items[index]))
if item.index not in self.applied_items and item.exists:
self.available_items_box.addItem(self.make_list_widget(item)) def apply_tags(self, node=None):
for index in self.applied_items: if self.current_cat_name is None:
self.applied_items_box.addItem(self.make_list_widget(self.all_items[index])) return
nodes = self.available_items_box.selectedItems() if node is None else [node]
def apply_tags(self, node=None): for node in nodes:
if self.current_cat_name is None: index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
return if index not in self.applied_items:
nodes = self.available_items_box.selectedItems() if node is None else [node] self.applied_items.append(index)
for node in nodes: self.applied_items.sort(cmp=lambda x, y:cmp(self.all_items[x].name.lower(), self.all_items[y].name.lower()))
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index self.display_filtered_categories(None)
if index not in self.applied_items:
self.applied_items.append(index) def unapply_tags(self, node=None):
self.applied_items.sort(cmp=lambda x, y:cmp(self.all_items[x].name.lower(), self.all_items[y].name.lower())) nodes = self.applied_items_box.selectedItems() if node is None else [node]
self.display_filtered_categories(None) for node in nodes:
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index
def unapply_tags(self, node=None): self.applied_items.remove(index)
nodes = self.applied_items_box.selectedItems() if node is None else [node] self.display_filtered_categories(None)
for node in nodes:
index = self.all_items[node.data(Qt.UserRole).toPyObject()].index def add_category(self):
self.applied_items.remove(index) self.save_category()
self.display_filtered_categories(None) cat_name = qstring_to_unicode(self.input_box.text()).strip()
if cat_name == '':
def add_category(self): return False
self.save_category() if cat_name not in self.categories:
cat_name = qstring_to_unicode(self.input_box.text()).strip() self.category_box.clear()
if cat_name == '': self.current_cat_name = cat_name
return False self.categories[cat_name] = []
if cat_name not in self.categories: self.applied_items = []
self.category_box.clear() self.populate_category_list()
self.current_cat_name = cat_name self.category_box.setCurrentIndex(self.category_box.findText(cat_name))
self.categories[cat_name] = [] else:
self.applied_items = [] self.select_category(self.category_box.findText(cat_name))
self.populate_category_list() return True
self.category_box.setCurrentIndex(self.category_box.findText(cat_name))
else: def del_category(self):
self.select_category(self.category_box.findText(cat_name)) if self.current_cat_name is not None:
return True if not confirm('<p>'+_('The current tag category will be '
'<b>permanently deleted</b>. Are you sure?')
def del_category(self): +'</p>', 'tag_category_delete', self):
if self.current_cat_name is not None: return
if not confirm('<p>'+_('The current tag category will be ' del self.categories[self.current_cat_name]
'<b>permanently deleted</b>. Are you sure?') self.current_cat_name = None
+'</p>', 'tag_category_delete', self): self.category_box.removeItem(self.category_box.currentIndex())
return
del self.categories[self.current_cat_name] def select_category(self, idx):
self.current_cat_name = None self.save_category()
self.category_box.removeItem(self.category_box.currentIndex()) s = self.category_box.itemText(idx)
if s:
def select_category(self, idx): self.current_cat_name = unicode(s)
self.save_category() else:
s = self.category_box.itemText(idx) self.current_cat_name = None
if s: if self.current_cat_name:
self.current_cat_name = unicode(s) self.applied_items = [cat[2] for cat in self.categories.get(self.current_cat_name, [])]
else: else:
self.current_cat_name = None self.applied_items = []
if self.current_cat_name: self.display_filtered_categories(None)
self.applied_items = [cat[2] for cat in self.categories.get(self.current_cat_name, [])]
else: def accept(self):
self.applied_items = [] self.save_category()
self.display_filtered_categories(None) config['user_categories'] = self.categories
QDialog.accept(self)
def accept(self):
self.save_category() def save_category(self):
config['user_categories'] = self.categories if self.current_cat_name is not None:
QDialog.accept(self) l = []
for index in self.applied_items:
def save_category(self): item = self.all_items[index]
if self.current_cat_name is not None: l.append([item.name, item.label, item.index])
l = [] self.categories[self.current_cat_name] = l
for index in self.applied_items:
item = self.all_items[index] def populate_category_list(self):
l.append([item.name, item.label, item.index]) for n in sorted(self.categories.keys(), cmp=lambda x,y: cmp(x.lower(), y.lower())):
self.categories[self.current_cat_name] = l self.category_box.addItem(n)
def populate_category_list(self):
for n in sorted(self.categories.keys(), cmp=lambda x,y: cmp(x.lower(), y.lower())):
self.category_box.addItem(n)

View File

@ -6,16 +6,15 @@ import os, textwrap, traceback, re, shutil, functools
from operator import attrgetter from operator import attrgetter
from math import cos, sin, pi from math import cos, sin, pi
from contextlib import closing from contextlib import closing
from datetime import date
from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \ from PyQt4.QtGui import QTableView, QAbstractItemView, QColor, \
QPainterPath, QLinearGradient, QBrush, \ QPainterPath, QLinearGradient, QBrush, \
QPen, QStyle, QPainter, QStyleOptionViewItemV4, \ QPen, QStyle, QPainter, QStyleOptionViewItemV4, \
QIcon, QImage, QMenu, \ QIcon, QImage, QMenu, \
QStyledItemDelegate, QCompleter, QIntValidator, \ QStyledItemDelegate, QCompleter, QIntValidator, \
QPlainTextEdit, QDoubleValidator, QCheckBox, QMessageBox QDoubleValidator, QCheckBox
from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \ from PyQt4.QtCore import QAbstractTableModel, QVariant, Qt, pyqtSignal, \
SIGNAL, QObject, QSize, QModelIndex, QDate, QRect SIGNAL, QObject, QSize, QModelIndex, QDate
from calibre import strftime from calibre import strftime
from calibre.ebooks.metadata import string_to_authors, fmt_sidx, authors_to_string from calibre.ebooks.metadata import string_to_authors, fmt_sidx, authors_to_string
@ -25,7 +24,7 @@ from calibre.gui2.dialogs.comments_dialog import CommentsDialog
from calibre.gui2.widgets import EnLineEdit, TagsLineEdit from calibre.gui2.widgets import EnLineEdit, TagsLineEdit
from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH from calibre.library.caches import _match, CONTAINS_MATCH, EQUALS_MATCH, REGEXP_MATCH
from calibre.ptempfile import PersistentTemporaryFile from calibre.ptempfile import PersistentTemporaryFile
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks
from calibre.utils.date import dt_factory, qt_to_dt, isoformat from calibre.utils.date import dt_factory, qt_to_dt, isoformat
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
@ -222,10 +221,7 @@ class CcBoolDelegate(QStyledItemDelegate):
QStyledItemDelegate.__init__(self, parent) QStyledItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index): def createEditor(self, parent, option, index):
m = index.model()
col = m.column_map[index.column()]
editor = QCheckBox(parent) editor = QCheckBox(parent)
val = m.db.data[index.row()][m.db.FIELD_MAP[m.custom_columns[col]['num']]]
if tweaks['bool_custom_columns_are_tristate'] == 'no': if tweaks['bool_custom_columns_are_tristate'] == 'no':
pass pass
else: else:

View File

@ -8,7 +8,6 @@ Browsing book collection by tags.
''' '''
from itertools import izip from itertools import izip
from copy import copy
from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \ from PyQt4.Qt import Qt, QTreeView, QApplication, pyqtSignal, \
QFont, SIGNAL, QSize, QIcon, QPoint, \ QFont, SIGNAL, QSize, QIcon, QPoint, \
@ -435,4 +434,4 @@ class TagsModel(QAbstractItemModel):
continue continue
tags_seen.append(tag.name) tags_seen.append(tag.name)
ans.append('%s%s:"=%s"'%(prefix, category, tag.name)) ans.append('%s%s:"=%s"'%(prefix, category, tag.name))
return ans return ans

View File

@ -62,8 +62,6 @@ from calibre.library.caches import CoverCache
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.dialogs.tag_categories import TagCategories from calibre.gui2.dialogs.tag_categories import TagCategories
from datetime import datetime
class SaveMenu(QMenu): class SaveMenu(QMenu):
def __init__(self, parent): def __init__(self, parent):
@ -129,7 +127,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
pixmap_to_data(pixmap)) pixmap_to_data(pixmap))
def __init__(self, listener, opts, actions, parent=None): def __init__(self, listener, opts, actions, parent=None):
self.last_time = datetime.now() self.last_time = datetime.datetime.now()
self.preferences_action, self.quit_action = actions self.preferences_action, self.quit_action = actions
self.spare_servers = [] self.spare_servers = []
self.must_restart_before_config = False self.must_restart_before_config = False
@ -2167,7 +2165,7 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
return return
for row in rows: for row in rows:
path = self.library_view.model().db.abspath(row.row()) path = self.library_view.model().db.abspath(row.row())
QDesktopServices.openUrl(QUrl('file:'+path)) QDesktopServices.openUrl(QUrl.fromLocalFile(path))
def view_book(self, triggered): def view_book(self, triggered):

View File

@ -13,7 +13,7 @@ from datetime import timedelta
from PyQt4.QtCore import QThread, QReadWriteLock from PyQt4.QtCore import QThread, QReadWriteLock
from PyQt4.QtGui import QImage from PyQt4.QtGui import QImage
from calibre.utils.config import tweaks, prefs from calibre.utils.config import tweaks
from calibre.utils.date import parse_date, now from calibre.utils.date import parse_date, now
from calibre.utils.search_query_parser import SearchQueryParser from calibre.utils.search_query_parser import SearchQueryParser
from calibre.utils.pyparsing import ParseException from calibre.utils.pyparsing import ParseException
@ -541,4 +541,4 @@ class ResultCache(SearchQueryParser):
return [] return []
def set_search_restriction(self, s): def set_search_restriction(self, s):
self.search_restriction = '' if not s else 'search:"%s"' % (s.strip()) self.search_restriction = '' if not s else 'search:"%s"' % (s.strip())

View File

@ -54,7 +54,7 @@ def set_metadata(stream, mi):
while True: while True:
try: try:
ret = p.wait() p.wait()
break break
except OSError, e: except OSError, e:
if e.errno == errno.EINTR: if e.errno == errno.EINTR: