/*
**	PatternWin.c
**
**	Copyright (C) 1994,95,96 Bernardo Innocenti
**
**	Parts of the code have been inspired by ScrollerWindow 0.3 demo
**	Copyright © 1994 Christoph Feck, TowerSystems.
**
**	Pattern editor handling functions.
**
**
**	Diagram of object interconnections:
**
**
**	           ScrollButtonClass objects
**	+----------+ +---------+ +----------+ +----------+
**	| UpButton | | DnButton| | SxButton | | DxButton |
**	+----------+ +---------+ +----------+ +----------+
**	 | GA_ID =   | GA_ID =    | GA_ID =    | GA_ID =
**	 | PATTA_Up  | PATTA_Down | PATTA_Left | PATTA_Right
**	 |           |            |            |
**	 |  +--------+            |            |
**	 |  |  +------------------+            |
**	 |  |  |  +----------------------------+
**	 |  |  |  |      propgclass  object                       icclass object
**	 |  |  |  |      +----------------+                     +-----------------+
**	 |  |  |  |   +--|     HSlider    |<--------------------| EditorToHSlider |
**	 |  |  |  |   |  +----------------+  PATTA_LeftTrack =  +-----------------+
**	 |  |  |  |   | PGA_Top =            PGA_Top                    ^
**	 |  |  |  |   | PATTA_TopLine        PATTA_DisplayTracks =      |
**	 |  |  |  |   |                      PGA_Visible                |
**	 |  |  |  |   |                   +-----------------------------+
**	 V  V  V  V   V                   |
**	+---------------+            ************
**	|  PattEditGad  |----------->*   Model  *----------------> IDCMP_IDCMPUPDATE
**	+---------------+            ************ PATTA_CursLine   to PatternWin
**	  ^                               |       PATTA_CursTrack
**	  | propgclass object             |
**	  | PGA_Top = PATTA_TopLine       V icclass object
**	+----------------+       +-----------------+
**	|    VSlider     |<------| EditorToVSlider | PATTA_TopLine      = PGA_Top
**	+----------------+       +-----------------+ PATTA_DisplayLines = PGA_Visible
*/


#include <intuition/intuition.h>
#include <intuition/icclass.h>
#include <intuition/gadgetclass.h>
#include <intuition/imageclass.h>
#include <graphics/gfxbase.h>
#include <libraries/gadtools.h>
#include <libraries/iffparse.h>
#include <libraries/patteditclass.h>

#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#include <proto/layers.h>
#include <proto/utility.h>
#include <proto/dos.h>
#include <proto/iffparse.h>
#include <proto/diskfont.h>
#include <proto/xmodule.h>

#include "XModulePriv.h"
#include "Gui.h"
#include "CustomClasses.h"


/* Gadget IDs */
enum {
	GD_PattEdit,
	GD_UpButton,
	GD_DnButton,
	GD_VSlider,
	GD_SxButton,
	GD_DxButton,
	GD_HSlider,

	Pattern_CNT
};


/* Local function prototypes */

static struct Gadget	*CreatePattEdit	(void);
static struct Gadget	*LayoutPatternWin (struct WinUserData *wud);

static void PatternPostOpen		(struct WinUserData *wud);
static void PatternWindowCleanup(struct WinUserData *wud);

static void RenderPatternWindow	(struct WinUserData *wud);
static void HandlePatternIDCMP	(struct WinUserData *wud);
static void PatternLoad			(STRPTR name, ULONG num, ULONG count);
static LONG PatternSave			(STRPTR name, struct Pattern *patt);

static void PatternMiOpen		(void);
static void PatternMiSave		(void);
static void PatternMiSaveAs		(void);
static void PatternMiSize		(void);
static void PatternMiMark		(struct WinUserData *wud);
static void PatternMiCut		(struct WinUserData *wud);
static void PatternMiCopy		(struct WinUserData *wud);
static void PatternMiPaste		(struct WinUserData *wud);
static void PatternMiErase		(struct WinUserData *wud);
static void PatternMiUndo		(struct WinUserData *wud);
static void PatternMiRedo		(struct WinUserData *wud);
static void PatternMiSettings	(void);



/* Local data */

static struct Library	*PattEditBase		= NULL;
static struct TextFont	*EditorFont			= NULL;
static Object			*Model				= NULL,
						*EditorToVSlider	= NULL,
						*EditorToHSlider	= NULL;

static UBYTE WindowTitle[128];
static UBYTE TitleInfo[16];



