
/* MenuEdit.c */

/* Musical Notation Editor for X, Chris Cannam 1994 */
/* Actions available from the Edit menu             */


#include "General.h"
#include "Widgets.h"
#include "Menu.h"
#include "Stave.h"
#include "StaveEdit.h"
#include "StavePrivate.h"
#include "Yawn.h"
#include "ItemList.h"
#include "GC.h"
#include "Marks.h"

#include <X11/Xaw/Box.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/Paned.h>
#include <X11/Xaw/Viewport.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>

/* clipboard is assumed to contain one outside-accessible item, which
   is a Bar pointer */

static ItemList clipboard = NULL;
static Boolean  cutting = False;

void EditMenuDelete (Widget, XtPointer, XtPointer);
void EditMenuCut    (Widget, XtPointer, XtPointer);
void EditMenuCopy   (Widget, XtPointer, XtPointer);
void EditMenuPaste  (Widget, XtPointer, XtPointer);


void EditMenuDelete(Widget w, XtPointer a, XtPointer b)
{
  ItemList       list;
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  int            staveNo = mstave->sweep.stave;
  Clef          *clef;

  Begin("EditMenuDelete");

  if (!stave || !mstave->sweep.swept || !end || start == end) {
    XBell(display, 70);
    End;
  }

  if (!start && (!end || !(Next(end)))) {
    IssueMenuComplaint("You can't remove all the items from a staff.");
    End;
  }    

  if (BarValid(mstave->bar_list->bars[staveNo]) &&
      mstave->bar_list->bars[staveNo]->bar.clef) {

    clef = mstave->bar_list->bars[staveNo]->bar.clef;
    clef = (Clef *)clef->methods->clone((MusicObject)clef);

  } else {

    clef = NewClef(NULL, TrebleClef);
  }

  mstave->music[staveNo] =
    ItemListExpungeGroups(mstave->music[staveNo], start, end);

  /* ideally we should shift marks left or right so as not to be
     within this group, but it's a lot easier just to remove them
     entirely -- maybe one day... */

  ClearMarks(mstave->music[staveNo], start, end);

  if (start) list = iNext(start);
  else       list = mstave->music[staveNo];

  while (list) {

    if (list == mstave->music[staveNo])
      mstave->music[staveNo] = iNext(list);

    list->item->methods->destroy(list->item);

    if (list == end) {                                      /* final item? */

      Remove(list);
      break;

    } else {

      list = (ItemList)Remove(list);                            /* iterate */
    }
  }

  if (!mstave->music[staveNo]) {
    mstave->music[staveNo] = NewItemList((Item *)clef);
  }

  FileMenuMarkChanged(stave, True);
  StaveResetFormatting(stave, staveNo);
  StaveRefreshAsDisplayed(stave);
  End;
}



void EditMenuCopy(Widget w, XtPointer a, XtPointer b)
{
  ItemList       list;
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  Item          *newItem;

  Begin("EditMenuCopy");

  if (!stave || !mstave->sweep.swept || !end || start == end) {
    XBell(display, 70);
    End;
  }

  if (clipboard) DestroyItemList(clipboard);
  clipboard = NULL;

  if (start) list = iNext(start);
  else       list = mstave->music[mstave->sweep.stave];

  while (list) {

    if (list->item->object_class == GroupClass) {

      if (list == end) break;
      list = iNext(list);
      continue;
    }

    newItem = (Item *)list->item->methods->clone(list->item);
    clipboard = (ItemList)Nconc(clipboard, NewItemList(newItem));

    if (list == end) break;
    list = iNext(list);
  }

  clipboard = (ItemList)First
    (Nconc
     (NewItemList((Item *)NewBar(NULL, 0L, 1600, 2)), clipboard)); /* ugh */

  ((Bar *)clipboard->item)->group.start = iNext(First(clipboard));
  ((Bar *)clipboard->item)->group.end   = (ItemList)Last(clipboard);

  if (w) {
    StaveResetFormatting(stave, mstave->sweep.stave);
    StaveRefreshAsDisplayed(stave);
  }

  End;
}


/* for the time being lets cop out and implement Cut as a Copy and a Delete */

