Code covered by the BSD License  

Highlights from
AutoWarnDlg

image thumbnail
from AutoWarnDlg by Jan Simon
Self-terminating modal warning dialog with flashing icon

AutoWarnDlg(Msg, Name, Opt)
function [Reply, TimeOut] = AutoWarnDlg(Msg, Name, Opt)
% Self-terminating modal warning dialog
% [Reply, TimeOut] = AutoWarnDlg(Msg, Name, [Opt])
% A warning dialog is displayed with a flashing exclamation mark. The dialog is
% terminated automatically with the default reply, if the user does not press a
% button in a specified period of time.
%
% This can be useful e.g. in functions, which are checked with automated tests,
% because the dialog does not block the execution completely.
%
% INPUT:
%   Msg:     Either a string or a cell string. For cell strings, empty lines
%            are inserted between the strings.
%   Name:    Window title as string.
%   Opt:     Struct to specify some options - all are optional:
%      .Delay: Number of seconds until the dialog closes automatically.
%            Use Inf for infinite waiting. Default: 60.
%      .Interpreter: Text interpreter 'none' or 'tex' (default).
%            In tex-mode use "\\" for a slash, "\_" for an underscore,
%            "\bf<your message>\rm" for bold face, etc.
%      .Wrap: Control wrapping of the Msg text. The width can be determined
%            automatically or manually depending on the different input types:
%            - Logical flag or double 1 or 0: The text is wrapped to get a width
%              of approximately 3/2 of the height.
%            - Double: Width of the textbox in pixels, minimum is 200.
%            - String: The width of this string is used for wrapping.
%            See NOTE below for wrapping TeX formatted strings.
%            Default: TRUE.
%      .Button: Cell string with 0 to 3 strings, which appear as buttons.
%            The 1st string is the rightmost button, which is the default
%            choice, if no button or the [ESC] key is pressed.
%            Default: {'Ok'}.
%
% OUTPUT:
%   Reply:   String of the pressed button, or the default button, if the
%            [Escape] key was pressed, the dialog was closed externally or the
%            timeout was reached.
%   TimeOut: Logical flag, TRUE if the dialog was terminated by a timeout.
%
% The button focus can be moved with [Left] and [Right] keys. [Return] and
% [Space] activate the currently focussed button, [ESC] the initial default
% button. The key [c] copies the current message to the clipboard (not
% [Ctrl-C]!).
%
% NOTE: The keyboard functions are not working, if the dialog was called from a
% callback of an UICONTROL with the property "Interruptible" set to 'off'.
% The button works even in this case, because they are toggle buttons.
%
% NOTE: Unfortunately the wrapping is performed by TEXTWRAP, which works for
% UICONTROLs only. In consequence the effects of the TeX interpreter cannot be
% considered currently. Perhaps I publish a TEXTWRAP, which works on TEXT
% objects also.
%
% EXAMPLES:
% A warning, which disappears after 15 seconds:
%   AutoWarnDlg('This is a \bfwarning\rm!', 'Ups', struct('Delay', 15));
% Warn and allow the user to cancel or accept:
%   Opt.Delay  = 15;
%   Opt.Button = {'Cancel', 'Reject', 'Accept'};  % First is default
%   [Reply, TimeOut] = AutoWarnDlg( ...
%        {'This is a \bfwarning\rm!', 'Accept, reject or cancel?'}, ...
%        'Question', Opt);
%   fprintf('Reply:   %s\nTimeout: %d\n', Reply, TimeOut);
%
% NOTE: In a STRUCT command double braces are needed to create a cell string!
%
% Tested: Matlab 6.5, 7.7, 7.8
% Author: Jan Simon, Heidelberg, (C) 2009 jATnMINUSsimonDOTde
% License: BSD: Feel free to copy and modify mentioning the original author.
%
% See also WARNDLG, MSGBOX.