XDEF struct PattSwitches PattSwitches =
{
	0,	1,					/* AdvanceTracks,	AdvanceLines	*/
	32,	8192,				/* MaxUndoLevels,	MaxUndoMem		*/
	0,						/* Flags							*/
	SCROLLERPLACE_RIGHT,	/* VScrollerPlace					*/
	SCROLLERPLACE_BOTTOM,	/* HScrollerPlace					*/
	0,	0,					/* ClipboardUnit,	Pad0			*/
	10, 0,					/* TrackChars, Backdrop				*/
	0,	1,	2,	2			/* BGPen, TextPen, LinesPen, TinyLinesPen */
};


static LONG MapVSlider2Editor[] =
{
	PGA_Top,				PEA_TopLine,
	TAG_DONE
};

static LONG MapHSlider2Editor[] =
{
	PGA_Top,				PEA_LeftTrack,
	TAG_DONE
};

static LONG MapEditorToVSlider[] =
{
	PEA_TopLine,			PGA_Top,
	PEA_DisplayLines,		PGA_Visible,
	TAG_DONE
};

static LONG MapEditorToHSlider[] =
{
	PEA_LeftTrack,		PGA_Top,
	PEA_DisplayTracks,	PGA_Visible,
	TAG_DONE
};

static LONG MapUp2Editor[] =
{
	GA_ID,		PEA_Up,
	TAG_DONE
};

static LONG MapDn2Editor[] =
{
	GA_ID,		PEA_Down,
	TAG_DONE
};

static LONG MapSx2Editor[] =
{
	GA_ID,		PEA_Left,
	TAG_DONE
};

static LONG MapDx2Editor[] =
{
	GA_ID,		PEA_Right,
	TAG_DONE
};



static struct NewMenu PatternNewMenu[] = {
	NM_TITLE, (STRPTR)MSG_PATTERNS_MEN, NULL, 0, NULL, NULL,
	NM_ITEM, (STRPTR)MSG_OPEN_MEN, (STRPTR)"O", 0, 0L, (APTR)PatternMiOpen,
	NM_ITEM, (STRPTR)MSG_SAVE_MEN, (STRPTR)"S", 0, 0L, (APTR)PatternMiSave,
	NM_ITEM, (STRPTR)MSG_SAVE_AS_MEN, (STRPTR)"A", 0, 0L, (APTR)PatternMiSaveAs,
	NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
	NM_ITEM, (STRPTR)MSG_SIZE_MEN, NULL, 0, 0L, (APTR)PatternMiSize,
	NM_TITLE, (STRPTR)MSG_EDIT_MEN, NULL, 0, NULL, NULL,
	NM_ITEM, (STRPTR)MSG_MARK_MEN, (STRPTR)"B", 0, 0L, (APTR)PatternMiMark,
	NM_ITEM, (STRPTR)MSG_CUT_MEN, (STRPTR)"X", 0, 0L, (APTR)PatternMiCut,
	NM_ITEM, (STRPTR)MSG_COPY_MEN, (STRPTR)"C", 0, 0L, (APTR)PatternMiCopy,
	NM_ITEM, (STRPTR)MSG_PASTE_MEN, (STRPTR)"V", 0, 0L, (APTR)PatternMiPaste,
	NM_ITEM, (STRPTR)MSG_ERASE_MEN, (STRPTR)"E", 0, 0L, (APTR)PatternMiErase,
	NM_ITEM, (STRPTR)NM_BARLABEL, NULL, 0, 0L, NULL,
	NM_ITEM, (STRPTR)MSG_UNDO_MEN, (STRPTR)"U", 0, 0L, (APTR)PatternMiUndo,
	NM_ITEM, (STRPTR)MSG_REDO_MEN, (STRPTR)"R", 0, 0L, (APTR)PatternMiRedo,
	NM_TITLE, (STRPTR)MSG_SETTINGS_MEN, NULL, 0, NULL, NULL,
	NM_ITEM, (STRPTR)MSG_EDITOR_SETTINGS_MEN, (STRPTR)"P", 0, 0L, (APTR)PatternMiSettings,
	NM_END, NULL, NULL, 0, 0L, NULL };



