/*
**  jazz - a midi sequencer for Linux
**
**  Copyright (C) 1994-1996 Andreas Voss (andreas@avix.rhein-neckar.de)
**
**  Copyright (C) 1995-1996 Per Sigmond (Per.Sigmond@hia.no)
**
**  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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "wx.h"
#pragma hdrstop

#include "trackwin.h"
#include "song.h"
#include "mstdfile.h"
#include "filter.h"
#include "pianowin.h"
#include "command.h"
#include "dialogs.h"
#include "player.h"
#include "harmony.h"
#include "rhythm.h"
#include "gs_dlgs.h"
#include "jazz.h"
#include "toolbar.h"
#ifdef wx_msw
#include "winplay.h"
#endif

#include <stdlib.h>
#include <ctype.h>
#include <string.h>

#ifdef VMS
#if __DECCXX_VER < 50200000
#include <signal.h>
#else
#include <unistd.h>
#endif
#endif

#ifdef wx_xt
#define wxbMessageBox wxMessageBox
#endif

tTrackWin *TrackWin = 0;
static char *defsong = 0;
static char *defpattern = 0;
char *lasts = "noname.mid";

#define MEN_LOAD 	1
#define MEN_SAVE 	2
#define MEN_QUIT 	3
#define MEN_ABOUT 	4
#define MEN_TWSETTING	5
#define MEN_FILTER	6
#define MEN_METERCH	7
#define MEN_MERGE	8
#define MEN_NEW		9
#define MEN_COPY	10
#define MEN_DELETE	11
#define MEN_QUANTIZE	12
#define MEN_UNDO	13
#define MEN_DEBUG	14
#define MEN_SETCHAN	15
#define MEN_TRANSP	16
#define MEN_VELOC	17
#define MEN_LENGTH	18
#define MEN_RESET       19
#define MEN_SONG	20
#define MEN_TMERGE	21
#define MEN_TSPLIT	22
#define MEN_MIXER	23
#define MEN_SOUND	24
#define MEN_VIBRATO	25
#define MEN_ENVELOPE	26
#define MEN_BEND_BASIC	27
#define MEN_BEND_LFO1	28
#define MEN_BEND_LFO2	29
#define MEN_EFFECTS	30
#define MEN_TIMING	31
#define MEN_MOD_BASIC	32
#define MEN_MOD_LFO1	33
#define MEN_MOD_LFO2	34
#define MEN_CAF_BASIC	35
#define MEN_CAF_LFO1	36
#define MEN_CAF_LFO2	37
#define MEN_PAF_BASIC	38
#define MEN_PAF_LFO1	39
#define MEN_PAF_LFO2	40
#define MEN_CC1_BASIC	41
#define MEN_CC1_LFO1	42
#define MEN_CC1_LFO2	43
#define MEN_CC2_BASIC	44
#define MEN_CC2_LFO1	45
#define MEN_CC2_LFO2	46
#define MEN_PART_RSRV	47
#define MEN_PART_MODE	48
#define MEN_HARMONY     49
#define MEN_RHYTHM      50
#define MEN_MASTER	51
#define MEN_SHIFT	52
#define MEN_HELP_MOUSE  53
#define MEN_DRUM_PARAM  54
#define MEN_HELP_JAZZ   55
#define MEN_HELP_TWIN   56
#define MEN_SUB_BENDER  57
#define MEN_SUB_MODUL   58
#define MEN_SUB_CAF     59
#define MEN_SUB_PAF     60
#define MEN_SUB_CC1     61
#define MEN_SUB_CC2     62
#define MEN_CLEANUP     63
#define MEN_DEVICE	64
#define MEN_SAVE_SET    65
#define MEN_MIDI_THRU   66
#define MEN_COPYRIGHT   67
#define MEN_SEARCHREP   68
#define MEN_LOADPATTERN 69
#define MEN_SAVEPATTERN 70
#define MEN_SAVEAS	71
#define MEN_SAVE_THRU   72
#define MEN_SAVE_TIM    73
#define MEN_SAVE_EFF    74
#define MEN_SAVE_GEO    75
#define MEN_SAVE_ALL    76

#ifdef wx_x
#include "../bitmaps/open.xpm"
#include "../bitmaps/save.xpm"
#include "../bitmaps/new.xpm"
#include "../bitmaps/repl.xpm"
#include "../bitmaps/delete.xpm"
#include "../bitmaps/quantize.xpm"
#include "../bitmaps/mixer.xpm"
#include "../bitmaps/undo.xpm"
#include "../bitmaps/panic.xpm"
#include "../bitmaps/help.xpm"
static tToolDef tdefs[] = {
  { MEN_LOAD,  		FALSE, 6, tb_open },
  { MEN_SAVE,  		FALSE, 6, tb_save },
  { MEN_NEW,   		FALSE, 12, tb_new  },

  { MEN_COPY,  		FALSE, 6, tb_repl },
  { MEN_DELETE, 	FALSE, 6, tb_delete  },
  { MEN_QUANTIZE, 	FALSE, 6, tb_quantize  },
  { MEN_MIXER, 		FALSE, 12, tb_mixer  },

  { MEN_UNDO, 		FALSE, 6, tb_undo },
  { MEN_RESET, 		FALSE, 6, tb_panic },
  { MEN_HELP_JAZZ, 	FALSE, 6, tb_help }
};

#else

static tToolDef tdefs[] = {
  { MEN_LOAD,  		FALSE, 2, "tb_open" },
  { MEN_SAVE,  		FALSE, 2, "tb_save" },
  { MEN_NEW,   		FALSE, 8, "tb_new"  },

  { MEN_COPY,  		FALSE, 2, "tb_repl" },
  { MEN_DELETE, 	FALSE, 2, "tb_delete"  },
  { MEN_QUANTIZE, 	FALSE, 2, "tb_quantize"  },
  { MEN_MIXER, 		FALSE, 8, "tb_mixer"  },

  { MEN_UNDO, 		FALSE, 2, "tb_undo" },
  { MEN_RESET, 		FALSE, 2, "tb_panic" },
  { MEN_HELP_JAZZ, 	FALSE, 2, "tb_help" }
};
#endif

tTrackWin::tTrackWin(wxFrame *frame, char *title, tSong *song, int x, int y, int width, int height)
  : tEventWin(frame, title, song, x, y, width, height)
{
  rhythm_win = 0;
  prev_clock = 0;
  prev_loop  = 0;
  prev_muted = 0;

  defsong = copystring(lasts);
  defpattern = copystring("noname.mid");

  tool_bar = new tToolBar(this, tdefs, 10);

  int *geo = PianoWinGeo;
  int i;
  int opt;

  opt = GetArgOpt( "-pianowin" ) + 1;
  for (i = 0; i < 4; i++, opt++) {
  if ((wxTheApp->argc > opt) && isdigit( wxTheApp->argv[opt][0] ))
	geo[i] = atoi( wxTheApp->argv[opt] );
  else
	break;
  }

  NextWin = new tPianoWin(frame, "Piano Roll", Song, geo[0], geo[1], geo[2], geo[3] );

  NextWin->Create();
  nBars = 0;
  RecInfo.Track = 0;
  RecInfo.Muted = 0;

  CounterMode = CmProgram;
  NumberMode = NmMidiChannel;
}

tTrackWin::~tTrackWin()
{
  delete rhythm_win;
  delete [] defsong;
  delete [] defpattern;
}


static char *file_selector(char * &deffile, char *title, Bool save = FALSE, Bool changed = FALSE)
{
  char *s;
  {
    char *file = 0, *path = 0;
    if (save)
    {
      file = wxFileNameFromPath(deffile);
      if (file)
	file = copystring(file);
    }
    path = wxPathOnly(deffile);
    if (path)
      path = copystring(path);
    s = wxFileSelector(title, path, file, NULL, "*.mid");
    delete [] file;
    delete [] path;
  }

  // warn if overwriting existent file
  if (s && save) 
  {
    if (wxFileExists(s))
    {
      char buf[500];
      sprintf(buf, "overwrite %s?", s);
      if (wxMessageBox(buf, "Save ?", wxYES_NO) == wxNO)
        s = 0;
    }
  }

  if (s && !save && changed) 
  {
    if (wxMessageBox("Song has changed. Load anyway?", "Load ?", wxYES_NO) == wxNO)
      s = 0;
  }

  if (s) 
  {
    delete [] deffile;
    deffile = copystring(s);
    return deffile;
  }

  return 0;
}


Bool tTrackWin::OnClose()
{
  if (tTrack::changed) {
    if (wxMessageBox("Song has changed. Quit anyway?", "Quit ?", wxYES_NO) == wxNO) 
      return FALSE;
  }

  if (Midi->Playing)
  {
    Midi->StopPlay();
#ifndef wx_msw
    sleep(1);
#endif
  }
  delete the_harmony_browser;
  delete Midi;
  delete NextWin;
  return TRUE;
}




void tTrackWin::Setup()
{
  float x, y;

  tEventWin::Setup();

  dc->GetTextExtent("H", &x, &y);
  LittleBit = (int)(x/2);

  dc->GetTextExtent("HXWjgi", &x, &y);
  hLine = (int)y + LittleBit;
  hTop = hFixedFont + 2 * LittleBit;

  dc->GetTextExtent("99", &x, &y);
  wNumber = (int)x + LittleBit;

  dc->GetTextExtent("Normal Trackname", &x, &y);
  wName = (int)x + LittleBit;

  dc->GetTextExtent("m", &x, &y);
  wState = (int)x + LittleBit;

  dc->GetTextExtent("999", &x, &y);
  wPatch = (int)x + 2 * LittleBit;

  wLeft = wNumber + wName + wState + wPatch + 1;

  UnMark();
}



// ************************************************************************
// Menubar
// ************************************************************************

void tTrackWin::CreateMenu()
{
  wxMenuBar *menu_bar = NULL;
  wxMenu *file_menu = new wxMenu;
  file_menu->Append(MEN_LOAD,          "&Load ...");
  file_menu->Append(MEN_SAVE,            "&Save");
  file_menu->Append(MEN_SAVEAS,          "Save &as...");
  file_menu->Append(MEN_NEW,           "&New ...");
  file_menu->Append(MEN_LOADPATTERN,   "Load Pattern...");
  file_menu->Append(MEN_SAVEPATTERN,   "Save Pattern...");
  file_menu->Append(MEN_QUIT,          "&Quit");

  wxMenu *edit_menu = new wxMenu;
  edit_menu->Append(MEN_COPY,      "&Replicate ...");
  edit_menu->Append(MEN_DELETE,    "&Delete ...");
  edit_menu->Append(MEN_QUANTIZE,  "&Quantize ...");
  edit_menu->Append(MEN_SETCHAN,   "&Set MIDI Channel ...");
  edit_menu->Append(MEN_TRANSP,    "&Transpose ...");
  edit_menu->Append(MEN_VELOC,     "&Velocity ...");
  edit_menu->Append(MEN_LENGTH,    "&Length ...");
  edit_menu->Append(MEN_SHIFT,     "Shi&ft ...");
  edit_menu->Append(MEN_CLEANUP,   "Cleanup ...");
  edit_menu->Append(MEN_SEARCHREP, "Search Replace ...");
  // edit_menu->Append(MEN_DEBUG,  "&Debug...");

  wxMenu *parts_menu = new wxMenu;
  parts_menu->Append(MEN_MIXER,    "&Mixer ...");
  parts_menu->Append(MEN_MASTER,    "&Master ...");
  parts_menu->Append(MEN_SOUND,    "&Sound ...");
  parts_menu->Append(MEN_VIBRATO,  "&Vibrato ...");
  parts_menu->Append(MEN_ENVELOPE, "&Envelope ...");

  wxMenu *bender_menu = new wxMenu;
  bender_menu->Append(MEN_BEND_BASIC,    "&Bender Basic...");
  bender_menu->Append(MEN_BEND_LFO1,    "&Bender LFO1...");
  bender_menu->Append(MEN_BEND_LFO2,    "&Bender LFO2...");
  parts_menu->Append(MEN_SUB_BENDER, "&Bender...", bender_menu );

  wxMenu *modulation_menu = new wxMenu;
  modulation_menu->Append(MEN_MOD_BASIC,    "&Modulation Basic...");
  modulation_menu->Append(MEN_MOD_LFO1,    "&Modulation LFO1...");
  modulation_menu->Append(MEN_MOD_LFO2,    "&Modulation LFO2...");
  parts_menu->Append(MEN_SUB_MODUL, "&Modulation...", modulation_menu );

  wxMenu *caf_menu = new wxMenu;
  caf_menu->Append(MEN_CAF_BASIC,    "&CAf Basic...");
  caf_menu->Append(MEN_CAF_LFO1,    "&CAf LFO1...");
  caf_menu->Append(MEN_CAF_LFO2,    "&CAf LFO2...");
  parts_menu->Append(MEN_SUB_CAF, "&CAf...", caf_menu );

  wxMenu *paf_menu = new wxMenu;
  paf_menu->Append(MEN_PAF_BASIC,    "&PAf Basic...");
  paf_menu->Append(MEN_PAF_LFO1,    "&PAf LFO1...");
  paf_menu->Append(MEN_PAF_LFO2,    "&PAf LFO2...");
  parts_menu->Append(MEN_SUB_PAF, "&PAf...", paf_menu );

  wxMenu *cc1_menu = new wxMenu;
  cc1_menu->Append(MEN_CC1_BASIC,    "&CC1 Basic...");
  cc1_menu->Append(MEN_CC1_LFO1,    "&CC1 LFO1...");
  cc1_menu->Append(MEN_CC1_LFO2,    "&CC1 LFO2...");
  parts_menu->Append(MEN_SUB_CC1, "&CC1...", cc1_menu );

  wxMenu *cc2_menu = new wxMenu;
  cc2_menu->Append(MEN_CC2_BASIC,    "&CC2 Basic...");
  cc2_menu->Append(MEN_CC2_LFO1,    "&CC2 LFO1...");
  cc2_menu->Append(MEN_CC2_LFO2,    "&CC2 LFO2...");
  parts_menu->Append(MEN_SUB_CC2, "&CC2...", cc2_menu );

  parts_menu->Append(MEN_DRUM_PARAM,    "&Drum Parameters...");
  parts_menu->Append(MEN_PART_RSRV,    "&Partial Reserve...");
  parts_menu->Append(MEN_PART_MODE,    "&Part Mode...");

  wxMenu *setting_menu = new wxMenu;
  setting_menu->Append(MEN_FILTER,    "&Filter ...");
  setting_menu->Append(MEN_TWSETTING, "&Window ...");
  setting_menu->Append(MEN_SONG,      "&Song ...");
  setting_menu->Append(MEN_EFFECTS,   "&Effects ...");
  setting_menu->Append(MEN_TIMING,    "&Timing ...");
  setting_menu->Append(MEN_MIDI_THRU, "&Midi Thru ...");
  #ifdef wx_msw
  setting_menu->Append(MEN_DEVICE,    "&Midi Device...");
  #endif
  wxMenu *save_settings_menu = new wxMenu;
  save_settings_menu->Append( MEN_SAVE_THRU, "&Midi Thru" );
  save_settings_menu->Append( MEN_SAVE_TIM, "&Timing" );
  save_settings_menu->Append( MEN_SAVE_EFF, "&Effect Macros" );
  save_settings_menu->Append( MEN_SAVE_GEO, "&Window Geometry" );
  save_settings_menu->Append( MEN_SAVE_ALL, "&Save All" );
  setting_menu->Append(MEN_SAVE_SET, "&Save settings", save_settings_menu );

  wxMenu *misc_menu = new wxMenu;
  misc_menu->Append(MEN_UNDO,     "&Undo ...");
  misc_menu->Append(MEN_TMERGE,   "&Merge Tracks ...");
  misc_menu->Append(MEN_TSPLIT,   "&Split Tracks ...");
  misc_menu->Append(MEN_METERCH,  "&Meterchange ...");
  misc_menu->Append(MEN_RESET,    "&Reset Midi (GS)");
  misc_menu->Append(MEN_HARMONY,  "&Harmony Browser");
  misc_menu->Append(MEN_RHYTHM,   "&Random Rhythm");
  misc_menu->Append(MEN_COPYRIGHT,"&Music Copyright ...");

  wxMenu *help_menu = new wxMenu;
  help_menu->Append(MEN_HELP_JAZZ, "&Jazz");
  help_menu->Append(MEN_HELP_TWIN, "&Trackwin");
  help_menu->Append(MEN_HELP_MOUSE, "&Mouse");
  help_menu->Append(MEN_ABOUT, "&About");

  menu_bar = new wxMenuBar;
  menu_bar->Append(file_menu,    "&File");
  menu_bar->Append(edit_menu,    "&Edit");
  menu_bar->Append(parts_menu,   "&Parts");
  menu_bar->Append(setting_menu, "&Settings");
  menu_bar->Append(misc_menu,    "&Misc");
  menu_bar->Append(help_menu,    "&Help");

  SetMenuBar(menu_bar);
}


#ifndef wxRELEASE_NUMBER

#define _MAXPATHLEN 500

// Return just the filename, not the path
// (basename)
char *
wxFileNameFromPath (char *path)
{
  if (path)
    {
      register char *tcp;

      tcp = path + strlen (path);
      while (--tcp >= path)
	{
	  if (*tcp == '/' || *tcp == '\\'
#ifdef VMS
     || *tcp == ':' || *tcp == ']')
#else
     )
#endif
	    return tcp + 1;
	}                       /* while */
