function varargout = vipToVideoDisplayPanel(varargin)
% VIPTOVIDEODISPLAYPANEL Create a figure for the To Video Display block.

% Copyright 2008 The MathWorks, Inc.
%   $Revision: 1.1.6.4 $  $Date: 2011/05/17 03:09:09 $


if nargin < 1 % If we have no parameters, create the dialog
    varargin{1} = 'create';
end

% Make sure the user is calling a sub-function
if ~ischar(varargin{1})
    error('vipblks:vipToVideoDisplayPanel:invalidFcnInput', 'First argument must be a string');
end

% This display should only be used on windows
if ~ispc
    if strcmp('figureOpening', varargin{1}) %&& ~strcmp(bdroot, 'vipsnks')
        errordlg('Video output functionality only supported on Windows platforms.', 'To Video Display Block');
    end
    return
end

% Get the handle of the function we are calling
switch varargin{1}
    case {'blockOpening', 'blockClosing', 'blockNameChange',...
          'blockCopying', 'blockLoading', 'blockSaving',...
          'mdlStart', 'mdlTerminate',...
          'figureOpening', 'figureClosing', 'figurePosition'}
        if strncmp(varargin{2}, 'SCOMP', 5)
            callback = @SCOMPDisplayManager;
        else
            callback = @DisplayManagerFactory;
        end
    otherwise
        callback = str2func(varargin{1});
        varargin(1) = [];
end

% Call the function
if nargout
    [varargout{1:nargout}] = feval(callback, varargin{:});
else
    feval(callback, varargin{:});
end
end


%% --- Create the figure
function varargout = create(varargin)
handles.id = [];
handles.isaBlock = false;
if nargin > 0 
    handles.id = varargin{1};
    if ~strncmp(varargin{1}, 'SCOMP', 5)
        handles.isaBlock = true;
    end
end

handles.figure = figure('Tag','ToVideoDisplay',...
    'windowStyle','normal',...
    'dockControls', 'off',...
    'NumberTitle', 'off',...
    'IntegerHandle','off',...
    'Color',[0 0 0],...
    'menuBar', 'none',...
    'handleVisibility', 'callback',...
    'visible', 'off');

addlistener(handles.figure, 'Position', 'PostSet', @(varargin)(vipToVideoDisplayPanel('figurePosition', handles.id)));

% We may need to find this figure later, so set a tag.
if nargin>0 && ischar(varargin{1})
    set(handles.figure, 'tag', varargin{1});
end

% Here we create the menu (if required)
if handles.isaBlock
    handles = createMenu(handles);
end

% Calculate and set the specified position
if nargin > 1
    pos = convertCoords('toML', varargin{2}, handles.figure);
else
    pos = get(0, 'defaultFigurePosition');
end
set(handles.figure,...
    'DeleteFcn', @(varargin)(vipToVideoDisplayPanel('destroy', handles.figure)),...
    'Position', pos,...
    'visible', 'on');
  

% Create the video container and store its HWND
[canv handles.container] = javacomponent('com.mathworks.hg.peer.AxisCanvas', [20 20 20 20], handles.figure);
set(handles.container, 'units', 'normalized',...
                       'position', [0 0 1 1],...
                       'tag', 'canvas');
drawnow;
if ~canv.isNativeWindowHandleValid
    error('vipblks:vipToVideoDisplayPanel:invalidHWND', 'Unable to obtain valid window handle');
end

handles.hwnd = canv.getNativeWindowHandle();
guidata(handles.figure, handles);

if nargout > 0
    varargout{1} = handles.figure;
    if nargout > 1
        varargout{2} = handles.hwnd;
    end
end
end

%% Delete function
function destroy(hFigure)
handles = guidata(hFigure);
if ~isempty(handles) 
  if handles.isaBlock
    dm = get_param(handles.id, 'UserData');
    dm('figureClosing');
  else
    SCOMPDisplayManager('figureClosing', handles.id);
  end
end
delete(hFigure);
end

%% Return a bitmask indicating the default renderer type.
function default = getRendererType() %#ok<DEFNU>
    default = getpref('ComputerVision', 'VideoDisplayRenderer', 1);
end

