Go Down

Topic: MenuBackend, new menu managment library (Read 46 times) previous topic - next topic

AlphaBeta

I just created a new library for menu managment.
It has no limits as to what kind of logical layout you want.

[size=14]MenuBackend 1.0[/size]

Every item can be thought of as a cross with directional connections in these direcitons: above, right, belov, left.

You can add an item to another item at these locations. Say you have two items, called A and B.
If you want these to be ordered as a horizontal menu with A being the first item, you simply add B to a at the right position.
A.addRight(B);
Now your menu support these actions (if you have added the A item to the menu):

Code: [Select]
menu.moveRight(); //now menu.getCurrent() == B
menu.moveLeft(); //now menu.getCurrent() == A


Both menu setup and menu navigation is demonstrated in the example below.

This menu also support backstepping. It is demonstrated in the example below.

Quote


#include <MenuBackend.h>

/*
     This program demonstrates a menu modeled after the menubar in the Arduino IDE
     
   +root
     +file                  +edit   +sketch                  +tools                  +help
      +new                   +cut       +verify (V)       +autoformat       +environment
      +open
      +examples
       +ArduinoISP
*/

//this controls the menu backend and the event generation
MenuBackend menu = MenuBackend(menuUseEvent,menuChangeEvent);
     //beneath is list of menu items needed to build the menu
     MenuItem miFile = MenuItem("File");
           MenuItem miNew = MenuItem("New");
           MenuItem miOpen = MenuItem("Open");
           MenuItem miExamples = MenuItem("Examples");
                 MenuItem miArduinoISP = MenuItem("ArduinoISP");

     MenuItem miEdit = MenuItem("Edit");
           MenuItem miCut = MenuItem("Cut");

     MenuItem miSkecth = MenuItem("Sketch");
           MenuItem miVerify = MenuItem("Verify",'V');

     MenuItem miTools = MenuItem("Tools");
           MenuItem miAutoformat = MenuItem("Autoformat");

     MenuItem miHelp = MenuItem("Help");
           MenuItem miEnvironment = MenuItem("Environment");

//this function builds the menu and connects the correct items together
void menuSetup()
{
     Serial.println("Setting up menu...");
     //add the file menu to the menu root
     //when add is used, as opposed to addX as you see below, the item is added below the item it's added to
     menu.getRoot().add(miFile);
     //add all items below File to the file menu item,
     //notice the arduino isp item is added _to the right_ of the examples item
     miFile.add(miNew).add(miOpen).add(miExamples).addRight(miArduinoISP);
     //because edit item is to the right of the file item, we use the addRight method when inserting this item
     //then we add the cut item, because it is below the edit
     miFile.addRight(miEdit).add(miCut);
     miEdit.addRight(miSkecth).add(miVerify);
     miSkecth.addRight(miTools).add(miAutoformat);
     miTools.addRight(miHelp).add(miEnvironment);
}

/*
     This is an important function
     Here all use events are handled
     
     This is where you define a behaviour for a menu item
*/
void menuUseEvent(MenuUseEvent used)
{
     Serial.print("Menu use ");
     Serial.println(used.item.getName());
     if (used.item == "ArduinoISP") //comparison using a string literal
     {
           Serial.println("menuUseEvent found ArduinoISP");
     }
     if (used.item == miVerify) //comparison agains a known item
     {
           Serial.println("menuUseEvent found Verify (V)");
     }
}

/*
     This is an important function
     Here we get a notification whenever the user changes the menu
     That is, when the menu is navigated
*/
void menuChangeEvent(MenuChangeEvent changed)
{
     Serial.print("Menu change ");
     Serial.print(changed.from.getName());
     Serial.print(" ");
     Serial.println(changed.to.getName());
}

void setup()
{
     Serial.begin(9600);
     
     menuSetup();
     Serial.println("Starting navigation (see source for description):");

     menu.moveDown();  //move to file
     menu.moveDown();  //move to new
     menu.moveDown();  //move to open
     menu.moveDown();  //move to examples
     menu.moveRight(); //move to arduinoisp
     menu.use();       //use arduniisp
     menu.moveLeft();  //move to examples
     menu.moveUp();    //move to open
     menu.moveUp();    //move to new
     menu.moveUp();    //move to file
     menu.moveRight(); //move to edit
     menu.moveRight(); //move to sketch
     menu.moveDown();  //move to verify
     menu.use();       //use verify
     menu.moveBack();  //move back to sketch
     menu.moveBack();  //move back to edit
     menu.moveBack();  //move back to file
     menu.moveBack();  //move back to new
     menu.use();       //use new

     menu.use('V');    //use verify based on its shortkey 'V'
}

