
/* Musical Notation Editor for X, Chris Cannam 1994   */
/* Functions to catch events, call dragging functions */
/* and so on.  Interacts with StaveCursor.c.  Also    */
/* handles insertion of the current palette entry.    */

#ifndef DEBUG_PLUS_PLUS
#undef DEBUG
#endif

#include <X11/X.h>
#include <X11/Intrinsic.h>
#include <X11/cursorfont.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Simple.h>
#ifndef XtNcursor
#define XtNcursor "cursor"
#endif

#include "General.h"
#include "Visuals.h"
#include "Classes.h"
#include "Stave.h"
#include "StaveEdit.h"
#include "StavePrivate.h"
#include "StaveCursor.h"
#include "GC.h"
#include "Yawn.h"
#include "ItemList.h"
#include "Palette.h"
#include "Menu.h"
#include "Marks.h"

#include "hourglass.xbm"
#include "hourglass_mask.xbm"

#include "drag.xbm"
#include "drag_mask.xbm"

#include "hour0.xbm"
#include "hour1.xbm"
#include "hour2.xbm"
#include "hour3.xbm"
#include "hour4.xbm"
#include "hour5.xbm"
#include "hour6.xbm"
#include "hour7.xbm"

#include <X11/keysym.h>

#define HOUR_FRAMES 8

static Pixmap dragPixmap      = 0;
static Pixmap dragMask        = 0;
static Pixmap hourglassPixmap = 0;
static Pixmap hourglassMask   = 0;

static Cursor hourglassCursor = 0;
static Cursor    insertCursor = 0;
static Cursor      dragCursor = 0;

static Pixmap      hourPixmap[] = { 0, 0, 0, 0, 0, 0, 0, 0, };
static Cursor      hourCursor[] = { 0, 0, 0, 0, 0, 0, 0, 0, };

static XPoint      pPrev;
static Boolean     xDrawn = False;
static Boolean     buttonPressed = False;
static Boolean     staveInsertMode;
static MusicObject staveVisual;
static int         staveVisualIndex;
static PaletteTag  staveVisualType;
                           /* PaletteNotes, PaletteRests, PaletteClefs */

static Boolean shiftPressed = False;
static Boolean  ctrlPressed = False;
static Boolean rCtrlPressed = False;
static Boolean   altPressed = False;

static Boolean startedDragging = False;	/* some redundancy with buttonPressed */


/* Makes a generic object to be inserted.  Doesn't currently bother about */
/* getting the pitch (note voices) in chords, or getting modifiers or any */
/* such right.  These should be dealt with by StaveEditInsertInsertion.   */

MusicObject StaveEditCreateInsertion(void)
{
  Begin("StaveEditCreateInsertion");

  switch(staveVisualType) {

  case PaletteNotes:

    Return
      ((MusicObject)NewChord
       (NULL, NULL, 0, ModNone,
	((NoteVisualCompound *)staveVisual)[staveVisualIndex].dotted.type,
	PaletteModDottedQuery()));

  case PaletteRests:

    Return
      ((MusicObject)NewRest
       (NULL,
	((RestVisualCompound *)staveVisual)[staveVisualIndex].dotted.type,
	PaletteModDottedQuery()));

  case PaletteClefs:

    Return((MusicObject)NewClef
	   (NULL, ((ClefVisualRec *)staveVisual)[staveVisualIndex].type));

  default:
    Return(NULL);	   /* this would probably crash it, but never mind */
  }
}


ItemList StaveEditCreateInsertionList(void)
{
  MusicObject insertion;

  Begin("StaveEditCreateInsertionList");

  if ((insertion = StaveEditCreateInsertion()) == NULL) Return(NULL);
  else Return(NewItemList((Item *)insertion));
}


