-
Notifications
You must be signed in to change notification settings - Fork 80
created LegendIconWidget a similar LegendIcon #4404
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@abmajith We introduced |
d1d333b
to
8363a49
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First round of feedbacks, here are the 2 main ones:
LegendItemList
should make minimal changes (add/remove) on the list rather than re-creating it on changes.- It seems
LegendItemIcon
andLegendItemWidget
are having similar logic, maybe best not to inherit fromLegendIconWidget
and implement the update logic at a single place inLegendItemWidget
?
src/silx/gui/plot/LegendItem.py
Outdated
self.setFixedWidth(200) | ||
self._layout = qt.QVBoxLayout(self) | ||
self._plot: PlotWidget | None = None | ||
self._binding(parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PlotWidget that is "observed" by the list of legends is not the "parent": "parent" is Qt widget tree structure, the plot can be anywhere in it.
Best to provide get|setPlotWidget
methods to set it separately, and eventually pass it in __init__
:
def __init__(
self,
parent: qt.QWidget | None = None,
plotWidget: PlotWidget | None = None,
):
src/silx/gui/plot/LegendItem.py
Outdated
if hasattr(parent, "sigVisibilityChanged"): | ||
parent.sigVisibilityChanged.connect(self._setVisibility) | ||
|
||
def _setVisibility(self, status: bool) -> None: | ||
if status: | ||
self.show() | ||
else: | ||
self.hide() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This widget shouldn't need to care about this, Qt does it already:
if hasattr(parent, "sigVisibilityChanged"): | |
parent.sigVisibilityChanged.connect(self._setVisibility) | |
def _setVisibility(self, status: bool) -> None: | |
if status: | |
self.show() | |
else: | |
self.hide() |
src/silx/gui/plot/LegendItem.py
Outdated
self._plot.sigActiveCurveChanged.connect(self._onActiveItemChanged) | ||
self._plot.sigActiveImageChanged.connect(self._onActiveItemChanged) | ||
self._plot.sigActiveScatterChanged.connect(self._onActiveItemChanged) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "active" state of the items is not used in _onActiveItemChanged
, so no need to handle those signals
self._plot.sigActiveCurveChanged.connect(self._onActiveItemChanged) | |
self._plot.sigActiveImageChanged.connect(self._onActiveItemChanged) | |
self._plot.sigActiveScatterChanged.connect(self._onActiveItemChanged) |
src/silx/gui/plot/LegendItem.py
Outdated
self._plot.sigItemAdded.connect(self._onContentChanged) | ||
self._plot.sigItemRemoved.connect(self._onContentChanged) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for wrapper method:
self._plot.sigItemAdded.connect(self._onContentChanged) | |
self._plot.sigItemRemoved.connect(self._onContentChanged) | |
self._plot.sigItemAdded.connect(self._updateAllItemsList) | |
self._plot.sigItemRemoved.connect(self._updateAllItemsList) |
But here, we don't want to redo the whole list each time an item is added/removed, just add/remove the corresponding LegendItemWidget
src/silx/gui/plot/LegendItem.py
Outdated
self._updateHeigth() | ||
|
||
def _updateHeigth(self): | ||
_totalHeight = (self._layout.count() - 1) * self._layout.spacing() | ||
_totalHeight += self._layout.contentsMargins().top() | ||
_totalHeight += self._layout.contentsMargins().bottom() | ||
if self._layout.count() > 0: | ||
item = self._layout.itemAt(0) | ||
_totalHeight += item.widget().sizeHint().height() * self._layout.count() | ||
self.setFixedHeight(_totalHeight) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is issue with layout, it should be dealt otherwise.
It's always risky to play with this, for instance, the text of the legend is clipped on my laptop.
src/silx/gui/plot/LegendItem.py
Outdated
def __init__(self, parent: PlotWidget): | ||
super().__init__(parent, qt.Qt.Window) | ||
self.setFixedWidth(200) | ||
self._layout = qt.QVBoxLayout(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to keep a reference on the layout, it can be retrieved with self.layout()
src/silx/gui/plot/LegendItem.py
Outdated
|
||
class LegendItemList(qt.QWidget): | ||
def __init__(self, parent: PlotWidget): | ||
super().__init__(parent, qt.Qt.Window) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either pass flags from __init__
or use the default:
super().__init__(parent, qt.Qt.Window) | |
super().__init__(parent) |
src/silx/gui/plot/LegendItem.py
Outdated
class LegendItemList(qt.QWidget): | ||
def __init__(self, parent: PlotWidget): | ||
super().__init__(parent, qt.Qt.Window) | ||
self.setFixedWidth(200) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do use fixed size, best to let Qt handles it: rendering changes depending on OS & screen...
src/silx/gui/plot/LegendItem.py
Outdated
super().__init__(parent, qt.Qt.Window) | ||
self.setFixedWidth(200) | ||
self._layout = qt.QVBoxLayout(self) | ||
self._plot: PlotWidget | None = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best to keep a weakreference
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the weekref not pointing to the item attribute class, it throws error, for example when i call ite,getName(), throws no methods called getName() something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you be more explicit?
Without the code and the complete error message, it is impossible to help.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
python3 examples/plotLegendLists.py
Traceback (most recent call last):
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 162, in _updateAllItemsList
self._onItemAdded(item)
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 171, in _onItemAdded
legendIcon = LegendItemWidget(None, item)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 29, in __init__
self._label = qt.QLabel(self._item.getName())
^^^^^^^^^^^^^^^^^^
AttributeError: 'weakref.ReferenceType' object has no attribute 'getName'
Traceback (most recent call last):
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 162, in _updateAllItemsList
self._onItemAdded(item)
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 171, in _onItemAdded
legendIcon = LegendItemWidget(None, item)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 29, in __init__
self._label = qt.QLabel(self._item.getName())
^^^^^^^^^^^^^^^^^^
AttributeError: 'weakref.ReferenceType' object has no attribute 'getName'
Traceback (most recent call last):
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 162, in _updateAllItemsList
self._onItemAdded(item)
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 171, in _onItemAdded
legendIcon = LegendItemWidget(None, item)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/noordhee/DAU_Projects/silx/src/silx/gui/plot/LegendItem.py", line 29, in __init__
self._label = qt.QLabel(self._item.getName())
^^^^^^^^^^^^^^^^^^
AttributeError: 'weakref.ReferenceType' object has no attribute 'getName'
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
when i store the item with weakref, this is the error i got
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weakref.ref
cannot be accessed as the item it references.
You have to "call" it to get back the item:
myweakref = weakref.ref(myitem)
...
item = myweakref()
if item is not None: # It returns None if the item has been deleted
item.getName()
An alternative is to use weakref.proxy
, but best IMO to know if the object still exists
Look at: https://docs.python.org/3/library/weakref.html#weak-reference-objects
src/silx/gui/plot/LegendItem.py
Outdated
self.layout().addStretch() | ||
|
||
self.label.setToolTip("Click to toggle visibility") | ||
self.label.mousePressEvent = self._onLabelClicked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a good idea to "monkey patch" a Qt method.
Since it would make sense to also accept the press on the icon, you can implement mousePressEvent
in this class.
Yes, i think add/removal should be minimum, and also we the second point, making single class for icon and widget, |
…dget (that shows name and legen item symbol in a row)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Second round of comments:
Much simpler with a single LegendItemWidget, ch and update only on add/remove!
There's something to check about removing/clearing widget, this needs to be robust: it should not let widgets alive that are no longer displayed.
There's a few comments from last review that where not taken into account nor commented. Before requesting a new review, can you address all comments, either by applying them if you agree or by commenting if you don't agree or need clarifications.
src/silx/gui/plot/LegendItem.py
Outdated
|
||
|
||
class LegendItemWidget(qt.QWidget): | ||
def __init__(self, parent, item: items): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
items
is a module, not a class
def __init__(self, parent, item: items): | |
def __init__(self, parent, item: items.Item): |
src/silx/gui/plot/LegendItem.py
Outdated
def _clearLayout(self, layout): | ||
"""Helper to clear all widgets from a layout.""" | ||
if layout is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to pass the layout as an argument:
def _clearLayout(self, layout): | |
"""Helper to clear all widgets from a layout.""" | |
if layout is not None: | |
def _clear(self): | |
"""Helper to clear all widgets from a layout.""" | |
layout = self.layout() | |
if layout is not None: |
- this does not clear
self._itemWidgets
src/silx/gui/plot/LegendItem.py
Outdated
if self._plot is None: | ||
return | ||
self._clearLayout(self.layout()) | ||
_activeItem: tuple = self._plot.getItems() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No _
prefix for local variables (there's others in this PR).
Already mentionned in #4404 (comment)
src/silx/gui/plot/LegendItem.py
Outdated
|
||
def _onItemRemoved(self, item: items.Item): | ||
if item in self._itemWidgets: | ||
self.layout().removeWidget(self._itemWidgets[item]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this enough to fully remove the widget? What is it's parent
after the removal?
If it's the LegendItemList
then it's not deleted.
Either this is enough and there is no need for deletedLater
in _clearLayout
or it's fully destroying the widget and something like in _clearLayout
should be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay for the consistency, I will follow the same mechanism as in _clearLayout
self.layout().addStretch() | ||
|
||
self._label.setToolTip("Click to toggle visibility") | ||
self._label.mousePressEvent = self._onLabelClicked |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Already mentioned in #4404 (comment)
This is not a good pattern. you can implement mousePressEvent
in this class instead
src/silx/gui/plot/LegendItem.py
Outdated
|
||
self._label.setToolTip("Click to toggle visibility") | ||
self._label.mousePressEvent = self._onLabelClicked | ||
self._label.setCursor(qt.Qt.PointingHandCursor) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can set it for the whole widget
self._label.setCursor(qt.Qt.PointingHandCursor) | |
self.setCursor(qt.Qt.PointingHandCursor) |
if isinstance(item, items.SymbolMixIn): | ||
self._icon.setSymbol(item.getSymbol()) | ||
if isinstance(item, items.LineMixIn): | ||
self._icon.setLineWidth(item.getLineWidth()) | ||
self._icon.setLineStyle(item.getLineStyle()) | ||
if isinstance(item, items.ColorMixIn): | ||
color_rgba = item.getColor() | ||
color = qt.QColor.fromRgbF( | ||
color_rgba[0], | ||
color_rgba[1], | ||
color_rgba[2], | ||
color_rgba[3], | ||
) | ||
self._icon.setLineColor(color) | ||
self._icon.setSymbolColor(color) | ||
if isinstance(item, items.ColormapMixIn): | ||
self._icon.setColormap(item.getColormap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
items.ItemChangedType.VISIBLE, | ||
items.ItemChangedType.HIGHLIGHTED, | ||
items.ItemChangedType.NAME, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Quite a few change events seems to be missing here: line, symbol, color...
Hi @t20100 I worked on your review, I have a problem with weakref, (provided the error message), |
Hi @t20100, I tested the LegendItem gui, there is one discomfort right now, When I am closing plot window that linked to LegendItem (as in this example/plotLegendItem), the legendItem has to be closed separately, Is this a problem, or we have to set up the parent widget to plot window so that it will automatically closed? |
In this pull request, created a unique LegendIcon, and LegendItem widget that is listed in vertical in qt.MainWindow,
the respective example are provided, but so far no unit test,
also I still not sure, whether I added some what generic use case to the LegendItem widget,