Edit metadata dialog: Show a confirmation dialog on cancel if some changes have been made. Fixes #1786544 [[Enhancement] Cancelling out of edit metadata dialog box should prompt user whether to save changes](https://bugs.launchpad.net/calibre/+bug/1786544)

This commit is contained in:
Kovid Goyal 2018-08-11 10:29:24 +05:30
parent f8e4062d9b
commit ed6184f628
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 116 additions and 2 deletions

View File

@ -13,7 +13,7 @@ from PyQt5.Qt import (QApplication, QFontInfo, QSize, QWidget, QPlainTextEdit,
QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, QFormLayout, QToolBar, QVBoxLayout, QAction, QIcon, Qt, QTabWidget, QUrl, QFormLayout,
QSyntaxHighlighter, QColor, QColorDialog, QMenu, QDialog, QLabel, QSyntaxHighlighter, QColor, QColorDialog, QMenu, QDialog, QLabel,
QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox, QPushButton, QHBoxLayout, QKeySequence, QLineEdit, QDialogButtonBox, QPushButton,
QCheckBox) pyqtSignal, QCheckBox)
from PyQt5.QtWebKitWidgets import QWebView, QWebPage from PyQt5.QtWebKitWidgets import QWebView, QWebPage
try: try:
from PyQt5 import sip from PyQt5 import sip
@ -74,6 +74,8 @@ class BlockStyleAction(QAction): # {{{
class EditorWidget(QWebView, LineEditECM): # {{{ class EditorWidget(QWebView, LineEditECM): # {{{
data_changed = pyqtSignal()
def __init__(self, parent=None): def __init__(self, parent=None):
QWebView.__init__(self, parent) QWebView.__init__(self, parent)
self.base_url = None self.base_url = None
@ -184,6 +186,7 @@ class EditorWidget(QWebView, LineEditECM): # {{{
self.setHtml('') self.setHtml('')
self.set_readonly(False) self.set_readonly(False)
self.page().contentsChanged.connect(self.data_changed)
def update_link_action(self): def update_link_action(self):
wac = self.pageAction(QWebPage.ToggleBold).isEnabled() wac = self.pageAction(QWebPage.ToggleBold).isEnabled()
@ -660,6 +663,7 @@ class Highlighter(QSyntaxHighlighter):
class Editor(QWidget): # {{{ class Editor(QWidget): # {{{
toolbar_prefs_name = None toolbar_prefs_name = None
data_changed = pyqtSignal()
def __init__(self, parent=None, one_line_toolbar=False, toolbar_prefs_name=None): def __init__(self, parent=None, one_line_toolbar=False, toolbar_prefs_name=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -671,6 +675,7 @@ class Editor(QWidget): # {{{
t = getattr(self, 'toolbar%d'%i) t = getattr(self, 'toolbar%d'%i)
t.setIconSize(QSize(18, 18)) t.setIconSize(QSize(18, 18))
self.editor = EditorWidget(self) self.editor = EditorWidget(self)
self.editor.data_changed.connect(self.data_changed)
self.set_base_url = self.editor.set_base_url self.set_base_url = self.editor.set_base_url
self.set_html = self.editor.set_html self.set_html = self.editor.set_html
self.tabs = QTabWidget(self) self.tabs = QTabWidget(self)

View File

@ -25,12 +25,20 @@ from calibre.gui2.library.delegates import ClearingDoubleSpinBox, ClearingSpinBo
from calibre.gui2.widgets2 import RatingEditor from calibre.gui2.widgets2 import RatingEditor
def safe_disconnect(signal):
try:
signal.disconnect()
except Exception:
pass
class Base(object): class Base(object):
def __init__(self, db, col_id, parent=None): def __init__(self, db, col_id, parent=None):
self.db, self.col_id = db, col_id self.db, self.col_id = db, col_id
self.col_metadata = db.custom_column_num_map[col_id] self.col_metadata = db.custom_column_num_map[col_id]
self.initial_val = self.widgets = None self.initial_val = self.widgets = None
self.signals_to_disconnect = []
self.setup_ui(parent) self.setup_ui(parent)
def initialize(self, book_id): def initialize(self, book_id):
@ -66,6 +74,12 @@ class Base(object):
def break_cycles(self): def break_cycles(self):
self.db = self.widgets = self.initial_val = None self.db = self.widgets = self.initial_val = None
for signal in self.signals_to_disconnect:
safe_disconnect(signal)
self.signals_to_disconnect = []
def connect_data_changed(self, slot):
pass
class SimpleText(Base): class SimpleText(Base):
@ -79,6 +93,10 @@ class SimpleText(Base):
def getter(self): def getter(self):
return self.widgets[1].text().strip() return self.widgets[1].text().strip()
def connect_data_changed(self, slot):
self.widgets[1].textChanged.connect(slot)
self.signals_to_disconnect.append(self.widgets[1].textChanged)
class LongText(Base): class LongText(Base):
@ -98,6 +116,10 @@ class LongText(Base):
def getter(self): def getter(self):
return self._tb.toPlainText() return self._tb.toPlainText()
def connect_data_changed(self, slot):
self._tb.textChanged.connect(slot)
self.signals_to_disconnect.append(self._tb.textChanged)
class Bool(Base): class Bool(Base):
@ -165,6 +187,10 @@ class Bool(Base):
def set_to_cleared(self): def set_to_cleared(self):
self.combobox.setCurrentIndex(2) self.combobox.setCurrentIndex(2)
def connect_data_changed(self, slot):
self.combobox.currentTextChanged.connect(slot)
self.signals_to_disconnect.append(self.combobox.currentTextChanged)
class Int(Base): class Int(Base):
@ -195,6 +221,10 @@ class Int(Base):
self.setter(0) self.setter(0)
self.was_none = to_what == self.widgets[1].minimum() self.was_none = to_what == self.widgets[1].minimum()
def connect_data_changed(self, slot):
self.widgets[1].valueChanged.connect(slot)
self.signals_to_disconnect.append(self.widgets[1].valueChanged)
class Float(Int): class Float(Int):
@ -223,6 +253,10 @@ class Rating(Base):
def getter(self): def getter(self):
return self.widgets[1].rating_value or None return self.widgets[1].rating_value or None
def connect_data_changed(self, slot):
self.widgets[1].currentTextChanged.connect(slot)
self.signals_to_disconnect.append(self.widgets[1].currentTextChanged)
class DateTimeEdit(QDateTimeEdit): class DateTimeEdit(QDateTimeEdit):
@ -302,6 +336,10 @@ class DateTime(Base):
def normalize_ui_val(self, val): def normalize_ui_val(self, val):
return as_utc(val) if val is not None else None return as_utc(val) if val is not None else None
def connect_data_changed(self, slot):
self.widgets[1].dateTimeChanged.connect(slot)
self.signals_to_disconnect.append(self.widgets[1].dateTimeChanged)
class Comments(Base): class Comments(Base):
@ -345,6 +383,10 @@ class Comments(Base):
self._tb.tab = val self._tb.tab = val
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def connect_data_changed(self, slot):
self._tb.data_changed.connect(slot)
self.signals_to_disconnect.append(self._tb.data_changed)
class MultipleWidget(QWidget): class MultipleWidget(QWidget):
@ -481,6 +523,14 @@ class Text(Base):
if d.exec_() == TagEditor.Accepted: if d.exec_() == TagEditor.Accepted:
self.setter(d.tags) self.setter(d.tags)
def connect_data_changed(self, slot):
if self.col_metadata['is_multiple']:
s = self.widgets[1].tags_box.currentTextChanged
else:
s = self.widgets[1].currentTextChanged
s.connect(slot)
self.signals_to_disconnect.append(s)
class Series(Base): class Series(Base):
@ -554,6 +604,11 @@ class Series(Base):
val, s_index = self.current_val val, s_index = self.current_val
mi.set('#' + self.col_metadata['label'], val, extra=s_index) mi.set('#' + self.col_metadata['label'], val, extra=s_index)
def connect_data_changed(self, slot):
for s in self.widgets[1].editTextChanged, self.widgets[3].valueChanged:
s.connect(slot)
self.signals_to_disconnect.append(s)
class Enumeration(Base): class Enumeration(Base):
@ -598,6 +653,10 @@ class Enumeration(Base):
val = None val = None
return val return val
def connect_data_changed(self, slot):
self.widgets[1].currentIndexChanged.connect(slot)
self.signals_to_disconnect.append(self.widgets[1].currentIndexChanged)
def comments_factory(db, key, parent): def comments_factory(db, key, parent):
fm = db.custom_column_num_map[key] fm = db.custom_column_num_map[key]

View File

@ -195,12 +195,14 @@ class TitleEdit(EnLineEdit, ToMetadataMixin):
TITLE_ATTR = FIELD_NAME = 'title' TITLE_ATTR = FIELD_NAME = 'title'
TOOLTIP = _('Change the title of this book') TOOLTIP = _('Change the title of this book')
LABEL = _('&Title:') LABEL = _('&Title:')
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
self.dialog = parent self.dialog = parent
EnLineEdit.__init__(self, parent) EnLineEdit.__init__(self, parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
self.textChanged.connect(self.data_changed)
def get_default(self): def get_default(self):
return _('Unknown') return _('Unknown')
@ -331,6 +333,7 @@ class AuthorsEdit(EditWithComplete, ToMetadataMixin):
TOOLTIP = '' TOOLTIP = ''
LABEL = _('&Author(s):') LABEL = _('&Author(s):')
FIELD_NAME = 'authors' FIELD_NAME = 'authors'
data_changed = pyqtSignal()
def __init__(self, parent, manage_authors): def __init__(self, parent, manage_authors):
self.dialog = parent self.dialog = parent
@ -343,6 +346,7 @@ class AuthorsEdit(EditWithComplete, ToMetadataMixin):
self.manage_authors_signal = manage_authors self.manage_authors_signal = manage_authors
manage_authors.triggered.connect(self.manage_authors) manage_authors.triggered.connect(self.manage_authors)
self.lineEdit().createStandardContextMenu = self.createStandardContextMenu self.lineEdit().createStandardContextMenu = self.createStandardContextMenu
self.lineEdit().textChanged.connect(self.data_changed)
def createStandardContextMenu(self): def createStandardContextMenu(self):
menu = QLineEdit.createStandardContextMenu(self.lineEdit()) menu = QLineEdit.createStandardContextMenu(self.lineEdit())
@ -444,6 +448,7 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin):
'red, then the authors and this text do not match.') 'red, then the authors and this text do not match.')
LABEL = _('Author s&ort:') LABEL = _('Author s&ort:')
FIELD_NAME = 'author_sort' FIELD_NAME = 'author_sort'
data_changed = pyqtSignal()
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):
@ -463,6 +468,7 @@ class AuthorSortEdit(EnLineEdit, ToMetadataMixin):
self.authors_edit.editTextChanged.connect(self.update_state_and_val, type=Qt.QueuedConnection) self.authors_edit.editTextChanged.connect(self.update_state_and_val, type=Qt.QueuedConnection)
self.textChanged.connect(self.update_state) self.textChanged.connect(self.update_state)
self.textChanged.connect(self.data_changed)
self.autogen_button = autogen_button self.autogen_button = autogen_button
self.copy_a_to_as_action = copy_a_to_as_action self.copy_a_to_as_action = copy_a_to_as_action
@ -591,6 +597,7 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin):
TOOLTIP = _('List of known series. You can add new series.') TOOLTIP = _('List of known series. You can add new series.')
LABEL = _('&Series:') LABEL = _('&Series:')
FIELD_NAME = 'series' FIELD_NAME = 'series'
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
EditWithComplete.__init__(self, parent) EditWithComplete.__init__(self, parent)
@ -602,6 +609,7 @@ class SeriesEdit(EditWithComplete, ToMetadataMixin):
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
self.setEditable(True) self.setEditable(True)
self.books_to_refresh = set([]) self.books_to_refresh = set([])
self.lineEdit().textChanged.connect(self.data_changed)
@dynamic_property @dynamic_property
def current_val(self): def current_val(self):
@ -645,9 +653,11 @@ class SeriesIndexEdit(make_undoable(QDoubleSpinBox), ToMetadataMixin):
TOOLTIP = '' TOOLTIP = ''
LABEL = _('&Number:') LABEL = _('&Number:')
FIELD_NAME = 'series_index' FIELD_NAME = 'series_index'
data_changed = pyqtSignal()
def __init__(self, parent, series_edit): def __init__(self, parent, series_edit):
super(SeriesIndexEdit, self).__init__(parent) super(SeriesIndexEdit, self).__init__(parent)
self.valueChanged.connect(self.data_changed)
self.dialog = parent self.dialog = parent
self.db = self.original_series_name = None self.db = self.original_series_name = None
self.setMaximum(10000000) self.setMaximum(10000000)
@ -795,11 +805,23 @@ class FormatList(_FormatList):
class FormatsManager(QWidget): class FormatsManager(QWidget):
data_changed = pyqtSignal()
@property
def changed(self):
return self._changed
@changed.setter
def changed(self, val):
self._changed = val
if val:
self.data_changed.emit()
def __init__(self, parent, copy_fmt): def __init__(self, parent, copy_fmt):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.dialog = parent self.dialog = parent
self.copy_fmt = copy_fmt self.copy_fmt = copy_fmt
self.changed = False self._changed = False
self.l = l = QGridLayout() self.l = l = QGridLayout()
self.setLayout(l) self.setLayout(l)
@ -1031,6 +1053,7 @@ class FormatsManager(QWidget):
class Cover(ImageView): # {{{ class Cover(ImageView): # {{{
download_cover = pyqtSignal() download_cover = pyqtSignal()
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
ImageView.__init__(self, parent, show_size_pref_name='edit_metadata_cover_widget', default_show_size=True) ImageView.__init__(self, parent, show_size_pref_name='edit_metadata_cover_widget', default_show_size=True)
@ -1202,6 +1225,7 @@ class Cover(ImageView): # {{{
tt = _('Cover size: %(width)d x %(height)d pixels') % \ tt = _('Cover size: %(width)d x %(height)d pixels') % \
dict(width=pm.width(), height=pm.height()) dict(width=pm.width(), height=pm.height())
self.setToolTip(tt) self.setToolTip(tt)
self.data_changed.emit()
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
@ -1245,6 +1269,7 @@ class CommentsEdit(Editor, ToMetadataMixin): # {{{
val = comments_to_html(val) val = comments_to_html(val)
self.set_html(val, self.allow_undo) self.set_html(val, self.allow_undo)
self.wyswyg_dirtied() self.wyswyg_dirtied()
self.data_changed.emit()
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def initialize(self, db, id_): def initialize(self, db, id_):
@ -1265,11 +1290,13 @@ class RatingEdit(RatingEditor, ToMetadataMixin): # {{{
LABEL = _('&Rating:') LABEL = _('&Rating:')
TOOLTIP = _('Rating of this book. 0-5 stars') TOOLTIP = _('Rating of this book. 0-5 stars')
FIELD_NAME = 'rating' FIELD_NAME = 'rating'
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
super(RatingEdit, self).__init__(parent) super(RatingEdit, self).__init__(parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
self.currentTextChanged.connect(self.data_changed)
@dynamic_property @dynamic_property
def current_val(self): def current_val(self):
@ -1301,9 +1328,11 @@ class TagsEdit(EditWithComplete, ToMetadataMixin): # {{{
'useful while searching. <br><br>They can be any words ' 'useful while searching. <br><br>They can be any words '
'or phrases, separated by commas.') 'or phrases, separated by commas.')
FIELD_NAME = 'tags' FIELD_NAME = 'tags'
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
EditWithComplete.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.currentTextChanged.connect(self.data_changed)
self.lineEdit().setMaxLength(655360) # see https://bugs.launchpad.net/bugs/1630944 self.lineEdit().setMaxLength(655360) # see https://bugs.launchpad.net/bugs/1630944
self.books_to_refresh = set([]) self.books_to_refresh = set([])
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
@ -1366,9 +1395,11 @@ class LanguagesEdit(LE, ToMetadataMixin): # {{{
LABEL = _('&Languages:') LABEL = _('&Languages:')
TOOLTIP = _('A comma separated list of languages for this book') TOOLTIP = _('A comma separated list of languages for this book')
FIELD_NAME = 'languages' FIELD_NAME = 'languages'
data_changed = pyqtSignal()
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
LE.__init__(self, *args, **kwargs) LE.__init__(self, *args, **kwargs)
self.textChanged.connect(self.data_changed)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
@dynamic_property @dynamic_property
@ -1458,11 +1489,13 @@ class IdentifiersEdit(QLineEdit, ToMetadataMixin):
'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.')%(
'isbn:1565927249, doi:10.1000/182, amazon:1565927249') 'isbn:1565927249, doi:10.1000/182, amazon:1565927249')
FIELD_NAME = 'identifiers' FIELD_NAME = 'identifiers'
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
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)
def contextMenuEvent(self, ev): def contextMenuEvent(self, ev):
m = self.createStandardContextMenu() m = self.createStandardContextMenu()
@ -1631,9 +1664,11 @@ class ISBNDialog(QDialog): # {{{
class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{ class PublisherEdit(EditWithComplete, ToMetadataMixin): # {{{
LABEL = _('&Publisher:') LABEL = _('&Publisher:')
FIELD_NAME = 'publisher' FIELD_NAME = 'publisher'
data_changed = pyqtSignal()
def __init__(self, parent): def __init__(self, parent):
EditWithComplete.__init__(self, parent) EditWithComplete.__init__(self, parent)
self.currentTextChanged.connect(self.data_changed)
self.set_separator(None) self.set_separator(None)
self.setSizeAdjustPolicy( self.setSizeAdjustPolicy(
self.AdjustToMinimumContentsLengthWithIcon) self.AdjustToMinimumContentsLengthWithIcon)
@ -1694,11 +1729,13 @@ class DateEdit(make_undoable(QDateTimeEdit), ToMetadataMixin):
FMT = 'dd MMM yyyy hh:mm:ss' FMT = 'dd MMM yyyy hh:mm:ss'
ATTR = FIELD_NAME = 'timestamp' ATTR = FIELD_NAME = 'timestamp'
TWEAK = 'gui_timestamp_display_format' TWEAK = 'gui_timestamp_display_format'
data_changed = pyqtSignal()
def __init__(self, parent, create_clear_button=True): def __init__(self, parent, create_clear_button=True):
super(DateEdit, self).__init__(parent) super(DateEdit, self).__init__(parent)
self.setToolTip(self.TOOLTIP) self.setToolTip(self.TOOLTIP)
self.setWhatsThis(self.TOOLTIP) self.setWhatsThis(self.TOOLTIP)
self.dateTimeChanged.connect(self.data_changed)
fmt = tweaks[self.TWEAK] fmt = tweaks[self.TWEAK]
if fmt is None: if fmt is None:
fmt = self.FMT fmt = self.FMT

View File

@ -17,6 +17,7 @@ from PyQt5.Qt import (Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton,
QSizePolicy, QFrame, QSize, QKeySequence, QMenu, QShortcut, QDialog) QSizePolicy, QFrame, QSize, QKeySequence, QMenu, QShortcut, QDialog)
from calibre.constants import isosx from calibre.constants import isosx
from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.ebooks.metadata import authors_to_string, string_to_authors from calibre.ebooks.metadata import authors_to_string, string_to_authors
from calibre.gui2 import error_dialog, gprefs, pixmap_to_data from calibre.gui2 import error_dialog, gprefs, pixmap_to_data
from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit, from calibre.gui2.metadata.basic_widgets import (TitleEdit, AuthorsEdit,
@ -56,6 +57,7 @@ class MetadataSingleDialogBase(QDialog):
def __init__(self, db, parent=None, editing_multiple=False): def __init__(self, db, parent=None, editing_multiple=False):
self.db = db self.db = db
self.was_data_edited = False
self.changed = set() self.changed = set()
self.books_to_refresh = set() self.books_to_refresh = set()
self.rows_to_refresh = set() self.rows_to_refresh = set()
@ -292,6 +294,8 @@ class MetadataSingleDialogBase(QDialog):
self.config_metadata_button.clicked.connect(self.configure_metadata) self.config_metadata_button.clicked.connect(self.configure_metadata)
self.config_metadata_button.setToolTip( self.config_metadata_button.setToolTip(
_('Change how calibre downloads metadata')) _('Change how calibre downloads metadata'))
for w in self.basic_metadata_widgets:
w.data_changed.connect(self.data_changed)
# }}} # }}}
@ -320,6 +324,7 @@ class MetadataSingleDialogBase(QDialog):
two_column=self.cc_two_column) two_column=self.cc_two_column)
self.__custom_col_layouts = [layout] self.__custom_col_layouts = [layout]
for widget in self.custom_metadata_widgets: for widget in self.custom_metadata_widgets:
widget.connect_data_changed(self.data_changed)
if isinstance(widget, Comments): if isinstance(widget, Comments):
self.comments_edit_state_at_apply[widget] = None self.comments_edit_state_at_apply[widget] = None
# }}} # }}}
@ -353,6 +358,9 @@ class MetadataSingleDialogBase(QDialog):
def do_layout(self): def do_layout(self):
raise NotImplementedError() raise NotImplementedError()
def data_changed(self):
self.was_data_edited = True
def __call__(self, id_): def __call__(self, id_):
self.book_id = id_ self.book_id = id_
self.books_to_refresh = set([]) self.books_to_refresh = set([])
@ -363,6 +371,7 @@ class MetadataSingleDialogBase(QDialog):
widget.initialize(id_) widget.initialize(id_)
if callable(self.set_current_callback): if callable(self.set_current_callback):
self.set_current_callback(id_) self.set_current_callback(id_)
self.was_data_edited = False
# Commented out as it doesn't play nice with Next, Prev buttons # Commented out as it doesn't play nice with Next, Prev buttons
# self.fetch_metadata_button.setFocus(Qt.OtherFocusReason) # self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)
@ -620,6 +629,10 @@ class MetadataSingleDialogBase(QDialog):
def reject(self): def reject(self):
self.save_state() self.save_state()
if self.was_data_edited and not confirm(
title=_('Are you sure?'), name='confirm-cancel-edit-single-metadata', msg=_(
'You will lose all unsaved changes, are you sure?'), parent=self):
return
QDialog.reject(self) QDialog.reject(self)
def save_state(self): def save_state(self):