#ifdef wx_msw
      if (isalpha (*path) && *(path + 1) == ':')
	return path + 2;
#endif
    }
  return path;
}

// Return just the directory, or NULL if no directory
char *
wxPathOnly (char *path)
{
  if (path && *path)
    {
      static char buf[_MAXPATHLEN];

      // Local copy
      strcpy (buf, path);

      int l = strlen(path);
      Bool done = FALSE;

      int i = l - 1;

      // Search backward for a backward or forward slash
      while (!done && i > -1)
      {
	if (path[i] == '/' || path[i] == '\\')
	{
	  done = TRUE;
	  buf[i] = 0;
	  return buf;
	}
	else i --;
      }

/* there's a bug here somewhere, so replaced with my original code.
      char *tcp;
      // scan back
      for (tcp = &buf[strlen (buf) - 1]; tcp >= buf; tcp--)
	{
	  // Search for Unix or Dos path sep {'\\', '/'}
	  if (*tcp == '\\' || *tcp == '/')
	    {
	      *tcp = '\0';
	      return buf;
	    }
	}                       // for()
*/
#ifdef wx_msw
      // Try Drive specifier
      if (isalpha (buf[0]) && buf[1] == ':')
	{
	  // A:junk --> A:. (since A:.\junk Not A:\junk)
	  buf[2] = '.';
	  buf[3] = '\0';
	  return buf;
	}
#endif
    }

  return NULL;
}
#endif