void StaveEditInsertInsertion(XPoint p)
{
  PointerRec     rec;
  int            barNo;
  int            staveNo;
  ItemList       i;
  StaveEltList   barList;
  NoteVoice     *voice;
  MajorStaveRec *mstave = (MajorStaveRec *)stave;

  Begin("StaveEditInsertInsertion");
  
  /* find the point at which to insert an item: */

  if ((staveNo = StaveGetPointedStave(stave, p)) < 0 ||
      (barList = (StaveEltList)StaveGetPointedBar(stave, p, staveNo))
      == NULL) {
    XBell(display, 70);
    End;
  }

  barNo = barList->bars[staveNo]->bar.number;

  rec = StaveGetPointedItem(stave, p, staveNo, (List)barList);
  if (TestNullRec(rec)) End;

  /* get the item to insert: */

  i = StaveEditCreateInsertionList();

  /* if it's a chord, find out a suitable pitch: */

  if (i->item->object_class == ChordClass) {

    Pitch pitch = StaveGetPointedPitch
      (stave, p, staveNo, barList->bars[staveNo]->bar.clef->clef.clef);
    
    if (PaletteFollowKey()) {
      voice = NewNoteVoice(NULL, pitch, ModNone);
    } else {
      voice = NewNoteVoice(NULL, pitch, PaletteGetNoteMods());
    }

    (void)NewChord((Chord *)i->item, voice, 1, ModNone,
		   ((Chord *)i->item)->chord.visual->type,
		   ((Chord *)i->item)->chord.visual->dotted);

    if (PaletteFollowKey()) {

      NoteMods pMods = PaletteGetNoteMods();

      KeyTransposeItemIntoNewKey
	(i->item, &defaultKey, StaveItemToKey(stave, staveNo, rec.left));

      if (pMods != ModNone) {
	((Chord *)i->item)->chord.voices[0].modifiers = pMods;
      }
    } 
  }

  if (rec.left) {
    ClearMarkType(mstave->music[staveNo], iPrev(rec.left), rec.left, Tie);
  }

  /* put in the new item, after rec.left: */

  if (rec.left == NULL)
    mstave->music[staveNo] =
      (ItemList)First(Insert(i, mstave->music[staveNo]));
  else if (Next(rec.left)) Insert(i, iNext(rec.left));
  else Nconc(rec.left, i);

  /* Hmm.  Why this line?  It means we auto-beam stuff in the previous
     bar, that might already have been differently beamed, which we
     don't want.  Maybe it was just all tied in somehow with the next
     bit that's also commented out. */
  /*
  if (Prev(barList)) barList = (StaveEltList)Prev(barList);
  */

 /* left out until I can work out what in the auto-beam breaks it:

  if (BarValid(barList->bars[staveNo])) {

    mstave->formats[staveNo].next  = barList->bars[staveNo]->bar.number;
    mstave->formats[staveNo].time  = barList->bars[staveNo]->bar.time;
    mstave->formats[staveNo].key   = barList->bars[staveNo]->bar.key;
    mstave->formats[staveNo].clef  = barList->bars[staveNo]->bar.clef;

    mstave->formats[staveNo].items =
      Prev(barList) ?
	barList->bars[staveNo]->group.start : mstave->music[staveNo];

  } else {
 */

    StaveResetFormatting(stave, staveNo);

  /* experimental auto-beaming stuff: */

  if (i->item->object_class == ChordClass &&
      rec.left && Next(rec.left) && !Next(Next(rec.left))) {

    ItemList       first, temp;
    TimeSignature *time;

    time = &barList->bars[staveNo]->bar.time;
    first = barList->bars[staveNo]->group.start;

    /* check that we're not beaming existing groups with tags other
       than Beamed: */

    for (temp = first; temp; temp = iNext(temp)) {
      if (temp->item->object_class == GroupClass &&
	  ((Group *)temp->item)->group.type != GroupBeamed) {
	first = iNext(((Group *)temp->item)->group.end);
      }
    }

    if (first && first != i) {

      /* this line appears to be optional: */

      if (first->item->object_class == GroupClass)
	first = ((Group *)(first->item))->group.start;

      mstave->music[staveNo] =
	ItemListExpungeGroups
	  ((ItemList)First(mstave->music[staveNo]),
	   iPrev(/*barList->bars[staveNo]->group.start*/ first), i); /*???*/

      ItemListAutoBeam(time, first, i);
    }
  }
  
  FileMenuMarkChanged(stave, True);
  StaveRefreshAsDisplayed(stave);

  End;
}



