Check if an Editor (object) is opened separately instead of embedded (i.e. in Browser)

Dear Anki Community,

Thank you very much for the project.

May anyone suggest a way to distinguish whether an Editor is embedded or not?
The main reason is that some functionality like Editor.checkValid has to run only when the Editor is actually opened, but not embedded.

Best and kind regards

Here is an example to do that:

import aqt


def on_editor_did_init(editor: aqt.editor.Editor) -> None:
    if editor.addMode:
        print("Add")
    elif isinstance(editor.parentWindow, aqt.browser.Browser):
        print("Browser")
    else:
        print("Edit Current")


aqt.gui_hooks.editor_did_init.append(on_editor_did_init)
4 Likes

I see. So, the hook. Thank you very much.
Does it mean that hook is supposed to be used as a tracker inside a plugin? I mean, that hooks may exist in every plugin separately?
Also, is there any resource online about that or how did you get such idea? Reading the source files of its repository?

It appears, having a global variable in a plugin alongside with that hook may not actually work. For example, Editor.checkValid sees the global variable set to its initial value - 0, where the hook has already changed the value.

A code to demonstrate the issue. A message “OK” should not appear if the Editor is opened in the Browser.

import aqt
from aqt.editor import Editor
from aqt.utils import showInfo, tooltip

# Global variable(s)
editorMode = 0

# The hook function
def OnEditorDidInit(editor: aqt.editor.Editor) -> None:
    if editor.addMode:
        editorMode = 1 # Add
    elif isinstance(editor.parentWindow, aqt.browser.Browser):
        editorMode = 2 # Browser
    else:
        editorMode = 3 # Edit

    Log("Mode has changed to {0}".format(editorMode))

# The function which runs every required field event
def CheckFields(self, _old):
    global matchCount
    global editorMode

    # If Editor appears in Browser
    if (editorMode == 2):
        return 0

    showInfo('OK')

    return 0

# Plugin initialization
def setup():
    aqt.gui_hooks.editor_did_init.append(OnEditorDidInit)
    Editor.checkValid = wrap(Editor.checkValid, CheckFields, "around")

I presume that setup() and its call to aqt.gui_hooks…append runs when your add-in loads because you have a call, not shown, made to it at that time. OnEditorDidInit does not run at that time; it runs when an editor is later activated by the user. So the value of editorMode will not have changed when checkValid is called.

Even if you put the hook addition outside of the setup() function, it doesn’t work.
Also, I verified. The setup() is called on Anki’s initial start.

The above function/script is called buy the manifest.json with a script “initializer” like:

from .script_1 import setup

setup()

Edit: I just checked what I wrote on Sidenote, and it was wrong, so I deleted it. Please forget about it.

That’s right.

As you may know, the official tutorial on add-on development can be found here. However, the document only covers the basics, so I think in most cases you will need to refer to other add-ons or read Anki’s source code.

Maybe global needs to be in OnEditorDidInit function as well. Try the following fix:

 # The hook function
 def OnEditorDidInit(editor: aqt.editor.Editor) -> None:
+    global editorMode
     if editor.addMode:
         editorMode = 1 # Add

See the Python manual for more information on global.

OnEditorDidInit will run when an editor activates, and setup() and its call to checkValid is run (as you say) when Anki initializes.

In other words,

Editor.checkValid sees the global variable set to its initial value - 0, where the hook has already changed the value.

is incorrect because your hook (more precisely, the function called by your hook) has not already changed its value because no editor has activated when Editor.checkValid runs.

Holy moly… How did I miss that…
Appending global editorMode to the hook fixed the whole thing.

import aqt
from aqt.editor import Editor
from aqt.utils import showInfo, tooltip

# Global variables
workingPath = os.path.dirname(os.path.realpath(__file__))
logFilePath = f"{workingPath}/log_1"
editorMode = 0

def Log(message):
    try:
        with open(logFilePath, 'a+') as logFile:
            logFile.write(f"{message}\n")
    except IOError:
        sys.stderr.write(f"The log file is not accessible: \"{logFilePath}\"")

# The hook function
def OnEditorDidInit(editor: aqt.editor.Editor) -> None:
    global editorMode

    if editor.addMode:
        editorMode = 1 # Add
    elif isinstance(editor.parentWindow, aqt.browser.Browser):
        editorMode = 2 # Browser
    else:
        editorMode = 3 # Edit

    Log("Mode has changed to {0}".format(editorMode))

# The function which runs every required field event
def CheckFields(self, _old):
    global matchCount
    global editorMode

    Log("Mode = {0}".format(editorMode))

    # If Editor appears in Browser
    if (editorMode == 2):
        return 0

    showInfo('OK')

    return 0

# Plugin initialization
def setup():
    aqt.gui_hooks.editor_did_init.append(OnEditorDidInit)
    Editor.checkValid = wrap(Editor.checkValid, CheckFields, "around")

Thank you very much, dear People.
Please, have a pleasant time and stay safe out there!

OK, then I’m wrong – but I don’t understand why I’m wrong and if anyone wants to take the trouble to explain why I’d be grateful.

Also, this one(for CheckFields()). Without hooks. self is an Editor instance:

# Perhaps, `hasattr` is not really required, but just in case...
if (hasattr(self, 'addMode') and self.addMode):
    editorMode = 1 # Add
elif (hasattr(self, 'parentWindow') and isinstance(self.parentWindow, aqt.browser.Browser)):
    editorMode = 2 # Browser
else:
    editorMode = 3 # Edit