void tTrackWin::OnMenuCommand(int id)
{
  char *s;

  switch (id)
  {

    #ifdef wx_msw
    case MEN_DEVICE:
      {
	long idev = -1, odev = -1;  // dummy, SettingsDlg writes to cfg-file
	tWinPlayer::SettingsDlg(idev, odev);
	wxMessageBox("restart jazz to activate changes", "Info");
      }
      break;
    #endif

    case MEN_SAVE_THRU:
      SaveThruSettings();
      break;

    case MEN_SAVE_TIM:
      SaveTimingSettings();
      break;

    case MEN_SAVE_EFF:
      SaveEffectSettings();
      break;

    case MEN_SAVE_GEO:
      SaveGeoSettings();
      break;

    case MEN_SAVE_ALL:
      SaveThruSettings();
      SaveTimingSettings();
      SaveEffectSettings();
      SaveGeoSettings();
      break;

    case MEN_RHYTHM:
      if (!rhythm_win)
	rhythm_win = new tRhythmWin(this, Song);
      rhythm_win->Show(TRUE);
      break;

    case MEN_HARMONY:
      harmony_browser(this);
      break;

    case MEN_NEW:
      if (MixerForm) {
	wxMessageBox("Quit parts dialog first.");
	break;
      }
      if (wxMessageBox("Clear Song?", "Shure?", wxOK | wxCANCEL) == wxOK)
      {
	Song->Clear();
	Redraw();
	delete [] defsong;
	defsong = copystring("noname.mid");
	SetTitle(defsong);
	NextWin->NewPosition(1, 0);
      }
      break;

    case MEN_LOADPATTERN:
      MenLoadPattern();
      break;

    case MEN_SAVEPATTERN:
      MenSavePattern();
      break;

    case MEN_LOAD:
      if (MixerForm) {
	wxMessageBox("Quit parts dialog first.");
	break;
      }
      s = file_selector(defsong, "Load File", 0, tTrack::changed);
      if (s)
      {
	tStdRead io;
	Song->Clear();
	Song->Read(io, s);
	SetTitle(s);
	NextWin->NewPosition(1, 0);
	Redraw();
	tTrack::changed = 0;
      }
      break;

    case MEN_SAVEAS:
      {
	char *s = file_selector(defsong, "Save File", 1);
	if (s)
	{
	  tStdWrite io;
	  Song->Write(io, s);
	  SetTitle(s);
	  tTrack::changed = 0;
	}
      }
      break;

    case MEN_SAVE:
      if (strcmp(defsong, "noname.mid") == 0)
        OnMenuCommand(MEN_SAVEAS);
      else
      {
	tStdWrite io;
	Song->Write(io, defsong);
	tTrack::changed = 0;
      }
      break;


    case MEN_QUIT:
      if ( OnClose() == FALSE)
        return;
      delete this;
      break;

    case MEN_TWSETTING: SettingsDialog(0); break;
    case MEN_COPY:      MenCopy(); break;
    case MEN_QUANTIZE:  MenQuantize(); break;
    case MEN_DELETE:    MenDelete(); break;
    case MEN_SETCHAN:   MenSetChannel(); break;
    case MEN_TRANSP:    MenTranspose(); break;
    case MEN_VELOC:     MenVelocity(); break;
    case MEN_LENGTH:    MenLength(); break;
    case MEN_SHIFT:     MenShift(GetPianoWin()->SnapClocks()); break;
    case MEN_CLEANUP:   MenCleanup(); break;
    case MEN_SEARCHREP: MenSearchReplace(); break;
    case MEN_METERCH:   MenMeterChange(); break;
    case MEN_RESET:     Midi->AllNotesOff(1); break;

    case MEN_SONG:      MenSongSettings(); break;
    case MEN_TSPLIT:
      if (MixerForm) {
	wxMessageBox("Quit parts dialog first.");
	break;
      }
      MenSplitTracks();
      break;
    case MEN_TMERGE:
      if (MixerForm) {
	wxMessageBox("Quit parts dialog first.");
	break;
      }
      MenMergeTracks();
      break;

    case MEN_UNDO:
      Song->Undo();
      Redraw();
      NextWin->Redraw();
      break;

    case MEN_DEBUG:
	    break;

    case MEN_MIXER:
      MenMixer();
      break;

    case MEN_MASTER:
      MenMaster();
      break;

    case MEN_VIBRATO:
      MenVibrato();
      break;

    case MEN_SOUND:
      MenSound();
      break;

    case MEN_ENVELOPE:
      MenEnvelope();
      break;

    case MEN_BEND_BASIC:
      MenBendBasic();
      break;

    case MEN_BEND_LFO1:
      MenBendLfo1();
      break;

    case MEN_BEND_LFO2:
      MenBendLfo2();
      break;

    case MEN_MOD_BASIC:
      MenModBasic();
      break;

    case MEN_MOD_LFO1:
      MenModLfo1();
      break;

    case MEN_MOD_LFO2:
      MenModLfo2();
      break;

    case MEN_CAF_BASIC:
      MenCAfBasic();
      break;

    case MEN_CAF_LFO1:
      MenCAfLfo1();
      break;

    case MEN_CAF_LFO2:
      MenCAfLfo2();
      break;

    case MEN_PAF_BASIC:
      MenPAfBasic();
      break;

    case MEN_PAF_LFO1:
      MenPAfLfo1();
      break;

    case MEN_PAF_LFO2:
      MenPAfLfo2();
      break;

    case MEN_CC1_BASIC:
      MenCC1Basic();
      break;

    case MEN_CC1_LFO1:
      MenCC1Lfo1();
      break;

    case MEN_CC1_LFO2:
      MenCC1Lfo2();
      break;

    case MEN_CC2_BASIC:
      MenCC2Basic();
      break;

    case MEN_CC2_LFO1:
      MenCC2Lfo1();
      break;

    case MEN_CC2_LFO2:
      MenCC2Lfo2();
      break;

    case MEN_DRUM_PARAM:
      MenDrumParam();
      break;

    case MEN_PART_RSRV:
      MenPartRsrv();
      break;

    case MEN_PART_MODE:
      MenPartMode();
      break;

    case MEN_FILTER:
      Filter->Dialog(0);
      break;

    case MEN_EFFECTS:
      MenEffects();
      break;

    case MEN_TIMING:
      MenTiming();
      break;

    case MEN_MIDI_THRU:
      MenMidiThru();
      break;

    case MEN_COPYRIGHT:
      MenCopyright();
      break;

    case MEN_ABOUT:
      extern const char *about_text;
      wxMessageBox( (char *) about_text, "About");
      break;

    case MEN_HELP_MOUSE:
      wxbMessageBox( (char *)
	"topline:\n"
	"  left: start/stop record/play\n"
	"    +shift: start/stop cycle record/play\n"
	"  middle: same as left+shift\n"
	"  right: start/stop record/play, mute selected track\n"
	"\n"
	"events:\n"
	"  left: select events\n"
	"    +shift: continue selection\n"
	"  right: open and position pianowin\n", "Help", wxOK);
      break;
    case MEN_HELP_JAZZ:
#ifndef VMS
      HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
      HelpInstance->DisplayContents();
#endif
      break;
    case MEN_HELP_TWIN:
#ifndef VMS
      HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
      HelpInstance->KeywordSearch("Track Window");
#endif
      break;
  }
}


// ************************************************************************
// Painting
// ************************************************************************



void tTrackWin::DrawCounters()
{
  char *str;
  int i;
  switch (CounterMode)
  {
    case CmProgram: str = "Prg"; break;
    case CmBank   : str = "Bnk"; break;
    case CmVolume : str = "Vol"; break;
    case CmPan    : str = "Pan"; break;
    case CmReverb : str = "Rev"; break;
    case CmChorus : str = "Cho"; break;
    default       : str = "???"; break;
  }
  LineText(xPatch, yy-1, wPatch, str, hTop);

  dc->SetClippingRegion(xPatch, yEvents, xPatch + wPatch, yEvents + hEvents);
  for (i = FromLine; i < ToLine; i++)
  {
    tTrack *t = Song->GetTrack(i);
    if (t)
    {
      char buf[20];
      int  val;
      switch (CounterMode)
      {
	case CmProgram: val = t->GetPatch(); break;
	case CmBank   : val = t->GetBank(); break;
	case CmVolume : val = t->GetVolume(); break;
	case CmPan    : val = t->GetPan(); break;
	case CmReverb : val = t->GetReverb(); break;
	case CmChorus : val = t->GetChorus(); break;
	default : val = 0; break;
      }
      sprintf(buf, "%3d", val);
      LineText(xPatch, Line2y(i), wPatch, buf);
    }
    else
      LineText(xPatch, Line2y(i), wPatch, "?");
  }
  dc->DestroyClippingRegion();
}




