Configurable suppression of replace result dialog (see 1610658)

This is the second part of the proposal filed originally as bug #1609111.

The result dialog, shown after replacements, slows down book editing in some cases.
For example, when the user repeatedly applies his own regex-function to relatively small pre-marked text fragments.

User should have a choice to suppress the result dialog.

This implementation allows for the following switch inside the user replace function:
replace.suppress_result_dialog = True

This way it nicely fits to the first part (see #1609111) and it allows individual configuration from case to case.
Actually, it's not dependent on the other two switches ('call_after_last_match' and 'append_final_output_to_marked'),
so it can be used in combination with any of them or even individually.

This implementation also should not have any impact on other modes of work and it should not change the previous behavior.
It also should not break the backwards compatibility, I hope.
This commit is contained in:
Mymei 2016-08-10 19:29:29 +02:00
parent 729cdb6306
commit 2479e304e9
2 changed files with 66 additions and 33 deletions

View File

@ -1,5 +1,5 @@
Function Mode for Search & Replace in the Editor Function Mode for Search & Replace in the Editor
======================================================================= ================================================
The Search & Replace tool in the editor support a *function mode*. In this The Search & Replace tool in the editor support a *function mode*. In this
mode, you can combine regular expressions (see :doc:`regexp`) with mode, you can combine regular expressions (see :doc:`regexp`) with
@ -23,7 +23,7 @@ complex tasks.
:align: center :align: center
Automatically fixing the case of headings in the document Automatically fixing the case of headings in the document
------------------------------------------------------------- ---------------------------------------------------------
Here, we will leverage one of the builtin functions in the editor to Here, we will leverage one of the builtin functions in the editor to
automatically change the case of all text inside heading tags to title case:: automatically change the case of all text inside heading tags to title case::
@ -37,7 +37,7 @@ the heading tags.
Your first custom function - smartening hyphens Your first custom function - smartening hyphens
------------------------------------------------------------------ -----------------------------------------------
The real power of function mode comes from being able to create your own The real power of function mode comes from being able to create your own
functions to process text in arbitrary ways. The Smarten Punctuation tool in functions to process text in arbitrary ways. The Smarten Punctuation tool in
@ -71,7 +71,7 @@ inside HTML tag definitions.
The power of function mode - using a spelling dictionary to fix mis-hyphenated words The power of function mode - using a spelling dictionary to fix mis-hyphenated words
---------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------
Often, ebooks created from scans of printed books contain mis-hyphenated words Often, ebooks created from scans of printed books contain mis-hyphenated words
-- words that were split at the end of the line on the printed page. We will -- words that were split at the end of the line on the printed page. We will
@ -116,7 +116,7 @@ main language of the book.
Auto numbering sections Auto numbering sections
--------------------------- -----------------------
Now we will see something a little different. Suppose your HTML file has many Now we will see something a little different. Suppose your HTML file has many
sections, each with a heading in an :code:`<h2>` tag that looks like sections, each with a heading in an :code:`<h2>` tag that looks like
@ -151,7 +151,7 @@ are processed in the order in which they appear in the book. See
Auto create a Table of Contents Auto create a Table of Contents
------------------------------------- -------------------------------
Finally, lets try something a little more ambitious. Suppose your book has Finally, lets try something a little more ambitious. Suppose your book has
headings in ``h1`` and ``h2`` tags that look like headings in ``h1`` and ``h2`` tags that look like
@ -221,7 +221,7 @@ you would be better off using the dedicated Table of Contents tool in
:guilabel:`Tools->Table of Contents`. :guilabel:`Tools->Table of Contents`.
The API for the function mode The API for the function mode
------------------------------- -----------------------------
All function mode functions must be python functions named replace, with the All function mode functions must be python functions named replace, with the
following signature:: following signature::
@ -236,7 +236,7 @@ the original string. The various arguments to the ``replace()`` function are
documented below. documented below.
The ``match`` argument The ``match`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
The ``match`` argument represents the currently found match. It is a The ``match`` argument represents the currently found match. It is a
`python Match object <https://docs.python.org/2.7/library/re.html#match-objects>`_. `python Match object <https://docs.python.org/2.7/library/re.html#match-objects>`_.
@ -245,14 +245,14 @@ text corresponding to individual capture groups in the search regular
expression. expression.
The ``number`` argument The ``number`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
The ``number`` argument is the number of the current match. When you run The ``number`` argument is the number of the current match. When you run
:guilabel:`Replace All`, every successive match will cause ``replace()`` to be :guilabel:`Replace All`, every successive match will cause ``replace()`` to be
called with an increasing number. The first match has number 1. called with an increasing number. The first match has number 1.
The ``file_name`` argument The ``file_name`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
This is the filename of the file in which the current match was found. When This is the filename of the file in which the current match was found. When
searching inside marked text, the ``file_name`` is empty. The ``file_name`` is searching inside marked text, the ``file_name`` is empty. The ``file_name`` is
@ -260,7 +260,7 @@ in canonical form, a path relative to the root of the book, using ``/`` as the
path separator. path separator.
The ``metadata`` argument The ``metadata`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
This represents the metadata of the current book, such as title, authors, This represents the metadata of the current book, such as title, authors,
language, etc. It is an object of class :class:`calibre.ebooks.metadata.book.base.Metadata`. language, etc. It is an object of class :class:`calibre.ebooks.metadata.book.base.Metadata`.
@ -268,7 +268,7 @@ Useful attributes include, ``title``, ``authors`` (a list of authors) and
``language`` (the language code). ``language`` (the language code).
The ``dictionaries`` argument The ``dictionaries`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This represents the collection of dictionaries used for spell checking the This represents the collection of dictionaries used for spell checking the
current book. It's most useful method is ``dictionaries.recognized(word)`` current book. It's most useful method is ``dictionaries.recognized(word)``
@ -276,7 +276,7 @@ which will return ``True`` if the passed in word is recognized by the dictionary
for the current book's language. for the current book's language.
The ``data`` argument The ``data`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^
This a simple python ``dict``. When you run This a simple python ``dict``. When you run
:guilabel:`Replace All`, every successive match will cause ``replace()`` to be :guilabel:`Replace All`, every successive match will cause ``replace()`` to be
@ -285,7 +285,7 @@ data between invocations of ``replace()`` during a :guilabel:`Replace All`
operation. operation.
The ``functions`` argument The ``functions`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
The ``functions`` argument gives you access to all other user defined The ``functions`` argument gives you access to all other user defined
functions. This is useful for code re-use. You can define utility functions in functions. This is useful for code re-use. You can define utility functions in
@ -328,7 +328,7 @@ uses it when it is run afterwards. Consider the following two functions:
... ...
Debugging your functions Debugging your functions
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
You can debug the functions you create by using the standard ``print()`` You can debug the functions you create by using the standard ``print()``
function from python. The output of print will be displayed in a popup window function from python. The output of print will be displayed in a popup window
@ -338,7 +338,7 @@ to output an entire table of contents above.
.. _file_order_replace_all: .. _file_order_replace_all:
Choose file order when running on multiple HTML files Choose file order when running on multiple HTML files
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When you run a :guilabel:`Replace All` on multiple HTML files, the order in When you run a :guilabel:`Replace All` on multiple HTML files, the order in
which the files are processes depends on what files you have open for editing. which the files are processes depends on what files you have open for editing.
@ -357,7 +357,7 @@ the search to process multiple files in the order they appear in the book,
either forwards or backwards, respectively. either forwards or backwards, respectively.
Having your function called an extra time after the last match is found Having your function called an extra time after the last match is found
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Sometimes, as in the auto generate table of contents example above, it is Sometimes, as in the auto generate table of contents example above, it is
useful to have your function called an extra time after the last match is useful to have your function called an extra time after the last match is
@ -373,7 +373,7 @@ function, like this:
Appending the output from the function to marked text Appending the output from the function to marked text
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When running search and replace on marked text, it is sometimes useful to When running search and replace on marked text, it is sometimes useful to
append so text to the end of the marked text. You can do that by setting append so text to the end of the marked text. You can do that by setting
@ -388,3 +388,26 @@ also need to set ``call_after_last_match``), like this:
replace.call_after_last_match = True replace.call_after_last_match = True
replace.append_final_output_to_marked = True replace.append_final_output_to_marked = True
If you wish, you can also suppress the result dialog by adding yet another
setting ``suppress_result_dialog`` to the previous ones, like this:
.. code-block:: python
replace.call_after_last_match = True
replace.append_final_output_to_marked = True
replace.suppress_result_dialog = True
The setting ``suppress_result_dialog`` is not dependent on the other two,
so you can also use the following combination:
.. code-block:: python
replace.call_after_last_match = True
replace.suppress_result_dialog = True
or even a single setting:
.. code-block:: python
replace.suppress_result_dialog = True

