function [] = mulaclab(file, varargin)
%-*- texinfo -*-
%@deftypefn {Function} mulaclab
%@verbatim
%MULACLAB Graphical interface for audio processing using frame multipliers
%   Usage: mulaclab;
%          mulaclab(filename);
%          mulaclab(signal, fs);
%
%   Input parameters:
%         filename : File name of the signal to process.
%         signal   : Signal to process, which must be a vector.
%         fs       : Sampling frequency of the signal.
% 
%   When starting MULACLAB without any input parameter, the user is asked to
%   choose the processed signal, named original signal in the interface.
%
%   Possible signals are .wav files and .mat files containing decompositions
%   preliminarily saved using the MULACLAB interface. The interface only
%   handles monochannel signals. So for multichannel .wav files, the first
%   channel is used as the original signal.
%
%   Optionnaly, the file name of the original signal can be directly passed as
%   an input parameter. The original signal can also be directly passed as a
%   vector along its sampling frequency.  
%
%   After choosing the original signal, the user is presented with the main
%   interface. This interface is divided in two areas: 
%
%    The right part of the figure contains the visualizations, which
%     represent the spectrograms of the original and modified signals.
%
%     The 'Original signal' visualization is used to display the original
%     signal spectrogram and to graphically define the symbol of the frame
%     multiplier that will be applied on this signal. 
%
%     The 'Overview of original signal' visualization also represents the
%     original signal spectrogram and can be used for fast zooming and moving
%     of the other visualizations. Zooming and moving is controlled by mouse
%     interaction with the white rectangle displayed on this visualization. 
%
%     The 'Modified signal' visualization is used to display the spectrogram
%     of the modified signal after application of the multiplier.
%
%     It is possible to hide the 'Overview of original signal' and 'Modified
%     signal' visulizations using the 'Visualization' menu.
%
%    The left part of the figure contains panels with tools for user 
%     interaction.
%
%     The 'Audioplayer' panel contains the controls for audio playback of the
%     original and modified signal.
%
%     The 'Visualization' panel contains tools used to adapt the display
%     of the visualizations.
%
%     The 'Selection' panel contains tools and information concerning the
%     multilayered selection used to graphically specify the symbol of the 
%     multiplier.
% 
%   Known MATLAB limitations: 
%
%    When using MATLAB on Linux with multiple screens, there might be a 
%     MATLAB bug preventing the display of the multiplier symbol. This can be
%     solved by docking the figure.
%
%    When using a MATLAB version prior to 7.3 (R2006b), the rectangle
%     displayed on the 'Overview of original signal' visualization is not
%     automatically updated when using the zoom and pan tools of the 'Zoom' 
%     panel. It can be manually updated by re-clicking on the currently
%     selected tool or by changing the current tool.
%@end verbatim
%@strong{Url}: @url{http://ltfat.github.io/doc/mulaclab.html}
%@end deftypefn

% Copyright (C) 2005-2023 Peter L. Soendergaard <peter@sonderport.dk> and others.
% This file is part of LTFAT version 2.6.0
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.

%AUTHOR: Florent Jaillet

% Known bugs:
% - There might be a bug with noise level when using selection layers of type
%   hole filling with noise.
%   Something really strange is hapenning, for a given selection we will have a
%   level which is too low, when everything works nicely with a slightly
%   modified version of the selection.
% - when using undo it seems that parameters of the type of layer are not
%   updated
%
% Acronym used in the comments:
% PI: possible improvement, used to specify things that could be done to
% improve the current version of mulaclab. In particular, it can be 
% efficiency improvements or suitable functionnality addition.
%
% Some PI :
% - add possibility to see an individual atom of the frame (and the dual)
%   (with right clic menu?)
% - possibility to have a nice view (3D?) of the symbol
% - possibility to choose the channel and a time selection of wave files
% - switch between original and modified when audio playing without stopping
%   playback
% - add editable keyboard shortcuts for almost everything
% - add something to show that a processing is currently running
%   (progressbar? change of mouse pointer?)
% - give possibility to use a Gaussian window
% - update information of contextual menu for layers to know what are the
%   currently selected properties
% - add possibility to modify afterward the polygons

% List of functions used from the image toolbox :
% bwselect: used for the magic wand.
%           In Octave, uses bwfill which is written in c++
% imdilate: used to convert layers of type fill and fillNoise.
%           In Octave, uses imerode which is written in c++

% _____________________ Description of shared data ________________________
%
% Mulaclab was initially developped only for MATLAB as Octave was at that time
% missing the needed features to build advanced GUIs.
%
% In this original version, nested functions were used to share data among GUI
% callbacks and a set of variables where defined at the top level of the
% mulaclab function to share the data between the GUI components.
%
% With its version 4.0.0, Octave gained most of the GUI features needed by
% mulaclab, so mulaclab was then adapted to work on both MATLAB and Octave.
%
% But Octave 4.0.0 doesn't support the use of nested functions in GUI callbacks.
% So the new version of mulaclab now uses global variables to share data
% among GUI callbacks (even if it's not an elegant solution, but we do with the
% limitations that we face...) .
%
% Here is a list of these global variables with their description:
% NOTE: This list has not been fully updated with the evolution of the mulaclab
% code. So it misses the description of some shared variables introduced later
% in the developement and might be incomplete and inaccurate in some places.
%
% sig: (struct) data describing the processed signal
%
% - sig.ori: (vector) samples of the original signal
%
% - sig.mod: (vector) samples of the modified signal
%
% - sig.sampFreq: (scalar) sampling frequency of the original and modified
%   signals
%
% - sig.real: (bool) boolean to know if the signal is real (true)
%   or complex (false)
%
% frame: (struct) data describing the frame
%
% - frame.type: (string) type of the frame (currently only Gabor frames are 
%   implemented, other frames could be added, like wavelet frames,
%   nonstationnary Gabor frames, nonuniform filterbanks, general frames)
%
% - frame.def: (struct) data defining the frame (fields depend of the
%   frame type)
%
%   For Gabor frames, frame.def fields are:
%   - frame.def.winType: (string) type of window as defined in the firwin
%     function of LTFAT, see parameter 'name' in the help of firwin 
%     function for details
%
%   - frame.def.winLen: (scalar) window length in number of samples
%
%   - frame.def.hop: (scalar) hop size (ie Gabor frame time shift) in
%     number of samples (parameter 'a' of LTFAT dgt function)
%
%   - frame.def.nbFreq: (scalar) number of frequency bins (parameter 'M' 
%     of LTFAT dgt function)
%
% coeff: (struct) coefficients obtained by decomposition of the signal 
% on the frame
%
% - coeff.ori: (matrix, dimension 1: frequency, dimension 2:time) 
%   coefficients for the original signal sig.ori
%
% - coeff.mod: (matrix, dimension 1: frequency, dimension 2:time)
%   coefficients for the modified signal sig.mod
%
% - coeff.oriC: (matrix, dimension 1: frequency, dimension 2:time)
%   plotted spectrogram as image in DB
%
% - coeff.info: (struct)
%   additonal data necesary for the reconstruction
%
% sel: (struct) data describing the graphical selection
% The graphical selection contains several layers, each layer containing
% several polygons defining a region of the time-frequency that must be 
% modified. These polygons can be drawn freely by the user using the two 
% available tools (freehand selection and magic wand).
% sel contains data defining these polygons and layers, and data
% specifying how they should be plotted
%
% - sel.mode: (string) specifies how the next polygon drawn by the user 
%   must be combined with the polygons already defined in the currently
%   edited layer
%   possible modes are 'union', 'intersection', 'difference'
%
% - sel.curLay: index of the currently edited layer
%
% - sel.lay: (struct) data describing the layers, it's a vector of struct,
%   and sel.lay(ind) contains data for layer number ind
%
%   - sel.lay.convType: (string) conversion type of the layer, it specifies
%     how the values of the symbol must be computed for the current layer,
%     possible types are:
%
%     'constGain': apply a constant gain on the whole time-frequency region
%     corresponding to the polygons of the layer
%
%     'smoothBorder': also apply a gain on the region corresponding to the
%     polygons of the layer, but the gain values are smoothed at the border
%     to go linearly from 1 outside the polygons to the gain value
%     specified for the layer inside the polygons
%
%     'fill': try to fill the region corresponding to the polygons of the
%     layer according to the neighborhood level, this is done by
%     replacing the absolute value of the coefficient inside the polygons 
%     by the interpolated values using the level of the coefficient in a 
%     specified neigborhood outside the polygons
%     the phase of the coefficients are left unchanged
%
%     'fillNoise': same as 'fill', but the coefficients inside the polygons
%     of the layers are replaced by coefficients obtained from a noise and
%     multiplied by the interpolated absolute values
%
%   - sel.lay.param: (struct) parameters for the conversion of the layer,
%     it's a struct array, sel.lay(indLay).param(indParam) defines the
%     parameter number indParam of the layer number indLay
%     the number of parameters and their nature depends of the convType
%     parameter of the layer according to the following:
%
%     * if convType is 'constGain': only one parameter named 'Gain' is 
%       defined and it contain the value of the constant gain applied to 
%       region corresponding to the polygons of the layer
%
%     * if convType is 'smoothBorder': two parameters named 'Gain' and 
%       'Border' are defined
%       'Gain' contain the value of the gain applied inside region 
%       corresponding to the polygons of the layer away from the border
%       'Border' specify the distance from the border (in number of
%       coeffiicents) in which the gain value is linearly interpolated 
%       between 1 and the value given by 'Gain'
%
%     * if convType is 'fill' or 'fillNoise' : two parameters 'Height' and
%       'Width' are defined
%       these parameters specifiy the size of the ellipse used to define
%       the size of the neighborhood used the estimate the level around the
%       polygons
%       'Height' is the radius (in number of coefficients) of the ellipse
%       along frequency (y axis), and 'Width' is the radius (in number of 
%       coefficients) of the ellipse along time (x axis)
%       
%     - sel.lay.param.name: (string) name of the parameter, as displayed 
%       in the GUI under the layer list
%
%     - sel.lay.param.val: (scalar) the value of the parameter
%
%   - sel.lay.lineWidth: (scalar) width of the lines representing the 
%     polygons of the layer
%
%   - sel.lay.color: (colorspec) color of the lines representing the 
%     polygons of the layer
%
%   - sel.lay.marker: (string) marker type of the lines representing the 
%     polygons of the layer
%
%   - sel.lay.markerSize: (scalar) marker size of the lines representing 
%     the polygons of the layer
%
%   - sel.lay.lineStyle: (string) line style of the lines representing the 
%     polygons of the layer
%
%   - sel.lay.poly: (struct) data defining the polygons of the layer, it's
%     a struct array, sel.lay(indLay).poly(indPoly) defines the
%     polygon number indPoly of the layer number indLay
%
%     - sel.lay.poly.x: (row vector) x-coordinates (ie time-coordinates)
%       of the polygon points
%
%     - sel.lay.poly.y: (row vector) y-coordinates (ie 
%       frequency-coordinates) of the polygon points
%
%     - sel.lay.poly.hole: (bool) specifies if the polygon is a hole
%
%     - sel.lay.poly.id: (graphics object handle) handle to the line
%       representing the polygon
%
%   - sel.lay.label: (string) layer label as displayed in the layer list in
%     the GUI
%
%
% visu: (struct) data defining parameters of the visualizations
% visu is a struct array, visu(ind) contains data for the visualization
% number ind.
% The visualizations are the representations plotted on the right part of
% the GUI. They are plotted in the order specified by visu from top to
% bottom, that is to say that visu(1) is plotted on top, visu(2) is plotted
% under visu(1) and so on.
% PI: Currently only three visualizations are available: the spectrogram
% of the original signal (used as the support for graphical definition of 
% the multiplier), the spectrogram of the modified signal to observe the 
% effect of the multiplier, and an overview spectrogram of the original 
% signal for fast navigation in the time-frequency plane.
% But it should be easy to add some new visualizations if needed. To add a
% new visualization, adding an element to vector visu with appropriate 
% parameters and implementing the associated updating function should be
% enough. If the possibility to show and hide the new visualization is
% also needed, some handling of it in the menu is also required.
%
% - visu.label: (string) the title appearing on top of the visualization
%
% - visu.height: (scalar) number specifying the height of the
%   visualization. It is a number between 0 and 1 specifying the ratio of
%   the figure height that should be used for each visualisation. To cover
%   the whole figure, values should be chosen so that they sum up to 1 when
%   considering all the visible visualizations
%
% - visu.position: (vector) the position vector of the uipanels containing
%   the visualizations. It is automatically computed by function resize
%   using the values of visu.height.
%
% - visu.updateFcn: (function handle) the handle of the function that must
%   be used to update the plot of the visualization
%
% - visu.dynamic: (scalar) colorscale dynamic of the visualization
%   (positive number in dB)
%
% - visu.visible: (bool) boolean specifying if the visualization is
%   visible and must be plotted (true) or not (false)
%
% - visu.linkX: (bool) boolean specifying if the visualization x axe (time
%   axe) must be linked with the one of the other visualizations
%
% - visu.linkY: (bool) boolean specifying if the visualization y axe 
%   (frequency axe) must be linked with the one of the other visualizations
%
% - visu.linkCLim: (bool) boolean specifying if the visualization 
%   colorscale limits must be linked with the one of the other varargin
%   visualizations
%
% - visu.uipanelId: (graphics object handle) handle of the uipanel
%   containing the visualization
%
% - visu.axesId: (graphics object handle) handle of the axes of the
%   visualization
%
% gui: (struct) graphical user interface data 
% gui contains data used to define properties of certain graphics object
% (in particular their position) and data used to dynamically handle the
% inferface (in particular many graphics object handles)
%
% - gui.mainFigPos: (4x1 vector) position of the main figure using the
%   usual matlab position definition of the form [left bottom width height]
%
% - gui.fontSize: (scalar) font size (in points) for the elements appearing
%   in the panels on the left part of the interface
%
% - gui.textHeight: (scalar) height (in number of pixels) for text
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.textWidth: (scalar) width (in number of pixels) for text
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.editWidth: (scalar) width (in number of pixels) for edit
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.buttonBackgroundColor: (colorspec) background color used for
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.buttonWidth: (scalar) width (in number of pixels) for button
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.buttonHeight: (scalar) height (in number of pixels) for button
%   uicontrol appearing in the panels on the left part of the interface
%
% - gui.horiDist: (scalar) horizontal distance (in number of pixels) 
%   between two elements appearing in the panels on the left part of the 
%   interface
%
% - gui.vertDist: (scalar) vertical distance (in number of pixels) 
%   between two elements appearing in the panels on the left part of the 
%   interface
%
% - gui.margin: (1x2 vector) distance from the border of the panel (in 
%   number of pixels) of the element closest to the border of the panel.
%   First element is horizontal distance, second element is vertical
%   distance.
%
% - gui.marginSub: (1x2 vector) same as gui.margin but for elements 
%   appearing in sub-panels
%
% - gui.panelWidth: (scalar) width (in number of pixels) of the panels 
%   appearing on the left part of the interface
%
% - gui.panelHeight: (vector) height (in number of pixels) of the panels 
%   appearing on the left part of the interface. These values must be
%   adapted by hand if other parameters such as gui.buttonHeight are 
%   changed
%
% - gui.panelTitle: (cell array of strings) title of the panels appearing 
%   on the left part of the interface
%
% - gui.visuResizeButtonWidth: (scalar) width (in number of pixels) of 
%   the draggable resize buttons appearing between two visualizations on 
%   the right part of the interface
%
% - gui.visuResizeButtonHeight: (scalar) height (in number of pixels) of 
%   the draggable resize buttons appearing between two visualizations on 
%   the right part of the interface
%
% - gui.curTool: (string) currently used graphical tool, can be one of the
%   possible names stored in gui.tool.name, which are:
%   'free' for freehand selection
%   'level' for level selection (ie magic wand)
%   'zoomIn' for zoom in mode
%   'zoomOut' for zoom out mode
%   'pan' for pan mode
%
% - gui.tool: (struct) struct array containing tool data
%   gui.tool(ind) contains data for tool number ind
%
%   - gui.tool.buttonId: (graphics object handle) handle of the tool button
%
%   - gui.tool.name: (string) name of the tool, used to identify the
%     current tool in gui.curTool
%
%   - gui.tool.function (function handle) handle of the function that must
%     be executed to initialize the tool when clicking the corresponding 
%     button
%
%   - gui.tool.param: (struct) struct array containing the tool parameters
%     the content of this element depends on the tool and is empty for all
%     the tools except for the levele selection (magick wand), for which 
%     one parameter named 'Tolerance' is defined
%
%     - gui.tool.param.name: (string) the name of the parameter, as
%     displayed under the tools subpanel
%
%     - gui.tool.param.val: (string) the value of the parameter represented
%       as a string (so a conversion is needed if the parameter is a
%       number)
%
% - gui.symbOpacity: (scalar) opacity value used for plotting of symbol 
%   (any value between 0:transparent and 1:opaque)
%
% - gui.showSymb: (bool) boolean specifying if the symbol should be plotted
%   (true) or not (false)
%
% - gui.symbImageId: (graphics object handle) handle of the image used to
%   plot the symbol (the symbol is plotted as a transparent image on top 
%   of the image of the spectrogram)
%
% - gui.player: (struct) structure containing the handles associated with
%   the gui of the audioplayer
%
%   - gui.player.buttongroupId: (graphics object handle) handle of the
%     buttongroup used for the selection of the signal played (original or 
%     modified) 
%
%   - gui.player.buttonOriId: (graphics object handle) handle of the radio
%     button for the selection of original signal
%
%   - gui.player.buttonModId: (graphics object handle) handle of the radio
%     button for the selection of modified signal
%
% - gui.ResizeVisuButtonId: (graphics object handle vector) vector
%   containing the handles of the draggable resize buttons appearing 
%   between two visualizations on the right part of the interface
%
% - gui.mainFigId: (graphics object handle) handle of the main figure of 
%   the interface
%
% - gui.undoMenuId: (graphics object handle) handle of the 'Undo' menu
%   element
%
% - gui.redoMenuId: (graphics object handle) handle of the 'Redo' menu 
%   element
%
% - gui.visuMenu: (struct) structure containing menu handles used to show
%   and hide visualizations
%
%   - gui.visuMenu.showModId: (graphics object handle) handle of the 
%   'Show modified signal' menu element
%
%   - gui.visuMenu.showOverviewId: (graphics object handle) handle of the 
%   'Show overview of original signal' menu element
%
% - gui.exploreRect: (struct) handle of the graphical objects used to
%   represent the rectangular selection on the overview of original signal
%   visualization
%
%   - gui.exploreRect.patchId: (graphics object handle) handle of the patch
%   used to draw the movable selection rectangle
%
%   - gui.exploreRect.lineId: (graphics object handle) handle of the line
%   used to draw the movable points at the corners of the rectangle
%
% - gui.panelId: : (graphics object handle vector) vector containg the
%   handles of the uipanels drawn on the left part of the interface
%
% - gui.editDynamicId: (graphics object handle) handle of the edit
%   uicontrol used to specify the colorscale dynamic
%
% - gui.layListId: (graphics object handle) handle of the listbox
%   uicontrol used to show the slection layers list
%
% - gui.layPanelId: (graphics object handle) uipanel handle of the 'Layers'
%   subpanel
%
% - gui.toolPanelId: (graphics object handle) uipanel handle of the 'Tools'
%   subpanel
%    
% link: (struct) data for linking of axes of the different visualization
%
% - link.CLim: (linkprop object) link object used to link the colorscale 
%   limits (through the CLim property) of the visualizations having their
%   parameter visu.linkCLim set to true
%
% - link.XLim: (linkprop object) link object used to link the time axe 
%   (through the XLim property) of the visualizations having their 
%   parameter visu.linkXLim set to true
%
% - link.YLim: (linkprop object) link object used to link the frequency axe 
%   (through the YLim property) of the visualizations having their 
%   parameter visu.linkYLim set to true
%
% player: (struct) data used for audio playing
% 
% - player.ori: (audioplayer object) audioplayer used to play the original
%   signal
%
% - player.mod: (audioplayer object) audioplayer used to play the modified
%   signal
%
% - player.selected: (string) indicate the currently selected audio signal
%   can be 'ori' for the original signal, or 'mod' for the modified signal
%
% - player.loop: (bool) indicate if the audio signal must be looped (true)
%   or not (false) when playing
%
% - player.position: (scalar) sample position at which the audio playing
%   will start at next pressing of start
%
% - player.forceStop: (bool) indicate to the stop function of the 
%   audioplayer that the player must be stopped (needed to be able to stop
%   when looping)
%
% - player.positionPlotId: (graphics object handle vector) contains handles
%   to the moving vertical white lines that are displayed on the 
%   visualizations to show the current position when playing
%
% default: default data for some variables
% PI: This could be used more systematically to define the default value
% of all the parameters of the interface. It could then be used to
% configure the application by loading variable default from a file during
% initialization, and we could also give the possibility to the user
% to modify these default values
%
% - default.dynamic: (scalar) default value for colorscale dynamic 
%   (positive number in dB)
%
% - default.opacity: (scalar) default value for opacity used for plotting
%   of symbol (any value between 0:transparent and 1:opaque)
%
% - default.sel: (struct) default sel variable (see the description of sel)
%
% - default.frame: (struct) default frame variable (see the description of
%   frame)     
%
% undoData: (cell) data for undo functionnality
% Each cell of undoData contain a copy of the sel variable as it was at a
% preceding state
%
% redoData: (cell) data for redo functionnality
% Each cell of redoData contain a copy of the sel variable as it was at a
% preceding state
%
% ___________________ End of description of shared data ___________________
%
% __________________________ Beginning of code ____________________________
%
% define the variables that will be used to share data between
% interface components. They are defined at the top level of the function
% so that they are usable by all the functions used by the interface (as
% these are nested functions).

  global coeff default export frame gui link player redoData sel sig symbol
  global undoData visu visucommon 
  
   % initialize the interface
  if(nargin<1)
     file = [];
  end
  
  if ~isempty(file) 
      if handleMulaclabAction(file,varargin{:})
          return;
      end
  end
  
  forceSingleInstanceOfMulaclab();

  coeff = struct;
  default = struct;
  export = struct;
  frame = struct;
  gui = struct;
  link = struct;
  player = struct;
  redoData = {};
  sel = struct;
  sig = struct;
  symbol = struct;
  undoData = {};
  visu = struct;
  visucommon = struct;

  % possible values for default.backendClipPoly: 'clipper' or 'polygonclip' or
  % 'binmasks'
  default.backendClipPoly = 'clipper';


  initialize(file, varargin{:});

end
% end of main function, from here everything is handled with the callbacks
% using the following functions

function forceSingleInstanceOfMulaclab()

  figNumber = getMulaclabFigNo();  

  if ~isempty(figNumber)
    error(['Only one single instance of the MulAcLab gui can be run at a '...
      'time and MulAcLab is already running in figure ', figNumber]);
  end
end

function figNumber = getMulaclabFigNo()
  % as we use global variables to share data between callbacks, we need to
  % insure that only one instance of the mulaclab gui is running
  mulaclabFigId = findall(0, 'name', 'MulAcLab');
  
  % depending on the version of MATLAB, the figure number can either be
  % known through its handle or through the property Number
  try
    figNumber = num2str(mulaclabFigId);
  catch
    figNumber = num2str(get(mulaclabFigId, 'Number'));
  end
end

function wasaction = handleMulaclabAction(action,varargin)
wasaction = 1;
isrunning = ~isempty(getMulaclabFigNo());

actions = {'playori','applySel'};

if ~any(strcmpi(action,actions)) 
    wasaction = 0;
    if isrunning
        error('Mulaclab is already running. Unrecognized action');
    end
    return;
end

if ~isrunning
    error('MulacLab is not running!!');
end

action = lower(action);
   
disp(action);
disp(varargin);

switch action
    case 'playori'
        global sig
        soundsc(sig.ori,sig.sampFreq);
    case 'applysel'
        applySel();
end

end

% __________________________ INITIALIZATION _______________________________
function initialize(file, varargin)
  
  global coeff default export frame gui link player redoData sel sig symbol
  global undoData visu visucommon
    
  % check that we have all the toolboxes and functions that we need
  checkToolbox;
  
  loadFile = 0;
  if nargin==0 || isempty(file)
    % get an original signal or decomposition to have something to show
    [fileName, pathName] = uigetfile({'*.wav;*.mat',...
      'Supported formats'},...
      'Open original signal or decomposition');

    if fileName == 0
      % the user pressed cancel, we stop here
      return;
    end
    loadFile = 1;
  elseif(ischar(file))
    fileName = file;
    pathName = '';
    loadFile = 1;
  end     

  if loadFile
    [~, ~, ext] = fileparts(fileName);
    switch lower(ext)
      case '.wav'
        % read the orginal signal in a wave file
        [sig.ori, sig.sampFreq] =...
          wavload([pathName, fileName]);
        sig.real = true;
      case '.mat'
         % read the original signal decomposition in a mat file
        data = load([pathName, fileName]);
        if isfield(data, 'frame') && isfield(data, 'savedSig')
          frame = data.frame;
          sig = data.savedSig;
        else
          errordlg([fileName ' is not a valid signal decomposition file']);
          return;
        end
      otherwise
          error('Unrecognized file. Only wav and mat files are supported');
    end
  elseif(nargin>=1)
    definput.keyvals.Fs=[];
    [flags,kv,Fs]=ltfatarghelper({'Fs'},definput,varargin);
    if(isempty(Fs))
      error('%s: Second parameter: sampling frequency is missing. ',...
        upper(mfilename));
    end

    if(numel(find(size(file)>1))>1)
      error('%s: Input has to be one channel signal.',upper(mfilename));
    end
    % normalize and change to column vector
    tmpMax = max(abs(file(:)));
    if tmpMax ~= 0
      sig.ori=file(:)/tmpMax;
    else
      sig.ori=file(:);
    end
    sig.real=isreal(file);
    sig.sampFreq = Fs;
  else
    errordlg(sprintf('%s: Unrecognized input parameters.',upper(mfilename)));
    return;
  end

  if size(sig.ori, 2) > 1
     % multichannel wave, we keep only the first channel
     sig.ori = sig.ori(:, 1);
  end
  
  sig.mod = sig.ori;
  % initialize the different parameters of the application
  initializeParameter();
  
  % initialize the frame definition and the coefficients
  frame = default.frame;
  
  % create main window
  gui.mainFigId = figure(...
    'Name', 'MulAcLab',...
    'Position', gui.mainFigPos,...
    'Toolbar', 'none',...
    'Colormap', jet(256),...
    'ResizeFcn', @resize,...
    'MenuBar', 'none',...
    'WindowStyle', 'normal',...
    'Visible', 'off');

  % create menu of main window
  menuFileId = uimenu(gui.mainFigId, 'Label','File');
  uimenu(menuFileId,...
    'Label','Open original signal',...
    'Callback',@openOriSig);
  uimenu(menuFileId,...
    'Label','Import original signal decomposition',...
    'Callback',@openOriDecompo);
  uimenu(menuFileId,...
    'Label','Import selection',...
    'Callback',@importSel);
  uimenu(menuFileId,...
    'Label','Import symbol',...
    'Callback',@importSymbol);
  uimenu(menuFileId,...
    'Label','Save modified signal',...
    'Callback',@saveModSig,...
    'Separator', 'on');
  uimenu(menuFileId,...
    'Label','Save original signal decomposition',...
    'Callback',@saveOriDecompo);
  uimenu(menuFileId,...
    'Label','Save modified signal decomposition',...
    'Callback',@saveModDecompo);
  uimenu(menuFileId,...
    'Label','Export selection as ...',...
    'Callback',@exportSel);
  uimenu(menuFileId,...
    'Label','Export symbol as ...',...
    'Callback',@exportSymbol);

  menuEditId = uimenu(gui.mainFigId,...
    'Label','Edit');
  gui.undoMenuId = uimenu(menuEditId,...
    'Label','Undo',...
    'Accelerator', 'z',...
    'enable', 'off',...
    'Callback',@undo);
  gui.redoMenuId = uimenu(menuEditId,...
    'Label','Redo',...
    'Accelerator', 'y',...
    'enable', 'off',...
    'Callback',@redo);

  menuFrameId = uimenu(gui.mainFigId,...
    'Label','Frame');
  menuFrameTypeId = uimenu(menuFrameId,...
    'Label','Choose frame type');
  for ind=1:length(gui.supportedFrames)
     uimenu(menuFrameTypeId,...
    'Label',gui.supportedFrames{ind}.type,...
    'Callback',@changeFrameType,...
    'UserData',ind);
  end
  uimenu(menuFrameId,...
    'Label','Edit frame parameters',...
    'Callback',@changeFrameDef);
  uimenu(menuFrameId,...
    'Label','Load frame parameters',...
    'Callback',@loadFrameParam,...
    'Separator', 'on');
  uimenu(menuFrameId,...
    'Label','Save frame parameters',...
    'Callback',@saveFrameParam);

  menuVisuId = uimenu(gui.mainFigId,...
    'Label','Visualization');
  % PI: the folowing checked on or off should be set automatically
  % during initialization
  gui.visuMenu.showModId = uimenu(menuVisuId,...
    'Label','Show modified signal',...
    'Checked', 'off',...
    'Callback',@toggleModVisu);
  gui.visuMenu.showOverviewId = uimenu(menuVisuId,...
    'Label','Show overview of original signal',...
    'Checked', 'off',...
    'Callback',@toggleOverviewVisu);
  
  % create gui panels
  currentY = 1;
  for indPanel = 1:length(gui.panelHeight)
    gui.panelId(indPanel) = uipanel(gui.mainFigId, ...
      'Title', gui.panelTitle{indPanel},...
      'TitlePosition', 'centertop',...
      'FontSize', gui.fontSize, ...
      'Units', 'pixels', ...
      'Position', [1 currentY gui.panelWidth gui.panelHeight(indPanel)]);
    currentY = currentY + gui.panelHeight(indPanel);
  end

  % create audioplayers panel
  drawAudioplayer(gui.panelId(1));

  % create visualization tools panel
  drawVisualizationTool(gui.panelId(2));

  % create selection tools panel
  drawSelectionTool(gui.panelId(3));
  
  resetMulaclab();
  toggleOverviewVisu(); % Do not show te overview window
  resetSel();
  resetSymbol();
  
  % end of initialization, show the interface to the user
  set(gui.mainFigId, 'Visible', 'on');
end

function [] = initializeParameter()

  global default gui visu export
  
  % Dictionary of structures field names string expansions
  gui.mulaclabDict = struct('winType','Window type',...
    'nbFreq','Number of frequency bins',...
    'hop','Hop size',...
    'winLen','Window length',...
    'wavelet','Wavelet type',...
    'J','Decomposition depth');
                   
  % Cellaray of supported frames                  
  gui.supportedFramesIdx = 1;
  gui.supportedFrames = ...
  {...
    struct('type','Gabreal','def',...
      struct('winType','hann','winLen',1024,'hop',256,...
             'nbFreq',2048)),... 
    struct('type','erblet','def',...
      struct('M',300,'downsampling','fractionaluniform')),... 
    ...
    struct('type','cqt','def',...
      struct('fmin',50,'fmax',20000,'bins',32,'downsampling','uniform')),... 
    ...
    struct('type','Gabor','def',...
      struct('winType','hann','winLen',1024,'hop',256,...
             'nbFreq',2048)),... 
    ...
    struct('type','DWT','def',...
      struct('wavelet','sym10','J',8)),...
    ...
    struct('type','UDWT','def',...
      struct('wavelet','sym10','J',8)),...
    ...
    struct('type','WFBT','def',...
      struct('wavelet','sym10','J',7)),... 
    ...
    struct('type','UWFBT','def',...
      struct('wavelet','sym10','J',4)),... 
  };
  
  
  % set default values

  % default colorscale dynamic in dB
  default.dynamic = 60;
  
  % default opacity parameter for symbol plotting (from 0:transparent to
  % 1:opaque)
  default.opacity = 0.5;
  
  % default selection
  default.sel.mode = 'union'; 
  default.sel.curLay = 1;
  default.sel.lay.convType = 'constGain';
  default.sel.lay.param(1).name = 'Gain';
  default.sel.lay.param(1).val = 0;
  default.sel.lay.lineWidth = 2;
  default.sel.lay.color = 'w';
  default.sel.lay.marker = 'none';
  default.sel.lay.markerSize = 2;
  default.sel.lay.lineStyle = '-';
  default.sel.lay.poly = [];
  default.sel.lay.label = 'Layer';
  default.frame = gui.supportedFrames{gui.supportedFramesIdx};
  
  default.symbol.data.name = 'Selection';
  default.symbol.data.val = [];
  default.symbol.data.invert = false;
  default.symbol.curSymb = 1;

  default.zerotodb = -100;
  
  % define parameters for the gui (graphical user interface)

  set(0,'Units','Pixels');
  screenSize = get(0,'ScreenSize');
  screenSize = screenSize(3:4);
  if screenSize(1) > 1920
    screenSize(1) = 1920;
  end
  if screenSize(2) > 1080
    screenSize(2) = 1080;
  end
  ratio = 0.8;
  gui.mainFigPos = round([screenSize*0.5*(1-ratio), round(screenSize*ratio)]);
  gui.fontSize = 9;
  gui.textHeight = gui.fontSize + 7;
  gui.textWidth = 57;
  gui.editWidth = 28;
  gui.buttonBackgroundColor = [0.7 0.7 0.7]; % chosen to fit icons color

  if isoctave()
    gui.buttonWidth = 40;
  else
    gui.buttonWidth = 27;
  end
  
  gui.buttonHeight = 22;
  gui.horiDist = 3;
  gui.vertDist = 2;
  gui.margin = [6, 6];
  gui.marginSub= [3, 3];
  nbButtton = 3; % number of buttons on one line
  gui.panelWidth = 2*gui.margin(2) + (nbButtton-1) * gui.horiDist +...
    nbButtton*gui.buttonWidth;

  gui.panelHeight(1) = 96;
  gui.panelTitle{1} = 'Audioplayer';

  gui.panelHeight(2) = 228;
  gui.panelTitle{2} = 'Visualization';

  gui.panelHeight(3) = 268;
  gui.panelTitle{3} = 'Selection';

  gui.visuResizeButtonWidth = 15;
  gui.visuResizeButtonHeight = 15;

  gui.curTool = 'freehand';

  gui.tool = struct;
  gui.symbOpacity = 0.5;
  gui.showSymb = false;
  gui.symbImageId = [];

  gui.player.buttongroupId = [];

  gui.iconpath=[ltfatbasepath,'mulaclab',filesep,'icons',filesep];
  
  % define parameters for the visualizations

  % NOTE: height of visu at initilisation must be choosen so that the sum
  % of visible visu is 1
  
  % Main visualization: spectrogram of the original signal
  visu(1).label = 'originalMain';
  visu(1).height = 0.67;
  visu(1).position = [0, 0, 1, 1]; % we can initialise this with wathever value
                                   % as it will be corrrected during resize
  visu(1).updateFcn = @updateVisuOriSpec;
  visu(1).dynamic = default.dynamic;
  visu(1).visible = true; % Important: the orginalMain visu must always stay
                          % visible
  visu(1).linkX = true;
  visu(1).linkY = true;
  visu(1).linkCLim = true;
  visu(1).uipanelId = [];
  visu(1).axesId = [];

  % Visualization of the spectrogram of the modified signal
  visu(2).label = 'modified';
  visu(2).height = 0.;
  visu(2).position = [0, 0, 1, 1];
  visu(2).updateFcn = @updateVisuModSpec;
  visu(2).dynamic = default.dynamic; % useless if linkCLim is true
  visu(2).visible = false;
  visu(2).linkX = true;
  visu(2).linkY = true;
  visu(2).linkCLim = true;
  visu(2).uipanelId = [];
  visu(2).axesId = [];

  % Visualization for the overview of the original spectrogram with fast
  % scrolling
  visu(3).label = 'originalOverview';
  visu(3).height = 0.33;
  visu(3).position = [0, 0, 1, 1];
  visu(3).updateFcn = @updateVisuOriOverview;
  visu(3).dynamic = default.dynamic; % useless if linkCLim is true
  visu(3).visible = true;
  visu(3).linkX = false;
  visu(3).linkY = false;
  visu(3).linkCLim = true;
  visu(3).uipanelId = [];
  visu(3).axesId = [];
  
  gui.ResizeVisuButtonId = NaN(length(visu), 1);
  
  % set export parameters
  export.xLim = 800;
  export.limitXaxesRes = false;
  export.symbol = {};
end

function checkToolbox()

  global default gui

  if isoctave()
    if getOctaveVersion() < 4.0
      error(['This function requires Octave version 4.0.0 or higher.']);
    end
  end

  % Check that the function for polygon clipping is available
  checkPolygonClipping();

  % Check that the Image Processing Toolbox is available
  if isoctave()
    imageInfo = ver('image');
    if isempty(imageInfo) || isempty(imageInfo.Version)
      warning(do_string_escapes(...
        ['For improved features in mulaclab, you can install the Image '...
         'package\nfrom Octave-forge using the following command:\n'...
         '  pkg install -forge image']));
      gui.hasImagePackage = false;
    else
      % load the image package in case it is not already loaded
      pkg('load', 'image');
      gui.hasImagePackage = true;
    end
    
  else
    if isempty(ver('images'))
      gui.hasImagePackage = false;
    else
      gui.hasImagePackage = true;
    end
  end
end

function checkPolygonClipping()
  global default gui
  
  usePolygonClip = false;
  useBinMasks = false;
  if strcmp(default.backendClipPoly, 'clipper')
    temp1 = {[1; 1]};
    temp2 = temp1;
    try   
      [tempPol, tempHoles] = polyboolclipper(temp1, temp2, 'or');
      gui.backendClipPoly = 'clipper';
      return;
    catch 
      useBinMasks = true;
    end
  end
  if strcmp(default.backendClipPoly, 'polygonclip') || usePolygonClip
    temp1.x = [1];
    temp1.y = [1];
    temp1.hole = false;
    temp2 = temp1;
    try
      temp = PolygonClip(temp1, temp2);
      gui.backendClipPoly = 'polygonclip';
      return;
    catch 
      useBinMasks = true;
    end
  end
  if strcmp(default.backendClipPoly, 'binmasks') || useBinMasks
    if useBinMasks
      warning(sprintf(...
        ['No compiled function for polygon clipping was found, switching to'...
         '\nbinary masks for polygon clipping.\n'...
         'Please compile using the "ltfatmex" command for a more efficient '...
         'solution.']));
    end
    gui.backendClipPoly = 'binmasks';
  end
end

% ___________________ COMPUTATIONAL FUNCTIONS _____________________________

function coef = calculateCoeff(f)

  global frame coeff

  switch lower(frame.type)
    case 'gabreal'
      win = firwin(frame.def.winType,frame.def.winLen);
      coef= dgtreal(f, win, frame.def.hop, frame.def.nbFreq);
    case 'gabor'
      win = firwin(frame.def.winType,frame.def.winLen);
      coef = dgt(f, win, frame.def.hop, frame.def.nbFreq);
    case {'erblet','cqt'}
      coef = filterbank(f,frame.precomp.g,frame.precomp.a);
    case 'dwt'
      [coef, coeff.info] = fwt(f,frame.def.wavelet,frame.def.J);
    case 'wfbt'
      [coef, coeff.info] = wfbt(f,{frame.def.wavelet,frame.def.J,'full'});
    case 'uwfbt'
      [coef, coeff.info] = uwfbt(f,{frame.def.wavelet,frame.def.J,'full'});
    case 'udwt'
      [coef, coeff.info] = ufwt(f,frame.def.wavelet,frame.def.J);
    otherwise
      error('%s: Unrecognized frame type',upper(mfilename));
  end
end

function fhat = recFromCoeff(coef)
   
  global coeff frame sig

  switch lower(frame.type)
    case 'gabreal'
      win = gabdual(firwin(frame.def.winType,frame.def.winLen),...
        frame.def.hop, frame.def.nbFreq);
      fhat = idgtreal(coef, win, frame.def.hop,frame.def.nbFreq,...
        length(sig.ori));
    case 'gabor'
      win = gabdual(firwin(frame.def.winType,frame.def.winLen),...
        frame.def.hop, frame.def.nbFreq);
      fhat = idgt(coef, win, frame.def.hop,length(sig.ori));
    case {'erblet','cqt'}
      fhat = 2*real(ifilterbank(coef,frame.precomp.gdual,frame.precomp.a,...
        length(sig.ori)));   
    case 'dwt'
      fhat = ifwt(coef,coeff.info);
    case 'wfbt'
      fhat = iwfbt(coef,{frame.def.wavelet,frame.def.J,'full'},length(sig.ori));
    case 'uwfbt'
      fhat = iuwfbt(coef,{frame.def.wavelet,frame.def.J,'full'});
    case 'udwt'
      fhat = iufwt(coef,coeff.info);
    otherwise
      error('%s: Unrecognized frame type',upper(mfilename)); 
  end
  fhat = real(fhat);
end

function C = plotCoeff(coef,axesId,key,value)

  global coeff frame sig export

  switch lower(frame.type)
    case 'gabreal'
      C = plotdgtreal(coef,frame.def.hop,frame.def.nbFreq,sig.sampFreq,key,...
        value);
    case 'gabor'
      C = plotdgt(coef,frame.def.hop,sig.sampFreq,key,value);
    case {'erblet','cqt'}
      C = plotfilterbank(coef,frame.precomp.a,frame.precomp.fc,sig.sampFreq,...
        key,value);
    case {'dwt','udwt','wfbt','uwfbt','ufwt'}
      C = plotwavelets(coef,coeff.info,sig.sampFreq,key,value);
    otherwise
      error('%s: Unrecognized frame type',upper(mfilename));
  end
  if export.limitXaxesRes
    C=interp1(linspace(0,1,size(C,2)),C.',linspace(0,1,export.xLim),'nearest');
    C=C.';
  end
end

function mult = convSymbToCoefFormat(symb)

  global frame export coeff

  switch lower(frame.type)
    % Classical gabor coeff format 
    case {'gabreal','gabor'}
      if strcmp(lower(frame.type), 'gabor')
        % we need to invert the circshift that was done in plotdgt to move zero
        % frequency to the center and Nyquist frequency to the top
        M = size(symb, 1);
        if rem(M, 2) == 0
          symb = circshift(symb, -(M/2-1));
        else
          symb = circshift(symb, -(M-1)/2);
        end
      end
      if export.limitXaxesRes
        L = size(coeff.ori,2);
        Lplot = size(symb,2);
        mult=interp1(linspace(0,1,Lplot),symb.',linspace(0,1,L),'nearest');
        mult = mult.';
      else
        mult = symb;
      end
       
    % Cell array containing column vectors format
    case {'wfbt','erblet','cqt'}
      M = numel(coeff.ori);
      Lplot = size(symb,2);
      Lc = cellfun(@(cEl) size(cEl,1),coeff.ori);
      mult = cell(size(coeff.ori));
      for m=1:M
        mult{m} = interp1(linspace(0,1,Lplot),symb(m,:),linspace(0,1,Lc(m)),...
          'nearest').';
      end
    % FWT specific coefficient format
    case 'dwt'
      Lc = coeff.info.Lc;
      Lcstart = cumsum([1;Lc(1:end-1)]);
      Lcend = cumsum(Lc);
      mult = zeros(sum(Lc),1);
      M = numel(Lc);
      Lplot = size(symb,2);
      for m=1:M
        mult(Lcstart(m):Lcend(m)) = interp1(1:Lplot,symb(m,:),...
          linspace(1,Lplot,Lc(m)),'nearest').';
      end  
    % ufilterbak and all wavelet functions beginning with u
    case {'udwt','uwfbt'}
      M = size(coeff.ori,2);
      L = size(coeff.ori,1);
      Lplot = size(symb,2);
      mult = zeros(size(coeff.ori));
      for m=1:M
        mult(:,m) = interp1(1:Lplot,symb(m,:),linspace(1,Lplot,L),'nearest').';
      end
    otherwise
      error('%s: Unrecognized frame type',upper(mfilename));
  end

end

function symb = convCoefFormatToSymb(mult)

  global frame export coeff

  switch lower(frame.type)
    % Classical gabor coeff format 
    case {'gabreal','gabor'}
      if export.limitXaxesRes
        L = size(coeff.ori,2);
        Lmult = size(mult,2);
        symb=interp1(linspace(0,1,Lmult),mult.',linspace(0,1,L),'nearest');
        symb = symb.';
      else
        symb=mult;
      end

    % Cell array containing column vectors format
    case {'wfbt','erblet','cqt'}
      M = numel(coeff.ori);
      Lsymb = size(coeff.oriC,2);
      Lc = cellfun(@(cEl) size(cEl,1),mult);
      symb = zeros(size(coeff.oriC));
      for m=1:M
        symb(m,:) = interp1(linspace(0,1,Lc(m)),mult{m},linspace(0,1,Lsymb),...
          'nearest');
      end
    % FWT specific coefficient format
    case 'dwt'
      Lc = coeff.info.Lc;
      Lcstart = cumsum([1;Lc(1:end-1)]);
      Lcend = cumsum(Lc);
      symb = zeros(size(coeff.oriC));
      %mult = zeros(sum(Lc),1);
      M = numel(Lc);
      Lplot = size(symb,2);
      for m=1:M
       symb(m,:) = interp1(linspace(1,Lplot,Lc(m)),mult(Lcstart(m):Lcend(m)),...
         1:Lplot,'nearest');
      end  
    % ufilterbak and all wavelet functions beginning with u
    case {'udwt','uwfbt'}
      L = size(coeff.oriC,2);
      M = size(coeff.ori,2);
      Lmult = size(mult,1);
      symb = zeros(size(coeff.oriC));
      for m=1:M
        symb(m,:) = interp1(1:Lmult,mult(:,m),linspace(1,Lmult,L),'nearest');
      end
    otherwise
      error('%s: Unrecognized frame type',upper(mfilename));
  end
end


function precomputeStuff()

  global frame sig

  if isfield(frame,'precomp') && isfield(frame.precomp,'L') ...
      && frame.precomp.L == length(sig.ori)
    % Already done, do nothing.
    return;
  end
  frame.precomp = struct();
  frame.precomp.L = length(sig.ori);      
  
  switch lower(frame.type)
    case 'erblet'
      [frame.precomp.g,frame.precomp.a,frame.precomp.fc] = erbfilters(...
        sig.sampFreq,length(sig.ori),'M',frame.def.M,frame.def.downsampling);
      frame.precomp.gdual = filterbankrealdual(frame.precomp.g,...
        frame.precomp.a,length(sig.ori));
    case 'cqt'
      [frame.precomp.g,frame.precomp.a,frame.precomp.fc] = cqtfilters(...
        sig.sampFreq,frame.def.fmin,frame.def.fmax,frame.def.bins,...
        length(sig.ori),frame.def.downsampling);
      frame.precomp.gdual = filterbankrealdual(frame.precomp.g,...
        frame.precomp.a,filterbanklength(length(sig.ori),frame.precomp.a));
  end
end

function applyMultiplier(c,mult)

  global coeff sig visu

  if(isnumeric(c)&&isnumeric(mult))
    coeff.mod = c.*mult;
  elseif(iscell(c)&&iscell(mult))
    coeff.mod = cellfun(@(x,y) x.*y,c,mult,'UniformOutput',false);
  else
    error('%s: Unrecognized frame type',upper(mfilename));
  end

  sig.mod  = recFromCoeff(coeff.mod);    
  updatePlayerMod;
  coeff.mod = calculateCoeff(sig.mod);
  modInd = find(strcmp('modified', {visu.label}), 1);
  if ~visu(modInd).visible
   toggleModVisu;
  end
  updateVisu;
end

function applySel(objId, eventData)    
  % convert the selection to get the symbol of the multiplier and apply 
  % the multiplier
  
  global symbol coeff
  
  if isempty(symbol.data(symbol.curSymb).val)    
    mult = convSymbToCoefFormat(convSelToSymb());
  else
    mult = convSymbToCoefFormat(symbol.data(symbol.curSymb).val);
  end
  applyMultiplier(coeff.ori, mult);
end

function symb = convSelToSymb()

  global coeff sel

  symb = ones(size(coeff.oriC));
  
  for indLay = 1:length(sel.lay)
    if ~isempty(sel.lay(indLay).poly)
      mask = convPolyToMask(sel.lay(indLay).poly);
      symbCurr = convMaskToSymb(mask, indLay);
      symb = symb.*symbCurr;
    else
      warning(['Selection layer ' num2str(indLay), ' is currently empty']);
    end
  end

end

function mask = convPolyToMask(poly)

  global coeff

  mask = zeros(size(coeff.oriC)); 
  % PI: type of data for the mask might be optimized (bool, uint8?)

  % PI: could be optimized by doing the conversion in smaller boxes 
  % around each polygon (not on the whole time-frequency plane)
  for ind = 1:length(poly)
    xvals = convAxesToIndX(poly(ind).x)-0.5;
    yvals = convAxesToIndY(poly(ind).y)-0.5;    
    tmpmask = octave_poly2mask(xvals,yvals,size(mask,1), size(mask,2)); 
    if poly(ind).hole
      mask = mask - tmpmask;
    else
      mask = mask + tmpmask;
    end
  end
end

function symb = convMaskToSymb(mask, indLay)

  global sel sig coeff

  % NOTE: a case should be added here when creating new selection type
  switch sel.lay(indLay).convType
    case 'constGain'
      gain = sel.lay(indLay).param(1).val;
      symb = ones(size(mask));
      symb = symb + (gain-1)*mask;
      %randphase = 2*pi*randn(size(symb));
      %symb(mask>0) = exp(1i*randphase(mask>0));
    case 'smoothBorder'
      gain = sel.lay(indLay).param(1).val;
      threshold = sel.lay(indLay).param(2).val;
      % bwdist returns single
      symb = double(bwdist(~mask));
      symb(symb > threshold) = threshold;
      symb = symb * (gain-1)/threshold + 1;
    case 'fill'
      % simple filling test, just interpolating in 3D the modulus
      
      % estimation of the level in the neighbourhood of the selection
      
      timeRadius = sel.lay(indLay).param(1).val;
      freqRadius = sel.lay(indLay).param(2).val;
      
      % find the points that are just next to the selection
      neighbour = imdilate(mask, strel('square',3)) - mask;
      
      % for these points, compute mean level in a specified neighbourhood
      % (but not taking into account the values of the transform inside 
      % the selection)
      
      % create the matrix defining the neighbourhood
      mat = computeEllipse(freqRadius, timeRadius); 
      % NOTE: this could be a more general filter if we want
      
      % PI: in the following applications of filter2, we need only the filtered
      % values for the points in neighbour, so this could be computed more
      % efficiently if needed
      temp1 = filter2(mat, 10.^(coeff.oriC/20) .* (1-mask));
      temp2 = filter2(mat, 1-mask);
      
      [ind1Neigh, ind2Neigh] = find(neighbour);

      valNeigh = sqrt(temp1(sub2ind(size(temp1), ind1Neigh,...
        ind2Neigh))./temp2(sub2ind(size(temp2), ind1Neigh, ind2Neigh)));

      [ind1Mask, ind2Mask] = find(mask);

      try 
        valInterp = griddata(ind1Neigh, ind2Neigh, valNeigh,...
          ind1Mask, ind2Mask, 'linear');
      catch
        % sometime griddata fail, and adding this option seem to solve
        % the problem, but it is not clear why
        valInterp = griddata(ind1Neigh, ind2Neigh, valNeigh,...
          ind1Mask, ind2Mask, 'linear', {'QJ'});
      end

      symb = ones(size(mask));
      % PI: there could be divisions by zero in the following, this
      % should be cleanly handled
      symb(sub2ind(size(symb), ind1Mask, ind2Mask)) = valInterp ./...
        abs(coeff.oriC(sub2ind(size(coeff.oriC), ind1Mask, ind2Mask)));

    case 'fillNoise'
      % other hole filling test, using coefficients taken from a noise
      % estimation of the level in the neighbourhood of the selection
      
      timeRadius = sel.lay(indLay).param(1).val;
      freqRadius = sel.lay(indLay).param(2).val;
      
      % find the points that are just next to the selection
      neighbour = imdilate(mask, strel('square',3)) - mask;
      
      % for these points, compute mean level in a specified neighbourhood
      % (but not taking into account the values of the transform inside 
      % the selection)
      
      % create the matrix defining the neighbourhood
      mat = computeEllipse(freqRadius, timeRadius);
      % NOTE: this could be a more general filter if we want
      
      % PI: in the following applications of filter2, we need only the filtered
      % values for the points in neighbour, so this could be computed more
      % efficiently if needed
      temp1 = filter2(mat, 10.^(coeff.oriC/20).* (1-mask));
      temp2 = filter2(mat, 1-mask);
      
      [ind1Neigh, ind2Neigh] = find(neighbour);

      valNeigh = sqrt(temp1(sub2ind(size(temp1), ind1Neigh,...
        ind2Neigh))./temp2(sub2ind(size(temp2), ind1Neigh, ind2Neigh)));

      [ind1Mask, ind2Mask] = find(mask);

      try 
        valInterp = griddata(ind1Neigh, ind2Neigh, valNeigh, ...
          ind1Mask, ind2Mask, 'linear');
      catch
        % sometime griddata fail, and adding this option seem to solve
        % the problem, but it is not clear why
        valInterp = griddata(ind1Neigh, ind2Neigh, valNeigh,...
          ind1Mask, ind2Mask, 'linear', {'QJ'});
      end

      symb = ones(size(mask));

      % compute the transform of a noise

      noise = rand(numel(sig.ori), 1) - 0.5;

      coeffNoise = calculateCoeff(noise);

      % PI: here all the interpolations and mean are done directly on the
      % absolute value of the  coefficients, it could be more meaningfull
      % to work with the sqaure of the modulus due to its possible
      % intepretation a energy ditribution
      coeffNoiseSymb = convCoefFormatToSymb(coeffNoise);
      meanLevelNoise = sum(abs(coeffNoiseSymb(sub2ind(size(coeffNoiseSymb), ...
        ind1Mask, ind2Mask-min(ind2Mask)+1)))) / length(ind1Mask);

      % PI: there could be divisions by zero in the following, this
      % should be cleanly handled
      symb(sub2ind(size(symb), ind1Mask, ind2Mask)) = valInterp .*...
        coeffNoiseSymb(sub2ind(size(coeffNoiseSymb), ind1Mask,...
        ind2Mask-min(ind2Mask)+1)) ./...
        (coeffNoiseSymb(sub2ind(size(coeffNoiseSymb), ind1Mask, ind2Mask)) *...
        meanLevelNoise);    
  end % of switch
end

function xData = convIndToAxesX(ind)
  % convert scale from image index to scale used by axes along X axe
  
  global sig coeff visucommon
  
  xData = (ind-1) * visucommon.timeStep;
end

function ind = convAxesToIndX(xData)
  % convert scale from scale used by axes to image index along X axe
  % NOTE: there is no round (on purpose), it must be added if when we really
  % want an integer index
  
  global visucommon

  ind = xData / visucommon.timeStep + 1;
end

function yData = convIndToAxesY(ind)
  % convert scale from image index to scale used by axes along Y axe
  
  global visucommon coeff
  
  Ylim = visucommon.oriYlim;
  yData = (ind-0.5) * ((Ylim(2)-Ylim(1)) / size(coeff.oriC,1)) + Ylim(1);
end

function ind = convAxesToIndY(yData)
  % convert scale from scale used by axes to image index along Y axe
  % NOTE: there is no round (on purpose), it must be added if when we really
  % want an integer index
  
  global visucommon coeff
  
  Ylim = visucommon.oriYlim;
  ind = (yData-Ylim(1)) * (size(coeff.oriC,1) / (Ylim(2)-Ylim(1))) + 0.5;
end

function ellipse = computeEllipse(dim1Radius, dim2Radius)
 
  ellipse = zeros(2*dim1Radius+1, 2*dim2Radius+1);
  ellipse(dim1Radius+1, :) = 1;
  ellipse(:, dim2Radius+1) = 1;
  for ind1 = 1:dim1Radius-1
    for ind2 = 1:dim2Radius-1
      if ((ind1/dim1Radius)^2 + (ind2/dim2Radius)^2) <= 1
        ellipse(dim1Radius+1+ind1, dim2Radius+1+ind2) = 1;
        ellipse(dim1Radius+1-ind1, dim2Radius+1+ind2) = 1;
        ellipse(dim1Radius+1+ind1, dim2Radius+1-ind2) = 1;
        ellipse(dim1Radius+1-ind1, dim2Radius+1-ind2) = 1;
      end
    end
  end
end


% _____________________ GENERAL FUNCTIONS FOR UI __________________________
  
function matlabVersion = getMatlabVersion()
  matlabInfo = ver('Matlab');
  matlabVersion = convertVersion(matlabInfo.Version);
end
  
function octaveVersion = getOctaveVersion()
  octaveInfo = ver('Octave');
  octaveVersion = convertVersion(octaveInfo.Version);
end
  
function versionNum = convertVersion(versionString)
  % the version number is sometime of the form X.X.X, so it cannot be
  % easily converted into a number, so we need to modify it so that it 
  % is more usable for comparison
  temp = find(versionString == '.');
  temp = temp(2:end);
  for ind = 1:size(temp, 2)
    % we remove the extra points '.' in the string
    versionString = versionString([1:temp(ind)-1, temp(ind)+1:end]);
  end
  versionNum = str2num(versionString);
end

function resetMulaclab()  
  
  global coeff gui sig visu visucommon

  initializePlayer();
  precomputeStuff();

  coeff.ori = calculateCoeff(sig.ori);
  sig.mod = sig.ori;
  coeff.mod = coeff.ori;
  resetSel();
  undoData = {};
  modInd = find(strcmp('modified', {visu.label}), 1);
  if visu(modInd).visible
     toggleModVisu();
  end
  updateVisu(true); % update visualization with reset of axes limits

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  visucommon.oriXlim = get(visu(oriInd).axesId,'Xlim');
  visucommon.oriYlim = get(visu(oriInd).axesId,'Ylim');

  % activate the current tool
  changeTool([], [], gui.curTool);
end

function openOriSig(objId, eventData) 
  
  global sig

  [fileName, pathName] = uigetfile('*.wav',...
    'Open original signal');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  [newSig, sampFreq] = wavload([pathName, fileName]);
  if size(newSig, 2) > 1
    % multichannel wave, we keep only the first channel
    newSig = newSig(:, 1);
  end
  sig.sampFreq = sampFreq;
  sig.ori = newSig;
  sig.mod = newSig;
  sig.real = true;

  resetMulaclab();
end

function saveModSig(objId, eventData)

  global sig

  [fileName, pathName] = uiputfile('*.wav', 'Save modified signal');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  wavsave(sig.mod, sig.sampFreq,[pathName, fileName]);
end

function openOriDecompo(objId, eventData)

  global frame sig

  [fileName, pathName] = uigetfile('*.mat', 'Load frame parameters');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  data = load([pathName, fileName]);
  if ~(isfield(data, 'frame') && isfield(data, 'savedSig'))
    errordlg([fileName ' is not a valid signal decomposition file']);
    return;
  end
  
  frame = data.frame;
  sig = data.savedSig;
  sig.mod = sig.ori;
  
  resetMulaclab();
end

function saveOriDecompo(objId, eventData)

  global sig frame

  [fileName, pathName] = uiputfile('*.mat', 'Export selection');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  savedSig = sig;
  rmfield(savedSig, 'mod');
  save([pathName, fileName], 'frame', 'savedSig');
end

function saveModDecompo(objId, eventData)

  global sig frame

  [fileName, pathName] = uiputfile('*.mat', 'Export selection');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  savedSig = sig;
  savedSig.ori = savedSig.mod;
  rmfield(savedSig, 'mod');
  save([pathName, fileName], 'frame', 'savedSig');
end

function exportSel(objId, eventData)

  global sel

  [fileName, pathName] = uiputfile('*.mat', 'Export selection');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  save([pathName, fileName], 'sel');
end

function exportSymbol(objId, eventData)

  global gui coeff visu

  [fileName, pathName] = uiputfile('*.png', 'Export symbol as...');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  cm = get(gui.mainFigId,'Colormap');
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  Clim = get(visu(oriInd).axesId,'CLim');
  oriIdx = round((flipud(coeff.oriC)-Clim(1))/(Clim(2)-Clim(1))*255)+1;
  oriExt = zeros([size(coeff.oriC),3]);
  oriExt(:,:,1) = reshape(cm(oriIdx,1),size(coeff.oriC));
  oriExt(:,:,2) = reshape(cm(oriIdx,2),size(coeff.oriC));
  oriExt(:,:,3) = reshape(cm(oriIdx,3),size(coeff.oriC));
  
  absSymb = flipud(abs(convSelToSymb()));
  if isoctave()
    % Octave doesn't yet support the bitdepth parameter in imwrite
    imwrite(oriExt,[pathName, fileName],'png','Alpha',absSymb);
  else
    imwrite(oriExt,[pathName, fileName],'png','Alpha',absSymb,'bitdepth',16);
  end
end

function importSel(objId, eventData)

  global sel gui

  [fileName, pathName] = uigetfile('*.mat', 'Import selection');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  data = load([pathName, fileName]);
  if ~isfield(data, 'sel')
    errordlg([fileName ' is not a valid signal selection file']);
    return;
  end
  
  % as the saved selection might have been created using a longer signal,
  % or a signal with a higher sampling frequency, it could be wider that
  % the part of the time-frequency plane corresponding to the signal. 
  % We need to force the selection to stay in the right region
  fullPoly = fullSigPoly;
  for ind = 1:length(data.sel.lay)
    % intersection of the polygon with a polygon covering the whole
    % signal
    data.sel.lay(ind).poly = clipPoly(data.sel.lay(ind).poly,...
      fullPoly, 1);
  end
  
  delSel;
  sel.lay(end+1:end+length(data.sel.lay)) = data.sel.lay;
  updateLayerList;
  set(gui.layListId, 'Value', 1);
  changeSelLay;
  drawAllSel;
end

function importSymbol(objId, eventData)
 
  global coeff default gui symbol

  [fileName, pathName] = uigetfile('*.png', 'Import symbol');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  [imag, ~, alpha] = imread([ pathName,fileName]);
  if(size(imag,3)>1)
    if isempty(alpha)
      errordlg(sprintf('%s does not contain alpha channel.',fileName));
      return;
    end
    mask = double(alpha)/double(max(alpha(:)));
  else
    mask = double(imag)/double(max(imag(:)));
  end

  if any(size(coeff.oriC)~=size(mask))
    errordlg([fileName ' is not a valid symbol image. The dimensions are different.']);
    return;
  end
  
  %limit
  mmss = 10^(default.zerotodb/20);
  mask(abs(mask)<mmss) = mmss;

  addSymbolListItem(flipud(mask));
  symbol.curSymb = numel(symbol.data);
  set(gui.symbListId, 'Value',symbol.curSymb);
  updateSymbolList();
  handleSymb();
end

function changeFrameDef(objId, eventData)
  % Menu callback function when frame parameters are changed
  changeFrameDialog();
end
 
function changeFrameType(objId, eventData)
  % Menu callback function when frame type is changed

  global gui frame

  resetSymbol(); 
   
  gui.supportedFramesIdx = get(objId,'UserData'); 
  newFrame = gui.supportedFrames{gui.supportedFramesIdx};
  if ~isempty(newFrame)
    frame = newFrame;
    resetMulaclab(); 
  end
end

function changeFrameDialog()

  global gui frame
  
  margin = [10, 10];
  textSize = [170, 20];
  editSize = [80, 20];
  spaceSize = [10, 10];
  
  nbParam = numel(fieldnames(frame.def));
  dialogPosition = [200, 200, ...
    2*margin(1)+textSize(1)+editSize(1)+spaceSize(1),...
    2*margin(2)+(nbParam+1)*textSize(2)+nbParam*spaceSize(1)];
  
  try
    gui.changeFrame.dialogId = dialog(...
      'Name', [frame.type,' frame parameters'],...
      'Position', dialogPosition);
  catch % dialog is not yet implemented in Octave so we use a simple figure
    gui.changeFrame.dialogId = figure(...
      'Name', [frame.type,' frame parameters'],...
      'Position', dialogPosition,...
      'Menubar', 'none',...
      'windowstyle', 'modal');
    % Note: the 'modal' windowstyle seems ignored in Octave
    % So we need to make the main window unclicable by hand
    % and the only solution seem to be hinding the main window, as the
    % following is not enough (the buttons are still clickable):
    % set(gui.mainFigId, 'HitTest', 'off');
    set(gui.mainFigId, 'Visible', 'off');
    uimenu(gui.changeFrame.dialogId); % Removing the default menu
  end
  
  buttonWidth = 50;
  buttonX = round((dialogPosition(3) -...
    (2*buttonWidth + spaceSize(1)))/2);
  
  buttonOkId = uicontrol(...
    'Parent', gui.changeFrame.dialogId,...
    'Style', 'pushbutton',...
    'String', 'OK',...
    'Callback', @callbackOk,...
    'Position', [buttonX, margin(2), buttonWidth, textSize(2)]);
  
  
  buttonCancelId = uicontrol(...
    'Parent', gui.changeFrame.dialogId,...
    'Style', 'pushbutton',...
    'String', 'Cancel',...
    'Callback', @callbackCancel,...
    'Position', [buttonX+buttonWidth+spaceSize(1), margin(2),...
      buttonWidth, textSize(2)]);

  fieldNames = fieldnames(frame.def);
  fieldNames = fieldNames(end:-1:1);
  gui.changeFrame.objIds = zeros(size(fieldNames));
  
  for ind = 1:length(fieldNames)
     posText = [margin+[0, ind*(spaceSize(2)+textSize(2))], textSize];
     posEdit = [margin+[0,...
        ind*(spaceSize(2)+textSize(2))]+[textSize(1)+spaceSize(1), 0],...
        editSize];
     
     propLabel = fieldNames{ind};
     mulaclabDict = gui.mulaclabDict;
     if(exist('mulaclabDict','var')&&isfield(mulaclabDict,propLabel))
        propLabel = getfield(mulaclabDict,propLabel);
     end
        
     uicontrol(...
     'Parent', gui.changeFrame.dialogId,...
     'Style', 'text',...
     'String', propLabel,...
     'Position', posText);
 
    strVal = getfield(frame.def,fieldNames{ind});
    if(isnumeric(strVal))
       strVal = num2str(strVal);
    end
     gui.changeFrame.objIds(ind) = uicontrol(...
     'Parent', gui.changeFrame.dialogId,...
     'Style', 'edit',...
     'String', strVal,...
     'Position', posEdit);
  end
  
end
    
function callbackOk(objId, eventData)

  global frame gui
  
  newFrame = frame;
  if isfield(newFrame,'precomp')
      newFrame = rmfield(newFrame,'precomp');
  end
  
  fieldNames = fieldnames(frame.def);
  fieldNames = fieldNames(end:-1:1);
  
  for ind2 = 1:length(fieldNames)
    val = get(gui.changeFrame.objIds(ind2),'string');
    if isempty(val)
      errordlg(sprintf('%s parameter is invalid.',fieldNames{ind2}));
      return;
    end
    if(isnumeric(getfield(frame.def,fieldNames{ind2})))
      newFrame.def = setfield(newFrame.def,fieldNames{ind2},...
        round(str2num(val)));
    else
      newFrame.def = setfield(newFrame.def,fieldNames{ind2},val);
    end
  end
  frame = newFrame;
  close(gui.changeFrame.dialogId);
  resetMulaclab();
  set(gui.mainFigId, 'Visible', 'on'); % needed for Octave
end
    
function callbackCancel(objId, eventData)

  global gui
  
  set(gui.mainFigId, 'Visible', 'on'); % needed for Octave
  close(gui.changeFrame.dialogId);
end

function loadFrameParam(objId, eventData)

  global frame coeff sig

  [fileName, pathName] = uigetfile('*.mat', 'Load frame parameters');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  data = load([pathName, fileName]);
  if ~isfield(data, 'frame')
    errordlg([fileName ' is not a valid signal frame file']);
    return;
  end
  frame = data.frame;
  resetMulaclab();
end

function saveFrameParam(objId, eventData)

  global frame

  [fileName, pathName] = uiputfile('*.mat', 'Save frame parameters');
  if fileName == 0
    % user pressed cancel, stop here
    return;
  end
  save([pathName, fileName], 'frame');
end

function pos = buttonPos(indHori, indVert, shift) 
  % compute the position of a button (useful to align buttons)
  % indHori: horizontal index of the button for which we want the 
  % position (first element with index 1 on the left)
  % indVert: vertical index of the button for which we want the 
  % position (first element with index 1 at the bottom)
  
  global gui
  
  pos = [shift(1) + (indHori-1)*(gui.buttonWidth+gui.horiDist)-1,...
    shift(2) + (indVert-1)*(gui.buttonHeight+gui.vertDist)-1,...
    gui.buttonWidth,...
    gui.buttonHeight];
  
end

function subSize = subpanelSize(nbRow)

  global gui
  
  subSize = [(gui.panelWidth - 4),...
    (2*gui.margin(2) + nbRow*gui.buttonHeight +...
    (nbRow-1) * gui.vertDist + gui.fontSize -4)];
end

function [nbFreq] = nbPlottedFreq()

  global coeff

  nbFreq = size(coeff.oriC, 1);
end

function [] = resize(objId, eventData)

  global gui visu

  figPos = get(gui.mainFigId, 'Position');
  xVisu = gui.panelWidth / figPos(3);
  % we need the following test to avoid a bug during creation
  if isinf(xVisu) || isnan(xVisu) 
    xVisu = 0.5;
  end
  
  widthVisu = (figPos(3)-gui.panelWidth) / figPos(3);
  % we need the follwing test to avoid a bug during creation
  if ~(widthVisu > 0)
    widthVisu = 0.5;
  end
  
  curY = 1;
  for ind = 1:length(visu)
    if visu(ind).visible
      curY = curY - visu(ind).height;
      visu(ind).position = [xVisu curY widthVisu visu(ind).height];
      set(visu(ind).uipanelId, 'Position', visu(ind).position);
      
      if ishandle(gui.ResizeVisuButtonId(ind))
        set(gui.ResizeVisuButtonId(ind), 'Position',...
          [figPos(3)-gui.visuResizeButtonWidth,...
            round(curY*figPos(4)-gui.visuResizeButtonHeight/2),...
            gui.visuResizeButtonWidth, ...
            gui.visuResizeButtonHeight]);
      end
    end
  end
end

function updateUndoData()

  global undoData redoData gui sel

  undoData{end+1} = sel;
  set(gui.undoMenuId, 'Enable', 'on');
  redoData = {};
  set(gui.redoMenuId, 'Enable', 'off');
end

function undo(objId, eventData)
  % PI: Currently the implementation of undo/redo is very rudimentary. 
  % Only undo/redo of actions modifying the polygons of the selection sel
  % are taken into account. For example, zoom actions, or change of layer 
  % parameters (type of conversion, color of line, ...) are not taken 
  % into account. A more general handling could be implemented.
  % The memory consumption for undo could also be improved. Currently for
  % each action modifying the polygons, the whole old variable sel is
  % stored in a cell of undoData. It's generally contains more
  % information than stricly needed to undo as only one layer is modified
  % at a time. If improved efficiency is needed, it could be taken into
  % account to save minimally needed information. The number of undo
  % levels is currently unlimited, this could be also changed if needed
  
  global undoData redoData sel gui
  
  if ~isempty(undoData)
    delSel;
    redoData{end+1} = sel;
    set(gui.redoMenuId, 'Enable', 'on');
    sel = undoData{end};
    undoData = undoData(1:end-1);
    if isempty(undoData)
      set(gui.undoMenuId, 'Enable', 'off');
    end
    drawAllSel;
    set(gui.layListId, 'Value', sel.curLay);
    updateLayerList;
  end
end

function redo(objId, eventData)
  % PI: see the PI for undo function
  
  global undoData redoData sel gui
  
  if ~isempty(redoData)
    delSel;
    undoData{end+1} = sel;
    set(gui.undoMenuId, 'Enable', 'on');
    sel = redoData{end};
    redoData = redoData(1:end-1);
    if isempty(redoData)
      set(gui.redoMenuId, 'Enable', 'off');
    end
    drawAllSel;
    set(gui.layListId, 'Value', sel.curLay);
    updateLayerList;
  end
end

function setButtonAppearance(buttonId, string, iconName)

  global gui

  % Octave doesn't yet support the use of CData to display icons on buttons
  % (see http://savannah.gnu.org/bugs/?44332) so we only display a string in
  % Octave and we display an icon in MATLAB
  if isoctave()
    set(buttonId, 'String', string);
  else
    icon = imread([gui.iconpath, iconName]);
    set(buttonId, 'CData', icon);
  end
end
  
  
% ______________________ VISUALIZATION FUNCTIONS __________________________

function updateVisu(resetVisu, changeCLim)

  global gui visu link
 
  if nargin == 0
    resetVisu = false;
    changeCLim = false;
  end
  
  delete(gui.symbImageId);
  gui.symbImageId = [];
  
  % keep the properties of the axes if we're not resetting the
  % visualization
  axesProp = struct;
  if ~resetVisu
    for ind = 1:length(visu)
      if ~isempty(visu(ind).axesId)
        axesProp(ind).xLim = get(visu(ind).axesId, 'XLim');
        axesProp(ind).yLim = get(visu(ind).axesId, 'YLim');
        axesProp(ind).cLim = get(visu(ind).axesId, 'CLim');
      end
    end
  end
  
  % update the visualization of the signal
  linkXId = [];
  linkYId = [];
  linkCLimId=[];
  for ind = 1:length(visu)
    delete(visu(ind).uipanelId);
    visu(ind).uipanelId = [];
    visu(ind).axesId = [];
    if visu(ind).visible
      visu(ind).uipanelId = uipanel(gui.mainFigId, ...
        'Units', 'normalized', ...
        'Position', visu(ind).position);

      if visu(ind).linkX
        linkXId = [linkXId ind];
      end
      if visu(ind).linkY
        linkYId = [linkYId ind];
      end
      if visu(ind).linkCLim
        linkCLimId = [linkCLimId ind];
      end
    end
  end
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  updateOneVisu(oriInd);
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  if isoctave()
    % Octave and MATLAB have slighlty diffrent call signatures for addlistener
    addlistener(visu(oriInd).axesId, 'XLim', @updateExploreRect);
    addlistener(visu(oriInd).axesId, 'YLim', @updateExploreRect);
  else
    addlistener(visu(oriInd).axesId, 'XLim', 'PostSet', @updateExploreRect);
    addlistener(visu(oriInd).axesId, 'YLim', 'PostSet', @updateExploreRect);
  end
  
  if length(linkCLimId) > 1
    link.CLim = get(visu(oriInd).axesId,'CLim');
  end
  range = 1:length(visu);
  range(oriInd) = [];
  for ind = range
     updateOneVisu(ind);
  end
   
  if length(linkXId) > 1
    link.XLim = linkprop([visu(linkXId).axesId],'XLim');
  end
  if length(linkYId) > 1
    link.YLim = linkprop([visu(linkXId).axesId],'YLim');
  end
  
  
  if ~resetVisu
    % we go in reverse order to update axes limits so that
    % all the linked axes come back to the limits of the original
    % spectrogram, even if some new linked axes are added
    for ind = length(visu):-1:1
      if visu(ind).visible
        try
          set(visu(ind).axesId, 'XLim', axesProp(ind).xLim);
        end
        try
          set(visu(ind).axesId, 'YLim', axesProp(ind).yLim);
        end
        if ~changeCLim
          try
            set(visu(ind).axesId, 'CLim', axesProp(ind).cLim);
          end
        end
      end
    end
  end
  
  drawAllSel;
  
  overviewInd = find(strcmp('originalOverview', {visu.label}), 1);
  if ~isempty(overviewInd)
    if visu(overviewInd).visible
      exploreOverview(visu(overviewInd).axesId);
    end
  end

  if ~resetVisu
    % restore the current tool
    changeTool([], [], gui.curTool);
  end
  
  drawResizeVisuButton;
  resize;    
end

function updateOneVisu(ind)
  global visu sig player gui

  if visu(ind).visible
    visu(ind).axesId = visu(ind).updateFcn(visu(ind));

    % plot for the visualisation of play position during playback
    lim = axis(visu(ind).axesId);
    xPos = (player.position-1) / sig.sampFreq;
    hold(visu(ind).axesId, 'on');
    player.positionPlotId(ind) = plot(visu(ind).axesId, [xPos xPos],...
     lim(3:4), 'w'); % PI: give the possibility to change the color
    set(player.positionPlotId(ind), 'Visible', 'off', 'LineWidth', 2);
    hold(visu(ind).axesId, 'off');

    % the following insures the possibility to zoom out
    % completely even if we are already zoomed in
    axes(visu(ind).axesId);
    zoom(gui.mainFigId, 'reset');
  end
end
 
function [axesId,C,timeStep] = updateVisuCommon(coef,lim,visuData)

  global link

  axesId = axes('Parent',visuData.uipanelId);
  if(lim)
     C = plotCoeff(coef,axesId,'clim',link.CLim);
  else
     C = plotCoeff(coef,axesId,'dynrange',visuData.dynamic);
  end   
  colorbar off;
  
  xLim = get(axesId,'XLim');
  timeStep = (xLim(2)-xLim(1)) / (size(C,2));
  
end

function [axesId] = updateVisuOriSpec(visuData)

  global coeff visucommon

  [axesId, coeff.oriC, timeStep] = updateVisuCommon(coeff.ori,false,visuData);
  title('Original signal');
  visucommon.timeStep = timeStep;
end

function [axesId] = updateVisuModSpec(visuData)

  global coeff

  [axesId, coeff.modC] = updateVisuCommon(coeff.mod,visuData.linkCLim,visuData); 
  title('Modified signal');
end

function [axesId] = updateVisuOriOverview(visuData)

  global coeff

  axesId = updateVisuCommon(coeff.ori,visuData.linkCLim,visuData);
  title('Overview of original signal');
end

function drawResizeVisuButton()

  global gui visu

  for ind = 1:length(gui.ResizeVisuButtonId)
    if ishandle(gui.ResizeVisuButtonId(ind))
      delete(gui.ResizeVisuButtonId(ind));
    end
  end
  
  nbVisibleVisu = length(find([visu.visible]));
  nbButton = 0;
  gui.ResizeVisuButtonId = NaN(length(visu), 1);
  if nbVisibleVisu > 1
    for ind = 1:length(visu)
      if visu(ind).visible
        gui.ResizeVisuButtonId(ind) = uicontrol(...
          'Parent', gui.mainFigId,...
          'Style', 'pushbutton',...
          'TooltipString', 'Resize visualization',...
          'BackgroundColor', gui.buttonBackgroundColor,...
          'Enable', 'off',...
          'buttonDownFcn', {@resizeVisu, ind});
        
        setButtonAppearance(gui.ResizeVisuButtonId(ind), 'x',...
                            'resizebutton.png');
          
        nbButton = nbButton + 1;
        if nbButton == nbVisibleVisu-1
          break;
        end
      end
    end
  end
end

function resizeVisu(objId, eventData, indVisu)

  global gui visu
  
  % In Octave, the button up event is generally lost, so we put the same
  % action on the right button down
  if strcmp(get(gui.mainFigId,'SelectionType'),'alt')
    if isequal(get(gui.mainFigId, 'WindowButtonMotionFcn'),...
               @resizeVisuButtonMotionFcn)
      resizeVisuButtonUpFcn(objId, eventData);
    end
    return;
  end
  
  initPos = get(gui.mainFigId, 'CurrentPoint');
  
  buttonMotionFcn = get(gui.mainFigId, 'WindowButtonMotionFcn');
  if (~isequal(buttonMotionFcn, @resizeVisuButtonMotionFcn))
    backupButtonMotionFcn = buttonMotionFcn;
  else 
    backupButtonMotionFcn = gui.resizeVisu.backupButtonMotionFcn;  
  end
  
  buttonUpFcn = get(gui.mainFigId, 'WindowButtonUpFcn');
  if (~isequal(buttonUpFcn, @resizeVisuButtonUpFcn))
    backupButtonUpFcn = buttonUpFcn;
  else 
    backupButtonUpFcn = gui.resizeVisu.backupButtonUpFcn; 
  end
  
  figPos = get(gui.mainFigId, 'Position');
  
  buttonPos = get(gui.ResizeVisuButtonId(indVisu), 'Position');
  
  % find the index of the visible visualization next to the one with 
  % index indVisu
  indNextVisu = 0;
  for ind = indVisu+1:length(visu)
    if visu(ind).visible
      indNextVisu = ind;
      break;
    end
  end
  
  initHeight = [visu(indVisu).height, visu(indNextVisu).height];
  
  % As the zoom and pan mode of matlab doesn't allow to change the
  % WindowButtonUpFcn, I temporarly change the tool
  curTool = gui.curTool;
  changeTool(objId, eventData, 'freehand');
  
  set(gui.mainFigId,...
    'WindowButtonUpFcn', @resizeVisuButtonUpFcn);
  set(gui.mainFigId,...
    'WindowButtonMotionFcn', @resizeVisuButtonMotionFcn);
  
  gui.resizeVisu.initPos = initPos;
  gui.resizeVisu.backupButtonMotionFcn = backupButtonMotionFcn;
  gui.resizeVisu.backupButtonUpFcn = backupButtonUpFcn;
  gui.resizeVisu.figPos = figPos;
  gui.resizeVisu.buttonPos = buttonPos;
  gui.resizeVisu.indNextVisu = indNextVisu;
  gui.resizeVisu.initHeight = initHeight;
  gui.resizeVisu.curTool = curTool;
  gui.resizeVisu.indVisu = indVisu; 
  
end
  
function resizeVisuButtonMotionFcn(objId, eventData)
  global gui visu
  
  initPos = gui.resizeVisu.initPos;
  backupButtonMotionFcn = gui.resizeVisu.backupButtonMotionFcn;
  backupButtonUpFcn = gui.resizeVisu.backupButtonUpFcn;
  figPos = gui.resizeVisu.figPos;
  buttonPos = gui.resizeVisu.buttonPos;
  indNextVisu = gui.resizeVisu.indNextVisu;
  initHeight = gui.resizeVisu.initHeight;
  curTool = gui.resizeVisu.curTool;
  indVisu = gui.resizeVisu.indVisu; 
  
  pos = get(gui.mainFigId, 'CurrentPoint');
  shift = (pos(2) - initPos(2)) / figPos(4);
  if (initHeight(1) - shift) > 0.01 && (initHeight(2) + shift) > 0.01
    visu(indVisu).height = initHeight(1) - shift;
    visu(indNextVisu).height = initHeight(2) + shift;
  end
  resize;
end
    
function resizeVisuButtonUpFcn(objId, eventData)

  global gui
  
  initPos = gui.resizeVisu.initPos;
  backupButtonMotionFcn = gui.resizeVisu.backupButtonMotionFcn;
  backupButtonUpFcn = gui.resizeVisu.backupButtonUpFcn;
  figPos = gui.resizeVisu.figPos;
  buttonPos = gui.resizeVisu.buttonPos;
  indNextVisu = gui.resizeVisu.indNextVisu;
  initHeight = gui.resizeVisu.initHeight;
  curTool = gui.resizeVisu.curTool;
  indVisu = gui.resizeVisu.indVisu; 
  
  set(gui.mainFigId, 'WindowButtonMotionFcn', backupButtonMotionFcn);
  set(gui.mainFigId, 'WindowButtonUpFcn', backupButtonUpFcn);
  % restore the tool
  changeTool(objId, eventData, curTool);
end

function toggleModVisu(objId, eventData)

  global visu gui

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  modInd = find(strcmp('modified', {visu.label}), 1);
  overInd = find(strcmp('originalOverview', {visu.label}), 1);
  
  if visu(modInd).visible
    visu(modInd).visible = false;
    set(gui.visuMenu.showModId, 'Checked', 'off');
    visu(oriInd).height = visu(oriInd).height + visu(modInd).height;
  else
    flagBuggyCase = false;
    if visu(overInd).visible
      % In Octave we get a crash if we show the modified visu while the
      % overview is already shown, so we need to temporary hide the overview 
      flagBuggyCase = true;
    end
          
    if flagBuggyCase
      oriHeight = visu(oriInd).height;
      overHeight = visu(overInd).height;
      toggleOverviewVisu;
    end
    
    visu(modInd).visible = true;
    set(gui.visuMenu.showModId, 'Checked', 'on');
    visu(modInd).height = visu(oriInd).height / 2;
    visu(oriInd).height = visu(oriInd).height / 2;
    
    if flagBuggyCase
      toggleOverviewVisu;
      visu(modInd).height = oriHeight / 2;
      visu(oriInd).height = oriHeight / 2;
      visu(overInd).height = overHeight;
    end
  end
  updateVisu;
  resize;
end

  function toggleOverviewVisu(objId, eventData)
  
    global visu gui
  
    oriInd = find(strcmp('originalMain', {visu.label}), 1);
    overviewInd = find(strcmp('originalOverview', {visu.label}), 1);
    
    if visu(overviewInd).visible
      visu(overviewInd).visible = false;
      set(gui.visuMenu.showOverviewId, 'Checked', 'off');
      visu(oriInd).height = visu(oriInd).height + visu(overviewInd).height;
    else
      visu(overviewInd).visible = true;
      set(gui.visuMenu.showOverviewId, 'Checked', 'on');
      visu(overviewInd).height = visu(oriInd).height * 1/3;
      visu(oriInd).height = visu(oriInd).height * 2/3;
    end
    updateVisu;
    resize;
  end


% ______________________ AUDIOPLAYER FUNCTIONS ____________________________

function initializePlayer()
  
  global player sig
  
  player.ori = audioplayer(sig.ori, sig.sampFreq);
  player.mod = audioplayer(sig.mod, sig.sampFreq);
  player.selected = 'ori';
  player.loop = false;
  player.position = 1;
  player.forceStop = false; 
end


function drawAudioplayer(uipanelId)

  global gui

  subPanelHeight = 2*gui.textHeight + 2*gui.marginSub(2) +...
    gui.vertDist+gui.fontSize+2;
  
  % Octave does not provide uibuttongroup so we need to handle the radiobuttons
  % by hand
  gui.player.buttongroupId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Signal',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, 1, gui.panelWidth-4, subPanelHeight]);
    
  gui.player.buttonOriId = uicontrol(...
    'Parent', gui.player.buttongroupId,...
    'Style', 'radiobutton',...
    'FontSize', gui.fontSize, ...
    'String', ' Original',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Tag', 'ori',...
    'Value', true,...
    'CallBack', @switchAudioplayer,...
    'Position', [gui.marginSub(1),...
      gui.textHeight+gui.marginSub(2)+gui.vertDist,...
      gui.panelWidth-4-2*gui.marginSub(2),...
      gui.textHeight]);
  
  gui.player.buttonModId = uicontrol(...
    'Parent', gui.player.buttongroupId,...
    'Style', 'radiobutton',...
    'FontSize', gui.fontSize, ...
    'String', ' Modified',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Tag', 'mod',...
    'Value', false,...
    'CallBack', @switchAudioplayer,...
    'Position', [gui.marginSub,...
      gui.panelWidth-4-2*gui.marginSub(2),...
      gui.textHeight]);
  
  yPos = subPanelHeight;
  
  buttonPlayPauseId = uicontrol(...
    'Parent', uipanelId,...
    'Style', 'pushbutton',...
    'TooltipString', 'Play/pause audio',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(1, 1, gui.margin + [0, yPos]),...
    'CallBack',@playPauseAudioplayer);
    
  setButtonAppearance(buttonPlayPauseId, 'play', 'play.png');
  
  buttonStopId = uicontrol(...
    'Parent', uipanelId,...
    'Style', 'pushbutton',...
    'TooltipString', 'Stop audio',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1, gui.margin + [0, yPos]),...
    'CallBack',@stopAudioplayer);
    
  setButtonAppearance(buttonStopId, 'stop', 'stop.png');
  
  buttonLoopId = uicontrol(...
    'Parent', uipanelId, ...
    'Style', 'togglebutton',...
    'TooltipString', 'Loop audio',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(3, 1, gui.margin + [0, yPos]),...
    'CallBack',@loopAudioplayer,...
    'Min', false,...
    'Max', true,...
    'Value', false);
    
  setButtonAppearance(buttonLoopId, 'loop', 'loop.png');
end

function switchAudioplayer(objId, eventData)

  global player gui

  stopAudioplayer;

  if player.selected == 'ori'
    set(gui.player.buttonOriId, 'Value', false);
    set(gui.player.buttonModId, 'Value', true);
    player.selected = 'mod';
  else
    set(gui.player.buttonOriId, 'Value', true);
    set(gui.player.buttonModId, 'Value', false);
    player.selected = 'ori';
  end
end

function playPauseAudioplayer(objId, eventData)

  global player visu sig

  switch player.selected
      case 'ori'
        currPlayer = player.ori;
      case 'mod'
        currPlayer = player.mod;
    end

  if isplaying(currPlayer)
    player.forceStop = true;
    pause(currPlayer);
    player.position = get(currPlayer, 'CurrentSample');
  else
        
    for ind = 1:length(player.positionPlotId)
      if visu(ind).visible
        set(player.positionPlotId(ind), 'Visible', 'on');
      end
    end
    
    player.forceStop = false;
    
    if get(currPlayer, 'CurrentSample') == 0 ||...
        get(currPlayer, 'CurrentSample') == get(currPlayer, 'TotalSamples')
      play(currPlayer);
    else
      resume(currPlayer);
    end

    while isplaying(currPlayer)
   
      pos = (get(currPlayer, 'CurrentSample')-1) / sig.sampFreq;
      
      for ind = 1:length(player.positionPlotId)
        if visu(ind).visible
          set(player.positionPlotId(ind), 'XData', [pos pos]);
        end
      end 

      pause(1/15);
    end
    
    if player.loop && ~player.forceStop
      pause(0.1); % Needed to avoid java error
      playPauseAudioplayer();
    end
    
    for ind = 1:length(player.positionPlotId)
      if visu(ind).visible
        set(player.positionPlotId(ind), 'Visible', 'off');
      end
    end
    
  end
end


function stopAudioplayer(objId, eventData)

  global player

  player.forceStop = true;
  stop(player.ori);
  stop(player.mod);
  player.position = 1;
end


function playerStopFcnOri(objId, eventData)

  global player visu

  for ind = 1:length(player.positionPlotId)
    if visu(ind).visible
      set(player.positionPlotId(ind), 'Visible', 'off');
    end
  end   
  
  player.position = get(player.ori, 'CurrentSample');
  
  if player.loop && ~player.forceStop
    pause(0.1); % needed to avoid java error
    play(player.ori, player.position);
  end
end

function playerStopFcnMod(objId, eventData)

  global player visu

  for ind = 1:length(player.positionPlotId)
    if visu(ind).visible
      set(player.positionPlotId(ind), 'Visible', 'off');
    end
  end
  
  player.position = get(player.mod, 'CurrentSample');
  
  if player.loop && ~player.forceStop
    pause(0.1); % needed to avoid java error
    play(player.mod, player.position);
  end
end

function loopAudioplayer(objId, eventData)

  global player

  player.loop = get(objId, 'Value');
end

function updatePlayPosition(objId, eventData)

  global player sig visu
  
  pos = (get(objId, 'CurrentSample')-1) / sig.sampFreq;
  
  for ind = 1:length(player.positionPlotId)
    if visu(ind).visible
      set(player.positionPlotId(ind), 'XData', [pos pos]);
    end
  end
end

function updatePlayerMod()

  global player sig

  player.mod = audioplayer(sig.mod, sig.sampFreq);
end


% ____________________ SELECTION TOOLS FUNCTIONS __________________________

function drawSelectionTool(uipanelId)

  global gui

  subSize = subpanelSize(1);
  
  processingToolPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Apply',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, 1, subSize]);
  
  % draw processing tools subpanel
  drawProcessingTool(processingToolPanelId);
  
  heightLayerPanel = 115;
  
  layerPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Layers',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, 1+subSize(2), subSize(1), heightLayerPanel]);
  
  % draw layer panel
  drawLayerPanel(layerPanelId);
  
  % Octave does not provide uibuttongroup so we need to handle the
  % tooglebuttons by hand
  buttongroupId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Mode',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, subSize(2)+heightLayerPanel+1, subSize]);
     
  gui.buttonUnionId = uicontrol(...
    'Parent',buttongroupId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Union',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Tag', 'union',...
    'Position', buttonPos(1, 1, gui.marginSub),...
    'Value', true,...
    'Callback', @switchSelMode);
    
  setButtonAppearance(gui.buttonUnionId, 'u', 'union.png');
  
  gui.buttonInterId = uicontrol(...
    'Parent',buttongroupId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Intersection',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Tag', 'intersection',...
    'Position', buttonPos(2, 1, gui.marginSub),...
    'Value', false,...
    'Callback', @switchSelMode);
    
  setButtonAppearance(gui.buttonInterId, 'n', 'intersection.png');
  
  gui.buttonDiffId = uicontrol(...
    'Parent',buttongroupId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Set difference',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Tag', 'difference',...
    'Position', buttonPos(3, 1, gui.marginSub), ...
    'Value', false,...
    'Callback', @switchSelMode);
    
  setButtonAppearance(gui.buttonDiffId, '-', 'difference.png');
  
  % reserved space to draw parameters of the tools
  gui.toolPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Tools',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize,...
    'Units', 'pixels',...
    'Position', [1, 2*subSize(2)+heightLayerPanel+1, subSize(1),...
      2*gui.marginSub(2) + gui.vertDist + gui.textHeight + ...
        gui.buttonHeight + gui.fontSize+2]);
  
  buttonFreehandId = uicontrol(...
    'Parent', gui.toolPanelId,...
    'HandleVisibility', 'off',...
    'Style', 'togglebutton',...
    'TooltipString', 'Free-hand selection',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(1, 1,...
      gui.marginSub+[0,...
      gui.marginSub(2)+gui.textHeight]),...
    'CallBack', {@changeTool, 'freehand'});
    
  setButtonAppearance(buttonFreehandId, 'free', 'freehand.png');
  
  gui.tool(end+1).buttonId = buttonFreehandId;
  gui.tool(end).name = 'freehand';
  gui.tool(end).function = @selecFreehand;
  
  buttonLevelId = uicontrol(...
    'Parent', gui.toolPanelId,...
    'HandleVisibility', 'off',...
    'Style', 'togglebutton',...
    'TooltipString', 'Magic wand (Level selection)',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1,...
      gui.marginSub+[0,...
      gui.marginSub(2)+gui.textHeight]),...
    'CallBack', {@changeTool, 'level'});
  
  setButtonAppearance(buttonLevelId, 'wand', 'magicwand.png');
  
  gui.tool(end+1).buttonId = buttonLevelId;
  gui.tool(end).name = 'level';
  gui.tool(end).function = @selecLevel;
  gui.tool(end).param.name = 'Tolerance';
  gui.tool(end).param.val = '10'; % value of the tolerance in dB
     
  buttonSubbandId = uicontrol(...
    'Parent', gui.toolPanelId,...
    'HandleVisibility', 'off',...
    'Style', 'togglebutton',...
    'TooltipString', 'Subband selection',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(3, 1,...
      gui.marginSub+[0,...
      gui.marginSub(2)+gui.textHeight]),...
    'CallBack', {@changeTool, 'subband'});
  
  setButtonAppearance(buttonSubbandId, 'sub', 'subbandsel.png');
  
  gui.tool(end+1).buttonId = buttonSubbandId;
  gui.tool(end).name = 'subband';
  gui.tool(end).function = @selecSubband;
end

function changeTool(objId, eventData, toolName)

  global gui

  zoom(gui.mainFigId, 'off');
  pan(gui.mainFigId, 'off');    
  for ind = 1:length(gui.tool)
    set(gui.tool(ind).buttonId, 'Value', false);
  end
  
  toolNameList = {gui.tool.name}; % cell array listing the tool names
  curInd = find(strcmp(toolName, toolNameList));
  set(gui.tool(curInd).buttonId, 'Value', true);
  gui.curTool = gui.tool(curInd).name;    
  gui.tool(curInd).function(curInd);
  drawToolParam(curInd);
end

function drawToolParam(toolInd)

  global gui
  
  child = findobj(gui.toolPanelId);
  child = setdiff(child, gui.toolPanelId);
  delete(child);    
  
  for ind = 1:length(gui.tool(toolInd).param)
    uicontrol(...
      'Parent', gui.toolPanelId,...
      'FontSize', gui.fontSize, ...
      'Style', 'text',...
      'String', gui.tool(toolInd).param(ind).name,...
      'Position', [gui.marginSub(1),...
        gui.marginSub(2)+(ind-1)*(gui.textHeight+gui.vertDist),...
        gui.textWidth, gui.textHeight]) ;
    uicontrol(...
      'Parent', gui.toolPanelId,...
      'FontSize', gui.fontSize, ...
      'Style', 'edit',...
      'BackgroundColor', gui.buttonBackgroundColor,...
      'String', gui.tool(toolInd).param(ind).val,...
      'Position', [gui.marginSub(1)+gui.textWidth,...
        gui.marginSub(2)+(ind-1)*(gui.textHeight+gui.vertDist),...
        gui.editWidth, gui.textHeight],...
      'Callback', {@changeToolParam, toolInd, ind});
  end
end

function changeToolParam(objId, eventData, toolInd, paramInd)

  global gui
  
  gui.tool(toolInd).param(paramInd).val = get(objId, 'String');
end

function selecFreehand(toolInd)
  % NOTE: toolInd is not used but needed as a parameter is always passed to
  % this function 

  global gui visu

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axesId = visu(oriInd).axesId;
  imageId = findobj(axesId, 'Type', 'Image');
  
  set(imageId, 'ButtonDownFcn', @selecFreeHandButtonDown);
  
  gui.selecFreehand.poly.x = [];
  gui.selecFreehand.poly.y = [];

  gui.selecFreehand.init = true; % boolean to know if we are drawing the first
                                 % point or not
  
  gui.selecFreehand.lineId = [];
end
    
function selecFreeHandButtonDown(objId, eventData)
  global gui visu sel

  poly = gui.selecFreehand.poly;
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axesId = visu(oriInd).axesId;
  
  switch get(gui.mainFigId,'SelectionType')
    case 'normal'
      if gui.selecFreehand.init
        point = get(axesId,'CurrentPoint');
        poly.x = [point(1,1)];
        poly.y = [point(1,2)];
        poly.hole = false;
        gui.selecFreehand.lineId = line(...
          'XData', poly.x,...
          'YData', poly.y,...
          'LineWidth', sel.lay(sel.curLay).lineWidth,...
          'Color', sel.lay(sel.curLay).color,...
          'Marker', sel.lay(sel.curLay).marker,...
          'MarkerSize', sel.lay(sel.curLay).markerSize,...
          'LineStyle', sel.lay(sel.curLay).lineStyle);
        gui.selecFreehand.init = false;
        set(gui.mainFigId,'WindowButtonDownFcn',@selecFreeHandButtonDown);
        set(gui.mainFigId,'WindowButtonMotionFcn',@selecFreeHandMotion);
      else
        point = get(axesId,'CurrentPoint');
        poly.x = [poly.x; point(1,1)];
        poly.y = [poly.y; point(1,2)];
        set(gui.selecFreehand.lineId, 'XData',poly.x , 'YData', poly.y);
        drawnow;
      end
    case 'alt'
      if ~gui.selecFreehand.init
        set(gui.mainFigId,'WindowButtonMotionFcn','');
        set(gui.mainFigId,'WindowButtonDownFcn','');
        set(gui.mainFigId,'WindowButtonUpFcn','');
        gui.selecFreehand.init = true;
        
        delete(gui.selecFreehand.lineId);

        combineSel(poly);
        drawCurSel;
      end
  end
  
  gui.selecFreehand.poly = poly;
end

function selecFreeHandMotion(objId, eventData)
  global gui visu
  
  poly = gui.selecFreehand.poly;
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axesId = visu(oriInd).axesId;
  
  point = get(axesId,'CurrentPoint');
  xData = [poly.x; point(1,1)];
  yData = [poly.y; point(1,2)];
  set(gui.selecFreehand.lineId, 'XData', xData, 'YData', yData);
  drawnow;
end

function selecLevel(toolInd)

  global visu

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  imageId = findobj(visu(oriInd).axesId, 'Type', 'Image');
  
  set(imageId, 'ButtonDownFcn', {@selecLevelButtonDown, toolInd});
end
  
function selecLevelButtonDown(objId, eventData, toolInd)

  global visu coeff gui

  oriInd = find(strcmp('originalMain', {visu.label}), 1);    
  point = get(visu(oriInd).axesId,'CurrentPoint');

  indTime = round(convAxesToIndX(point(1,1)));
  indFreq = round(convAxesToIndY(point(1,2)));

  refLevel = coeff.oriC(indFreq, indTime);

  levelTol = str2num(gui.tool(toolInd).param.val);
  if isempty(levelTol) || levelTol < 0
    errordlg(['Tolerance parameter for magic wand is invalid, '...
      'it must be a positive number in dB']);
    return;
  end
  
  lowLevel = refLevel - levelTol;
  highLevel = refLevel + levelTol;

  mask = (coeff.oriC >= lowLevel) & (coeff.oriC <= highLevel);
  mask = selectRoi(mask, indTime, indFreq);

  poly = convMaskToPoly(mask);

  combineSel(poly);
  drawCurSel;
end

function maskRoi = selectRoi(mask, indTime, indFreq)
  global gui
  
  if gui.hasImagePackage
    maskRoi = bwselect(mask, indTime, indFreq, 4);
  else
    % PI: The following implementation of the flood fill algorithm is not very
    % efficient and could be improved (for example using a scan-line approach)
    maskRoi = zeros(size(mask));
    queueRow = indFreq;
    queueCol = indTime;
    while ~isempty(queueRow)
      row = queueRow(1);
      col = queueCol(1);
      queueRow = queueRow(2:end);
      queueCol = queueCol(2:end);
      if ~maskRoi(row, col) && mask(row, col)
        maskRoi(row, col) = 1;
        if col+1 < size(mask, 1)
          queueRow(end+1) = row;
          queueCol(end+1) = col+1;
        end
        if col-1 >= 1
          queueRow(end+1) = row;
          queueCol(end+1) = col-1;
        end
        if row+1 < size(mask, 2)
          queueRow(end+1) = row+1;
          queueCol(end+1) = col;
        end
        if row-1 >= 1
          queueRow(end+1) = row-1;
          queueCol(end+1) = col;
        end
      end
    end  
  end
end

function selecSubband(toolInd)
  global visu gui

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axesId = visu(oriInd).axesId;
  imageId = findobj(axesId, 'Type', 'Image');
  
  set(imageId, 'ButtonDownFcn', @selecSubbandButtonDown);
  
  gui.selecSubband.selection = [];
  
  set(gui.mainFigId,'WindowButtonMotionFcn','');
  set(gui.mainFigId,'WindowButtonUpFcn','');
end

function selecSubbandButtonDown(objId, eventData)
  global visu gui coeff sel
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  
  point = get(visu(oriInd).axesId,'CurrentPoint');
  gui.selecSubband.selection = [point(1, 2), point(1, 2)];
        
  hold(visu(oriInd).axesId, 'on');
  
  xVal = convIndToAxesX([0.5, size(coeff.oriC, 2)+0.5]);
  yVal = gui.selecSubband.selection;
  
  gui.selecSubband.lineId(1) = line(...
    'XData', [xVal(1), xVal(2), xVal(2), xVal(1), xVal(1)],...
    'YData', [yVal(1), yVal(1), yVal(2), yVal(2), yVal(1)],...
    'LineWidth', sel.lay(sel.curLay).lineWidth,...
    'Color', sel.lay(sel.curLay).color,...
    'Marker', sel.lay(sel.curLay).marker,...
    'MarkerSize', sel.lay(sel.curLay).markerSize,...
    'LineStyle', sel.lay(sel.curLay).lineStyle);
  hold(visu(oriInd).axesId, 'off');
  
  set(gui.mainFigId, 'WindowButtonMotionFcn', @selecSubbandMotionDown);
  set(gui.mainFigId,'WindowButtonUpFcn', @selecSubbandButtonUp);
end
    
function selecSubbandMotionDown(objId, eventData)
  global visu gui
  
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  
  point = get(visu(oriInd).axesId,'CurrentPoint');
  gui.selecSubband.selection(2) = point(1,2);
  
  set(gui.selecSubband.lineId, 'YData',...
    [gui.selecSubband.selection(1), gui.selecSubband.selection(1),...
     gui.selecSubband.selection(2), gui.selecSubband.selection(2),...
     gui.selecSubband.selection(1)]);
end
    
function selecSubbandButtonUp(objId, eventData)
  global gui
  
  set(gui.mainFigId,'WindowButtonMotionFcn','');
  set(gui.mainFigId,'WindowButtonUpFcn','');
  
  poly.x = get(gui.selecSubband.lineId, 'XData').';
  poly.y = get(gui.selecSubband.lineId, 'YData').';
  poly.hole = false;
  
  delete(gui.selecSubband.lineId);
  
  combineSel(poly);
  drawCurSel;
end

function [poly] = convMaskToPoly(mask)
  poly = mask2poly(mask);
  for ind = 1:length(poly)
    poly(ind).x = convIndToAxesX(poly(ind).x);
    poly(ind).y = convIndToAxesY(poly(ind).y);
  end
end

function [poly] = mask2poly(mask)
  % Convert region mask to region of interest (ROI) polygon

  % PI: The polygon outputed by this function should be simplified (the portions
  % having multiple points aligned should be replaced by a single line segment)
  
  maskRef = mask;
  mask = zeros(size(mask, 1)+2, size(mask, 2)+2);
  mask(2:end-1,2:end-1) = maskRef;

  [ind1, ind2] = find(mask);

  % position of segments on the edge for first dimension
  seg1 = sparse(size(mask,1)+1, size(mask,2)); 
  % position of segments on the edge for second dimension
  seg2 = sparse(size(mask,1), size(mask,2)+1) ;

  for n = 1:length(ind1)
    if ~mask(ind1(n)-1, ind2(n))
      seg1(ind1(n), ind2(n)) = 1;
    end
    if ~mask(ind1(n)+1, ind2(n))
      seg1(ind1(n)+1, ind2(n)) = 1;
    end
    if ~mask(ind1(n), ind2(n)-1)
      seg2(ind1(n), ind2(n)) = 1;
    end
    if ~mask(ind1(n), ind2(n)+1)
      seg2(ind1(n), ind2(n)+1) = 1;
    end
  end

  ind = 0;
  
  while nnz(seg1)>1
    curve = chainSeg;
    if ~isempty(curve)
      ind = ind+1;
      poly(ind).x = curve(:,2);
      poly(ind).y = curve(:,1);
      for xVal = poly(ind).x(1) + [0.5, -0.5]
        for yVal = poly(ind).y(1) + [0.5, -0.5]
          if inpolygon (xVal, yVal, poly(ind).x, poly(ind).y)
            poly(ind).hole = ~logical(maskRef(yVal, xVal));
            break
          end
        end
      end
    end
  end
  
  function curve = chainSeg()
  % chaining of segments

    % choose one start segment
    [ind1, ind2] = find(seg1);
    ind1 = ind1(1);
    ind2 = ind2(1);
    curve = [ind1, ind2; ind1, ind2+1];

    seg1(ind1, ind2) = 0;

    % pos is a variable to remember in which direction we are
    % progressing
    % [1 1] if we're going in a growing index number
    % [-1 0] otherwise
    pos = [1, 1];

    % precise if the last added segment comes from seg1 or seg2
    last1 = true; 

    while true
      if last1
        % the last segment was from dimension 1
        if seg1(ind1, ind2+pos(1))
          ind2 = ind2+pos(1);
          seg1(ind1, ind2) = 0;
          curve = [curve; ind1, ind2+pos(2)];
        elseif seg2(ind1-1, ind2+pos(2))
          ind1 = ind1-1;
          ind2 = ind2+pos(2);
          pos = [-1, 0];
          seg2(ind1, ind2) = 0;
          curve = [curve; ind1, ind2];
          last1 = false;
        elseif seg2(ind1, ind2+pos(2))
          ind2 = ind2+pos(2);
          pos = [1, 1];
          seg2(ind1, ind2) = 0;
          curve = [curve; ind1+1, ind2];
          last1 = false;
        else
          break;
        end
      else
        % the last segment was from dimension 2
        if seg2(ind1+pos(1), ind2)
          ind1 = ind1+pos(1);
          seg2(ind1, ind2) = 0;
          curve = [curve; ind1+pos(2), ind2];
        elseif seg1(ind1+pos(2), ind2-1)
          ind1 = ind1+pos(2);
          ind2 = ind2-1;
          pos = [-1, 0];
          seg1(ind1, ind2) = 0;
          curve = [curve; ind1, ind2];
          last1 = true;
        elseif seg1(ind1+pos(2), ind2)
          ind1 = ind1+pos(2);
          pos = [1, 1];
          seg1(ind1, ind2) = 0;
          curve = [curve; ind1, ind2+1];
          last1 = true;
        else
          break;
        end
      end
    end
    
    curve = curve-1.5;
    
    if size(curve, 1)==2
      % the first segment couldn't be linked to some other segment,
      % there is no polygon
      curve = [];
    end
    
  end
  
end

function [outPol] = clipPoly(refPol, clipPol, typeFlag)
  % typeFlag defines the polygon clipping operation with: 
  % 0 - Substraction
  % 1 - Intersection
  % 2 - Xor
  % 3 - Union
  
  global gui

  switch gui.backendClipPoly
    case 'clipper'
      [refPolCell, refHoles] = convertPolIn(refPol);
      [clipPolCell, clipHoles] = convertPolIn(clipPol);
      operation = convertTypeFlagToOperation(typeFlag);
      [outPolCell, outHoles] = polyboolclipper(refPolCell, clipPolCell,...
        operation, refHoles, clipHoles);
      outPol = convertPolOut(outPolCell, outHoles);
    case 'polygonclip'
      outPol = PolygonClip(refPol, clipPol, typeFlag);
    case 'binmasks'
      refMask = convPolyToMask(refPol);
      clipMask = convPolyToMask(clipPol);
      switch typeFlag
        case 0 % substraction
          mask = refMask - clipMask;
        case 1 % intersection
          mask = and(refMask, clipMask);
        case 2 % xor
          mask = xor(refMask, clipMask);
        case 3 % union
          mask = or(refMask, clipMask);
      end
      outPol = convMaskToPoly(mask);
  end
end

function [operation] = convertTypeFlagToOperation(type)
  operation_match = {'notb', 'and', 'xor', 'or'};
  operation = operation_match{type+1};
end

function [polCell, holes] = convertPolIn(pol)
  polCell = {};
  holes = false(length(pol), 1);
  for ind = 1:length(pol)
    polCell{end+1} = [pol(ind).x, pol(ind).y];
    holes(ind) = logical(pol(ind).hole);
  end
end

function [pol] = convertPolOut(polCell, holes)
  if isempty(polCell)
    pol = [];
  else
    for ind = 1:length(polCell)
      pol(ind).x = polCell{ind}(:, 1);
      pol(ind).y = polCell{ind}(:, 2);
      pol(ind).hole = holes(ind);
    end
  end
end

function combineSel(poly)

  global sel

  if ~isempty(sel.lay(sel.curLay).poly)
    % there is already a selection that should be combined with the new one
    
    switch sel.mode
      case 'union'
        newPoly = clipPoly(sel.lay(sel.curLay).poly, poly, 3);
      case 'intersection'
        newPoly = clipPoly(sel.lay(sel.curLay).poly, poly, 1);
      case 'difference'
        newPoly = clipPoly(sel.lay(sel.curLay).poly, poly, 0);
    end
    
    % we must remove the selection plot before replacing the selection data
    delCurPoly;
    sel.lay(sel.curLay).poly = newPoly;   
  else
    % the selection was empty
    if strcmp(sel.mode, 'union')
      updateUndoData;
      sel.lay(sel.curLay).poly = poly;
    end
  end
end

function delCurPoly()

  global sel

  updateUndoData;
  if ~isempty(sel.lay(sel.curLay).poly)
    for ind = 1:length(sel.lay(sel.curLay).poly)
      delete(sel.lay(sel.curLay).poly(ind).id);
    end
    sel.lay(sel.curLay).poly = [];
  end
end

function delAllPoly()

  global sel

  updateUndoData;
  for indLay = 1:length(sel.lay)
    if ~isempty(sel.lay(indLay).poly)
      for ind = 1:length(sel.lay(indLay).poly)
        delete(sel.lay(indLay).poly(ind).id);
      end
      sel.lay(indLay).poly = [];
    end
  end
end

function delSel()
% delete graphical selection (but keep the polygons)

  global sel

  for indLay = 1:length(sel.lay)
    if ~isempty(sel.lay(indLay).poly)
      for ind = 1:length(sel.lay(indLay).poly)
        delete(sel.lay(indLay).poly(ind).id);
      end
    end
  end
end

function resetSel()
% erase sel and put it to default value

  global sel gui default

  sel = default.sel;
  if isfield(gui, 'layListId')
    if ishandle(gui.layListId)
      set(gui.layListId, 'Value', sel.curLay);
      drawLayParam(sel.curLay);
      updateLayerList;
    end
  end
end

function drawCurSel()

  global visu sel

  handleSymb;
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  hold(visu(oriInd).axesId, 'on');
  if ~isempty(sel.lay(sel.curLay).poly)
    for ind = 1:length(sel.lay(sel.curLay).poly)
      sel.lay(sel.curLay).poly(ind).id = line(...
        'XData', [sel.lay(sel.curLay).poly(ind).x;...
          sel.lay(sel.curLay).poly(ind).x(1)],...
        'YData', [sel.lay(sel.curLay).poly(ind).y;...
          sel.lay(sel.curLay).poly(ind).y(1)],...
        'LineWidth', sel.lay(sel.curLay).lineWidth,...
        'Color', sel.lay(sel.curLay).color,...
        'Marker', sel.lay(sel.curLay).marker,...
        'MarkerSize', sel.lay(sel.curLay).markerSize,...
        'LineStyle', sel.lay(sel.curLay).lineStyle,...
        'Parent', visu(oriInd).axesId);
    end
  end
  hold(visu(oriInd).axesId, 'off');
end

function drawAllSel()

  global visu sel
   
  handleSymb;
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  hold(visu(oriInd).axesId, 'on');
  for indLay = 1:length(sel.lay)
    if ~isempty(sel.lay(indLay).poly)
      for ind = 1:length(sel.lay(indLay).poly)
        sel.lay(indLay).poly(ind).id = line(...
          'XData', [sel.lay(indLay).poly(ind).x;...
            sel.lay(indLay).poly(ind).x(1)],...
          'YData', [sel.lay(indLay).poly(ind).y;...
            sel.lay(indLay).poly(ind).y(1)],...
          'LineWidth', sel.lay(indLay).lineWidth,...
          'Color', sel.lay(indLay).color,...
          'Marker', sel.lay(indLay).marker,...
          'MarkerSize', sel.lay(indLay).markerSize,...
          'LineStyle', sel.lay(indLay).lineStyle,...
          'Parent', visu(oriInd).axesId);
      end
    end
  end
  hold(visu(oriInd).axesId, 'off');
end

function handleSymb()

  global gui

  delete(gui.symbImageId);
  gui.symbImageId = [];
  if gui.showSymb
    showSymb();
  end
end

function showSymb()

  global coeff gui symbol visu

  % plot symbol of the multiplier
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axesId = visu(oriInd).axesId;
  hold(axesId, 'on');
  
  symb = symbol.data(symbol.curSymb).val;
  if(isempty(symb))
     absSymb = abs(convSelToSymb());
  else
     absSymb = abs(symb);
  end

  if(symbol.data(symbol.curSymb).invert)
     absSymb = max([1,max(absSymb(:))])-absSymb;
  end
  
  % Symbol is represented using transparency, with two different
  % colors for absolute values of the symbol in [0,1] and for values > 1. 
  % Transparency is total for absolute value of 1, and opacity
  % grows when going away from 1
  
  % PI: these colors should be set as variables that the user can set
  color1 = [0 0 0]; % black
  color2 = [1 1 1]; % white
  
  % PI: A dB scale could be used for transparency
  
  nbTime = size(coeff.oriC, 2);
  % construct the image with white for value > 1 and black in other parts
 
  tempIm = repmat(reshape(color1, [1 1 3]), [nbPlottedFreq() nbTime 1]);
  [ind1, ind2] = find(absSymb(1:nbPlottedFreq(), :) > 1);
  
  if ~isempty(ind1)
    for ind3 = 1:size(tempIm, 3)
      ind = sub2ind(size(tempIm), ind1, ind2, ind3 + zeros(size(ind1)));
      tempIm(ind) = color2(ind3);
    end
  end

  
  % construct transparency data
  
  % initialisation using formula valid for values in [0, 1]
  tempAlpha = (1 - absSymb(1:nbPlottedFreq(), :));
  
  % correction for values > 1
  ind = sub2ind(size(tempAlpha), ind1, ind2);
  tempAlpha(ind) = 1 - 1./absSymb(ind);
  
  gui.symbImageId = image(...
    convIndToAxesX([1, nbTime]),...
    convIndToAxesY([1, nbPlottedFreq]),...
    tempIm,...
    'alphaData',gui.symbOpacity * tempAlpha,...
    'Parent', axesId);
  hold(axesId, 'off');
  % restore the current tool
  changeTool([], [], gui.curTool);
end
  
function switchSelMode(objId,eventData)

  global sel gui

  sel.mode = get(objId,'Tag');
  modesIds = [gui.buttonUnionId,gui.buttonInterId,gui.buttonDiffId];
  modesIds = setdiff(modesIds, objId);
  for modeId = modesIds
    set(modeId, 'Value', false);
  end
  set(objId, 'Value', true);
end

function drawProcessingTool(uipanelId)
% creation of apply button

  global gui

  applyId = uicontrol(...
    'Parent', uipanelId,...
    'Style', 'pushbutton',...
    'TooltipString', 'Apply multiplier',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1, gui.marginSub),...
    'CallBack',@applySel);
  
  setButtonAppearance(applyId, 'x', 'apply.png');
end


% __________________________ LAYERS FUNCTIONS _____________________________

function drawLayerPanel(uipanelId)

  global gui

  layUicontextmenu = uicontextmenu;
  uimenu(layUicontextmenu,...
    'Label', 'Line color',...
    'Callback', @changeSelLayColor);
  
  layStyleMenuId = uimenu(layUicontextmenu,...
    'Label', 'Line style');
  uimenu(layStyleMenuId,...
    'Label', 'Solid',...
    'Callback', {@changeSelLayStyle, '-'});
  uimenu(layStyleMenuId,...
    'Label', 'Dashed',...
    'Callback', {@changeSelLayStyle, '--'});
  uimenu(layStyleMenuId,...
    'Label', 'Dot',...
    'Callback', {@changeSelLayStyle, ':'});
  uimenu(layStyleMenuId,...
    'Label', 'Dash-dot',...
    'Callback', {@changeSelLayStyle, '-.'});
  uimenu(layStyleMenuId,...
    'Label', 'None',...
    'Callback', {@changeSelLayStyle, 'none'});
  
  layWidthMenuId = uimenu(layUicontextmenu,...
    'Label', 'Line width');
  uimenu(layWidthMenuId,...
    'Label', '1.0',...
    'Callback', {@changeSelLayWidth, 1});
  uimenu(layWidthMenuId,...
    'Label', '2.0',...
    'Callback', {@changeSelLayWidth, 2});
  uimenu(layWidthMenuId,...
    'Label', '3.0',...
    'Callback', {@changeSelLayWidth, 3});
  uimenu(layWidthMenuId,...
    'Label', '4.0',...
    'Callback', {@changeSelLayWidth, 4});
  uimenu(layWidthMenuId,...
    'Label', '5.0',...
    'Callback', {@changeSelLayWidth, 5});
  uimenu(layWidthMenuId,...
    'Label', '6.0',...
    'Callback', {@changeSelLayWidth, 6});
  uimenu(layWidthMenuId,...
    'Label', '7.0',...
    'Callback', {@changeSelLayWidth, 7});
  uimenu(layWidthMenuId,...
    'Label', '8.0',...
    'Callback', {@changeSelLayWidth, 8});
  uimenu(layWidthMenuId,...
    'Label', '9.0',...
    'Callback', {@changeSelLayWidth, 9});
  uimenu(layWidthMenuId,...
    'Label', '10.0',...
    'Callback', {@changeSelLayWidth, 10});

  layMarkerMenuId = uimenu(layUicontextmenu,...
    'Label', 'Marker');
  uimenu(layMarkerMenuId,...
    'Label', 'None',...
    'Callback', {@changeSelLayMarker, 'none'});    
  uimenu(layMarkerMenuId,...
    'Label', '+',...
    'Callback', {@changeSelLayMarker, '+'});
  uimenu(layMarkerMenuId,...
    'Label', 'o',...
    'Callback', {@changeSelLayMarker, 'o'});
  uimenu(layMarkerMenuId,...
    'Label', '*',...
    'Callback', {@changeSelLayMarker, '*'});
  uimenu(layMarkerMenuId,...
    'Label', '.',...
    'Callback', {@changeSelLayMarker, '.'});
  uimenu(layMarkerMenuId,...
    'Label', 'x',...
    'Callback', {@changeSelLayMarker, 'x'});
  uimenu(layMarkerMenuId,...
    'Label', 'Square',...
    'Callback', {@changeSelLayMarker, 'square'});
  uimenu(layMarkerMenuId,...
    'Label', 'Diamond',...
    'Callback', {@changeSelLayMarker, 'diamond'});
  uimenu(layMarkerMenuId,...
    'Label', '^',...
    'Callback', {@changeSelLayMarker, '^'});
  uimenu(layMarkerMenuId,...
    'Label', 'v',...
    'Callback', {@changeSelLayMarker, 'v'});
  uimenu(layMarkerMenuId,...
    'Label', '>',...
    'Callback', {@changeSelLayMarker, '>'});
  uimenu(layMarkerMenuId,...
    'Label', '<',...
    'Callback', {@changeSelLayMarker, '<'});
  uimenu(layMarkerMenuId,...
    'Label', 'Pentagram',...
    'Callback', {@changeSelLayMarker, 'pentagram'});
  uimenu(layMarkerMenuId,...
    'Label', 'Hexagram',...
    'Callback', {@changeSelLayMarker, 'hexagram'});
  
  
  layMarkerSizeMenuId = uimenu(layUicontextmenu,...
    'Label', 'Marker Size');
  uimenu(layMarkerSizeMenuId,...
    'Label', '2.0',...
    'Callback', {@changeSelLayMarkerSize, 2});
  uimenu(layMarkerSizeMenuId,...
    'Label', '3.0',...
    'Callback', {@changeSelLayMarkerSize, 3});
  uimenu(layMarkerSizeMenuId,...
    'Label', '4.0',...
    'Callback', {@changeSelLayMarkerSize, 4});
  uimenu(layMarkerSizeMenuId,...
    'Label', '5.0',...
    'Callback', {@changeSelLayMarkerSize, 5});
  uimenu(layMarkerSizeMenuId,...
    'Label', '6.0',...
    'Callback', {@changeSelLayMarkerSize, 6});
  uimenu(layMarkerSizeMenuId,...
    'Label', '7.0',...
    'Callback', {@changeSelLayMarkerSize, 7});
  uimenu(layMarkerSizeMenuId,...
    'Label', '8.0',...
    'Callback', {@changeSelLayMarkerSize, 8});
  uimenu(layMarkerSizeMenuId,...
    'Label', '9.0',...
    'Callback', {@changeSelLayMarkerSize, 9});
  uimenu(layMarkerSizeMenuId,...
    'Label', '10.0',...
    'Callback', {@changeSelLayMarkerSize, 10});
  
  uimenu(layUicontextmenu,...
    'Label', 'Edit label',...
    'Callback', @changeSelLayLabel);
  
  
  layTypeMenuId = uimenu(layUicontextmenu,...
    'Label', 'Layer type');
  uimenu(layTypeMenuId,...
    'Label', 'Constant gain',...
    'Callback', {@changeSelLayConvType, 'constGain'});
  uimenu(layTypeMenuId,...
    'Label', 'Gain with smoothed border',...
    'Callback', {@changeSelLayConvType, 'smoothBorder'});
  if gui.hasImagePackage
    uimenu(layTypeMenuId,...
      'Label', 'Hole filling',...
      'Callback', {@changeSelLayConvType, 'fill'});
    uimenu(layTypeMenuId,...
      'Label', 'Hole filling with noise phase',...
      'Callback', {@changeSelLayConvType, 'fillNoise'});
  end
  
  uimenu(layUicontextmenu,...
    'Label', 'Invert layer',...
    'Callback', @invertSelLay);
  
  uimenu(layUicontextmenu,...
    'Label', 'Duplicate layer',...
    'Callback', @duplicateSelLay);
  
  uimenu(layUicontextmenu,...
    'Label', 'Clear layer',...
    'Callback', @clearSelLay);
  
  uimenu(layUicontextmenu,...
    'Label', 'Delete layer',...
    'Callback', @delSelLay);
  
  uimenu(layUicontextmenu,...
    'Label', 'Add new layer',...
    'Callback', @addSelLay);
  
  gui.layListId = uicontrol(...
    'Parent', uipanelId,...
    'HandleVisibility', 'off',...
    'Style', 'listbox',...
    'Fontsize', gui.fontSize,...
    'String', '',...
    'UIContextMenu', layUicontextmenu,...
    'BackgroundColor', [1 1 1],...
    'Position', [gui.marginSub(1),...
      gui.marginSub(2)+2*gui.textHeight+2*gui.vertDist+1,...
      gui.panelWidth-2*gui.margin(1)-1, 60],...
    'CallBack',@changeSelLay);
  
  gui.layPanelId = uipanelId; 
  updateLayerList;
  changeSelLay;
end

function updateLayerList()

  global gui sel

  layCell = {};
  if isfield(sel,'lay')
    for ind = 1:length(sel.lay)
      layCell{end+1} = sel.lay(ind).label;
    end
    set(gui.layListId, 'String', layCell);
  end
end
  
function changeSelLayColor(objId, eventData)

  global sel

  try
    sel.lay(sel.curLay).color = uisetcolor;
    delSel;
    drawAllSel;
  catch
    % uisetcolor is not available in Octave, so we provide a simple gui
    chooseLineColor();
  end
end

function chooseLineColor()
  global gui;
  
  mainFigPosition = get(gui.mainFigId, 'Position');
  position = [mainFigPosition(1)+mainFigPosition(3)/2,...
              mainFigPosition(2)+mainFigPosition(4)/2, 250, 100];
  
  gui.chooseLineColor.figId = figure(...
    'Name', 'Line color',...
    'Position', position,...
    'Menubar', 'none',...
    'windowstyle', 'modal');
  % Note: the 'modal' windowstyle seems ignored in Octave
  % So we need to make the main window unclicable by hand
  % and the only solution seem to be hinding the main window, as the
  % following is not enough (the buttons are still clickable):
  % set(gui.mainFigId, 'HitTest', 'off');
  set(gui.mainFigId, 'Visible', 'off');
  uimenu(gui.chooseLineColor.figId); % Removing the default menu
  
  
  buttonOkId = uicontrol(...
    'Parent', gui.chooseLineColor.figId,...
    'Style', 'pushbutton',...
    'String', 'OK',...
    'Callback', @chooseLineColorOk,...
    'Position', [50, 10, 50, 30]);
  
  
  buttonCancelId = uicontrol(...
    'Parent', gui.chooseLineColor.figId,...
    'Style', 'pushbutton',...
    'String', 'Cancel',...
    'Callback', @chooseLineColorCancel,...
    'Position', [150, 10, 50, 30]);
  
  gui.chooseLineColor.colors = {'w', 'k', 'r', 'g', 'b', 'y', 'm', 'c'};
  colorLabels = {'white', 'black', 'red', 'green', 'blue', 'yelow',...
                 'magenta', 'cyan'};
  defaultValue = 1;    
  gui.chooseLineColor.popupmenuId = uicontrol(...
    'Parent', gui.chooseLineColor.figId,...
    'Style', 'popupmenu',...
    'String', colorLabels,...
    'Value', defaultValue,...
    'Position', [25, 50, 200, 30]);

end
  
function chooseLineColorOk(objId, eventData)
  global gui sel;
  set(gui.mainFigId, 'Visible', 'on'); % Needed for Octave
  figure(gui.mainFigId);
  
  ind = get(gui.chooseLineColor.popupmenuId, 'Value');
  close(gui.chooseLineColor.figId);
  sel.lay(sel.curLay).color = gui.chooseLineColor.colors{ind};
  delSel;
  drawAllSel;  
end
  
function chooseLineColorCancel(objId, eventData)
  global gui;
  set(gui.mainFigId, 'Visible', 'on'); % Needed for Octave
  figure(gui.mainFigId);
  close(gui.chooseLineColor.figId);
end
  
function changeSelLayStyle(objId, eventData, lineStyle)

  global sel

  sel.lay(sel.curLay).lineStyle = lineStyle;
  delSel;
  drawAllSel;
end

function changeSelLayWidth(objId, eventData, lineWidth)

  global sel

  sel.lay(sel.curLay).lineWidth = lineWidth;
  delSel;
  drawAllSel;
end

function changeSelLayMarker(objId, eventData, marker)

  global sel

  sel.lay(sel.curLay).marker = marker;
  delSel;
  drawAllSel;
end

function changeSelLayMarkerSize(objId, eventData, markerSize)

  global sel

  sel.lay(sel.curLay).markerSize = markerSize;
  delSel;
  drawAllSel;
end

function changeSelLayLabel(objId, eventData)

  global sel

  newLabel = inputdlg('New label for the current selection layer',...
    'Change layer label');
  sel.lay(sel.curLay).label = newLabel{1};
  updateLayerList;    
end

function changeSelLayConvType(objId, eventData, convType)

  global sel

  delSel;
  sel.lay(sel.curLay).convType = convType;
  
  sel.lay(sel.curLay).param = [];
  
  switch convType
    case 'constGain'
      sel.lay(sel.curLay).param(1).name = 'Gain';
      sel.lay(sel.curLay).param(1).val = 0;
    case 'smoothBorder'
      sel.lay(sel.curLay).param(1).name = 'Gain';
      sel.lay(sel.curLay).param(1).val = 0;
      sel.lay(sel.curLay).param(2).name = 'Border';
      sel.lay(sel.curLay).param(2).val = 10;
    case 'fill'
      sel.lay(sel.curLay).param(1).name = 'Width';
      sel.lay(sel.curLay).param(1).val = 2;
      sel.lay(sel.curLay).param(2).name = 'Height';
      sel.lay(sel.curLay).param(2).val = 3;
    case 'fillNoise'
      sel.lay(sel.curLay).param(1).name = 'Width';
      sel.lay(sel.curLay).param(1).val = 2;
      sel.lay(sel.curLay).param(2).name = 'Height ';
      sel.lay(sel.curLay).param(2).val = 3;
  end
  changeSelLay;
  drawAllSel;    
end

function invertSelLay(objId, eventData)

  global sel
     
    if isempty(sel.lay(sel.curLay).poly)
      sel.lay(sel.curLay).poly = fullSigPoly;
    else
      % compute difference between a polygon around the whole signal and 
      % the current selection to inverse current selection
      newPoly = clipPoly(fullSigPoly, sel.lay(sel.curLay).poly, 0);
      delCurPoly;
      sel.lay(sel.curLay).poly = newPoly;
    end
    
    drawCurSel;
end

function poly = fullSigPoly()
  % compute a polygon corresponding to the whole signal

  global coeff

  tempX = convIndToAxesX([0.5, size(coeff.oriC, 2)+0.5]);
  tempY = convIndToAxesY([0.5, size(coeff.oriC, 1)+0.5]);
  poly.x = [tempX(1); tempX(2); tempX(2); tempX(1)];
  poly.y = [tempY(1); tempY(1); tempY(2); tempY(2)];
  poly.hole = false;
end

function duplicateSelLay(objId, eventData)

  global sel

  sel.lay(end+1) = sel.lay(sel.curLay);
  sel.lay(end).label = [sel.lay(end).label ' copy'];
  if isfield(sel.lay(end).poly(1), 'id')
    for ind = 1:length(sel.lay(end).poly)
      sel.lay(end).poly(ind).id = [];
    end
  end
  updateLayerList;
end

function clearSelLay(objId, eventData)

  global sel

  delSel;
  updateUndoData;
  sel.lay(sel.curLay).poly = [];
  drawAllSel;
end

function delSelLay(objId, eventData)
  % check that we leave at least one layer

  global gui sel

  if length(sel.lay) == 1
    warndlg('The selection must contain at least one layer');
    return;
  end
  
  delSel;
  updateUndoData;
  sel.lay = sel.lay([1:sel.curLay-1 sel.curLay+1:end]);
  updateLayerList;
  set(gui.layListId, 'Value', 1);
  changeSelLay;
  drawAllSel;
end

function addSelLay(objId, eventData)

  global sel default

  sel.lay(end+1) = default.sel.lay;
  sel.lay(end).label = 'New layer';
  updateLayerList;
end

function changeSelLay(objId, eventData)

  global sel gui

  sel.curLay = get(gui.layListId, 'Value');
  drawLayParam(sel.curLay);
end

function drawLayParam(layInd)

  global sel gui

  if ~isfield(sel,'lay')
     return;
  end
  child = findobj(gui.layPanelId);
  child = setdiff(child, gui.layPanelId);
  delete(child);
  for ind = 1:length(sel.lay(layInd).param)
    uicontrol(...
      'Parent', gui.layPanelId,...
      'FontSize', gui.fontSize, ...
      'Style', 'text',...
      'String', sel.lay(layInd).param(ind).name,...
      'Position', [gui.marginSub(1),...
        gui.marginSub(2)+(ind-1)*(gui.textHeight+gui.vertDist),...
        gui.textWidth, gui.textHeight]) ;
    uicontrol(...
      'Parent', gui.layPanelId,...
      'FontSize', gui.fontSize, ...
      'Style', 'edit',...
      'String', num2str(sel.lay(layInd).param(ind).val),...
      'BackgroundColor', gui.buttonBackgroundColor,...
      'Position', [gui.marginSub(1)+gui.textWidth,...
        gui.marginSub(2)+(ind-1)*(gui.textHeight+gui.vertDist),...
        gui.editWidth, gui.textHeight],...
      'Callback', {@changeLayParam, layInd, ind});
  end
end

function changeLayParam(objId, eventData, layInd, paramInd)

  global sel

  handleSymb;
  temp = str2num(get(objId, 'String'));
  if isempty(temp)
    errordlg([sel.lay(layInd).param(paramInd).name...
      ' parameter is invalid']);
    set(objId, 'String', num2str(sel.lay(layInd).param(paramInd).val));
    return;
  end
  sel.lay(layInd).param(paramInd).val = temp;
end

function changeOpacity(objId, eventData)

  global gui

  temp = str2num(get(objId, 'String'));
  if isempty(temp) || temp < 0 || temp > 1
    errordlg(['Opacity parameter is invalid, it must be a number '...
      'between 0 (transparent) and 1 (opaque)']);
    set(objId, 'String', num2str(gui.symbOpacity));
    return;
  end
  gui.symbOpacity = temp;
  handleSymb();
end


% __________________ VISUALIZATION TOOLS FUNCTIONS ________________________

function drawVisualizationTool(uipanelId)

  global gui default

  subPanelHeight = gui.margin(2) + gui.buttonHeight +...
    gui.textHeight + gui.fontSize + 4;
 
  visPanelPos = get(uipanelId,'Position');

  symbolPanelHeight = 2*subPanelHeight;

  currPos = 0;
  symbolPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Symbol',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, currPos+1, gui.panelWidth-4 , symbolPanelHeight]);
  currPos = currPos + symbolPanelHeight;
  
  gui.symbListId = uicontrol(...
    'Parent', uipanelId,...
    'HandleVisibility', 'off',...
    'Style', 'listbox',...
    'Fontsize', gui.fontSize,...
    'String', '',...
    'BackgroundColor', [1 1 1],...
    'Position', [gui.marginSub(1),...
      gui.marginSub(2)+2*gui.textHeight+2*gui.vertDist+6,...
      gui.panelWidth-2*gui.margin(1)-1, 50],...
    'CallBack',@changeSelSymb);
  
  symbId = uicontrol(...
    'Parent', symbolPanelId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Show multiplier symbol',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1, gui.marginSub),...
    'Value', false,...
    'CallBack',@clicSymb);
    
  setButtonAppearance(symbId, 'show', 'showsymbol.png');

  opacityTextId = uicontrol(...
    'Parent', symbolPanelId,...
    'FontSize', gui.fontSize, ...
    'Style', 'Text',...
    'FontSize', gui.fontSize, ...
    'String', 'Opacity',...
    'Position', [gui.marginSub(1),...
      gui.marginSub(2)+gui.buttonHeight,...
      gui.textWidth,...
      gui.textHeight],...
    'CallBack',@changeOpacity);
  
  opacityId = uicontrol(...
    'Parent', symbolPanelId,...
    'FontSize', gui.fontSize, ...
    'Style', 'edit',...
    'String', num2str(default.opacity),...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', [gui.marginSub(1)+gui.textWidth,...
      gui.marginSub(2)+gui.buttonHeight, gui.editWidth, gui.textHeight],...
    'CallBack',@changeOpacity);
 
  if isoctave()
    % Octave doen't support transprency with images, so we cannot display 
    % the symbol over the spectrogram, so we remove the associated controls
    set(symbId, 'Visible', 'off');
    set(opacityTextId, 'Visible', 'off');
    set(opacityId, 'Visible', 'off');
  end
 
  colormapPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Colormap',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, currPos+1, gui.panelWidth-4 , subPanelHeight]);
  currPos = currPos + subPanelHeight;
  
  buttonColormapId = uicontrol(...
    'Parent', colormapPanelId,...
    'Style', 'pushbutton',...
    'TooltipString', 'Edit colormap',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1, gui.marginSub),...
    'CallBack',@editColormap);
    
  setButtonAppearance(buttonColormapId, 'map', 'colormap.png');
  
  uicontrol(...
    'Parent', colormapPanelId,...
    'FontSize', gui.fontSize, ...
    'Style', 'Text',...
    'FontSize', gui.fontSize, ...
    'String', 'Dynamic',...
    'Position', [gui.marginSub(1), gui.marginSub(2)+gui.buttonHeight,...
      gui.textWidth, gui.textHeight],...
    'CallBack',@changeOpacity);
  
  gui.editDynamicId = uicontrol(...
    'Parent', colormapPanelId,...
    'FontSize', gui.fontSize, ...
    'Style', 'edit',...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'String', num2str(default.dynamic),...
    'Position', [gui.marginSub(1)+gui.textWidth,...
      gui.marginSub(2)+gui.buttonHeight,...
      gui.editWidth,...
      gui.textHeight],...
    'CallBack',@changeDynamic);
  
  zoomPanelId = uipanel(...
    'Parent', uipanelId,...
    'Title', 'Zoom',...
    'TitlePosition', 'centertop',...
    'FontSize', gui.fontSize, ...
    'Units', 'pixels',...
    'Position', [1, currPos+1, subpanelSize(1)]);
  
  buttonZoomInId = uicontrol(...
    'Parent', zoomPanelId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Zoom in',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(1, 1, gui.marginSub),...
    'CallBack', {@changeTool, 'zoomIn'});
    
  setButtonAppearance(buttonZoomInId, 'in', 'zoomin.png');
  
  gui.tool(end).buttonId = buttonZoomInId;
  gui.tool(end).name= 'zoomIn';
  gui.tool(end).function = @switchZoomIn;
  
  buttonZoomOutId = uicontrol(...
    'Parent', zoomPanelId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Zoom out',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(2, 1, gui.marginSub),...
    'CallBack', {@changeTool, 'zoomOut'});
    
  setButtonAppearance(buttonZoomOutId, 'out', 'zoomout.png');
  
  gui.tool(end+1).buttonId = buttonZoomOutId;
  gui.tool(end).name = 'zoomOut';
  gui.tool(end).function = @switchZoomOut;
  
  buttonPanId = uicontrol(...
    'Parent', zoomPanelId,...
    'Style', 'togglebutton',...
    'TooltipString', 'Pan',...
    'Min', false,...
    'Max', true,...
    'Value', false,...
    'BackgroundColor', gui.buttonBackgroundColor,...
    'Position', buttonPos(3, 1, gui.marginSub),...
    'CallBack', {@changeTool, 'pan'});
    
  setButtonAppearance(buttonPanId, 'pan', 'pan.png');
  
  gui.tool(end+1).buttonId = buttonPanId;
  gui.tool(end).name = 'pan';
  gui.tool(end).function = @switchPan;
  