void tTrackWin::DrawNumbers()
{
  char *str;
  int i;
  switch (NumberMode)
  {
    case NmTrackNr     : str = "T"; break;
    case NmMidiChannel : str = "M"; break;
    default            : str = "?"; break;
  }
  LineText(xNumber, yy-1, wNumber, str, hTop);

  dc->SetClippingRegion(xNumber, yEvents, xNumber + wNumber, yEvents + hEvents);
  for (i = FromLine; i < ToLine; i++)
  {
    tTrack *t = Song->GetTrack(i);
    if (t)
    {
      char buf[20];
      int  val;
      switch (NumberMode)
      {
	case NmTrackNr: val = i; break;
	case NmMidiChannel : val = t->Channel; break;
	default : val = 0; break;
      }
      sprintf(buf, "%02d", val);
      LineText(xNumber, Line2y(i), wNumber, buf);
    }
    else
      LineText(xNumber, Line2y(i), wNumber, "?");
  }
  dc->DestroyClippingRegion();
}



void tTrackWin::DrawSpeed(int Value)
{
  char buf[50];
  if (Value < 0)
    Value = Song->GetTrack(0)->GetSpeed();
  sprintf(buf, "speed: %3d", Value);
  LineText(xName, yy-1, wName, buf, hTop);
}


void tTrackWin::OnPaint(long x, long y)
{
  tEventWin::OnPaint(x, y);

  xNumber  = xx;
  xName    = xNumber  + wNumber;
  xState   = xName    + wName;
  xPatch   = xState   + wState;

  long StopClk;
  tBarInfo BarInfo(Song);
  char buf[20];

  dc->BeginDrawing();
  dc->DestroyClippingRegion();
  dc->SetBackground(wxWHITE_BRUSH);
  DrawPlayPosition();
  SnapSel->Draw(xEvents, yEvents, wEvents, hEvents);
  dc->Clear();

  // clear playposition and selection

  #define VLine(x) DrawLine(x,  yy, x, yEvents+hEvents)
  #define HLine(y) DrawLine(xx, y, xx + ww, y)

  dc->SetPen(wxBLACK_PEN);

  // vertikale Linien
  dc->VLine(xNumber);
  dc->VLine(xName);
  dc->VLine(xState);
  dc->VLine(xPatch);
  dc->VLine(xEvents);
  dc->VLine(xEvents-1);
  dc->HLine(yEvents);
  dc->HLine(yEvents-1);


  // Taktstriche und -nummern

  BarInfo.SetClock(FromClock);
  StopClk = x2Clock(xx + ww);
  nBars = 0;
  dc->SetPen(wxGREY_PEN);
  while (1)
  {
    x = Clock2x(BarInfo.Clock);
    if (x > xx + ww)
      break;
    if (x >= xEvents)   // so ne Art clipping
    {
      if ((BarInfo.BarNr % 4) == 0)
      {
	dc->SetPen(wxBLACK_PEN);
	sprintf(buf, "%d", BarInfo.BarNr + 1);
	dc->DrawText(buf, x + LittleBit, yEvents - hLine);
	dc->DrawLine(x, yEvents - hLine, x, yEvents + hEvents);
	dc->SetPen(wxGREY_PEN);
      }
      else
	dc->DrawLine(x, yEvents, x, yEvents+hEvents);

      if (nBars < MaxBars)      // x-Koordinate fuer MouseAction->Snap()
	xBars[nBars++] = x;

    }
    BarInfo.Next();
  }
  dc->SetPen(wxBLACK_PEN);


  DrawNumbers();
  DrawSpeed();
  DrawCounters();


  // je Track Nummer, Name, State, Patch anzeigen

  dc->SetClippingRegion(xx, yEvents, xx+ww, yEvents + hEvents);
  int TrackNr = FromLine;
  for (y = Line2y(TrackNr); y < yEvents + hEvents; y += hLine)
  {
    dc->HLine(y);
    tTrack *Track = Song->GetTrack(TrackNr);
    if (Track)
    {
      // TrackName
      dc->DrawText(Track->GetName(), xName + LittleBit, y + LittleBit);
      // TrackStatus
      dc->DrawText(Track->GetStateChar(), xState + LittleBit, y + LittleBit);
    }
    ++ TrackNr;
  }
  dc->DestroyClippingRegion();

  // Events zeichnen
  dc->SetClippingRegion(xEvents, yEvents, wEvents, hEvents);
  TrackNr = FromLine;
  for (y = Line2y(TrackNr); y < yEvents + hEvents; y += hLine)
  {
    tTrack *Track = Song->GetTrack(TrackNr);
    if (Track)
    {
      tEventIterator Iterator(Track);
      tEvent *e = Iterator.Range(FromClock, StopClk);
      float y0 = y + LittleBit;
      float y1 = y + hLine - LittleBit;

      if (UseColors)
      {
	while (e)       // slow!
	{
	  float x = Clock2x(e->Clock);
	  dc->SetPen(e->GetPen());
	  dc->DrawLine(x, y0, x, y1);
	  e = Iterator.Next();
	}
	dc->SetPen(wxBLACK_PEN);
      }
      else
      {
	float xblack = -1.0;
	while (e)
	{
	  float x = Clock2x(e->Clock);

	  // Avoid painting events ON the bar
	  if ( !(e->Clock % BarInfo.TicksPerBar) ) x = x + 1;

	  if (x > xblack)
	  {
	    dc->DrawLine(x, y0, x, y1);
#ifndef SLOW_MACHINE
	    xblack = x;
#else
	    xblack = x + 4;
#endif
	  }
	  e = Iterator.Next();
	}
      }
    }

    ++ TrackNr;
  }

  // strange bug? after SetPen(GREY_PEN) XOR-Painting doesnt work anymore
  // unless SetBackground(WHITE_BRUSH) is called
  dc->SetBackground(wxWHITE_BRUSH);

  SnapSel->Draw(xEvents, yEvents, wEvents, hEvents);

  if (Marked.x > 0)
    LineText((long)Marked.x, (long)Marked.y, (long)Marked.w, ">");

  dc->DestroyClippingRegion();
  DrawPlayPosition();
  dc->EndDrawing();
}

// ************************************************************************
// Utilities
// ************************************************************************

long tTrackWin::x2xBar(long x)
{
  for (int i = 1; i < nBars; i++)
    if (x < xBars[i])
      return xBars[i - 1];
  return -1;
}


long tTrackWin::x2wBar(long x)
{
  for (int i = 1; i < nBars; i++)
    if (x < xBars[i])
      return xBars[i] - xBars[i - 1];
  return 0;
}


tTrack *tTrackWin::y2Track(long y)
{
  return Song->GetTrack(y2Line(y));
}

void tTrackWin::Mark(long x, long y)
{
  Marked.x = x2xBar(x);
  Marked.y = y2yLine(y);
  Marked.w = x2wBar(x);
  Marked.h = hLine;
  LineText((int)Marked.x, (int)Marked.y, (int)Marked.w, ">");
}

void tTrackWin::UnMark()
{
  Marked.x = -1;
}


// ********************************************************************
// PlayPosition
// ********************************************************************

void tTrackWin::NewPlayPosition(long Clock)
{
  if (!SnapSel->Active && ((Clock > (FromClock + 5 * ToClock) / 6L) || (Clock < FromClock)) && (Clock >= 0L) )
  {
    long x = Clock2x(Clock);
    Canvas->SetScrollPosition(x - wLeft, yy);
  }
  tEventWin::NewPlayPosition(Clock);
}

// ********************************************************************
// Snapper
// ********************************************************************

void tTrackWin::SnapSelStart(wxMouseEvent &e)
{
  SnapSel->SetXSnap(nBars, xBars);
  SnapSel->SetYSnap(Line2y(FromLine), yEvents + hEvents, hLine);
}


void tTrackWin::SnapSelStop(wxMouseEvent &e)
{
  if (SnapSel->Selected)
  {
    Filter->FromTrack = y2Line((long)SnapSel->r.y);
    Filter->ToTrack   = y2Line((long)(SnapSel->r.y + SnapSel->r.h - 1));
    Filter->FromClock = x2BarClock((long)SnapSel->r.x + 1);
    Filter->ToClock   = x2BarClock((long)(SnapSel->r.x + SnapSel->r.w + 1));
  }
}



// --------------------------------------------------------------------
// Tracknummer
// --------------------------------------------------------------------

void tTrackWin::MouseNumber(wxMouseEvent &e)
{
  if (e.LeftDown())
  {
    float x, y;
    e.Position(&x, &y);
    tTrack *t = y2Track((long)y);
    if (t != 0)
    {
      tRect r;
      r.x = 0;
      r.y = y2yLine((long)y);
      r.w = Clock2x(t->GetLastClock()) + 1000;
      r.h = hLine;
      SnapSel->Select(r, xEvents, yEvents, wEvents, hEvents);
      SnapSelStop(e);
    }
  }
}


