smart menu

Pages: 123
Menu.h:
---------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
Copyright <2017> <Daniel Rossinsky>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#ifndef MENU_H
#define MENU_H

#include"Console.h"
#include<iostream>
#include<vector>

class Menu
{
private:
	using voidFuncPtr = void(*)();
	using strFuncPtrPair = std::pair<std::string, voidFuncPtr>;
	std::vector<strFuncPtrPair>	            m_options;
	std::string								m_header;
	bool									m_enabledHeader{ false };
	bool								    m_boxedMenu{ false };
	int										m_yJumpRange{ 1 };
	int									    m_menuHight{};
	int										m_menuLength;
	int										m_menuXPos;
	int										m_menuYPos;

	void updateLengths(const std::string& opt, int padding);
	void printBoxedOptions(int& curXPos, int& curYPos) const;
	void printBareOptions(int& curXPos, int& curYPos) const;
	void printHeader(int& curXPos, int& curYPos) const;
	void printMenu(int curXPos, int curYPos) const;
	void executeOption(intPair coords) const;
	int  getPadding() const;

public:
	explicit Menu(int x = 0, int y = 0)
	{
		setMenuXY(x, y);
	}

	void addOption(const std::string& opt, const voidFuncPtr func);
	void addHeader(const std::string& header);
	void deleteOption(const std::string& opt);
	void enableBoxedOptions(int boxHight = 3); // 3 is the minimum hight, the number must be odd
	void setMenuXY(int x, int y);
	void start();
};

#endif    


Menu.cpp:
-----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
#include"Menu.h"
#include<algorithm>
#include<string>

namespace 
{
	constexpr unsigned char selectionArrow     { 175 };
	constexpr int		    boxedLengthPadding { 4 };
	constexpr int			yRangePadding      { 2 };
	constexpr int           bareLengthPadding  { 1 };
	constexpr int		    xJumpRange         { 0 };

	inline int findMiddle(int num) 
	{
		return ((num / 2) + 1);
	}
}

/*__MENU__*/
/*private_function_definitions_start*/ 
inline void Menu::updateLengths(const std::string& opt, int padding)
{
	if ((static_cast<int>(opt.length()) + padding) > m_menuLength)
	{
		m_menuLength = static_cast<int>(opt.length()) + padding;
	}//end of if
}

inline void Menu::printBoxedOptions(int& curXPos, int& curYPos) const
{
	for (size_t index{}; index < m_options.size(); ++index)
	{
		Console::drawBox(m_yJumpRange - 2, m_menuLength - boxedLengthPadding, curXPos, curYPos);
		Console::gotoxy(curXPos + 2, curYPos + findMiddle(m_yJumpRange) - 1); 
		std::cout << m_options[index].first;
		curYPos += m_yJumpRange;
	}//end of for
}

inline void Menu::printBareOptions(int& curXPos, int& curYPos) const
{
	++curXPos;
	for (size_t index{}; index < m_options.size(); ++index)
	{
		Console::gotoxy(curXPos, curYPos++);
		std::cout << m_options[index].first;							
	}//end of for
}

inline void Menu::printHeader(int& curXPos, int& curYPos) const
{
	Console::gotoxy(curXPos + 1, curYPos);
	std::cout << m_header;											
	Console::gotoxy(curXPos + 1, ++curYPos);
	for (size_t itr{}; itr < m_header.length(); ++itr) std::cout << '-';
	++curYPos;
}

void Menu::printMenu(int curXPos, int curYPos) const
{
	Console::drawBox(m_menuHight, m_menuLength, curXPos, curYPos);
	curXPos += 2;
	++curYPos;
	if (m_enabledHeader) printHeader(curXPos, curYPos);
	if (m_boxedMenu)     printBoxedOptions(curXPos, curYPos);
	else                 printBareOptions(curXPos, curYPos);
}

void Menu::executeOption(intPair coords) const 
{
	m_options[(coords.second - (m_menuYPos + getPadding())) / m_yJumpRange].second();
}

int Menu::getPadding() const
{
	if (m_enabledHeader) return(findMiddle(m_yJumpRange) + yRangePadding);
	else				 return(findMiddle(m_yJumpRange));
}
/*private_function_definitions_end*/