void EditMenuCut(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;

  Begin("EditMenuCut");

  if (!stave || !mstave->sweep.swept || !end || start == end) {
    XBell(display, 70);
    End;
  }

  cutting = True;

  EditMenuCopy   (NULL, a, b);
  EditMenuDelete (w,    a, b);

  cutting = False;

  End;
}



void EditMenuPaste(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  int            staveNo = mstave->sweep.stave;
  Item          *newItem;
  ItemList       newList = NULL;
  ItemList       list;

  Begin("EditMenuPaste");

  if (!stave || !mstave->sweep.swept || start != end) {
    XBell(display, 70);
    End;
  }

  if (!clipboard) {
    IssueMenuComplaint("The clipboard is empty.");
    End;
  }

  for (list = iNext(First(clipboard));	/* miss out grouping item */
       list; list = iNext(list)) {

    newItem = (Item *)list->item->methods->clone(list->item);
    newList = (ItemList)Nconc(newList, NewItemList(newItem));
  }

  if (start)
    if (Next(start)) Insert(newList, Next(start));
    else Nconc(start, newList);
  else start = (ItemList)Nconc(newList, start);

  ItemListNotifyGroupChange(newList);
  FileMenuMarkChanged(stave, True);
  StaveResetFormatting(stave, staveNo);
  StaveRefreshAsDisplayed(stave);
  End;
}


static void ClipboardCallback(Widget w, XtPointer client, XtPointer call)
{
  Begin("ClipboardCallback");

  *((Boolean *)client) = True;

  End;
}


void EditMenuShowClipboard(Widget w, XtPointer a, XtPointer b)
{
  Widget       clipShell;
  Widget       clipPane;
  Widget       clipMapForm;
  Widget       clipMapViewport;
  Widget       clipMapLabel;
  Widget       clipBottomBox;
  Widget       clipButton;
  Widget       scrollbar;
  Pixmap       clipMap = NULL;
  XPoint       op;
  Dimension    h;
  Dimension    width;
  int          sy;
  Boolean      done = False;
  XtAppContext context;

  Begin("EditMenuShowClipboard");

  clipShell     = XtCreatePopupShell
    ("Editor Clipboard", transientShellWidgetClass, topLevel, NULL, 0);

  clipPane      = YCreateWidget("Clipboard Pane", panedWidgetClass, clipShell);

  clipMapForm   = YCreateShadedWidget
    ("Clipboard Label Form", formWidgetClass, clipPane, LightShade);
  clipBottomBox = YCreateShadedWidget
    ("Clipboard Button Box",  boxWidgetClass, clipPane, MediumShade);

  YSetValue(clipMapForm,   XtNshowGrip, False);
  YSetValue(clipBottomBox, XtNshowGrip, False);

  clipMapViewport = YCreateWidget
    ("Clipboard Viewport", viewportWidgetClass, clipMapForm);
  clipMapLabel    = YCreateWidget
    ("Clipboard Contents", labelWidgetClass, clipMapViewport);
  clipButton      = YCreateCommand("Dismiss", clipBottomBox);

  if (clipboard) {

    width = GetBarWidth((Bar *)clipboard->item, (Bar *)clipboard->item);

    clipMap = XCreatePixmap
      (display, RootWindowOfScreen(XtScreen(topLevel)), width + 10,
       StaveHeight + 2 * StaveUpperGap,
       DefaultDepthOfScreen(XtScreen(topLevel)));

    XFillRectangle(display, clipMap, clearGC, 0, 0, width + 10,
		   StaveHeight + StaveUpperGap * 2);

    (void)DrawBar((Bar *)clipboard->item, (Bar *)clipboard->item,
		  0, clipMap, 5, StaveUpperGap, 0, width);

    for (sy = 0 ; sy < StaveHeight; sy += NoteHeight + 1)
      XDrawLine(display, clipMap, drawingGC,
		4, StaveUpperGap + sy, width + 4, StaveUpperGap + sy);

    XDrawLine(display, clipMap, drawingGC,
	      4, StaveUpperGap, 4, StaveUpperGap + StaveHeight - 1);

    YSetValue(clipMapLabel, XtNbitmap, clipMap);

  } else {

    YSetValue(clipMapLabel, XtNlabel, "The clipboard is empty.");
  }

  XtAddCallback(clipButton, XtNcallback, ClipboardCallback, &done);

  XtRealizeWidget(clipShell);
  YGetValue(clipButton, XtNheight, &h);
  XtUnrealizeWidget(clipShell);

  YSetValue(clipBottomBox, XtNmax, h + 15);
  YSetValue(clipBottomBox, XtNmin, h + 15);

  XtRealizeWidget(clipShell);

  scrollbar = XtNameToWidget(clipMapViewport, "horizontal");
  if (scrollbar) YSetValue(scrollbar, XtNthumb, lightGreyMap);

  op = YPlacePopupAndWarp(clipShell, XtGrabExclusive, clipButton, clipButton);
  YAssertDialogueActions(clipShell, clipButton, clipButton, NULL);

  context = XtWidgetToApplicationContext(clipShell);
  while (!done || XtAppPending(context)) XtAppProcessEvent(context, XtIMAll);

  if (op.x || op.y) (void) YPopPointerPosition();

  YPopdown(clipShell);
  YRetractDialogueActions(clipShell);
  XtDestroyWidget(clipShell);

  End;
}