// --------------------------------------------------------------------
// PatchCounter
// --------------------------------------------------------------------

class tPatchCounter : public tMouseCounter
{
    tTrackWin *tw;
  public:
    int Event(wxMouseEvent &e);
    tPatchCounter(tTrackWin *t, tRect *r, int v, int min, int max)
      : tMouseCounter(t->dc, r, v, min, max)
    {
      tw = t;
    }
};


int tPatchCounter::Event(wxMouseEvent &e)
{
  if (tMouseCounter::Event(e))
  {
    tTrack *t = tw->y2Track((long)r.y);
    if (t)
    {
      switch (tw->CounterMode)
      {
	case CmProgram : t->SetBank( t->GetBank() ); t->SetPatch(Value); break;
	case CmBank    : t->SetBank(Value); t->SetPatch( t->GetPatch() ); break;
	case CmVolume  : t->SetVolume(Value);
			 tMixerDlg::SetSliderVal( t->Channel - 1, MxVol, Value );
			 break;
	case CmPan     : t->SetPan(Value);
			 tMixerDlg::SetSliderVal( t->Channel - 1, MxPan, Value );
			 break;
	case CmReverb  : t->SetReverb(Value);
			 tMixerDlg::SetSliderVal( t->Channel - 1, MxRev, Value );
			 break;
	case CmChorus  : t->SetChorus(Value);
			 tMixerDlg::SetSliderVal( t->Channel - 1, MxCho, Value );
			 break;
	default: break;
      }
    }
    tw->MouseAction = 0;
    delete this;
  }
  return 0;
}


void tTrackWin::MousePatch(wxMouseEvent &e)
{
  float x, y;

  if (!e.LeftDown() && !e.RightDown())
    return;

  e.Position(&x, &y);
  tTrack *t = y2Track((long)y);
  if (t)
  {
    tRect r;
    int Value;
    switch (CounterMode)
    {
      case CmProgram: Value = t->GetPatch(); break;
      case CmBank   : Value = t->GetBank(); break;
      case CmVolume : Value = t->GetVolume(); break;
      case CmPan    : Value = t->GetPan(); break;
      case CmReverb : Value = t->GetReverb(); break;
      case CmChorus : Value = t->GetChorus(); break;
      default       : Value = 0; break;
    }
    r.x = xPatch + LittleBit;
    r.y = y2yLine((long)y) + LittleBit;
    r.w = wPatch - LittleBit;
    r.h = hLine - LittleBit;

    tPatchCounter *PatchCounter = new tPatchCounter(this, &r, Value, 0, 128);
    PatchCounter->Event(e);
    MouseAction = PatchCounter;
  }
}

// --------------------------------------------------------------------
// SpeedCounter
// --------------------------------------------------------------------

class tSpeedCounter : public tMouseCounter
{
    tTrackWin *tw;
  public:
    int Event(wxMouseEvent &e);
    tSpeedCounter(tTrackWin *t, tRect *r, int v, int min, int max)
      : tMouseCounter(t->dc, r, v, min, max)
    {
      tw = t;
    }
    void ShowValue();
};


int tSpeedCounter::Event(wxMouseEvent &e)
{
  if (tMouseCounter::Event(e))
  {
    tTrack *t = tw->Song->GetTrack(0);
    t->SetSpeed(Value);
    tw->MouseAction = 0;
    delete this;
  }
  return 0;
}

void tSpeedCounter::ShowValue()
{
  tw->DrawSpeed(Value);
}

void tTrackWin::MouseSpeed(wxMouseEvent &e)
{
  tRect r;
  int Value;

  if (!e.LeftDown() && !e.RightDown())
    return;

  Value = Song->GetTrack(0)->GetSpeed();
  tSpeedCounter *SpeedCounter = new tSpeedCounter(this, &r, Value, 20, 250);
  SpeedCounter->Event(e);
  MouseAction = SpeedCounter;
}

// -----------------------------------------------------------------------
// Track-Status
// -----------------------------------------------------------------------


void tTrackWin::MouseState(wxMouseEvent &e)
{
  if (e.LeftDown() || e.RightDown())
  {
    float x, y;
    e.Position(&x, &y);
    tTrack *t = y2Track((long)y);
    if (t)
    {
      t->ToggleState(e.LeftDown() ? 1 : -1); // toggle
      LineText(xState, (long)y, wState, t->GetStateChar());
    }
  }
}


// --------------------------------------------------------------------------
// Name
// --------------------------------------------------------------------------

void tTrackWin::MouseName(wxMouseEvent &e)
{
  if (e.LeftDown())
  {
    float x, y;
    e.Position(&x, &y);
    tTrack *t = y2Track((long)y);
    if (t && !MixerForm) {
      t->Dialog(this);
    }
    else {
      wxMessageBox("Quit parts dialog first.");
    }
  }
}

// --------------------------------------------------------------------------
// Event-Bereich
// --------------------------------------------------------------------------

void tTrackWin::MouseEvents(wxMouseEvent &e)
{
  if (e.RightDown())
  {
    float x, y;
    e.Position(&x, &y);
    int TrackNr = y2Line((long)y);
    long Clock  = x2BarClock((long)x);
    NextWin->NewPosition(TrackNr, Clock);
    NextWin->Show(TRUE);
  }
  else
    tEventWin::OnMouseEvent(e);
}

// --------------------------------------------------------------------------
// Playbar
// --------------------------------------------------------------------------

/*
 * not playing:
 *  events selected:
 *    left : start rec/play
 *    right: mute + start rec/play
 *  no events selected:
 *    left+right: start play
 * playing:
 *  left+right: stop
 */

void tTrackWin::MousePlay(wxMouseEvent *e)
{
  // e == 0: space bar hit
  if (!e || e->ButtonDown())
  {
    if (e && !Midi->Playing)
    {
      float x, y;
      e->Position(&x, &y);
      prev_clock = x2BarClock((long)x);
      prev_muted = (e->RightDown() != 0);
      if (SnapSel->Selected && (e->ShiftDown() || e->MiddleDown()))
	prev_loop = TRUE;
      else
        prev_loop = FALSE;
    }

    long Clock = prev_clock;
    Bool loop  = prev_loop;
    Bool muted  = prev_muted;

    if (!Midi->Playing)         // Start Record/Play
    {
      if (SnapSel->Selected)
      {
	RecInfo.TrackNr   = Filter->FromTrack;
	RecInfo.Track     = Song->GetTrack(RecInfo.TrackNr);
	RecInfo.FromClock = Filter->FromClock;
	RecInfo.ToClock   = Filter->ToClock;
	if (muted)
	{
	  RecInfo.Muted = 1;
	  RecInfo.Track->SetState(tsMute);
	  LineText(xState, Line2y(RecInfo.TrackNr), wState, RecInfo.Track->GetStateChar());
	}
	else
	  RecInfo.Muted = 0;
      }
      else
	RecInfo.Track = 0;
      long LoopClock = 0;
      if (RecInfo.Track && loop)
      {
	Clock     = RecInfo.FromClock;
	LoopClock = RecInfo.ToClock;
      }
      Midi->StartPlay(Clock, LoopClock);
    }

    else                        // Stop Record/Play
    {
      Midi->StopPlay();
      if (RecInfo.Track)
      {
	if (RecInfo.Muted)
	{
	  RecInfo.Track->SetState(tsPlay);
	  LineText(xState, Line2y(RecInfo.TrackNr), wState, RecInfo.Track->GetStateChar());
	}
	if (!Midi->RecdBuffer.IsEmpty())
	{
	  int choice = wxMessageBox("Keep recorded events?", "You played", wxOK | wxCANCEL);
	  if (choice == wxOK)
	  {
	    wxBeginBusyCursor();
	    Song->NewUndoBuffer();
	    RecInfo.Track->MergeRange(&Midi->RecdBuffer, RecInfo.FromClock, RecInfo.ToClock, RecInfo.Muted);
	    wxEndBusyCursor();
	    Redraw();
	    NextWin->Redraw();
	  }
	}
      }
    }
  }
}


int tTrackWin::OnKeyEvent(wxKeyEvent &e)
{
  if (e.KeyCode() == ' ')
  {
    MousePlay(0);
    return 1;
  }
  return 0;
}



// -----------------------------------------------------------------------
// event dispatcher
// -----------------------------------------------------------------------


int tTrackWin::OnMouseEvent(wxMouseEvent &e)
{

  if (!MouseAction)
  {
    float x, y;
    e.Position(&x, &y);
    if (y > yEvents)
    {
      // Events
      if (xNumber < x && x < xNumber + wNumber)
	MouseNumber(e);
      else if (xName < x && x < xName + wName)
	MouseName(e);
      else if(xState < x && x < xState + wState)
	MouseState(e);
      else if (xPatch < x && x < xPatch + wPatch)
	MousePatch(e);
      else if (xEvents < x && x < xEvents + wEvents)
	MouseEvents(e);
      else
	tEventWin::OnMouseEvent(e);
    }
    else
    {
      // Playbar

      if (xNumber < x && x < xNumber + wNumber)
      {
	if (e.LeftDown())
	{
	  NumberMode = (tNumberModes)(((int)NumberMode + 1) % (int)NmModes);
	  DrawNumbers();
	}
      }
      else if (xName < x && x < xName + wName)
	MouseSpeed(e);
      else if(xState < x && x < xState + wState)
	;
      else if (xPatch < x && x < xPatch + wPatch)
      {
	if (e.LeftDown())
	{
	  CounterMode = (tCounterModes)(((int)CounterMode + 1) % (int)CmModes);
	  DrawCounters();
	}
	else if (e.RightDown())
	{
	  CounterMode = (tCounterModes)(((int)CounterMode + (int)CmModes - 1) % (int)CmModes);
	  DrawCounters();
	}
      }
      else if (xEvents < x && x < xEvents + wEvents)
	MousePlay(&e);
      else
	tEventWin::OnMouseEvent(e);
    }
  }

  else
    tEventWin::OnMouseEvent(e);

  return 0;
}