void loop()
{
  //
}



I'd be glad if someone could test this and give me some feedback. I unfortunantly have no arduino available at the moment.

orbitalair

Hi,
It works for me.  I use RBBB clones.

Curious why you develop with just a .h file?  You will throw the newbies for a loop.

I want to extend this to use a lcd and buttons, but I see no reason why it would not work.

Great job, thanks.

AlphaBeta

#2
May 09, 2010, 12:46 am Last Edit: May 09, 2010, 12:49 am by AlphaBeta Reason: 1
I see many advantages with useing just a .h file.

It is easier to share, it never becomes a problem that the .h version does not match the .cpp version.

When coding with templates or libraries with static methods/members you get far less linkage problems.

It is much faster to develop this way, and since I license it LGPL anyways I do not need to hide the implementation from the interface.

Legacy reason: Back in the days Arduino stored a .o for compiled classes. I then used only .h to avoid having to delete the .o in order to force a reompile.


The only problem I've encountered is that it can be slightly troublesome to separate classes that need each other. But such classes (if they're not an artifact of poor system design) are often better placed in the same .h anyway.

[edit]BTW: Thank you for taking the time to test this!
I'll be glad to know how your experience is like when you've made the menu and lcd link. If you have any suggestions or find anything hard to understand, do not hesitate to let me know.

I'll put this up on the playground sometime in the future. But if you see this post, and need assistance, please send me a mail og PM and I'll help you, and get to the playground site authoring.  :)[/edit]


Dan9186

Once I have everything figured out and working, I will provide some pictures and info on using the menu with a LCD and Keypad.

One thing that I can think of is the ability to getUp(), getRight(), getDown(), and getLeft() without adjusting the current point so that you could theoretically traverse the menu tree in the background to find the surrounding items.  For example on my LCD I have four lines and I would like to display the surrounding items if there are any.

i.e.
Code: [Select]

//User opens menu
----------------------------------------
|>Readings                             |
| Controls                             |
| Settings                             |
|                                      |
----------------------------------------

//User presses right twice
----------------------------------------
| Readings                             |
| Controls                             |
|>Settings                             |
|                                      |
----------------------------------------

//User presses down
----------------------------------------
|>Actions                              |
| Date & Time                          |
| Water Level                          |
|                                      |
----------------------------------------

//User presses down again
//User presses down
----------------------------------------
|>Add Action                           |
| Edit Action                          |
| Delete Action                        |
|                                      |
----------------------------------------


AlphaBeta


orbitalair

I think its poor form, but you wrote it, so ok.  I would prefer .h/.cpp form myself.

I think dan9186 is on the right track, but I use 16x2 lcds myself...

Coding Badly

Quote
I think its poor form, but you wrote it, so ok.  I would prefer .h/.cpp form myself

Why?  What's the benefit of splitting the declaration from the definition?

Dan9186

Ok now I guess my next question would be, how do I make use of that to get two levels to the right or something like that?

If I try something like
Code: [Select]

lcd.at(1,2,menu.getCurrent().getName());
lcd.at(2,2,menu.getCurrent().getRight()->getName());


I get exactly what I expected, the current menu item and the one to the right of it.  Problem though is that if I do one more
Code: [Select]

lcd.at(3,2,menu.getCurrent().getRight().getRight()->getName());


I get the following error.
Code: [Select]

In function 'void loop()':
error: request for member 'getRight' in '((MenuItem*)menu.MenuBackend::getCurrent())->MenuItem::getRight()', which is of non-class type 'MenuItem*