/*public_function_definitions_start*/
void Menu::addOption(const std::string& opt, const voidFuncPtr func)
{
	if(std::find_if(m_options.begin(), 
		            m_options.end(),
		            [&opt, func](strFuncPtrPair const& element) 
	                {return (element.first == opt && element.second == func); }) == m_options.end())
	{
		m_options.emplace_back(strFuncPtrPair(opt, func));
		if (m_boxedMenu) updateLengths(opt, boxedLengthPadding);
		else             updateLengths(opt, bareLengthPadding);
	}//end of if
	else std::cout << "An option with this name and or function already exists.\n";
}

void Menu::addHeader(const std::string& header)
{
	m_enabledHeader = true;
	m_header        = header;
	m_menuHight    += 2; //header will take additional 2 spaces length wise
	if (static_cast<int>(m_header.length()) + 1 > m_menuLength )
	{
		m_menuLength = static_cast<int>(m_header.length()) + 1;
	}//end of if
}

void Menu::deleteOption(const std::string& opt)
{
	auto itr{ std::find_if(m_options.begin(),
		                   m_options.end(),
		                   [&opt](strFuncPtrPair const& element) 
	                       {return element.first == opt; }) };
	if (itr != m_options.end())
	{
		m_options.erase(std::remove_if(m_options.begin(),
									   m_options.end(),
			                           [&opt](const strFuncPtrPair& element)
		                               {return element.first == opt; }), m_options.end());
	}
	else std::cout << "The option doesn't exist.\n";
}

void Menu::enableBoxedOptions(int boxHight)
{
	if ((boxHight >= 3) && (boxHight % 2 != 0))
	{
		m_boxedMenu  = true;
		m_yJumpRange = boxHight;
	}//end of if
	else std::cout << "Invalid box size.\n";
}

void Menu::setMenuXY(int x, int y)
{
	if (x < 0)
	{
		m_menuXPos = -x;
		std::cout << "X value was made positive.\n";
	}//end of if
	else m_menuXPos = x;
	if (y < 0)
	{
		m_menuYPos = -y;
		std::cout << "Y value was made positive.\n";
	}//end of if
	else m_menuYPos = y;
}

void Menu::start()
{
	if (static_cast<int>(m_options.size()) == 0) 
		std::cout << "No options found, unable to print menu.\n";
	else
	{
	    m_menuHight += m_yJumpRange * static_cast<int>(m_options.size());
		printMenu(m_menuXPos, m_menuYPos);

		if (m_boxedMenu)
		{
			executeOption(Console::navigation(intPair(xJumpRange, m_yJumpRange),
											  intPair(m_menuXPos + 1, m_menuXPos + 1),
											  intPair(m_menuYPos + getPadding(),
													 (m_menuYPos + getPadding()) + m_yJumpRange * (static_cast<int>(m_options.size()) - 1)),
											  selectionArrow));
		}//end of if
		else
		{
			executeOption(Console::navigation(intPair(xJumpRange, m_yJumpRange),
											  intPair(m_menuXPos + 1, m_menuXPos + 1),
											  intPair(m_menuYPos + getPadding(),
												     (m_menuYPos + getPadding()) + m_yJumpRange * (static_cast<int>(m_options.size()) - 1)),
											  selectionArrow));
		}//end of else 
	}//end of else
}
/*public_function_definitions_end*/
/*__MENU__*/
Last edited on
console.h
-----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef CONSOLE_GRAPHICS_H
#define CONSOLE_GRAPHICS_H

#include<utility>

using intPair     = std::pair<int, int>;
using voidFuncPtr = void(*)();

namespace Console 
{
	intPair navigation(const intPair& jumpLength,
		               const intPair& xRange,
		               const intPair& yRange,
					   unsigned const char symbol);
	void putSymbol(int xPos, int yPos, const char symbol);
	void clrSec(int curX, int curY, int destX, int destY);
	void drawBox(int hight, int length, int x, int y);
	void gotoxy(int x, int y);
	void clrScr();
}

#endif 


console.cpp
--------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include"Constants.h"
#include"Console.h" 
#include<iostream>
#include<Windows.h>
#include<conio.h>