View File

@ -1320,20 +1320,21 @@ def run_search(
return no_replace(_( return no_replace(_(
'Currently selected text does not match the search query.')) 'Currently selected text does not match the search query.'))
def count_message(replaced, count, show_diff=False): def count_message(replaced, count, show_diff=False, show_dialog=True):
if replaced: if show_dialog:
msg = _('Performed the replacement at {num} occurrences of {query}') if replaced:
else: msg = _('Performed the replacement at {num} occurrences of {query}')
msg = _('Found {num} occurrences of {query}') else:
msg = msg.format(num=count, query=errfind) msg = _('Found {num} occurrences of {query}')
if show_diff and count > 0: msg = msg.format(num=count, query=errfind)
d = MessageBox(MessageBox.INFO, _('Searching done'), prepare_string_for_xml(msg), parent=gui_parent, show_copy_button=False) if show_diff and count > 0:
d.diffb = b = d.bb.addButton(_('See what &changed'), d.bb.ActionRole) d = MessageBox(MessageBox.INFO, _('Searching done'), prepare_string_for_xml(msg), parent=gui_parent, show_copy_button=False)
b.setIcon(QIcon(I('diff.png'))), d.set_details(None), b.clicked.connect(d.accept) d.diffb = b = d.bb.addButton(_('See what &changed'), d.bb.ActionRole)
b.clicked.connect(partial(show_current_diff, allow_revert=True), type=Qt.QueuedConnection) b.setIcon(QIcon(I('diff.png'))), d.set_details(None), b.clicked.connect(d.accept)
d.exec_() b.clicked.connect(partial(show_current_diff, allow_revert=True), type=Qt.QueuedConnection)
else: d.exec_()
info_dialog(gui_parent, _('Searching done'), prepare_string_for_xml(msg), show=True) else:
info_dialog(gui_parent, _('Searching done'), prepare_string_for_xml(msg), show=True)
def do_all(replace=True): def do_all(replace=True):
count = 0 count = 0
@ -1392,7 +1393,16 @@ def run_search(
return do_find() return do_find()
if action == 'replace-all': if action == 'replace-all':
if marked: if marked:
return count_message(True, sum(editor.all_in_marked(p, repl) for p, repl in searches)) suppress_dialog = False
try:
if hasattr(searches[0][1], 'func'):
user_func = getattr(searches[0][1], 'func')
user_feature = 'suppress_result_dialog'
if hasattr(user_func, user_feature):
suppress_dialog = getattr(user_func, user_feature, False)
except:
pass
return count_message(True, sum(editor.all_in_marked(p, repl) for p, repl in searches), show_dialog = not suppress_dialog)
add_savepoint(_('Before: Replace all')) add_savepoint(_('Before: Replace all'))
count = do_all() count = do_all()
if count == 0: if count == 0: