classdef GrandlyUnifyView < handle
  properties
    model  % a ref for the model
    controller % a ref for the controller
      % this is necessary b/c we create buttons on-the-fly, and need
      % callbacks for them
    fig  % the figure handle
    projectFileLabelText
    projectFileText
    chooseProjectFileButton
    scoreFeatureLabelText
    scoreFeatureMatTexts=zeros(0,1)
    scoreFeatureJabTexts=zeros(0,1)
    chooseScoreFeatureJabButtons=zeros(0,1)
    classifierFileLabelText
    classifierFileText
    chooseClassifierFileButton
    gtExpDirsLabelText
    gtExpDirsListbox
    gtExpDirsAddButton
    gtExpDirsRemoveButton
    %quitButton
    convertButton
    spinDepth=0  % how many times spin() has been called, minus how many 
                 % times unspin() has been called
  end
  methods
    function self=GrandlyUnifyView(model,controller)
      self.model=model;
      self.controller=controller;
      
      % Create the figure, make it invisible for now
      self.fig= ...
        figure('units','points', ...
               'resize','off', ...
               'numbertitle','off', ...
               'menubar','none', ...
               'name','Grandly Unify', ...
               'windowstyle','normal', ...
               'visible','off', ...
               'closeRequestFcn',@(g,e)(controller.quit()));
      
      % make the labels
      self.projectFileLabelText= ...
        uicontrol('parent',self.fig, ...
                  'units','points', ...
                  'style','text', ...
                  'backgroundcolor',get(self.fig,'color'), ...
                  'string','Project File ', ...
                  'HorizontalAlignment','right',...
                  'fontsize',12);

      self.classifierFileLabelText= ...
        uicontrol('parent',self.fig, ...
                  'units','points', ...
                  'style','text', ...
                  'backgroundcolor',get(self.fig,'color'), ...
                  'string','Classifier File ', ...
                  'HorizontalAlignment','right',...
                  'fontsize',12);

      self.gtExpDirsLabelText= ...
        uicontrol('parent',self.fig, ...
                  'units','points', ...
                  'style','text', ...
                  'backgroundcolor',get(self.fig,'color'), ...
                  'string',{'Ground-truth ', ...
                            'Experiment ', ...
                            'Directory Names '}, ...
                  'HorizontalAlignment','right',...
                  'fontsize',12);
      
      % Create the non-label controls  
      self.projectFileText= ...
        uicontrol('parent',self.fig, ...
                  'style','text', ...
                  'backgroundcolor','w', ...
                  'HorizontalAlignment','left',...
                  'fontsize',12, ...
                  'units','points');
      self.classifierFileText= ...
        uicontrol('parent',self.fig, ...
                  'style','text', ...
                  'backgroundcolor','w', ...
                  'HorizontalAlignment','left',...
                  'fontsize',12, ...
                  'units','points');
      self.gtExpDirsListbox=...
        uicontrol('parent',self.fig, ...
                  'style','listbox', ...
                  'fontsize',12, ...
                  'units','points');
      self.chooseProjectFileButton= ...
        uicontrol('parent',self.fig, ...
                  'style','pushbutton', ...
                  'string','Choose', ...
                  'fontsize',12, ...
                  'units','points', ...
                  'callback',@(g,e)(controller.chooseProjectFileButtonPressed()));
      self.chooseClassifierFileButton= ...
        uicontrol('parent',self.fig, ...
                  'style','pushbutton', ...
                  'string','Choose', ...
                  'fontsize',12, ...
                  'units','points', ...
                  'callback',@(g,e)(controller.chooseClassifierFileButtonPressed()));
      self.gtExpDirsAddButton= ...
        uicontrol('parent',self.fig, ...
                  'style','pushbutton', ...
                  'string','Add', ...
                  'fontsize',12, ...
                  'units','points', ...
                  'callback',@(g,e)(controller.gtExpDirsAddButtonPressed()));
      self.gtExpDirsRemoveButton= ...
        uicontrol('parent',self.fig, ...
                  'style','pushbutton', ...
                  'string','Remove', ...
                  'fontsize',12, ...
                  'units','points', ...
                  'callback',@(g,e)(controller.gtExpDirsRemoveButtonPressed()));
      self.convertButton= ...
        uicontrol('parent',self.fig, ...
                  'style','pushbutton', ...
                  'string','Convert', ...
                  'fontsize',12, ...
                  'units','points', ...
                  'callback',@(g,e)(controller.convertButtonPressed()));
                              
      % sync the view with the model
      self.update();
      
      % Get the screen dimensions
      savedUnits=get(0,'units');
      set(0,'units','points');
      screenPosition=get(0,'screensize');
      set(0,'units',savedUnits);
      
      % Figure the placement of the figure, want it centered relative to
      % primary screen
      pos=get(self.fig,'position');
      figureWidth=pos(3);
      figureHeight=pos(4);
      figureXOffset=screenPosition(1)+(screenPosition(3)-figureWidth )/2;
      figureYOffset=screenPosition(2)+(screenPosition(4)-figureHeight)/2;
      
      % Position the figure
      set(self.fig, ...
          'position',[figureXOffset figureYOffset figureWidth figureHeight]);
      
      % make it visible
      set(self.fig,'visible','on');
    end  % constructor method

    
    % ---------------------------------------------------------------------
    function spin(self)
      % Change the cursor to the watch cursor
      self.spinDepth=self.spinDepth+1;
      set(self.fig,'pointer','watch');
      drawnow('update');
    end
      
      
    % ---------------------------------------------------------------------
    function unspin(self)
      % Change the cursor back to normal, unless spin() has been called
      % multiple times without a corresponding unspin() calls.
      if self.spinDepth>0 ,
        self.spinDepth=self.spinDepth-1;
        drawnow('update');  % make sure the figure is as it should be before
                            % switching to the normal cursor
        set(self.fig,'pointer','arrow');
        drawnow('update');
      end
    end
    
    
    % ---------------------------------------------------------------------
    function update(self)
      % Update the view to match the model

      %
      % Do the layout
      % 
      
      % Set figure layout parameters
      figureWidth=730+50;
      %figureHeightApprox=272;

      sideButtonWidth=80;  % choose buttons, add/remove buttons
      sideButtonHeight=26;
      sideButtonOffsetWidth=20;  % side buttons are this far to the right of 
                                 % the center column
      smallSideButtonWidth=70;
      smallSideButtonHeight=24;
        % The 'Choose' buttons for the score feature jab file names are 
        % small side buttons
      addRemoveInterButtonHeight=10;
      
      bottomButtonWidth=100;  % Convert and Quit are the bottom buttons
      bottomButtonHeight=30;
      %interBottomButtonWidth=20;
      
      centerColumnWidth=470+50;
      %fileNameTextHeight=20;
      gtExpDirsListboxHeight=100;
      
      labelOffsetWidth=20;  % Labels are this far to the left of the 
                            % text/listbox that they label
      scoreFeatureJabIndentWidth=20;  % the names of the jab file names
                                      % are indented this much relative
                                      % to the score feature mat file names
                            
      topPadHeight=22;
      underProjectPadHeight=10;
      interScoreFeaturePadHeight=5;      
      belowScoreFeaturePadHeight=5;
      overClassifierPadHeight=10;
      classifierToGTPadHeight=21;
      bottomAreaHeight=70;
        % The "bottom area" is the rectangular area below the listbox.  It
        % is as wide as the figure, and it's bottom edge is aligned with
        % with bottom edge of the figure.  The row of bottom buttons is
        % centered within this area.
                            
      % Want the top edge of the figure to stay fixed
      % The width of the figure is constant
      pos=get(self.fig,'position');
      figureTopYOffset=pos(2)+pos(4);
      figureXOffset=pos(1);
            
      % Get text extents
      extent=get(self.projectFileLabelText,'extent');
      extentSize=extent(3:4);
      projectFileLabelTextSize=extentSize;
      fileNameTextHeight=extentSize(2);  % height of a single line of text

      extent=get(self.classifierFileLabelText,'extent');
      extentSize=extent(3:4);
      %classifierFileLabelTextSize=extentSize+4;  % pad a little
      classifierFileLabelTextSize=extentSize;

      extent=get(self.gtExpDirsLabelText,'extent');
      extentSize=extent(3:4);
      %gtExpDirsLabelTextSize=extentSize+4;  % pad a little
      gtExpDirsLabelTextSize=extentSize;
      
      % Calculate overall figure height
      nScoreFeatures=length(self.model.scoreFeatureMatFileNames);
      scoreFeatureAreaHeight= ...
        2*nScoreFeatures*fileNameTextHeight + ...
        max(nScoreFeatures-1,0)*interScoreFeaturePadHeight + ...
        (nScoreFeatures>0)*belowScoreFeaturePadHeight;
      figureHeight=topPadHeight + ...
                   fileNameTextHeight + ...
                   underProjectPadHeight + ...
                   scoreFeatureAreaHeight + ...
                   overClassifierPadHeight + ...
                   fileNameTextHeight + ...
                   classifierToGTPadHeight + ...
                   gtExpDirsListboxHeight + ...
                   bottomAreaHeight;
      figureYOffset=figureTopYOffset-figureHeight;
      
      % Calculate positions
      projectCenterLineYOffset=figureHeight-topPadHeight-fileNameTextHeight/2;
      scoreFeatureAreaTopYOffset= ...
        projectCenterLineYOffset-fileNameTextHeight/2-underProjectPadHeight;
      classifierCenterLineYOffset= ...
        scoreFeatureAreaTopYOffset ...
          -scoreFeatureAreaHeight ...
          -overClassifierPadHeight ...
          -fileNameTextHeight/2;
      gtTopLineYOffset= ...
        classifierCenterLineYOffset ...
          -fileNameTextHeight/2 ...
          -classifierToGTPadHeight;
      
      centerColumnXOffset=(figureWidth-centerColumnWidth)/2;
      projectFileTextXOffset=centerColumnXOffset;
      classifierFileTextXOffset=centerColumnXOffset;
      gtExpDirsListboxXOffset=centerColumnXOffset;
      
      sideButtonXOffset=centerColumnXOffset+centerColumnWidth+sideButtonOffsetWidth;
      chooseProjectFileButtonXOffset=sideButtonXOffset;
      chooseClassifierFileButtonXOffset=sideButtonXOffset;
      gtExpDirsAddButtonXOffset=sideButtonXOffset;
      gtExpDirsRemoveButtonXOffset=sideButtonXOffset;
      
      convertButtonXOffset=(figureWidth-bottomButtonWidth)/2;
      projectFileLabelTextXOffset=centerColumnXOffset-labelOffsetWidth-projectFileLabelTextSize(1);
      classifierFileLabelTextXOffset=centerColumnXOffset-labelOffsetWidth-classifierFileLabelTextSize(1);
      gtExpDirsLabelTextXOffset=centerColumnXOffset-labelOffsetWidth-gtExpDirsLabelTextSize(1);

      projectFileLabelTextYOffset=projectCenterLineYOffset-projectFileLabelTextSize(2)/2;
      classifierFileLabelTextYOffset=classifierCenterLineYOffset-classifierFileLabelTextSize(2)/2;
      gtExpDirsLabelTextYOffset=gtTopLineYOffset-gtExpDirsLabelTextSize(2);
      
      projectFileTextYOffset=projectCenterLineYOffset-fileNameTextHeight/2;
      classifierFileTextYOffset=classifierCenterLineYOffset-fileNameTextHeight/2;
      gtExpDirsListboxYOffset=gtTopLineYOffset-gtExpDirsListboxHeight;
      
      chooseProjectFileButtonYOffset=projectCenterLineYOffset-sideButtonHeight/2;
      chooseClassifierFileButtonYOffset=classifierCenterLineYOffset-sideButtonHeight/2;
      gtExpDirsAddButtonYOffset=gtTopLineYOffset-sideButtonHeight;
      gtExpDirsRemoveButtonYOffset=gtTopLineYOffset-sideButtonHeight-addRemoveInterButtonHeight-sideButtonHeight;

      bottomButtonYOffset=(bottomAreaHeight-bottomButtonHeight)/2;  
        % for both Quit and Convert buttons

      projectFileTextWidth=centerColumnWidth;
      classifierFileTextWidth=centerColumnWidth;
      gtExpDirsListboxWidth=centerColumnWidth;      
        
      projectFileTextHeight=fileNameTextHeight;
      classifierFileTextHeight=fileNameTextHeight;

      % Position the figure
      set(self.fig, ...
          'position',[figureXOffset figureYOffset figureWidth figureHeight]);

      % Position the labels
      set(self.projectFileLabelText, ...
          'position',[projectFileLabelTextXOffset projectFileLabelTextYOffset projectFileLabelTextSize]);
      set(self.classifierFileLabelText, ...
          'position',[classifierFileLabelTextXOffset classifierFileLabelTextYOffset classifierFileLabelTextSize]);
      set(self.gtExpDirsLabelText, ...
          'position',[gtExpDirsLabelTextXOffset gtExpDirsLabelTextYOffset gtExpDirsLabelTextSize]);
        
      % Position everything else
      set(self.projectFileText, ...
          'position',[projectFileTextXOffset ...
                      projectFileTextYOffset ...
                      projectFileTextWidth ...
                      projectFileTextHeight]);
      set(self.classifierFileText , ...
          'position',[classifierFileTextXOffset ...
                      classifierFileTextYOffset ...
                      classifierFileTextWidth ...
                      classifierFileTextHeight]);
      set(self.gtExpDirsListbox, ...
          'position',[gtExpDirsListboxXOffset ...
                      gtExpDirsListboxYOffset ...
                      gtExpDirsListboxWidth ...
                      gtExpDirsListboxHeight]);
      set(self.chooseProjectFileButton, ...
          'position',[chooseProjectFileButtonXOffset ...
                      chooseProjectFileButtonYOffset ...
                      sideButtonWidth ...
                      sideButtonHeight]);
      set(self.chooseClassifierFileButton, ...
          'position',[chooseClassifierFileButtonXOffset ...
                      chooseClassifierFileButtonYOffset ...
                      sideButtonWidth ...
                      sideButtonHeight]);
      set(self.gtExpDirsAddButton, ...
          'position',[gtExpDirsAddButtonXOffset ...
                      gtExpDirsAddButtonYOffset ...
                      sideButtonWidth ...
                      sideButtonHeight]);
      set(self.gtExpDirsRemoveButton, ...
          'position',[gtExpDirsRemoveButtonXOffset ...
                      gtExpDirsRemoveButtonYOffset ...
                      sideButtonWidth ...
                      sideButtonHeight]);
      set(self.convertButton, ...
          'position',[convertButtonXOffset ...
                      bottomButtonYOffset ...
                      bottomButtonWidth ...
                      bottomButtonHeight]);
      
      
      %                
      % Update the score feature controls
      %
      delete(self.scoreFeatureLabelText(ishandle(self.scoreFeatureLabelText)));      
      delete(self.scoreFeatureMatTexts(ishandle(self.scoreFeatureMatTexts)));
      delete(self.scoreFeatureJabTexts(ishandle(self.scoreFeatureJabTexts)));
      delete(self.chooseScoreFeatureJabButtons(ishandle(self.chooseScoreFeatureJabButtons)));
      self.scoreFeatureLabelText=nan;
      self.scoreFeatureMatTexts=nan(nScoreFeatures,1);
      self.scoreFeatureJabTexts=nan(nScoreFeatures,1);
      self.chooseScoreFeatureJabButtons=nan(nScoreFeatures,1);
      if nScoreFeatures>0
        self.scoreFeatureLabelText= ...
          uicontrol('parent',self.fig, ...
                    'units','points', ...
                    'style','text', ...
                    'backgroundcolor',get(self.fig,'color'), ...
                    'string',{'Score Feature ', ...
                              'File Names '}, ...
                    'HorizontalAlignment','right',...
                    'fontsize',12, ...
                    'visible','off');
        extent=get(self.scoreFeatureLabelText,'extent');
        extentSize=extent(3:4);
        scoreFeatureLabelTextSize=extentSize;
        scoreFeatureLabelTextXOffset=centerColumnXOffset-labelOffsetWidth-scoreFeatureLabelTextSize(1);
        scoreFeatureLabelTextYOffset=scoreFeatureAreaTopYOffset-scoreFeatureLabelTextSize(2);
        set(self.scoreFeatureLabelText, ...
            'position',[scoreFeatureLabelTextXOffset scoreFeatureLabelTextYOffset scoreFeatureLabelTextSize], ...
            'visible','on');
      end
      for i=1:nScoreFeatures
        % Create the mat file name text object for this one
        xOffset=centerColumnXOffset;
        yOffset=scoreFeatureAreaTopYOffset-(i-1)*2*fileNameTextHeight-fileNameTextHeight;
        width=centerColumnWidth;
        height=fileNameTextHeight;
        self.scoreFeatureMatTexts(i)= ...
          uicontrol('parent',self.fig, ...
                    'units','points', ...
                    'style','text', ...
                    'backgroundcolor',get(self.fig,'color'), ...
                    'string',sprintf('%s:',self.model.scoreFeatureMatFileNames{i}), ...
                    'HorizontalAlignment','left',...
                    'fontsize',12, ...
                    'position',[xOffset yOffset width height]);
        % Create the jab file name text object for this one
        xOffset=centerColumnXOffset+scoreFeatureJabIndentWidth;
        yOffset=yOffset-fileNameTextHeight;
        width=centerColumnWidth-scoreFeatureJabIndentWidth;
        height=fileNameTextHeight;                  
        self.scoreFeatureJabTexts(i)= ...
          uicontrol('parent',self.fig, ...
                    'units','points', ...
                    'style','text', ...
                    'backgroundcolor',get(self.fig,'color'), ...
                    'HorizontalAlignment','right',...
                    'fontsize',12, ...
                    'position',[xOffset yOffset width height]);
        % Create the 'Choose' button for this one
        xOffset=centerColumnXOffset+centerColumnWidth+sideButtonOffsetWidth+(sideButtonWidth-smallSideButtonWidth)/2;
        yOffset=yOffset+fileNameTextHeight-sideButtonHeight/2+(sideButtonHeight-smallSideButtonHeight)/2;
        width=smallSideButtonWidth;
        height=smallSideButtonHeight;
        self.chooseScoreFeatureJabButtons(i)= ...
          uicontrol('parent',self.fig, ...
                    'style','pushbutton', ...
                    'string','Choose', ...
                    'fontsize',12, ...
                    'units','points', ...
                    'callback',@(g,e)(self.controller.chooseScoreFeatureJabFileButtonPressed(self.model.scoreFeatureMatFileNames{i})), ...
                    'position',[xOffset yOffset width height]);
      end

      
      %                
      % Update the contents of UI elements
      %
                
      % Update the project file text object
      if self.model.isProjectFileSpecified() ,
        set(self.projectFileText, ...
            'String',self.model.projectFileName, ...
            'FontAngle','normal');
      else
        set(self.projectFileText, ...
            'String','Not specified', ...
            'FontAngle','italic');
      end
        
      % Update the score feature jab file text object(s)
      for i=1:nScoreFeatures
        if self.model.isScoreFeatureJabFileSpecified(i) ,
          set(self.scoreFeatureJabTexts(i), ...
              'String',[self.model.scoreFeatureJabFileNames{i} ' '], ...
              'FontAngle','normal');
        else
          set(self.scoreFeatureJabTexts(i), ...
              'String','Not specified ', ...
              'FontAngle','italic');
        end
      end
      
      % Update the classifier file text object
      if self.model.isClassifierFileSpecified() ,
        set(self.classifierFileText, ...
            'String',self.model.classifierFileName, ...
            'FontAngle','normal');
      else
        set(self.classifierFileText, ...
            'String','Not specified', ...
            'FontAngle','italic');
      end
      
      % Update the GT experiment listbox
      set(self.gtExpDirsListbox,'String',self.model.gtExpDirNames);
      set(self.gtExpDirsListbox,'Value',self.model.iCurrentGTExpDir);

      % Update enablement of Remove button
      set(self.gtExpDirsRemoveButton,'enable',offIff(isempty(self.model.gtExpDirNames)));
      
      % Update enablement of convert button
      set(self.convertButton,'enable',onIff(self.model.isProjectFileSpecified()));
    end  % method
    
    % ---------------------------------------------------------------------
    function close(self)
      if ishandle(self.fig)
        delete(self.fig);
        self.fig=[];
      end
    end  % method
    
  end
end
