Use an icon rather than a color to report errors in line edits. Fixes #2003652 [color frame in edit metadata](https://bugs.launchpad.net/calibre/+bug/2003652)

This commit is contained in:
Kovid Goyal 2023-01-23 07:42:36 +05:30
parent 084cf4e65f
commit 7e67e62c11
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 68 additions and 37 deletions

View File

@ -1291,10 +1291,6 @@ class Application(QApplication):
ans.setDevicePixelRatio(device_pixel_ratio) ans.setDevicePixelRatio(device_pixel_ratio)
return ans return ans
def stylesheet_for_line_edit(self, is_error=False):
col = '#FF2400' if is_error else '#50c878'
return f'QLineEdit {{ border: 2px solid {col}; border-radius: 3px }}'
def _send_file_open_events(self): def _send_file_open_events(self):
with self._file_open_lock: with self._file_open_lock:
if self._file_open_paths: if self._file_open_paths:

View File

@ -7,9 +7,8 @@ import regex
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from io import BytesIO from io import BytesIO
from qt.core import ( from qt.core import (
QApplication, QComboBox, QCompleter, QDateTime, QDialog, QDialogButtonBox, QFont, QComboBox, QCompleter, QDateTime, QDialog, QDialogButtonBox, QFont, QGridLayout,
QGridLayout, QInputDialog, QLabel, QLineEdit, QProgressBar, QSize, Qt, QVBoxLayout, QInputDialog, QLabel, QLineEdit, QProgressBar, QSize, Qt, QVBoxLayout, pyqtSignal,
pyqtSignal,
) )
from threading import Thread from threading import Thread
@ -27,7 +26,9 @@ from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog from calibre.gui2.dialogs.metadata_bulk_ui import Ui_MetadataBulkDialog
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor from calibre.gui2.dialogs.template_line_editor import TemplateLineEditor
from calibre.gui2.widgets import LineEditECM from calibre.gui2.widgets import (
LineEditECM, setup_status_actions, update_status_actions,
)
from calibre.startup import connect_lambda from calibre.startup import connect_lambda
from calibre.utils.config import JSONConfig, dynamic, prefs, tweaks from calibre.utils.config import JSONConfig, dynamic, prefs, tweaks
from calibre.utils.date import internal_iso_format_string, qt_to_dt from calibre.utils.date import internal_iso_format_string, qt_to_dt
@ -499,6 +500,7 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
def __init__(self, window, rows, model, tab, refresh_books): def __init__(self, window, rows, model, tab, refresh_books):
QDialog.__init__(self, window) QDialog.__init__(self, window)
self.setupUi(self) self.setupUi(self)
setup_status_actions(self.test_result)
self.series.set_sort_func(title_sort) self.series.set_sort_func(title_sort)
self.model = model self.model = model
self.db = model.db self.db = model.db
@ -899,10 +901,11 @@ class MetadataBulkDialog(QDialog, Ui_MetadataBulkDialog):
self.s_r_search_field_changed(self.search_field.currentIndex()) self.s_r_search_field_changed(self.search_field.currentIndex())
def s_r_set_colors(self): def s_r_set_colors(self):
tt = ''
if self.s_r_error is not None: if self.s_r_error is not None:
self.test_result.setText(error_message(self.s_r_error)) tt = error_message(self.s_r_error)
self.test_result.setStyleSheet( self.test_result.setText(tt)
QApplication.instance().stylesheet_for_line_edit(self.s_r_error is not None)) update_status_actions(self.test_result, self.s_r_error is None, tt)
for i in range(0,self.s_r_number_of_books): for i in range(0,self.s_r_number_of_books):
getattr(self, 'book_%d_result'%(i+1)).setText('') getattr(self, 'book_%d_result'%(i+1)).setText('')

View File

