/*****************************************************************************

GRAOUMF TRACKER 2

Copyright (c) 1996 - 2002 Laurent de Soras

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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Contact the author : laurent@ohmforce.com
More information about this license : http://www.gnu.org/licenses/gpl.html

*****************************************************************************/



/*\\\ FICHIERS INCLUDE \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/

#include	<stdio.h>
#include	<math.h>

#include	"base.h"
#include	"base_ct.h"
#include	"gtracker.h"
#include	"intrface.h"
#include	"log.h"
#include	"PatEdTracks.h"
#include	"PatSelection.h"
#include	"patt.h"
#include	"Player.h"
#include	"Popup.h"
#include	"resource.h"
#include	"tracks.h"
#include	"sliders.h"
#include	"UndoCellPatNote.h"



/*\\\ CONSTANTES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ CONSTANTES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ TYPES & STRUCTURES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ DEFINITION DES VARIABLES DE CLASSE PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*\\\ METHODES PUBLIQUES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



/*==========================================================================*/
/*      Nom: (constructeur)                                                 */
/*      Description: Initialise                                             */
/*      Parametres en entree:                                               */
/*        - number: numero du preset.                                       */
/*==========================================================================*/

PatEdTracks::PatEdTracks (void)
{
	SLID_INFO	slider =
	{
		RSC_OBJ_MP_TRK_SBAR,
		RSC_OBJ_MP_TRK_SBAR_SLIDER,
		0, 0, 0, 0, 0, -1,
		SLID_DIRECTION_HORIZONTAL,
		false, false, 0, 0
	};

	_rsc_object_ptr = NULL;
	_selection_ptr = NULL;

	_width = 0;
	_height = 0;

	_rsc_object_ptr = new PatEdTracks_RscObj [1];
	_rsc_object_ptr [0].info = RSC_OBJ_MP_TRK_INFO;
	_rsc_object_ptr [0].sep = RSC_OBJ_MP_TRK_DATA_SEP;
	_nbr_rsc_col = 1;
	_nbr_disp_lines = 1;

	_track_slider = slider;
}



/*==========================================================================*/
/*      Nom: (destructeur)                                                  */
/*      Description: Detruit                                                */
/*==========================================================================*/

PatEdTracks::~PatEdTracks (void)
{
	if (_selection_ptr != NULL)
	{
		delete	_selection_ptr;
		_selection_ptr = NULL;
	}

	if (_rsc_object_ptr != NULL)
	{
		delete [] _rsc_object_ptr;
		_rsc_object_ptr = NULL;
	}
}



/*==========================================================================*/
/*      Nom: check_ok                                                       */
/*      Description: Verifie l'intergrite de l'objet.                       */
/*      Retour: 0 si l'objet parait correct.                                */
/*==========================================================================*/

signed int	PatEdTracks::check_ok (void) const
{
	if (this == NULL)
	{
		LOG_printf ("PatEdTracks::check_ok: Error: \"this\" pointer is NULL.\n");
		return (-1);
	}

	if (_rsc_object_ptr != NULL)
	{
		LOG_printf ("PatEdTracks::check_ok: Error: _rsc_object_ptr is NULL.\n");
		return (-1);
	}

	return (0);
}



/*==========================================================================*/
/*      Nom: self_display                                                   */
/*      Description: Affiche "textuellement" le contenu courant de l'objet. */
/*==========================================================================*/

void	PatEdTracks::self_display (void) const
{

	/*** A faire ***/

}



/*==========================================================================*/
/*      Nom: redraw                                                         */
/*      Description: Affiche la composante d'interface.                     */
/*                   Faire un set_width () et set_height () avant le        */
/*                   premier appel !                                        */
/*==========================================================================*/

void	PatEdTracks::redraw (void)
{
	int		col;
	int		object;
	int		col_width;
	int		col_space;
	int		trknum_obj;
	int		up_obj;
	int		down_obj;
	int		pix_x;
	int		pix_y;
	int		win_height;
	PatEdTracks_RscObj	*temp_ptr;

	col_width = get_col_width ();
	col_space = (col_width + 1) * RSC_CHAR_W;
	RSC_get_relative_object_position (RSC_OBJ_MP_TRK_LINES, pix_x, pix_y);
	_nbr_disp_lines =   (  _height - pix_y - (4*2)
	                     - RSC_get_height (RSC_OBJ_MP_TRK_SBAR))
	                  / RSC_CHAR_H;
	_nbr_disp_lines = MAX (_nbr_disp_lines, 1);

	/* Calcule le nombre de colonne affichable selon le mode de visualisation */
	calc_max_disp_col ();

	/* Fabrique les objets de chaque ligne */
	RSC_get_relative_object_position (RSC_OBJ_MP_TRK_DATA, pix_x, pix_y);
	if (_nbr_rsc_col < _max_disp_col)
	{
		temp_ptr = new PatEdTracks_RscObj [_max_disp_col];
		memcpy (temp_ptr, _rsc_object_ptr, sizeof (*temp_ptr) * _nbr_rsc_col);
		delete [] _rsc_object_ptr;
		_rsc_object_ptr = temp_ptr;

		for (col = _nbr_rsc_col; col < _max_disp_col; col ++)
		{
			object = RSC_duplicate_object (_rsc_object_ptr [0].info);
			_rsc_object_ptr [col].info = object;
			RSC_object_ptr [_rsc_object_ptr [col - 1].info]->brother.number = object;

			object = RSC_duplicate_object (_rsc_object_ptr [0].sep);
			_rsc_object_ptr [col].sep = object;
			RSC_object_ptr [_rsc_object_ptr [col - 1].sep]->brother.number = object;
		}

		_nbr_rsc_col = _max_disp_col;
	}

	/* Positionne la fenetre des donnees */
	RSC_set_width (RSC_OBJ_MP_TRK_DATA,
	               col_space * (_max_disp_col - 1) + col_width * RSC_CHAR_W);

	win_height = _nbr_disp_lines * RSC_CHAR_H;
	RSC_set_height (RSC_OBJ_MP_TRK_DATA, win_height);
	RSC_set_height (RSC_OBJ_MP_TRK_LINES, win_height);

	/* Ajuste les attributs de chaque objet */
	for (col = 0; col < _nbr_rsc_col; col ++)
	{
		RSC_set_relative_object_position (_rsc_object_ptr [col].info,
		                                  pix_x + col * col_space, 0);
		RSC_set_relative_object_position (_rsc_object_ptr [col].sep,
		                                    col * col_space
		                                  + col_width * RSC_CHAR_W, 0);

		/* Colonne visible */
		if (col < _max_disp_col)
		{
			RSC_clear_flag (_rsc_object_ptr [col].info, RSC_ATTR_NOTDISP);
			RSC_set_width (_rsc_object_ptr [col].info, MIN (col_width * RSC_CHAR_W,
			                                                5 * RSC_CHAR_W));

			trknum_obj = RSC_get_child (_rsc_object_ptr [col].info);
			up_obj = RSC_get_brother (trknum_obj);
			down_obj = RSC_get_brother (up_obj);
			
			/* Pas de l'affichage de numero de piste ou de LED ? */
			if (col_width < 5)
			{
				RSC_set_flag (trknum_obj, RSC_ATTR_NOTDISP);
				RSC_set_flag (up_obj, RSC_ATTR_NOTDISP);
				RSC_set_flag (down_obj, RSC_ATTR_NOTDISP);
			}

			/* Pas de l'affichage des fleches */
			else if (col_width < 9)
			{
				RSC_clear_flag (trknum_obj, RSC_ATTR_NOTDISP);
				RSC_set_flag (up_obj, RSC_ATTR_NOTDISP);
				RSC_set_flag (down_obj, RSC_ATTR_NOTDISP);
			}

			/* Affichage de la totale */
			else
			{
				RSC_clear_flag (trknum_obj, RSC_ATTR_NOTDISP);
				RSC_clear_flag (up_obj, RSC_ATTR_NOTDISP);
				RSC_clear_flag (down_obj, RSC_ATTR_NOTDISP);
			}
		}

		/* Colonne invisible */
		else
		{
			RSC_set_flag (_rsc_object_ptr [col].info, RSC_ATTR_NOTDISP);
		}

		/* Separateur */
		if (col < _max_disp_col - 1)
		{
			RSC_clear_flag (_rsc_object_ptr [col].sep, RSC_ATTR_NOTDISP);
		}
		else
		{
			RSC_set_flag (_rsc_object_ptr [col].sep, RSC_ATTR_NOTDISP);
		}
		RSC_set_height (_rsc_object_ptr [col].sep, win_height);
	}

	/* Reinitialise le slider */
	RSC_set_relative_object_position (_track_slider.sbar_object, RSC_CHAR_W,
	                                  _height - RSC_get_height (RSC_OBJ_MP_TRK_SBAR) - 4);
	RSC_set_width (_track_slider.sbar_object,
	               INTR_graph_ptr->get_width () - RSC_CHAR_W * 2);
	RSC_set_width (_track_slider.slider_object, 4);

	/* Affichage */
	RSC_display_object (RSC_OBJ_MP_TRK);

	check_cursor ();

	refresh ();
}