% $JRev: R0d V:004 Sum:0533E3C9 Date:11-Aug-2009 18:29:58 $
% $File: User\JSim\Published\AutoWarnDlg\AutoWarnDlg.m $
% History:
% 001: 29-Jul-2009 09:54, No external subfunction from the GL-toolbox.
%      Adjusted XWarnDlg V:069 to be published on Mathwork's FEX.
% 002: 31-Jul-2008 02:51, SQRT inserted to calculate nice size.
% 003: 03-Aug-2009 23:17, [Opt.Sec] -> [Opt.Delay].
%      Typo in the help section found by Jiro Doke - thanks!
% 004: 11-Aug-2009 01:34, 2nd output [TimeOut], expanded input [Opt.Wrap].
%      If the update of the dialog fails, but the dialog is still open, the
%      program stops with an error message now.

% Initialize: ==================================================================
% Global Interface: ------------------------------------------------------------
% Initial values: --------------------------------------------------------------
% Program Interface: -----------------------------------------------------------
nArg = nargin;
if nArg == 0
   Msg      = {'This is an \bfunknown warning\rm!', 'Keep care!'};
   Name     = 'Important';
   Opt.Wrap = false;
elseif nArg == 1
   Name = 'Warning';
   Opt  = [];
elseif nArg == 2
   Opt  = [];
end

% Parse Opt struct:
[Delay, Interpreter, Wrap, Button] = GetOpt(Opt, ...
   {'Delay', 'Interpreter', 'Wrap', 'Button'}, ...
   {60,      'tex',         true,   {}      });

if isempty(Button)
   Button = {'ok'};
elseif ischar(Button)
   Button = {Button};
elseif iscellstr(Button) == 0
   error(['*** ', mfilename, ': [Opt.Button] must be a string or cell string.']);
end
if numel(Button) > 3
   error(['*** ', mfilename, ': [Opt.Button] has more than 3 strings.']);
end

if isa(Msg, 'cell') == 0
   Msg = {Msg};
end

if isempty(Wrap)
   Wrap = true;  % Automatic wrapping
elseif islogical(Wrap)
   Wrap = any(Wrap);
elseif isnumeric(Wrap)
   if Wrap <= 1
      Wrap = (Wrap ~= 0);
   end
elseif not(ischar(Wrap))
   error(['*** ', mfilename, ...
         ': [Opt.Wrap] must be a logical, string or number.']);
end

% User Interface: --------------------------------------------------------------
% Do the work: =================================================================
% Adjust to your demands:
TextFontName   = get(0, 'DefaultTextFontName');
TextFontSize   = 10;
ButtonFontName = TextFontName;
ButtonFontSize = 12;

% Window size:
bakUnits    = get(0, 'Units');
set(0, 'Units', 'pixels');
ScreenVec   = get(0, 'ScreenSize');
set(0, 'Units', bakUnits);
ScreenSize  = ScreenVec(3:4);

WinWidth    = 200;   % Minimal extent, expanded on demand
WinHeight   = 100;
WinWidthMax = 800;
DlgExtent   = [WinWidth, WinHeight];
DlgOrigin   = (ScreenSize - DlgExtent) / 2;

DlgH = figure( ...
   'Name',          Name, ...
   'IntegerHandle', 'off', ...
   'WindowStyle',   'normal', ...
   'MenuBar',       'none', ...
   'NumberTitle',   'off', ...
   'Resize',        'off', ...
   'Units',         'pixels', ...
   'Position',      [DlgOrigin, DlgExtent], ...
   'NextPlot',      'add', ...
   'Visible',       'off', ...
   'Renderer',      'painters', ...
   'DoubleBuffer',  'on', ...
   'BackingStore',  'off', ...
   'DefaultUIControlFontSize', ButtonFontSize, ...
   'DefaultUIControlFontName', ButtonFontName, ...
   'DefaultTextFontName',      TextFontName, ...
   'DefaultTextFontSize',      TextFontSize, ...
   'DefaultTextInterpreter',   Interpreter, ...
   'HandleVisibility',         'on');

% Axis over entire dialog box for text and graphics:
FullAxis = axes( ...
   'Parent',   DlgH, ...
   'Units',    'pixels', ...
   'Position', [1, 1, DlgExtent], ...
   'Visible',  'off', ...
   'XLimMode', 'manual', 'XLim', [1, WinWidth], ...
   'YLimMode', 'manual', 'YLim', [1, WinHeight], ...
   'NextPlot', 'add', ...
   'DrawMode', 'normal');