%% Create the menu
function handles = createMenu(handles)
    % The View menu.
    handles.viewMenu = uimenu(handles.figure,...
        'Label','View',...
        'Tag', 'viewMenu',...
        'Callback', 'vipToVideoDisplayPanel(''setMenuState_Callback'', gcbo)');
    handles.windowSizeMenu = uimenu(handles.viewMenu,...
        'Label','Window Size',...
        'Tag', 'viewMenuRoot');
    handles.sizeMenu(1) = uimenu(handles.windowSizeMenu,...
        'Label','Normal',...
        'Tag', 'normalSize',...
        'Callback','vipToVideoDisplayPanel(''sizeMenu_Callback'', gcbo)');
    handles.sizeMenu(2) = uimenu(handles.windowSizeMenu,...
        'Label','Full-screen (Esc to exit)',...
        'Tag', 'fullscreen',...
        'Callback','vipToVideoDisplayPanel(''sizeMenu_Callback'', gcbo)');
    handles.sizeMenu(3) = uimenu(handles.windowSizeMenu,...
        'Label','True Size (1:1)',...
        'Tag', 'trueSize',...
        'Callback','vipToVideoDisplayPanel(''sizeMenu_Callback'', gcbo)');
    handles.openAtMdlStart = uimenu(handles.viewMenu,...
        'Label','Open at Start of Simulation',...
        'Tag', 'openAtMdlStart',...
        'Callback','vipToVideoDisplayPanel(''menuToggle_Callback'', gcbo)');

    % The Settings menu
    handles.settingsMenu = uimenu(handles.figure,...
        'Label','Settings',...
        'Tag', 'settingsMenu',...
        'Callback', 'vipToVideoDisplayPanel(''setMenuState_Callback'', gcbo)');
    handles.colorFormat = uimenu(handles.settingsMenu,...
        'Label','Input Color Format',...
        'Tag', 'colorFormat');
    handles.colorFormatChoice(1) = uimenu(handles.colorFormat,...
        'Label','RGB',...
        'Tag', 'colorFormat_RGB',...
        'Callback','vipToVideoDisplayPanel(''colorFormat_Callback'', gcbo)');
    handles.colorFormatChoice(2) = uimenu(handles.colorFormat,...
        'Label','YCbCr 4:2:2',...
        'Tag', 'colorFormat_YCbCr',...
        'Callback','vipToVideoDisplayPanel(''colorFormat_Callback'', gcbo)');
    handles.imageSignal = uimenu(handles.settingsMenu,...
        'Label','Image Signal',...
        'Tag', 'imageSignal');
    handles.inputPort(1) = uimenu(handles.imageSignal,...
        'Label','One Multidimensional Signal',...
        'Tag', 'multipleInputPort',...
        'Callback','vipToVideoDisplayPanel(''inputPort_Callback'', gcbo)');
    handles.inputPort(2) = uimenu(handles.imageSignal,...
        'Label','Separate Color Signals',...
        'Tag', 'singleInputPort',...
        'Callback','vipToVideoDisplayPanel(''inputPort_Callback'', gcbo)');
    handles.preferences = uimenu(handles.settingsMenu,...
        'Label','Preferences...',...
        'Tag', 'preferences',...
        'Callback', 'preferences(''Computer Vision System Toolbox'')');
    
    % Save the handles whose state becomes inactive when playing
    handles.activated = [handles.sizeMenu handles.openAtMdlStart ...
                         handles.colorFormatChoice handles.inputPort];
    
    % The help menu
    handles.helpMenu = uimenu(handles.figure,...
        'Label','Help',...
        'Tag', 'helpMenu',...
        'Callback', 'vipToVideoDisplayPanel(''setMenuState_Callback'', gcbo)');
    uimenu(handles.helpMenu,...
        'Label','To Video Display Block',...
        'Tag', 'toVideoDisplayHelp',...
        'Callback','doc(''To Video Display'')');%'slhelp(gcbh)'
    uimenu(handles.helpMenu,...
        'Label','Computer Vision System Toolbox',...
        'Tag', 'vipblksHelp',...
        'Callback','doc(''vision'')');
    uimenu(handles.helpMenu,...
        'Separator','on',...
        'Label','About Computer Vision System Toolbox',...
        'Tag', 'vipblksAboutDlg',...
        'Callback','aboutvipblks');
