Skip to content

Commit d778319

Browse files
committed
typing fix in het_colormap and added activable editor
1 parent d228fa2 commit d778319

5 files changed

Lines changed: 63 additions & 39 deletions

File tree

plotpy/mathutils/colormaps.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
import json
1212
import os
13-
from typing import Sequence
13+
from typing import Dict, Sequence
1414

1515
import numpy as np
1616
import qtpy.QtGui as QG
@@ -24,6 +24,8 @@
2424
FULLRANGE = QwtInterval(0.0, 1.0)
2525
DEFAULT = EditableColormap(name="jet")
2626

27+
CmapDictType = Dict[str, EditableColormap]
28+
2729

2830
def load_raw_colormaps_from_json(
2931
json_path: str,
@@ -48,7 +50,7 @@ def load_raw_colormaps_from_json(
4850
return {}
4951

5052

51-
def load_qwt_colormaps_from_json(json_path: str) -> dict[str, EditableColormap]:
53+
def load_qwt_colormaps_from_json(json_path: str) -> CmapDictType:
5254
"""Same as function load_raw_colormaps_from_json but transforms the raw colormaps
5355
into CustomQwtLinearColormap objects that are used by plotpy.
5456
@@ -65,7 +67,7 @@ def load_qwt_colormaps_from_json(json_path: str) -> dict[str, EditableColormap]:
6567
}
6668

6769

68-
def save_colormaps(json_filename: str, colormaps: dict[str, EditableColormap]):
70+
def save_colormaps(json_filename: str, colormaps: CmapDictType):
6971
"""Saves colormaps into the given json file. Refer ton function get_cmap_path to
7072
know what json_filename can be used.
7173
@@ -126,6 +128,24 @@ def get_cmap(cmap_name: str) -> EditableColormap:
126128
return ALL_COLORMAPS.get(cmap_name.lower(), DEFAULT)
127129

128130

131+
def cmap_exists(cmap_name: str, cmap_dict: CmapDictType | None = None) -> bool:
132+
"""Returns True if the colormap with the given name exists in the given colormap
133+
dictionary, False otherwise. If no dictionary is given, the ALL_COLORMAPS global
134+
variable is used.
135+
136+
Args:
137+
cmap_name: colormap name to search in given colormap dictionnary. All keys in
138+
the dictionary are lower case, so the given name is also lowered.
139+
cmap_dict: colormap dictionnary to search in. If None, ALL_COLORMAPS is used.
140+
141+
Returns:
142+
True if the colormap exists, False otherwise.
143+
"""
144+
if cmap_dict is None:
145+
cmap_dict = ALL_COLORMAPS
146+
return cmap_name.lower() in cmap_dict
147+
148+
129149
def add_cmap(cmap: EditableColormap) -> None:
130150
"""Adds the given colormap to both ALL_COLORMAPS and CUSTOM_COLORMAPS global
131151
variables.
@@ -186,8 +206,8 @@ def get_cmap_path(config_path: str):
186206
)
187207

188208
# Load default and custom colormaps from json files
189-
DEFAULT_COLORMAPS = load_qwt_colormaps_from_json(DEFAULT_COLORMAPS_PATH)
190-
CUSTOM_COLORMAPS = load_qwt_colormaps_from_json(CUSTOM_COLORMAPS_PATH)
209+
DEFAULT_COLORMAPS: CmapDictType = load_qwt_colormaps_from_json(DEFAULT_COLORMAPS_PATH)
210+
CUSTOM_COLORMAPS: CmapDictType = load_qwt_colormaps_from_json(CUSTOM_COLORMAPS_PATH)
191211

192212
# Merge default and custom colormaps into a single dictionnary to simplify access
193-
ALL_COLORMAPS = {**DEFAULT_COLORMAPS, **CUSTOM_COLORMAPS}
213+
ALL_COLORMAPS: CmapDictType = {**DEFAULT_COLORMAPS, **CUSTOM_COLORMAPS}

plotpy/tests/features/test_colormap_manager.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def test_colormap_manager() -> None:
3737
result = dlg.exec()
3838
execenv.print("Dialog result:", result)
3939
cmap = dlg.get_colormap()
40-
execenv.print("Selected colormap:", cmap.name)
40+
execenv.print("Selected colormap:", None if cmap is None else cmap.name)
4141

4242

4343
if __name__ == "__main__":

plotpy/tools/image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,8 @@ def activate_command(self, plot: BasePlot, checked: bool) -> None:
473473
manager = ColorMapManager(
474474
plot.parent(), active_colormap=self._active_colormap.name
475475
)
476-
if exec_dialog(manager):
477-
self.activate_cmap(manager.get_colormap())
476+
if exec_dialog(manager) and (cmap := manager.get_colormap()) is not None:
477+
self.activate_cmap(cmap)
478478

479479
def get_selected_images(self, plot: BasePlot) -> list[IBasePlotItem]:
480480
"""Returns the currently selected images in the given plot.

plotpy/widgets/colormap/manager.py

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
DEFAULT_COLORMAPS,
2626
add_cmap,
2727
build_icon_from_cmap,
28+
cmap_exists,
29+
get_cmap,
2830
)
2931
from plotpy.widgets.colormap.editor import ColorMapEditor
3032
from plotpy.widgets.colormap.widget import EditableColormap
@@ -110,11 +112,12 @@ def __init__(
110112

111113
self.__returned_colormap: EditableColormap | None = None
112114

113-
if active_colormap is None or active_colormap.lower() not in ALL_COLORMAPS:
115+
if active_colormap is None or not cmap_exists(active_colormap, ALL_COLORMAPS):
114116
active_colormap = next(iter(ALL_COLORMAPS))
115117

116118
# Select the active colormap
117119
self._cmap_choice = QW.QComboBox()
120+
self._cmap_choice.setMaxVisibleItems(15)
118121
for cmap in ALL_COLORMAPS.values():
119122
icon = build_icon_from_cmap(cmap)
120123
self._cmap_choice.addItem(icon, cmap.name, cmap)
@@ -140,6 +143,9 @@ def __init__(
140143
edit_gbox_layout.setContentsMargins(0, 0, 0, 0)
141144
edit_gbox_layout.addWidget(self.colormap_editor)
142145
edit_gbox.setLayout(edit_gbox_layout)
146+
edit_gbox.setCheckable(True)
147+
edit_gbox.setChecked(False)
148+
new_btn.clicked.connect(lambda: edit_gbox.setChecked(True))
143149
self.colormap_editor.colormap_widget.COLORMAP_CHANGED.connect(
144150
self._changes_not_saved
145151
)
@@ -153,7 +159,7 @@ def __init__(
153159
)
154160
self._changes_saved = True
155161
self._save_btn = self.bbox.button(QW.QDialogButtonBox.Save)
156-
self._save_btn.setEnabled(False)
162+
self._save_btn.setEnabled(False) # type: ignore
157163
self.bbox.clicked.connect(self.button_clicked)
158164

159165
dialog_layout = QW.QVBoxLayout()
@@ -181,7 +187,7 @@ def button_clicked(self, button: QW.QAbstractButton) -> None:
181187
def _changes_not_saved(self) -> None:
182188
"""Callback function to be called when the colormap is modified. Enables the
183189
save button and sets the current_changes_saved attribute to False."""
184-
self._save_btn.setEnabled(True)
190+
self._save_btn.setEnabled(True) # type: ignore
185191
self._changes_saved = False
186192

187193
@property
@@ -203,11 +209,11 @@ def set_colormap(self, index: int) -> None:
203209
"""
204210
cmap_copy: EditableColormap = deepcopy(self._cmap_choice.itemData(index))
205211
self.colormap_editor.set_colormap(cmap_copy)
206-
is_new_colormap = cmap_copy.name.lower() not in ALL_COLORMAPS
212+
is_new_colormap = not cmap_exists(cmap_copy.name, CUSTOM_COLORMAPS)
207213
self._changes_saved = True
208-
self._save_btn.setEnabled(is_new_colormap)
214+
self._save_btn.setEnabled(is_new_colormap) # type: ignore
209215

210-
def get_colormap(self) -> EditableColormap:
216+
def get_colormap(self) -> EditableColormap | None:
211217
"""Return the selected colormap object.
212218
213219
Returns:
@@ -231,7 +237,7 @@ def __get_new_colormap_name(self, title: str, name: str) -> str | None:
231237
new_name = ColorMapNameEdit.edit(self, new_name)
232238
if new_name is None:
233239
return None
234-
if new_name.lower() in DEFAULT_COLORMAPS:
240+
if cmap_exists(new_name, DEFAULT_COLORMAPS):
235241
QW.QMessageBox.warning(
236242
self,
237243
title,
@@ -243,22 +249,21 @@ def __get_new_colormap_name(self, title: str, name: str) -> str | None:
243249
% new_name,
244250
)
245251
continue
246-
if new_name.lower() in CUSTOM_COLORMAPS:
247-
if (
248-
QW.QMessageBox.question(
249-
self,
250-
title,
251-
_(
252-
"Name <b>%s</b> is already used by a custom colormap.<br><br>"
253-
"Do you want to overwrite it?"
254-
)
255-
% new_name,
256-
QW.QMessageBox.Yes | QW.QMessageBox.No,
257-
QW.QMessageBox.No,
252+
if cmap_exists(new_name, CUSTOM_COLORMAPS) and (
253+
QW.QMessageBox.question(
254+
self,
255+
title,
256+
_(
257+
"Name <b>%s</b> is already used by a custom colormap.<br><br>"
258+
"Do you want to overwrite it?"
258259
)
259-
== QW.QMessageBox.No
260-
):
261-
continue
260+
% new_name,
261+
QW.QMessageBox.Yes | QW.QMessageBox.No,
262+
QW.QMessageBox.No,
263+
)
264+
== QW.QMessageBox.No
265+
):
266+
continue
262267
break
263268
return new_name
264269

@@ -287,13 +292,13 @@ def save_colormap(self, cmap: EditableColormap | None = None) -> bool:
287292
return False
288293

289294
# Before modifying CUSTOM_COLORMAPS, save this boolean expression:
290-
is_existing_custom_cmap = new_name.lower() in CUSTOM_COLORMAPS
295+
is_existing_custom_cmap = cmap_exists(new_name, CUSTOM_COLORMAPS)
291296

292297
# Take into account the case where the color map exists in custom colormaps
293298
# but not with the same case (e.g. "viridis" vs "Viridis"). So we need to
294299
# get the original name of the colormap:
295300
if is_existing_custom_cmap:
296-
new_name = CUSTOM_COLORMAPS[new_name.lower()].name
301+
new_name = get_cmap(new_name).name
297302

298303
cmap.name = new_name
299304
add_cmap(cmap)
@@ -308,7 +313,7 @@ def save_colormap(self, cmap: EditableColormap | None = None) -> bool:
308313
self._cmap_choice.addItem(icon, new_name, cmap)
309314
self._cmap_choice.setCurrentText(new_name)
310315

311-
self._save_btn.setEnabled(False)
316+
self._save_btn.setEnabled(False) # type: ignore
312317
self._changes_saved = True
313318
return True
314319

plotpy/widgets/colormap/widget.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,16 +36,15 @@ class EditableColormap(QwtLinearColorMap):
3636
Args:
3737
*args: QwtLinearColorMap arguments
3838
name: Optional str name given to the colormap. Useful for the interactions
39-
with the rest of PlotPy, notably with the gobal colormaps dictionnary
40-
such as colormaps.ALL_COLORMAPS. Defaults to "None".
39+
with the rest of PlotPy, notably with the gobal colormaps dictionaries
40+
such as colormaps.ALL_COLORMAPS. If None, the colormap name will be set to
41+
"temporary". Defaults to None.
4142
"""
4243

4344
def __init__(self, *args, name: str | None = None) -> None:
4445
super().__init__(*args)
4546
# TODO: Add this feature in a release of QwtPython
46-
self.stops: list[
47-
ColorStop
48-
] = (
47+
self.stops: list[ColorStop] = (
4948
self._QwtLinearColorMap__data.colorStops._ColorStops__stops # pylint: disable=no-member # type: ignore
5049
)
5150
self.name = name or "temporary"

0 commit comments

Comments
 (0)