end

function clicSymb(objId, eventData)

  global gui

  button_state = get(objId,'Value');
  if button_state == get(objId,'Max')
     delSel;
     gui.showSymb = true;
     drawAllSel;
  elseif button_state == get(objId,'Min')
     delSel;
     gui.showSymb = false;
     drawAllSel;
  end
end

function switchZoomIn(toolInd)
  % NOTE: toolInd is not used but needed as a parameter is always passed to
  % this function 

  global gui
  
  if isoctave()
    zoom(gui.mainFigId, 'on');
  else
    if getMatlabVersion() >= 7.3
      zoomId = zoom(gui.mainFigId);
      set(zoomId,...
        'Direction', 'in',...
        'Enable', 'on');
    else
      zoom(gui.mainFigId, 'on');
      % NOTE: this is undocumented in version 7.2 but it works, something more
      % general would be better
      zoom(gui.mainFigId, 'Direction', 'in');
    end
  end
end

function switchZoomOut(toolInd) 
  % NOTE: toolInd is not used but needed as a parameter is always passed to
  % this function 
  
  global gui visu visucommon

  if isoctave()
    oriInd = find(strcmp('originalMain', {visu.label}), 1);
    axesId = visu(oriInd).axesId;
    imageId = findobj(axesId, 'Type', 'Image');
    set(imageId, 'ButtonDownFcn', '');  
    zoom(gui.mainFigId, 'out');
    % setting zoom(gui.mainFigId, 'out') does not reliably fully zoom out in
    % some cases, so we also force the full zoom out by hand
    axis(visu(oriInd).axesId, [visucommon.oriXlim, visucommon.oriYlim]);
  else
    if getMatlabVersion() >= 7.3
      zoomId = zoom(gui.mainFigId);
      set(zoomId,...
        'Direction', 'out',...
        'Enable', 'on');
    else
      zoom(gui.mainFigId, 'on');
      % NOTE: this is undocumented in version 7.2 but it works, something more
      % general would be better
      zoom(gui.mainFigId, 'Direction', 'out');
    end
  end