end

%% MENU callbacks and helpers
function menuToggle_Callback(hObject) %#ok<DEFNU>
toggleMenuState(hObject);
setBlockState(ancestor(hObject,'figure'));
end
function sizeMenu_Callback(hObject) %#ok<DEFNU>
handles = guidata(hObject);
set(handles.sizeMenu, 'Checked', 'off');
set(hObject, 'Checked', 'on');
setBlockState(ancestor(hObject,'figure'));
end
function colorFormat_Callback(hObject) %#ok<DEFNU>
handles = guidata(hObject);
set(handles.colorFormatChoice, 'Checked', 'off');
set(hObject, 'Checked', 'on');
setBlockState(ancestor(hObject,'figure'));
end
function inputPort_Callback(hObject) %#ok<DEFNU>
handles = guidata(hObject);
set(handles.inputPort, 'Checked', 'off');
set(hObject, 'Checked', 'on');
setBlockState(ancestor(hObject,'figure'));
end
function help_Callback(hObject) %#ok<INUSD,DEFNU>
    slhelp(gcbh);
end
function toggleMenuState(hObject)
checked = strcmp('on', get(hObject, 'Checked'));
if checked
    set(hObject, 'Checked', 'off');
else
    set(hObject, 'Checked', 'on');
end
end
function string = checked(value)
if value
    string = 'on';
else
    string = 'off';
end
end

%% Set the menu state based on the block parameters
function setMenuState_Callback(hFigure) %#ok<DEFNU>
handles = guidata(hFigure);
if isempty(handles.id)
    return
end

% What size should the window be? normal, truesize, or fullscreen?
itemToCheck = 1;
if strcmp(get_param(handles.id, 'fullScreen'), 'on')
    itemToCheck = 2;
elseif ~strcmp(get_param(handles.id, 'saveWindowSize'), 'on')
    itemToCheck = 3;
end
set(handles.sizeMenu, 'Checked', 'off');
set(handles.sizeMenu(itemToCheck), 'Checked', 'on');

% Should we open automatically at model start?
openAtMdlStart = strcmp(get_param(handles.id, 'openAtMdlStart'), 'on');
set(handles.openAtMdlStart, 'Checked', checked(openAtMdlStart));

% Are we accepting RGB or YCbCr 4:2:2?
isRGB = strcmp(get_param(handles.id, 'inputColorFormat'), 'RGB');
set(handles.colorFormatChoice, 'Checked', 'off');
if isRGB
    set(handles.colorFormatChoice(1), 'Checked', 'on');
    % enable choice of number of input ports
    set(handles.imageSignal, 'Enable', 'on');
else
    set(handles.colorFormatChoice(2), 'Checked', 'on');
    % disable choice of number of input ports
    set(handles.imageSignal, 'Enable', 'off');
end

if isRGB
    % Is there a single input port?
    singleInputPort = strcmp(get_param(handles.id, 'imagePorts'), 'One multidimensional signal');
    set(handles.inputPort, 'Checked', 'off');
    if singleInputPort
        set(handles.inputPort(1), 'Checked', 'on');
    else
        set(handles.inputPort(2), 'Checked', 'on');
    end
end

end

%% Set the block parameters based on the menu state
function setBlockState(hFigure)
handles = guidata(hFigure);

% Should we resize ourselves to fit the image? 
% Should we enter fullscreen mode when playing?
fullscreen = strcmp('on', get(handles.sizeMenu(2), 'Checked'));
resizeToFit = ~strcmp('on', get(handles.sizeMenu(3), 'Checked'));
set_param(handles.id, 'saveWindowSize', checked(resizeToFit));
set_param(handles.id, 'fullScreen', checked(fullscreen));

% Should we open automatically at model start?
openAtMdlStart = strcmp('on', get(handles.openAtMdlStart, 'Checked'));
set_param(handles.id, 'openAtMdlStart', checked(openAtMdlStart));

% RGB or YCbCr?
isRGB = strcmp('on', get(handles.colorFormatChoice(1), 'Checked'));
if isRGB
    set_param(handles.id, 'inputColorFormat', 'RGB');
