
/* Methods.c */

/* Musical Notation Editor for X, Chris Cannam 1994    */
/* Class methods, excluding constructors, Draw methods */
/* and all methods within the MajorStave               */

/* Feb 95: occurrences of "this" changed to "curr" to  */
/* satisfy C++ compilers                               */

#include <Lists.h>

#include "General.h"
#include "Tags.h"
#include "Visuals.h"
#include "Classes.h"
#include "Notes.h"
#include "GC.h"
#include "ItemList.h"

Methods itemMethods = {
  GetZeroWidth,   GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    CloneItem,
  WriteNothing,   MidiWriteNothing, DrawNothing,
  DrawMTNothing,  DestroyItem,  
};

Methods metronomeMethods = {
  GetZeroWidth,   GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    CloneMetronome,
  WriteMetronome, MidiWriteNothing, DrawMetronome,
  DrawMTMetronome,DestroyMetronome,
};

Methods clefMethods = {
  GetClefWidth,   GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    CloneClef,
  WriteClef,      MidiWriteNothing, DrawClef,
  DrawMTClef,     DestroyClef,  
};

Methods keyMethods = {
  GetKeyWidth,    GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    CloneKey,
  WriteKey,       MidiWriteNothing, DrawKey,
  DrawMTKey,      DestroyKey,   
};

Methods textMethods = {
  GetZeroWidth,   GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    CloneText,
  WriteText,      MidiWriteText,    DrawText,
  DrawMTText,     DestroyText,  
};

Methods phraseMethods = {
  GetZeroWidth,   GetLongestChord,  GetLowestNote,
  GetHighestNote, GetZeroLength,    ClonePhrase,
  WriteNothing,   MidiWriteNothing, DrawNothing,
  DrawMTNothing,  DestroyPhrase,
};

/* rests are written to MIDI even though they're empty, so as to */
/* enable emphasis deduced from their existence, or something:   */

Methods restMethods = {
  GetRestWidth,   GetRestShortest,  GetLowestNote,
  GetHighestNote, GetRestLength,    CloneRest,
  WriteRest,      MidiWriteRest,    DrawRest,
  DrawMTRest,     DestroyRest,  
};

Methods chordMethods = {
  GetChordWidth,  GetChordShortest, GetChordHighest,
  GetChordLowest, GetChordLength,   CloneChord,
  WriteChord,     MidiWriteChord,   DrawChord,
  DrawMTChord,    DestroyChord,  
};

Methods groupMethods = {
  GetGroupWidth,  GetGroupShortest, GetGroupHighest,
  GetGroupLowest, GetGroupLength,   CloneGroup,
  WriteGroup,     MidiWriteNothing, DrawGroup,
  DrawMTGroup,    DestroyGroup,  
};

Methods barMethods = {
  GetZeroWidth,   GetGroupShortest, GetGroupHighest,
  GetGroupLowest, GetGroupLength,   CloneBar,
  WriteNothing,   MidiWriteNothing, DrawNothing,
  DrawMTBar,      DestroyBar,
};


NoteVoice highestNoteVoice =
{
  (Pitch)20, ModSharp, ModSharp,
};

NoteVoice lowestNoteVoice =
{
  (Pitch)-10, ModFlat, ModFlat,
};

NoteVoice metronomeNoteVoice =
{
  (Pitch)3, ModNone, ModNone,
};

Chord longestChord =
{
  ChordClass,
  &chordMethods,
  { NULL, 0, },
  { False, False, },
  {
    longestNoteTime,
    ModNone, &(noteVisuals[LongestNote].dotted),
    &lowestNoteVoice, (short)1, NoteModWidth,
  },
};

TimeSignature defaultTimeSignature =
{
  4, 4, 64L,
};

Clef defaultClef =
{
  ClefClass,
  &clefMethods,
  { NULL, 0, },
  { TrebleClef, &clefVisuals[0], },
};