% Get Warn icon as RGB array:
DlgBG   = get(DlgH, 'Color');
IconRGB = WarnIcon_L(DlgBG);
IconY   = size(IconRGB, 1);
IconX   = size(IconRGB, 2);

% Create buttons: --------------------------------------------------------------
nButton   = length(Button);
ButtonH   = zeros(nButton, 1);
FrameH    = ButtonH;            % Faster than: zeros(nButton, 1);
ButtonExt = zeros(nButton, 4);
FrameAdd  = [-1, -1, 1, 1];
for iButton = 1:length(Button)
   pos              = [WinWidth - 70 * iButton, 8, 60, 24];
   ButtonH(iButton) = uicontrol(DlgH, ...
      'Style',    'ToggleButton', ...
      'String',   Button{iButton}, ...
      'Position', pos);
   FrameH(iButton) = rectangle( ...
      'Position',  pos + FrameAdd, ...
      'EdgeColor', 'none', ...
      'FaceColor', 'none');
   ButtonExt(iButton, :) = get(ButtonH(iButton), 'Extent');
end  % for iButton
UD.FrameH  = FrameH;
UD.ButtonH = ButtonH;

% Match window width to buttons:
maxButtonWidth = max(ButtonExt(:, 3));
if maxButtonWidth > 52
   if (maxButtonWidth + 10) * nButton + 40 < 440
      ButtonWidth(1:nButton) = maxButtonWidth + 10;
   else
      ButtonWidth = max(24, ButtonExt(:, 3)) + 10;
   end
   AllButtonWidth = sum(ButtonWidth) + 10 * nButton + 60;
else  % Small enough for default size:
   ButtonWidth(1:nButton) = 60;
   AllButtonWidth         = 70 * nButton + 60;
end

% Current default button:
UD.ButtonFocus = 1;
if nButton > 1
   set(FrameH(1), 'EdgeColor', zeros(1, 3));
end

% Create message box: ----------------------------------------------------------
% (Unfortunately the UICONTROL cannot handle TeX interpreter!)
minTextWidth = WinWidth - 10 - IconX;
TextWidth    = max(AllButtonWidth, minTextWidth);  % Adjusted on demand
blindH       = uicontrol(DlgH, 'Style', 'text', ...
   'String',   '', ...
   'Position', [0, 0, TextWidth, 500], ...
   'FontSize', TextFontSize, ...     % Skip default ButtonFontSize
   'FontName', TextFontName, ...     % Skip default ButtonFontName
   'Visible',  'off');

if ischar(Wrap)  % Dummy string to get width:
   set(blindH, 'String', Wrap);
   TextExt = ceil(get(blindH, 'Extent'));
   set(blindH, 'Position', [0, 0, max(minTextWidth, TextExt(3) + 20), 500]);
   WrapMsg = textwrap(blindH, Msg);
   
elseif isa(Wrap, 'double')  % Width in pixels:
   set(blindH, 'Position', [0, 0, max(minTextWidth, Wrap), 500]);
   WrapMsg = textwrap(blindH, Msg);
   
elseif Wrap  % Automatic wrapping:
   % Find a nice size for the text with approximate 2/3 size:
   WrapMsg = textwrap(blindH, Msg);
   set(blindH, 'String', WrapMsg);
   TextExt = ceil(get(blindH, 'Extent'));
   
   % Try a width/height approximately 3/2:
   if TextExt(4) / 2 > TextExt(3) / 3
      TextExt(3) = 3 * sqrt(TextExt(3) * TextExt(4) * 1.5) / 2;
   end
   TextWidth = min(WinWidthMax - 40 - IconX, max(TextWidth, TextExt(3) + 20));
   set(blindH, 'Position', [0, 0, TextWidth, 500]);  % Interpreter = none
   WrapMsg = textwrap(blindH, Msg);
   
else   % No wrapping:
   WrapMsg = Msg;
