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

        GRAOUMF TRACKER 2
        Author: Laurent de Soras, 1996-2016

--- Legal stuff ---

This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://sam.zoy.org/wtfpl/COPYING for more details.

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



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

#include	<stdio.h>
#include	<string.h>

#include	"archi.h"
#include	"base.h"
#include	"base_ct.h"
#include	"gt_limit.h"
#include	"gtracker.h"
#include	"List.h"
#include	"log.h"
#include	"Pattern.h"
#include	"PatternTools.h"
#include	"player.h"
#include	"song.h"
#include	"tracks.h"
#include	"UndoCell.h"
#include	"UndoCellList.h"
#include	"UndoCellPatClear.h"
#include	"UndoCellPatMove.h"
#include	"UndoCellPatPaste.h"



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

const char	*const PatternTools::SCOPE_NAME_0 [PatternTools_NBR_SCOPES] =
{
	"Track", "Pattern", "Preset"
};



/*\\\ 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:                                               */
/*      Parametres en sortie:                                               */
/*      Parametres en entree/sortie:                                        */
/*==========================================================================*/

PatternTools::PatternTools (void)
{
	const PatternTools_BLOCK_INFO	init_selection =
	{
		0,						/* Pattern */
		{ 0, 0, 0, 0 },	/* Track */
		{						/* Valid */
			{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
			{ 0xFF, 0xFF, 0xFF },
			{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
			{ 0xFF, 0xFF, 0xFF, 0xFF }
		},
		{
			{					/* Mask on flag */
				{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF, 0xFF }
			},
			{					/* Replace by flag */
				{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
				{ 0xFF, 0xFF, 0xFF, 0xFF }
			}
		},
		{
			{					/* Mask on value */
				{ 0x00, 0x00, 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00, 0x00 }
			},
			{					/* Replace by value */
				{ 0x00, 0x00, 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
				{ 0x00, 0x00, 0x00, 0x00 }
			}
		},
		0,						/* Start */
		0						/* End */
	};

	_selection = init_selection;
	_scope = PatternTools_SCOPE_TRACK;
	_all_track_type_flag = false;
	_song_flag = false;
	_tracks_on_flag = false;
	_transp_flag = false;
	_use_mask_flag = false;
}



PatternTools::PatternTools (const PatternTools &other)
{
	memcpy (this, &other, sizeof (*this));
}



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

PatternTools::~PatternTools (void)
{
	/* Ne fait rien */
}



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

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

	return (0);
}



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

void	PatternTools::self_display (void) const
{

	/*** A faire ***/

}



/*==========================================================================*/
/*      Nom: operator =                                                     */
/*      Description: Redefinition de l'operateur =                          */
/*      Parametres en entree:                                               */
/*        - other: objet a copier dans this.                                */
/*==========================================================================*/

void	PatternTools::operator = (const PatternTools &other)
{
	if (this == &other)
	{
		return;
	}

	memcpy (this, &other, sizeof (*this));
}



/****************************************************************************/
/*                                                                          */
/*      ETATS                                                               */
/*                                                                          */
/****************************************************************************/



void	PatternTools::check_block (void)
{
	int		nbr_lines;

	nbr_lines = PAT_get_pattern_height (_selection.pattern);
	_selection.end = MIN (_selection.end, nbr_lines - 1);
	_selection.end = MAX (_selection.end, 0);
	_selection.start = MIN (_selection.start, _selection.end);
	_selection.start = MAX (_selection.start, 0);
}



void	PatternTools::set_block_start (int pattern, int line, int track_type, int track)
{
	int		nbr_lines;

	nbr_lines = PAT_get_pattern_height (pattern);
	line = MIN (line, nbr_lines - 1);
	line = MAX (line, 0);
	_selection.start = line;
	_selection.end = MIN (_selection.end, nbr_lines - 1);
	_selection.end = MAX (_selection.end, line);
	_selection.pattern = pattern;
	_selection.track [track_type] = track;
}



void	PatternTools::set_block_end (int pattern, int line, int track_type, int track)
{
	int		nbr_lines;

	nbr_lines = PAT_get_pattern_height (pattern);
	line = MIN (line, nbr_lines - 1);
	line = MAX (line, 0);
	_selection.end = line;
	_selection.start = MIN (_selection.start, line);
	_selection.pattern = pattern;
	_selection.track [track_type] = track;
}



void	PatternTools::set_block_single_line (int pattern, int line, int track_type, int track)
{
	set_block_start (pattern, line, track_type, track);
	set_block_end (pattern, line, track_type, track);
}



void	PatternTools::set_scope (int scope)
{
	_scope = scope;
}



void	PatternTools::set_all_track_type_flag (bool flag)
{
	_all_track_type_flag = (flag != false);
}



void	PatternTools::set_song_flag (bool flag)
{
	_song_flag = (flag != false);
}



void	PatternTools::set_tracks_on_flag (bool flag)
{
	_tracks_on_flag = (flag != false);
}



void	PatternTools::set_transp_flag (bool flag)
{
	_transp_flag = (flag != false);
}



void	PatternTools::set_use_mask_flag (bool flag)
{
	_use_mask_flag = (flag != false);
}



/*==========================================================================*/
/*      Nom: set_filter_flag                                                */
/*      Description: Fixe le flag de filtre pour une section d'un type de   */
/*                   piste.                                                 */
/*      Parametres en entree:                                               */
/*        - track_type: type de piste.                                      */
/*        - pos: offset de la section dans la note.                         */
/*        - len: longueur de la section dans la note.                       */
/*        - flag: true si le filtre est actif, false si la section est      */
/*                desactivee.                                               */
/*==========================================================================*/

void	PatternTools::set_filter_flag (int track_type, int pos, int len, bool flag)
{
	UBYTE		fill_value;

	fill_value = 0x00;
	if (flag)
	{
		fill_value = 0xFF;
	}
	memset (& _selection.valid [track_type] [pos], fill_value, len);
}



/*==========================================================================*/
/*      Nom: set_mask_flag                                                  */
/*      Description: Fixe le flag de masque pour une section d'un type de   */
/*                   piste.                                                 */
/*      Parametres en entree:                                               */
/*        - track_type: type de piste.                                      */
/*        - pos: offset de la section dans la note.                         */
/*        - pos_2: numero du quartet conserne.                              */
/*        - len: longueur de la section dans la note (en quartets).         */
/*        - mask: numero du masque (0 ou 1).                                */
/*        - flag: true si le masque est actif, false si la section est      */
/*                desactivee.                                               */
/*==========================================================================*/

void	PatternTools::set_mask_flag (int track_type, int pos, int pos_2, int len, int mask, bool flag)
{
	pos_2 = (pos_2 << 2) & 0x04;

	for ( ; len > 0; len --)
	{
		if (flag)
		{
			_selection.mask_flag [mask] [track_type] [pos] |= (UBYTE) (0x0F << pos_2);
		}
		else
		{
			_selection.mask_flag [mask] [track_type] [pos] &= (UBYTE) (0xF0 >> pos_2);
		}
		pos_2 += 4;
		if (pos_2 > 4)
		{
			pos_2 = 0;
		}
		else
		{
			pos ++;
		}
	}
}



/*==========================================================================*/
/*      Nom: set_mask_value                                                 */
/*      Description: Fixe la valeur de masque pour une section d'un type de */
/*                   piste.                                                 */
/*      Parametres en entree:                                               */
/*        - track_type: type de piste.                                      */
/*        - pos: offset de la section dans la note.                         */
/*        - pos_2: numero du quartet conserne.                              */
/*        - mask: numero du masque (0 ou 1).                                */
/*        - value: quartet a placer.                                        */
/*==========================================================================*/

void	PatternTools::set_mask_value (int track_type, int pos, int pos_2, int mask, int value)
{
	pos_2 = (pos_2 << 2) & 0x04;
	_selection.mask_value [mask] [track_type] [pos] &= (UBYTE) (0xF0 >> pos_2);
	_selection.mask_value [mask] [track_type] [pos] |= (UBYTE) (value << pos_2);
}



PatternTools_BLOCK_INFO	PatternTools::get_current_selection (void)
{
	return (_selection);
}



int	PatternTools::get_block_height (void)
{
	return (_selection.end - _selection.start + 1);
}



int	PatternTools::get_scope (void)
{
	return (_scope);
}



bool	PatternTools::get_all_track_type_flag (void)
{
	return (_all_track_type_flag);
}



bool	PatternTools::get_song_flag (void)
{
	return (_song_flag);
}



bool	PatternTools::get_tracks_on_flag (void)
{
	return (_tracks_on_flag);
}



bool	PatternTools::get_transp_flag (void)
{
	return (_transp_flag);
}



bool	PatternTools::get_use_mask_flag (void)
{
	return (_use_mask_flag);
}



bool	PatternTools::get_full_clipboard_flag (void)
{

	/*** A faire ***/

	return (false);
}



/*==========================================================================*/
/*      Nom: get_filter_flag                                                */
/*      Description: Donne le flag de filtre pour une section d'un type de  */
/*                   piste.                                                 */
/*      Parametres en entree:                                               */
/*        - track_type: type de piste.                                      */
/*        - pos: offset de la section dans la note.                         */
/*        - len: longueur de la section dans la note.                       */
/*      Retour: true si le filtre est actif, false si la section est        */
/*              desactivee.                                                 */
/*==========================================================================*/

bool	PatternTools::get_filter_flag (int track_type, int pos, int len)
{
	int		count;

	for (count = 0; count < len; count ++)
	{
		if (_selection.valid [track_type] [pos + count] != 0)
		{
			return (true);
			break;
		}
	}

	return (false);
}



/****************************************************************************/
/*                                                                          */
/*      ACTIONS                                                             */
/*                                                                          */
/****************************************************************************/



signed int	PatternTools::copy_block (void)
{

	/*** A faire ***/

	return (0);
}



signed int	PatternTools::paste_block (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{

	/*** A faire ***/

	return (0);
}



signed int	PatternTools::insert_block (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{

	/*** A faire ***/

	return (0);
}




signed int	PatternTools::paste_selection (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{
	int		pos;
	int		pat;
	List		undo_list;
	UndoCell	*undo_ptr;

	if (_song_flag)
	{
		for (pos = 0; pos < SONG_get_song_length (); pos ++)
		{
			pat = SONG_get_pattern_number (pos);
			paste_selection_1_pat (pat, line, track_type, track, &undo_ptr);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}
	}

	else
	{
		paste_selection_1_pat (pattern, line, track_type, track, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::insert_selection (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{
	int		pos;
	int		pat;
	List		undo_list;
	UndoCell	*undo_ptr;

	if (_song_flag)
	{
		for (pos = 0; pos < SONG_get_song_length (); pos ++)
		{
			pat = SONG_get_pattern_number (pos);
			insert_selection_1_pat (pat, line, track_type, track, &undo_ptr);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}
	}

	else
	{
		insert_selection_1_pat (pattern, line, track_type, track, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::clear_selection (int track_type, UndoCell **undo_ptr_ptr)
{
	int		pos;
	int		pat;
	List		undo_list;
	UndoCell	*undo_ptr;

	if (_song_flag)
	{
		for (pos = 0; pos < SONG_get_song_length (); pos ++)
		{
			pat = SONG_get_pattern_number (pos);
			clear_selection_1_pat (pat, track_type, &undo_ptr);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}
	}

	else
	{
		clear_selection_1_pat (_selection.pattern, track_type, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::delete_selection (int track_type, UndoCell **undo_ptr_ptr)
{
	int		pos;
	int		pat;
	List		undo_list;
	UndoCell	*undo_ptr;

	if (_song_flag)
	{
		for (pos = 0; pos < SONG_get_song_length (); pos ++)
		{
			pat = SONG_get_pattern_number (pos);
			delete_selection_1_pat (pat, track_type, &undo_ptr);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}
	}

	else
	{
		delete_selection_1_pat (_selection.pattern, track_type, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::transpose_selection (int track_type, UndoCell **undo_ptr_ptr, int transpose)
{
	int		pos;
	int		pat;
	List		undo_list;
	UndoCell	*undo_ptr;

	if (_song_flag)
	{
		for (pos = 0; pos < SONG_get_song_length (); pos ++)
		{
			pat = SONG_get_pattern_number (pos);
			transpose_selection_1_pat (pat, track_type, &undo_ptr, transpose);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}
	}

	else
	{
		transpose_selection_1_pat (_selection.pattern, track_type, &undo_ptr, transpose);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::swap_selection (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{
	List		undo_list;

	/*** A faire ***/

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::filter_selection (UndoCell **undo_ptr_ptr)
{
	List		undo_list;

	/*** A faire ***/

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



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



int	PatternTools::clip_block (int pattern, int line)
{
	int		length;
	int		nbr_lines;

	nbr_lines = PAT_get_pattern_height (pattern);
	length = get_block_height ();
	length = MIN (length, nbr_lines - line);
	length = MAX (length, 0);

	return (length);
}



void	PatternTools::select_tracks (bool track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI], int track_type)
{
	int		type;
	int		cur_track;
	int		nbr_col;
	int		col;
	bool		flag;
	bool		flag_2;

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

	for (type = 0; type < Pattern_NBR_TYPES; type ++)
	{
		flag = false;

		/* Si on est sur le bon type de piste, c'est potentiellement ok. */
		if (   _all_track_type_flag
		    || type == track_type)
		{
			flag = true;
		}

		/* Si on est en mode Preset et que le type de piste n'est pas celui du
		   preset courant, c'est foutu. */
		if (   _scope == PatternTools_SCOPE_PRESET
		    && TRK_preset_data [TRK_preset_nbr].track_type != track_type)
		{
			flag = false;
		}

		for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
		{
			flag_2 = flag;

			/* Si on est en mode Tracks On et que la piste ne l'est pas,
			   c'est foutu */
			if (   _tracks_on_flag
			    && ! player.get_track_onoff (type, cur_track, false))
			{
				flag_2 = false;
			}

			/* Si on est en mode Track et que la piste n'est pas la piste
			   specifiee, ou qu'on est pas dans le bon type de piste, c'est
			   foutu. */
			else if (_scope == PatternTools_SCOPE_TRACK)
			{
				if (   cur_track != _selection.track [type]
				    || type != track_type)
				{
					flag_2 = false;
				}
			}

			/* Si on est en mode preset, on regarde si la piste est dans
			   le preset. Si elle n'y est pas, c'est foutu. */
			else if (   _scope == PatternTools_SCOPE_PRESET
			         && TRK_preset_data [TRK_preset_nbr].track_type == track_type)
			{
				nbr_col = TRK_preset_data [TRK_preset_nbr].nbr;
				for (col = 0; col < nbr_col; col ++)
				{
					if (TRK_preset_data [TRK_preset_nbr].track_nbr [col] == cur_track)
					{
						break;
					}
				}

				if (col >= nbr_col)
				{
					flag_2 = false;
				}
			}

			track_selection [type] [cur_track] = flag_2;
		}
	}
}



void	PatternTools::copy_note (int dest_pat, int track_type, int dest_track, int dest_line, int src_pat, int src_track, int src_line)
{
	int		count;
	int		mask;
	bool		take_source_flag;
	bool		copy_flag;
	const UBYTE	*src_ptr;
	UBYTE		*dest_ptr;
	UBYTE		note [8];

	src_ptr = (const UBYTE *) PAT_get_note_adr_pat (track_type, src_pat, src_line, src_track);
	dest_ptr = (UBYTE *) PAT_get_note_adr_pat (track_type, dest_pat, dest_line, dest_track);

	/* Copie normale (pour optimiser un peu) */
	if (   ! _transp_flag
	    && ! _use_mask_flag)
	{
		for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
		{
			if (_selection.valid [track_type] [count] != 0)
			{
				*dest_ptr = *src_ptr;
			}
			dest_ptr ++;
			src_ptr ++;
		}

		return;
	}

	if (_use_mask_flag)
	{
		/* Regarde en fonction du masque source si on doit prendre la note */
		take_source_flag = true;
		for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
		{
			mask = ~_selection.mask_flag [0] [track_type] [count];
			if (   (mask & _selection.mask_value [0] [track_type] [count])
			    != (mask & src_ptr [count]))
			{
				take_source_flag = false;
			}
		}
		if (take_source_flag)
		{
			memcpy (note, src_ptr, Pattern::NOTE_SIZE [track_type]);

			/* Remplacement des notes */
			for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
			{
				mask = _selection.mask_flag [1] [track_type] [count];
				note [count] =   ( mask & note [count])
									| (~mask & _selection.mask_value [1] [track_type] [count]);
			}
		}
		else
		{
			memset (note, 0, Pattern::NOTE_SIZE [track_type]);
		}
	}

	else
	{
		memcpy (note, src_ptr, Pattern::NOTE_SIZE [track_type]);
	}

	/* Teste si on doit copier ou pas la note */
	if (_transp_flag)
	{
		copy_flag = false;
		for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
		{
			if (   _selection.valid [track_type] [count] != 0
			    && note [count] != 0)
			{
				copy_flag = true;
			}
		}
		
		if (! copy_flag)
		{
			return;
		}
	}

	/* Copie de la note */
	for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
	{
		if (_selection.valid [track_type] [count] != 0)
		{
			*dest_ptr = note [count];
		}
		dest_ptr ++;
	}
}



void	PatternTools::clear_note (int track_type, int pattern, int track, int line)
{
	int		count;
	int		mask;
	UBYTE	*note_ptr;

	note_ptr = (UBYTE *) PAT_get_note_adr_pat (track_type, pattern, line, track);

	if (_use_mask_flag)
	{
		/* Regarde en fonction du masque source si on doit effacer la note */
		for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
		{
			mask = ~_selection.mask_flag [0] [track_type] [count];
			if (   (mask & _selection.mask_value [0] [track_type] [count])
			    != (mask & note_ptr [count]))
			{
				return;
			}
		}
	}

	/* Destruction de la note */
	for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
	{
		if (_selection.valid [track_type] [count] != 0)
		{
			*note_ptr = 0;
		}
		note_ptr ++;
	}
}



void	PatternTools::transpose_note (int track_type, int pattern, int track, int line, int transpose)
{
	int		count;
	int		mask;
	UBYTE	*note_ptr;

	// Works only on SPL tracks
	if (track_type == Pattern_TYPE_SPL)
	{
		note_ptr = (UBYTE *) PAT_get_note_adr_pat (track_type, pattern, line, track);

		if (_use_mask_flag)
		{
			/* Regarde en fonction du masque source si on doit transposer la note */
			for (count = 0; count < Pattern::NOTE_SIZE [track_type]; count ++)
			{
				mask = ~_selection.mask_flag [0] [track_type] [count];
				if (   (mask & _selection.mask_value [0] [track_type] [count])
					 != (mask & note_ptr [count]))
				{
					return;
				}
			}
		}

		/* Transposition de la note */
		if (_selection.valid [track_type] [0] != 0)
		{
			int				note = *note_ptr;
			if (note != 0)
			{
				note += transpose;
				*note_ptr = UBYTE (MAX (MIN (note, GTK_NBRNOTES_MAXI - 1), 1));
			}
		}
	}
}



signed int	PatternTools::paste_selection_1_pat (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{
	int		type;
	int		length;
	int		cur_line;
	int		first_line;
	int		last_line;
	signed int	direction;
	int		cur_track;
	int		dest_track;
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;

	/* Clipping */
	length = clip_block (pattern, line);

	if (length > 0)
	{
		/* Selection des pistes */
		select_tracks (track_selection, track_type);

		/* A l'envers */
		if (line > _selection.start)
		{
			direction = -1;
			first_line = length - 1;
			last_line = 0;
		}

		/* A l'endroit */
		else
		{
			direction = 1;
			first_line = 0;
			last_line = length - 1;
		}

		dest_track = track;
		last_line += direction;

		/* Sauvegarde des informations pour l'undo */
		if (undo_ptr_ptr != NULL)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						if (_scope != PatternTools_SCOPE_TRACK)
						{
							dest_track = cur_track;
						}
						undo_list.add (&UndoCellPatPaste (type, pattern, line, dest_track, length));
					}
				}
			}
		}

		/* Copie */
		for (cur_line = first_line; cur_line != last_line; cur_line += direction)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						if (_scope != PatternTools_SCOPE_TRACK)
						{
							dest_track = cur_track;
						}

						copy_note (pattern, type, dest_track, line + cur_line,
						           _selection.pattern, cur_track,
						           _selection.start + cur_line);
					}
				}
			}
		}
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::insert_selection_1_pat (int pattern, int line, int track_type, int track, UndoCell **undo_ptr_ptr)
{
	int		type;
	int		cur_track;
	int		new_line;
	int		length;
	int		last_pattern_line;
	PatternTools	mover (*this);
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;
	UndoCell	*undo_ptr;

	/* Clipping */
	length = clip_block (pattern, line);
	if (length > 0)
	{
		/* Descend la fin du pattern de la longueur de la selection */
		last_pattern_line = PAT_get_pattern_height (pattern) - 1;
		mover.set_block_start (pattern, line, track_type, track);
		mover.set_block_end (pattern, last_pattern_line, track_type, track);
		mover.select_tracks (track_selection, track_type);
		new_line = line + get_block_height ();
		for (type = 0; type < Pattern_NBR_TYPES; type ++)
		{
			for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
			{
				if (track_selection [type] [cur_track])
				{
					undo_list.add (&UndoCellPatClear (type, pattern, PAT_get_pattern_height (pattern) - length, cur_track, length));
				}
			}
		}
		mover.move_selection_1_pat (pattern, new_line, track_type, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;

		/* Efface l'espace degage */
		mover.set_block_end (pattern, new_line - 1, track_type, track);
		mover.clear_selection_1_pat (pattern, track_type, NULL);

		/* Copie la selection dessus */
		paste_selection_1_pat (pattern, line, track_type, track, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::clear_selection_1_pat (int pattern, int track_type, UndoCell **undo_ptr_ptr)
{
	int		type;
	int		length;
	int		cur_line;
	int		cur_track;
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;

	/* Clipping */
	length = clip_block (pattern, _selection.start);
	if (length > 0)
	{
		/* Selection des pistes */
		select_tracks (track_selection, track_type);

		/* Sauvegarde des informations pour l'undo */
		if (undo_ptr_ptr != NULL)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						undo_list.add (&UndoCellPatClear (type, pattern, _selection.start, cur_track, length));
					}
				}
			}
		}

		/* Effacement */
		for (cur_line = 0; cur_line < length; cur_line ++)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						clear_note (type, pattern, cur_track, _selection.start + cur_line);
					}
				}
			}
		}
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::delete_selection_1_pat (int pattern, int track_type, UndoCell **undo_ptr_ptr)
{
	int		type;
	int		new_line;
	int		length;
	int		last_pattern_line;
	int		cur_track;
	PatternTools	mover (*this);
	PatternTools	clear (*this);
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;
	UndoCell	*undo_ptr;

	/* Clipping */
	length = clip_block (pattern, _selection.start);
	if (length > 0)
	{
		/* Remonte la fin du pattern de la longueur de la selection */
		new_line = _selection.end + 1;
		last_pattern_line = PAT_get_pattern_height (pattern) - 1;
		if (new_line <= last_pattern_line)
		{
			mover.set_block_start (pattern, new_line, track_type, _selection.track [track_type]);
			mover.set_block_end (pattern, last_pattern_line, track_type, _selection.track [track_type]);
			mover.select_tracks (track_selection, track_type);
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						undo_list.add (&UndoCellPatClear (type, pattern, _selection.start, cur_track, length));
					}
				}
			}
			mover.move_selection_1_pat (pattern, _selection.start, track_type, &undo_ptr);
			undo_list.add (undo_ptr);
			delete undo_ptr;
		}

		/* Efface la fin du pattern de la longueur de la selection */
		clear.set_block_start (pattern, last_pattern_line + 1 - length, track_type, _selection.track [track_type]);
		clear.set_block_end (pattern, last_pattern_line, track_type, _selection.track [track_type]);
		clear.clear_selection_1_pat (pattern, track_type, &undo_ptr);
		undo_list.add (undo_ptr);
		delete undo_ptr;
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::transpose_selection_1_pat (int pattern, int track_type, UndoCell **undo_ptr_ptr, int transpose)
{
	int		type;
	int		length;
	int		cur_line;
	int		cur_track;
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;

	/* Clipping */
	length = clip_block (pattern, _selection.start);
	if (length > 0)
	{
		/* Selection des pistes */
		select_tracks (track_selection, track_type);

		/* Sauvegarde des informations pour l'undo */
		if (undo_ptr_ptr != NULL)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						// UndoCellPatClear is not optimal here but works.
						undo_list.add (&UndoCellPatClear (type, pattern, _selection.start, cur_track, length));
					}
				}
			}
		}

		/* Effacement */
		for (cur_line = 0; cur_line < length; cur_line ++)
		{
			for (type = 0; type < Pattern_NBR_TYPES; type ++)
			{
				for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
				{
					if (track_selection [type] [cur_track])
					{
						transpose_note (type, pattern, cur_track, _selection.start + cur_line, transpose);
					}
				}
			}
		}
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



signed int	PatternTools::move_selection_1_pat (int pattern, int line, int track_type, UndoCell **undo_ptr_ptr)
{
	int		type;
	int		length;
	int		cur_line;
	int		first_line;
	int		last_line;
	signed int	direction;
	int		cur_track;
	long		note_size;
	bool		track_selection [Pattern_NBR_TYPES] [GTK_NBRTRACKS_MAXI];
	List		undo_list;

	/* Clipping */
	length = clip_block (pattern, line);

	if (length > 0)
	{
		select_tracks (track_selection, track_type);

		/* A l'envers */
		if (line > _selection.start)
		{
			direction = -1;
			first_line = length - 1;
			last_line = 0;
		}

		/* A l'endroit */
		else
		{
			direction = 1;
			first_line = 0;
			last_line = length - 1;
		}

		last_line += direction;

		for (type = 0; type < Pattern_NBR_TYPES; type ++)
		{
			note_size = Pattern::NOTE_SIZE [type];
			for (cur_track = 0; cur_track < GTK_nbr_tracks [type]; cur_track ++)
			{
				if (track_selection [type] [cur_track])
				{
					/* Sauvegarde des informations pour l'undo */
					if (undo_ptr_ptr != NULL)
					{
						undo_list.add (&UndoCellPatMove (type, pattern,
						                                 _selection.start, cur_track, 
						                                 length, 1, line, cur_track));
					}

					/* Deplacement */
					for (cur_line = first_line; cur_line != last_line; cur_line += direction)
					{
						memmove (PAT_get_note_adr_pat (type, pattern, line + cur_line, cur_track),
						         PAT_get_note_adr_pat (type, pattern, _selection.start + cur_line, cur_track),
						         note_size);
					}
				}
			}
		}
	}

	if (undo_ptr_ptr != NULL)
	{
		*undo_ptr_ptr = new UndoCellList (undo_list);
	}

	return (0);
}



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



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