/*==========================================================================*/
/*      Nom: refresh                                                        */
/*      Description: Rafraichit l'affichage des donnees.                    */
/*==========================================================================*/

void	PatEdTracks::refresh (void)
{
	int		track_type;
	int		track;
	int		col;
	int		nbr_col;
	int		real_col;
	int		trknum_obj;
	char		string_0 [3+1];

	const Player &	player = Player::use_instance ();

	/* Slider */
	_track_slider.virtual_len = MIN (  TRK_preset_data [TRK_preset_nbr].nbr
	                                 + _max_disp_col - 1,
	                                 TRK_NBRDISPVOICES_MAXI);
	_track_slider.virtual_pos = TRK_cursor_offset;
	_track_slider.virtual_win = _max_disp_col;
	SLID_display_slider (&_track_slider);

	/* Numero de piste et LED mute */
	track_type = TRK_preset_data [TRK_preset_nbr].track_type;
	nbr_col = TRK_preset_data [TRK_preset_nbr].nbr;
	for (col = 0; col < _max_disp_col; col ++)
	{
		trknum_obj = RSC_get_child (_rsc_object_ptr [col].info);
		real_col = TRK_cursor_offset + col;
		if (real_col < nbr_col)
		{
			track = TRK_preset_data [TRK_preset_nbr].track_nbr [real_col];
			if (player.get_track_onoff (track_type, track, false))
			{
				*string_0 = 'o';
			}
			else
			{
				*string_0 = 'x';
			}
			sprintf (string_0 + 1, INTR_base_track_2, track + 1);
		}
		else
		{
			sprintf (string_0, "   ");
		}
		RSC_set_string (trknum_obj, string_0);
		RSC_display_object (trknum_obj);
	}

	refresh_dynamic (true);
}



/*==========================================================================*/
/*      Nom: refresh_dynamic                                                */
/*      Description: Rafraichit l'affichage des donnees dynamiques.         */
/*        - force_flag: true indique qu'on doit forcer le rafraichissement. */
/*==========================================================================*/

void	PatEdTracks::refresh_dynamic (bool force_flag)
{
	display_current_pattern (force_flag);
}



/*==========================================================================*/
/*      Nom: manage                                                         */
/*      Description: Gere la composante d'interface                         */
/*      Parametres en entree:                                               */
/*        - sel_object: objet detecte par le gestionnaire d'interface       */
/*        - sel_elder: aine de sel_object                                   */
/*==========================================================================*/

