Yesterday I posted my first update for the BOOPSI Menu Class project (developed for OpenAmiga.org):
"First step taken. The skeleton code for the class is ready. The class library is initialized and the method dispatcher is in place. A simple test program successfully opens the class from disk and instantiates an empty object."
However, I couldn't sleep somehow so I ended up coding overnight In addition to the above, the class now successfully instantiates a full hierarchy of menu objects, like this one below:
A little test program I've written shows (using a listbrowser) how this code translates into an internal hierarchical structure the class will use to build the menu from. You can see that all objects have been instantiated all right and that they are in the right places:
You have mentioned in the other thread that the menus are linear because of the NewMenu structure.
But that's only needed if you're using CreateMenus() or the WINDOW_NewMenu tag.
You could build your own Menu and MenuItem structures (that's what CreateMenus() does as I understand it) and only use the LayoutMenus() function to get the required visuals.
You could also expand the two structures with an APTR for the userdata (like the NewMenu does).
That are all just some of my thoughts as I don't have done anything like that for myself.
Thanks for your ideas! I'll explain in a bit more detail what I'm trying to do.
I'm designing the class in such a way that it provides a general description of a menu hierarchy, and a set of atributes and methods to manipulate it. The keyword here is general. Neither Intuition, nor Gadtools is involved in the class' inner workings. Only when there is a request to attach the menu object to a window will the class translate the object data into something that Intuition or Gadtools can use. This is to allow an easy rewrite should a new menu system ever be introduced: much of the class will remain the same, only the translation code will have to change.
If the object data gets happily translated into a field of NewMenus then I'll certainly want to use CreateMenus(), to save me trouble and to make the translation code as short/simple as possible. I'll only dive to Intuition's treacherous depths if there's no other way
If you do it via NewMenu you can do other funky stuff, like menu callbacks, which is probably what you want in a OO based system.
So, it should be - create menu, attach menu to window, the menu calls the hook functions and the user doesn't need to manually handle any menu events (other than writing the functions they call).
IIRC the UserData field is used to specify the hook function, but there's a second UserData field (which is called something else but can be used for user data) that can hold any useful data the user might want.
You could even do your own hook function which handles *all* menu items, and then pass that through to the application's callback with the userdata as one of the args.
(edit: the way I'm doing it is I'm holding my own data in the hook's data field, so the second userdatary field I'm thinking of must be to do with something other than menus)
(edit2: turns out the hooks are a window.class feature, so you might not be able to use them - I suppose it depends if you are tying the menu.class to window.class or allowing it to be used for standard Intuition windows too. WINDOW_MenuUserData, WGUD_HOOK)
If you do it via NewMenu you can do other funky stuff, like menu callbacks, which is probably what you want in a OO based system.
I quite like the idea that each menu object can be associated with a callback function that gets invoked automatically upon the user selecting the respective item. But I wouldn't like to tie this to either NewMenu, or Window Class.
So I have taken my own way. You can now associate a "handler" function with any menu object, which you pass via MENU_Handler. This tag takes a pointer to a hook function, where the particular reaction to selecting the item will be handled. The hook for this hook function is allocated, installed and disposed of internally by the class. (This of course saves a lot of work. Considering that menus can contain dozens of items, just imagine having to prepare dozens of hooks manually!)
Upon receiving a menu event for your window, you just call the MM_HANDLEINPUT method and that is all. The method will process the menu selection and call the respective handler. (The ARexx class works in this way, too, so I'm not introducing any foreign solution here.)
If we manage to establish some cooperation between Window Class and Menu Class, the MM_HANDLEINPUT method could even be called by Window Class upon receiving IDCMP_MENUPICK. Thus, the programmer would become completely free of having to process menu events in the event loop.
Along with MENU_Handler I have also added MENU_UserData, through which you can associate arbitrary data with the menu object. If MENU_UserData is provided, the hook for the handler function will put it in its ASOHOOK_Data, so that the user data can be accessed from within the handler if needed.
So I have taken my own way. You can now associate a "handler" function with any menu object, which you pass via MENU_Handler. This tag takes a pointer to a hook function, where the particular reaction to selecting the item will be handled. The hook for this hook function is allocated, installed and disposed of internally by the class. (This of course saves a lot of work. Considering that menus can contain dozens of items, just imagine having to prepare dozens of hooks manually!)
You should offer a MENU_Hook too then maybe, as I can quite see that the same handler could be called for many menus items and allocating a hook for each idetical instance is hardly efficient.
Quote:
Upon receiving a menu event for your window, you just call the MM_HANDLEINPUT method and that is all. The method will process the menu selection and call the respective handler. (The ARexx class works in this way, too, so I'm not introducing any foreign solution here.)
Not it not foreign but surely it will clash with window.class menu handling? arexx.class is a standalone class so no issues, but this menu.class must intertwine with other classes to be useful.
Quote:
If we manage to establish some cooperation between Window Class and Menu Class, the MM_HANDLEINPUT method could even be called by Window Class upon receiving IDCMP_MENUPICK. Thus, the programmer would become completely free of having to process menu events in the event loop.
Okay didn't read far enough ahead. But it seems you are forcing a hook function approach this way and adding extra complexity and overhead. That is ofcourse always a danger with OO approaches, but not inevitable.
Quote:
Along with MENU_Handler I have also added MENU_UserData, through which you can associate arbitrary data with the menu object. If MENU_UserData is provided, the hook for the handler function will put it in its ASOHOOK_Data, so that the user data can be accessed from within the handler if needed.
I begin to see that catering for the varying user expectations will be far more difficult than actually writing the class :)
A week or so later, I can now see a couple of problems with my current callback approach. For example, having to provide a separate callback function for each menu item is probably too imposing. Surely with commands like "Quit" you'd prefer to just perform a simple operation (such as leaving the event loop) instead of writing a dedicated function for it?
@thread
As you can see, things are evolving and get changed on the fly. This is normal. What is important is that we discuss it all here and compare ideas to find optimum solutions. If there's something I really hate about AmigaOS4 development it's the fact that sometimes sub-par solutions get implemented behind the scenes, without any feedback from the programmers' camp (the Application Library being a prime example).
As one of the coordinators of the OpenAmiga.org project I'd like to encourage participating programmers to use this forum, inform about their projects' progress, and present their implementation ideas for public scrutiny. This is the only way to make OpenAmiga.org helpful and relevant.
The reason I changed from an event loop to using callbacks was, IIRC, because the switch-case section was getting unwieldy. It's much easier to manage a set of functions even if you have one per menu item, and you don't need to shunt things around when adding menus either (use enums, you say - that helps certainly, but when NewMenu is a flat array, and figuring out which menu item has been selected is hierarchal, well you see that that isn't necessarily ideal)
So I switched to callback functions, and in certain cases the callback for items is the same, but with different data in the h_Data field.
It's truckloads easier to manage, and I can insert and remove items with relative ease, even taking out items using #defines won't screw up the menu picking code.
How much more difficult is it to assign a callback for each menu, than pick the menu item in a switch-case section? In one case you have: menu_func(struct Hook *hook etc) { do stuff here }
In the other you have: switch { case ambigous_menu_number: switch { case ambiguous_item_number: do stuff here break; } break; }
The top one is much more readable.
The menu class will solve the flat array to hierarchy translation so you might be able to automagically disambiguise the switch-case, but I still think the callbacks will be better, even in simple programs.
Well, I'm not convinced TBH. You either have a function which selects between "20" different options, or you have 20 different functions for each menu.
While hooks are a nice idea, they aren't always the best solution...
Simon
Comments made in any post are personal opinion, and are in no-way representative of any commercial entity unless specifically stated as such. ---- http://codebench.co.uk
For me personally, using callbacks in the Menu Class will only be a good solution if two conditions are met:
1. The low-level stuff (like allocating/freeing hooks) is handled by the class. BOOPSI is the highest-level component of Intuition so it had better behave like one; and as I've already said above, I can't imagine having to manage two dozen hooks on top of two dozen hook functions.
2. The callback function is a function that I need to write anyway - for example, an about() function that creates and displays the info requester - and which I can reuse and invoke from other parts of the program (such as the toolbar).
And there comes my current problem. If the about() callback is written as a hook function (i.e. with parameters required by the AmigaOS hook API) and is invoked by the class (which duly supplies the hook and all), how would I invoke the callback from the toolbar when there's no hook to pass as parameter?
The ARexx Class uses callbacks but doesn't use hook functions. This is something I'd like to know more about.
1. The low-level stuff (like allocating/freeing hooks) is handled by the class. BOOPSI is the highest-level component of Intuition so it had better behave like one; and as I've already said above, I can't imagine having to manage two dozen hooks on top of two dozen hook functions.
Agreed, allocating hooks manually would be annoying. window.class already solves this one - you just pass the functon pointer.
Quote:
2. The callback function is a function that I need to write anyway - for example, an about() function that creates and displays the info requester - and which I can reuse and invoke from other parts of the program (such as the toolbar).
And there comes my current problem. If the about() callback is written as a hook function (i.e. with parameters required by the AmigaOS hook API) and is invoked by the class (which duly supplies the hook and all), how would I invoke the callback from the toolbar when there's no hook to pass as parameter?
Pass NULL? It's a fair point, although how often are toolbar buttons duplicates of what is in the menu?
Even if your hook functions just call a function elsewhere it's still easier to manage.
Quote:
The ARexx Class uses callbacks but doesn't use hook functions. This is something I'd like to know more about.
That may be a better option, although you are still constrained to the parameters for the callback that the class wants you to use.
No problem with NULL but what about the user data the callback function may want to use?
Quote:
how often are toolbar buttons duplicates of what is in the menu?
Quite often, actually. Toolbars normally have buttons for "New", "Open", "Save", "Print", "Cut", "Copy", "Paste", "Help"... all of which will normally appear in the menu as well.
Is this really any better than: static void menu_func(struct Hook *hook, APTR window, struct IntuiMessage *msg)?
No, not this particular one, but
static void menu_func(APTR userData)
would be quite all right and straightforward, I guess. What puzzles me is the autodoc note that ARexx Class callbacks pass their parameters in registers. I don't know how this is done, or whether it is still needed on OS4 (it may be a left-over comment from OS3 times).
would be quite all right and straightforward, I guess.
You'd need a pointer to the window (or window object) too.
Quote:
What puzzles me is the autodoc note that ARexx Class callbacks pass their parameters in registers. I don't know how this is done, or whether it is still needed on OS4 (it may be a left-over comment from OS3 times).
No idea how they work internally, that might be what the compiler does. I believe for 68k you had to specify the registers, but not for OS4 (at least, I never have). *shrug* There's a similar comment about hook functions IIRC.
You'd need a pointer to the window (or window object) too.
In that case it might be more sensible to make it
static void menu_func(Object *menuObject)
and then GetAttrs() attributes like MENU_Window and MENU_UserData from the object, inside the callback function.
EDIT: In which case the callback can as well be a regular hook function because their second argument is always an object pointer. So in reaction to a toolbar event, the programmer can call
menu_func(NULL, menuObject, NULL);
and use the same function the Menu Class uses as callback.
@Rigo
Quote:
While hooks are a nice idea, they aren't always the best solution...
Don't worry, the callbacks are going to be optional. There will be two ways to process menu events:
1. The handler-based approach The programmer will have to write dedicated handler functions (callbacks) that will be invoked automatically by the class upon menu selection. No further processing will be needed.
2. The traditional approach The programmer will be able to process menu events in a traditional loop, probably based on a MENU_ID value returned by the handleinput method (an attribute analogous to GA_ID).
These two approaches could even be combined to get the best of both worlds. For menu items that have no handler assigned to them the handleinput method will return the respective menu ID. For menu items with a handler the method will invoke the callback (= no ID will be returned).
Edited by trixie on 2014/7/13 14:17:40 Edited by trixie on 2014/7/13 16:06:44
So in reaction to a toolbar event, the programmer can call
menu_func(NULL, menuObject, NULL);
and use the same function the Menu Class uses as callback.
Or maybe you could consider splitting things up once more, so you have a "function execution" layer in one class, in which you can define "things you want done", and those can be called by any "arbitration layer", be it a menu class or a toolbar class - well, something like that, anyway (if it makes any sense ).
These two approaches could even be combined to get the best of both worlds. For menu items that have no handler assigned to them the handleinput method will return the respective menu ID. For menu items with a handler the method will invoke the callback (= no ID will be returned).
Reading through recent posts I was getting a litle worried, with all this talk of callbacks, so I pleased to read this last statement.
As you can see, things are still being discussed and I'm open to suggestions. The aim is to make the class powerful and flexible yet simple and easy to use. You'll certainly have your choice: if you don't want to hear about handlers or callbacks, the good old event loop will be there for you.
Which reminds me I need to think about how to handle menu multiselect if the handleinput method is supposed to return a single value. Oh well...