end
    
function switchPan(toolInd)
  % NOTE: toolInd is not used but needed as a parameter is always passed to
  % this function 

  global gui

  if isoctave()
    pan(gui.mainFigId, 'on');
  else
    if getMatlabVersion() >= 7.3
      panId = pan(gui.mainFigId);
      set(panId,...
        'Enable', 'on');
    else
      pan(gui.mainFigId, 'on');
    end
  end
end

function [res] = updateExploreRect(objId, eventData)

  global visu

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  exploreSetRect(get(visu(oriInd).axesId, 'XLim'),...
    get(visu(oriInd).axesId, 'YLim'));
end

function editColormap(objId, eventData)

  global gui

  try
    colormapeditor(gui.mainFigId);
  catch
    % the colormapeditor is not available in Octave so we implement a simple gui
    % to choose the colormap
    chooseColormap;
  end
end

function chooseColormap()
  global gui;
  
  mainFigPosition = get(gui.mainFigId, 'Position');
  position = [mainFigPosition(1)+mainFigPosition(3)/2,...
              mainFigPosition(2)+mainFigPosition(4)/2, 250, 130];
  
  gui.chooseColormap.figId = figure(...
    'Name', 'Colormap',...
    'Position', position,...
    'Menubar', 'none',...
    'windowstyle', 'modal');
  % Note: the 'modal' windowstyle seems ignored in Octave
  % So we need to make the main window unclicable by hand
  % and the only solution seem to be hinding the main window, as the
  % following is not enough (the buttons are still clickable):
  % set(gui.mainFigId, 'HitTest', 'off');
  set(gui.mainFigId, 'Visible', 'off');
  uimenu(gui.chooseColormap.figId); % Removing the default menu
  
  
  buttonOkId = uicontrol(...
    'Parent', gui.chooseColormap.figId,...
    'Style', 'pushbutton',...
    'String', 'OK',...
    'Callback', @chooseColormapOk,...
    'Position', [50, 10, 50, 30]);
  
  
  buttonCancelId = uicontrol(...
    'Parent', gui.chooseColormap.figId,...
    'Style', 'pushbutton',...
    'String', 'Cancel',...
    'Callback', @chooseColormapCancel,...
    'Position', [150, 10, 50, 30]);
  
  gui.chooseColormap.checkboxId = uicontrol(...
    'Parent', gui.chooseColormap.figId,...
    'Style', 'checkbox',...
    'String', 'Invert colormap',...
    'Value', false,...
    'Position', [25, 50, 200, 30]);
  
  maps = colormap('list');
  defaultValue = find(strcmp('jet', maps));    
  gui.chooseColormap.popupmenuId = uicontrol(...
    'Parent', gui.chooseColormap.figId,...
    'Style', 'popupmenu',...
    'String', maps,...
    'Value', defaultValue,...
    'Position', [25, 90, 200, 30]);