void StavePointerMovedCallback(Widget w, XtPointer client,
			       XEvent *e, Boolean *cont)
{
  XMotionEvent  *event = (XMotionEvent *)e;
  XPoint         p;
  Pitch          pitch;
  int            staveNo;
  StaveEltList   barList;
  Dimension      height;
  MajorStaveRec *mstave = (MajorStaveRec *)stave;

  Begin("StavePointerMovedCallback");
  if (event->type != MotionNotify || !stave ||
      (pPrev.x == event->x && pPrev.y == event->y)) End;

  p.x = event->x;
  p.y = event->y;

  if (xDrawn) {
    StaveCursorDrawX(pPrev);
    xDrawn = False;
  }

  if (staveInsertMode) {

    if (pPrev.y == p.y) pPrev.x = p.x;
    else {

      if ((staveNo = StaveGetPointedStave(stave, p)) < 0 ||
	  (barList = (StaveEltList)StaveGetPointedBar(stave, p, staveNo))
	  == NULL)
	End;
      
      pitch = StaveGetPointedPitch
	(stave, p, staveNo, barList->bars[staveNo]->bar.clef->clef.clef);

      YGetValue(staveLabel, XtNheight, &height);

      p.y = (height - StaveTotalHeight(mstave))/2 + StaveTopHeight(staveNo) +
	STAVE_Y_COORD
	  (pitch +
	   ClefPitchOffset(barList->bars[staveNo]->bar.clef->clef.clef));
  
      pPrev = p;
    }

    StaveCursorDrawX(pPrev);
    xDrawn = True;

  } else {

    pPrev = p;
    if (buttonPressed && startedDragging) StaveCursorExtend(stave, p);
  }

  End;
}


static int staveClickCount = 0;
static XtIntervalId staveTimeoutId = 0;

static void StaveClearMultiClickCallback(XtPointer p, XtIntervalId *id)
{
  staveClickCount = 0;
  staveTimeoutId = 0;
}

void StaveButtonPressCallback(Widget w, XtPointer client,
			      XEvent *e, Boolean *cont)
{
  XButtonEvent  *event = (XButtonEvent *)e;
  XPoint         p;

  Begin("StaveButtonPressCallback");
  if (event->type != ButtonPress || !stave) End;
  if (event->button != Button1) End;
  if (slaveMode) End;

  p.x = event->x;
  p.y = event->y;
  pPrev = p;

  buttonPressed = True;

  if (staveInsertMode) {

    StaveEditInsertInsertion(p);

  } else {
    if (shiftPressed) {
      startedDragging = False;
      StaveCursorExplicitExtend(stave, p);
    } else {

      ++staveClickCount;

      switch(staveClickCount) {

      case 1:
	startedDragging = True;
	StaveCursorMark(stave, p);
	break;

      case 2:
	startedDragging = False;
	StaveCursorSelectBar(stave, p, True);
	break;

      case 3:
	startedDragging = False;
	StaveCursorSelectStaff(stave, p, True);
	break;

      default:
	break;
      }

      if (staveTimeoutId) XtRemoveTimeOut(staveTimeoutId);
      staveTimeoutId = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
				       XtGetMultiClickTime(XtDisplay(w)),
				       StaveClearMultiClickCallback, 0);
    }
  }

  End;
}


void StaveButtonReleaseCallback(Widget w, XtPointer client,
				XEvent *e, Boolean *cont)
{
  XButtonEvent *event = (XButtonEvent *)e;
  XPoint        p;

  Begin("StaveButtonReleaseCallback");

  if (event->type != ButtonRelease || !stave) End;
  if (slaveMode) End;

  if (event->button == Button3) {
    PaletteChangeMode(!staveInsertMode);
    End;
  } else if (event->button != Button1) End;

  buttonPressed = False;

  if (staveInsertMode || !startedDragging) End;
  startedDragging = False;

  p.x = event->x;
  p.y = event->y;

  StaveCursorExtend(stave, p);
  StaveCursorFinish(stave);

  End;
}


/* this function is a right mess */

