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