namespace
{
	constexpr int			downKey { 80 };
	constexpr int			rightKey{ 77 };
	constexpr int			leftKey { 75 };
	constexpr int			upKey   { 72 };
	constexpr unsigned char enterKey{ 13 }; 
	constexpr int			padding { 2 };
}

intPair Console::navigation(const intPair& jumpLength,
	                        const intPair& xRange,
	                        const intPair& yRange,
							unsigned const char symbol)
{
	int curX{ xRange.first };
	int curY{ yRange.first };
	do {
		putSymbol(curX, curY, symbol);
		gotoxy(curX, curY);
		if(_getch() == enterKey) return intPair(curX, curY);
		switch (static_cast<int>(_getch()))
		{
		case upKey: 
		{
			if ((curY - jumpLength.second) < yRange.first)
			{
				curY = yRange.second;
				putSymbol(curX, yRange.first, ' ');
			}//end of if
			else
			{
				curY -= jumpLength.second;
				putSymbol(curX, curY + jumpLength.second, ' ');
			}//end of else
		}//end of case 
		break;
		case downKey: 
		{
			if ((curY + jumpLength.second) > yRange.second)
			{
				curY = yRange.first;
				putSymbol(curX, yRange.second, ' ');
			}//end of if
			else
			{
				curY += jumpLength.second;
				putSymbol(curX, curY - jumpLength.second, ' ');
			}//end of else
		}//end of case 
		break;
		case leftKey: 
		{
			if ((curX - jumpLength.first) < xRange.first)
			{
				curX = xRange.second;
				putSymbol(xRange.first, curY, ' ');
			}//end of if
			else
			{
				curX -= jumpLength.first;
				putSymbol(curX + jumpLength.first, curY, ' ');
			}//end of else
		}//end of case 
		break;
		case rightKey: 
		{
			if ((curX + jumpLength.first) > xRange.second)
			{
				curX = xRange.first;
				putSymbol(xRange.second, curY, ' ');
			}//end of if
			else
			{
				curX += jumpLength.first;
				putSymbol(curX - jumpLength.first, curY, ' ');
			}//end of else
		}//end of case 
		break;
		}//end of switch
	} while (true);
}

inline void Console::putSymbol(int xPos, int yPos, const char symbol)
{
	gotoxy(xPos, yPos);
	std::cout << symbol;
}

void Console::clrSec(int curX, int curY, int destX, int destY)
{
	gotoxy(curX, curY);
	for (int col{}; col < destY; ++col)
		for (int pos{}; pos < destX; ++pos)
			std::cout << ' ';
	gotoxy(curX, curY);
}

void Console::drawBox(int hight, int length, int x, int y)
{
	gotoxy(x, y);
	std::cout << Shapes::TLAP;
	for (int i{}; i < (length + padding); ++i) std::cout << Shapes::HP; 
	std::cout << Shapes::TRAP;

	for (int i{}; i < hight; ++i)
	{
		gotoxy(x, ++y);
		std::cout << Shapes::VP;
		gotoxy(x + length + 3, y); // x + length + 3 represents the furthest boarder						   
		std::cout << Shapes::VP;
	}//end of for

	gotoxy(x, ++y);
	std::cout << Shapes::BLAP;
	for (int i{}; i < (length + padding); ++i) std::cout << Shapes::HP; 
	std::cout << Shapes::BRAP;
}

inline void Console::gotoxy(int x, int y)
{
	COORD pos = { static_cast<short>(x), static_cast<short>(y) };
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);
}

void Console::clrScr()
{
	CONSOLE_SCREEN_BUFFER_INFO csbi;
	HANDLE                     hStdOut;
	DWORD                      count;
	DWORD                      cellCount;
	COORD                      homeCoords = { 0, 0 };

	hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);						
	if (hStdOut == INVALID_HANDLE_VALUE) return;

	if (!GetConsoleScreenBufferInfo(hStdOut, &csbi)) return;
	cellCount = csbi.dwSize.X *csbi.dwSize.Y;

	if (!FillConsoleOutputCharacter(
		hStdOut,
		(TCHAR) ' ',
		cellCount,
		homeCoords,
		&count
	)) return;

	SetConsoleCursorPosition(hStdOut, homeCoords);
}
Constants.h
---------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#ifndef CONSTANTS_H
#define CONSTANTS_H