else
    set_param(handles.id, 'inputColorFormat', 'YCbCr 4:2:2');
end

% A single input port, or multiple ports?
singleInputPort = strcmp('on', get(handles.inputPort(1), 'Checked'));
if singleInputPort
    set_param(handles.id, 'imagePorts', 'One multidimensional signal');
else
    set_param(handles.id, 'imagePorts', 'Separate color signals');
end

end

%% Convert to/from a Windows-based coordinate system 
function newpos = convertCoords(direction, oldpos, hFigure)
screensize = get(0, 'ScreenSize');
inpos  = get(hFigure, 'position');
outpos = get(hFigure, 'outerPosition');
leftInset = inpos(1) - outpos(1);
topInset  = outpos(4) - inpos(4) - (inpos(2)-outpos(2));
newpos = oldpos;
menuHeight = getMenuHeight();
switch direction
    case {'fromML'} % MATLAB coordinates to Windows coordinates
        newpos(2) = screensize(4)-(oldpos(4)+oldpos(2));
        % adjust by the insets
        newpos(1) = newpos(1) - leftInset;
        newpos(2) = newpos(2) - topInset;
    case {'toML'}
        % Windows coordinates to MATLAB coordinates
        newpos(2) = screensize(4)-(oldpos(4)+oldpos(2));
        % adjust by the insets
        newpos(1) = newpos(1) + leftInset;
        newpos(2) = newpos(2) - topInset - menuHeight;
    otherwise
        error('vipblks:vipToVideoDisplayPanel:invalidFcnInput', 'incorrect usage');
end
    % We need to dynamically calculate the height of the menu
    function height = getMenuHeight()
        h = figure('menubar', 'none', 'visible', 'off');
        outerPos = get(h, 'outerPosition');
        if ~isempty(findobj(hFigure, 'Type', 'uimenu'))
            uimenu('parent', h);
        end
        drawnow;
        outerPos = get(h, 'outerPosition') - outerPos;
        height = outerPos(4);
        delete(h);
    end
end


%% The function that is called in mdlStart/mdlTerminate of the SCOMP
function varargout = SCOMPDisplayManager(varargin)
persistent nInstances
if isempty(nInstances)
    nInstances = 0;
end

method = varargin{1};
id     = varargin{2};
varargin(1:2) = [];

switch method
    case 'mdlStart'
        id    = varargin{1};
        title = varargin{2};
        pos = varargin{3}(1:4);
        useSavedSize = varargin{3}(7);
        if ~useSavedSize
            pos(3:4) = varargin{3}(5:6);
        end
        % Load the library
        nInstances = nInstances + 1;
        if nInstances == 1 % Load the tovideodisplay library
            loadlibrary('tovideodevice.dll', @tvd_proto, 'alias', 'SCOMPvideodevice');
        end
        % Create the GUI here, so that we have the figure handle.
        [fig hwnd] = vipToVideoDisplayPanel('create', id, pos);
        set(fig, 'name', title);
        varargout = {fig hwnd};
        % Register the window with the DLL
        calllib('SCOMPvideodevice', 'createVideoWindow', id, int32(hwnd));
    case 'figureClosing'
        % unregister the window and destroy the graph
        calllib('SCOMPvideodevice', 'destroyVideoWindow', id);
        % Destroy the HG window here.
        h = findall(0, 'tag', id);
        delete(h);
        % Unload the libarary
        nInstances = nInstances - 1;
        if nInstances == 0 % Unload the library
            unloadlibrary('SCOMPvideodevice');
        end
end

end

%% This factory method attaches a function handle to the userData of the specified Simulink block
function varargout = DisplayManagerFactory(varargin)
persistent nInstances
if isempty(nInstances)
    nInstances = 0;
end

% If we are in a locked subsystem, display an error.
if strcmp(get_param(bdroot(varargin{2}),'Lock'), 'on')
    if strcmp(varargin{1},'figureOpening')
        msg = sprintf(['"%s" is in a locked system.  You must place it in a ' ...
        'model in order to operate it.'], varargin{3});
        errordlg(msg, 'To Video Display Error');
        return
    end
end

% Get the callback (which is stored in the block userData).
dm = get_param(varargin{2}, 'UserData');

