classdef ConsoleMenu < handle
% This source file is subject to version 3 of the GPL license,
% that is bundled with this package in the file LICENSE, and is
% available online at http://www.gnu.org/licenses/gpl.txt
%
% This source file can be linked to GPL-incompatible facilities,
% produced or made available by MathWorks, Inc.
properties (GetAccess = public, SetAccess = private)
Title;
AppData;
Items;
Parent;
% The Select element determines whether this particular menu layer
% allows the selection of multiple items - or a single item. The default is 'One'.
Select = 'One';
% PreSelectCallback and PostSelectCallback may be defined to point
% to the function handler to be executed prior selection is made
% or after.
PreSelectCallback;
PostSelectCallback;
end
methods
function CMobj = ConsoleMenu(Title, AppData, Props)
CMobj.Title = Title;
CMobj.AppData = AppData;
if (nargin == 3)
for t=1:size(Props,2) / 2,
CMobj.(Props{t*2-1}) = Props{t*2};
end
end
end
function ret = DeleteItem(CMobj, id)
ret = false;
if ((id < 1) || (id > size(CMobj.Items,1)))
return;
end
CMobj.Items(id) = [];
ret = true;
end
function InsertItem(CMobj, item, pos)
if (size(CMobj.Items,1) == 0 )
CMobj.Items = { item };
else
switch upper(pos)
case 'BGN'
CMobj.Items = [ { item }; CMobj.Items ];
case 'END'
CMobj.Items = [ CMobj.Items; { item } ];
otherwise
CMobj.Items = [ CMobj.Items(1:pos-1,1); { item }; CMobj.Items(pos:end,1)];
end
end
end
function ret = SetItemProperty(CMobj, itemTag, itemProperty, value)
ret = false;
menuItem = CMobj.GetItemByTag(itemTag);
if ~isempty(menuItem)
menuItem.(itemProperty) = value;
ret = true;
end
end
function ret = Pick(CMobj)
% When it's needed, multiple item selection can be implemented
% here based on CMobj.Select property.
while ( true)
clc;
if (isa(CMobj.PreSelectCallback, 'function_handle'))
CMobj.PreSelectCallback();
end
menuPath = FM.ConsoleMenu.traceMenuPath(CMobj, CMobj.Title);
fprintf('\n%s\n\n',menuPath);
for t=1:size(CMobj.Items,1),
if (CMobj.Items{t}.Separator)
fprintf('\n');
end
if (CMobj.Items{t}.Enabled)
if (CMobj.Items{t}.Checked)
fprintf ('\t%d.\t\t%s [*]\n', t, CMobj.Items{t}.Label);
else
fprintf ('\t%d.\t\t%s\n', t, CMobj.Items{t}.Label);
end
else
fprintf ('\t..\t\t%s\n', CMobj.Items{t}.Label);
end
end
sel = input('\n(Press "q" to quit, "k" for keyboard mode)\n\nPLEASE ENTER A CHOICE: ', 's');
switch upper(sel)
case 'Q'
ret = 0;
return;
case 'K'
CMobj.AppData.KeyboardMode();
otherwise
% Check if input is a hotkey sequence
if regexp(sel, '^!')
hotkey = regexprep(sel, '!', '');
rootMenuObj = CMobj.GetRootMenu();
hotkeyCallback = rootMenuObj.findCallbackByHotkey(hotkey);
if (isa(hotkeyCallback, 'function_handle'))
hotkeyCallback();
end
continue;
end
if (isa(CMobj.PostSelectCallback, 'function_handle'))
CMobj.PostSelectCallback(sel);
end
if (str2num(sel))
sel = str2num(sel);
if ((sel < 1) || sel > size(CMobj.Items,1))
continue;
elseif ~(CMobj.Items{sel}.Enabled)
continue;
end
if (strcmp(class(CMobj.Items{sel}.Callback), 'FM.ConsoleMenu'))
subMenu = CMobj.Items{sel}.Callback;
ret = subMenu.Pick;
% If a sub-menu returns a negative
% code, terminate execution of the
% current menu and return the code.
if (ret < 0)
return;
end
elseif (isa(CMobj.Items{sel}.Callback, 'function_handle'))
ret = CMobj.Items{sel}.Callback(sel);
% If selection callback returns a
% negative value, end the menu and
% return the code.
if (ret ~= 0)
return;
else
continue;
end
end
else
continue;
end
end
end
end
function ret = GetItemByTag(CMobj, itemTag)
ret = {};
for t=1:size(CMobj.Items,1),
if (strcmp(CMobj.Items{t}.Tag,itemTag))
ret = CMobj.Items{t};
return;
end
end
end
function rootMenu = GetRootMenu(cmObj)
if ~isempty(cmObj.Parent)
rootMenu = cmObj.Parent.GetRootMenu();
else
rootMenu = cmObj;
return;
end
end
end
methods (Access = private)
function callback = findCallbackByHotkey(cmObj, hotkey)
callback = NaN;
% Check items of the menu
for i=1:size(cmObj.Items,1)
if (strcmpi(hotkey, cmObj.Items{i}.Hotkey) && strcmpi(cmObj.Items{i}.Enabled, 'true'))
callback = cmObj.Items{i}.Callback;
return;
elseif isa(cmObj.Items{i}.Callback, 'FM.ConsoleMenu')
callback = cmObj.Items{i}.Callback.findCallbackByHotkey(hotkey);
if (isa(callback, 'function_handle'))
return
end
end
end
end
end
methods (Access = private, Static)
function menuPath = traceMenuPath(cmObj, menuPath)
if ~isempty(cmObj.Parent)
menuPath = [ cmObj.Parent.Title, ' > ', menuPath ];
FM.ConsoleMenu.traceMenuPath(cmObj.Parent, menuPath);
else
return;
end
end
end
end