Wings 3D Development Forum
"How to" about context menus - Printable Version

+- Wings 3D Development Forum (https://www.wings3d.com/forum)
+-- Forum: Wings 3D (https://www.wings3d.com/forum/forumdisplay.php?fid=1)
+--- Forum: Programming (https://www.wings3d.com/forum/forumdisplay.php?fid=7)
+--- Thread: "How to" about context menus (/showthread.php?tid=3116)



"How to" about context menus - micheus - 05-24-2023

I'm going to try share here a little about how to read and try to understand the menus when reading the source code the way I understood it.
It's not finished yet, so I going to keep adding any other information I remember here.

I hope this can at least help other to get a start point to find their own understanding. Smile


I'm going to start by spotting that in order to support multiple languages (translations) we must to use the macro ?__(<index>, <label>) in every place a text needs to be translated. This means menus, dialogs, windows, etc. The index must be unique for functions with the same name, which means we can reuse them in any other function in the same module without any problem.

All module/plugin that implements menus/sub-menus must export a menu/2 function;
Code:
export([menu/2, ...])


The function get two parameters which the first is the parent's Id and the second a list having its options (the menu itself);
Code:
menu({<id>}, <options_list>)

So, to append an menu option to the main menu as the Tools we can check for that this way:
PHP Code:
menu({tolls}, Menu) ->
    
MyMenu build_my_menu(),
    
Menu ++ MyMenu;
menu(_Menu) ->
    
Menu.

build_my_menu() ->
    [{?
__(1"My Option"), my_option, ?__(2"My single option for LMB")}]. 


In Wings3D, a menu option can have three different responses depending on which mouse button was used to select it. We refer them as LMB, MMB and RMB action (Left, Middle and Right mouse button, respectively).

A simple option, which the action is activated by pressing LMB, is constructed by using a tuple with three elements:
Code:
{<label>, <id>, <description/hint>}

It will look like this (option and description/hint/tooltip):
[Image: w3d-menu-single.png]

If the the action will have the option to show the user a input dialog instead of only to run the action directly, then the item must includes a 4th parameter to indicate that which will result in a small gerar icon be shown in the right corner of the option when the menu is shown:
Code:
{<label>, <id>, <description/hint>, [option]}

in this case our menu item would look like this:
PHP Code:
build_my_menu() ->
    [{?
__(1"My Option"), my_option, ?__(2"My single option for LMB")}, [option]]. 

It will look like this (the [option] parameter will automatically add a second command/hint for RMB):
[Image: w3d-menu-single-option.png]


Multiple options, which the action is activated by others buttons than only pressing LMB, is constructed by using a list of tuples similar to the single option one, but the id will be get from a pick option function which will handle the button clicked and now the 3rd parameter is a tuple that contains the description for each button option. If an mouse option will not be used its respective description must be an empty list ([]). The 4th parameter stays optional and will keep working for LMB option only:
Code:
{<label>, <click_function>, {<description/hint LMB>, <description/hint MMB>, <description/hint RMB>}}

This click_function is a function that will return the real function that handle the mouse buttons pressed (or help atom). The mouse_button value is 1, 2 and 3 for LMB, MMB and RMB respectively:

Code:
<click_function>() ->
    fun
        (<help|mouse_button>, _Ns) -> <command>;
        (_, _) -> ignore
    end.
* that _Ns parameter I don't know exactly what it stands for, but it is used in directions function we can find in wings_util.erl and is used to assembly commands and help information for menus.

in this case our menu item would be defined like this:
PHP Code:
build_my_menu() ->
    [{?
__(1"My Option"), my_menu_items(), {?__(2"My option 1 for LMB"),
                                            ?
__(3"My option 2 for MMB"),
                                            ?
__(4"My option 3 for RMB")} }].

my_menu_items() ->
    
fun
        
(1_) -> {my_optionopt1};
        (
2_) -> {my_optionopt2};
        (
3_) -> {my_optionopt3};
        (
__) -> ignore
    end


and it will look like this:
[Image: w3d-menu-multiple-3.png]

if we define only options for LMB and RMB, for instance, it can look like this:
[Image: w3d-menu-multiple-2.png]


"Pure" sub-menus are constructed in a similar way as menu items, but it doesn't need a description (because it's not used) and we use a set <id,function> to create the menu cascade. This function will return a list of menu items. In case on of these items have a sub-menu we repeat this structure all over:
Code:
{<label>, <{id, build_submenu_fun}>}

build_submenu_fun() ->
    [{<label>, <id>, <description/hint>, [option]},
     :
     {<label>, <id>, <description/hint>, [option]}]

and then it can have this look:
PHP Code:
build_my_menu() ->
    [{?
__(1,"My menu"), {my_menubuild_submenu()}}]

build_submenu() ->
    [{?
__(1"My option 1 for submenu level 1"), id_option1, ?__(2"Description of my option 1")},
     {?
__(3"My option 2 for submenu level 1"), my_menu_items() , {?__(4"Description of my option 2 LMB"),
                                                                   [],
                                                                   ?
__(5"Description of my option 2 RMB")}].

my_menu_items() ->
    
fun
        
(1_) -> {my_optionopt1};
        (
3_) -> {my_optionopt2};
        (
__) -> ignore
    end


Here is how a "pure" sub-menu item will look like:
[Image: w3d-menu-submenu.png]

and in this one there are three actions for the item which the action for LMB will call a sub-menu (lets call it mixed menu/sub-menus):
[Image: w3d-menu-multiple-submenu.png]


Menus can also have check marked options. We create them by providing as 4th parameter with a list with a single tuple this way:
Code:
{<label>, <id>, <description/hint>, [{crossmark, <boolean_value>}]}

in this case our menu item would look like this:
PHP Code:
build_my_menu() ->
    [{?
__(1"My Option"), my_check_option, ?__(2"My single option for LMB"), 
                      [{
crossmarkwings_pref:get_value(my_check_preffalse)}]]. 
In this case we are checking for the my_check_pref id stored in the preferences and giving it a default value false.

The result is something like this:
[Image: menus-checkmark.png]

Default preferences values can be defined in a module using the init/0 function.



p.s.: The elements not delimited by '<' and '>' are kept as they are in the code.


Menus: Command handles - micheus - 05-27-2023

Menu id's and action id's are going to be "packed" when them arrive in command handle function.

So, for our "Pure" sub-menus example we are going to define the command function like this:
PHP Code:
command({my_menuid_option1}, St0) ->
    %% 
// Make some processing and update St as needed (if so)
    
:
    
St;
command({my_menu, {my_optionopt1}}, St0) ->
    %% 
// Make some processing and update St as needed (if so)
    
:
    
St;
command({my_menu, {my_optionopt2}}, St0) ->
    %% 
// Make some processing and update St as needed (if so)
    
:
    
St;
command(_St) ->
    
St


When our menu items were set with option in the optional 4th parameter its value will be placed in a tuple composed by the packed command id and its value.
We also need to write at least two command function to handle it. The first one is going to handle the value returned by the menu item which will be a boolean value usually seen it named on Wings3D code as Ask. This first function will prepare and show a dialog that will be displayed to the user make the input. After the dialog be processed and ended with an OK then we are going to generate the second command which its pair in the tuple will be a list with the values of the elements we fill on the dialog/form.

So, by using our example for the single option menu, we can have a code like this:
PHP Code:
command({my_optionAsk}, #st{sel=Sel}=St) when is_atom(Ask)  ->
    
if Sel == [] ->
        
wings_u:message(?__(1,"A selection is required"));
    else
        
build_my_option_dialog(St);
    
end;
command({my_option, [{clear,true}]=_Ask}, St) ->
    
St#st{sel=[]};
command(_St) -> St.

build_my_option_dialog(St) ->
    
Qs = [{hframe,[{?__(1,"Clear Selection"), false, [{key,clear}]}]}],
    
wings_dialog:dialog(Ask, ?__(1,"Handle Selection"), Qs
                        
fun(Opts) -> {my_optionOpts}} end). 
For more information about the dialog construction we can check the wings_dialog.erl which has a description of all elements supported(from line 40).


For the check marked options they can look like this fragment bellow, but a more "complex" example can be found in wings_view.erl source code.

To keep the preference updated for our use we need to switch its value in its respective command handle:
PHP Code:
command({my_check_option}, St) ->
    
Value wings_pref:get_value(my_check_preffalse),
    
wings_pref:set_value(my_check_prefnot Value),
    
St;
  :