Key defaultKey =
{
  KeyClass,
  &keyMethods,
  { NULL, 0, },
  { KeyC, KeyC, &keyVisuals[(int)KeyC], },
};



Pitch ClefPitchOffset(ClefTag clef)
{
  Begin("ClefPitchOffset");

  switch(clef) {

  case  TrebleClef: Return( 0);
  case    BassClef: Return(-2);
  case    AltoClef: Return(-1);
  case   TenorClef: Return( 1);
  case InvalidClef: Return( 0);
  }

  Return(0);
}


Dimension GetZeroWidth(MusicObject item)
{
  Begin("GetZeroWidth");
  Return(0);
}

MTime GetZeroLength(MusicObject item)
{
  Begin("GetZeroLength");
  Return(zeroTime);
}

NoteVoice *GetHighestNote(MusicObject item)
{
  Begin("GetHighestNote");
  Return(&highestNoteVoice);
}

NoteVoice *GetLowestNote(MusicObject item)
{
  Begin("GetLowestNote");
  Return (&lowestNoteVoice);
}

MusicObject GetLongestChord(MusicObject item)
{
  Begin("GetLongestChord");
  Return((MusicObject)&longestChord);
}



Dimension GetChordWidth(MusicObject chord)
{
  Dimension width;
  Begin("GetChordWidth");
  if (!chord) return 0;

  width = NoteWidth +
    (((Chord *)chord)->chord.note_modifier_width) +
    (((Chord *)chord)->chord.visual->dotted? DotWidth : 0);

  /* if we've got any second intervals, we're gonna want more width;
     should we try to deal with that here? */

  Return(width);
}


MTime GetChordLength(MusicObject chord)
{
  Begin("GetChordLength");
  Return(chord ? ((Chord *)chord)->chord.length : zeroTime);
}


MusicObject GetChordShortest(MusicObject obj)
{
  Begin("GetChordShortest");
  Return(obj ? obj : (MusicObject)&longestChord);
}


NoteVoice *GetChordHighest(MusicObject obj)
{
  Chord *c = (Chord *)obj;
  Begin("GetChordHighest");

  if (c && c->chord.voice_count > 0) {
    Return(&c->chord.voices[c->chord.voice_count-1]);
  } else {
    Return(GetLowestNote((MusicObject)NULL));
  }
}


NoteVoice *GetChordLowest(MusicObject obj)
{
  Chord *c = (Chord *)obj;
  Begin("GetChordLowest");

  if (c && c->chord.voice_count > 0) {
    Return(&c->chord.voices[0]);
  } else {
    Return(GetHighestNote((MusicObject)NULL));
  }
}


Dimension GetClefWidth(MusicObject clef)
{
  Begin("GetClefWidth");
  Return(clef ? ClefWidth + 3 : 0);
}


Dimension GetKeyWidth(MusicObject obj)
{
  Key *key = (Key *)obj;
  Dimension width = 0;
  KeyVisual prevV;
  Boolean sharps;
  int n, number;

  Begin("GetKeyWidth");

  sharps = key->key.visual->sharps;
  number = key->key.visual->number;

  for (n = 0; n < keyVisualCount; ++n) {
    if (keyVisuals[n].key == key->key.changing_from) prevV = &keyVisuals[n];
  }

  if (prevV->sharps == sharps && number < prevV->number) {
    width = (prevV->number - number) * (NoteModWidth - 2) + 4;
  } else {
    if (prevV->sharps != sharps) {
      width = prevV->number * (NoteModWidth - 2) + 4;
    }
  }

  width += number * (NoteModWidth - 2);

  Return(width + 3);

  /*  Return(key ? ((Key *)key)->key.visual->number * (NoteModWidth - 2) + 3 : 0);*/
}