end
delete(blindH);

% Final text object (Interpreter defined in inputs):
TextH = text(20 + IconX, WinHeight - 10, WrapMsg, ...
   'Parent',            FullAxis, ...
   'VerticalAlignment', 'top');
UD.TextH = TextH;
TextExt  = ceil(get(TextH, 'Extent'));

% Match size for button and text:
ContWidth = max(IconX + 40 + TextExt(3), AllButtonWidth);
WinWidth  = max(WinWidth, min(ContWidth, WinWidthMax));
WinHeight = min(40 + max(TextExt(4), IconY) + 14, ScreenSize(2) - 86);
DlgExtent = [WinWidth, WinHeight];
DlgOrigin = ceil((ScreenSize - DlgExtent) / 2);
set(DlgH, 'Position', [DlgOrigin, DlgExtent]);

% Move text and buttons:
set(FullAxis, ...
   'Position', [1, 1, DlgExtent], ...
   'XLim', [1, WinWidth], 'YLim', [1, WinHeight]);
set(TextH, 'Position', [20 + IconX, WinHeight - 10], 'Visible', 'on');

pos = [WinWidth, 8, 60, 24];
for iButton = 1:length(Button)
   pos(1) = pos(1) - ButtonWidth(iButton) - 10;
   pos(3) = ButtonWidth(iButton);
   set(ButtonH(iButton), 'Position', pos);
   set(FrameH(iButton),  'Position', pos + FrameAdd);
end  % for iButton

% Draw the Icon:
tmpAxisH = axes( ...
   'Parent',   DlgH, ...
   'Units',    'pixels', ...
   'NextPlot', 'add', ...
   'Position', [10, WinHeight - 10 - IconY, IconX, IconY], ...
   'HitTest',  'off', ...
   'Visible',  'off', ...
   'YDir',     'reverse', ...
   'XLimMode', 'manual',    'YLimMode', 'manual', ...
   'XLim',     [0.5, 46.5], 'YLim', [0.5, 48.5]);
IconH = image(IconRGB, 'Parent', tmpAxisH, 'EraseMode', 'none');

% Wait for deleting of the window:
set(DlgH, 'Visible', 'on', 'HandleVisibility', 'callback', ...
   'KeyPressFcn', @localKeyPress, ...
   'UserData',    UD);

% Write remaining seconds:
TimeFmt = '%02d';
TimeH   = text(4, 1, sprintf(TimeFmt, Delay), ...
   'Parent',   FullAxis, ...
   'FontSize', 7, ...
   'FontName', 'fixedwidth', ...
   'VerticalAlignment',   'bottom', ...
   'HorizontalAlignment', 'left', ...
   'Interpreter',         'none');

% Ugly problems of Matlab's Callback methods:
% The callback of the button is not processed when this routine was called from
% a callback with Interruptible==off... Therefore the value of the toggle button
% is tested too.
IconNo          = zeros(size(IconRGB));
IconNo(:, :, 1) = DlgBG(1);
IconNo(:, :, 2) = DlgBG(2);
IconNo(:, :, 3) = DlgBG(3);
IconDiff        = IconRGB - IconNo;

% The wait loop: ---------------------------------------------------------------
Ti      = datenum(clock) * 86400;
TEnd    = Ti + Delay;
q       = sin(0:0.12:pi) .^ 2;
nq      = length(q);
iq      = 1;
pressed = 0;
cValue  = {'Value'};  % GET(H, {Prop}) replies a cell even for scalar H!
while and(Ti < TEnd, any(pressed) == 0)
   % Note: PAUSE does not advance CPUTIME on Matlab 7.
   % Therefore the slower DATENUM(CLOCK) is used here.
   pause(max(0.01, 0.035 - datenum(clock) * 86400 + Ti));
   Ti = datenum(clock) * 86400;
   try
      set(TimeH, 'String', sprintf(TimeFmt, round(TEnd - Ti)));
   catch  % Handle TimeH is invalid, if the dialog was closed externally:
      if ishandle(DlgH)
         error(['*** ', mfilename, ': Unexpected problem: ', ...
               char(10), lasterr]);
      end
      break;
   end
   
   % Check for a pressed key:
   try
      pressedC = get(ButtonH, cValue);
      pressed  = cat(2, pressedC{:});
      set(IconH, 'CData', IconRGB - IconDiff * q(iq));
   catch  % Handle IconH is invalid, if the dialog was closed externally:
      if ishandle(DlgH)
         error(['*** ', mfilename, ': Unexpected problem: ', ...
               char(10), lasterr]);
      end
      break;
   end
   iq = rem(iq, nq) + 1;