XDEF LONG PatternWinTags[] =
{
	TAG_IGNORE,			TRUE,	/* WA_SizeB(Left|Right) */
#ifdef OS30_ONLY
	WA_BackFill,		LAYERS_NOBACKFILL,
#endif /* OS30_ONLY */
	XMWIN_NewMenu,		(LONG)PatternNewMenu,
	XMWIN_LayoutFunc,	(LONG)LayoutPatternWin,
	XMWIN_GCount,		Pattern_CNT,
	XMWIN_Title,		MSG_PATTERN_TITLE,
	XMWIN_WindowFlags,	WFLG_CLOSEGADGET | WFLG_ACTIVATE|
		WFLG_SIZEGADGET | WFLG_SIMPLE_REFRESH | WFLG_NOCAREREFRESH,
	XMWIN_IDCMPFlags,	IDCMP_MENUPICK|IDCMP_CLOSEWINDOW|IDCMP_ACTIVEWINDOW|IDCMP_INACTIVEWINDOW|IDCMP_NEWSIZE|IDCMP_IDCMPUPDATE|IDCMP_INTUITICKS,
	XMWIN_IDCMPFunc,	(LONG)HandlePatternIDCMP,
	XMWIN_PostOpenFunc,	(LONG)PatternPostOpen,
	XMWIN_LayoutCleanup,(LONG)PatternWindowCleanup,
	XMWIN_HelpNode,		(LONG)"Pattern",
	TAG_DONE
};



static struct Gadget *CreatePattEdit (void)
{
	/* We do not initialize PEA_Pattern right now because
	 * it is done later by UpdatePattern().
	 */
	return ((struct Gadget *)NewObject (NULL, PATTEDITCLASS,
		GA_ID,				GD_PattEdit,
		GA_Left,			((PattSwitches.VScrollerPlace == SCROLLERPLACE_LEFT) ? SizeWidth : OffX),
		GA_Top,				OffY,
		GA_RelWidth,		- SizeWidth - ((PattSwitches.VScrollerPlace == SCROLLERPLACE_LEFT) ? Scr->WBorRight : OffX),
		GA_RelHeight,		- OffY - (((PattSwitches.HScrollerPlace == SCROLLERPLACE_BOTTOM) ||
			(PattSwitches.VScrollerPlace != SCROLLERPLACE_RIGHT)) ? SizeHeight : Scr->WBorBottom),
		PEA_TextFont,		EditorFont ? EditorFont : TopazFont,
		PEA_AdvanceCurs,	(PattSwitches.AdvanceTracks << 16) | PattSwitches.AdvanceLines,
		PEA_MaxUndoLevels,	PattSwitches.MaxUndoLevels,
		PEA_MaxUndoMem,		PattSwitches.MaxUndoMem,
		PEA_Flags,			PattSwitches.Flags,
		PEA_TrackChars,		PattSwitches.TrackChars,
		PEA_BGPen,			PattSwitches.BGPen,
		PEA_TextPen,		PattSwitches.TextPen,
		PEA_LinesPen,		PattSwitches.LinesPen,
		PEA_TinyLinesPen,	PattSwitches.TinyLinesPen,
		TAG_DONE));
}



static struct Gadget *LayoutPatternWin (struct WinUserData *wud)
{
	struct Gadget *pattedit;

	if (!PattEditBase)
		if (!(PattEditBase = MyOpenLibrary (PATTEDITNAME, PATTEDITVERS)))
			return NULL;

	{
		ULONG numcols = 1 << Scr->RastPort.BitMap->Depth; /**/

		if (PattSwitches.TextPen >= numcols)
			PattSwitches.TextPen = 1;

		if (PattSwitches.LinesPen >= numcols)
			PattSwitches.LinesPen = 2;

		if (PattSwitches.TinyLinesPen >= numcols)
			PattSwitches.TinyLinesPen = 2;
	}

	if (!(EditorFont = OpenFont (&EditorAttr)))
		if (DiskfontBase)
			EditorFont = OpenDiskFont (&EditorAttr);

	if (!EditorFont)
	{
		CantOpenLib (EditorAttr.ta_Name, 0);
		EditorFont = OpenFont (&TopazAttr);
	}


	PatternWinTags[0] = (PattSwitches.VScrollerPlace == SCROLLERPLACE_RIGHT) ?
		WA_SizeBRight : WA_SizeBBottom;

	if (PattSwitches.Backdrop)
	{
		wud->Flags = WFLG_BORDERLESS | WFLG_BACKDROP | WFLG_SMART_REFRESH | WFLG_NOCAREREFRESH;
		wud->WindowSize.Left = wud->WindowSize.Top = 0;
		wud->WindowSize.Width = Scr->Width;
		wud->WindowSize.Height = Scr->Height;
	}
	else
	{
		wud->WindowSize.Width = max (wud->WindowSize.Width,
			EditorFont->tf_XSize * (MINTRACKCHARS + 4));
		wud->WindowSize.Height = max (wud->WindowSize.Height,
			EditorFont->tf_YSize * 2);
	}