void StaveKeyPressCallback(Widget w, XtPointer client,
			   XEvent *e, Boolean *cont)
{
  XKeyEvent *event = (XKeyEvent *)e;
  KeySym key;

  Begin("StaveKeyPressCallback");

  if (slaveMode) End;
  key = XKeycodeToKeysym(XtDisplay(w), event->keycode, 0);

  switch(key) {

  case XK_Left:
    if (!client && !buttonPressed) StaveLeftCallback(NULL, NULL, NULL);
    break;

  case XK_Right:
    if (!client && !buttonPressed) StaveRightCallback(NULL, NULL, NULL);
    break;
    
  case XK_Up:
    if (client) break;
    if (staveInsertMode) PaletteMoveUp();
    else StaveScrollUpOrDownABit(True);
    break;

  case XK_Down:
    if (client) break;
    if (staveInsertMode) PaletteMoveDown();
    else StaveScrollUpOrDownABit(False);
    break;

  case XK_Shift_L:
    if (!staveInsertMode) { shiftPressed = !client; break; }
    if (client && shiftPressed) PalettePopDot();
    else if (!shiftPressed) PalettePushDot(True);
    shiftPressed = !client;
    break;

  case XK_Control_L:
    if (!staveInsertMode) { ctrlPressed = !client; break; }
    if (client && ctrlPressed) PalettePopSharp();
    else if (!ctrlPressed) PalettePushSharp(True);
    ctrlPressed = !client;
    break;

  case XK_Alt_R:
    if (!staveInsertMode) { rCtrlPressed = !client; break; }
    if (client && rCtrlPressed) PalettePopNatural();
    else if (!rCtrlPressed) PalettePushNatural(True);
    rCtrlPressed = !client;
    break;

  case XK_Alt_L:
    if (!staveInsertMode) { altPressed = !client; break; }
    if (client && altPressed) PalettePopFlat();
    else if (!altPressed) PalettePushFlat(True);
    altPressed = !client;
    break;

  default: break;
  }

  End;
}


void StaveExposedCallback(Widget w, XtPointer client,
			  XEvent *e, Boolean *cont)
{
  static Dimension staveLabelWidth = 0;
  Dimension        thisWidth;

  Begin("StaveExposedCallback");

  xDrawn = False;

  if (stave && staveLabel) {

    YGetValue(staveLabel, XtNwidth, &thisWidth);

    if (thisWidth != staveLabelWidth) StaveRefreshAsDisplayed(stave);
    else StaveCursorExpose(stave);

    staveLabelWidth = thisWidth;
  }

  End;
}


void StaveEditAssertInsertVisual(PaletteTag type, MusicObject visual, int n)
{
  Begin("StaveEditAssertInsertVisual");

  staveVisualType  = type;
  staveVisual      = visual;
  staveVisualIndex = n;

  End;
}


void StaveEditEnterInsertMode(void)
{
  Begin("StaveEditEnterInsertMode");

  if (staveLabel) XDefineCursor(display, XtWindow(staveLabel), insertCursor);
  staveInsertMode = True;

  End;
}


void StaveEditEnterEditMode(void)
{
  Begin("StaveEditEnterEditMode");

  if (staveLabel) XDefineCursor(display, XtWindow(staveLabel), dragCursor);
  staveInsertMode = False;

  End;
}


void StaveBusy(Boolean busy)
{
  Begin("StaveBusy");

  if (!staveLabel) End;

  if (busy) {

    XDefineCursor(display, XtWindow(staveLabel), hourglassCursor);
    XSync(display, False);

  } else {

    XDefineCursor(display, XtWindow(staveLabel),
		  staveInsertMode ? insertCursor : dragCursor);
  }
}


static void StaveBusyDoCount(int count, Boolean total)
{
  static int totalCount = 7;

  Begin("StaveBusyDoCount");

  if (total) totalCount = count;
  else {

    if (count > totalCount) count = totalCount;

    XDefineCursor(display, XtWindow(staveLabel),
		  hourCursor[7 * count / totalCount]);

    XSync(display, False);
  }

  End;
}


/* Start the animated cursor.  Pass `count,' the total */
/* number of frames.  Must be at least 1.  Does not    */
/* install a cursor; you must call StaveBusyMakeCount  */
/* as well.                                            */

void StaveBusyStartCount(int count)
{
  Begin("StaveBusyStartCount");
  StaveBusyDoCount(count - 1, True);
  End;
}


/* Install the `count'th image in the animation sequence.  */
/* The total number of images is the same as the number    */
/* passed to StaveBusyStartCount originally.  (If that     */
/* number was greater than 8, multiple virtual images will */
/* mapped to each actual screen cursor image.)             */

