Skip to content
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

Refactor disable/enable logic for statusbar widgets #7407

Open
The-Compiler opened this issue Sep 22, 2022 · 7 comments
Open

Refactor disable/enable logic for statusbar widgets #7407

The-Compiler opened this issue Sep 22, 2022 · 7 comments
Assignees
Labels
component: style / refactoring Issues related to coding styles or code that should be refactored. priority: 2 - low Issues which are currently not very important.
Milestone

Comments

@The-Compiler
Copy link
Member

The-Compiler commented Sep 22, 2022

As evident in #7389, handling of enabling/disabling statusbar widgets is rather error-prone at the moment: bar.py needs to have special handling for some widgets which handle their visibility themselves (progress.py and backforward.py).

We should instead unify this somehow in the API a statusbar item exposes. One possibility would be to give them a .enable() and .disable() method, which then either just call self.hide() and self.show(), or instead set self._enabled accordingly.

First draft:

diff --git i/qutebrowser/mainwindow/statusbar/backforward.py w/qutebrowser/mainwindow/statusbar/backforward.py
index 76614a14f..d3cdf987f 100644
--- i/qutebrowser/mainwindow/statusbar/backforward.py
+++ w/qutebrowser/mainwindow/statusbar/backforward.py
@@ -28,7 +28,14 @@ class Backforward(textbase.TextBase):
 
     def __init__(self, parent=None):
         super().__init__(parent)
-        self.enabled = False
+        self._enabled = False
+
+    def enable(self):
+        self._enabled = True
+
+    def disable(self):
+        self._enabled = False
+        self.hide()
 
     def on_tab_cur_url_changed(self, tabs):
         """Called on URL changes."""
@@ -49,4 +56,4 @@ def on_tab_changed(self, tab):
         if text:
             text = '[' + text + ']'
         self.setText(text)
-        self.setVisible(bool(text) and self.enabled)
+        self.setVisible(bool(text) and self._enabled)
diff --git i/qutebrowser/mainwindow/statusbar/bar.py w/qutebrowser/mainwindow/statusbar/bar.py
index 7e5a8ff2c..451a1665b 100644
--- i/qutebrowser/mainwindow/statusbar/bar.py
+++ w/qutebrowser/mainwindow/statusbar/bar.py
@@ -262,13 +262,8 @@ def _draw_widgets(self):
             if segment == 'scroll_raw':
                 widget.set_raw()
             elif segment in ('history', 'progress'):
-                widget.enabled = True
                 if tab:
                     widget.on_tab_changed(tab)
-
-                # Do not call .show() for these widgets. They are not always shown, and
-                # dynamically show/hide themselves in their on_tab_changed() methods.
-                continue
             elif segment.startswith('text:'):
                 widget.setText(segment.split(':', maxsplit=1)[1])
             elif segment.startswith('clock:') or segment == 'clock':
@@ -278,7 +273,7 @@ def _draw_widgets(self):
                 else:
                     widget.format = '%X'
 
-            widget.show()
+            widget.enable()
 
     def _clear_widgets(self):
         """Clear widgets before redrawing them."""
@@ -287,9 +282,7 @@ def _clear_widgets(self):
                        self.backforward, self.tabindex,
                        self.keystring, self.prog, self.clock, *self._text_widgets]:
             assert isinstance(widget, QWidget)
-            if widget in [self.prog, self.backforward]:
-                widget.enabled = False  # type: ignore[attr-defined]
-            widget.hide()
+            widget.disable()
             self._hbox.removeWidget(widget)
         self._text_widgets.clear()
 
diff --git i/qutebrowser/mainwindow/statusbar/progress.py w/qutebrowser/mainwindow/statusbar/progress.py
index 9d56cd03f..4a49f32f5 100644
--- i/qutebrowser/mainwindow/statusbar/progress.py
+++ w/qutebrowser/mainwindow/statusbar/progress.py
@@ -46,11 +46,18 @@ class Progress(QProgressBar):
     def __init__(self, parent=None):
         super().__init__(parent)
         stylesheet.set_register(self)
-        self.enabled = False
+        self._enabled = False
         self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
         self.setTextVisible(False)
         self.hide()
 
+    def enable(self):
+        self._enabled = True
+
+    def disable(self):
+        self._enabled = False
+        self.hide()
+
     def __repr__(self):
         return utils.get_repr(self, value=self.value())
 

but the main problem with that approach is that we have a lot of copy-paste-code.

Would be nice to have a common base class for statusbar widgets, but they also inherit from whatever widget they want to display... Possible solutions:

  • Make widgets StatusbarItem subclasses, and have the widget as .widget instead of inheriting from it directly - somewhat similar to what we did for TabbedBrowser in e169e21 essentially. Seems like the cleanest option to me.
  • Simply have two different mixins for enable/disable handling, and perhaps a typing.Protocol to statically check that they are there in bar.py somehow
  • Use a miscwidgets.WrapperLayout like we do for browser tabs, so that statusbar items are widgets, but with our own API instead of the QWidget one.

cc @tomiesz @Nicholas42 in case you're interested!

@The-Compiler The-Compiler added component: style / refactoring Issues related to coding styles or code that should be refactored. priority: 2 - low Issues which are currently not very important. labels Sep 22, 2022
@tomiesz
Copy link
Contributor