// **************************************************************************
// Copy
// **************************************************************************


class tCopyDlg;

class tCopyCommand : public wxObject, public tMouseAction
{
    tTrackWin *tw;
    int MarkRepeat;
    float StartX, StartY, StopX, StopY;

    tMarkDestin *Mouse;
    tCopyDlg *CopyDlg;

  public:
    static Bool RepeatCopy;
    static Bool EraseSource;
    static Bool EraseDestin;
    static Bool InsertSpace;

    int Event(wxMouseEvent &e);
    tCopyCommand(tTrackWin *t);
    void EditForm(wxPanel *panel);
    void OnOk();
    void OnCancel();
    void Execute(int doit);
};

Bool tCopyCommand::EraseDestin = 1;
Bool tCopyCommand::RepeatCopy = 0;
Bool tCopyCommand::EraseSource = 0;
Bool tCopyCommand::InsertSpace = 0;


class tCopyDlg : public wxForm
{
  tCopyCommand *cp;

 public:
  tCopyDlg(tCopyCommand *c)
	: wxForm( USED_WXFORM_BUTTONS ) { cp = c; }
  void OnOk()                           { cp->OnOk(); wxForm::OnOk(); }
  void OnCancel()                       { cp->OnCancel(); wxForm::OnCancel(); }
  void OnHelp();
  void EditForm(wxPanel *panel);
};

void tCopyDlg::OnHelp()
{
#ifndef VMS
	HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
	HelpInstance->KeywordSearch("Replicate");
#endif
}


void tCopyDlg::EditForm(wxPanel *panel)
{
  Add(wxMakeFormBool("Erase Destin", &cp->EraseDestin));
  Add(wxMakeFormBool("Repeat Copy", &cp->RepeatCopy));
  Add(wxMakeFormNewLine());
  Add(wxMakeFormBool("Erase Source", &cp->EraseSource));
  Add(wxMakeFormBool("Insert Space", &cp->InsertSpace));
  AssociatePanel(panel);
}



tCopyCommand::tCopyCommand(tTrackWin *t)
{
  tw = t;
  MarkRepeat = 0;
  Mouse = new tMarkDestin(tw->Canvas, tw, 0);
  CopyDlg = 0;
}


int tCopyCommand::Event(wxMouseEvent &e)
{
  if (!CopyDlg && Mouse && Mouse->Event(e))
  {
    if (Mouse->Aborted)
    {
      if (CopyDlg)
	CopyDlg->OnCancel();
      else
	Execute(0);
    }
    else if (!MarkRepeat)
    {
      e.Position(&StartX, &StartY);
      tw->Mark((long)StartX, (long)StartY);
      wxDialogBox *panel = new wxDialogBox(tw, "Replicate", FALSE );
      CopyDlg = new tCopyDlg(this);
      CopyDlg->EditForm(panel);
      panel->Fit();
      panel->Show(TRUE);
    }
    else
    {
      e.Position(&StopX, &StopY);
      Execute(1);
    }
  }
  return 0;
}


void tCopyCommand::OnOk()
{
  CopyDlg = 0;
  if (RepeatCopy)
  {
    delete Mouse;
    Mouse = new tMarkDestin(tw->Canvas, tw, 1);
    MarkRepeat = 1;
  }
  else
    Execute(1);
}


void tCopyCommand::OnCancel()
{
  CopyDlg = 0;
  Execute(0);
}


void tCopyCommand::Execute(int doit)
{

  if (doit)
  {
    long DestTrack = tw->y2Line((long)StartY);
    long DestClock = tw->x2BarClock((long)StartX);
    tCmdCopy cpy(tw->Filter, DestTrack, DestClock);

    if (RepeatCopy)
      cpy.RepeatClock = tw->x2BarClock((long)StopX, 1);
    cpy.EraseSource = EraseSource;
    cpy.EraseDestin = EraseDestin;
    cpy.InsertSpace = InsertSpace;
    cpy.Execute();
  }

  tw->UnMark();
  tw->MouseAction = 0;
  tw->Redraw();
  tw->NextWin->Redraw();
  delete Mouse;
  delete this;
}


void tTrackWin::MenCopy()
{
  if (!EventsSelected())
    return;
  MouseAction = new tCopyCommand(this);
}

// ******************************************************************
// Song-Settings Dialog
// ******************************************************************



class tSongSettingsDlg : public wxForm
{
 public:
  tEventWin *EventWin;
  int TicksPerQuarter;
  tSongSettingsDlg(tEventWin *w);
  void EditForm(wxPanel *panel);
  virtual void OnOk();
  virtual void OnCancel();
  virtual void OnHelp();
};



tSongSettingsDlg::tSongSettingsDlg(tEventWin *w)
: wxForm( USED_WXFORM_BUTTONS )
{
  EventWin = w;
  TicksPerQuarter = EventWin->Song->TicksPerQuarter;
}


void tSongSettingsDlg::OnHelp()
{
#ifndef VMS
	HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
	HelpInstance->KeywordSearch("Song");
#endif
}


void tSongSettingsDlg::OnCancel()
{
  EventWin->DialogBox = 0;
  wxForm::OnCancel();
}


void tSongSettingsDlg::OnOk()
{
  EventWin->Song->SetTicksPerQuarter(TicksPerQuarter);
  EventWin->Redraw();
  EventWin->DialogBox = 0;
  wxForm::OnOk();
}



void tSongSettingsDlg::EditForm(wxPanel *panel)
{
  Add(wxMakeFormMessage("Supported ticks/quarter: 48, 72, 96, 120, 144, 168, 192"));
  Add(wxMakeFormNewLine());
  Add(wxMakeFormShort("value:", &TicksPerQuarter, wxFORM_DEFAULT,
                       new wxList(wxMakeConstraintRange(48.0, 192.0), 0)));
  AssociatePanel(panel);
}


void tTrackWin::MenSongSettings()
{
  tSongSettingsDlg *dlg;
  if (DialogBox)
  {
    DialogBox->Show(TRUE);
    return;
  }
  DialogBox = new wxDialogBox(this, "Song Settings", FALSE );
  dlg = new tSongSettingsDlg(this);
  dlg->EditForm(DialogBox);
  DialogBox->Fit();
  DialogBox->Show(TRUE);
}


// *************************************************************************************
// Split / Merge Tracks
// *************************************************************************************

void tTrackWin::MenMergeTracks()
{
  int choice = wxMessageBox("Merge all Tracks to Track 0?", "Shure?", wxOK | wxCANCEL);
  if (choice == wxOK)
  {
    int tn;
    Song->NewUndoBuffer();
    tTrack *dst = Song->GetTrack(0);
    for (tn = 1; tn < Song->nTracks; tn++)
    {
      tTrack *src = Song->GetTrack(tn);
      tEventIterator Iterator(src);
      tEvent *e = Iterator.First();
      while (e)
      {
	tEvent *c = e->Copy();
	src->Kill(e);
	dst->Put(c);
	e = Iterator.Next();
      }
      src->Cleanup();
    }
    dst->Cleanup();
    Redraw();
  }
}


void tTrackWin::MenSplitTracks()
{
  int choice = wxMessageBox("Split Track 0 by Midi-Channel to Track 1..16?", "Shure?", wxOK | wxCANCEL);
  if (choice == wxOK)
  {
    int ch;
    Song->NewUndoBuffer();
    tTrack *src = Song->GetTrack(0);
    tEventIterator Iterator(src);
    tEvent *e = Iterator.First();
    while (e)
    {
      tChannelEvent *ce = e->IsChannelEvent();
      if (ce)
      {
	int cn = ce->Channel;
	tTrack *dst = Song->GetTrack(cn + 1);
	if (dst)
	{
	  tEvent *cp = ce->Copy();
	  src->Kill(ce);
	  dst->Put(cp);
	}
      }
      e = Iterator.Next();
    }

    for (ch = 0; ch <= 16; ch++)
    {
      src = Song->GetTrack(ch);
      if (src)
	src->Cleanup();
    }
    Redraw();
  }
}

// ******************************************************************
// Midi Timing Dialog
// ******************************************************************

class tMidiButton;