end

% Delete the window if not done before: ----------------------------------------
if ishandle(DlgH)
   delete(DlgH);
end
drawnow;

% Copy information about hit button to output, if requested:
if nargout
   TimeOut  = (Ti > TEnd);
   fPressed = find(pressed);        % [pressed] is not LOGICAL!
   if TimeOut || isempty(fPressed)  % After closing the window manually
      Reply = Button{1};            % The default button
   else
      Reply = Button{fPressed};
   end
end

return;


% ******************************************************************************
function RGBIcon = WarnIcon_L(BG)
% Storing the icon as CHAR array takes less memory.
ind = [ ...
      'aaaaaaaaaaaaaaaaaaaakjbkaaaaaaaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaaaaaajlcccccfaaaaaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaaaaafccclccccljaaaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaaaafcclbmddlcclkaaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaaajlcfoooeggdcclaaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaaafpfoooiheggdccjaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaabcpnoihhhhhgglplaaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaaafpfoihhhhhhhgdpcbaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaablpnohhhhhhhhhgfplaaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaaafpboihhhhhhhhhgdpcbaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaabccnohhhhggghhhhgfplaaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaaafpboihhhdfffdhhhgdccbaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaajccnohhhhfpppfhhhhgfplaaaaaaaaaaaaa'; ...
      'aaaaaaaaaaafpboihhhhfcccfhhhhgdccbaaaaaaaaaaaa'; ...
      'aaaaaaaaaajclnohhhhhfcccfhhhhhgfplaaaaaaaaaaaa'; ...
      'aaaaaaaaaafpboihhhhhfcccfhhhhhgdccbaaaaaaaaaaa'; ...
      'aaaaaaaaajclnohhhhhhfcccfhhhhhhgfplaaaaaaaaaaa'; ...
      'aaaaaaaaafpboihhhhhhfcccfhhhhhhgdccbaaaaaaaaaa'; ...
      'aaaaaaaajclnohhhhhhhfcccfhhhhhhhgfplaaaaaaaaaa'; ...
      'aaaaaaaafpboihhhhhhhfcccfhhhhhhhgdccbaaaaaaaaa'; ...
      'aaaaaaajclnohhhhhhhhfcccdhhhhhhhhgfplaaaaaaaaa'; ...
      'aaaaaaafpboihhhhhhhhdcccdhhhhhhhhedlcbaaaaaaaa'; ...
      'aaaaaajclnohhhhhhhhhdcccdhhhhhhhhhgfplkaaaaaaa'; ...
      'aaaaaafpfoihhhhhhhhhgcccghhhhhhhhhedlcbaaaaaaa'; ...
      'aaaaajclnohhhhhhhhhhgcccghhhhhhhhhhgfclkaaaaaa'; ...
      'aaaaalpfoihhhhhhhhhhhccchhhhhhhhhhhedlcbaaaaaa'; ...
      'aaaabclnohhhhhhhhhhhhccchhhhhhhhhhhhgfclkaaaaa'; ...
      'aaaalcboihhhhhhhhhhhhlcfhhhhhhhhhhhhedlcbaaaaa'; ...
      'aaabclkohhhhhhhhhhhhhfcfhhhhhhhhhhhhhgfclkaaaa'; ...
      'aaalcboihhhhhhhhhhhhhdcdhhhhhhhhhhhhhedlcbaaaa'; ...
      'aajclkohhhhhhhhhhhhhhdlghhhhhhhhhhhhhhgfclkaaa'; ...
      'aalcboihhhhhhhhhhhhhhhghhhhhhhhhhhhhhhedlcbaaa'; ...
      'abcloohhhhhhhhhhhhhhhggghhhhhhhhhhhhhhhgfclaaa'; ...
      'alcboihhhhhhhhhhhhhhgllldhhhhhhhhhhhhhhgdlcbaa'; ...
      'bcloohhhhhhhhhhhhhhhlpcpcghhhhhhhhhhhhhhgfclaa'; ...
      'lcfoihhhhhhhhhhhhhhgcccccdhhhhhhhhhhhhhhgdlcba'; ...
      'clmoihhhhhhhhhhhhhhhcccccghhhhhhhhhhhhhhgglcla'; ...
      'clmoihhhhhhhhhhhhhhhdpppfhhhhhhhhhhhhhhhgglcla'; ...
      'clmoihhhhhhhhhhhhhhhhgdghhhhhhhhhhhhhhhhgglclj'; ...
      'ccbooihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhegglclj'; ...
      'ccloooihhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhgggfccfj'; ...
      'bccfoooeggggggggggggggggggggggggggggggggdcccbj'; ...
      'accclmngdddddddddddddddddddddddddddddddfcccfbk'; ...
      'abccccllllllllllllllllllllllllllllllllcccclbba'; ...
      'aablcccccccccccccccccccccccccccccccccccccfbbka'; ...
      'aaabbllllllllllllllllllllllllllllllllllfbbbkaa'; ...
      'aaaajbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbkaaa'; ...
      'aaaaaakjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjkaaaaa'];