% Initialize variables if required
if isempty(dm)
    dm = @DisplayManager;
    try
        set_param(varargin{2}, 'UserData', dm);
    catch err %#ok<NASGU>
        return % If we cannot modify the block, there is nothing to do.
    end
    id        = [];
    fig       = [];
    hwnd      = [];
    isRunning = false;
    hasRun    = false;
    isOpenBeforeSim = false;
    position(1) = str2double(get_param(varargin{2}, 'videoWindowX'));
    position(2) = str2double(get_param(varargin{2}, 'videoWindowY'));
    position(3) = str2double(get_param(varargin{2}, 'videoWindowWidth'));
    position(4) = str2double(get_param(varargin{2}, 'videoWindowHeight'));
    videoSize = position(3:4);
end

% remove the second argument, since it was only used for dispatching
varargin(2) = [];

% Call the function
if nargout
    [varargout{1:nargout}] = dm(varargin{:});
else
    dm(varargin{:});
end

%% The callback function
    function varargout = DisplayManager(varargin)
    method = varargin{1};
    varargin(1) = [];
    % Switch over the method
    switch method
        case {'blockOpening'}
            % The block might have had its name changed: if so, change the ID.
            % Note that this name change cannot happen in between mdlStart and
            % mdlTerminate, otherwise it might be necessary to close the window.
            id = varargin{1};
            % Load the library
            nInstances = nInstances + 1;
            if nInstances == 1 % Load the tovideodisplay library
                loadlibrary('tovideodevice.dll', @tvd_proto);
                mlock;
            end
            % Create the GUI here
            if strcmp(get_param(id, 'isWindowOpen'), 'on')
                DisplayManager('figureOpening', id);
            end
        case {'blockClosing'}
            if ~isempty(fig)
                close(fig);
                drawnow; % Essential (enable the figure to fully close).
            end
            % Unload the libarary
            nInstances = nInstances - 1;
            if nInstances == 0 % Unload the library
                unloadlibrary('tovideodevice');
                munlock;
            end
        case {'blockNameChange'}
            if nInstances > 0
                % Inform the library of the name change
                calllib('tovideodevice', 'renameVideoWindow', id, varargin{1});
            end
            % Change the name
            id = varargin{1};
        case {'blockCopying'}
            % At this point (which is after the copy has taken place), 
            % the current block will have the wrong ID.  Erase the data and
            % reinitialize the block.
            set_param(gcbh, 'UserData', []);
            set_param(gcbh, 'isWindowOpen', 'off');
            vipToVideoDisplayPanel('blockOpening', gcbh, gcb);
        case {'blockLoading'}
            DisplayManager('blockOpening', varargin{:});
        case {'blockSaving'}
            % Save the position just before saving
            set_param(id, 'videoWindowX',      sprintf('%d', position(1)));
            set_param(id, 'videoWindowY',      sprintf('%d', position(2)));
            set_param(id, 'videoWindowWidth',  sprintf('%d', position(3)));
            set_param(id, 'videoWindowHeight', sprintf('%d', position(4)));
            if ~isempty(fig)
                set_param(id, 'isWindowOpen', 'on');
            else
                set_param(id, 'isWindowOpen', 'off');
            end
        case {'figurePosition'}
            if ~isempty(fig)
                pos = get(fig, 'Position');
                position = convertCoords('fromML', pos, fig);
            end
        case {'figureClosing'}    
            % Delete the graph.  Drawnows are essential for synchronization.
            drawnow;
            calllib('tovideodevice', 'destroyVideoWindow', id);
            drawnow;
            % Close the figure.
            delete(fig);
            fig  = [];
            hwnd = [];
            hasRun = false;
        case {'figureOpening'}    
            % Open the figure.
            if isempty(fig) 
                [fig hwnd] = vipToVideoDisplayPanel('create', get_param(id, 'handle'), position);
                % Register the window with the DLL
                calllib('tovideodevice', 'createVideoWindow', id, int32(hwnd));
            else
                % No need to create the figure, just foreground it.
                if ~strcmp(get_param(id, 'fullScreen'), 'on')
                    figure(fig);
                end
            end
            % Hide the canvas if we are not running
            showCanvas(fig, isRunning || hasRun);
            enableMenuItems(fig, isRunning);
            resizeToFitImage(id, fig, videoSize);
            % Set the figure title to the name of the block
            title = '';
            if isstudent
                title = '<Student Version> : ';
            end
            title = [title get_param(id, 'name')];
            set(fig, 'name', title);
        case {'mdlStart'}
            hasRun = true;
            isRunning = true;
            % We are being called from the block.
            id = varargin{1};
            title = varargin{2}; %#ok<NASGU>
            videoSize = varargin{3}(5:6);
            bSeparateProcess = varargin{4};
            % We should open the block, if that parameter is set.
            % If the graph runs in a separate process, however, we should
            % not open that window (in fact we close it).
            openBlockAtStart = strcmp(get_param(id, 'openAtMdlStart'), 'on');
            isOpen = ~isempty(fig);
            if bSeparateProcess
                % Close the figure, since a new window will be created
                if isOpen
                    close(fig);
                end
                isOpenBeforeSim = isOpen || openBlockAtStart;
            else
                if openBlockAtStart
                    DisplayManager('figureOpening', id);
                end
            end
            if isOpen
                % Alter parameters on pre-existing windows, now that we are running
                showCanvas(fig, isRunning);
                enableMenuItems(fig, isRunning);
                resizeToFitImage(id, fig, videoSize);
                %set(fig, 'name', title);
            end
        case {'mdlTerminate'}
            isRunning = false;
            if isOpenBeforeSim
                DisplayManager('figureOpening', id);
                isOpenBeforeSim = false;
            else
                enableMenuItems(fig, isRunning);
            end
        otherwise
            error('vipblks:vipToVideoDisplayPanel:invalidFcnInput', 'Unknown method');
    end
    varargout = {fig hwnd};
    end