	if (!(pattedit = wud->Gadgets[GD_PattEdit] = CreatePattEdit()))
		return NULL;

	if (PattSwitches.VScrollerPlace)
	{
		if (!(wud->Gadgets[GD_UpButton] = CreateUpButton (GD_UpButton, pattedit, MapUp2Editor, PattSwitches.VScrollerPlace)))
			return NULL;
		if (!(wud->Gadgets[GD_DnButton] = CreateDnButton (GD_DnButton, pattedit, MapDn2Editor, PattSwitches.VScrollerPlace)))
			return NULL;
		if (!(wud->Gadgets[GD_VSlider] = CreateVSlider (GD_VSlider, pattedit, MapVSlider2Editor,
			wud->Gadgets[GD_UpButton]->Height + wud->Gadgets[GD_DnButton]->Height,
			PattSwitches.VScrollerPlace)))
			return NULL;
	}

	if (PattSwitches.HScrollerPlace)
	{
		if (!(wud->Gadgets[GD_SxButton] = CreateSxButton (GD_SxButton, pattedit, MapSx2Editor)))
			return NULL;
		if (!(wud->Gadgets[GD_DxButton] = CreateDxButton (GD_DxButton, pattedit, MapDx2Editor)))
			return NULL;
		if (!(wud->Gadgets[GD_HSlider] = CreateHSlider (GD_HSlider, pattedit, MapHSlider2Editor,
		wud->Gadgets[GD_DxButton]->Width + wud->Gadgets[GD_SxButton]->Width)))
			return NULL;
	}


	/* Link gadgets together */

	if (PattSwitches.VScrollerPlace)
	{
		wud->Gadgets[GD_PattEdit]->NextGadget	= wud->Gadgets[GD_UpButton];
		wud->Gadgets[GD_UpButton]->NextGadget	= wud->Gadgets[GD_DnButton];
		wud->Gadgets[GD_DnButton]->NextGadget	= wud->Gadgets[GD_VSlider];
		if (PattSwitches.HScrollerPlace)
			wud->Gadgets[GD_VSlider]->NextGadget = wud->Gadgets[GD_SxButton];
	}
	if (PattSwitches.HScrollerPlace)
	{
		if (!PattSwitches.VScrollerPlace)
			wud->Gadgets[GD_PattEdit]->NextGadget	= wud->Gadgets[GD_SxButton];

		wud->Gadgets[GD_SxButton]->NextGadget	= wud->Gadgets[GD_DxButton];
		wud->Gadgets[GD_DxButton]->NextGadget	= wud->Gadgets[GD_HSlider];
	}


	if (PattSwitches.VScrollerPlace || PattSwitches.HScrollerPlace)
	{
		/* Create the Model */
		if (Model = NewObject (NULL, MODELCLASS,
			ICA_TARGET,		ICTARGET_IDCMP,
			TAG_DONE))
		{
			/* Connect Editor to Model */
			SetAttrs (wud->Gadgets[GD_PattEdit],
				ICA_TARGET, Model,
				TAG_DONE);

			if (PattSwitches.VScrollerPlace)
			{
				if (EditorToVSlider = NewObject (NULL, ICCLASS,
					ICA_TARGET,	wud->Gadgets[GD_VSlider],
					ICA_MAP,	MapEditorToVSlider,
					TAG_DONE))
					/* Connect Model to VSlider */
					if (!DoMethod (Model, OM_ADDMEMBER, EditorToVSlider))
						DisposeObject (EditorToVSlider); EditorToVSlider = NULL;
			}

			if (PattSwitches.HScrollerPlace)
			{
				if (EditorToHSlider = NewObject (NULL, ICCLASS,
					ICA_TARGET,	wud->Gadgets[GD_HSlider],
					ICA_MAP,	MapEditorToHSlider,
					TAG_DONE))
						/* Connect Model to HSlider */
						if (!DoMethod (Model, OM_ADDMEMBER, EditorToHSlider))
							DisposeObject (EditorToHSlider); EditorToHSlider = NULL;
			}
		}
	}
	else
		/* Connect Editor to application */
		SetAttrs (wud->Gadgets[GD_PattEdit],
			ICA_TARGET, ICTARGET_IDCMP,
			TAG_DONE);

	return wud->Gadgets[GD_PattEdit];

	/* NOTE: The model will also dispose its members... */
/*
	DisposeObject (Model); Model = NULL;
	EditorToVSlider = NULL; EditorToHSlider = NULL;
	return NULL;
*/
}




#ifndef OS30_ONLY