map = cat(1, BG, ...
   [  123, 123, 123; ...
      16,    8,   8; ...
      156, 107,  16; ...
      247, 181,  33; ...
      90,   66,   8; ...
      214, 156,  16; ...
      255, 206,   0; ...
      255, 231,  66; ...
      140, 140, 132; ...
      173, 173, 148; ...
      33,   33,  24; ...
      156, 156, 107; ...
      181, 181, 123; ...
      255, 255, 165; ...
      0,     0,   0] / 255);

s       = size(ind);
RGBIcon = reshape(map(double(ind) + (1 - double('a')), 1:3), s(1), s(2), 3);

return;

% ******************************************************************************
function localKeyPress(DlgH, EventData)  %#ok<INUSD>

UD      = get(DlgH, 'UserData');
nButton = length(UD.ButtonH);
switch get(DlgH, 'CurrentCharacter')
   case {13, 32}  % Return, Space: Currently focussed button
      set(UD.ButtonH(UD.ButtonFocus), 'Value', 1);
   case 27  % Escape: Initial default button
      set(UD.ButtonH(1), 'Value', 1);
   case 29  % Left: Move focus
      if nButton > 1  % Nothing to do for a single button!
         UD.ButtonFocus = mod(UD.ButtonFocus - 2, nButton) + 1;
         set(UD.FrameH,                 'EdgeColor', 'none');
         set(UD.FrameH(UD.ButtonFocus), 'EdgeColor', zeros(1, 3));
         set(DlgH, 'UserData', UD);
      end
   case 28  % Right: Move focus
      if nButton > 1
         UD.ButtonFocus = 1 + rem(UD.ButtonFocus, nButton);
         set(UD.FrameH,                 'EdgeColor', 'none');
         set(UD.FrameH(UD.ButtonFocus), 'EdgeColor', zeros(1, 3));
         set(DlgH, 'UserData', UD);
      end
   case {67, 99}  % c, C: Copy warning to clipboard
      Msg = get(UD.TextH, 'String');
      clipboard('copy', sprintf('%s\n', Msg{:}));
end

return;

% ******************************************************************************
function varargout = GetOpt(Opt, Field, Default)

if isa(Opt, 'struct') && ~isempty(Opt)
   OptField = fieldnames(Opt);
   OptData  = struct2cell(Opt);
else
   varargout = Default;
   return;
end

for iField = 1:length(Field)
   Ind = find(strcmpi(OptField, Field{iField}));
   if length(Ind) == 1
      Default{iField} = OptData{Ind};
   end
end
varargout = Default;

return;

Contact us at files@mathworks.com