void	PatEdTracks::manage (int sel_object, int sel_elder)
{
	int		ext_track_type;
	int		track_type;
	int		disp_type;
	int		pattern;
	int		track;
	int		col;
	int		pos;
	int		real_col;
	int		old_value;
	int		col_width;
	int		col_space;
	int		trknum_obj;
	int		up_obj;
	int		down_obj;
	signed int	char_xpos;
	signed int	char_ypos;
	signed int	disp_line;
	signed int	line;
	bool		active_flag;

	/* Donnnes reservees pour la selection */
	static bool	track_clicked_flag = false;
	static clock_t	click_time = 0;
	static int	click_button = 0;
	static int	first_click_line = 0;
	static int	first_click_col = 0;
	static int	last_click_line = 0;
	static int	last_click_col = 0;
	static int	last_click_pos = 0;
	
	Player &			player = Player::use_instance ();

	track_type = TRK_preset_data [TRK_preset_nbr].track_type;

	switch (sel_object)
	{
	/* Patterns */
	case	RSC_OBJ_MP_TRK_DATA:
		col_width = get_col_width ();
		col_space = (col_width + 1) * RSC_CHAR_W;
		col = (int) floor (  (double)(  INTR_mouse.x
		                              - RSC_absolute_object_pixxpos [RSC_OBJ_MP_TRK_DATA])
		                   / col_space);
		char_xpos =   (  INTR_mouse.x
		               - RSC_absolute_object_pixxpos [RSC_OBJ_MP_TRK_DATA]
		               - col * col_space)
		            / RSC_CHAR_W;
		char_ypos = (int) floor (  (double)  (  INTR_mouse.y
		                                      - RSC_absolute_object_pixypos [RSC_OBJ_MP_TRK_DATA])
		                         / RSC_CHAR_H);
		real_col = TRK_cursor_offset + col;
		real_col = MIN (real_col, TRK_preset_data [TRK_preset_nbr].nbr - 1);
		real_col = MAX (real_col, 0);
		track = TRK_preset_data [TRK_preset_nbr].track_nbr [real_col];

		/* Relachement */
		if (INTR_mouse.k == 0)
		{
			if (track_clicked_flag)
			{
				/* Clic valable */
				if (INTR_mouse.last_released >= click_time)
				{
					/* Regarde si c'etait juste un instantane */
					if (clock () - click_time <= ((long)CLOCKS_PER_SEC * INTR_CLICK_SPEED / 100))
					{
						/* Place le curseur */
						GTK_set_line_position (last_click_line);
						TRK_cursor_col = last_click_col - TRK_cursor_offset;
						TRK_cursor_pos = last_click_pos;
						check_cursor ();

						/* Travaille avec les donnees reelles */
						line = GTK_get_line_position ();
						pattern = GTK_get_current_pattern_number ();

						/* Clic droit: selection d'un effet */
						if (click_button & 2)
						{
							if (   track_type == Pattern_TYPE_FX
								 && TRK_cursor_pos < 8)
							{
								select_fx_command (pattern,
								                   player.get_line_position (),
								                   track);
							}
							else
							{
								select_track_command (pattern, track_type,
								                      player.get_line_position (),
								                      track);
							}
						}

						display_current_pattern ();
					}
				}

				/* Hop on oublie maintenant */
				track_clicked_flag = false;
			}
		}

		/* Clic milieu: piste muette/active */
		else if (RSC_mouse_key & 4)
		{
			active_flag = ! player.get_track_onoff (track_type, track, false);
			player.set_track_onoff (track_type, track, false, active_flag);
			player.set_track_onoff (track_type, track, true, active_flag);
			TRK_display_mute ();
			INTR_wait_mouse (true);
		}

		else
		{
			/* Trouve la position du curseur */
			pattern = GTK_get_current_pattern_number ();
			line = GTK_get_line_position ();
			disp_line = update_disp_line (pattern, line);
			last_click_line = disp_line + char_ypos;
			last_click_line = MIN (last_click_line, PAT_get_pattern_height (pattern));
			last_click_line = MAX (last_click_line, 0);
			last_click_col = real_col;
			disp_type = TRK_preset_data [TRK_preset_nbr].disp_type;
			ext_track_type = GTK_get_ext_track_type (track_type,
				                                      PAT_get_note_adr_pat (track_type, pattern, last_click_line, track));
			for (pos = TRK_cursor_last_pos [ext_track_type] [disp_type]; pos >= 0; pos --)
			{
				if (char_xpos >= TRK_cursor_colxpos [ext_track_type] [pos])
				{
					last_click_pos = pos;
					break;
				}
			}

			/* Verifie le clic si on en a un de memorise */
			if (track_clicked_flag)
			{
				if (INTR_mouse.last_released > click_time)
				{
					track_clicked_flag = false;
				}
			}

			/* Si on n'a pas de click memorise */
			if (! track_clicked_flag)
			{
				track_clicked_flag = true;
				first_click_col = last_click_col;
				first_click_line = last_click_line;
				click_time = INTR_mouse.last_pressed;
				click_button = RSC_mouse_key;
			}

			/* Si on clique depuis longtemps ou qu'on a bouge,
				on modifie la selection */
			if (   clock () - click_time > ((long)CLOCKS_PER_SEC * INTR_CLICK_SPEED / 100)
			    || first_click_col != last_click_col
			    || first_click_line != last_click_line)
			{
				if (_selection_ptr != NULL)
				{
					delete _selection_ptr;
					_selection_ptr = NULL;
				}

				int	left_col = MIN (first_click_col, last_click_col);
				int	nbr_col = ABS (first_click_col - last_click_col) + 1;
				int	start_line = MIN (first_click_line, last_click_line);
				int	nbr_lines = ABS (first_click_line - last_click_line) + 1;
				_selection_ptr = new PatSelection (pattern, TRK_preset_nbr,
				                                   left_col, start_line,
				                                   nbr_col, nbr_lines);

				/* On ne fait rien si la construction a echoue, c'est
					pas la peine. */
				if (_selection_ptr != NULL)
				{
					_selection_ptr->check ();
				}

				/* On bouge le pattern si la souris depasse de la zone */
				if (col < 0)
				{
					TRK_cursor_offset += col;
					check_cursor ();
					refresh ();
				}
				else if (real_col >= TRK_cursor_offset + _max_disp_col)
				{
					TRK_cursor_offset += col - _max_disp_col + 1;
					check_cursor ();
					if (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset < _max_disp_col)
					{
						RSC_display_object (RSC_OBJ_MP_TRK_DATA);
					}
					refresh ();
				}

				line = GTK_get_line_position ();
				pattern = GTK_get_current_pattern_number ();
				if (char_ypos < 0)
				{
					GTK_set_line_position (line + char_ypos);
					check_cursor ();
				}
				else if (char_ypos >= _nbr_disp_lines)
				{
					GTK_set_line_position (line + char_ypos - _nbr_disp_lines + 1);
					check_cursor ();
				}

				display_current_pattern ();
			}
		}
		break;

	/* Slider de defilement */
	case	RSC_OBJ_MP_TRK_SBAR:
	case	RSC_OBJ_MP_TRK_SBAR_SLIDER:
		SLID_gere_slider (&_track_slider, sel_object);
		old_value = TRK_cursor_offset;
		TRK_cursor_offset = _track_slider.virtual_pos;
		if (TRK_cursor_offset != old_value)
		{
			check_cursor ();
			if (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset < _max_disp_col)
			{
				RSC_display_object (RSC_OBJ_MP_TRK_DATA);
			}
			refresh ();
		}
		break;

	/* Gadgets en haut des pistes */
	default:
		for (col = 0; col < _max_disp_col; col ++)
		{
			trknum_obj = RSC_get_child (_rsc_object_ptr [col].info);
			up_obj = RSC_get_brother (trknum_obj);
			down_obj = RSC_get_brother (up_obj);
			real_col = TRK_cursor_offset + col;

			/* Numero de la piste */
			if (   sel_object == trknum_obj
			    || sel_object == _rsc_object_ptr [col].info)
			{
				/* Clic droit: change le nombre de voies du preset */
				if (RSC_mouse_key == 2)
				{
					/* Fixe le nouveau nombre de colonnes */
					old_value = TRK_preset_data [TRK_preset_nbr].nbr;
					TRK_preset_data [TRK_preset_nbr].nbr = real_col + 1;
					if (TRK_preset_data [TRK_preset_nbr].nbr != old_value)
					{
						TRK_check_presets ();
						check_cursor ();
						RSC_display_object (RSC_OBJ_MP_TRK_DATA);
						refresh ();
					}
				}

				/* Clic gauche: pop-up de selection du numero de piste */
				else
				{
					if (real_col < TRK_preset_data [TRK_preset_nbr].nbr)
					{

						/*** A faire ***/

					}
				}
				break;
			}

			/* Fleche vers le haut */
			else if (sel_object == up_obj)
			{
				if (real_col < TRK_preset_data [TRK_preset_nbr].nbr)
				{
					TRK_set_column_track (real_col, INTR_CHGTYPE_REL, INTR_inc_speed [0] [RSC_mouse_key]);
				}
			}

			/* Fleche vers le bas */
			else if (sel_object == down_obj)
			{
				if (real_col < TRK_preset_data [TRK_preset_nbr].nbr)
				{
					TRK_set_column_track (real_col, INTR_CHGTYPE_REL, -INTR_inc_speed [0] [RSC_mouse_key]);
				}
			}
		}
		break;
	}
}



int	PatEdTracks::get_width (void)
{
	return (_width);
}



int	PatEdTracks::get_height (void)
{
	return (_height);
}



void	PatEdTracks::set_width (int width)
{
	_width = width;
	RSC_set_width (RSC_OBJ_MP_TRK_BKGND, _width);
}



void	PatEdTracks::set_height (int height)
{
	_height = height;
	RSC_set_height (RSC_OBJ_MP_TRK_BKGND, _height);
}



void	PatEdTracks::get_coordinates (int &pix_x, int &pix_y)
{
	pix_x = RSC_absolute_object_pixxpos [RSC_OBJ_MP_TRK];
	pix_y = RSC_absolute_object_pixypos [RSC_OBJ_MP_TRK];
}



void	PatEdTracks::move_to (int pix_x, int pix_y)
{
	RSC_set_absolute_object_position (RSC_OBJ_MP_TRK, pix_x, pix_y);
}



/*==========================================================================*/
/*      Nom: check_cursor                                                   */
/*      Description: Verifie si le curseur est bien dans un champ de note,  */
/*                   et corrige si necessaire. Ne reaffiche pas.            */
/*==========================================================================*/