void EditMenuSelectBar(Widget w, XtPointer a, XtPointer b)
{
  static XPoint p = { 0, 0 };
  Begin("EditMenuSelectBar");
  StaveCursorSelectBar(stave, p, False);
  End;
}


void EditMenuSelectStaff(Widget w, XtPointer a, XtPointer b)
{
  static XPoint p = { 0, 0 };
  Begin("EditMenuSelectStaff");
  StaveCursorSelectStaff(stave, p, False);
  End;
}



Chord     *changeChordTempChord;
NoteVoice *changeChordNoteVoices;
int        changeChordNoteVoice;
int        changeChordTotalVoices;
Clef      *changeChordClef;
Widget     changeHeadCount;
Dimension  changeHeadCountWidth;


void ChangeChordDrawChord(Widget w, Dimension wd, Dimension ht)
{
  static Pixmap map = 0;
  int i, y, topY;

  Begin("ChangeChordDrawChord");

  /* use voice_count from actual chord structure here, not total */
  /* voices, 'cos we're modifying values in the actual structure */
  for (i = 0; i < changeChordTempChord->chord.voice_count; ++i) {
    changeChordTempChord->chord.voices[i].display_mods =
      changeChordTempChord->chord.voices[i].modifiers;
  }

  if (!map) {
    map = XCreatePixmap(XtDisplay(w), RootWindowOfScreen(XtScreen(w)),
			wd, ht, DefaultDepthOfScreen(XtScreen(w)));
  }

  XFillRectangle(XtDisplay(w), map, clearGC, 0, 0, wd, ht);
  topY = (ht - StaveHeight) / 2;

  if (changeChordClef) {

    changeChordClef->methods->draw
      (changeChordClef, map, 10, topY, 0, 0, NULL);

    changeChordTempChord->methods->draw
      (changeChordTempChord, map, wd - 32 - NoteWidth,
       topY, ClefPitchOffset(changeChordClef->clef.clef), 0, NULL);

    y = topY + NoteHeight/2 + 
      STAVE_Y_COORD(changeChordNoteVoices[changeChordNoteVoice].pitch +
		    ClefPitchOffset(changeChordClef->clef.clef));

  } else {

    changeChordTempChord->methods->draw
      (changeChordTempChord, map, wd/2 - 10, topY, 0, 0, NULL);

    y = topY + NoteHeight/2 +
      STAVE_Y_COORD(changeChordNoteVoices[changeChordNoteVoice].pitch);
  }

  XDrawLine(XtDisplay(w), map, drawingGC, wd - 13, y - 3, wd - 16, y);
  XDrawLine(XtDisplay(w), map, drawingGC, wd - 16, y, wd - 8, y);
  XDrawLine(XtDisplay(w), map, drawingGC, wd - 16, y, wd - 13, y + 3);

  for (y = 0; y < StaveHeight;  y += NoteHeight + 1) {
    XDrawLine(XtDisplay(w), map, drawingGC, 6, y + topY, wd - 24, y + topY);
  }

  YSetValue(w, XtNbitmap, map);

  End;
}