tomiesz commented Sep 23, 2022

I'm not available for ~2 weeks from now. If nobody gets this done in the meantime I'd gladly take a stab at it.

@toofar
Copy link
Member

toofar commented Sep 24, 2022

Couple more notes:

  • @tomiesz already pointed on Fix for dissapearing Widgets #7390 that QWidget already has setEnabled() and isEnabled() methods, maybe we should be using them instead of having our own ones. I'm not sure if there are any complications around that stuff or not (the docs say that is for when the widget is disabled but maybe still visible (so greyed out), does that fit how we are using them? maybe)
  • I'm not sure if it's too big a bit to take on under the scope of just this issue but eventually I would like the StatusBar widget to know nothing about any individual status bar item. I think how I would tackle that would be
    • make all the individual widgets inherit from a common base class
    • add the hardcoded widgets to a list
    • in each place the hardcoded widgets are accessed at the moment turn that into a loop over the list that calls generic methods of the new base class, the child widgets would override these generic methods and do the work that's being done in the StatusBar class now
    • add a decorator so the individual items can be dynamically registered instead of having a list of them maintained in the bar file
    • think about adding the new base class and decorator into the extension API
    • the txt and cmd items might continue to need special treatment, or maybe the relationship between them can be pushed down into the command widget

@4nd3r
Copy link
Contributor

4nd3r commented Feb 13, 2023

Maybe #4894 can be also included in statusbar refactoring? Or at least lay the groundwork.

toofar added a commit to toofar/qutebrowser that referenced this issue Oct 6, 2023
In the initial implementation the widget had `show()` and `hide()`
called on it and we used `isVisible()` to tell whether we should update
the widget on events.

Since the widget showing in the statubar got refactored in https://github.com/qutebrowser/qutebrowser/pull/6448/files
the only other widgets that have an `on_tab_changed()` method don't get
get `.show()` called on them anymore, than handle that themselves with
the help of a `.enabled` attribute. Not 100% sure if it's the right
thing to do to lump this settings widget in with them just because it
also takes a tab (eg do the history and progress widgets handle showing
themselves because they hide when they aren't needed? The setting widget
is always visible when configured) but it's not big deal.

In general I want to refactor the statusbar to not have explicit
knowledge of the widgets it can show (eg have a common abstract class
for them with same defaults and helpers). In the meantime this is all
pretty self contained.

More words on that eventual refactoring here: qutebrowser#7407
toofar added a commit to toofar/qutebrowser that referenced this issue Oct 6, 2023
In the initial implementation the widget had `show()` and `hide()`
called on it and we used `isVisible()` to tell whether we should update
the widget on events.

Since the widget showing in the statubar got refactored in https://github.com/qutebrowser/qutebrowser/pull/6448/files
the only other widgets that have an `on_tab_changed()` method don't get
get `.show()` called on them anymore, than handle that themselves with
the help of a `.enabled` attribute. Not 100% sure if it's the right
thing to do to lump this settings widget in with them just because it
also takes a tab (eg do the history and progress widgets handle showing
themselves because they hide when they aren't needed? The setting widget
is always visible when configured) but it's not big deal.

In general I want to refactor the statusbar to not have explicit
knowledge of the widgets it can show (eg have a common abstract class
for them with same defaults and helpers). In the meantime this is all
pretty self contained.

More words on that eventual refactoring here: qutebrowser#7407
@pylbrecht pylbrecht self-assigned this Apr 21, 2024
@pylbrecht
Copy link
Collaborator

I am going to take a stab at this.

@pylbrecht pylbrecht modified the milestones: v3.2.0, v3.3.0 May 19, 2024
@pylbrecht
Copy link
Collaborator

I did some spiking over the past weeks and now have a somewhat clear implementation idea.

@toofar, as you predicted, writing an integration test for the StatusBar class is tricky. I will put together a draft PR of my implementation idea to collect some feedback before going all-in.

@toofar
Copy link
Member

toofar commented May 20, 2024

@pylbrecht that's great to hear! I'm looking forward to the sketch.

@pylbrecht
Copy link
Collaborator

Here you go: #8203

Feedback is very much appreciated!

pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 17, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 22, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 22, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 22, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 22, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
pylbrecht added a commit to pylbrecht/qutebrowser that referenced this issue Jun 30, 2024
# DISCLAIMER
Super duper WIP changes ahead.

---

I just want to see what problems arise when using composition over inheritance, as
suggested by @The-Compiler in
qutebrowser#7407 (comment).

There is a lot of unaddressed ugliness (e.g. `widget.widget` everywhere) in this commit.
I just started with moving `QLabel` into `TextBase.widget`. Then fixing failing tests
one by one with the *easiest* solution (e.g. `widget.widget`); I even marked one test
with `xfail` because I could not quickly figure out why it was failing.

My hope is to get a better feel for what belongs to `QWidget` and what belongs to
`TextBase` (or its subclasses).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component: style / refactoring Issues related to coding styles or code that should be refactored. priority: 2 - low Issues which are currently not very important.
Projects
None yet
Development

No branches or pull requests

5 participants