end
  
function chooseColormapOk(objId, eventData)
  global gui;
  set(gui.mainFigId, 'Visible', 'on'); % Needed for Octave
  figure(gui.mainFigId);
  
  invertColormap = get(gui.chooseColormap.checkboxId, 'Value');
  maps = get(gui.chooseColormap.popupmenuId, 'String');
  mapName = maps{get(gui.chooseColormap.popupmenuId, 'Value')};
  map = colormap(mapName);
  if invertColormap
    map = map(end:-1:1, :);
  end
  colormap(map);
  close(gui.chooseColormap.figId);    
end
  
function chooseColormapCancel(objId, eventData)
  global gui;
  set(gui.mainFigId, 'Visible', 'on'); % Needed for Octave
  figure(gui.mainFigId);
  close(gui.chooseColormap.figId);
end

function changeDynamic(objId, eventData)

  global visu gui

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  temp = str2num(get(gui.editDynamicId, 'String'));
  if isempty(temp) || temp < 0
    errordlg(['Dynamic parameter is invalid, it must be a positive '...
      'number in dB']);
    set(gui.editDynamicId, 'String', num2str(visu(oriInd).dynamic));
    return;
  end
  visu(oriInd).dynamic = temp;
  updateVisu(false, true);
end

function exploreOverview(axesId)

  global visu gui

  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axLim = axis(visu(oriInd).axesId);
  
  xLim = axLim(1:2);
  yLim = axLim(3:4);
  
  indXData = [1, 2, 2, 1];
  indYData = [1, 1, 2, 2];
  
  gui.exploreRect.patchId = patch(...
    'Parent', axesId,...
    'XData', xLim(indXData),...
    'YData', yLim(indYData),...
    'FaceColor', 'none',...
    'LineWidth', 2,...
    'EdgeColor', 'w',... % PI: put the color and width as variables
    'ButtonDownFcn', @explorePatchButtonDown);
  
  gui.exploreRect.lineId = line(...
    'Parent', axesId,...
    'XData', xLim(indXData),...
    'YData', yLim(indYData),...
    'LineStyle', 'None',...
    'LineWidth', 2,...
    'Color', 'w',... % PI: put the color and width as variables
    'Marker', 'o',...
    'MarkerSize', 7,...
    'ButtonDownFcn', @exploreLineButtonDown);
  
  patchId = gui.exploreRect.patchId;
  lineId = gui.exploreRect.lineId;
  figId = gui.mainFigId;
  initPoint = [];

  indX = [];
  indY = [];
  
  exploreAxesXLim = [];
  exploreAxesYLim = [];
  
  gui.exploreOverview.axesId = axesId;
  gui.exploreOverview.indX = indX;
  gui.exploreOverview.indY = indY;
  gui.exploreOverview.xLim = xLim;
  gui.exploreOverview.yLim = yLim;
  gui.exploreOverview.initPoint = initPoint;
  gui.exploreOverview.exploreAxesXLim = exploreAxesXLim;
  gui.exploreOverview.exploreAxesYLim = exploreAxesYLim;