There is one other issue that I've come up with as I plan this a little more.  If I wanted to use the same scheme to display 4 items at a time, then using the .getRight() like that wont work.  It will work just fine for when I am at the level just after the menu root, but if you wanted to list the items under one of the base menus then it would need to be .getAfter().  Which would mean you would either need some sorta way to tell if you are at the head of one of the menus or you would have to change the structure of the implementation.  It would be my preference to show more of the menu than just the current item that you are on so that the user can have a better understanding of where they are in the menu structure.  Do you have any suggestions for this or ideas?  If it is just all way to complicated I can simply go with using one line to display the menu possibilities and some sort of character to designate when the menu item has children/siblings or not.

Thanks for your help and ideas.

Coding Badly


Does this work...
lcd.at(3,2,menu.getCurrent().getRight()[glow]->[/glow]getRight()->getName());

AlphaBeta

@Dan9186: What you want to print to the user is entirely up to you. You can easily check to see if there are any items above/right/below/left and print them accordingly. You could use getCurrent if you want your print to be relative to where the user is, or you could use the root, to have a static reference for printing.

I might write a MenuRendrer or something one day, but people want their menus printed differently so I'm guessing a good framework for that (that satisfies most users) is a lot of work, and takes a lot of time.

And, Coding Badly makes a good guess about that ->, all getX() returns pointers, this is so you can test if there are an item at that location, as the example demonstrates. :)

Dan9186

Just thinking on it, what would be the disadvantages to having getCurrent() return a pointer as well?  This would be soley for the purpose of having any item that you return from getX() will respond the same way.  This way you could use the same code to display a name if you got a menu item from getCurrent() just like you would from getRight().

Done like this it currently gives an error since getCurrent() does not return the same as the other getX() does.
Code: [Select]

     MenuItem *miLine1 = menu.getCurrent();
     MenuItem *miLine2 = menu.getCurrent().getRight();
     MenuItem *miLine3 = menu.getCurrent().getRight()->getRight();
     MenuItem *miLine4 = menu.getCurrent().getRight()->getRight()->getRight();
     
     if(miLine1) lcd.at(1,2,miLine1->getName());
     if(miLine2) lcd.at(2,2,miLine2->getName());
     if(miLine3) lcd.at(3,2,miLine3->getName());
     if(miLine4) lcd.at(4,2,miLine4->getName());


I am trying to come up with some way that I can have a ">" as a current selection indicator and have it move instead of the rest of the menu.  Any thoughts?

My other question is since I quite often get pointers confused, what exactly does the -> do there?  I'm not sure that I really understand the syntax.

Dan9186

#11
May 11, 2010, 02:32 am Last Edit: May 11, 2010, 02:57 am by dan9186 Reason: 1
Also just thought about this.  Two things that might be useful.  One something like hasChildren() that returns true/false on any menu item, and two the option to setCurrent( MenuItem item ) so that you could setup an escape from the menu by calling setCurrent( menu.getRoot() ).

orbitalair

Oh yes Dan9186, those 2 functions would definitely be handy.
Sometimes you do want to jump back to the top menu or exit to somewhere else.

Looks like you guys are on it, so I should get to work on my 16x2 menus...

AlphaBeta

#13
May 11, 2010, 05:04 am Last Edit: May 11, 2010, 05:05 am by AlphaBeta Reason: 1
Cant use my Playground profile tonight, so here is:
Header: http://pastebin.org/219915
Keywords:  http://pastebin.org/219920
Version 1.2 with hasChildren and setCurrent

The hasChildren can be tested as normal:
Code: [Select]
if (item.hasChildren())

Or you can do specific tests
if ( mi->hasChildren() & MenuItem::RIGHT) {/*yay! we have a child to the right!*/}

When using setCurrent, it will trigger an event if the item you set it to does not match the current.

Code: [Select]
menu.setCurrent( menu.getRoot() ); //will trigger a change if current!=root

I will keep the getRoot and getCurrent to returning references, because this is the methods most user will need. And, I do not want to introduce pointers (and the need to test for 0) to the beginners. Also, I like to guarantee that both the root, and the current returns a reference not a null, because of the reasons just stated.  :)

[edit]Thank you for useful feedback and suggestions![/edit]

Dan9186

Just tested it out and at first glance, moveLeft() and moveDown() don't seem to be working properly.  I can't get them to move the current anywhere.  On top of that moveRight() seems to be doing what moveDown() did previously.  Did you change the structure of the directions or is this just a error from where you added in the changes to do hasChildren()?

Go Up