namespace Shapes
{
	extern const unsigned char BRAP;
	extern const unsigned char TRAP;
	extern const unsigned char BLAP;
	extern const unsigned char TLAP;
	extern const unsigned char HP;
	extern const unsigned char VP;
}

/*
#########
meanings:
#########
BRAP - Bottom right angle piece
TRAP - Top right angle piece
BLAP - Bottom left angle piece
TLAP - Top left angle piece
HP   - Horizontal piece
VP   - Vertial piece
SA   - Selection arrow
*/

#endif  


Constants.cpp
----------------
1
2
3
4
5
6
7
8
9
10
11
#include"Constants.h"

namespace Shapes
{
	extern const unsigned char BRAP{ 188 };
	extern const unsigned char TRAP{ 187 };
	extern const unsigned char BLAP{ 200 };
	extern const unsigned char TLAP{ 201 };
	extern const unsigned char HP  { 205 };
	extern const unsigned char VP  { 186 };
}


source.cpp
-------------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include"Menu.h"

void printA()
{
	Console::clrScr();
	std::cout << "A\n";
}

void printB()
{
	Console::clrScr();
	std::cout << "B\n";
}

void printC()
{
	Console::clrScr();
	std::cout << "C\n";
}

void printD()
{
	Console::clrScr();
	std::cout << "D\n";
}

int main() {
	Menu m;
	m.setMenuXY(10, 2);
	//m.enableBoxedOptions(3);
	m.addOption("option A", printA);
	m.addOption("option print B", printB);
	m.addOption("print C", printC);
	m.addOption("option option print D", printD);
	m.deleteOption("print C");
	m.addHeader("Main Menu");
	m.start();
	return 0;
}
Last edited on
you can now decide if you want the options to be boxed or not and specify the box size (which must be an odd number for it to look aesthetic). And you can now add a header if you want. Apart from that i fixed some bugs i didn't see before.
Hi,

Some thing I noticed:

There are quite a lot of functions that should be marked const, they don't change the class state.

The cast is unnecessary here:
1
2
3
void Menu::start()
{
	if (static_cast<int>(m_options.size()) == 0) 



If you change the formatting in your IDE editor, to change tabs to spaces (4 say), then the code will display better here. This site changes tabs to be 8 spaces which results in excessive indenting.

Good Work !!
Hello,

Thanks for the feedback on the updated code, i will mark the functions i can as const i just forgot about it. I will edit the code above with the const.

If you change the formatting in your IDE editor, to change tabs to spaces (4 say), then the code will display better here. This site changes tabs to be 8 spaces which results in excessive indenting.

Didn't know about that thanks for the tip!

EDIT: i made the relevant functions const and implemented a const operator overload of [] in insertion_map.h and i get the following errors:
-
Severity Code Description Project File Line Suppression State
Error C2678 binary '[': no operator found which takes a left-hand operand of type 'const std::map<varOne,varTwo,std::less<_Kty>,std::allocator<std::pair<const _Kty,_Ty>>>' (or there is no acceptable conversion) vlog c:\users\user\documents\visual studio 2015\projects\vlog\vlog\insertion_map.h 45
Severity Code Description Project File Line Suppression State
Error C2440 '<function-style-cast>': cannot convert from 'const std::basic_string<char,std::char_traits<char>,std::allocator<char>>' to 'std::pair<varOne,varTwo>' vlog c:\users\user\documents\visual studio 2015\projects\vlog\vlog\insertion_map.h 45


the code above is edited
Last edited on
EDIT: i made the relevant functions const and implemented a const operator overload of [] in insertion_map.h and i get the following errors:


I don't know why there is an error there (I don't know enough about it), but the cppreference example shows return by reference for both the const and non-const versions. Not sure if that will make any difference.
Question: When overloading operator[] for std::map , doesn't the index have to be the same type as the key?
Question: When overloading operator[] for std::map , doesn't the index have to be the same type as the key?

It is, i call it through the vector because the vector does the bound check for us.

I don't know why there is an error there (I don't know enough about it), but the cppreference example shows return by reference for both the const and non-const versions. Not sure if that will make any difference.