end

function exploreLineButtonDown(objId, eventData)

  global gui

  axesId = gui.exploreOverview.axesId;
  lineId = gui.exploreRect.lineId;
  figId = gui.mainFigId;

  % the coordinate that should be modified depends on the selected corner
  point = get(axesId,'CurrentPoint');
  
  xData = get(lineId, 'XData');
  yData = get(lineId, 'YData');
  xLim = xData(1:2);
  yLim = yData(2:3);
  
  [mi, indX] = min(abs(xLim - point(1,1)));
  [mi, indY] = min(abs(yLim - point(1,2)));
  
  gui.exploreOverview.indX = indX;
  gui.exploreOverview.indY = indY;
  gui.exploreOverview.xLim = xLim;
  gui.exploreOverview.yLim = yLim;
  
  set(figId,'WindowButtonMotionFcn',@exploreMotionLine);
  set(figId,'WindowButtonUpFcn',@exploreButtonUp);
end
    
function exploreMotionLine(objId, eventData)

  global gui visu

  indXData = [1, 2, 2, 1];
  indYData = [1, 1, 2, 2];
  
  patchId = gui.exploreRect.patchId;
  lineId = gui.exploreRect.lineId;
  axesId = gui.exploreOverview.axesId;
  indX = gui.exploreOverview.indX;
  indY = gui.exploreOverview.indY;
  xLim = gui.exploreOverview.xLim;
  yLim = gui.exploreOverview.yLim;

  point = get(axesId,'CurrentPoint');
  xLim(indX) = point(1,1);
  yLim(indY) = point(1,2);
  
  % reorder the values
  xLimSort = sort(xLim);
  yLimSort = sort(yLim);
  
  set(patchId,...
    'XData', xLimSort(indXData),...
    'YData', yLimSort(indYData));
  set(lineId,...
    'XData', xLimSort(indXData),...
    'YData', yLimSort(indYData));
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axis(visu(oriInd).axesId, [xLimSort, yLimSort]);
  
  gui.exploreOverview.indX = indX;
  gui.exploreOverview.indY = indY;
  gui.exploreOverview.xLim = xLim;
  gui.exploreOverview.yLim = yLim;
