diff --git a/resources/default_tweaks.py b/resources/default_tweaks.py
index b7c02dbea7..3f8c016722 100644
--- a/resources/default_tweaks.py
+++ b/resources/default_tweaks.py
@@ -522,12 +522,6 @@ content_server_thumbnail_compression_quality = 75
# cover_drop_exclude = {'tiff', 'webp'}
cover_drop_exclude = ()
-#: Show the Saved searches box in the Search bar
-# In newer versions of calibre, only a single button that allows you to add a
-# new Saved search is shown in the Search bar. If you would like to have the
-# old Saved searches box with its two buttons back, set this tweak to True.
-show_saved_search_box = False
-
#: Exclude fields when copy/pasting metadata
# You can ask calibre to not paste some metadata fields when using the
# Edit metadata->Copy metadata/Paste metadata actions. For example,
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 5b7ea82709..480b9e9581 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -284,6 +284,8 @@ def create_defs():
defs['action-layout-toolbar-child'] = ()
+ defs['action-layout-searchbar'] = ('Saved searches',)
+
defs['action-layout-context-menu'] = (
'Edit Metadata', 'Send To Device', 'Save To Disk',
'Connect Share', 'Copy To Library', None,
@@ -395,6 +397,7 @@ def create_defs():
defs['edit_metadata_templates_only_F2_on_booklist'] = False
# JSON dumps converts integer keys to strings, so do it explicitly
defs['tb_search_order'] = {'0': 1, '1': 2, '2': 3, '3': 4, '4': 0}
+ defs['search_tool_bar_shows_text'] = True
def migrate_tweak(tweak_name, pref_name):
# If the tweak has been changed then leave the tweak in the file so
diff --git a/src/calibre/gui2/bars.py b/src/calibre/gui2/bars.py
index 14909aeb36..8eb3accd6a 100644
--- a/src/calibre/gui2/bars.py
+++ b/src/calibre/gui2/bars.py
@@ -8,7 +8,8 @@ __docformat__ = 'restructuredtext en'
from functools import partial
from qt.core import (
Qt, QAction, QMenu, QObject, QToolBar, QToolButton, QSize, pyqtSignal, QKeySequence, QMenuBar,
- QTimer, QPropertyAnimation, QEasingCurve, pyqtProperty, QPainter, QWidget, QPalette, sip)
+ QTimer, QPropertyAnimation, QEasingCurve, pyqtProperty, QPainter, QWidget, QPalette, sip,
+ QHBoxLayout, QFrame)
from calibre.constants import ismacos
from calibre.gui2 import gprefs, native_menubar_defaults, config
@@ -628,6 +629,7 @@ class BarsManager(QObject):
self.main_bars = tuple(bars[:2])
self.child_bars = tuple(bars[2:])
self.reveal_bar = RevealBar(parent)
+ self.search_tool_bar = QHBoxLayout()
self.menu_bar = MenuBar(self.location_manager, self.parent())
is_native_menubar = self.menu_bar.is_native_menubar
@@ -636,6 +638,7 @@ class BarsManager(QObject):
self.menubar_device_fallback = native_menubar_defaults['action-layout-menubar-device'] if is_native_menubar else ()
self.apply_settings()
+ self.search_tool_bar_actions = []
self.init_bars()
def database_changed(self, db):
@@ -669,6 +672,41 @@ class BarsManager(QObject):
for bar, actions in zip(self.bars, self.bar_actions[:3]):
bar.init_bar(actions)
+ # Build the layout containing the buttons to go into the search bar
+ self.build_search_tool_bar()
+
+ def build_search_tool_bar(self):
+ for ac in self.search_tool_bar_actions:
+ self.search_tool_bar.removeWidget(ac)
+
+ self.search_tool_bar.setContentsMargins(0, 0, 0, 0)
+ self.search_tool_bar.setSpacing(0)
+
+ self.search_tool_bar_actions = []
+ for what in gprefs['action-layout-searchbar']:
+ if what is None:
+ frame = QFrame()
+ frame.setFrameShape(QFrame.Shape.VLine)
+ frame.setFrameShadow(QFrame.Shadow.Sunken)
+ frame.setLineWidth(1)
+ frame.setContentsMargins(0, 5, 0, 5)
+ self.search_tool_bar.addWidget(frame)
+ self.search_tool_bar_actions.append(frame)
+ elif what in self.parent().iactions:
+ qact = self.parent().iactions[what].qaction
+ tb = QToolButton()
+ tb.setContentsMargins(0, 0, 0, 0)
+ tb.setDefaultAction(qact)
+ if not gprefs['search_tool_bar_shows_text']:
+ tb.setText(None)
+ else:
+ tb.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
+ tb.setCursor(Qt.CursorShape.PointingHandCursor)
+ tb.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
+ tb.setAutoRaise(True)
+ self.search_tool_bar.addWidget(tb)
+ self.search_tool_bar_actions.append(tb)
+
def update_bars(self, reveal_bar=False):
'''
This shows the correct main toolbar and rebuilds the menubar based on
@@ -693,6 +731,9 @@ class BarsManager(QObject):
self.menu_bar.init_bar(self.bar_actions[4 if showing_device else 3])
self.menu_bar.update_lm_actions()
self.menu_bar.setVisible(bool(self.menu_bar.added_actions))
+ self.build_search_tool_bar()
+ from calibre.gui2.ui import get_gui
+ get_gui().search_bar.update()
def apply_settings(self):
sz = gprefs['toolbar_icon_size']
diff --git a/src/calibre/gui2/layout.py b/src/calibre/gui2/layout.py
index 1725dc7817..da49012515 100644
--- a/src/calibre/gui2/layout.py
+++ b/src/calibre/gui2/layout.py
@@ -14,8 +14,7 @@ from qt.core import (
from calibre import human_readable
from calibre.constants import __appname__
from calibre.gui2.bars import BarsManager
-from calibre.gui2.search_box import SavedSearchBox, SearchBox2
-from calibre.gui2.widgets2 import RightClickButton
+from calibre.gui2.search_box import SearchBox2
from calibre.utils.config_base import tweaks
@@ -265,40 +264,8 @@ class SearchBar(QFrame): # {{{
x.setIcon(QIcon.ic('arrow-down.png'))
l.addWidget(x)
- x = parent.saved_search = SavedSearchBox(self)
- x.setObjectName("saved_search")
- l.addWidget(x)
- x.setVisible(tweaks['show_saved_search_box'])
-
- x = parent.copy_search_button = QToolButton(self)
- x.setAutoRaise(True)
- x.setCursor(Qt.CursorShape.PointingHandCursor)
- x.setIcon(QIcon.ic("search_copy_saved.png"))
- x.setObjectName("copy_search_button")
- l.addWidget(x)
- x.setToolTip(_("Copy current search text (instead of search name)"))
- x.setVisible(tweaks['show_saved_search_box'])
-
- x = parent.save_search_button = RightClickButton(self)
- x.setAutoRaise(True)
- x.setCursor(Qt.CursorShape.PointingHandCursor)
- x.setIcon(QIcon.ic("search_add_saved.png"))
- x.setObjectName("save_search_button")
- l.addWidget(x)
- x.setVisible(tweaks['show_saved_search_box'])
-
- x = parent.add_saved_search_button = RightClickButton(self)
- x.setToolTip(_(
- 'Use an existing Saved search or create a new one'
- ))
- x.setText(_('Saved search'))
- x.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
- x.setCursor(Qt.CursorShape.PointingHandCursor)
- x.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
- x.setAutoRaise(True)
- x.setIcon(QIcon.ic("folder_saved_search.png"))
- l.addWidget(x)
- x.setVisible(not tweaks['show_saved_search_box'])
+ # Add the searchbar tool buttons to the bar
+ l.addLayout(self.parent().bars_manager.search_tool_bar)
def populate_sort_menu(self):
from calibre.gui2.ui import get_gui
@@ -342,9 +309,10 @@ class MainWindowMixin: # {{{
self.iactions['Fetch News'].init_scheduler()
- self.search_bar = SearchBar(self)
self.bars_manager = BarsManager(self.donate_action,
self.location_manager, self)
+ # instantiating SearchBar must happen after setting bars manager
+ self.search_bar = SearchBar(self)
for bar in self.bars_manager.main_bars:
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, bar)
bar.setStyleSheet('QToolBar { border: 0px }')
diff --git a/src/calibre/gui2/preferences/search.py b/src/calibre/gui2/preferences/search.py
index 17ca258514..30a48a97c2 100644
--- a/src/calibre/gui2/preferences/search.py
+++ b/src/calibre/gui2/preferences/search.py
@@ -32,6 +32,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
r('show_highlight_toggle_button', gprefs)
r('limit_search_columns', prefs)
r('use_primary_find_in_search', prefs)
+ r('search_tool_bar_shows_text', gprefs)
r('case_sensitive', prefs)
fl = db.field_metadata.get_search_terms()
r('limit_search_columns_to', prefs, setting=CommaSeparatedList, choices=fl)
@@ -236,6 +237,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
return ConfigWidgetBase.commit(self)
def refresh_gui(self, gui):
+ gui.refresh_search_bar_widgets()
gui.current_db.new_api.clear_caches()
set_use_primary_find_in_search(prefs['use_primary_find_in_search'])
gui.set_highlight_only_button_icon()
diff --git a/src/calibre/gui2/preferences/search.ui b/src/calibre/gui2/preferences/search.ui
index 39768af479..ebc1957cc6 100644
--- a/src/calibre/gui2/preferences/search.ui
+++ b/src/calibre/gui2/preferences/search.ui
@@ -24,7 +24,7 @@
Genera&l
- -
+
-
@@ -40,7 +40,7 @@
- -
+
-
What to search by default
@@ -120,7 +120,7 @@
- -
+
-
Qt::Vertical
@@ -140,6 +140,13 @@
+ -
+
+
+ Show text next to buttons in the search bar
+
+
+
diff --git a/src/calibre/gui2/preferences/toolbar.py b/src/calibre/gui2/preferences/toolbar.py
index 39fe7a223e..72f3acd2ef 100644
--- a/src/calibre/gui2/preferences/toolbar.py
+++ b/src/calibre/gui2/preferences/toolbar.py
@@ -245,6 +245,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
('toolbar', _('The main toolbar')),
('toolbar-device', _('The main toolbar when a device is connected')),
('toolbar-child', _('The optional second toolbar')),
+ ('searchbar', ('The buttons on the search bar')),
('menubar', _('The menubar')),
('menubar-device', _('The menubar when a device is connected')),
('context-menu', _('The context menu for the books in the '
diff --git a/src/calibre/gui2/search_box.py b/src/calibre/gui2/search_box.py
index 13ba3a215d..546b1f7eb2 100644
--- a/src/calibre/gui2/search_box.py
+++ b/src/calibre/gui2/search_box.py
@@ -12,10 +12,9 @@ from functools import partial
from qt.core import (
QComboBox, Qt, QLineEdit, pyqtSlot, QDialog, QEvent,
pyqtSignal, QCompleter, QAction, QKeySequence, QTimer,
- QIcon, QMenu, QApplication, QKeyEvent)
+ QIcon, QApplication, QKeyEvent)
-from calibre.gui2 import config, error_dialog, question_dialog, gprefs, QT_HIDDEN_CLEAR_ACTION
-from calibre.gui2.dialogs.confirm_delete import confirm
+from calibre.gui2 import config, question_dialog, gprefs, QT_HIDDEN_CLEAR_ACTION
from calibre.gui2.dialogs.saved_search_editor import SavedSearchEditor
from calibre.gui2.dialogs.search import SearchDialog
from calibre.utils.icu import primary_sort_key
@@ -320,147 +319,6 @@ class SearchBox2(QComboBox): # {{{
# }}}
-class SavedSearchBox(QComboBox): # {{{
-
- '''
- To use this class:
- * Call initialize()
- * Connect to the changed() signal from this widget
- if you care about changes to the list of saved searches.
- '''
-
- changed = pyqtSignal()
-
- def __init__(self, parent=None):
- QComboBox.__init__(self, parent)
-
- self.line_edit = SearchLineEdit(self)
- self.setLineEdit(self.line_edit)
- self.line_edit.key_pressed.connect(self.key_pressed, type=Qt.ConnectionType.DirectConnection)
- self.textActivated.connect(self.saved_search_selected)
-
- # Turn off auto-completion so that it doesn't interfere with typing
- # names of new searches.
- completer = QCompleter(self)
- self.setCompleter(completer)
-
- self.setEditable(True)
- self.setMaxVisibleItems(25)
- self.setInsertPolicy(QComboBox.InsertPolicy.NoInsert)
- self.setSizeAdjustPolicy(QComboBox.SizeAdjustPolicy.AdjustToMinimumContentsLengthWithIcon)
- self.setMinimumContentsLength(25)
- self.tool_tip_text = self.toolTip()
-
- def initialize(self, _search_box, colorize=False, help_text=_('Search')):
- self.search_box = _search_box
- try:
- self.line_edit.setPlaceholderText(help_text)
- except:
- # Using Qt < 4.7
- pass
- self.colorize = colorize
- self.clear()
-
- def normalize_state(self):
- # need this because line_edit will call it in some cases such as paste
- pass
-
- def clear(self):
- QComboBox.clear(self)
- self.initialize_saved_search_names()
- self.setEditText('')
- self.setToolTip(self.tool_tip_text)
- self.line_edit.home(False)
-
- def key_pressed(self, event):
- if event.key() in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
- self.saved_search_selected(self.currentText())
-
- def saved_search_selected(self, qname):
- from calibre.gui2.ui import get_gui
- db = get_gui().current_db
- qname = str(qname)
- if qname is None or not qname.strip():
- self.search_box.clear()
- return
- if not db.saved_search_lookup(qname):
- self.search_box.clear()
- self.setEditText(qname)
- return
- self.search_box.set_search_string('search:"%s"' % qname, emit_changed=False)
- self.setEditText(qname)
- self.setToolTip(db.saved_search_lookup(qname))
-
- def initialize_saved_search_names(self):
- from calibre.gui2.ui import get_gui
- gui = get_gui()
- try:
- names = gui.current_db.saved_search_names()
- except AttributeError:
- # Happens during gui initialization
- names = []
- self.addItems(names)
- self.setCurrentIndex(-1)
-
- # SIGNALed from the main UI
- def save_search_button_clicked(self):
- from calibre.gui2.ui import get_gui
- db = get_gui().current_db
- name = str(self.currentText())
- if not name.strip():
- name = str(self.search_box.text()).replace('"', '')
- name = name.replace('\\', '')
- if not name:
- error_dialog(self, _('Create saved search'),
- _('Invalid saved search name. '
- 'It must contain at least one letter or number'), show=True)
- return
- if not self.search_box.text():
- error_dialog(self, _('Create saved search'),
- _('There is no search to save'), show=True)
- return
- db.saved_search_delete(name)
- db.saved_search_add(name, str(self.search_box.text()))
- # now go through an initialization cycle to ensure that the combobox has
- # the new search in it, that it is selected, and that the search box
- # references the new search instead of the text in the search.
- self.clear()
- self.setCurrentIndex(self.findText(name))
- self.saved_search_selected(name)
- self.changed.emit()
-
- def delete_current_search(self):
- from calibre.gui2.ui import get_gui
- db = get_gui().current_db
- idx = self.currentIndex()
- if idx <= 0:
- error_dialog(self, _('Delete current search'),
- _('No search is selected'), show=True)
- return
- if not confirm(''+_('The selected search will be '
- 'permanently deleted. Are you sure?') +
- '
', 'saved_search_delete', self):
- return
- ss = db.saved_search_lookup(str(self.currentText()))
- if ss is None:
- return
- db.saved_search_delete(str(self.currentText()))
- self.clear()
- self.search_box.clear()
- self.changed.emit()
-
- # SIGNALed from the main UI
- def copy_search_button_clicked(self):
- from calibre.gui2.ui import get_gui
- db = get_gui().current_db
- idx = self.currentIndex()
- if idx < 0:
- return
- self.search_box.set_search_string(db.saved_search_lookup(str(self.currentText())))
-
- # }}}
-
-
class SearchBoxMixin: # {{{
def __init__(self, *args, **kwargs):
@@ -497,6 +355,16 @@ class SearchBoxMixin: # {{{
self.highlight_only_action = ac = QAction(self)
self.addAction(ac), ac.triggered.connect(self.highlight_only_clicked)
self.keyboard.register_shortcut('highlight search results', _('Highlight search results'), action=self.highlight_only_action)
+ self.refresh_search_bar_widgets()
+
+ def refresh_search_bar_widgets(self):
+ self.set_highlight_only_button_icon()
+ if gprefs['search_tool_bar_shows_text']:
+ self.search_bar.search_button.setText(_('Search'))
+ self.search_bar.search_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
+ else:
+ self.search_bar.search_button.setText(None)
+ self.search_bar.search_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
def highlight_only_clicked(self, state):
if not config['highlight_search_matches'] and not question_dialog(self, _('Are you sure?'),
@@ -513,10 +381,20 @@ class SearchBoxMixin: # {{{
b = self.highlight_only_button
if config['highlight_search_matches']:
b.setIcon(QIcon.ic('highlight_only_on.png'))
- b.setText(_('Filter'))
+ if gprefs['search_tool_bar_shows_text']:
+ b.setText(_('Filter'))
+ b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
+ else:
+ b.setText(None)
+ b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
else:
b.setIcon(QIcon.ic('highlight_only_off.png'))
- b.setText(_('Highlight'))
+ if gprefs['search_tool_bar_shows_text']:
+ b.setText(_('Highlight'))
+ b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
+ else:
+ b.setText(None)
+ b.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.highlight_only_button.setVisible(gprefs['show_highlight_toggle_button'])
self.library_view.model().set_highlight_only(config['highlight_search_matches'])
@@ -526,11 +404,9 @@ class SearchBoxMixin: # {{{
def search_box_cleared(self):
self.tags_view.clear()
- self.saved_search.clear()
self.set_number_of_books_shown()
def search_box_changed(self):
- self.saved_search.clear()
self.tags_view.conditional_clear(self.search.current_text)
def do_advanced_search(self, *args):
@@ -554,41 +430,10 @@ class SavedSearchBoxMixin: # {{{
pass
def init_saved_seach_box_mixin(self):
- self.saved_search.changed.connect(self.saved_searches_changed)
- ac = self.search.findChild(QAction, QT_HIDDEN_CLEAR_ACTION)
- if ac is not None:
- ac.triggered.connect(self.saved_search.clear)
- self.save_search_button.clicked.connect(
- self.saved_search.save_search_button_clicked)
- self.copy_search_button.clicked.connect(
- self.saved_search.copy_search_button_clicked)
- # self.saved_searches_changed()
- self.saved_search.initialize(self.search, colorize=True,
- help_text=_('Saved searches'))
- self.saved_search.tool_tip_text=_('Choose saved search or enter name for new saved search')
- self.saved_search.setToolTip(self.saved_search.tool_tip_text)
- self.saved_search.setStatusTip(self.saved_search.tool_tip_text)
- for x in ('copy', 'save'):
- b = getattr(self, x+'_search_button')
- b.setStatusTip(b.toolTip())
- self.save_search_button.setToolTip('' +
- _("Save current search under the name shown in the box. "
- "Press and hold for a pop-up options menu.") + '
')
- self.save_search_button.setMenu(QMenu(self.save_search_button))
- self.save_search_button.menu().addAction(
- QIcon.ic('search_add_saved.png'),
- _('Create Saved search'),
- self.saved_search.save_search_button_clicked)
- self.save_search_button.menu().addAction(
- QIcon.ic('search_delete_saved.png'), _('Delete Saved search'), self.saved_search.delete_current_search)
- self.save_search_button.menu().addAction(
- QIcon.ic('search.png'), _('Manage Saved searches'), partial(self.do_saved_search_edit, None))
- self.add_saved_search_button.setMenu(QMenu(self.add_saved_search_button))
- self.add_saved_search_button.menu().aboutToShow.connect(self.populate_add_saved_search_menu)
+ pass
-
- def populate_add_saved_search_menu(self, to_menu=None):
- m = to_menu if to_menu is not None else self.add_saved_search_button.menu()
+ def populate_add_saved_search_menu(self, to_menu):
+ m = to_menu
m.clear()
m.clear()
m.addAction(QIcon.ic('search_add_saved.png'), _('Add Saved search'), self.add_saved_search)
@@ -631,7 +476,6 @@ class SavedSearchBoxMixin: # {{{
def do_rebuild_saved_searches(self):
self.saved_searches_changed()
- self.saved_search.clear()
def add_saved_search(self):
from calibre.gui2.dialogs.saved_search_editor import AddSavedSearch