NoteVoice *ChangeDuplicateVoices(NoteVoice *voices, int voiceCount)
{
  int i;
  NoteVoice *rtn;
  Begin("ChangeDuplicateVoices");

  rtn = (NoteVoice *)XtMalloc(sizeof(NoteVoice) * voiceCount);
  for (i = 0; i < voiceCount; ++i) {
    (void)NewNoteVoice(rtn + i, voices[i].pitch, voices[i].modifiers);
  }

  Return(rtn);
}


void ChangeUp(Widget w, XtPointer client, XtPointer call)
{
  Chord *tempChord;
  Begin("ChangeUp");

  if (!changeChordNoteVoices[changeChordNoteVoice].modifiers) {

    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModSharp;

  } else if (changeChordNoteVoices[changeChordNoteVoice].modifiers == ModFlat) {

    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModNone;

  } else {
    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModFlat;
    changeChordNoteVoices[changeChordNoteVoice].pitch ++;
  }

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices, changeChordTempChord->chord.modifiers,
     changeChordTempChord->chord.visual->type,
     changeChordTempChord->chord.visual->dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeDown(Widget w, XtPointer client, XtPointer call)
{
  Chord *tempChord;
  Begin("ChangeDown");

  if (!changeChordNoteVoices[changeChordNoteVoice].modifiers) {

    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModFlat;

  } else if (changeChordNoteVoices[changeChordNoteVoice].modifiers == ModSharp){

    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModNone;

  } else {
    changeChordNoteVoices[changeChordNoteVoice].modifiers = ModSharp;
    changeChordNoteVoices[changeChordNoteVoice].pitch --;
  }

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices, changeChordTempChord->chord.modifiers,
     changeChordTempChord->chord.visual->type,
     changeChordTempChord->chord.visual->dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeOctDown(Widget w, XtPointer client, XtPointer call)
{
  Chord *tempChord;
  Begin("ChangeOctDown");

  changeChordNoteVoices[changeChordNoteVoice].pitch -= 7;

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices, changeChordTempChord->chord.modifiers,
     changeChordTempChord->chord.visual->type,
     changeChordTempChord->chord.visual->dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeOctUp(Widget w, XtPointer client, XtPointer call)
{
  Chord *tempChord;
  Begin("ChangeOctUp");

  changeChordNoteVoices[changeChordNoteVoice].pitch += 7;

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices, changeChordTempChord->chord.modifiers,
     changeChordTempChord->chord.visual->type,
     changeChordTempChord->chord.visual->dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeShorter(Widget w, XtPointer client, XtPointer call)
{
  NoteTag tag;
  Boolean dotted;
  Chord *tempChord;

  Begin("ChangeShorter");

  tag    = changeChordTempChord->chord.visual->type;
  dotted = changeChordTempChord->chord.visual->dotted;

  if (dotted) dotted = False;
  else if (tag > ShortestNote) { --tag; dotted = True; }

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices,
     changeChordTempChord->chord.modifiers, tag, dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeLonger(Widget w, XtPointer client, XtPointer call)
{
  NoteTag tag;
  Boolean dotted;
  Chord *tempChord;

  Begin("ChangeLonger");

  tag    = changeChordTempChord->chord.visual->type;
  dotted = changeChordTempChord->chord.visual->dotted;

  if (dotted) {
    if (tag < LongestNote) { ++tag; dotted = False; }
  } else dotted = True;

  tempChord = NewChord
    (NULL,
     ChangeDuplicateVoices(changeChordNoteVoices, changeChordTotalVoices),
     changeChordTotalVoices,
     changeChordTempChord->chord.modifiers, tag, dotted);

  DestroyChord(changeChordTempChord);
  changeChordTempChord = tempChord;

  *((Boolean *)client) = True;

  End;
}


void ChangeHeadUp(Widget w, XtPointer client, XtPointer call)
{
  static String s = 0;
  Begin("ChangeHeadUp");

  if (changeChordNoteVoice < changeChordTotalVoices - 1) {
    changeChordNoteVoice ++;
    *((Boolean *)client) = True;
  }

  if (!s) s = XtNewString("Head xxxx");
  sprintf(s, "Head %d", changeChordNoteVoice + 1);

  YSetValue(changeHeadCount, XtNlabel, s);
  YSetValue(changeHeadCount, XtNwidth, changeHeadCountWidth);

  End;
}


void ChangeHeadDown(Widget w, XtPointer client, XtPointer call)
{
  static String s = 0;
  Begin("ChangeHeadDown");

  if (changeChordNoteVoice > 0) {
    changeChordNoteVoice --;
    *((Boolean *)client) = True;
  }

  if (!s) s = XtNewString("Head xxxx");
  sprintf(s, "Head %d", changeChordNoteVoice + 1);

  YSetValue(changeHeadCount, XtNlabel, s);
  YSetValue(changeHeadCount, XtNwidth, changeHeadCountWidth);

  End;
}


void ChangeApply(Widget w, XtPointer client, XtPointer call)
{
  Begin("ChangeApply");

  *((int *)client) = 1;

  End;
}


void ChangeCancel(Widget w, XtPointer client, XtPointer call)
{
  Begin("ChangeCancel");

  *((int *)client) = 0;

  End;
}


void EditMenuChangeChord(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  int            staveNo = mstave->sweep.stave;

  Widget changeShell;
  Widget changePane;
  Widget changeTopBox;
  Widget changeTopPane;
  Widget changeForm;
  Widget changeUpperBox;
  Widget changeLowerBox;
  Widget changeOctUp;
  Widget changeOctDown;
  Widget changeUp;
  Widget changeDown;
  Widget changeNote;
  Widget changeShorter;
  Widget changeLonger;
  Widget changeHeadUp;
  Widget changeHeadDown;
  Widget changeApply;
  Widget changeCancel;
  Widget changeHelp;

  Dimension wd, ht;

  MarkList markList;
  XtAppContext context;
  Boolean madeChange;
  int result;

  Begin("EditMenuChangeNote");

  /* don't check one-item-only here; assume stave code did it right
     (it was complicated enough there, don't want to duplicate it) */
  if (!stave || !mstave->sweep.swept || !end ||
      end->item->object_class != ChordClass) {

    XBell(display, 70);
    End;
  }

  changeChordClef = 0;

  while (start) {

    if (start->item->object_class == ClefClass) {
      changeChordClef = (Clef *)start->item->methods->clone(start->item);
      break;

    } else {
      start = iPrev(start);
    }
  }

  changeChordTempChord = (Chord *)end->item->methods->clone(end->item);
  changeChordTotalVoices = changeChordTempChord->chord.voice_count;
  changeChordNoteVoice = 0;
  changeChordNoteVoices = ChangeDuplicateVoices
    (changeChordTempChord->chord.voices,
     changeChordTempChord->chord.voice_count);

  changeShell = XtCreatePopupShell
    ("Edit Chord", transientShellWidgetClass, topLevel, NULL, 0);

  changePane = YCreateWidget
    ("Change Chord Pane", panedWidgetClass, changeShell);

  changeTopBox = XtCreateManagedWidget
    ("Change Chord Top Box", boxWidgetClass, changePane, NULL, 0);
  YSetValue(changeTopBox, XtNhSpace, 8);

  changeTopPane = XtCreateManagedWidget
    ("Change Chord Top Pane", panedWidgetClass, changeTopBox, NULL, 0);
  
  changeForm = YCreateShadedWidget
    ("Change Chord Form", formWidgetClass, changeTopPane, LightShade);

  changeUpperBox = YCreateShadedWidget
    ("Change Chord Upper Box", formWidgetClass, changeTopPane, MediumShade);

  changeLowerBox = YCreateShadedWidget
    ("Change Chord Lower Box", formWidgetClass, changePane, LightShade);

  changeNote    = YCreateLabel(" 00 ", changeForm);
  changeUp      = YCreateArrowButton("Raise",   changeForm, YArrowUp);
  changeDown    = YCreateArrowButton("Lower",   changeForm, YArrowDown);
  changeShorter = YCreateArrowButton("Shorter", changeForm, YArrowLeft);
  changeLonger  = YCreateArrowButton("Longer",  changeForm, YArrowRight);

  changeOctUp   = YCreateCommand("+8ve", changeForm);
  changeOctDown = YCreateCommand("-8ve", changeForm);
  YSetValue(changeOctUp,   XtNleftBitmap, 0);
  YSetValue(changeOctDown, XtNleftBitmap, 0);
  
  YSetValue(XtParent(changeOctUp),   XtNfromHoriz, XtParent(changeNote));
  YSetValue(XtParent(changeOctUp),   XtNfromVert,  XtParent(changeUp));
  YSetValue(XtParent(changeOctDown), XtNfromHoriz, XtParent(changeNote));
  YSetValue(XtParent(changeOctDown), XtNfromVert,  XtParent(changeOctUp));
  YSetValue(XtParent(changeUp),      XtNfromHoriz, XtParent(changeShorter));
  YSetValue(XtParent(changeNote),    XtNfromVert,  XtParent(changeUp));
  YSetValue(XtParent(changeNote),    XtNfromHoriz, XtParent(changeShorter));
  YSetValue(XtParent(changeLonger),  XtNfromHoriz, XtParent(changeNote));
  YSetValue(XtParent(changeDown),    XtNfromVert,  XtParent(changeNote));
  YSetValue(XtParent(changeDown),    XtNfromHoriz, XtParent(changeShorter));

  ht = StaveHeight + StaveUpperGap + StaveLowerGap;
  YSetValue(changeNote, XtNheight, ht);

  YSetValue(XtParent(changeShorter), XtNvertDistance, ht + 14);
  YSetValue(XtParent(changeLonger),  XtNvertDistance, ht + 14);

  changeHeadDown = YCreateArrowButton("Head Down", changeUpperBox, YArrowLeft);
  changeHeadCount = YCreateLabel("Head 1", changeUpperBox);
  changeHeadUp = YCreateArrowButton("Head Up", changeUpperBox, YArrowRight);

  YSetValue(XtParent(changeHeadCount), XtNfromHoriz, XtParent(changeHeadDown));
  YSetValue(XtParent(changeHeadUp), XtNfromHoriz, XtParent(changeHeadCount));

  if (changeChordTotalVoices < 2) {
    YSetValue(changeHeadUp,   XtNsensitive, False);
    YSetValue(changeHeadDown, XtNsensitive, False);
  }    

  YGetValue(changeHeadCount, XtNwidth, &wd);
  if (wd < 85) wd = 85;

  YSetValue(changeNote,      XtNwidth, wd);
  YSetValue(changeUp,        XtNwidth, wd);
  YSetValue(changeDown,      XtNwidth, wd);
  YSetValue(changeHeadCount, XtNwidth, wd);
  changeHeadCountWidth = wd;

  YSetValue(changeNote, XtNinternalWidth,  0);
  YSetValue(changeNote, XtNinternalHeight, 0);

  changeApply = YCreateCommand("OK", changeLowerBox);
  changeCancel = YCreateCommand("Cancel", changeLowerBox);

  YSetValue(XtParent(changeCancel), XtNfromHoriz, XtParent(changeApply));

  if (appData.interlockWindow) {
    changeHelp = YCreateCommand("Help", changeLowerBox);
    YSetValue(XtParent(changeHelp), XtNfromHoriz, XtParent(changeCancel));
    XtAddCallback(changeHelp, XtNcallback, yHelpCallbackCallback,
		  (XtPointer)"Editor Edit - Edit Chord");
  } else {
    changeHelp = NULL;
  }

  XtAddCallback(changeUp,       XtNcallback,
		ChangeUp,       (XtPointer)&madeChange);
  XtAddCallback(changeDown,     XtNcallback,
		ChangeDown,     (XtPointer)&madeChange);
  XtAddCallback(changeOctUp,    XtNcallback,
		ChangeOctUp,    (XtPointer)&madeChange);
  XtAddCallback(changeOctDown,  XtNcallback,
		ChangeOctDown,  (XtPointer)&madeChange);
  XtAddCallback(changeShorter,  XtNcallback,
		ChangeShorter,  (XtPointer)&madeChange);
  XtAddCallback(changeLonger,   XtNcallback,
		ChangeLonger,   (XtPointer)&madeChange);
  XtAddCallback(changeHeadUp,   XtNcallback,
		ChangeHeadUp,   (XtPointer)&madeChange);
  XtAddCallback(changeHeadDown, XtNcallback,
		ChangeHeadDown, (XtPointer)&madeChange);

  XtAddCallback(changeApply,  XtNcallback, ChangeApply, (XtPointer)&result);
  XtAddCallback(changeCancel, XtNcallback, ChangeCancel, (XtPointer)&result);

  madeChange = False;
  result = -1;

  YPushPointerPosition();
  YPlacePopupAndWarp(changeShell, XtGrabNonexclusive,
		     changeCancel, changeCancel);

  ChangeChordDrawChord(changeNote, wd, ht);

  context = XtWidgetToApplicationContext(changeShell);
  while (result < 0 || XtAppPending(context)) {

    XtAppProcessEvent(context, XtIMAll);

    if (madeChange) {
      ChangeChordDrawChord(changeNote, wd, ht);
      madeChange = False;
    }
  }  

  XtPopdown(changeShell);
  XtDestroyWidget(changeShell);
  YPopPointerPosition();

  if (result == 0) End;

  /* This is a very poor way to do this: */

  markList = end->item->item.marks;
  end->item->item.marks = 0;
  DestroyChord((Chord *)end->item);

  end->item = (Item *)NewChord(NULL,
			       changeChordNoteVoices, changeChordTotalVoices,
			       changeChordTempChord->chord.modifiers,
			       changeChordTempChord->chord.visual->type,
			       changeChordTempChord->chord.visual->dotted);
  
  end->item->item.marks = markList;

  DestroyChord(changeChordTempChord);
  ItemListNotifyGroupChange(end);
  FileMenuMarkChanged(stave, True);
  StaveResetFormatting(stave, staveNo);
  StaveRefreshAsDisplayed(stave);

  End; 
}



/* These used to be in MenuGroup.c */

void EditMenuChord(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  int            staveNo = mstave->sweep.stave;
  int            voiceCount, i, j;
  NoteVoice     *voices;
  ItemList       list;
  Chord         *chord;
  ChordMods      mods = ModNone;
  NoteTag        tag;
  Boolean        dotted;
  MarkList       marklist = 0;
  ItemList       newItem;

  Begin("EditMenuChord");

  if (!stave || !mstave->sweep.swept || !end || start == end) {

    XBell(display, 70);

  } else {

    mstave->music[staveNo] =
      ItemListExpungeGroups(mstave->music[staveNo], start, end);

    for (voiceCount = 0, list = iNext(start);
	 list && iPrev(list) != end; list = iNext(list)) {

      if (list->item->object_class !=  ChordClass &&
	  list->item->object_class !=   TextClass) {

	IssueMenuComplaint
	  ("You can only chord notes, not other musical objects.");

	End;
      }

      if (list->item->object_class == ChordClass)
	voiceCount += ((Chord *)list->item)->chord.voice_count;
    }

    if (voiceCount == 0) {

      IssueMenuComplaint("There are no notes here to chord.");
      End;
    }

    voices = (NoteVoice *)XtMalloc(voiceCount * sizeof(NoteVoice));
    voiceCount = 0;
    list = iNext(start);

    tag = ShortestNote;
    dotted = False;

    while (list) {

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

	/* accumulate marks onto one list, for the new chord */
	marklist = (MarkList)Nconc(marklist, list->item->item.marks);

	chord  = (Chord *)list->item;
	mods  |= chord->chord.modifiers;

	if ( chord->chord.visual->type >  tag ||
	    (chord->chord.visual->type == tag &&
	     chord->chord.visual->dotted)) {

	  tag    = chord->chord.visual->type;
	  dotted = chord->chord.visual->dotted;
	}

	for (i = 0; i < chord->chord.voice_count; ++i) {

	  for (j = 0; j < voiceCount; ++j)
	    if (voices[j].pitch     == chord->chord.voices[i].pitch &&
		voices[j].modifiers == chord->chord.voices[i].modifiers) break;

	  if (j >= voiceCount) voices[voiceCount++] = chord->chord.voices[i];
	}

	chord->methods->destroy((MusicObject)chord);

	if (list == end) { Remove(list); break; }
	else list = (ItemList)Remove(list);

      } else list = iNext(list);
    }
    
    newItem = NewItemList((Item *)NewChord(NULL, voices, voiceCount,
	 				   mods, tag, dotted)); 

    newItem->item->item.marks = marklist;
    
    if (Next(start)) Insert(newItem, Next(start));
    else Nconc(start, newItem);

    FileMenuMarkChanged(stave, True);
    StaveResetFormatting(stave, staveNo);
    StaveRefreshAsDisplayed(stave);
  }

  End;
}


void EditMenuUnchord(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  ItemList       end     = mstave->sweep.to.left;
  int            staveNo = mstave->sweep.stave;
  int            voiceCount, i;
  NoteVoice     *voices;
  NoteVoice     *newVoice;
  Chord         *chord;
  ChordMods      mods = ModNone;
  NoteTag        tag;
  Boolean        dotted;
  ItemList       newItem;

  Begin("EditMenuUnchord");

  if (!stave || !mstave->sweep.swept || !end || start == end) {

    XBell(display, 70);

  } else {

    if (start != iPrev(end)) {
      IssueMenuComplaint("You can only unchord a single chord.");
      End;
    }

    if (end->item->object_class != ChordClass) {
      IssueMenuComplaint("You can't unchord that; it's not a chord.");
      End;
    }

    chord      = (Chord *)end->item;
    voices     = chord->chord.voices;
    voiceCount = chord->chord.voice_count;
    mods       = chord->chord.modifiers;
    tag        = chord->chord.visual->type;
    dotted     = chord->chord.visual->dotted;

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

      newVoice = (NoteVoice *)XtMalloc(sizeof(NoteVoice));
     *newVoice = voices[i];

     newItem =
       NewItemList((Item *)NewChord(NULL, newVoice, 1, mods, tag, dotted));

     if (i == 0) {
       newItem->item->item.marks = chord->item.marks;
     }

     Insert(newItem, end);
    }

    chord->methods->destroy((MusicObject)chord);
    Remove(end);

    FileMenuMarkChanged(stave, True);
    StaveResetFormatting(stave, staveNo);
    StaveRefreshAsDisplayed(stave);
  }
}


Chord *SpellChord(char *, ClefTag, Boolean);


void EditMenuCreateChord(Widget w, XtPointer a, XtPointer b)
{
  MajorStaveRec *mstave  = (MajorStaveRec *)stave;
  ItemList       start   = mstave->sweep.from.left;
  int            staveNo = mstave->sweep.stave;
  Item          *newItem;
  ItemList       newItemList;
  String         str;
  static String  prevStr = NULL;
  char           *chordName;

  Begin("EditMenuCreateChord");

  if (!stave || !start) {
    XBell(display, 70);
    End;
  }

  if (!prevStr) prevStr = XtNewString("C");

  if ((str = YGetUserInput(topLevel, "Chord name:", prevStr, YOrientHorizontal,
			   "Editor Edit - Create Chord")) == NULL) End;

  XtFree(prevStr);
  prevStr = XtNewString(str);

  chordName = strtok(str, ",");
  while(chordName) {
    newItem = (Item *)SpellChord
      (chordName,
       StaveItemToClef(stave, staveNo, start)->clef.clef,
       StaveItemToKey(stave, staveNo, start)->key.visual->sharps);

    if (!newItem) {
      XBell(display, 70);
      break;
    }

    newItemList = NewItemList(newItem);
    if (Next(start)) Insert(newItemList, Next(start));
    else Nconc(start, newItemList);

    chordName = strtok(NULL, ",");
  }

  FileMenuMarkChanged(stave, True);
  StaveResetFormatting(stave, staveNo);
  StaveRefreshAsDisplayed(stave);
  End;
}