Dimension GetTimeSignatureWidth(MusicObject timesig)
{
  XGCValues          values;
  static int         dir, asc, dsc = -1;
  static XCharStruct info;

  Begin("GetTimeSignatureWidth");
  
  if (dsc == -1) {
    XGetGCValues(display, timeSigGC, GCFont, &values);
    XQueryTextExtents(display, values.font, "4", 1, &dir, &asc, &dsc, &info);
    dsc = 0;
  }

  if (((TimeSignature *)timesig)->numerator   > 9 ||
      ((TimeSignature *)timesig)->denominator > 9)
    Return(2 * info.width + 8);
  else  Return(info.width + 14); /* was 8 */
}


Dimension GetRestWidth(MusicObject rest)
{
  Begin("GetRestWidth");
  Return(rest ? ((Rest *)rest)->rest.visual->width : 0);
}


MTime GetRestLength(MusicObject rest)
{
  Begin("GetRestLength");
  Return(rest ? ((Rest *)rest)->rest.length : zeroTime);
}


/* bit of a hack here */

MusicObject GetRestShortest(MusicObject obj)
{
  static Chord restChords[NoteCount*2];
  Rest *rest = (Rest *)obj;
  int i;
  Begin("GetRestShortest");

  if (!rest) Return((MusicObject)&longestChord);

  i = rest->rest.visual->type + (rest->rest.visual->dotted ? NoteCount : 0);

  (void)NewChord(&restChords[i], &lowestNoteVoice, 0, ModNone,
		 rest->rest.visual->type, rest->rest.visual->dotted);

  Return((MusicObject)&restChords[i]);
}


Dimension GetGroupWidth(MusicObject obj)
{
  Dimension  width = 0;
  Group     *group = (Group *)obj;
  ItemList   list;

  Begin("GetGroupWidth");

  if (!group) Return(0);

  for (ItemList_ITERATE_GROUP(list, group))
    width +=
      list->item->methods->get_min_width((MusicObject)(list->item));

  Return(width);
}


MTime GetGroupLength(MusicObject obj)
{
  MTime     curr;
  MTime     shortest;
  MTime     length = zeroTime;
  Group    *group  = (Group *)obj;
  ItemList  list;

  Begin("GetGroupLength");

  if (!group || (group->group.type == GroupDeGrace)) Return(zeroTime);

  shortest = longestChord.chord.length;

  if (group->group.type == GroupTupled) {
    length = group->group.tupled_length;
  } else {
    for (ItemList_ITERATE_GROUP(list, group)) {   
      curr   = list->item->methods->get_length((MusicObject)(list->item));
      length = AddMTime(length, curr);
    }
  }

  Return(length);
}


MusicObject GetGroupShortest(MusicObject obj)
{
  Chord   *curr;
  Chord   *shortest;
  Group   *group = (Group *)obj;
  ItemList list;

  Begin("GetGroupShortest");

  shortest = (Chord *)GetLongestChord((MusicObject)NULL);
  if (!obj || group->group.type == GroupDeGrace) Return(shortest);

  for (ItemList_ITERATE_GROUP(list, group)) {

    curr = (Chord *)(list->item->methods->get_shortest
		     ((MusicObject)(list->item)));
    if (MTimeLesser(curr->chord.length, shortest->chord.length))
      shortest = curr;
  }

  Return((MusicObject)shortest);
}


NoteVoice *GetGroupHighest(MusicObject obj)
{
  NoteVoice *curr;
  NoteVoice *highest;
  Group     *group = (Group *)obj;
  ItemList   list;

  Begin("GetGroupHighest");

  highest = GetLowestNote((MusicObject)NULL);
  if (!obj || group->group.type == GroupDeGrace) Return(highest);

  for (ItemList_ITERATE_GROUP(list, group)) {

    curr = list->item->methods->get_highest((MusicObject)(list->item));
    if (curr->pitch > highest->pitch) highest = curr;
  }

  Return(highest);
}