static ULONG __asm BFHookFunc (void)

/* No-op backfilling hook.  Since we are going to redraw the whole window
 * anyway, we can disable backfilling.  This avoids ugly flashing while
 * resizing or revealing the window.
 *
 * This function hasn't the __saveds attribute because it makes no
 * references to external data.
 */
{
	return 1;	/* Do nothing */
}

static struct Hook BFHook =
{
	NULL, NULL,
	BFHookFunc,
};
#endif /* !OS30_ONLY */



static void PatternPostOpen (struct WinUserData *wud)
{
	/* Limit window flashing by providing a no-op hook for window
	 * backfilling.
	 */
#ifndef OS30_ONLY
	InstallLayerHook (wud->Win->WLayer, (LayersBase->lib_Version >= 39) ?
		((struct Hook *)LAYERS_NOBACKFILL) : &BFHook);
#endif /* !OS30_ONLY */

	UpdatePattern();

	/* Allow window resizing.  Minimum size is chosen so that at least
	 * two lines are visible.
	 */
	WindowLimits (wud->Win,
		wud->Win->BorderLeft + wud->Win->BorderRight + EditorFont->tf_XSize * (10 + 4),
		wud->Win->BorderTop + wud->Win->BorderBottom + EditorFont->tf_YSize * 2,
		-1, -1);
}



static void PatternWindowCleanup (struct WinUserData *wud)
{
	/* NOTE: The model will also dispose its members. */
	DisposeObject (Model);	Model = NULL;
	EditorToVSlider = NULL;
	EditorToHSlider = NULL;

	if (wud->Gadgets)
	{
		DisposeObject (wud->Gadgets[GD_VSlider]);
		wud->Gadgets[GD_VSlider] = NULL;
		DisposeObject (wud->Gadgets[GD_HSlider]);
		wud->Gadgets[GD_HSlider] = NULL;

		if (wud->Gadgets[GD_UpButton])
		{
			DisposeObject (wud->Gadgets[GD_UpButton]->GadgetRender);
			DisposeObject (wud->Gadgets[GD_UpButton]);
			wud->Gadgets[GD_UpButton] = NULL;
		}

		if (wud->Gadgets[GD_DnButton])
		{
			DisposeObject (wud->Gadgets[GD_DnButton]->GadgetRender);
			DisposeObject (wud->Gadgets[GD_DnButton]);
			wud->Gadgets[GD_DnButton] = NULL;
		}

		if (wud->Gadgets[GD_SxButton])
		{
			DisposeObject (wud->Gadgets[GD_SxButton]->GadgetRender);
			DisposeObject (wud->Gadgets[GD_SxButton]);
			wud->Gadgets[GD_SxButton] = NULL;
		}

		if (wud->Gadgets[GD_DxButton])
		{
			DisposeObject (wud->Gadgets[GD_DxButton]->GadgetRender);
			DisposeObject (wud->Gadgets[GD_DxButton]);
			wud->Gadgets[GD_DxButton] = NULL;
		}

		DisposeObject (wud->Gadgets[GD_PattEdit]);
		wud->Gadgets[GD_PattEdit] = NULL;
	}

	if (EditorFont)
	{
		CloseFont (EditorFont);
		EditorFont = NULL;
	}

	if (PattEditBase)
	{
		CloseLibrary (PattEditBase);
		PattEditBase = NULL;
	}
}



static void HandlePatternIDCMP (struct WinUserData *wud)
{
	switch (IntuiMsg.Class)
	{
		case IDCMP_IDCMPUPDATE:
		{
			struct TagItem *ti, *tstate = IntuiMsg.IAddress;
			ULONG line = ((UWORD)~0), track = ((UWORD)~0);

			while (ti = NextTagItem (&tstate))
				switch (ti->ti_Tag)
				{
					case PEA_CursLine:
						line = ti->ti_Data;
						break;

					case PEA_CursTrack:
						track = ti->ti_Data;
						break;

					case PEA_ChangeNote:
						/* TODO: Increment changes counter of song */
						break;

					default:
						break;
				}

			if (track != ((UWORD)~0))
			{
				SPrintf (TitleInfo, "   %lu, %lu", track, line);
				RenderPatternWindow (wud);
			}

			break;
		}

		case IDCMP_ACTIVEWINDOW:
		case IDCMP_INACTIVEWINDOW:
		case IDCMP_NEWSIZE:
			RenderPatternWindow (wud);
			break;

		case IDCMP_INTUITICKS:
			if (!(wud->Gadgets[GD_PattEdit]->Flags & GFLG_SELECTED))
				ActivateGadget (wud->Gadgets[GD_PattEdit], wud->Win, NULL);
			break;

		default:
			break;
	}
}



static void RenderPatternWindow (struct WinUserData *wud)

/* Prints <TitleInfo> on the right hand of the window title bar */
{
	UWORD tlen, twidth;
	struct RastPort *rp;
	struct TextFont *oldfont;

	tlen = strlen (TitleInfo);	/* Couldn't get it from SPrintf()!! */

	rp = wud->Win->RPort;
	oldfont = rp->Font;
	SetFont (rp, DrawInfo->dri_Font);

	twidth = TextLength (rp, TitleInfo, tlen);

	if (wud->Win->Flags & WFLG_WINDOWACTIVE)
	{
#ifndef OS30_ONLY
		if (GfxBase->LibNode.lib_Version >= 39)
#endif /* !OS30_ONLY */
			SetABPenDrMd (rp, DrawInfo->dri_Pens[FILLTEXTPEN],
				DrawInfo->dri_Pens[FILLPEN], JAM2);
#ifndef OS30_ONLY
		else
		{
			SetAPen (rp, DrawInfo->dri_Pens[FILLTEXTPEN]);
			SetBPen (rp, DrawInfo->dri_Pens[FILLPEN]);
			SetDrMd (rp, JAM2);
		}
#endif /* !OS30_ONLY */
	}
	else
	{
#ifndef OS30_ONLY
		if (GfxBase->LibNode.lib_Version >= 39)
#endif /* !OS30_ONLY */
			SetABPenDrMd (rp, DrawInfo->dri_Pens[TEXTPEN],
				DrawInfo->dri_Pens[BACKGROUNDPEN], JAM2);
#ifndef OS30_ONLY
		else
		{
			SetAPen (rp, DrawInfo->dri_Pens[TEXTPEN]);
			SetBPen (rp, DrawInfo->dri_Pens[BACKGROUNDPEN]);
			SetDrMd (rp, JAM2);
		}
#endif /* !OS30_ONLY */
	}

	Move (rp, wud->Win->Width - twidth - 60, rp->TxBaseline + 1);
	Text (rp, TitleInfo, tlen);

	SetFont (rp, oldfont);
}



GLOBALCALL void UpdatePattern (void)
{
	struct WinUserData *wud = WDescr[WID_PATTERN].Wud;

	if (wud && wud->Win)
	{
		struct SongInfo *si;
		struct Pattern *patt;
		ULONG currentinst;

		if (si = xmLockActiveSong (SM_SHARED))
		{
			patt = si->Patt[si->CurrentPatt];
			currentinst = si->CurrentInst;

			SPrintf (WindowTitle, "%s/%03ld: %s  (%ld/%ld)",
				si->Title,
				si->CurrentPatt,
				patt->Name ? patt->Name : (UBYTE *)"", patt->Tracks, patt->Lines);

			ReleaseSemaphore (&si->Lock);
		}
		else
		{
			patt = NULL;
			currentinst = 0;
			strcpy (WindowTitle, "Pattern Editor");
		}

		SetWindowTitles (wud->Win, WindowTitle, NULL);

		SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
			PEA_Pattern,		patt,
			PEA_CurrentInst,	currentinst,
			TAG_DONE);

		SetGadgetAttrs (wud->Gadgets[GD_VSlider], wud->Win, NULL,
			PGA_Total,			patt ? patt->Lines : 0,
			TAG_DONE);

		SetGadgetAttrs (wud->Gadgets[GD_HSlider], wud->Win, NULL,
			PGA_Total,			patt ? patt->Tracks : 0,
			TAG_DONE);
	}

	UpdatePattSize();
}



GLOBALCALL void UpdateEditorInst (void)
{
	struct WinUserData *wud = WDescr[WID_PATTERN].Wud;

	if (wud && wud->Win)
	{
		struct SongInfo *si;

		if (si = xmLockActiveSong (SM_SHARED))
		{
			SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
				PEA_CurrentInst,	si->CurrentInst,
				TAG_DONE);
			ReleaseSemaphore (&si->Lock);
		}
	}
}




static void PatternLoad (STRPTR name, ULONG num, ULONG count)

