Go to QuArK Web Site
Help, etc.
Updated 05 Apr 2018
Upper levels:
QuArK Information Base
3. Advanced customization
3.6. Plug-Ins
3.6.1. Plugin Tutorial

 3.6.1.4. Help, etc.

 [ Prev - Up - Next ] 

In this lesson we'll take on various help-related issues.


 Index


 Flyover help

tiglari - 05 Apr 2018   [ Top ] 

Help is the easiest. In the third position of qmenu.item, we can put a string which is displayed at the bottom-left of the screen when the cursor sits on the menu item. Or if the text is too long to be likely to fit into the window down there, begin it with a `|', then there's a message to press <F1> for help in the little window, and the string will appear in a popup window in the middle of the screen when <F1> is pressed.

So we might replace our current menu-adding commands with something like this:

quarkpy.mapcommands.items.append(quarkpy.qmenu.item("&Tag Side", tagSideClick, "|`Tags' the selected side so that another side can be snapped to it by a later `Glue to Tagged' command.\n\nJust the thing for making sure the legs of the table are actually touching the ground."))
quarkpy.mapcommands.items.append(quarkpy.qmenu.item("&Glue to Tagged", glueSideClick, "Snap selected to tagged side"))
Note that these strings get rather long in the text-editor (and the browser!!), but they wrap around nicely in the display window.


 Disablers

tiglari - 05 Apr 2018   [ Top ] 

Now for something a bit harder, which is to cause the menu items to become `disabled' (light grey and unresponsive) when they can't do anything useful. The technique for doing this depends on the fact hat menu items are full-fledged objects and can have any kind of data attached to them. Furthermore there is an attribute `.state' with these values defined in quarkpy.qmenu.py:

# menu state
normal     = 0
checked    = 2
radiocheck = 3
disabled   = 4    # can be added to the above
default    = 8    # can be added to the above
Menu items are created in a normal state; all we have to do to disable them is to change their state to disabled.

These means first giving them names, then using those names when we append them to the command menu:

mentagside  = qmenu.item("&Tag Side", tagSideClick)
menglueside = qmenu.item("&Glue to Tagged", glueSideClick)
quarkpy.mapcommands.items.append(qmenu.sep)   # separator
quarkpy.mapcommands.items.append(mentagside)
quarkpy.mapcommands.items.append(menglueside)
For brevity, we've dropped the help text. But we've also added a `separator', to make things look more organized.

Our next trick is a bit deeper. The `mapcommands' object in quarkpy has a `method' (some functions or procedures associated with the object), called `onclick', which does stuff when the commands menu is activated, before the items on it actually get displayed. What we're going to do is to redefine this method. Some code that does this is:

def commandsclick(menu, oldcommand=quarkpy.mapcommands.onclick):
    oldcommand(menu)
quarkpy.mapcommands.onclick = commandsclick
First we define our new onclick function, calling it `commandsclick' for the moment. It takes an optional parameter `oldcommand', which is equated by default to the current quarkpy.mapcommands.onclick (this is a basic technique for redefining things from quarkpy). What it does (for now) is execute the old method. I tried to get it to display a message box, but for some unknown reason that trashed the display of the menu, so I commented it out. Finally we equate quarkpy.madpcommands.onclick to our new commandsclick function. You should now test this to make sure nothing has gotten accidently broken, and then we'll move on to making it do something.

But maybe I should first say a bit about what's really going on. In the running of a Python program, the first time a module is imported, the statements in it are executed. The  def  and  class  statements cause things to get defined, but you can write code to do anything you like. Subsequent imports of a module don't re-execute its statements, they just make the stuff defined in the module available, not only for `read' access, but also for `write' access (redefinition). This makes plugins extremely powerful, and is described in the Python documentation as a rather dangerous practice, but it seems to work out OK, the way it's managed in QuArK.

But now what we want to do of course is check for the preconditions of our two menu items. For side-tagging, we want the selection to consist of exactly one side. If this is true, we want the menu state to be normal, otherwise disabled. Here's some code that does it:

def selFace(editor):
    sel = editor.layout.explorer.sellist
    if len(sel)!=1 or sel[0].type!=":f":
        return None
    return sel[0]
def commandsclick(menu, oldcommand=quarkpy.mapcommands.onclick):
    editor = mapeditor()
    if editor is None: return
    oldcommand(menu)
    mentagside.state = qmenu.normal
    sel = selFace(editor)
    if sel is None:
        mentagside.state = qmenu.disabled
We can now remove the checks & message boxes from the tagSideClick code (tho it might be sensible to leave them in for a while to make sure our disabler is really picking up on all the preconditions).

The next step is to extend commandsclick to test whether the gluing command is applicable. Working out all of the conditions is a bit tricky, here's something that seems to be OK:

def commandsclick(menu, oldcommand=quarkpy.mapcommands.onclick):
    editor = mapeditor()
    if editor is None: return
    oldcommand(menu)
    mentagside.state = menglueside.state = qmenu.normal
    sel = selFace(editor)
    if sel is None:
        mentagside.state = qmenu.disabled
        menglueside.state = qmenu.disabled
    tagged = gettagged(editor)
    if tagged is None:
        menglueside.state = qmenu.disabled
    elif sel == tagged:
        menglueside.state = mentagside.state = qmenu.disabled
Now we've lost a little bit, because there aren't any more explanations of what the problem is when the user tries to do the wrong thing. So the conditions under which the menu item can be used should be explained in its hint.

It is also, in my experience, rather tricky to work out the conditions under which items should be disabled, & the code tends to get fragile. So it's good if the operations check that they're not going to cause trouble before proceeding.

A further possibility is to dynamically adjust the hint, by writing code that says things like:

    hint = "|This menu item is disabled because ..."+hint[1:]
There is an example of this in the function curvemenu in plugins/mb2curves.py. Hey, it begins with `mb2', and yet loads as a plugin! That's because things to do with bezier curves, that are only useful with Q3A-engine games, should be put into plugins starting with `mb2'.

A possible project would be to enhance the `tagging.py' module with some code that put into the tagging object info about the unsuccessful attempts to retrieve various kinds of tagged things; this info could then be used to add information to hints.


 Hot Keys

tiglari - 05 Apr 2018   [ Top ] 

Our last topic is something that's easy for us, but was very hard for Armin to implement, he says, `hot keys'. These are keys you can press to get an action instead of going thru the tedium of finding the relevant menu & the item on it. Here's how to set up a hot key:

quarkpy.mapcommands.shortcuts["Ctrl+T"] = mentagside
quarkpy.mapcommands.shortcuts["Ctrl+G"] = menglueside
If you've been reading up on Python, you'll perceive that `shortcuts' is just a `dictionary' (hash table, for techos) attached to the mapcommands object, and we're just sticking the menu items into it, indexed by string codes for their hotkeys. Then some code in quarkpy, of which we will all hopefully be able to remain blissfully ignorant, gathers up all of the shortcuts that have been defined at various places & sticks them up on the menu, and makes them work from the keyboard.

Note however that each shortcut key combo can only activiate one menu item, so a certain amount of cooperative behavior is desirable for plugin writers. I think it would be a cool and possibly feasable (but pretty hard) project to allow users to set up shortcuts the way they want them.

Working example of stuff so far in  maptagside2.py , as usual.



Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/

 [ Prev - Top - Next ]