Returning by reference allows us to modify a value at the specified index through the non const [] overload on a non const object therefore i don't think that it causes the problem but i cant return by reference because i return a temporary object that gets destroyed beyond the scope of the operator overload. Although i think i have a clue why it does throw an error:
when i call m_insertionMap[m_insertionOrder[index]] in the const version of the overload there is no suitable conversion for the function pointer to const which makes sense, but again its only my guess not sure about that, cant find any info online on this specific case.
Last edited on
I got to fix the problem but with a way that i don't really like:
i made std::map<varOne, varTwo> m_insertionMap;
into mutable std::map<varOne, varTwo> m_insertionMap;
which allows the map to ignore constness in const functions.

Basically the problem is that the operator[] cant convert the m_insertion map to its regular value from a const value so if you have an idea on how to make the m_insertionMap[m_insertionOrder[index]] not const in the const [] overload it would solve the problem any ideas ?
Found a second solution we have options now! XD
we can const_cast the map on the return of the overloaded const [] operator:
1
2
3
4
5
const std::pair<varOne, varTwo> operator[](const int index) const
	{ //The vector will throw an error if were out of range 
		return std::pair<varOne, varTwo>(m_insertionOrder[index],
		          	                     const_cast<Insertion_map<varOne,varTwo>*>(this)->m_insertionMap[m_insertionOrder[index]]);
	}


In other words we tell that we want to const_cast the current object's (this pointer) m_insertionMap into a non const object of type Insertion_map pointer allowing us to return a temporary non const version of the function pointer
Last edited on
Hi,

I have an idea that you could extend your menu system to cope with sub-menus. Each sub menu should automatically have options to: return to the previous menu; return to the main menu; or quit altogether. The system should work with an arbitrary number of levels of sub menus. There might be various Design Patterns that could come in handy here.

With your current code, I still don't think you need template code for your insertion map: the types will always be the same. It also looks like your Insertion map member functions do the same thing as the STL container. For these reasons you might not need Insertion map at all. Could you have a std::vector<std::pair<std::string, std::function>> ? Or even a std::tuple version ? I mention std::function again because it is more general:

cppreference wrote:
Class template std::function is a general-purpose polymorphic function wrapper. Instances of std::function can store, copy, and invoke any Callable target -- functions, lambda expressions, bind expressions, or other function objects, as well as pointers to member functions and pointers to data members.


Have fun!
Hello,

With your current code, I still don't think you need template code for your insertion map: the types will always be the same. It also looks like your Insertion map member functions do the same thing as the STL container. For these reasons you might not need Insertion map at all. Could you have a std::vector<std::pair<std::string, std::function>> ? Or even a std::tuple version ?

Thanks for pointing this out! i just noticed i made things far more complex than they should be i will definitely make the needed changes

I mention std::function again because it is more general:

Ok ok i will use std::function you convinced me :)

just one question about his:
I have an idea that you could extend your menu system to cope with sub-menus. Each sub menu should automatically have options to: return to the previous menu; return to the main menu; or quit altogether. The system should work with an arbitrary number of levels of sub menus. There might be various Design Patterns that could come in handy here.