end
     
function explorePatchButtonDown(objId, eventData)

  global gui

  axesId = gui.exploreOverview.axesId;
  patchId = gui.exploreRect.patchId;
  figId = gui.mainFigId;

  initPoint = get(axesId,'CurrentPoint');
  exploreAxesXLim = get(axesId, 'XLim');
  exploreAxesYLim = get(axesId, 'YLim');
  xData = get(patchId, 'XData');
  yData = get(patchId, 'YData');
  xLim = xData(1:2);
  yLim = yData(2:3);
  set(figId,'WindowButtonMotionFcn',@exploreMotionPatch);
  set(figId,'WindowButtonUpFcn',@exploreButtonUp);
  
  gui.exploreOverview.xLim = xLim;
  gui.exploreOverview.yLim = yLim;
  gui.exploreOverview.initPoint = initPoint;
  gui.exploreOverview.xLim = xLim;
  gui.exploreOverview.yLim = yLim;
  gui.exploreOverview.exploreAxesXLim = exploreAxesXLim;
  gui.exploreOverview.exploreAxesYLim = exploreAxesYLim;
end
    
function exploreMotionPatch(objId, eventData)

  global gui visu
  
  axesId = gui.exploreOverview.axesId;
  xLim = gui.exploreOverview.xLim;
  yLim = gui.exploreOverview.yLim;
  initPoint = gui.exploreOverview.initPoint;
  exploreAxesXLim = gui.exploreOverview.exploreAxesXLim;
  exploreAxesYLim = gui.exploreOverview.exploreAxesYLim;
  patchId = gui.exploreRect.patchId;
  lineId = gui.exploreRect.lineId;
  
 
  indXData = [1, 2, 2, 1];
  indYData = [1, 1, 2, 2];
  
  point = get(axesId,'CurrentPoint');
  newXLim = xLim + (point(1,1)-initPoint(1,1));
  newYLim = yLim + (point(1,2)-initPoint(1,2));
  
  if ~(newXLim(2) < exploreAxesXLim(1) ||...
      newXLim(1) > exploreAxesXLim(2) ||...
      newYLim(2) < exploreAxesYLim(1) ||...
      newYLim(1) > exploreAxesYLim(2))
    set(patchId,...
      'XData', newXLim(indXData),...
      'YData', newYLim(indYData));
    set(lineId,...
      'XData', newXLim(indXData),...
      'YData', newYLim(indYData));
    oriInd = find(strcmp('originalMain', {visu.label}), 1);
    axis(visu(oriInd).axesId, [newXLim(1), newXLim(2), newYLim(1), newYLim(2)]);
  end