@ -37,7 +37,7 @@ from calibre.gui2.comments_editor import Editor
from calibre.gui2.complete2 import EditWithComplete from calibre.gui2.complete2 import EditWithComplete
from calibre.gui2.dialogs.tag_editor import TagEditor from calibre.gui2.dialogs.tag_editor import TagEditor
from calibre.gui2.languages import LanguagesEdit as LE from calibre.gui2.languages import LanguagesEdit as LE
from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView from calibre.gui2.widgets import EnLineEdit, FormatList as _FormatList, ImageView, LineEditIndicators
from calibre.gui2.widgets2 import ( from calibre.gui2.widgets2 import (
DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key, DateTimeEdit, Dialog, RatingEditor, RightClickButton, access_key,
populate_standard_spinbox_context_menu, populate_standard_spinbox_context_menu,
@ -261,7 +261,7 @@ class TitleEdit(EnLineEdit, ToMetadataMixin):
self.dialog = None self.dialog = None
class TitleSortEdit(TitleEdit, ToMetadataMixin): class TitleSortEdit(TitleEdit, ToMetadataMixin, LineEditIndicators):
TITLE_ATTR = FIELD_NAME = 'title_sort' TITLE_ATTR = FIELD_NAME = 'title_sort'
TOOLTIP = _('Specify how this book should be sorted when by title.' TOOLTIP = _('Specify how this book should be sorted when by title.'
@ -270,15 +270,16 @@ class TitleSortEdit(TitleEdit, ToMetadataMixin):
def __init__(self, parent, title_edit, autogen_button, languages_edit): def __init__(self, parent, title_edit, autogen_button, languages_edit):
TitleEdit.__init__(self, parent) TitleEdit.__init__(self, parent)
self.setup_status_actions()
self.title_edit = title_edit self.title_edit = title_edit
self.languages_edit = languages_edit self.languages_edit = languages_edit
base = self.TOOLTIP base = self.TOOLTIP
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>' + _( ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>' + _(
' The green color indicates that the current ' ' The ok icon indicates that the current '
'title sort matches the current title')) 'title sort matches the current title'))
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>' + _( bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>' + _(
' The red color warns that the current ' ' The error icon warns that the current '
'title sort does not match the current title. ' 'title sort does not match the current title. '
'No action is required if this is what you want.')) 'No action is required if this is what you want.'))
self.tooltips = (ok_tooltip, bad_tooltip) self.tooltips = (ok_tooltip, bad_tooltip)
@ -314,8 +315,8 @@ class TitleSortEdit(TitleEdit, ToMetadataMixin):
def update_state(self, *args): def update_state(self, *args):
ts = title_sort(self.title_edit.current_val, lang=self.book_lang) ts = title_sort(self.title_edit.current_val, lang=self.book_lang)
normal = ts == self.current_val normal = ts == self.current_val
self.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not normal))
tt = self.tooltips[0 if normal else 1] tt = self.tooltips[0 if normal else 1]
self.update_status_actions(normal, tt)
self.setToolTip(tt) self.setToolTip(tt)
self.setWhatsThis(tt) self.setWhatsThis(tt)
@ -456,7 +457,7 @@ class AuthorsEdit(EditWithComplete, ToMetadataMixin):
pass pass
class AuthorSortEdit(EnLineEdit, ToMetadataMixin): class AuthorSortEdit(EnLineEdit, ToMetadataMixin, LineEditIndicators):
TOOLTIP = _('Specify how the author(s) of this book should be sorted. ' TOOLTIP = _('Specify how the author(s) of this book should be sorted. '
'For example Charles Dickens should be sorted as Dickens, ' 'For example Charles Dickens should be sorted as Dickens, '
@ -470,15 +471,16 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin):
def __init__(self, parent, authors_edit, autogen_button, db, def __init__(self, parent, authors_edit, autogen_button, db,
copy_a_to_as_action, copy_as_to_a_action, a_to_as, as_to_a): copy_a_to_as_action, copy_as_to_a_action, a_to_as, as_to_a):
EnLineEdit.__init__(self, parent) EnLineEdit.__init__(self, parent)
self.setup_status_actions()
self.authors_edit = authors_edit self.authors_edit = authors_edit
self.db = db self.db = db
base = self.TOOLTIP base = self.TOOLTIP
ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>' + _( ok_tooltip = '<p>' + textwrap.fill(base+'<br><br>' + _(
' The green color indicates that the current ' ' The ok icon indicates that the current '
'author sort matches the current author')) 'author sort matches the current author'))
bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+ _( bad_tooltip = '<p>'+textwrap.fill(base + '<br><br>'+ _(
' The red color indicates that the current ' ' The error icon indicates that the current '
'author sort does not match the current author. ' 'author sort does not match the current author. '
'No action is required if this is what you want.')) 'No action is required if this is what you want.'))
self.tooltips = (ok_tooltip, bad_tooltip) self.tooltips = (ok_tooltip, bad_tooltip)
@ -529,8 +531,8 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin):
au = self.author_sort_from_authors(string_to_authors(au)) au = self.author_sort_from_authors(string_to_authors(au))
normal = au == self.current_val normal = au == self.current_val
self.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not normal))
tt = self.tooltips[0 if normal else 1] tt = self.tooltips[0 if normal else 1]
self.update_status_actions(normal, tt)
self.setToolTip(tt) self.setToolTip(tt)
self.setWhatsThis(tt) self.setWhatsThis(tt)
@ -1572,7 +1574,7 @@ class Identifiers(Dialog):
Dialog.accept(self) Dialog.accept(self)
class IdentifiersEdit(QLineEdit, ToMetadataMixin): class IdentifiersEdit(QLineEdit, ToMetadataMixin, LineEditIndicators):
LABEL = _('&Ids:') LABEL = _('&Ids:')
BASE_TT = _('Edit the identifiers for this book. ' BASE_TT = _('Edit the identifiers for this book. '
'For example: \n\n%s\n\nIf an identifier value contains a comma, you can use the | character to represent it.')%( 'For example: \n\n%s\n\nIf an identifier value contains a comma, you can use the | character to represent it.')%(
@ -1582,6 +1584,7 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin):
def __init__(self, parent): def __init__(self, parent):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
self.setup_status_actions()
self.pat = re.compile(r'[^0-9a-zA-Z]') self.pat = re.compile(r'[^0-9a-zA-Z]')
self.textChanged.connect(self.validate) self.textChanged.connect(self.validate)
self.textChanged.connect(self.data_changed) self.textChanged.connect(self.data_changed)
@ -1652,16 +1655,17 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin):
isbn = identifiers.get('isbn', '') isbn = identifiers.get('isbn', '')
tt = self.BASE_TT tt = self.BASE_TT
extra = '' extra = ''
ok = None
if not isbn: if not isbn:
sheet = '' pass
elif check_isbn(isbn) is not None: elif check_isbn(isbn) is not None:
sheet = QApplication.instance().stylesheet_for_line_edit() ok = True
extra = '\n\n'+_('This ISBN is valid') extra = '\n\n'+_('This ISBN is valid')
else: else:
sheet = QApplication.instance().stylesheet_for_line_edit(True) ok = False
extra = '\n\n' + _('This ISBN is invalid') extra = '\n\n' + _('This ISBN is invalid')
self.setToolTip(tt+extra) self.setToolTip(tt+extra)
self.setStyleSheet(sheet) self.update_status_actions(ok, self.toolTip())
def paste_identifier(self): def paste_identifier(self):
identifier_found = self.parse_clipboard_for_identifier() identifier_found = self.parse_clipboard_for_identifier()
@ -1751,6 +1755,9 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin):
return False return False
# }}} # }}}
class IndicatorLineEdit(QLineEdit, LineEditIndicators):
pass
class ISBNDialog(QDialog): # {{{ class ISBNDialog(QDialog): # {{{
@ -1763,7 +1770,8 @@ class ISBNDialog(QDialog): # {{{
l.addWidget(w, 0, 0, 1, 2) l.addWidget(w, 0, 0, 1, 2)
w = QLabel(_('ISBN:')) w = QLabel(_('ISBN:'))
l.addWidget(w, 1, 0, 1, 1) l.addWidget(w, 1, 0, 1, 1)
self.line_edit = w = QLineEdit() self.line_edit = w = IndicatorLineEdit()
w.setup_status_actions()
w.setText(txt) w.setText(txt)
w.selectAll() w.selectAll()
w.textChanged.connect(self.checkText) w.textChanged.connect(self.checkText)
@ -1787,17 +1795,17 @@ class ISBNDialog(QDialog): # {{{
def checkText(self, txt): def checkText(self, txt):
isbn = str(txt) isbn = str(txt)
ok = None
if not isbn: if not isbn:
sheet = '' pass
extra = ''
elif check_isbn(isbn) is not None: elif check_isbn(isbn) is not None:
sheet = QApplication.instance().stylesheet_for_line_edit()
extra = _('This ISBN is valid') extra = _('This ISBN is valid')
ok = True
else: else:
sheet = QApplication.instance().stylesheet_for_line_edit(True)
extra = _('This ISBN is invalid') extra = _('This ISBN is invalid')
ok = False
self.line_edit.setToolTip(extra) self.line_edit.setToolTip(extra)
self.line_edit.setStyleSheet(sheet) self.line_edit.update_status_actions(ok, extra)
def text(self): def text(self):
return check_isbn(str(self.line_edit.text())) return check_isbn(str(self.line_edit.text()))