/* Load a pattern from an IFF PATT file.  This function is called only
 * by the file requester handler.
 */
{
	struct SongInfo *si;
	struct IFFHandle *iff;
	LONG err;

	if (si = xmLockActiveSong (SM_SHARED))
	{
		LockWindows();

		if (iff = AllocIFF())
		{
			InitIFFasDOS (iff);
			if (iff->iff_Stream = (ULONG) Open (name, MODE_OLDFILE))
			{
				if (!(err = OpenIFF (iff, IFFF_READ)))
				{
					LoadPattern (si, si->CurrentPatt + num, iff);
					CloseIFF (iff);
				}

				Close (iff->iff_Stream);
			}
			else err = IoErr();

			FreeIFF (iff);
		}
		else err = ERROR_NO_FREE_STORE;

		ReleaseSemaphore (&si->Lock);

		UpdatePatternList(); // This will also update the Pattern Editor.

		UnlockWindows();

		LastErr = err;
	}
}



static LONG PatternSave (STRPTR name, struct Pattern *patt)

/* Store a pattern to an IFF PATT file. */
{
	struct IFFHandle	*iff;
	LONG err;


	if (iff = AllocIFF())
	{
		if (name)
		{
			InitIFFasDOS (iff);
			iff->iff_Stream = (ULONG) Open (name, MODE_NEWFILE);
		}
		else /* Clipboard */
		{
			InitIFFasClip (iff);
			iff->iff_Stream = (ULONG) OpenClipboard (PattSwitches.ClipboardUnit);
		}

		if (iff->iff_Stream)
		{

			if (!(err = OpenIFF (iff, IFFF_WRITE)))
			{
				SavePattern (iff, patt);
				CloseIFF (iff);
			}

			if (name)
				Close (iff->iff_Stream);
			else
				CloseClipboard ((struct ClipboardHandle *)iff->iff_Stream);
		}
		else err = IoErr();

		FreeIFF (iff);
	}
	else err = ERROR_NO_FREE_STORE;

	return err;
}



/*****************/
/* Pattern Menus */
/*****************/

static void PatternMiOpen (void)
{
	StartFileRequest (FREQ_LOADPATT, PatternLoad);
}



