[Help needed] How to effectively update a QWidget (add, remove, then add again) that was appended to the Editor?

TLDR:
I appended to the Editor window a progress bar that registers the amount of time I spent adding cards alongside with a widget that displays how many cards I added and the time (in minutes) it took to create them.
These widgets should be replaced with an updated version of themselves every time I add a card. However, what actually happens is that they get stacked instead. How to make it work properly?

I’ve managed to do a similar thing a few weeks ago, when I added a progress bar to the Main Window by adapting a trick I learned from the source code of the Card Info During Review addon. This “trick” works like this:

  1. Create a function that removes all the widgets from the Main Window web;

  2. Create a function that add these widgets back alongside with the progress bar widget;

  3. Create a function that encompasses the first then the second function;

  4. Use the addHook to fire the last function when certain things happen (e.g. profile loaded, question was showed, etc.)

I couldn’t reproduce this same trick with the Editor window, though.

Here’s a picture showing what I’ve described above:

Here’s the code¹:

import json

from anki import hooks, buildinfo
from anki.hooks import addHook
from aqt import editor, gui_hooks, mw
from aqt.utils import tooltip
from aqt.utils import *
from aqt.theme import theme_manager
from aqt.webview import AnkiWebView
from datetime import time, datetime
from PyQt5 import QtCore

class ProgressBar(object):
    
    def __init__(self):

        gui_hooks.editor_did_init.append(self.editor_init_hook)
        
    def editor_init_hook(self, ed: editor.Editor):
        
        # self.inject_pBar(ed)
        gui_hooks.add_cards_did_init.append(lambda o: self.inject_pBar(ed))
        # gui_hooks.add_cards_will_add_note.remove(lambda o: self._remove_pBar(ed))
        gui_hooks.add_cards_did_add_note.append(lambda o: self.inject_pBar(ed))
        
        # gui_hooks.editor_web_view_did_init.append(lambda o: self.inject_pBar(ed))
        
    def inject_pBar(self, editor: editor.Editor):
        lims = []
        lims.append(
                        "id > %d" % ((mw.col.sched.day_cutoff - (1 * 1 * 86400)) * 1000)
                    )
        lims.append("did in %s" % mw.col.sched._deck_limit())
        lim = "where " + " and ".join(lims)
        chunk = 1

        a = mw.col.db.all(
                    """
        select
        (cast((id/1000.0 - ?) / 86400.0 as int))/? as day,
        (id)
        from cards %s
        group by day order by day"""
                    % lim,
                    mw.col.sched.day_cutoff,
                    chunk,
                )
        b = datetime.fromtimestamp(a[0][1] / 1000).strftime('%H:%M:%S')

        c = mw.col.db.all(
                    """
        select
        (cast((id/1000.0 - ?) / 86400.0 as int))/? as day,
        count(id)
        from cards %s
        group by day order by day"""
                    % lim,
                    mw.col.sched.day_cutoff,
                    chunk,
                )
        d = c[0][1]

        e = mw.col.db.all(
                    """
        select
        (cast((id/1000.0 - ?) / 86400.0 as int))/? as day,
        (id)
        from cards %s
        """
                    % lim,
                    mw.col.sched.day_cutoff,
                    chunk,
                )

        f = datetime.fromtimestamp(e[-1][-1] / 1000).strftime('%H:%M:%S')

        time1 = datetime.strptime(b, "%H:%M:%S")
        time2 = datetime.strptime(f, "%H:%M:%S")
        g = time2 - time1
        gg = e[-1][-1] - a[0][1]
        minutes=(gg/(1000*60))%60
        minutes = int(minutes)
        hours=(gg/(1000*60*60))%24

        self.pbar = QProgressBar()
        self.pbar.setTextVisible(False)
        self.pbar.setUpdatesEnabled(True)
        self.pbar.setMaximum(1000)
        self.pbar.setMinimum(0)
        self.pbar.setValue(round(hours * 60))
        self.pbar.setStyleSheet('QProgressBar' '{ min-height: 2px; max-height: 2px; border-radius: 6px; text-align: center; font-size: 0px; background: #3a3a3a;}' 'QProgressBar::chunk {' +
                         'border-radius: 6px; background-color: #007ACC;}')
        if self.pbar.value() == 0:
            self.pbar.setStyleSheet('QProgressBar' '{ min-height: 2px; max-height: 2px; border-radius: 6px; text-align: center; font-size: 0px; background: #ffd700 /*#f1d710*/ /*#CC7A00*/;}' 'QProgressBar::chunk {' +
                         'border-radius: 6px; background-color: #f1d710 /*#CC7A00*/;}')
        if round(hours * 60) == self.pbar.maximum():
            self.pbar.setStyleSheet('QProgressBar' '{ min-height: 2px; max-height: 2px; border-radius: 6px; text-align: center; font-size: 0px; background: #3a3a3a;}' 'QProgressBar::chunk {' +
                         'border-radius: 6px; background-color: #00CC7A;}')

        
        self.label = QLineEdit(str(d) + " cards | " + str(round(hours * 60)) + " min")
        self.label.setMaximumHeight(35)
        self.label.setMaximumWidth(110)
        self.label.setReadOnly(True)
        self.label.setAlignment(Qt.AlignCenter)

        self.hlayout = QHBoxLayout()
        self.hlayout.addWidget(self.label)
        self.hlayout.addWidget(self.pbar)

        self.qwidget = QWidget()
        self.qwidget.setMaximumHeight(34)
        self.qwidget.setLayout(self.hlayout)

        layout = editor.outerLayout
        
        layout.removeWidget(editor.web)
        layout.addWidget(editor.web)
        layout.addWidget(self.qwidget)

bar = ProgressBar()

1 - It’s adapted from the Editor Live Preview addon(Editor Live Preview - AnkiWeb) (source code here).

EDIT: The code above only works if you’ve already added at least one card the current day. Also you’ve to move to the Overview screen (that screen Anki displays after you click on a deck name) before you open the Editor (Add Card) window.
These are issues I still have to figure out how to fix, but please let me know If you have the solution for them!

What I’d do is when a card is added, check if there is a progress bar and if it exists, update the widget value instead of destroying then re-creating the widget. e.g. qwidget.setValue(progress)

But if you want to destroy then create the widget, you can probably store a reference to the widget, and whenever a card is added do something like

widget: QWidget
widget.setParent(None)
widget.deleteLater()
create_widget()
5 Likes

Thank you. I’m gonna try your suggestion.

I tried the first approach you suggested and it worked. Thank you again!