void	PatEdTracks::check_cursor (void)
{
	int		track_type;
	int		ext_track_type;
	int		pattern;
	int		line;
	int		track;
	int		last_column;
	int		real_col;
	int		height;
	void		*note_ptr;

	calc_max_disp_col ();

	/* Cherche le type etendu de la note */
	track_type = TRK_preset_data [TRK_preset_nbr].track_type;
	pattern = GTK_get_current_pattern_number ();
	line = GTK_get_line_position ();
	height = PAT_get_pattern_height (pattern);
	if (   line < 0
	    || line >= height)
	{
		line = MIN (line, height - 1);
		line = MAX (line, 0);
		GTK_set_line_position (line);
	}

	TRK_cursor_offset = MIN (TRK_cursor_offset,
	                           TRK_NBRDISPVOICES_MAXI
	                         - _max_disp_col);
	TRK_cursor_offset = MIN (TRK_cursor_offset,
	                         TRK_preset_data [TRK_preset_nbr].nbr - 1);
	TRK_cursor_offset = MAX (TRK_cursor_offset, 0);
	last_column = MIN (  TRK_preset_data [TRK_preset_nbr].nbr
	                   - TRK_cursor_offset,
	                   _max_disp_col) - 1;
	TRK_cursor_col = MIN (TRK_cursor_col, last_column);
	TRK_cursor_col = MAX (TRK_cursor_col, 0);
	real_col = TRK_cursor_offset + TRK_cursor_col;
	track = TRK_preset_data [TRK_preset_nbr].track_nbr [real_col];
	note_ptr = PAT_get_note_adr_pat (track_type, pattern, line, track);
	ext_track_type = GTK_get_ext_track_type (track_type, note_ptr);

	/* Verification */
	TRK_cursor_pos = MIN (TRK_cursor_pos,
								 TRK_cursor_last_pos [ext_track_type] [TRK_preset_data [TRK_preset_nbr].disp_type]);
	TRK_cursor_pos = MAX (TRK_cursor_pos, 0);
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::cursor_left (void)
{
	int		track_type;
	int		ext_track_type;
	int		pattern;
	int		line;
	int		track;
	void		*note_ptr;

	/* Bouge le curseur */
	TRK_cursor_pos --;
	if (TRK_cursor_pos < 0)
	{
		TRK_cursor_col --;
		if (TRK_cursor_col < 0)
		{
			if (TRK_cursor_offset > 0)
			{
				TRK_cursor_offset --;
				TRK_cursor_col ++;
			}
			else
			{
				TRK_cursor_col = MIN (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset,
											 _max_disp_col) - 1;
			}
		}

		/* Cherche le type etendu de la note */
		track_type = TRK_preset_data [TRK_preset_nbr].track_type;
		pattern = GTK_get_current_pattern_number ();
		line = GTK_get_line_position ();
		track = TRK_preset_data [TRK_preset_nbr].track_nbr [TRK_cursor_col + TRK_cursor_offset];
		note_ptr = PAT_get_note_adr_pat (track_type, pattern, line, track);
		ext_track_type = GTK_get_ext_track_type (track_type, note_ptr);

		TRK_cursor_pos = TRK_cursor_last_pos [ext_track_type] [TRK_preset_data [TRK_preset_nbr].disp_type];
	}
	INTR_pattern_editor_track_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::cursor_right (void)
{
	int		track_type;
	int		ext_track_type;
	int		pattern;
	int		line;
	int		track;
	void		*note_ptr;

	/* Cherche le type etendu de la note */
	track_type = TRK_preset_data [TRK_preset_nbr].track_type;
	pattern = GTK_get_current_pattern_number ();
	line = GTK_get_line_position ();
	track = TRK_preset_data [TRK_preset_nbr].track_nbr [TRK_cursor_col + TRK_cursor_offset];
	note_ptr = PAT_get_note_adr_pat (track_type, pattern, line, track);
	ext_track_type = GTK_get_ext_track_type (track_type, note_ptr);

	/* Bouge le curseur */
	TRK_cursor_pos ++;
	if (TRK_cursor_pos > TRK_cursor_last_pos [ext_track_type] [TRK_preset_data [TRK_preset_nbr].disp_type])
	{
		TRK_cursor_pos = 0;
		TRK_cursor_col ++;
		if (TRK_cursor_col >= MIN (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset,
		                           _max_disp_col))
		{
			if (   TRK_cursor_offset < TRK_preset_data [TRK_preset_nbr].nbr - _max_disp_col
			    && TRK_cursor_offset + TRK_cursor_col < TRK_preset_data [TRK_preset_nbr].nbr)
			{
				TRK_cursor_offset ++;
				TRK_cursor_col --;
			}
			else
			{
				TRK_cursor_col = 0;
			}
		}
	}
	INTR_pattern_editor_track_ptr->refresh ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::prev_line (void)
{
	int		position;

	const Player &	player = Player::use_instance ();

	position = GTK_get_line_position () - 1;
	if (position < 0)
	{
		position = PAT_get_current_pattern_height () - 1;
	}
	GTK_set_line_position (position);
	if (player.get_play_mode () == Player::MODE_STOP)
	{
		INTR_pattern_editor_track_ptr->refresh ();
	}
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::next_line (void)
{
	int		position;

	const Player &	player = Player::use_instance ();

	position = GTK_get_line_position () + 1;
	if (position >= PAT_get_current_pattern_height ())
	{
		position = 0;
	}
	GTK_set_line_position (position);
	if (player.get_play_mode () == Player::MODE_STOP)
	{
		INTR_pattern_editor_track_ptr->refresh ();
	}
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::prev_column (void)
{
	TRK_cursor_col --;
	if (TRK_cursor_col < 0)
	{
		TRK_cursor_col = MIN (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset,
									 _max_disp_col) - 1;
	}
	check_cursor ();
	display_current_pattern ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::next_column (void)
{
	TRK_cursor_col ++;
	if (TRK_cursor_col >= MIN (TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset,
										_max_disp_col))
	{
		TRK_cursor_col = 0;
	}
	check_cursor ();
	display_current_pattern ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::set_rel_column (int col)
{
	col = MIN (col, TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset - 1);
	col = MIN (col, _max_disp_col);
	TRK_cursor_col = MAX (col, 0);
	check_cursor ();
	display_current_pattern ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::set_abs_column (int col)
{
	col = MIN (col, TRK_preset_data [TRK_preset_nbr].nbr - 1);
	col = MAX (col, 0);
	TRK_cursor_offset = MIN (TRK_cursor_offset, col);
	TRK_cursor_offset = MAX (TRK_cursor_offset, col - _max_disp_col + 1);
	TRK_cursor_col = col - TRK_cursor_offset;
	check_cursor ();
	refresh ();
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

PatSelection	PatEdTracks::get_selection (bool &sel_flag) const
{
	sel_flag = (_selection_ptr != NULL);

	if (! sel_flag)
	{
		return (PatSelection ());
	}

	return (*_selection_ptr);
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::set_selection_top_left_intr (int col, int line)
{
	int		nbr_cols;
	int		nbr_lines;
	int		pattern;

	nbr_cols = 1;
	nbr_lines = 1;

	pattern = GTK_get_current_pattern_number ();
	if (_selection_ptr != NULL)
	{
		if (   pattern == _selection_ptr->get_pattern ()
		    && TRK_preset_nbr == _selection_ptr->get_preset ())
		{
			nbr_cols =   _selection_ptr->get_nbr_col ()
						  + _selection_ptr->get_left_col () - col;
			nbr_cols = MAX (nbr_cols, 1);
			nbr_lines =   _selection_ptr->get_nbr_lines ()
							+ _selection_ptr->get_start_line () - line;
			nbr_lines = MAX (nbr_lines, 1);
		}
	}

	set_selection_intr (col, line, nbr_cols, nbr_lines);
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::set_selection_bottom_right_intr (int col, int line)
{
	int		nbr_cols;
	int		nbr_lines;
	int		pattern;
	int		col_2;
	int		line_2;

	col_2 = col;
	col_2 = line;
	nbr_cols = 1;
	nbr_lines = 1;

	pattern = GTK_get_current_pattern_number ();
	if (_selection_ptr != NULL)
	{
		if (   pattern == _selection_ptr->get_pattern ()
		    && TRK_preset_nbr == _selection_ptr->get_preset ())
		{
			col_2 = _selection_ptr->get_left_col ();
			line_2 = _selection_ptr->get_start_line ();
			nbr_cols = col - col_2 + 1;
			nbr_cols = MAX (nbr_cols, 1);
			nbr_lines = line - line_2 + 1;
			nbr_lines = MAX (nbr_lines, 1);
		}
	}

	set_selection_intr (col_2, line_2, nbr_cols, nbr_lines);
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::set_selection_intr (int col, int line, int nbr_col, int nbr_lines)
{
	int		pattern;

	if (_selection_ptr != NULL)
	{
		delete _selection_ptr;
		_selection_ptr = NULL;
	}

	pattern = GTK_get_current_pattern_number ();
	_selection_ptr = new PatSelection (pattern, TRK_preset_nbr,
	                                   col, line, nbr_col, nbr_lines);

	display_current_pattern ();
}



/*\\\ METHODES PRIVEES \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\*/



void	PatEdTracks::calc_max_disp_col (void)
{
	int		pix_width;
	int		pix_x;
	int		pix_y;

	pix_width = (get_col_width () + 1) * RSC_CHAR_W;
	RSC_get_relative_object_position (RSC_OBJ_MP_TRK_DATA, pix_x, pix_y);
	_max_disp_col = (_width - pix_x) / pix_width;
	_max_disp_col = MAX (_max_disp_col, 1);
}



int	PatEdTracks::get_col_width (void)
{
	return (TRK_column_width [TRK_preset_data [TRK_preset_nbr].track_type]
	                         [TRK_preset_data [TRK_preset_nbr].disp_type]);
}



/*==========================================================================*/
/*      Nom: display_current_pattern                                        */
/*      Description: Affiche le pattern courant.                            */
/*==========================================================================*/

void	PatEdTracks::display_current_pattern (bool force_flag)
{
	int		pattern;
	int		curs_line;
	signed int	disp_line;
	bool		old_auto_hide_mouse_flag;
	
	static int	old_pattern = -1;
	static int	old_line = -1;

	pattern = GTK_get_current_pattern_number ();
	curs_line = GTK_get_line_position ();

	if (   pattern == old_pattern
	    && curs_line == old_line
	    && ! force_flag)
	{
		return;
	}
	old_pattern = pattern;
	old_line = curs_line;

	disp_line = update_disp_line (pattern, curs_line);

	old_auto_hide_mouse_flag = INTR_graph_ptr->get_auto_hide_mouse_flag ();
	INTR_graph_ptr->set_auto_hide_mouse_flag (false);
	display_pattern (pattern, disp_line, curs_line, TRK_cursor_col, TRK_cursor_pos);
	INTR_graph_ptr->set_auto_hide_mouse_flag (old_auto_hide_mouse_flag);
}



signed int	PatEdTracks::update_disp_line (int pattern, int curs_line)
{
	/*** Faire l'option de defilement "par page" (pattern sert ici) ***/

	_current_pattern_line = curs_line - (_nbr_disp_lines >> 1);

	return (_current_pattern_line);
}



/*==========================================================================*/
/*      Nom: display_pattern                                                */
/*      Description: Affiche un pattern.                                    */
/*      Parametres en entree:                                               */
/*        - pattern: numero du pattern a afficher.                          */
/*        - disp_line: numero de la premiere ligne a afficher. Peut etre en */
/*                     dehors du pattern (avant ou apres).                  */
/*        - curs_line: numero de la ligne ou se trouve le curseur.          */
/*        - curs_col: colonne dans laquelle se trouve me curseur.           */
/*        - curs_pos: position du curseur dans la colonne.                  */
/*==========================================================================*/

void	PatEdTracks::display_pattern (int pattern, signed int disp_line, int curs_line, int curs_col, int curs_pos)
{
	BYTE		*line_ptr;
	BYTE		*note_ptr;
	WORD		*preset_ptr;
	WORD		*preset_2_ptr;
	char		note_0 [17+1] = "12345678901234567";
	char		blanc_0 [17+1] = "12345678901234567";
	char		linenbr_0 [2+1] = "00";
	const char	linenbr_blanc_0 [2+1] = "  ";
	int		y_disp;
	int		y_disp_2;
	int		x_disp;
	int		x_disp_2;
	int		x_step;
	int		x_disp_linenbr;
	int		col_len;
	int		colonne;
	int		nbr_disp_col;
	int		nbr_tracks;
	int		pattern_height;
	int		line_count;
	int		disp_line_2;
	int		track;
	int		ext_track_type;
	int		current_color;
	int		display_type;
	int		track_type;
	int		note_size;
	int		bk_color;
	int		time_h;
	int		time_l;
	int		content;
	int		col_pos;
	int		nbr_col;
	int		field;
	int		curs_width;
	int		sel_first_line;
	int		sel_first_col;
	int		sel_last_line;
	int		sel_last_col;
	int		temp_color;
	bool		sel_flag;

	const Player &	player = Player::use_instance ();

	player.get_bar_time (time_h, time_l);
	time_l = 16 / time_l;

	/* Parametre la taille des colonnes suivant le type d'affichage */
	track_type = TRK_preset_data [TRK_preset_nbr].track_type;
	display_type = TRK_preset_data [TRK_preset_nbr].disp_type;
	col_len = TRK_column_width [track_type] [display_type];
	x_step = (col_len + 1) * RSC_CHAR_W;
	memset (blanc_0, ' ', col_len);
	blanc_0 [col_len] = 0;
	note_size = Pattern::NOTE_SIZE [track_type];

	sel_flag = false;
	if (_selection_ptr != NULL)
	{
		sel_flag = (   _selection_ptr->get_pattern () == pattern
		            && _selection_ptr->get_track_type () == track_type
		            && _selection_ptr->get_preset () == TRK_preset_nbr);
		sel_first_line = _selection_ptr->get_start_line ();
		sel_first_col = _selection_ptr->get_left_col () - TRK_cursor_offset;
		sel_last_line = sel_first_line + _selection_ptr->get_nbr_lines () - 1;
		sel_last_col = sel_first_col + _selection_ptr->get_nbr_col () - 1;
	}

	/* Coordonnees d'affichage */
	x_disp_linenbr = RSC_absolute_object_pixxpos [RSC_OBJ_MP_TRK_LINES];
	x_disp =   x_disp_linenbr
				+ ((RSC_OBJTYPE_EXTBOX *) RSC_object_ptr [RSC_OBJ_MP_TRK_LINES])->l
				+ RSC_CHAR_W;
	y_disp = RSC_absolute_object_pixypos [RSC_OBJ_MP_TRK_LINES];

	y_disp_2 = y_disp;	/* On sauve sa valeur pour la reutiliser pour le curseur */
	disp_line_2 = disp_line;	/* Idem */

	nbr_tracks = GTK_nbr_tracks [track_type];
	line_ptr = (BYTE *) PAT_get_note_adr_pat (track_type, pattern, disp_line, 0);
	pattern_height = PAT_get_pattern_height (pattern);

	preset_ptr = TRK_preset_data [TRK_preset_nbr].track_nbr + TRK_cursor_offset;	/* Pointe sur la premiere voie affichee */
	nbr_disp_col = TRK_preset_data [TRK_preset_nbr].nbr - TRK_cursor_offset;	/* Nombre de colonne a afficher */
	nbr_disp_col = MIN (_max_disp_col, nbr_disp_col);	/* Ajuste avec le nombre reellement affichable */

	/* Cache la souris si elle est sur le pattern */
	if (   INTR_mouse.y + 16 >= y_disp
		 && INTR_mouse.y < y_disp + _nbr_disp_lines * RSC_CHAR_H)
	{
		INTR_graph_ptr->hide_mouse ();
	}

	for (line_count = 0; line_count < _nbr_disp_lines; line_count ++)
	{
		/* On est dans le pattern */
		if (   disp_line >= 0
			 && disp_line < pattern_height)
		{
			preset_2_ptr = preset_ptr;
			x_disp_2 = x_disp;

			/* Couleur des caracteres */
			current_color = Graphic::COL_BLUE_STD;
			if (disp_line == curs_line)
			{
				current_color = Graphic::COL_RED;
			}

			/* Couleur de fond */
			bk_color = Graphic::COL_BLACK;
			if ((disp_line % (time_l * time_h)) == 0)
			{
				bk_color = Graphic::COL_GREY_GRAD + 1;
			}
			else if ((disp_line % time_l) == 0)
			{
				bk_color = Graphic::COL_GREY_GRAD;
			}

			sprintf (linenbr_0, INTR_base_lines_2, disp_line);
			INTR_graph_ptr->display_string (linenbr_0, x_disp_linenbr,
			                                y_disp, current_color, bk_color);

			for (colonne = 0; colonne < nbr_disp_col; colonne ++)
			{
				note_ptr = line_ptr + (*preset_2_ptr++) * note_size;
				GTK_note_to_string (note_ptr, note_0, track_type, display_type);
				note_0 [col_len] = 0;

				temp_color = bk_color;
				if (sel_flag)
				{
					if (   disp_line >= sel_first_line
					    && disp_line <= sel_last_line
					    && colonne >= sel_first_col
					    && colonne <= sel_last_col)
					{
						temp_color = Graphic::COL_MAGENTA_GRAD + 2;
					}
				}
				INTR_graph_ptr->display_string (note_0, x_disp_2, y_disp, current_color, temp_color);
				x_disp_2 += x_step;
			}
		}

		/* On est en dehors du pattern */
		else
		{
			x_disp_2 = x_disp;

			INTR_graph_ptr->display_string (linenbr_blanc_0, x_disp_linenbr,
			                                y_disp, current_color);

			for (colonne = 0; colonne < nbr_disp_col; colonne ++)
			{
				INTR_graph_ptr->display_string (blanc_0, x_disp_2, y_disp, current_color);
				x_disp_2 += x_step;
			}
		}

		disp_line ++;
		y_disp += RSC_CHAR_H;
		line_ptr += nbr_tracks * note_size;
	}

	/* Affichage du curseur */
	track = preset_ptr [curs_col];
	note_ptr = (BYTE *) PAT_get_note_adr_pat (track_type, pattern, curs_line, track);
	ext_track_type = GTK_get_ext_track_type (track_type, note_ptr);
	disp_line_2 = curs_line - disp_line_2;		/* Numero de ligne dans l'affichage */
	if (   disp_line_2 >= 0
		 && disp_line_2 < _nbr_disp_lines)
	{
		col_pos = TRK_cursor_colxpos [ext_track_type] [curs_pos];
		y_disp_2 += disp_line_2 * RSC_CHAR_H;
		x_disp_2 = x_disp + curs_col * x_step + col_pos * RSC_CHAR_W;
		field = 0;
		do
		{
			content = TRK_note_content [ext_track_type] [field].content;
			nbr_col = TRK_note_element [content].nbr_col;
			curs_width = TRK_note_element [content].curs_width;
			curs_pos -= nbr_col;
			field ++;
		}
		while (curs_pos >= 0);
		INTR_graph_ptr->invert_2_colors (x_disp_2, y_disp_2,
		                                 curs_width * RSC_CHAR_W, RSC_CHAR_H,
		                                 Graphic::COL_RED);
	}

	INTR_graph_ptr->unlock_screen ();
	INTR_graph_ptr->show_mouse ();
}



/*==========================================================================*/
/*      Nom: select_track_command                                           */
/*      Description: Selectionne une commande de piste par affichage d'un   */
/*                   menu pop-up. Les parametres ne sont pas verifies et    */
/*                   le pattern n'est pas redessine.                        */
/*      Parametres en entree:                                               */
/*        - pattern: numero du pattern a modifier.                          */
/*        - track_type: type de la piste a modifier.                        */
/*        - line: numero de la ligne sur laquelle se trouve la commande a   */
/*                modifier.                                                 */
/*        - track: numero de la piste.                                      */
/*==========================================================================*/

void	PatEdTracks::select_track_command (int pattern, int track_type, int line, int track)
{
	signed int	menu_line;
	signed long	function;
	UWORD		command;
	Popup		menu;

	/* On ne fait ca que sur les pistes qui ont des commandes */
	if (   track_type != Pattern_TYPE_SPL
	    && track_type != Pattern_TYPE_AIN
	    && track_type != Pattern_TYPE_FX)
	{
		return;
	}

	/* Construction du pop-up de selection */
	menu.add_line ("     No command", 0x0000);
	menu.add_line ("01xx Portamento up", 0x0100);
	menu.add_line ("02xx Portamento down", 0x0200);
	menu.add_line ("03xx Tone portamento", 0x0300);
	menu.add_line ("04xy Vibrato", 0x0400);
	menu.add_line ("05xx Tone portamento + Vibrato", 0x0500);
	menu.add_line ("06xy Vibrato + Tone portamento", 0x0600);
	menu.add_line ("07xy Tremolo", 0x0700);
	menu.add_line ("08xy Detune", 0x0800);
	menu.add_line ("09xx Note delay", 0x0900);
	menu.add_line ("0Axx Cut note/Key off", 0x0A00);
	menu.add_line ("0Bxx Jump to position", 0x0B00);
	menu.add_line ("0C0x Set vibrato waveform", 0x0C00);
	menu.add_line ("0Dxx Break pattern", 0x0D00);
	menu.add_line ("0E0x Set tremolo waveform", 0x0E00);
	menu.add_line ("0Fxx Set global speed/Tempo", 0x0F00);
	menu.add_line ("10xy Arpeggio", 0x1000);
	menu.add_line ("11xx Fine portamento up", 0x1100);
	menu.add_line ("12xx Fine portamento down", 0x1200);
	menu.add_line ("13xy Roll + Volume slide", 0x1300);
	menu.add_line ("14xx Linear volume slide up", 0x1400);
	menu.add_line ("15xx Linear volume slide down", 0x1500);
	menu.add_line ("16xx Logarithmic volume slide up", 0x1600);
	menu.add_line ("17xx Logarithmic volume slide down", 0x1700);
	menu.add_line ("18xx Linear volume slide up + Tone portamento", 0x1800);
	menu.add_line ("19xx Linear volume slide down + Tone portamento", 0x1900);
	menu.add_line ("1Axx Logarithmic volume slide up + Tone portamento", 0x1A00);
	menu.add_line ("1Bxx Logarithmic volume slide down + Tone portamento", 0x1B00);
	menu.add_line ("1Cxx Linear volume slide up + Vibrato", 0x1C00);
	menu.add_line ("1Dxx Linear volume slide down + Vibrato", 0x1D00);
	menu.add_line ("1Exx Logarithmic volume slide up + Vibrato", 0x1E00);
	menu.add_line ("1Fxx Logarithmic volume slide down + Vibrato", 0x1F00);
	menu.add_line ("2xxx Set linear volume", 0x2000);
	menu.add_line ("3xxx Set logarithmic volume", 0x3000);
	menu.add_line ("4xxx Set balance", 0x4000);
	menu.add_line ("5xxx Set linear master volume", 0x5000);
	menu.add_line ("6xxx Set logarithmic master volume", 0x6000);
	menu.add_line ("7xyy Roll", 0x7000);
	menu.add_line ("8zxy Roll + Volume slide + Set balance", 0x8000);
	menu.add_line ("9xxx Sample Offset", 0x9000);
	menu.add_line ("A0xx Linear master volume slide up", 0xA000);
	menu.add_line ("A1xx Linear master volume slide down", 0xA100);
	menu.add_line ("A4xx Fine linear volume slide up", 0xA400);
	menu.add_line ("A5xx Fine linear volume slide down", 0xA500);
	menu.add_line ("A6xx Fine linear master volume slide up", 0xA600);
	menu.add_line ("A7xx Fine linear master volume slide down", 0xA700);
	menu.add_line ("A8xx Set number of ticks", 0xA800);
	menu.add_line ("A9xx Set fine speed", 0xA900);
	menu.add_line ("AAxx Pattern delay", 0xAA00);
	menu.add_line ("ABxx Extra fine tone portamento", 0xAB00);
	menu.add_line ("ACxx Extra fine portamento up", 0xAC00);
	menu.add_line ("ADxx Extra fine portamento down", 0xAD00);
	menu.add_line ("AExx Left balance move", 0xAE00);
	menu.add_line ("AFxx Right balance move", 0xAF00);
	menu.add_line ("B0xy Tremor", 0xB000);
	menu.add_line ("B1xx Pattern loop", 0xB100);
	menu.add_line ("B2xx Set flags", 0xB200);
	menu.add_line ("B3xx Set Volume Envelope", 0xB300);
	menu.add_line ("B4xx Set Tone Envelope", 0xB400);
	menu.add_line ("B5xx Set Panning Envelope", 0xB500);
	menu.add_line ("B6xx Set Cut-Off Envelope", 0xB600);
	menu.add_line ("B7xx Set Reso Envelope", 0xB700);
	menu.add_line ("B9xx Demo Synchro", 0xB900);
	menu.add_line ("BAxx Fine Sample Offset", 0xBA00);
	menu.add_line ("BBxx Very Fine Sample Offset", 0xBB00);
	menu.add_line ("BCxx Increment Sample Position", 0xBC00);
	menu.add_line ("BDxx Decrement Sample Position", 0xBD00);
	menu.add_line ("BExx Auto Tempo", 0xBE00);
	menu.add_line ("BFxx Auto Period", 0xBF00);
	menu.add_line ("C1xx Set mix preset", 0xC100);
	menu.add_line ("C2xx Set linear track volume", 0xC200);
	menu.add_line ("C3xx Set Logarithmic track volume", 0xC300);
	menu.add_line ("C4xx Linear track volume slide up", 0xC400);
	menu.add_line ("C5xx Linear track volume slide down", 0xC500);
	menu.add_line ("C6xx Logarithmic track volume slide up", 0xC600);
	menu.add_line ("C7xx Logarithmic track volume slide down", 0xC700);
	menu.add_line ("C8xx Fine Logarithmic track volume slide up", 0xC800);
	menu.add_line ("C9xx Fine Logarithmic track volume slide down", 0xC900);
	menu.add_line ("CAxx Set mixing preset on input tracks", 0xCA00);
	menu.add_line ("CBxx Add mixing preset to input tracks", 0xCB00);
	menu.add_line ("CCxx Change input track volume", 0xCC00);
	menu.add_line ("CDxx Change input track panning", 0xCD00);

	/* Recuperation de la commande courante */
	command = PAT_get_track_command (pattern, track_type, line, track);
	function = command & 0xFF00;
	if (   function >= 0x2000
	    && function < 0xA000)
	{
		function &= 0xF000;
		command &= 0x0FFF;
	}
	else
	{
		command &= 0x00FF;
	}

	/* Choix de la commande dans le pop-up */
	menu_line = menu.select_radio_by_code (function);
	function = menu.manage (menu_line);
	if (function >= 0)
	{
		/* Modification de la commande */
		if (GTK_get_edit_status () != 0)
		{
			/* Memorisation des donnees pour l'undo */
			GTK_undo_list_ptr->add_action (UndoCellPatNote (track_type, pattern, line, track),
			                               "Track command");

			/* Changement de la commande */
			if (function == 0x0000)
			{
				command = 0x0000;
			}
			if (   function < 0x2000
			    || function >= 0xA000)
			{
				command &= 0x00FF;
			}
			command |= (UWORD)function;
			PAT_set_track_command (pattern, track_type, line, track, command);
		}
	}
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::select_fx_command (int pattern, int line, int track)
{
	ULWORD	command;
	signed long	function;
	int		group;
	Popup		general_menu;
	Popup		delay_menu;
	Popup		resfilt_menu;
	Popup		disto_menu;
	int		nbr_popup;
	int		cnt;
	int		cnt_2;
	signed int	menu_line;
	Popup		*menu_ptr;
	struct
	{
		Popup		*popup_ptr;
		const char	*text_0;
	}			list [] =
	{
		{ &general_menu, "Geneal" },
		{ &delay_menu, "Delay" },
		{ &resfilt_menu, "Resonant filter" },
		{ &disto_menu, "Distortion" },
		{ NULL, NULL }
	};

	/* Titre des pop-up */
	nbr_popup = 0;
	while (list [nbr_popup].popup_ptr != NULL)
	{
		list [nbr_popup].popup_ptr->add_line (list [nbr_popup].text_0,
		                                      0x10000, false, true);
		nbr_popup ++;
	}

	/* General */
	general_menu.add_line ("C100 xxyy   Add track yy of type xx to the effect", 0xC100);
	general_menu.add_line ("C200 xxyy   Remove track yy of type xx from the effect", 0xC200);
	general_menu.add_line ("C300 xxxx   Apply Fx preset xxxx to current track", 0xC300);
	general_menu.add_line ("C400        Remove FX form track", 0xC400);
	general_menu.add_line ("C500        Mute all track inputs (set volumes to 0)", 0xC500);
	general_menu.add_line ("C600 xxxx   Set FX stereo (if it's possible): 1 or 2", 0xC600);

	/* Delay */
	delay_menu.add_line ("0101        Delay On", 0x0101);
	delay_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (delay_menu, Player::FX_TIME_PARAMETER, 0x0110, "period", NULL);
	delay_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (delay_menu, Player::FX_DOUBLE_PARAMETER_2D, 0x0120, "feedback", "%");

	/* Filtre resonnant */
	resfilt_menu.add_line ("0201 xyzt   Filter On", 0x0201);
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_FREQ_PARAMETER, 0x0210, "frequency", NULL);
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_DOUBLE_PARAMETER_2D, 0x0220, "q", "");
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_INT_PARAMETER, 0x0230, "# of biquads", "order/2");
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_FREQ_PARAMETER, 0x0240, "LFO frequency", "");
	resfilt_menu.add_line ("0245 xxxxx  Set LFO waveform", 0x0245);
	resfilt_menu.add_line ("0246 xxx.xx Set LFO phase (degree)", 0x0246);
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_INT_PARAMETER, 0x0250, "LFO depth", "tone");
	resfilt_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (resfilt_menu, Player::FX_INT_PARAMETER, 0x0260, "Multiband delta-freq", "tone");

	/* Distorsion */
	disto_menu.add_line ("0301 xxxx   Distortion On", 0x0301);
	disto_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (disto_menu, Player::FX_DOUBLE_PARAMETER_3D, 0x0310, "gain", "dB");
	disto_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (disto_menu, Player::FX_DOUBLE_PARAMETER_3D, 0x0320, "pure gain attenuation", "dB");
	disto_menu.add_line ("", 0x10001, false, true);
	add_special_fx_command (disto_menu, Player::FX_DOUBLE_PARAMETER_3D, 0x0330, "harmonic threshold", "dB");

	/* Liaison vers les autres pop-up menus */
	for (cnt = 0; cnt < nbr_popup; cnt ++)
	{
		list [cnt].popup_ptr->add_line ("\n", 0x10001, false, true);
		for (cnt_2 = 0; cnt_2 < nbr_popup; cnt_2 ++)
		{
			if (cnt_2 != cnt)
			{
				list [cnt].popup_ptr->add_line (list [cnt_2].text_0, 0x20000 + cnt_2,
				                                false, false, list [cnt_2].popup_ptr);
			}
		}
	}

	/* Recuperation de la commande courante */
	command = PAT_get_fx_command (pattern, line, track);
	function = command >> 16;
	command &= 0x0000FFFFUL;
	group = (int)(function >> 8);
	switch (group)
	{
	case	0x01:
		menu_ptr = &delay_menu;
		break;
	case	0x02:
		menu_ptr = &resfilt_menu;
		break;
	default:
		menu_ptr = &general_menu;
		break;
	}

	/* Choix de la commande dans le pop-up */
	menu_line = menu_ptr->select_radio_by_code (function);
	function = menu_ptr->manage (menu_line);
	if (function >= 0)
	{
		/* Modification de la commande */
		if (GTK_get_edit_status () != 0)
		{
			/* Memorisation des donnees pour l'undo */
			GTK_undo_list_ptr->add_action (UndoCellPatNote (Pattern_TYPE_FX, pattern, line, track),
			                               "FX command");

			/* Changement de la commande */
			if (function == 0x0000)
			{
				command = 0x00000000UL;
			}
			command |= (ULWORD)function << 16;
			PAT_set_fx_command (pattern, line, track, command);
		}
	}
}



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

void	PatEdTracks::add_special_fx_command (Popup &menu, long param_type, int cmd, const char *cmd_name_0, const char *unit_0)
{
	char	text_0 [99+1];
	int	digits;
	const	char	digits_0 [2] [7+1] =
	{
		"xxx.xx", "xx.xxx"
	};

	switch (param_type)
	{
	case	Player::FX_TIME_PARAMETER:
		sprintf (text_0, "%04X xxx.xx Set %s (ms)", cmd + 0x0, cmd_name_0);
		menu.add_line (text_0, cmd + 0x0);
		sprintf (text_0, "%04X xxx.xx %s increase (ms)", cmd + 0x1, cmd_name_0);
		menu.add_line (text_0, cmd + 0x1);
		sprintf (text_0, "%04X xxx.xx %s decrease (ms)", cmd + 0x2, cmd_name_0);
		menu.add_line (text_0, cmd + 0x2);
		sprintf (text_0, "%04X xxx.xx Fine %s increase (ms)", cmd + 0x3, cmd_name_0);
		menu.add_line (text_0, cmd + 0x3);
		sprintf (text_0, "%04X xxx.xx Fine %s decrease (ms)", cmd + 0x4, cmd_name_0);
		menu.add_line (text_0, cmd + 0x4);
		sprintf (text_0, "%04X xxxyy  Drive %s with note per. & general trk cmd", cmd + 0x7, cmd_name_0);
		menu.add_line (text_0, cmd + 0x7);
		sprintf (text_0, "%04X xx.xxx Set %s (s)", cmd + 0x8, cmd_name_0);
		menu.add_line (text_0, cmd + 0x8);
		sprintf (text_0, "%04X xx.xxx %s increase (s)", cmd + 0x9, cmd_name_0);
		menu.add_line (text_0, cmd + 0x9);
		sprintf (text_0, "%04X xx.xxx %s decrease (s)", cmd + 0xA, cmd_name_0);
		menu.add_line (text_0, cmd + 0xA);
		sprintf (text_0, "%04X xx.xxx Fine %s increase (s)", cmd + 0xB, cmd_name_0);
		menu.add_line (text_0, cmd + 0xB);
		sprintf (text_0, "%04X xx.xxx Fine %s decrease (s)", cmd + 0xC, cmd_name_0);
		menu.add_line (text_0, cmd + 0xC);
		sprintf (text_0, "%04X xxxyy  Drive %s with note period", cmd + 0xD, cmd_name_0);
		menu.add_line (text_0, cmd + 0xD);
		sprintf (text_0, "%04X xxxyy  Drive %s with note period & portamento", cmd + 0xE, cmd_name_0);
		menu.add_line (text_0, cmd + 0xE);
		sprintf (text_0, "%04X xxx.yy Set %s to xxx/yy beat", cmd + 0xF, cmd_name_0);
		menu.add_line (text_0, cmd + 0xF);
		break;

	case	Player::FX_FREQ_PARAMETER:
		sprintf (text_0, "%04X xxxxx  Set %s (Hz)", cmd + 0x0, cmd_name_0);
		menu.add_line (text_0, cmd + 0x0);
		sprintf (text_0, "%04X xxxxx  %s increase", cmd + 0x1, cmd_name_0);
		menu.add_line (text_0, cmd + 0x1);
		sprintf (text_0, "%04X xxxxx  %s decrease", cmd + 0x2, cmd_name_0);
		menu.add_line (text_0, cmd + 0x2);
		sprintf (text_0, "%04X xxxxx  Fine %s increase", cmd + 0x3, cmd_name_0);
		menu.add_line (text_0, cmd + 0x3);
		sprintf (text_0, "%04X xxxxx  Fine %s decrease", cmd + 0x4, cmd_name_0);
		menu.add_line (text_0, cmd + 0x4);
		sprintf (text_0, "%04X xxxyy  Drive %s with note freq. & general trk cmd", cmd + 0x7, cmd_name_0);
		menu.add_line (text_0, cmd + 0x7);
		sprintf (text_0, "%04X xx.xxx Set %s (mHz)", cmd + 0x8, cmd_name_0);
		menu.add_line (text_0, cmd + 0x8);
		sprintf (text_0, "%04X xxxyy  Drive %s with note frequency", cmd + 0xD, cmd_name_0);
		menu.add_line (text_0, cmd + 0xD);
		sprintf (text_0, "%04X xxxyy  Drive %s with note freq. & portamento", cmd + 0xE, cmd_name_0);
		menu.add_line (text_0, cmd + 0xE);
		sprintf (text_0, "%04X xxx.yy Set %s to xxx/yy beat", cmd + 0xF, cmd_name_0);
		menu.add_line (text_0, cmd + 0xF);
		break;

	case	Player::FX_DOUBLE_PARAMETER_2D:
	case	Player::FX_DOUBLE_PARAMETER_3D:
		digits = 0;
		if (param_type == Player::FX_DOUBLE_PARAMETER_3D)
		{
			digits = 1;
		}
		sprintf (text_0, "%04X %s Set %s (%s)", cmd + 0x0, digits_0 [digits], cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x0);
		sprintf (text_0, "%04X %s %s increase (%s)", cmd + 0x1, digits_0 [digits], cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x1);
		sprintf (text_0, "%04X %s %s decrease (%s)", cmd + 0x2, digits_0 [digits], cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x2);
		sprintf (text_0, "%04X %s Fine %s increase (%s)", cmd + 0x3, digits_0 [digits], cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x3);
		sprintf (text_0, "%04X %s Fine %s decrease (%s)", cmd + 0x4, digits_0 [digits], cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x4);
		break;
	
	case	Player::FX_INT_PARAMETER:
		sprintf (text_0, "%04X xxxxx  Set %s (%s)", cmd + 0x0, cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x0);
		sprintf (text_0, "%04X xxxxx  %s increase (%s)", cmd + 0x1, cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x1);
		sprintf (text_0, "%04X xxxxx  %s decrease (%s)", cmd + 0x2, cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x2);
		sprintf (text_0, "%04X xxxxx  Fine %s increase (%s)", cmd + 0x3, cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x3);
		sprintf (text_0, "%04X xxxxx  Fine %s decrease (%s)", cmd + 0x4, cmd_name_0, unit_0);
		menu.add_line (text_0, cmd + 0x4);
		break;
	}
}



/****************************************************************************/
/*                                                                          */
/*                                                                          */
/*                                                                          */
/****************************************************************************/



/*==========================================================================*/
/*      Nom:                                                                */
/*      Description:                                                        */
/*      Parametres en entree:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*      Retour:                                                             */
/*==========================================================================*/