static void PatternMiSave (void)
{
	struct SongInfo *si;
	struct Pattern *patt;


	if (si = xmLockActiveSong (SM_SHARED))
	{
		LockWindows();
		patt = si->Patt[si->CurrentPatt];
		LastErr = PatternSave (patt->Name, patt);
		UnlockWindows();
		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiSaveAs (void)
{
	struct SongInfo *si;
	struct Pattern *patt;
	UBYTE name[PATHNAME_MAX];

	if (si = xmLockActiveSong (SM_SHARED))
	{
		LockWindows();

		patt = si->Patt[si->CurrentPatt];

		strncpy (name, patt->Name, PATHNAME_MAX-1);
		name[PATHNAME_MAX-1] = '\0';

		if (FileRequest (FREQ_SAVEINST, name))
			LastErr = PatternSave (name, patt);

		UnlockWindows();
		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiSize (void)
{
	NewWindow (WID_PATTSIZE);
}



static void PatternMiMark (struct WinUserData *wud)
{
	SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
		PEA_MarkRegion,	-1,
		TAG_DONE);
}



static void PatternMiCut (struct WinUserData *wud)
{
	struct SongInfo *si;
	struct Pattern *patt, *cpatt;
	struct Rectangle markregion;
	UWORD i;

	if (si = xmLockActiveSong (SM_SHARED))
	{
		patt = si->Patt[si->CurrentPatt];

		if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
		{
			if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
				return;	/* Not in mark mode */

			if (cpatt = xmAddPattern (si, -1,
				PATTA_Tracks,	markregion.MaxX - markregion.MinX + 1,
				PATTA_Lines,	0, /* Do not allocate anything */
				PATTA_Name,		patt->Name,
				TAG_DONE))
			{
				cpatt->Lines = markregion.MaxY - markregion.MinY + 1;

				for (i = 0; i < cpatt->Tracks; i++)
					cpatt->Notes[i] = patt->Notes[i + markregion.MinX] + markregion.MinY;

				/* Snap marked region to Clipboard */
				PatternSave (NULL, cpatt);

				/* And then clear it */
				for (i = 0; i < cpatt->Tracks; i++)
					memset (cpatt->Notes[i], 0, sizeof (struct Note) * cpatt->Lines);

				/* Free dummy pattern */
				cpatt->Lines = 0;
				for (i = 0; i < cpatt->Tracks; i++)
					cpatt->Notes[i] = NULL;
				xmRemPattern (si, -1, (ULONG)cpatt);

				SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
					PEA_Pattern,		patt,		/* Refresh display	*/
					PEA_MarkRegion,		NULL,		/* Stop marking		*/
					TAG_DONE);
			}
		}

		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiCopy (struct WinUserData *wud)
{
	struct SongInfo *si;
	struct Pattern *patt, *cpatt;
	struct Rectangle markregion;
	UWORD i;

	if (si = xmLockActiveSong (SM_SHARED))
	{
		patt = si->Patt[si->CurrentPatt];

		if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
		{
			if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
				return;	/* Not in mark mode */

			if (cpatt = xmAddPattern (si, -1,
				PATTA_Tracks,	markregion.MaxX - markregion.MinX + 1,
				PATTA_Lines,	0, /* Do not allocate anything */
				PATTA_Name,		patt->Name,
				TAG_DONE))
			{
				cpatt->Lines = markregion.MaxY - markregion.MinY + 1;

				for (i = 0; i < cpatt->Tracks; i++)
					cpatt->Notes[i] = patt->Notes[i + markregion.MinX] + markregion.MinY;

				PatternSave (NULL, cpatt);

				/* Free dummy pattern */
				cpatt->Lines = 0;
				for (i = 0; i < cpatt->Tracks; i++)
					cpatt->Notes[i] = NULL;
				xmRemPattern (si, -1, (ULONG)cpatt);

				SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
					PEA_MarkRegion,	NULL,	/* Stop marking */
					TAG_DONE);
			}
		}

		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiPaste (struct WinUserData *wud)
{
	struct SongInfo *si;
	struct IFFHandle *iff;
	struct Pattern *patt, *cpatt;
	ULONG track, line;
	UWORD i;

	if (si = xmLockActiveSong (SM_EXCLUSIVE))
	{
		patt = si->Patt[si->CurrentPatt];


		/* Get pattern in Clip */

		if (iff = AllocIFF())
		{
			InitIFFasClip (iff);
			if (iff->iff_Stream = (ULONG) OpenClipboard (PattSwitches.ClipboardUnit))
			{
				if (!OpenIFF (iff, IFFF_READ))
				{
					if (cpatt = LoadPattern (si, -1, iff))
					{
						if (GetAttr (PEA_CursTrack, wud->Gadgets[GD_PattEdit], &track) &&
							GetAttr (PEA_CursLine, wud->Gadgets[GD_PattEdit], &line))
						{
							/* Paste it into the pattern */

							for (i = 0; i < min (cpatt->Tracks, patt->Tracks - track); i++)
								CopyMem (cpatt->Notes[i], patt->Notes[i + track] + line,
									sizeof (struct Note) * min (patt->Lines - line, cpatt->Lines));

							SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
								PEA_Pattern,	patt,	/* Refresh display	*/
								PEA_CursTrack,	min (track + cpatt->Tracks, patt->Tracks) - 1,
								PEA_CursLine,	min (line + cpatt->Lines, patt->Lines)  - 1,
								TAG_DONE);
						}

						xmRemPattern (si, -1, (ULONG)cpatt);
					}
					CloseIFF (iff);
				}

				CloseClipboard ((struct ClipboardHandle *)iff->iff_Stream);
			}

			FreeIFF (iff);
		}

		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiErase (struct WinUserData *wud)
{
	struct SongInfo *si;
	struct Pattern *patt;
	struct Rectangle markregion;
	UWORD i;

	if (si = xmLockActiveSong (SM_EXCLUSIVE))
	{
		patt = si->Patt[si->CurrentPatt];

		if (GetAttr (PEA_MarkRegion, wud->Gadgets[GD_PattEdit], (ULONG *)&markregion))
		{
			if ((markregion.MaxX == 0) && (markregion.MaxY == 0))
				return;	/* Not in mark mode */

			/* Clear marked region */
			for (i = markregion.MinX; i <= markregion.MaxX; i++)
				memset (patt->Notes[i] + markregion.MinY, 0, sizeof (struct Note) *
					(markregion.MaxY - markregion.MinY + 1));

			SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
				PEA_Pattern,	patt,	/* Refresh display	*/
				PEA_MarkRegion,	NULL,	/* Stop marking		*/
				TAG_DONE);
		}

		ReleaseSemaphore (&si->Lock);
	}
}



static void PatternMiUndo (struct WinUserData *wud)
{
	SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
		PEA_UndoChange,	1,
		TAG_DONE);
}



static void PatternMiRedo (struct WinUserData *wud)
{
	SetGadgetAttrs (wud->Gadgets[GD_PattEdit], wud->Win, NULL,
		PEA_UndoChange,	-1,
		TAG_DONE);
}



static void PatternMiSettings (void)
{
	NewWindow (WID_PATTPREFS);
}