Isn't it already possible ? i mean you can always make 2 objects of type menu and link menu.start() with the string "sub menu" which will call upon that menu and in there link to the "main menu" (association pattern, main menu will know of the existence of sub menu and vise versa).
Or did you mean to add another function called addSubMenu that will look as following:
void addSubMenu(const Menu& subMenu) {//all the magic goes in here}; ?
Hello,

I updated the code above.

I really tried to use std::function but because it is a wrapper i cant use "==" on two objects of it. It causes a problem because the addOption function must check for both the name and the function pointer, and std::function simply wouldn't allow me to make the comparison inside of the lambda whereas the old c style function pointer with the c++11 "using" keyword does as we just compare addresses.

will look into the design patterns now, thanks for the links!
As far as i can understand the creational builder pattern is most suitable for the menu:
Builder: Separates object construction from its representation

I think its logical to first construct the menu with its sub menus and only than represent them.

Lets say the Menu class is the builder and another two classes mainMenu and subMenu are the concrete builders while there also is another class named makeMenu who is the director that allows us to create the product (i.e. the sub menu and main menu).
from the link: http://www.dofactory.com/net/builder-design-pattern


did i get it right or am i completely wrong here ?
Last edited on
Hi,

It can be confusing some times to decide which pattern(s) to use. In the example in the SO link I gave, it used the Visitor Pattern, and mentioned Composite. I have the Design Patterns book by Gamma, Helm, Johnson and Vlissides (aka the gang of four) and to me it seems the Composite might be the best fit. The structure it gave goes like this, in my words:

A Container (Composite) consists of: 0 or more Containers, and 0 or more Leaf items.

So for you, a Container is a menu, while a Leaf item is a concrete menu item that does something. This is also analogous to a file system where the container is a directory, the leaves are files.

DP's can also be combined, so your idea of using Builder to build the menus looks to be fine in combination with Composite.

In my book, it has a diagram of DP relationships, the following are all related to Composite:

Builder for "creating composites" ;
Decorator for "adding responsibilities to objects";
Flyweight for "sharing composites" ;
Visitor for "adding operations" ;
Interpreter for "defining grammar" ;
Chain of Responsibility for "defining the chain" ;
Command for "composed using" ;
Iterator for "enumerating children" ;

You might even want to combine several of these.

Here's another way DP can be combined, although it might not be related to what you are doing now, I think it is really useful for lots of things. Am only mentioning it because I think it is nifty :+)

http://www.marco.panizza.name/dispenseTM/slides/exerc/eventNotifier/eventNotifier.html


Iv'e been researching design patterns on https://sourcemaking.com/design_patterns and don't really see a reason to implement the builder or visitor pattern from the following reasons:

Builder pattern use case - if you want to create an object that is composed of complex objects and there is a dependency between all of those objects (a sub menu and a main may allow you to navigate between the two or even go into deeper sub menus but they are not dependant on each other to work).

The problem with the visitor pattern is the way its explained it should be implemented - lets say the menu is our base abstract class and subMenu and mainMenu classes inherit from it (i know there should be a visitor class its not relevant to explain the problem), by the implementation here https://sourcemaking.com/design_patterns/visitor/cpp/1 each public method in menu should be made into a class (i.e. CountVisitor in the link above) but then how do i call the code previously written in void start() for example ? make a protected field in menu class and inherit the start method into mainMenu and subMenu classes as protected? or plain copy and paste the method into each of mainMenu and subMenu as private (this one i really doubt because it will just be code bloat) ? In short the guidelines of this pattern are confusing for menu implementation or unsuitable in my opinion.

The pattern i will be going with: composite.
as you said:
So for you, a Container is a menu, while a Leaf item is a concrete menu item that does something. This is also analogous to a file system where the container is a directory, the leaves are files.

It's much more clear how to implement it.

Another thought on the visitor pattern is i think its possible to achieve:
I have an idea that you could extend your menu system to cope with sub-menus. Each sub menu should automatically have options to: return to the previous menu; return to the main menu; or quit altogether. The system should work with an arbitrary number of levels of sub menus

But it will be analogous to digging a well with your hands as opposed to using a shovel, its possible but there is a better tool for the task.
The pattern i will be going with: composite.


Yes, that's what I was thinking. The additional use of the other patterns is optional, I guess one has to decide whether it is worth it to implement them.

To implement the return to previous or main menu, I imagine the base class will maintain pointers to those things. The quit function can live in the base class as well, because it will be the same for all the menus.
well i redone it in the composite pattern and it works as i wanted but i have a problem with a few small things.

the relevant code:

Menu.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#ifndef MENU_H
#define MENU_H

#include"Console.h"
#include<functional>
#include<iostream>
#include<vector>

class ContainerComponent
{
public:
	virtual const std::string& getName() const = 0;
	virtual void print(int x, int y) const = 0;
	virtual void call() = 0;
};

class CompositeMenu : public ContainerComponent
{
public:
	void addChild(ContainerComponent& child);
	void removeChild(const ContainerComponent& child);

	void addHeader(const std::string& header);
	void enableBoxedOptions(int boxHight = 3); //3 is the minimal size for symmetry
	void disableBoxedOptions();
	void setMenuXY(int x, int y);

	const std::string& getName() const;
	void print(int x, int y) const; //must be private
	void call();

	explicit CompositeMenu(int x = 0, int y = 0, bool enabledBoxedMenu = false)
		: m_menuXPos{ x }, m_menuYPos{ y }, m_enabledBoxedMenu{ enabledBoxedMenu }
	{
	}

private:
	std::vector<ContainerComponent*> m_children;
	std::string					     m_header;
	bool					         m_enabledBoxedMenu;
	bool                             m_enabledHeader{ false };
	int								 m_menuXPos;
	int								 m_menuYPos;
	int                              m_menuHight;
	int                              m_menuLength;
	int                              m_jumpRange{ 1 };

	void updateLengths(const std::string& opt, int padding);
	void printBoxedOptions(int& curXPos, int& curYPos) const;
	void printBareOptions(int& curXPos, int& curYPos) const;
	void printHeader(int& curXPos, int& curYPos) const;
	void executeOption(intPair coords) const;
	void updateMenuHight();
	int getPadding() const;
};

class Action : public ContainerComponent
{
public:
	const std::string& getName() const;
	void print(int x, int y) const; //must be private
	void call();

	explicit Action(const std::string& actionName,
					const std::function<void()> action)
		: m_actionName{ actionName }, m_action{ action } 
	{
	}

private:
	const std::function<void()> m_action;
	const std::string           m_actionName;
};

#endif 


Menu.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
#include"Menu.h"
#include<string>
#include<algorithm>

namespace
{
	constexpr unsigned char selectionArrow    { 175 };
	constexpr int		    boxedLengthPadding{ 4 };
	constexpr int           boxedHightPadding { 2 };
	constexpr int           yRangePadding     { 2 };
	constexpr int           bareLengthPadding { 1 };
	constexpr int		    xJumpRange        { 0 };

	int findMiddle(int num)
	{
		return ((num / 2) + 1);
	}
}

/*|---COMPOSITE_MENU_FUNCTIONS_START---|*/
/*|---PUBLIC_FUNCTIONS_START---|*/
void CompositeMenu::addChild(ContainerComponent& child) //taking const reference causes c2440 error on line 31
{
	if (std::find_if(m_children.begin(),
					 m_children.end(),
					 [&child](const ContainerComponent* cmp)
					 {return cmp->getName() == child.getName(); }) == m_children.end()) //is it possible to still compare function addresses ?
	{
		if (std::is_same<decltype(child), CompositeMenu>::value)
		{
			m_children.emplace_back(&Action(child.getName(), std::bind(&ContainerComponent::call, &child))); 
		}//end of if
		else m_children.emplace_back(&child);
		if (m_enabledBoxedMenu) updateLengths(child.getName(), boxedLengthPadding);
		else                    updateLengths(child.getName(), bareLengthPadding);
	}//end of if
	else std::cout << "An Action with this name already exists.\n";
}

void CompositeMenu::removeChild(const ContainerComponent& child)
{
	if (std::find_if(m_children.begin(),
					 m_children.end(),
					 [&child](const ContainerComponent* cmp)
					 {return cmp == &child; }) != m_children.end())
	{
		m_children.erase(std::remove_if(m_children.begin(),
										m_children.end(),
										[&child](const ContainerComponent* cmp)
										{return cmp == &child; }),
			                            m_children.end());
	}//end of if
	else std::cout << "The option doesn't exist.\n";
}

void CompositeMenu::addHeader(const std::string& header)
{
	m_enabledHeader = true;
	m_header        = header;
	if (static_cast<int>(m_header.length()) + 1 > m_menuLength)
	{
		m_menuLength = static_cast<int>(m_header.length()) + 1;
	}//end of if
}

void CompositeMenu::enableBoxedOptions(int boxHight)
{//must be an odd number for symmetry reasons
	if ((boxHight >= 3) && (boxHight % 2 != 0))
	{
		m_enabledBoxedMenu = true;
		m_jumpRange        = boxHight;
	}//end of if
	else std::cout << "Invalid box size.\n";
}

void CompositeMenu::disableBoxedOptions()
{
	if (m_enabledBoxedMenu)
	{
		m_enabledBoxedMenu = false;
		m_jumpRange = 1; //default jump range with no boxes
		updateMenuHight();
	}//end of if
}

void CompositeMenu::setMenuXY(int x, int y)
{
	Console::setXY(x, y, m_menuXPos, m_menuYPos);
}

/*|---VIRTUAL_FUNCTIONS_START---|*/
const std::string& CompositeMenu::getName() const
{
	return m_header;
}

void CompositeMenu::print(int x, int y) const
{
	Console::drawBox(m_menuHight, m_menuLength, x, y);
	x += 2;
	++y;
	if (m_enabledHeader)        printHeader(x, y);
	if (m_enabledBoxedMenu)     printBoxedOptions(x, y);
	else                        printBareOptions(x, y);
}

void CompositeMenu::call() 
{
	if (static_cast<int>(m_children.size()) == 0)
		std::cout << "No options found, unable to print menu.\n";
	else
	{
		updateMenuHight();
		print(m_menuXPos, m_menuYPos);
		executeOption(Console::navigation(intPair(xJumpRange, m_jumpRange),
			                              intPair(m_menuXPos + 1, m_menuXPos + 1),
			                              intPair(m_menuYPos + getPadding(),
			                              (m_menuYPos + getPadding()) + m_jumpRange * (static_cast<int>(m_children.size()) - 1)),
			                              selectionArrow));
	}//end of else
}
/*|----VIRTUAL_FUNCTIONS_END----|*/
/*|----PUBLIC_FUNCTIONS_END----|*/

/*|---PRIVATE_FUNCTIONS_START---|*/
void CompositeMenu::updateLengths(const std::string& opt, int padding)
{
	if ((static_cast<int>(opt.length()) + padding) > m_menuLength)
	{
		m_menuLength = static_cast<int>(opt.length()) + padding;
	}//end of if
}

void CompositeMenu::printBoxedOptions(int& curXPos, int& curYPos) const
{
	for (size_t index{}; index < m_children.size(); ++index)
	{
		Console::drawBox(m_jumpRange - boxedHightPadding, m_menuLength - boxedLengthPadding, curXPos, curYPos);
		m_children[index]->print(curXPos + 2, curYPos + findMiddle(m_jumpRange) - 1);
		curYPos += m_jumpRange;
	}//end of for
}

void CompositeMenu::printBareOptions(int& curXPos, int& curYPos) const
{
	++curXPos;
	for (size_t index{}; index < m_children.size(); ++index)
	{
		m_children[index]->print(curXPos, curYPos++);
	}//end of for
}

void CompositeMenu::printHeader(int& curXPos, int& curYPos) const
{
	Console::gotoxy(curXPos + 1, curYPos);
	std::cout << m_header;
	Console::gotoxy(curXPos + 1, ++curYPos);
	for (size_t index{}; index < m_header.length(); ++index) std::cout << '-';
	++curYPos;
}

void CompositeMenu::executeOption(intPair coords) const
{
	m_children[(coords.second - (m_menuYPos + getPadding())) / m_jumpRange]->call();
}

void CompositeMenu::updateMenuHight()
{
	if (m_enabledHeader) m_menuHight = m_jumpRange * static_cast<int>(m_children.size()) + 2;
	else                 m_menuHight = m_jumpRange * static_cast<int>(m_children.size());
}

int CompositeMenu::getPadding() const 
{
	if (m_enabledHeader) return(findMiddle(m_jumpRange) + yRangePadding);
	else				 return(findMiddle(m_jumpRange));
}
/*|----PRIVATE_FUNCTIONS_END----|*/
/*|----COMPOSITE_MENU_FUNCTIONS_END----|*/

/*|---ACTION_FUNCTIONS_START---|*/
/*|---PUBLIC_FUNCTIONS_START---|*/
/*|---VIRTUAL_FUNCTIONS_START---|*/
const std::string& Action::getName() const
{
	return m_actionName;
}

void Action::print(int x, int y) const
{
	Console::gotoxy(x, y);
	std::cout << getName();
}

void Action::call() 
{
	m_action();
}
/*|----VIRTUAL_FUNCTIONS_END----|*/
/*|----PUBLIC_FUNCTIONS_END----|*/
/*|----ACTION_FUNCTIONS_END----|*/
Pages: 123