void StaveBusyMakeCount(int count)
{
  Begin("StaveBusyMakeCount");
  StaveBusyDoCount(count, False);
  End;
}


/* Finish the animation and restore the mode cursor. */

void StaveBusyFinishCount(void)
{
  Begin("StaveBusyFinishCount");
  StaveBusy(False);
  End;
}


/* We don't really need the argument here; sLabel is actually the same
   as the global staveLabel anyway */

void StaveEditInitialise(Widget sLabel)
{
  XColor screenFg, screenBg;
  XColor exact;
  Window w;
  int    i;

  Begin("StaveEditInitialise");

  if (!display) End;

  w = RootWindowOfScreen(XtScreen(topLevel));

  /* Yeuch: */

  XLookupColor(display,
	       DefaultColormapOfScreen(XtScreen(topLevel)),
	       "black", &exact, &screenFg);

  XLookupColor(display,
	       DefaultColormapOfScreen(XtScreen(topLevel)),
	       "white", &exact, &screenBg);

  dragPixmap = XCreateBitmapFromData
    (display, w, drag_bits, drag_width, drag_height);

  dragMask  = XCreateBitmapFromData
    (display, w, drag_mask_bits, drag_mask_width, drag_mask_height);

  dragCursor = XCreatePixmapCursor
    (display, dragPixmap, dragMask, &screenFg, &screenBg,
     drag_x_hot, drag_y_hot);

  hourglassPixmap = XCreateBitmapFromData
    (display, w, hourglass_bits, hourglass_width, hourglass_height);

  hourglassMask   = XCreateBitmapFromData
    (display, w, hourglass_mask_bits, hourglass_width, hourglass_height);

  hourglassCursor = XCreatePixmapCursor
    (display, hourglassPixmap, hourglassMask, &screenFg, &screenBg,
     hourglass_x_hot, hourglass_y_hot);

  insertCursor = XCreateFontCursor(display, XC_crosshair);

  for (i = 0; i < HOUR_FRAMES; ++i) {

    hourPixmap[i] = XCreateBitmapFromData
      (display, w,
       i == 0 ? hour0_bits :
       i == 1 ? hour1_bits :
       i == 2 ? hour2_bits :
       i == 3 ? hour3_bits :
       i == 4 ? hour4_bits :
       i == 5 ? hour5_bits :
       i == 6 ? hour6_bits : hour7_bits, hourglass_width, hourglass_height);

    hourCursor[i] = XCreatePixmapCursor
      (display, hourPixmap[i], hourglassMask, &screenFg, &screenBg,
       hourglass_x_hot, hourglass_y_hot);
  }

  YSetValue(sLabel, XtNcursor, dragCursor);

  XtAddEventHandler(sLabel, ButtonPressMask, False,
		    StaveButtonPressCallback, NULL);

  XtAddEventHandler(sLabel, ButtonReleaseMask, False,
		    StaveButtonReleaseCallback, NULL);

  XtAddEventHandler(sLabel, KeyPressMask, False,
		    StaveKeyPressCallback, NULL);

  XtAddEventHandler(sLabel, KeyReleaseMask, False,
		    StaveKeyPressCallback, (XtPointer)1); /* "released" arg */

  XtAddEventHandler(sLabel, PointerMotionMask, False,
		    StavePointerMovedCallback, NULL);

  XtAddEventHandler(sLabel, ExposureMask, False,
		    StaveExposedCallback, NULL);

  End;
}


void StaveEditCleanUp(void)
{
  int i;

  Begin("StaveEditCleanUp");

  if (!display) End;

  for (i = 0; i < HOUR_FRAMES; ++i) {
    if (hourPixmap[i]) XFreePixmap(display, hourPixmap[i]);
    hourPixmap[i] = 0;
  }

  if (hourglassPixmap) XFreePixmap(display, hourglassPixmap);
  if (hourglassMask)   XFreePixmap(display, hourglassMask);
  hourglassPixmap = hourglassMask = 0;

  if (dragPixmap) XFreePixmap(display, dragPixmap);
  if (dragMask)   XFreePixmap(display, dragMask);
  dragPixmap = dragMask = 0;

  End;
}