NoteVoice *GetGroupLowest(MusicObject obj)
{
  NoteVoice *curr;
  NoteVoice *lowest;
  Group     *group = (Group *)obj;
  ItemList   list;

  Begin("GetGroupLowest");

  lowest = GetHighestNote((MusicObject)NULL);
  if (!obj || group->group.type == GroupDeGrace) Return(lowest);

  for (ItemList_ITERATE_GROUP(list, group)) {

    curr = list->item->methods->get_lowest((MusicObject)(list->item));
    if (curr->pitch < lowest->pitch) lowest = curr;
  }

  Return(lowest);
}


/* To find the minimum comfortable width of a whole bar, we need  */
/* the absolute minimum width of the bar (by "casting" it to a    */
/* Group, and getting the width of that), and then add on as much */
/* comfortable width as would be needed if the bar was made up    */
/* entirely of repetitions of its shortest note (this being the   */
/* scenario that would take most space).  This gap is the product */
/* of the number of the bar's shortest notes that will fit in the */
/* length of the bar, and the comfortable gap for each of those.  */

Dimension GetBarWidth(Bar *bar, Bar *prevBar)
{
  Dimension width = 0;
  Dimension minWidth;
  ItemList  list;
  ItemList  subList;
  Chord    *curr;
  Chord    *shortest;
  int       shortCount;
  MTime     length;
  MTime     totalLen;
  BarTagElt start, end;

  Begin("GetBarWidth");

  if (!bar) Return(ClefWidth + NoteWidth);              /* arbitrary */

  /* this fails to cope with the stretching that StaveRefresh now does: */
  /*  if (bar->bar.still_as_drawn) Return(bar->bar.width); */

  shortest = (Chord *)GetLongestChord((MusicObject)NULL);
  totalLen = zeroTime;
  shortCount = 0;

  /* can't actually use get_min_width on the Group parent of the bar,
     because it may contain Groups itself */

  for (ItemList_ITERATE_GROUP(list, bar)) {

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

      for (ItemList_ITERATE_GROUP(subList, ((Group *)list->item))) {
	
	length = subList->item->methods->get_length
	  ((MusicObject)(subList->item));
	
	if (MTimeEqual(length, zeroTime))
	  width += subList->item->methods->get_min_width
	    ((MusicObject)(subList->item));
      }
      /* hack: */
      if (((Group *)list->item)->group.type == GroupTupled) {
	width += NoteWidth + 4;
      }
    }

    length = list->item->methods->get_length((MusicObject)(list->item));
    totalLen += length;

    if (MTimeEqual(length, zeroTime)) {

      width +=
	list->item->methods->get_min_width((MusicObject)(list->item));

    } else {

      curr = (Chord *)(list->item->methods->
		       get_shortest((MusicObject)(list->item)));

      if (MTimeLesser(curr->chord.length, shortest->chord.length)) {
	shortCount = 1;
	shortest = curr;
      } else if (MTimeEqual(curr->chord.length, shortest->chord.length)) {
	shortCount ++;
      }
    }
  }

  minWidth = shortest->methods->get_min_width((MusicObject)shortest);
  if (shortest->chord.note_modifier_width == 0) minWidth += NoteModWidth/2;

  /* if there aren't many of the shortest notes, we don't want to
     allow so much space to accommodate them */
  if (shortCount < 3) minWidth -= 3 - shortCount;

  width +=
  (MTimeToNumber(totalLen) *
    (shortest->chord.visual->comfortable_gap + minWidth) /
     MTimeToNumber(shortest->methods->get_length((MusicObject)shortest)));

  /* if we'll need to draw a timesig, add the width */
  if (!prevBar ||
      prevBar->bar.time.numerator != bar->bar.time.numerator ||
      prevBar->bar.time.denominator != bar->bar.time.denominator) {

    width += GetTimeSignatureWidth(&bar->bar.time);
  }

  start = BAR_START_TYPE(bar);
  width += (start == NoFixedBar ? 3 : start == RepeatBar ? 5 + DotWidth : 5);

  end = BAR_END_TYPE(bar);
  width += (end == NoFixedBar ? 3 : end == RepeatBar ? 10 + DotWidth : 10);

  Return(width + 3);
}