end
    
function exploreButtonUp(objId, eventData)

  global gui visu

  figId = gui.mainFigId;
  patchId = gui.exploreRect.patchId;

  set(figId,'WindowButtonMotionFcn','');
  set(figId,'WindowButtonUpFcn','');
  xData = get(patchId, 'XData');
  yData = get(patchId, 'YData');
  oriInd = find(strcmp('originalMain', {visu.label}), 1);
  axis(visu(oriInd).axesId, [xData(1), xData(2), yData(2), yData(3)]);      
end

function exploreSetRect(xLim, yLim)

  global gui

  indXData = [1, 2, 2, 1];
  indYData = [1, 1, 2, 2];
  if ishandle(gui.exploreRect.patchId)
    set(gui.exploreRect.patchId,...
      'XData', xLim(indXData),...
      'YData', yLim(indYData));
  end
  if ishandle(gui.exploreRect.lineId)
    set(gui.exploreRect.lineId,...
      'XData', xLim(indXData),...
      'YData', yLim(indYData));
  end
end

function changeSelSymb(objId, eventData)

  global symbol gui

  symbol.curSymb = get(gui.symbListId, 'Value');
  handleSymb();
end

function resetSymbol()
% erase sel and put it to default value

  global symbol default gui

  symbol = default.symbol;
  if isfield(gui, 'symbListId')
    if ishandle(gui.symbListId)
      set(gui.symbListId, 'Value', symbol.curSymb);
      drawSymbolParam(symbol.curSymb);
      updateSymbolList;
    end
  end
end

function updateSymbolList()

  global symbol gui

  symCell = {};
  if isfield(symbol,'data')
     for ind = 1:length(symbol.data)
       symCell{end+1} = symbol.data(ind).name;
     end
     set(gui.symbListId, 'String', symCell);
  end
end

function drawSymbolParam(symbInd)
  % nothing to do yet
end

function addSymbolListItem(symb)

  global symbol

  symbol.data(end+1).val = symb;
  symbol.data(end).name = 'Imported';
  updateSymbolList();
end

