From b32521b6bed743594c4c8ffeff9b29ef5c1f5e9e Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 10:33:39 +0100 Subject: [PATCH 1/4] Fix problem where row numbers in selections are incorrect after a sort --- src/calibre/gui2/library/views.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index d25325be17..665112005c 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -235,13 +235,8 @@ class BooksView(QTableView): # {{{ self.selected_ids = [idc(r) for r in selected_rows] def sorting_done(self, indexc): - if self.selected_ids: - indices = [self.model().index(indexc(i), 0) for i in - self.selected_ids] - sm = self.selectionModel() - for idx in indices: - sm.select(idx, sm.Select|sm.Rows) - self.scroll_to_row(indices[0].row()) + self.select_rows(self.selected_ids, using_ids=True, change_current=True, + scroll=True) self.selected_ids = [] def sort_by_named_field(self, field, order, reset=True): From 53a4516337c6b5a6dc243e22e15e430e2ddfda71 Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 11:10:37 +0100 Subject: [PATCH 2/4] Add the swap_around_comma function. Update template language documentation. --- src/calibre/manual/template_lang.rst | 9 +++++++-- src/calibre/utils/formatter_functions.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst index 02a77432c9..f9824187e5 100644 --- a/src/calibre/manual/template_lang.rst +++ b/src/calibre/manual/template_lang.rst @@ -116,7 +116,7 @@ If you have programming experience, please note that the syntax in this mode (si Many functions use regular expressions. In all cases, regular expression matching is case-insensitive. -The functions available are: +The functions available are listed below. Note that the definitive documentation for functions is available in the section :ref:`Function classification `: * ``lowercase()`` -- return value of the field in lower case. * ``uppercase()`` -- return the value of the field in upper case. @@ -129,6 +129,7 @@ The functions available are: * ``list_item(index, separator)`` -- interpret the field as a list of items separated by `separator`, returning the `index`th item. The first item is number zero. The last item can be returned using `list_item(-1,separator)`. If the item is not in the list, then the empty value is returned. The separator has the same meaning as in the `count` function. * ``re(pattern, replacement)`` -- return the field after applying the regular expression. All instances of `pattern` are replaced with `replacement`. As in all of |app|, these are python-compatible regular expressions. * ``shorten(left chars, middle text, right chars)`` -- Return a shortened version of the field, consisting of `left chars` characters from the beginning of the field, followed by `middle text`, followed by `right chars` characters from the end of the string. `Left chars` and `right chars` must be integers. For example, assume the title of the book is `Ancient English Laws in the Times of Ivanhoe`, and you want it to fit in a space of at most 15 characters. If you use ``{title:shorten(9,-,5)}``, the result will be `Ancient E-nhoe`. If the field's length is less than ``left chars`` + ``right chars`` + the length of ``middle text``, then the field will be used intact. For example, the title `The Dome` would not be changed. + * ``swap_around_comma(val) `` -- given a value of the form ``B, A``, return ``A B``. This is most useful for converting names in LN, FN format to FN LN. If there is no comma, the function returns val unchanged. * ``switch(pattern, value, pattern, value, ..., else_value)`` -- for each ``pattern, value`` pair, checks if the field matches the regular expression ``pattern`` and if so, returns that ``value``. If no ``pattern`` matches, then ``else_value`` is returned. You can have as many ``pattern, value`` pairs as you want. * ``lookup(pattern, field, pattern, field, ..., else_field)`` -- like switch, except the arguments are field (metadata) names, not text. The value of the appropriate field will be fetched and used. Note that because composite columns are fields, you can use this function in one composite field to use the value of some other composite field. This is extremely useful when constructing variable save paths (more later). * ``select(key)`` -- interpret the field as a comma-separated list of items, with the items being of the form "id:value". Find the pair with the id equal to key, and return the corresponding value. This function is particularly useful for extracting a value such as an isbn from the set of identifiers for a book. @@ -230,13 +231,14 @@ For various values of series_index, the program returns: **All the functions listed under single-function mode can be used in program mode**. To do so, you must supply the value that the function is to act upon as the first parameter, in addition to the parameters documented above. For example, in program mode the parameters of the `test` function are ``test(x, text_if_not_empty, text_if_empty)``. The `x` parameter, which is the value to be tested, will almost always be a variable or a function call, often `field()`. -The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions): +The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions). Note that the definitive documentation for functions is available in the section :ref:`Function classification `: * ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want. * ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers. * ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression * ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats. * ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. + * ``days_between(date1, date2)`` -- return the number of days between ``date1`` and ``date2``. The number is positive if ``date1`` is greater than ``date2``, otherwise negative. If either ``date1`` or ``date2`` are not dates, the function returns the empty string. * ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers. * ``field(name)`` -- returns the metadata field named by ``name``. * ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want. @@ -266,7 +268,10 @@ The following functions are available in addition to those described in single-f * ``strcmp(x, y, lt, eq, gt)`` -- does a case-insensitive comparison x and y as strings. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``. * ``substr(str, start, end)`` -- returns the ``start``'th through the ``end``'th characters of ``str``. The first character in ``str`` is the zero'th character. If end is negative, then it indicates that many characters counting from the right. If end is zero, then it indicates the last character. For example, ``substr('12345', 1, 0)`` returns ``'2345'``, and ``substr('12345', 1, -1)`` returns ``'234'``. * ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers. + * ``today()`` -- return a date string for today. This value is designed for use in format_date or days_between, but can be manipulated like any other string. The date is in ISO format. * ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value. + +.. _template_functions_reference: Function classification --------------------------- diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py index c6f4bd1b0e..1684b9f85b 100644 --- a/src/calibre/utils/formatter_functions.py +++ b/src/calibre/utils/formatter_functions.py @@ -417,6 +417,18 @@ class BuiltinRe(BuiltinFormatterFunction): def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement): return re.sub(pattern, replacement, val, flags=re.I) +class BuiltinSwapAroundComma(BuiltinFormatterFunction): + name = 'swap_around_comma' + arg_count = 1 + category = 'String Manipulation' + __doc__ = doc = _('swap_around_comma(val) -- given a value of the form ' + '"B, A", return "A B". This is most useful for converting names ' + 'in LN, FN format to FN LN. If there is no comma, the function ' + 'returns val unchanged') + + def evaluate(self, formatter, kwargs, mi, locals, val): + return re.sub(r'^(.*?),(.*$)', r'\2 \1', val, flags=re.I) + class BuiltinIfempty(BuiltinFormatterFunction): name = 'ifempty' arg_count = 2 @@ -825,6 +837,7 @@ builtin_subitems = BuiltinSubitems() builtin_sublist = BuiltinSublist() builtin_substr = BuiltinSubstr() builtin_subtract = BuiltinSubtract() +builtin_swaparound = BuiltinSwapAroundComma() builtin_switch = BuiltinSwitch() builtin_template = BuiltinTemplate() builtin_test = BuiltinTest() From 3bae54056ff680c4a20f63eaca1cc87274eb64fa Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 11:23:12 +0100 Subject: [PATCH 3/4] Prevent exception if removing from calibre a library that does not exist on the file system. --- src/calibre/gui2/actions/choose_library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/calibre/gui2/actions/choose_library.py b/src/calibre/gui2/actions/choose_library.py index f96a261790..b233575fa2 100644 --- a/src/calibre/gui2/actions/choose_library.py +++ b/src/calibre/gui2/actions/choose_library.py @@ -260,7 +260,8 @@ class ChooseLibraryAction(InterfaceAction): 'The files remain on your computer, if you want ' 'to delete them, you will have to do so manually.') % loc, show=True) - open_local_file(loc) + if os.path.exists(loc): + open_local_file(loc) def backup_status(self, location): dirty_text = 'no' From d16df15022b1791b5c052f4f2aab7ee6d18d696b Mon Sep 17 00:00:00 2001 From: Charles Haley <> Date: Thu, 30 Jun 2011 12:40:01 +0100 Subject: [PATCH 4/4] Ensure that a column in a book view cannot be sized to bigger than the displayable area. --- src/calibre/gui2/library/views.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/calibre/gui2/library/views.py b/src/calibre/gui2/library/views.py index 665112005c..4ba8b0edcf 100644 --- a/src/calibre/gui2/library/views.py +++ b/src/calibre/gui2/library/views.py @@ -110,6 +110,7 @@ class BooksView(QTableView): # {{{ self.column_header.sectionMoved.connect(self.save_state) self.column_header.setContextMenuPolicy(Qt.CustomContextMenu) self.column_header.customContextMenuRequested.connect(self.show_column_header_context_menu) + self.column_header.sectionResized.connect(self.column_resized) # }}} self._model.database_changed.connect(self.database_changed) @@ -451,7 +452,9 @@ class BooksView(QTableView): # {{{ traceback.print_exc() old_state['sort_history'] = sh + self.column_header.blockSignals(True) self.apply_state(old_state) + self.column_header.blockSignals(False) # Resize all rows to have the correct height if self.model().rowCount(QModelIndex()) > 0: @@ -460,6 +463,15 @@ class BooksView(QTableView): # {{{ self.was_restored = True + def column_resized(self, col, old_size, new_size): + # arbitrary: scroll bar + header + some + max_width = self.width() - (self.verticalScrollBar().width() + + self.verticalHeader().width() + 10) + if new_size > max_width: + self.column_header.blockSignals(True) + self.setColumnWidth(col, max_width) + self.column_header.blockSignals(False) + # }}} # Initialization/Delegate Setup {{{