class tTimingDlg : public wxForm
{
 public:
  tEventWin *EventWin;
  tTimingDlg(tEventWin *w);
  void EditForm(wxPanel *panel);
  virtual void OnOk();
  virtual void CloseWindow();
  virtual void OnHelp();
  char *ClkSrcArray[4];
  char *MtcTypeArray[4];
  wxListBox *ClkSrcListBox;
  wxListBox *MtcTypeListBox;
  wxCheckBox *RealTimeCheckBox;
  wxText *MtcOffsetEntry;
  void MtcInitRec();
  void MtcFreezeRec();
  static void MtcRecFunc( tMidiButton& button, wxCommandEvent& event );
  static void OkFunc( tMidiButton& button, wxCommandEvent& event );
  static void CancelFunc( tMidiButton& button, wxCommandEvent& event );
  static void HelpFunc( tMidiButton& button, wxCommandEvent& event );
};

class tMidiButton : public wxButton {
  public:
    tMidiButton( tTimingDlg *dlg,
		 wxPanel *panel, wxFunction func, char *label, int x = -1, int y = -1,
		 int width = -1, int height = -1, long style = 0, char *name = "button")
    : wxButton( panel, func, label, x, y, width, height, style, name )
    {
      midiDlg = dlg;
    }

    void OnOk()
    {
      midiDlg->OnOk();
    }

    void OnCancel()
    {
      midiDlg->CloseWindow();
    }

    void OnHelp()
    {
      midiDlg->OnHelp();
    }

    void OnInitRec()
    {
      midiDlg->MtcInitRec();
    }

    void OnFreezeRec()
    {
      midiDlg->MtcFreezeRec();
    }
  private:
    tTimingDlg *midiDlg;
};

void tTimingDlg::OkFunc( tMidiButton& button, wxCommandEvent& event )
{
  button.OnOk();
}

void tTimingDlg::CancelFunc( tMidiButton& button, wxCommandEvent& event )
{
  button.OnCancel();
}

void tTimingDlg::HelpFunc( tMidiButton& button, wxCommandEvent& event )
{
  button.OnHelp();
}

void tTimingDlg::MtcRecFunc( tMidiButton& button, wxCommandEvent& event )
{
  if ( !strcmp( button.GetLabel(), "Start" ) )
  {
    button.OnInitRec();
    button.SetLabel( "Freeze" );
  }
  else
  {
    button.OnFreezeRec();
    button.SetLabel( "Start" );
  }
}

void tTimingDlg::MtcInitRec()
{
#ifdef wx_msw
  if (ClockSource != CsMtc)
  {
    delete Midi;
    ClockSource = CsMtc;
    ClkSrcListBox->SetStringSelection( ClkSrcArray[ ClockSource ] );
    Midi = new tWinMtcPlayer(EventWin->Song);
    if (!Midi->Installed())
    {
      wxMessageBox("no midi driver installed", "Error");
      Midi = new tNullPlayer(EventWin->Song);
    }
  }
#endif
  Midi->InitMtcRec();
}

void tTimingDlg::MtcFreezeRec()
{
  tMtcTime *offs = Midi->FreezeMtcRec();
  if (offs)
  {
    char str[80];
    offs->ToString( str );
    MtcTypeListBox->SetStringSelection( MtcTypeArray[ offs->type ] );
    delete offs;
    MtcOffsetEntry->SetValue( str );
  }
}

tTimingDlg::tTimingDlg(tEventWin *w)
: wxForm( USED_WXFORM_BUTTONS )
{
  EventWin = w;
  ClkSrcArray[CsInt] = "INTERNAL";
  ClkSrcArray[CsFsk] = "FSK";
  ClkSrcArray[CsMidi] = "SONG PTR";
  ClkSrcArray[CsMtc] = "MTC";
  MtcTypeArray[Mtc24] = "24 fm/sec";
  MtcTypeArray[Mtc25] = "25 fm/sec";
  MtcTypeArray[Mtc30Df] = "30 drop";
  MtcTypeArray[Mtc30Ndf] = "30 non-drop";
  ClkSrcListBox = 0;
  MtcTypeListBox = 0;
  RealTimeCheckBox = 0;
  MtcOffsetEntry = 0;
}

void tTimingDlg::CloseWindow()
{
  EventWin->DialogBox->Show( FALSE );
  delete EventWin->DialogBox;
  EventWin->DialogBox = 0;
  delete this;
}


void tTimingDlg::OnOk()
{
  int i;
  char *str = copystring( ClkSrcListBox->GetStringSelection() );
  for (i = 0; i < 4; i++) {
	if (!strcmp(str,ClkSrcArray[i])) {
		break;
	}
  }
  delete str;
  if (i > 3)
    i = CsInt;

  if (i != ClockSource)
  {
    ClockSource = (tClockSource) i;
#ifdef wx_msw
    // Re-install the midi device
    delete Midi;

    // create new player
    switch (ClockSource)
    {
      case CsMidi:
        Midi = new tWinMidiPlayer(EventWin->Song);
        break;
      case CsMtc:
        Midi = new tWinMtcPlayer(EventWin->Song);
        break;
      case CsFsk:
      case CsInt:
      default:
        Midi = new tWinIntPlayer(EventWin->Song);
        break;
    }
    if (!Midi->Installed())
    {
      wxMessageBox("no midi driver installed", "Error");
      Midi = new tNullPlayer(EventWin->Song);
    }
#endif
  }

  tMtcType MtcType;
  str = copystring( MtcTypeListBox->GetStringSelection() );
  for (i = 0; i < 4; i++) {
	if (!strcmp(str,MtcTypeArray[i])) {
		MtcType = (tMtcType) i;
		break;
	}
  }
  delete str;
  if (i > 3)
    MtcType = Mtc30Ndf;
  tMtcTime *offs = new tMtcTime( MtcOffsetEntry->GetValue(), MtcType );
  EventWin->Song->GetTrack(0)->SetMtcOffset( offs );
  delete offs;

  RealTimeOut = RealTimeCheckBox->GetValue();
  EventWin->Redraw();
  CloseWindow();
}

void tTimingDlg::OnHelp()
{
#ifndef VMS
	HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
	HelpInstance->KeywordSearch("Timing");
#endif
}



void tTimingDlg::EditForm(wxPanel *panel)
{

  (void) new tMidiButton( this, panel, (wxFunction) OkFunc, "Ok" );
  (void) new tMidiButton( this, panel, (wxFunction) CancelFunc, "Cancel" );
  (void) new tMidiButton( this, panel, (wxFunction) HelpFunc, "Help" );
  panel->NewLine();

  panel->SetLabelPosition(wxVERTICAL);
  ClkSrcListBox = new wxListBox( panel,
				 NULL,
				 "Clock Source",
				 wxSINGLE|wxALWAYS_SB,
				 -1, -1, -1, -1 );

#ifdef wx_msw
  ClkSrcListBox->Append( ClkSrcArray[CsInt] );
  ClkSrcListBox->Append( ClkSrcArray[CsMidi] );
  ClkSrcListBox->Append( ClkSrcArray[CsMtc] );
#else
  if (Seq2Driver)
  {
    ClkSrcListBox->Append( ClkSrcArray[CsInt] );
  }
  else
  {
    ClkSrcListBox->Append( ClkSrcArray[CsInt] );
    ClkSrcListBox->Append( ClkSrcArray[CsMidi] );
    ClkSrcListBox->Append( ClkSrcArray[CsFsk] );
  }
#endif
  ClkSrcListBox->SetStringSelection( ClkSrcArray[ ClockSource ] );
  panel->NewLine();

  tTrack *t = EventWin->Song->GetTrack(0);
  char str[80];
  tMtcTime *offs = t->GetMtcOffset();
  offs->ToString( str );
  MtcOffsetEntry = new wxText( panel,
			       NULL,
			       "MTC offset",
			       str );

  panel->NewLine();

#ifdef wx_msw
  (void) new wxMessage( panel, "Record MTC offset: " );
  (void) new tMidiButton( this, panel, (wxFunction) MtcRecFunc, "Start" );
  panel->NewLine();
#endif

  MtcTypeListBox = new wxListBox( panel,
				  NULL,
				  "MTC Type",
				  wxSINGLE|wxALWAYS_SB,
				  -1, -1, -1, -1 );
  MtcTypeListBox->Append( MtcTypeArray[0] );
  MtcTypeListBox->Append( MtcTypeArray[1] );
  MtcTypeListBox->Append( MtcTypeArray[2] );
  MtcTypeListBox->Append( MtcTypeArray[3] );
  MtcTypeListBox->SetStringSelection( MtcTypeArray[ offs->type ] );
  delete offs;
  panel->NewLine();

  RealTimeCheckBox = new wxCheckBox( panel,
				     NULL,
				     "Realtime to MIDI Out" );
  RealTimeCheckBox->SetValue( RealTimeOut );
  panel->NewLine();
}


void tTrackWin::MenTiming()
{
  tTimingDlg *dlg;
  if (DialogBox)
  {
    DialogBox->Show(TRUE);
    return;
  }
  DialogBox = new wxDialogBox(this, "MIDI Timing Settings", FALSE );
  dlg = new tTimingDlg(this);
  dlg->EditForm(DialogBox);
  DialogBox->Fit();
  DialogBox->Show(TRUE);
}

// ******************************************************************
// Midi thru dialog
// ******************************************************************

