-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcommon_widgets.py
330 lines (277 loc) · 11.8 KB
/
common_widgets.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2022, Grant Drake'
from six import text_type as unicode
try:
from qt.core import (Qt, QTableWidgetItem, QComboBox, QHBoxLayout, QLabel, QFont,
QDateTime, QStyledItemDelegate, QLineEdit)
except ImportError:
from PyQt5.Qt import (Qt, QTableWidgetItem, QComboBox, QHBoxLayout, QLabel, QFont,
QDateTime, QStyledItemDelegate, QLineEdit)
try:
load_translations()
except NameError:
pass # load_translations() added in calibre 1.9
from calibre.gui2 import error_dialog, UNDEFINED_QDATETIME
from calibre.utils.date import now, format_date, UNDEFINED_DATE
from calibre_plugins.find_duplicates.common_icons import get_pixmap
# get_date_format
#
# CheckableTableWidgetItem
# DateDelegate
# DateTableWidgetItem
# ImageTitleLayout
# ReadOnlyTableWidgetItem
# ReadOnlyTextIconWidgetItem
# ReadOnlyCheckableTableWidgetItem
# TextIconWidgetItem
#
# CustomColumnComboBox
# KeyValueComboBox
# NoWheelComboBox
# ReadOnlyLineEdit
# ----------------------------------------------
# Functions
# ----------------------------------------------
def get_date_format(tweak_name='gui_timestamp_display_format', default_fmt='dd MMM yyyy'):
from calibre.utils.config import tweaks
format = tweaks[tweak_name]
if format is None:
format = default_fmt
return format
# ----------------------------------------------
# Widgets
# ----------------------------------------------
class CheckableTableWidgetItem(QTableWidgetItem):
'''
For use in a table cell, displays a checkbox that can potentially be tristate
'''
def __init__(self, checked=False, is_tristate=False):
super(CheckableTableWidgetItem, self).__init__('')
try:
self.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled )
except:
self.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled ))
if is_tristate:
self.setFlags(self.flags() | Qt.ItemFlag.ItemIsUserTristate)
if checked:
self.setCheckState(Qt.Checked)
else:
if is_tristate and checked is None:
self.setCheckState(Qt.PartiallyChecked)
else:
self.setCheckState(Qt.Unchecked)
def get_boolean_value(self):
'''
Return a boolean value indicating whether checkbox is checked
If this is a tristate checkbox, a partially checked value is returned as None
'''
if self.checkState() == Qt.PartiallyChecked:
return None
else:
return self.checkState() == Qt.Checked
from calibre.gui2.library.delegates import DateDelegate as _DateDelegate
class DateDelegate(_DateDelegate):
'''
Delegate for dates. Because this delegate stores the
format as an instance variable, a new instance must be created for each
column. This differs from all the other delegates.
'''
def __init__(self, parent, fmt='dd MMM yyyy', default_to_today=True):
super(DateDelegate, self).__init__(parent)
self.default_to_today = default_to_today
self.format = get_date_format(default_fmt=fmt)
def createEditor(self, parent, option, index):
qde = QStyledItemDelegate.createEditor(self, parent, option, index)
qde.setDisplayFormat(self.format)
qde.setMinimumDateTime(UNDEFINED_QDATETIME)
qde.setSpecialValueText(_('Undefined'))
qde.setCalendarPopup(True)
return qde
def setEditorData(self, editor, index):
val = index.model().data(index, Qt.DisplayRole)
if val is None or val == UNDEFINED_QDATETIME:
if self.default_to_today:
val = self.default_date
else:
val = UNDEFINED_QDATETIME
editor.setDateTime(val)
def setModelData(self, editor, model, index):
val = editor.dateTime()
if val <= UNDEFINED_QDATETIME:
model.setData(index, UNDEFINED_QDATETIME, Qt.EditRole)
else:
model.setData(index, QDateTime(val), Qt.EditRole)
class DateTableWidgetItem(QTableWidgetItem):
def __init__(self, date_read, is_read_only=False, default_to_today=False, fmt=None):
if date_read is None or date_read == UNDEFINED_DATE and default_to_today:
date_read = now()
if is_read_only:
super(DateTableWidgetItem, self).__init__(format_date(date_read, fmt))
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
self.setData(Qt.DisplayRole, QDateTime(date_read))
else:
super(DateTableWidgetItem, self).__init__('')
self.setData(Qt.DisplayRole, QDateTime(date_read))
class ImageTitleLayout(QHBoxLayout):
'''
A reusable layout widget displaying an image followed by a title
'''
def __init__(self, parent, icon_name, title):
super(ImageTitleLayout, self).__init__()
self.title_image_label = QLabel(parent)
self.update_title_icon(icon_name)
self.addWidget(self.title_image_label)
title_font = QFont()
title_font.setPointSize(16)
shelf_label = QLabel(title, parent)
shelf_label.setFont(title_font)
self.addWidget(shelf_label)
self.insertStretch(-1)
def update_title_icon(self, icon_name):
pixmap = get_pixmap(icon_name)
if pixmap is None:
error_dialog(self.parent(), _('Restart required'),
_('Title image not found - you must restart Calibre before using this plugin!'), show=True)
else:
self.title_image_label.setPixmap(pixmap)
self.title_image_label.setMaximumSize(32, 32)
self.title_image_label.setScaledContents(True)
class ReadOnlyTableWidgetItem(QTableWidgetItem):
'''
For use in a table cell, displays text the user cannot select or modify.
'''
def __init__(self, text):
if text is None:
text = ''
super(ReadOnlyTableWidgetItem, self).__init__(text)
self.setFlags(Qt.ItemIsSelectable|Qt.ItemIsEnabled)
class ReadOnlyTextIconWidgetItem(ReadOnlyTableWidgetItem):
'''
For use in a table cell, displays an icon the user cannot select or modify.
'''
def __init__(self, text, icon):
super(ReadOnlyTextIconWidgetItem, self).__init__(text)
if icon:
self.setIcon(icon)
class ReadOnlyCheckableTableWidgetItem(ReadOnlyTableWidgetItem):
'''
For use in a table cell, displays a checkbox next to some text the user cannot select or modify.
'''
def __init__(self, text, checked=False, is_tristate=False):
super(ReadOnlyCheckableTableWidgetItem, self).__init__(text)
try: # For Qt Backwards compatibility.
self.setFlags(Qt.ItemFlag.ItemIsSelectable | Qt.ItemFlag.ItemIsUserCheckable | Qt.ItemFlag.ItemIsEnabled )
except:
self.setFlags(Qt.ItemFlags(Qt.ItemIsSelectable | Qt.ItemIsUserCheckable | Qt.ItemIsEnabled ))
if is_tristate:
self.setFlags(self.flags() | Qt.ItemIsTristate)
if checked:
self.setCheckState(Qt.Checked)
else:
if is_tristate and checked is None:
self.setCheckState(Qt.PartiallyChecked)
else:
self.setCheckState(Qt.Unchecked)
def get_boolean_value(self):
'''
Return a boolean value indicating whether checkbox is checked
If this is a tristate checkbox, a partially checked value is returned as None
'''
if self.checkState() == Qt.PartiallyChecked:
return None
else:
return self.checkState() == Qt.Checked
class TextIconWidgetItem(QTableWidgetItem):
'''
For use in a table cell, displays text with an icon next to it.
'''
def __init__(self, text, icon):
super(TextIconWidgetItem, self).__init__(text)
self.setIcon(icon)
# ----------------------------------------------
# Controls
# ----------------------------------------------
class CustomColumnComboBox(QComboBox):
CREATE_NEW_COLUMN_ITEM = _("Create new column")
def __init__(self, parent, custom_columns={}, selected_column='', initial_items=[''], create_column_callback=None):
super(CustomColumnComboBox, self).__init__(parent)
self.create_column_callback = create_column_callback
self.current_index = 0
if create_column_callback is not None:
self.currentTextChanged.connect(self.current_text_changed)
self.populate_combo(custom_columns, selected_column, initial_items)
def populate_combo(self, custom_columns, selected_column, initial_items=[''], show_lookup_name=True):
self.clear()
self.column_names = []
selected_idx = 0
if isinstance(initial_items, dict):
for key in sorted(initial_items.keys()):
self.column_names.append(key)
display_name = initial_items[key]
self.addItem(display_name)
if key == selected_column:
selected_idx = len(self.column_names) - 1
else:
for display_name in initial_items:
self.column_names.append(display_name)
self.addItem(display_name)
if display_name == selected_column:
selected_idx = len(self.column_names) - 1
for key in sorted(custom_columns.keys()):
self.column_names.append(key)
display_name = '%s (%s)'%(key, custom_columns[key]['name']) if show_lookup_name else custom_columns[key]['name']
self.addItem(display_name)
if key == selected_column:
selected_idx = len(self.column_names) - 1
if self.create_column_callback is not None:
self.addItem(self.CREATE_NEW_COLUMN_ITEM)
self.column_names.append(self.CREATE_NEW_COLUMN_ITEM)
self.setCurrentIndex(selected_idx)
def get_selected_column(self):
selected_column = self.column_names[self.currentIndex()]
if selected_column == self.CREATE_NEW_COLUMN_ITEM:
selected_column = None
return selected_column
def current_text_changed(self, new_text):
if new_text == self.CREATE_NEW_COLUMN_ITEM:
result = self.create_column_callback()
if not result:
self.setCurrentIndex(self.current_index)
else:
self.current_index = self.currentIndex()
class KeyValueComboBox(QComboBox):
def __init__(self, parent, values, selected_key):
QComboBox.__init__(self, parent)
self.values = values
self.populate_combo(selected_key)
def populate_combo(self, selected_key):
self.clear()
selected_idx = idx = -1
for key, value in self.values.items():
idx = idx + 1
self.addItem(value)
if key == selected_key:
selected_idx = idx
self.setCurrentIndex(selected_idx)
def selected_key(self):
for key, value in self.values.items():
if value == unicode(self.currentText()).strip():
return key
class NoWheelComboBox(QComboBox):
'''
For combobox displayed in a table cell using the mouse wheel has nasty interactions
due to the conflict between scrolling the table vs scrolling the combobox item.
Inherit from this class to disable the combobox changing value with mouse wheel.
'''
def wheelEvent(self, event):
event.ignore()
class ReadOnlyLineEdit(QLineEdit):
def __init__(self, text, parent):
if text is None:
text = ''
super(ReadOnlyLineEdit, self).__init__(text, parent)
self.setEnabled(False)