View File

@ -158,7 +158,6 @@ class SearchBox2(QComboBox): # {{{
items.append(item) items.append(item)
self.addItems(items) self.addItems(items)
self.line_edit.setPlaceholderText(help_text) self.line_edit.setPlaceholderText(help_text)
self.colorize = colorize
self.clear() self.clear()
def clear_history(self): def clear_history(self):
@ -210,10 +209,6 @@ class SearchBox2(QComboBox): # {{{
self.clear(emit_search=False) self.clear(emit_search=False)
return return
self._in_a_search = ok self._in_a_search = ok
if self.colorize:
self.line_edit.setStyleSheet(QApplication.instance().stylesheet_for_line_edit(not ok))
else:
self.line_edit.setStyleSheet('')
# Comes from the lineEdit control # Comes from the lineEdit control
def key_pressed(self, event): def key_pressed(self, event):
@ -337,7 +332,7 @@ class SearchBoxMixin: # {{{
pass pass
def init_search_box_mixin(self): def init_search_box_mixin(self):
self.search.initialize('main_search_history', colorize=True, self.search.initialize('main_search_history',
help_text=_('Search (For advanced search click the gear icon to the left)')) help_text=_('Search (For advanced search click the gear icon to the left)'))
self.search.cleared.connect(self.search_box_cleared) self.search.cleared.connect(self.search_box_cleared)
# Queued so that search.current_text will be correct # Queued so that search.current_text will be correct

View File

@ -546,6 +546,35 @@ class EnLineEdit(LineEditECM, QLineEdit): # {{{
# }}} # }}}
# LineEditIndicators {{{
def setup_status_actions(self: QLineEdit):
self.status_actions = (
self.addAction(QIcon.ic('ok.png'), QLineEdit.ActionPosition.TrailingPosition),
self.addAction(QIcon.ic('dialog_error.png'), QLineEdit.ActionPosition.TrailingPosition))
self.status_actions[0].setVisible(False)
self.status_actions[1].setVisible(False)
def update_status_actions(self: QLineEdit, ok, tooltip: str = ''):
self.status_actions[0].setVisible(bool(ok))
self.status_actions[1].setVisible(not ok)
if ok:
self.status_actions[0].setToolTip(tooltip)
elif ok is None:
self.status_actions[1].setVisible(False)
else:
self.status_actions[1].setToolTip(tooltip)
class LineEditIndicators:
def setup_status_actions(self):
setup_status_actions(self)
def update_status_actions(self, ok, tooltip=''):
update_status_actions(self, ok, tooltip)
# }}}
class ItemsCompleter(QCompleter): # {{{ class ItemsCompleter(QCompleter): # {{{
''' '''