class tMidiThruDlg : public wxForm
{
 public:
  tEventWin *EventWin;
  tMidiThruDlg(tEventWin *w);
  void EditForm(wxPanel *panel);
  virtual void OnOk();
  virtual void OnCancel();
  virtual void OnHelp();
};



tMidiThruDlg::tMidiThruDlg(tEventWin *w)
: wxForm( USED_WXFORM_BUTTONS )
{
  EventWin = w;
}


void tMidiThruDlg::OnHelp()
{
#ifndef VMS
	HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
	HelpInstance->KeywordSearch("Track Window");
#endif
}


void tMidiThruDlg::OnCancel()
{
  EventWin->DialogBox = 0;
  wxForm::OnCancel();
}


void tMidiThruDlg::OnOk()
{
  Midi->SetSoftThru( SoftThru );
  Midi->SetHardThru( HardThru );
  EventWin->Redraw();
  EventWin->DialogBox = 0;
  wxForm::OnOk();
}



void tMidiThruDlg::EditForm(wxPanel *panel)
{
#ifdef wx_msw
  Add(wxMakeFormBool( "Software MIDI thru", &SoftThru ));
  Add(wxMakeFormNewLine());
#else
  if (Seq2Driver)
  {
    Add(wxMakeFormBool( "Software MIDI thru", &SoftThru ));
    Add(wxMakeFormNewLine());
  }
  else
  {
    Add(wxMakeFormBool( "Hardware MIDI thru", &HardThru ));
    Add(wxMakeFormNewLine());
  }
#endif
  AssociatePanel(panel);
}

void tTrackWin::MenMidiThru()
{
  tMidiThruDlg *dlg;
  if (DialogBox)
  {
    DialogBox->Show(TRUE);
    return;
  }
  DialogBox = new wxDialogBox(this, "Midi Thru Settings", FALSE );
  dlg = new tMidiThruDlg(this);
  dlg->EditForm(DialogBox);
  DialogBox->Fit();
  DialogBox->Show(TRUE);
}

void tTrackWin::SaveThruSettings()
{
  PutConfig( ".softthru", (long) SoftThru );
  PutConfig( ".hardthru", (long) HardThru );
}

void tTrackWin::SaveTimingSettings()
{
  PutConfig( ".realtime_out", (long) RealTimeOut );
  PutConfig( ".clocksource", (long) ClockSource );
}

void tTrackWin::SaveEffectSettings()
{
  PutConfig( ".use_reverb_macro", (long) UseReverbMacro );
  PutConfig( ".use_chorus_macro", (long) UseChorusMacro );
}

void tTrackWin::SaveGeoSettings()
{
  GetPosition( &TrackWinGeo[0], &TrackWinGeo[1] );
  GetSize( &TrackWinGeo[2], &TrackWinGeo[3] );
  PutConfig( ".trackwin_xpos", (long) TrackWinGeo[0] );
  PutConfig( ".trackwin_ypos", (long) TrackWinGeo[1] );
  PutConfig( ".trackwin_width", (long) TrackWinGeo[2] );
  PutConfig( ".trackwin_height", (long) TrackWinGeo[3] );

  if (NextWin)
  {
    NextWin->GetPosition( &PianoWinGeo[0], &PianoWinGeo[1] );
    NextWin->GetSize( &PianoWinGeo[2], &PianoWinGeo[3] );
    PutConfig( ".pianowin_xpos", (long) PianoWinGeo[0] );
    PutConfig( ".pianowin_ypos", (long) PianoWinGeo[1] );
    PutConfig( ".pianowin_width", (long) PianoWinGeo[2] );
    PutConfig( ".pianowin_height", (long) PianoWinGeo[3] );
  }

  if (MixerForm)
  {
    MixerForm->GetPosition( &PartsDlgGeo[0], &PartsDlgGeo[1] );
  }
  PutConfig( ".partsdialog_xpos", (long) PartsDlgGeo[0] );
  PutConfig( ".partsdialog_ypos", (long) PartsDlgGeo[1] );
  PutConfig( ".trackdialog_xpos", (long) TrackDlgGeo[0] );
  PutConfig( ".trackdialog_ypos", (long) TrackDlgGeo[1] );

  if (rhythm_win)
  {
    rhythm_win->GetPosition( &RhythmGeo[0], &RhythmGeo[1] );
  }
  PutConfig( ".randomrhythm_xpos", (long) RhythmGeo[0] );
  PutConfig( ".randomrhythm_ypos", (long) RhythmGeo[1] );

  if (the_harmony_browser)
  {
    ((HBFrame *)the_harmony_browser)->GetPosition( &HarmonyGeo[0], &HarmonyGeo[1] );
  }
  PutConfig( ".harmonybrowser_xpos", (long) HarmonyGeo[0] );
  PutConfig( ".harmonybrowser_ypos", (long) HarmonyGeo[1] );
}

// ******************************************************************
// Copyright dialog
// ******************************************************************



class tCopyrightDlg : public wxForm
{
 public:
  tEventWin *EventWin;
  char *String;
  tCopyrightDlg(tEventWin *w);
  ~tCopyrightDlg() { delete String; }
  void EditForm(wxPanel *panel);
  virtual void OnOk();
  virtual void OnCancel();
  virtual void OnHelp();
};



tCopyrightDlg::tCopyrightDlg(tEventWin *w)
: wxForm( USED_WXFORM_BUTTONS )
{
  EventWin = w;
  char *cs = EventWin->Song->GetTrack(0)->GetCopyright();
  if ( cs && strlen(cs) )
  {
    String = copystring( cs );
  }
  else
  {
    String = copystring( "Copyright (C) <year> <owner>" );
  }
}


void tCopyrightDlg::OnHelp()
{
#ifndef VMS
	HelpInstance->LoadFile( HelpPathList.FindValidPath("jazz") );
	HelpInstance->KeywordSearch("Music copyright");
#endif
}


void tCopyrightDlg::OnCancel()
{
  EventWin->DialogBox = 0;
  wxForm::OnCancel();
}


void tCopyrightDlg::OnOk()
{
  EventWin->Song->GetTrack(0)->SetCopyright( String );
  EventWin->Redraw();
  EventWin->DialogBox = 0;
  wxForm::OnOk();
}



void tCopyrightDlg::EditForm(wxPanel *panel)
{
  Add(wxMakeFormString("Copyright notice:",
                       &String,
                       wxFORM_DEFAULT,
                       NULL, NULL, wxVERTICAL, 400 ));
  AssociatePanel(panel);
}


void tTrackWin::MenCopyright()
{
  tCopyrightDlg *dlg;
  if (DialogBox)
  {
    DialogBox->Show(TRUE);
    return;
  }
  DialogBox = new wxDialogBox(this, "Music copyright", FALSE );
  dlg = new tCopyrightDlg(this);
  dlg->EditForm(DialogBox);
  DialogBox->Fit();
  DialogBox->Show(TRUE);
}


class tLoadPattern : public tMarkDestin {
  public:
    tLoadPattern(tTrackWin *t, char *fn) 
      : tMarkDestin(t->Canvas, t, 0), fname(fn), tw(t) {}

    int LeftDown(wxMouseEvent &e);
  private:
    char *fname;
    tTrackWin *tw;
};

int tLoadPattern::LeftDown(wxMouseEvent &e)
{
  tMarkDestin::LeftDown(e);
  tSong *sng = new tSong;
  tStdRead io;
  sng->Read(io, fname);

  int dst_track = tw->y2Line((long)y);
  int num_tracks = sng->NumUsedTracks();
  long delta = tw->x2BarClock((long)x);
  tw->Song->NewUndoBuffer();
  for (int i = 0; i < num_tracks; i++) {
    tTrack *src = sng->GetTrack(i);
    tTrack *dst = tw->Song->GetTrack(dst_track++);
    tEventIterator iter(src);
    tEvent *e = iter.First();
    while (e) {
      e = e->Copy();
      e->Clock += delta;
      dst->Put(e);
      e = iter.Next();
    }
    dst->Cleanup();
  }
  delete sng;

  tw->Redraw();
  tw->MouseAction = 0;
  delete this;
  return 1;
}

void tTrackWin::MenLoadPattern()
{
  if (MouseAction)
    return;
  char * fname = file_selector(defpattern, "Load Pattern", 0, 0);
  if (fname) 
    MouseAction = new tLoadPattern(this, fname);
}


void tTrackWin::MenSavePattern()
{
  if (!EventsSelected())
    return;
  char * fname = file_selector(defpattern, "Save Selected Range", 1, 0);
  if (fname) {
    int tracknr = 0;
    tSong *sng = new tSong;
    delete sng;
    sng = new tSong;
    tTrackIterator Tracks(Filter);
    tTrack *src = Tracks.First();
    while (src != 0) 
    {
      tTrack *dst = sng->GetTrack(tracknr++);
      tEventIterator Events(src);
      long delta = Filter->FromClock;
      tEvent *e = Events.Range(Filter->FromClock, Filter->ToClock);
      while (e) {
        if (Filter->IsSelected(e)) {
          e = e->Copy();
          e->Clock -= delta;
          dst->Put(e);
        }
        e = Events.Next();
      }
      dst->Cleanup();
      src = Tracks.Next();
    }
    tStdWrite io;
    sng->Write(io, fname);
    delete sng;
  }
}