end

% Resize the window to fit the image size.
function resizeToFitImage(id, fig, videoSize)
    resizeToFit = strcmp(get_param(id, 'saveWindowSize'), 'off');
    if resizeToFit && ~isempty(fig)
        pos = get(fig, 'position');
        pos(2) = pos(2) + (pos(4) - videoSize(2));
        pos(3:4) = videoSize;
        % HG will not let us display windows less than a certain width.
        % So, by setting the position twice, we ensure that the second "set"
        % will be accurate (we need this accurate information because we 
        % a window listener that controls the size of the rendered image).
        set(fig, 'position', pos);
        drawnow;
        set(fig, 'position', get(fig, 'position'));
    end
end

% The window will not redraw if there is no graph, so we should
% hide the canvas if there is no graph attached to it.
function showCanvas(fig, isRunning)
if ~isempty(fig)
    h = findobj(fig, 'tag', 'canvas');
    if isRunning
        set(h, 'units', 'normalized', 'position', [0 0 1 1]);
    else
        set(h, 'units', 'pixels', 'position', [-1 -1 1 1]);
    end
end
end

% We should show or hide certain menu items which cannot be set while the
% model is running
function enableMenuItems(fig, isRunning)
if ~isempty(fig)
    handles = guidata(fig);
    set(handles.activated, 'Enable', checked(~isRunning));
end
end

%% An interface to the tovideodevice shared library, for use by loadlibrary
function [fcns,structs,enuminfo,ThunkLibName]=tvd_proto
% We will use two functions from the shared library.
ival={cell(3,1)};
fcns=struct('name',ival,'calltype',ival,'LHS',ival,'RHS',ival,'alias',ival);
structs=[]; enuminfo=[]; ThunkLibName=[];
% void createVideoWindow ( const char * blockPath, HWND hwnd );
fcns.name{1}='createVideoWindow';
fcns.calltype{1}='cdecl';
fcns.LHS{1}=[];
fcns.RHS{1}={'cstring','int32'};
% void destroyVideoWindow ( const char * blockPath, HWND hwnd );
fcns.name{2}='destroyVideoWindow';
fcns.calltype{2}='cdecl';
fcns.LHS{2}=[];
fcns.RHS{2}={'cstring'};
% void renameVideoWindow ( const char * from, const char * to );
fcns.name{3}='renameVideoWindow';
fcns.calltype{3}='cdecl';
fcns.LHS{3}=[];
fcns.RHS{3}={'cstring','cstring'};
end

