
/*
 * 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 3 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 Library 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., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

#include <errno.h>
#include <math.h>
#include <glib/gstdio.h>
#include "audio/playback.h"
#include "export/print.h"
#include "core/view.h"
#include "core/menusystem.h"
#include "command/scorelayout.h"
#include "command/lilydirectives.h"
#include "export/exportlilypond.h"
#include "export/exportmidi.h"
#include "printview/printview.h"
#include "scripting/scheme-callbacks.h"
#include "source/sourceaudio.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <librsvg/rsvg.h>


#ifndef USE_EVINCE
#ifndef USE_ATRIL
void set_continuous_typesetting (gboolean setting){return FALSE;}
gboolean continuous_typesetting () {return FALSE;}
#endif
#endif

static gint changecount = -1;   //changecount when the playback typeset was last created
static gboolean RightButtonPressed = FALSE;
static gboolean LeftButtonPressed = FALSE;
static gboolean Dragging = FALSE;
static gint RightButtonX, LeftButtonX, DragX;
static gint RightButtonY, LeftButtonY, DragY, LastY;
static gdouble IntroTime = 10.0, ScrollRate = 10.0;
static gboolean AllPartsTypeset = FALSE;
static gboolean PartOnly = FALSE;
static GtkAdjustment *VAdj = NULL;
static gdouble ScrollTime = -1.0;
static GtkWidget *ClearScrollPointsButton = NULL;
static gchar *LilyPondSVGPageSuffix = "-"; // earlier LilyPond versions output -page-n.svg for nth page of SVG
static GtkWidget *playalong_toggle;
typedef struct Timing {
    gdouble time;
    gdouble duration;
    gdouble x;
    gdouble y;
    gint line;
    gint col;
    DenemoObject *object;//the denemo object that corresponds to line, col
} Timing;

GList *TheTimings = NULL, *LastTiming=NULL, *NextTiming=NULL;
gdouble TheScale = 1.0; //Scale of score font size relative to 18pt
static gint Locationx = -1, Locationy;




/* Defines for making traversing XML trees easier */

#define FOREACH_CHILD_ELEM(childElem, parentElem) \
for ((childElem) = (parentElem)->children; \
     (childElem) != NULL; \
     (childElem) = (childElem)->next)

#define ELEM_NAME_EQ(childElem, childElemName) \
(strcmp ((gchar *)(childElem)->name, (childElemName)) == 0)

#define ILLEGAL_ELEM(parentElemName, childElem) \
do \
  { \
    g_warning ("Illegal element inside <%s>: <%s>", parentElemName, \
               (childElem)->name); \
  } while (0)

#define RETURN_IF_ELEM_NOT_FOUND(parentElemName, childElem, childElemName) \
do \
  { \
    if (childElem == NULL) \
      { \
        g_warning ("Element <%s> not found inside <%s>", childElemName, \
                   parentElemName); \
        return -1; \
      } \
  } while (0)

/**
 * Get the text from the child node list of elem, convert it to an integer,
 * and return it.  If unsuccessful, return G_MAXINT.
 */
static gint
getXMLIntChild (xmlNodePtr elem)
{
  gchar *text = (gchar *) xmlNodeListGetString (elem->doc, elem->children, 1);
  gint num = G_MAXINT;
  if (text == NULL)
    {
      g_warning ("No child text found %s", elem->name);
    }
  else
    {
      if (sscanf (text, " %d", &num) != 1)
        {
          g_warning ("Could not convert child text \"%s\" of <%s> to number", text, elem->name);
          num = G_MAXINT;
        }
      g_free (text);
    }
  return num;
}





//Ensures the playback view window is visible.
static void
show_playback_view (void)
{
    GtkWidget *w = gtk_widget_get_toplevel (Denemo.playbackview);
	if (Denemo.project->midi_destination & MIDIPLAYALONG)
		{
			gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (playalong_toggle), TRUE);
			Denemo.project->midi_destination |= MIDIPLAYALONG;
		}
	else
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (playalong_toggle), FALSE);

    if (!gtk_widget_get_visible (w))
        set_toggle (TogglePlaybackView_STRING, TRUE);
    else
		gtk_window_present (GTK_WINDOW (w));
		
	gtk_widget_hide (get_playalong_button());//only show toggle in playback view if it is made visible.
}


//draw a circle
static void
place_spot (cairo_t * cr, gdouble x, gdouble y, gdouble size)
{
  cairo_move_to (cr, x, y);
  cairo_arc (cr, x, y, size, 0.0, 2 * M_PI);
  cairo_fill (cr);
}

static void
get_window_size (gint * w, gint * h)
{
  GdkWindow *window;
  if (!GTK_IS_LAYOUT (Denemo.playbackview))
    window = gtk_widget_get_window (GTK_WIDGET (Denemo.playbackview));
  else
    window = gtk_layout_get_bin_window (GTK_LAYOUT (Denemo.playbackview));
  if (window)
    {

#if GTK_MAJOR_VERSION==2
      gdk_drawable_get_size (window, w, h);
#else
      *w = gdk_window_get_width (window);
      *h = gdk_window_get_height (window);
#endif
    }
}

gboolean attach_timings (void)
{
  if (TheTimings == NULL)
        return FALSE;
  GList *g;
  for (g=TheTimings;g;g=g->next)
    {
        Timing *this = (Timing *)g->data;
        DenemoObject *obj = get_object_at_lilypond (this->line, this->col);
        //g_print ("Attaching time %.2f (duration %.2f) (x=%.2f, y-%.2f) at line %d column %d\n",this->time, this->duration, this->x, this->y, this->line, this->col);
            if (obj)
               {
                  obj->earliest_time = this->time;
                  obj->latest_time = this->time + this->duration; //g_print ("Set %.2f %.2f\n", obj->earliest_time, obj->latest_time);
                  this->object = obj;
                }
            else
               return FALSE;
    }

  return TRUE;
}
DenemoObject *get_object_for_time (gdouble time, gboolean start)
{
    if ((changecount != Denemo.project->movement->changecount) || (Denemo.project->movement->changecount != Denemo.project->movement->smfsync))
        return NULL;
    GList *g;
    for (g=TheTimings;g;g=g->next)
        {
         Timing *this = (Timing *)g->data;
         //g_print ("Seeking %.2f Timing %.2f to %.2f\n", time, this->object->earliest_time, this->object->latest_time);
         if (this->object && ((start? this->object->earliest_time:this->object->latest_time) > time))
            return this->object;

        }
    return NULL;
}

static 
gint playalong_highlight = 0xb5;
void highlight_playback_note (gint color)
{
	playalong_highlight = color;//g_print ("set color");
	gtk_widget_queue_draw (Denemo.playbackview);
}
//over-draw the evince widget with padding etc ...
static gboolean needs_retypeset = FALSE;
static gboolean
overdraw_print (cairo_t * cr)
{
  gint x, y;
  gdouble this, duration;
  gboolean drew_rectangle = FALSE;
  if (needs_retypeset)
	{
			cairo_set_source_rgba (cr, 0.75, 0.55, 0.25, 0.8);
			cairo_set_font_size (cr, 40.0);
			cairo_move_to (cr, 1, 40);
			cairo_show_text (cr, ("Choose \"All Parts\" or \"Current Part\" to re-typeset the Playback View"));
	}
  if (Dragging)
    {   //g_print ("Dragging from %d %d to %d %d \n", RightButtonX, RightButtonY, DragX, DragY);
        if (RightButtonPressed)
            cairo_set_source_rgba (cr, 0.2, 0.8, 0.8, 0.5);
        else
            cairo_set_source_rgba (cr, 0.8, 0.2, 0.4, 0.5);
        cairo_set_line_width (cr, 5.0);
        cairo_move_to (cr, RightButtonPressed?(double)RightButtonX:(double)LeftButtonX, RightButtonPressed?(double)RightButtonY:(double)LeftButtonY);
        cairo_line_to (cr, (double)DragX, (double)DragY);
        cairo_stroke (cr);

       return TRUE;
    }
  if ((!audio_is_playing()) && (LeftButtonX))
            {
                cairo_set_source_rgba (cr, 0.4, 0.6, 0.8, 0.5);
                place_spot (cr, (gdouble)LeftButtonX, (gdouble)LeftButtonY, PRINTMARKER/2.0);
                cairo_fill (cr);
            }

  cairo_scale (cr, TheScale, TheScale);
  if(!audio_is_playing())
    {
        GList *g;
        cairo_set_source_rgba (cr, 0.5, 0.7, 0.2, 0.5);
         //cairo_set_source_rgba (cr, 0.8, 0.2, 0.4, 0.5);
         //cairo_rectangle (cr, 0 , 0, 10, 10);
        for (g=Denemo.project->movement->scroll_points;g;g=g->next)
            {
                DenemoScrollPoint *sp = (DenemoScrollPoint*)g->data;
                //g_print ("drawing at %.2f %.2f\n", (gdouble)sp->x  - (PRINTMARKER/5)/4, (gdouble)sp->y - (PRINTMARKER/5)/2);
                place_spot (cr, (gdouble)sp->x, (gdouble)sp->y, PRINTMARKER / 6.0);
            }


        cairo_fill (cr);
    return TRUE;
    }

  if (TheTimings == NULL)
        return TRUE;

  if (LastTiming == NULL)
        {
            LastTiming = TheTimings;
        }

    cairo_set_source_rgba (cr, 0x6e/255.0, 0xb9/255.0, 0xd5/255.0, 0.3);//6eb9d5

    gdouble time = Denemo.project->movement->playhead;
    GList *g;
    this = ((Timing *)LastTiming->data)->time;
    duration = ((Timing *)LastTiming->data)->duration;
    if (time < (this-0.01))
        {// g_print ("\n\n\nResetting LastTiming at %.2f for %.2f\n", time, this);
            LastTiming = TheTimings;
        }

    for(g=LastTiming;g && g->next;g=g->next)
        {
           this = ((Timing *)g->data)->time;
           duration = ((Timing *)g->data)->duration;
           //g_print (" %f this = %f test time>this %d and this-end < time %d Durations is %f\n ",  time,  this, (time > (this - 0.01)), (this + duration < time), duration);
           if (this + duration < time)
                       continue;
           if (time > (this - 0.1))
                    { // g_print ("draw note at %.2f %.2f\n", ((Timing *)((g)->data))->x  - (PRINTMARKER/5)/4, ((Timing *)((g)->data))->y - (PRINTMARKER/5)/2 );
						cairo_set_source_rgba (cr, playalong_highlight/255.0, 0x0/255.0, playalong_highlight/255.0, 0.5);
                        cairo_rectangle (cr, ((Timing *)((g)->data))->x  - (PRINTMARKER/5)/4, ((Timing *)((g)->data))->y - (PRINTMARKER/5)/2, PRINTMARKER/5, PRINTMARKER/5);
                        if(!drew_rectangle)
                            LastTiming = g;
                        drew_rectangle = TRUE;
                    }
            else
               break;
        }
  if (drew_rectangle)
     cairo_fill (cr);
  return TRUE;
}
static gboolean
predraw_print (cairo_t * cr)
{
  gint width, height;
  get_window_size (&width, &height);
  cairo_set_source_rgba (cr, 0xf1/255.0, 0xf4/255.0, 0x9d/255.0, 1.0);//cfdd36 f1f49d
  cairo_rectangle (cr, 0, 0.0, (gdouble)width, (gdouble)height);
  cairo_fill (cr);
  cairo_set_source_rgba (cr, 0x50/255.0, 0x0/255.0, 0x60/255.0, 1.0);//this should have no effect ...
  return FALSE;//propagate further
}
#if GTK_MAJOR_VERSION==3
static gint
playbackview_draw_event (G_GNUC_UNUSED GtkWidget * w, cairo_t * cr)
{
  return overdraw_print (cr);
}
static gint
playbackview_predraw_event (G_GNUC_UNUSED GtkWidget * w, cairo_t * cr)
{
  return predraw_print (cr);
}
#else
static gint
playbackview_draw_event (GtkWidget * widget, GdkEventExpose * event)
{
  /* Setup a cairo context for rendering and clip to the exposed region. */
  cairo_t *cr = gdk_cairo_create (event->window);
  gdk_cairo_region (cr, event->region);
  cairo_clip (cr);
  overdraw_print (cr);
  cairo_destroy (cr);
  return TRUE;
}
static gint
playbackview_predraw_event (GtkWidget * widget, GdkEventExpose * event)
{
  /* Setup a cairo context for rendering and clip to the exposed region. */
  cairo_t *cr = gdk_cairo_create (event->window);
  gdk_cairo_region (cr, event->region);
  cairo_clip (cr);
  predraw_print (cr);
  cairo_destroy (cr);
  return FALSE; //propagate further
}
#endif

static Timing *get_svg_position(gchar *id, GList *ids)
{
  Timing *timing = NULL;
  setlocale (LC_ALL, "C");
  for(;ids;ids=ids->next)
        {//g_print ("Testing %s with %s\n", ids->data, id);
            if (g_str_has_prefix ((gchar*)ids->data, id))
                {
                  timing = (Timing *)g_malloc (sizeof(Timing));
                  if (2==sscanf ((gchar*)ids->data, "Note-%*d-%*d translate(%lf,%lf)%*s%*s", &timing->x, &timing->y))
                    {
                       break;
                    } else if (2==sscanf ((gchar*)ids->data, "Rest-%*d-%*d translate(%lf,%lf)%*s%*s", &timing->x, &timing->y))
                    {
                      break;
                    }
                }
        }
    localization_init ();
    if (timing==NULL)    
        g_warning ("Failed to find a position in events.txt for %s\n", id);
    return timing;
}

static void add_note (Timing *t)
{
    TheTimings = g_list_append (TheTimings, (gpointer)t);
    //g_print ("Added %.2f seconds (%.2f,%.2f)\n", t->time, t->x, t->y);
}
static void free_timings (void)
{
    GList *g;
    for (g = TheTimings; g;g=g->next)
        {
            g_free(g->data);
        }
    g_list_free (TheTimings);
    TheTimings = NULL;
    LastTiming = NextTiming = NULL;
}

static void compute_timings (gchar *base, GList *ids)
{
    free_timings();
    gchar *events = g_build_filename (base, "events.txt", NULL);
    FILE *fp = fopen (events, "r");
    //g_print ("Collected %d ids\n", g_list_length (ids));
    if(fp)
        {
            gdouble moment, duration;
            gchar type [10];
            gint  col, line, midi;
            gdouble tempo = 60;
            gdouble timeCoef =  4;
            gdouble latestMoment = 0;
            gdouble adjustedElapsedTime = 0;
            gdouble nextTempo = 0;
            gdouble nextTempoMoment = 0;
            gboolean incomingTempo = FALSE;
            while (2 == fscanf (fp, "%lf%10s", &moment, type))
                { 
               //g_print ("moment %.2f %s latestMoment %.2f\n", moment, type, latestMoment);
                moment /= 1000; //now events.txt stores ms to avoid decimal point
                  if (!strcmp (type, "tempo"))
                        {
                            if (1 == fscanf (fp, "%lf", &nextTempo))
                                {
                                nextTempo /= 1000; //now events.txt stores ms to avoid decimal point
                                nextTempoMoment = moment;//g_print ("Next tempo %s %.2f\n", type, nextTempo);
                                incomingTempo = TRUE;
                                } else g_warning ("Malformed events file");
                        }
                 else
                    {//breve duration is log = -1  if longa log = -2
						//e.g.  0	rest	log = -1	2000	point-and-click 9 21
					gchar basedur[10];
					if (1 == fscanf (fp, "%9s", basedur))
						{ //g_print("basedur is %s\n", basedur);
						if (!strcmp (basedur, "log"))
							fscanf (fp, " %*s %*s");
                        if (!strcmp (type, "note"))
                            {
								if (4 == fscanf (fp, " %lf%*s%d%d%d", &duration, &col, &line, &midi))
                                    {
                                       //g_print ("moment ... %.2f %s %.2f %d %d %d\n", moment, type, duration, col, line, midi);
                                       duration /= 1000; //now events.txt stores ms to avoid decimal point
                                       if (incomingTempo)
                                        {
                                            if (moment > nextTempoMoment)
                                                {
                                                    tempo = nextTempo;//g_print (" tempo %.2f\n", tempo);
                                                    timeCoef = (60 / tempo);//g_print (" timeCoef %.2f\n", timeCoef);
                                                    incomingTempo = FALSE;
                                                }
                                        }
                                        gdouble elapsedTime = moment - latestMoment;
                                        adjustedElapsedTime += elapsedTime * timeCoef;//g_print ("adjustedElapsedtime %f\n", adjustedElapsedTime);
                                        gchar *idStr;
                                        Timing *timing;

                                                idStr = g_strdup_printf ("Note-%d-%d" , line, col);
                                                timing = get_svg_position (idStr, ids);

                                                if(timing)
                                                    {
                                                    timing->line = line;
                                                    timing->col = col;
                                                    timing->time = adjustedElapsedTime;
                                                    timing->duration = duration;
                                                    add_note (timing);//g_print ("AdjustedElapsed time %.2f note %d line %d column %d\n", adjustedElapsedTime, midi, line, col);
                                                    }
                                    }
                                    else
                                    g_warning ("Could not parse type %s\n", type);
                            }
                        else if (!strcmp (type, "rest"))
                            {
                                if (3 == fscanf (fp, " %lf%*s%d%d",  &duration, &col, &line))
                                    {
                                       //g_print ("moment ... %.2f %s %.2f %d %d %d\n", moment, type, duration, col, line, midi);
                                       duration /= 1000; //now events.txt stores ms to avoid decimal point
                                       if (incomingTempo)
                                        {
                                            if (moment > nextTempoMoment)
                                                {
                                                    tempo = nextTempo;//g_print (" tempo %.2f\n", tempo);
                                                    timeCoef = (60 / tempo);//g_print (" timeCoef %.2f\n", timeCoef);
                                                    incomingTempo = FALSE;
                                                }
                                        }
                                        gdouble elapsedTime = moment - latestMoment;
                                        adjustedElapsedTime += elapsedTime * timeCoef;//g_print ("adjustedElapsedtime %f\n", adjustedElapsedTime);
                                        gchar *idStr;
                                        Timing *timing;
										idStr = g_strdup_printf ("Rest-%d-%d" , line, col);
										timing = get_svg_position (idStr, ids);
										if(timing)
											{
											timing->line = line;
											timing->col = col;
											timing->time = adjustedElapsedTime;
											add_note (timing);//g_print ("AdjustedElapsed time %.2f rest \n", adjustedElapsedTime);
											}
									} else
										g_warning ("Could not parse type %s\n", type);

							} //rest
                          else 
							g_warning ("Don't know how to handle %s\n", type);
                          }
                            latestMoment = moment;
                        }// not tempo
                    } //while events
                 //g_print ("Finished collecting timings");
                fclose (fp);
                stop_typeset_progress ();
                needs_retypeset = FALSE;
            } //if events file
    else
        {
			gchar *msg = g_strdup_printf ("Failure! No events file created by LilyPond - file too big???.");
			warningdialog (msg);
			g_free (msg);
          g_critical ("Unable to open file %s Playback View will not work", events);  
        }

    g_free (events);
}
static xmlNodePtr get_path_elem (xmlNodePtr rootElem)
{
xmlNodePtr childElem;
FOREACH_CHILD_ELEM (childElem, rootElem)
	{
	//g_print ("Elem %s has transform %s\n", childElem->name, xmlGetProp (childElem, (xmlChar *) "transform"));
	if (ELEM_NAME_EQ (childElem, "g"))
		{
			if (xmlGetProp (childElem, (xmlChar *) "transform"))
				return childElem;
			else
				return get_path_elem (childElem);
		}
	else 			
		if (xmlGetProp (childElem, (xmlChar *) "transform"))
			return childElem;
	}
return NULL;
}
static GList * create_positions (gchar *filename)
{
  GList *ret = NULL;
  GError *err = NULL;
  xmlDocPtr doc = NULL;
  xmlNsPtr ns;
  xmlNodePtr rootElem;
  /* ignore blanks between nodes that appear as "text" */
  xmlKeepBlanksDefault (0);
  /* Try to parse the file(s). */
  filename = g_strdup (filename); //we may modify it
  while (g_file_test (filename, G_FILE_TEST_EXISTS)) //multiple svg files
    {
      doc = xmlParseFile (filename);
      if (doc == NULL)
        {
          g_warning ("Could not read svg file %s", filename);
          break;
        }
        else
        {
		  //g_print ("Parsing %s\n", filename);
          rootElem = xmlDocGetRootElement (doc);
          xmlNodePtr childElem;
          FOREACH_CHILD_ELEM (childElem, rootElem)
          {
              if (ELEM_NAME_EQ (childElem, "g"))
                { xmlNodePtr pathElem;
                  gchar *id = xmlGetProp (childElem, (xmlChar *) "id");
                  //g_print ("Elem %s has id %s\n", childElem->name, id);
                  if (id)
					{
					  pathElem = get_path_elem (childElem);
					  if (pathElem)
						{
							gchar *coords = xmlGetProp (pathElem, (xmlChar *) "transform");
							//g_print ("ID %s has Coords %s\n", id, coords);
							if (id && coords)
								{
								gchar *data = g_strconcat (id, coords, NULL);
								ret = g_list_append (ret, data);
								xmlFree (id);
								xmlFree (coords);
								}
						}
					}
                }
			}
		}
    if (doc != NULL)
         xmlFreeDoc (doc);
    //It may have spilt over into several svg files denemoprintA-page-1.svg etc
    gint num_pos = strlen (filename)-5;//"<n>.svg"
       *(filename+num_pos) = *(filename+num_pos) + 1; //no attempt beyond 9 pages!
       //FIXME check that mtime of this file is later than the last, or delete old svg's before starting.
    }
  //g_print ("Read %d ids from file %s\n", g_list_length (ret), filename);
  g_free (filename);
  return ret;
}
static gint get_number_of_pages (gchar *base)
{
    gint i;
    for (i=1;i<10;i++)
        {
            gchar *filename = g_strdup_printf ("%s%s%d%s", Denemo.printstatus->printbasename[Denemo.printstatus->cycle], "-", i, ".svg");
            if (!(g_file_test (filename, G_FILE_TEST_EXISTS)))
                {
                    g_free(filename);
                    break;
                }
            //g_print ("Found %s\n", filename);
            g_free (filename);
        }
   return i-1;
}

static gchar *get_script (gboolean part)
{
    gint w, h;
    GtkWidget *win = gtk_widget_get_toplevel (Denemo.playbackview); 
    gtk_window_get_size (GTK_WINDOW(win), &w, &h); //why does get_window_size() not work???
    //g_print ("Width = %d\n", w);//default 709
    w = 20*w/709.0;
    return g_strdup_printf ("(d-PlaybackView (list %s \"%d\" \"100\"))", part?"#t":"#f", w);	
}



static gboolean
set_playback_view (void)
{
  static gint num_pages = 0; //although not directly recursive, this function does spawn off another lilypond typeset if it there are multiple svg pages created, when finished that calls this routine.
  GFile *file;
  gchar *filename = g_strdup (Denemo.printstatus->printname_svg[Denemo.printstatus->cycle]);
  gboolean multipage = FALSE;
  //g_print("Output to %s num_pages starts at %d\n", filename, num_pages);
  if (Denemo.printstatus->invalid)
    g_warning ("We got print status invalid %d\nTypeset may not be good.", Denemo.printstatus->invalid);
  if (!(g_file_test (filename, G_FILE_TEST_EXISTS)))
      {
          g_free (filename);
          if (num_pages>0) //recursion failed, give up
            {
				gchar *msg = g_strdup_printf ("Failure! LilyPond - stopped after %d\n pages.", num_pages);
				warningdialog (msg);
				g_free (msg);
                num_pages = 0;
                g_warning ("Unable to get the right page length\n");
                return FALSE;
            }
          filename = g_strconcat (Denemo.printstatus->printbasename[Denemo.printstatus->cycle], "-1.svg", NULL);
          if (g_file_test (filename, G_FILE_TEST_EXISTS))
			LilyPondSVGPageSuffix = "-";
		  else
			LilyPondSVGPageSuffix = "-page-";
		  g_free (filename);
		  filename = g_strconcat (Denemo.printstatus->printbasename[Denemo.printstatus->cycle], LilyPondSVGPageSuffix, "1.svg", NULL);
			
          if (g_file_test (filename, G_FILE_TEST_EXISTS))
                {
                    g_free (filename);
                    num_pages = get_number_of_pages (Denemo.printstatus->printbasename[Denemo.printstatus->cycle]);
                    if (num_pages<2)
                        {
                        g_warning ("Unable to determine number of pages\n");
                        return FALSE;
                        }
					gchar *command = get_script (PartOnly);
					call_out_to_guile (command);//this installs the temporary directives to typeset svg and thenthen calls LilyPond
					g_free (command);
                    return FALSE;
                }
         g_free (filename);
         return FALSE; // no svg at all
      }
    if (num_pages == 0)
        num_pages = 1; //no recursion, so one page

    //if (Denemo.printstatus->invalid == 0) ignore errors as it may have typeset anyway.
  Denemo.printstatus->invalid = (g_file_test (filename, G_FILE_TEST_EXISTS)) ? 0 : 3;

 if (Denemo.printstatus->invalid == 0)
    {

    compute_timings (g_path_get_dirname(filename), create_positions (filename));

#ifdef G_OS_WIN32
    GError *err = NULL;
    err = NULL;
    //if (Denemo.prefs.dynamic_compression == 88)
     //      filename = string_dialog_entry (Denemo.project, "Back Door SVG Load", "Give SVG full path:", locateprintdir());
    GdkPixbuf *pb = rsvg_pixbuf_from_file (filename, &err);
    //rsvg_pixbuf_from_file_at_size (filename, 709, 3543 * num_pages, &err);

    if(pb)
        {
            //g_print ("Width %d\nHeight %d\n", gdk_pixbuf_get_width (pb), gdk_pixbuf_get_height (pb)); 709, 7087 for two page, 709 by 3543 for single
            if(Denemo.playbackview)
                gtk_image_set_from_pixbuf (GTK_IMAGE (Denemo.playbackview), pb);
            else
                Denemo.playbackview = gtk_image_new_from_pixbuf (pb);
            //g_print ("Loaded %s via rsvg pixbuf loader", filename);
        } else
        g_warning ("\n\nThe rsvg pixbuf load of %s gave error: %s\n\n", filename, err?err->message: "no error return");

#else
      if(Denemo.playbackview)
        gtk_image_set_from_file (GTK_IMAGE (Denemo.playbackview), filename);
      else
        Denemo.playbackview = gtk_image_new_from_file (filename);
#endif

      static gboolean shown_once = FALSE;   //Make sure the user knows that the printarea is on screen
     // if (!shown_once)
        {
          shown_once = TRUE;
          show_playback_view ();
        }
    }
    else
    {
       g_critical ("Filename %s was not created - Playback View will not work"); 
    }
    g_free (filename);
  num_pages = 0;
  return TRUE;
}
static void clear_scroll_points (void)
{
     if (ClearScrollPointsButton)
        gtk_widget_set_sensitive (ClearScrollPointsButton, FALSE);
     g_list_free_full (Denemo.project->movement->scroll_points, g_free);
     Denemo.project->movement->scroll_points = NULL;
     gtk_widget_queue_draw (Denemo.playbackview);
}

static void help_scroll_points (void)
{
    infodialog (_("This is the Playback View Window. Click on a note to play from that note to the end. \
Click again to stop play. Drag between two notes to play from the first to the last, shift drag to create a loop. \
Shift-Click on a note to position the Denemo cursor on that note in the Denemo Display. \
For simple scrolling check the box. For more sophisticated control use the right mouse button:\nright click on a note when you have scrolled the page to the position you want it to be at when it is playing. \
In detail: First right click at the start of the second system (this means that the music will not scroll before that); then scroll to position the end and right click the first note of the last system of the piece. \
To delete a scroll point right-click on it.\nIf there are changes of pace then set extra scroll points to control the scrolling in more detail. \
\nFor PlayAlong playback set the checkbox and use left clicks:\nleft click on the note you want to start playing at - it will sound immediately, but you must still play it on your MIDI keyboard for the playback to advance. \
If you want an intro use Ctrl-left click - it will play from the bar before until it plays the note you clicked, \
you have to play that note and then the following ones to continue the playback. \
You can shape the part you are playing by playing staccato or using rubato to slow up the playback."));
}
static void
playbackview_finished (G_GNUC_UNUSED GPid pid, G_GNUC_UNUSED gint status, gboolean print)
{
  progressbar_stop ();
  console_output (_("Done (Playback View)"));
  g_spawn_close_pid (Denemo.printstatus->printpid);
  //g_print ("background %d\n", Denemo.printstatus->background);
  if (Denemo.printstatus->background == STATE_NONE)
    {
      call_out_to_guile ("(FinalizeTypesetting)");
      process_lilypond_errors ((gchar *) get_printfile_pathbasename ());
    }
  else
    {
      if (LilyPond_stderr != -1)
        close (LilyPond_stderr);
      LilyPond_stderr = -1;
    }
  Denemo.printstatus->printpid = GPID_NONE;
  if(set_playback_view ())
    {
      gdouble total_time;
      changecount = Denemo.project->movement->changecount;
      total_time = load_lilypond_midi (NULL, AllPartsTypeset);//g_print ("MIDI file total time = %.2f\n", total_time);
 
      Denemo.project->movement->smfsync = Denemo.project->movement->changecount;
      AllPartsTypeset = FALSE;
  }
}



static gboolean
initialize_typesetting (void)
{
  return call_out_to_guile ("(InitializeTypesetting)");
}

//A button could be placed in the playback view to create an svg file from the view...
static void
copy_svg (void)
{
  gchar *filename;
  gchar *outuri = get_output_uri_from_scoreblock ();
  gchar *outpath;
  gchar *outname;
  outuri += strlen ("file://"); //skip the uri bit of it
  outpath = g_path_get_dirname (outuri);
  outname = g_path_get_basename (outuri);
  GtkWidget *chooser = gtk_file_chooser_dialog_new (_("SVG creation"),
                                                    GTK_WINDOW (Denemo.window),
                                                    GTK_FILE_CHOOSER_ACTION_SAVE,
                                                    _("_Cancel"),
                                                    GTK_RESPONSE_REJECT,
                                                    _("_Save"),
                                                    GTK_RESPONSE_ACCEPT, NULL);
  GtkFileFilter *filter = gtk_file_filter_new();
  gtk_file_filter_set_name (filter, _("SVG files"));
  gtk_file_filter_add_pattern (filter, "*.svg");
  gtk_file_filter_add_pattern (filter, "*.SVG");
  gtk_file_chooser_add_filter (GTK_FILE_CHOOSER(chooser), filter);
  gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), outpath);
  gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (chooser), outname);
  gtk_widget_show_all (chooser);
  if (gtk_dialog_run (GTK_DIALOG (chooser)) == GTK_RESPONSE_ACCEPT)
    filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
  else
    filename = NULL;
  gtk_widget_destroy (chooser);

  if (filename)
    {
      gchar *contents;
      gsize length;


      if (g_file_get_contents (Denemo.printstatus->printname_svg[Denemo.printstatus->cycle], &contents, &length, NULL))
        {

            if ((!g_file_test (filename, G_FILE_TEST_EXISTS)) || confirm (_( "SVG creation"), _( "File Exists, overwrite?")))
                {
                  if (!g_file_set_contents (filename, contents, length, NULL))
                    {
                      gchar *msg = g_strdup_printf (_("Errno %d:\nCould not copy %s to %s. Perhaps because some other process is using the destination file. Try again with a new location\n"),
                                                    errno,
                                                    Denemo.printstatus->printname_svg[Denemo.printstatus->cycle],
                                                    filename);
                      warningdialog (msg);
                      g_free (msg);
                    }
                  else
                    {
                      gchar *uri = g_strconcat ("file://", filename, NULL);
                      if (strcmp(uri, get_output_uri_from_scoreblock ()))
                        score_status (Denemo.project, TRUE);
                      set_current_scoreblock_uri (uri);

                      //g_print ("I have copied %s to %s (default was %s) uri %s\n", Denemo.printstatus->printname_svg[Denemo.printstatus->cycle], filename, outname, uri);
                    }
                  g_free (contents);
                }
        }
      g_free (outpath);
      g_free (outname);
      g_free (filename);
    }
}

void delete_svgs (void) {
    gint cycle = Denemo.printstatus->cycle;
    cycle = !cycle;
    if (!Denemo.printstatus->printname_svg[cycle])
        return;//not yet initialized
    g_unlink ( Denemo.printstatus->printname_svg[cycle]);
    gint i;
    for (i=1;i<10;i++)
        {
            gchar *filename = g_strdup_printf ("%s%s%d%s", Denemo.printstatus->printbasename[cycle], LilyPondSVGPageSuffix, i, ".svg");
            if (!g_file_test (filename, G_FILE_TEST_EXISTS))
                {
                 //g_print ("No file %s\n", filename);
                 g_free (filename);
                 break;
             }
            //g_print ("deleting %s\n", filename);
            g_unlink (filename);
#ifdef G_OS_WIN32
    if (g_file_test (filename, G_FILE_TEST_EXISTS)) g_warning ("File %s deletion failed\n\n", filename);
#endif

            g_free (filename);
        }
}

//re-creates the svg image and displays it
static void remake_playback_view (gboolean part)
{
    if (Denemo.project->movement->markstaffnum)
        Denemo.project->movement->markstaffnum = 0;//It can (and would otherwise) typeset just the selection - would that be useful?
    delete_svgs ();
    set_continuous_typesetting (FALSE);
    create_svg (part, FALSE);//there is a typeset() function defined which does initialize_typesetting() ...
    //g_print ("Denemo.playbackview is at %p, Denemo at %p", Denemo.playbackview, &Denemo);
    g_child_watch_add (Denemo.printstatus->printpid, (GChildWatchFunc) playbackview_finished, (gpointer) (FALSE));
}

//returns TRUE if a re-build has been kicked off.
static gboolean update_playback_view (void)
{
    //g_print ("Testing %d not equal %d or %d not equal %d \n", changecount, Denemo.project->changecount, Denemo.project->movement->changecount, Denemo.project->movement->smfsync);
 if ((changecount != Denemo.project->movement->changecount) || (Denemo.project->movement->changecount != Denemo.project->movement->smfsync))
        {
        call_out_to_guile (PartOnly?"(d-PlaybackView 'part)":"(d-PlaybackView)");//this installs the temporary directives to typeset svg and thendisplay_svg (gdouble scale, gboolean part)
        Denemo.project->movement->smfsync = Denemo.project->movement->changecount;
        changecount = Denemo.project->movement->changecount;
        return TRUE;
        }
return FALSE;
}
//Typeset and svg and display in playbackview window. Scale is the font size relative to 18.0 pt.
void
display_svg (gdouble scale, gboolean part)
{
    TheScale = 5.61 * scale; //5.61 found by trial and error FIXME!
    (void)remake_playback_view (part);
      //bring print view back to show cursor
    if (Denemo.textview)
        gtk_text_view_scroll_to_mark (GTK_TEXT_VIEW (Denemo.textview),
                                      gtk_text_buffer_get_insert (Denemo.textbuffer),
                                      0.0,
                               TRUE, 0.5, 0.5);
    if(Denemo.project->movement)
        gtk_widget_set_sensitive (ClearScrollPointsButton, (Denemo.project->movement->scroll_points!=NULL));
}



static void button_press (GtkWidget *event_box, GdkEventButton *event)
{
    Locationx = Locationy = -1;
    if (audio_is_playing ())
        return;


    if ((get_wysiwyg_info()->stage != TypesetForPlaybackView) ||
		(changecount != Denemo.project->movement->changecount) ||
		(Denemo.project->movement->changecount != Denemo.project->movement->smfsync) || (Denemo.project->changecount != Denemo.project->lilysync))
       {
		    typeset_current_movement ();
            warningdialog (_("Wait for the Print View to update, then choose \"All Parts\" or \"Current Part\" from the Playback View and wait for that to re-typeset, then you can play,"));
            needs_retypeset = TRUE;
            gtk_widget_queue_draw (Denemo.playbackview);
            return;
       }
	
    gint x = event->x;
    gint y = event->y;
    //g_print ("At %d %d\n", x, y);
    GList *g;
    if (event->button == 3)
        {
            RightButtonPressed = TRUE;
            RightButtonX = x;
            RightButtonY = y;

        }
    else
        {
            LeftButtonPressed = TRUE;
            LeftButtonX = x;
            LeftButtonY = y;
        }

    for (g = TheTimings; g;g=g->next)
        {
            Timing *timing = g->data;
            if((x-timing->x*TheScale < PRINTMARKER/(2)) && (y-timing->y*TheScale < PRINTMARKER/(2)))
                {

                    gboolean found = goto_lilypond_position (timing->line, timing->col);
                    ScrollTime = timing->time;
                    if (found)
                        {
                            //g_print ("Found line %d column %d\n", timing->line, timing->col);
                            Locationx = timing->col;
                            Locationy = timing->line;
                            Dragging = TRUE;
                            DragX = x;
                            DragY = y;
                            LastY = event->y_root;
                        }
                    if (event->button != 3)
                        call_out_to_guile ("(DenemoSetPlaybackStart)");
                     else
                        {
                            if (found)
                                call_out_to_guile ("(d-PlayMidiNote 72 255 9 100)");
                        }
                    //if (!found)
                        //g_print ("Line %d column %d NOT FOUND for (x, y) = (%d, %d) \n", timing->line, timing->col, x, y);
                    //else
                        //g_print ("\nFound!!! Line %d column %d for (x, y) = (%d, %d) \n", timing->line, timing->col, x, y);
                    return;

                }
            //g_print ("compare %d %d with %.2f, %.2f\n", x, y, timing->x*TheScale, timing->y*TheScale);
        }

    call_out_to_guile ("(d-PlayMidiNote 36 255 9 100)");
}
static void scroll_by (gdouble amount)
{

    gdouble value =  gtk_adjustment_get_value  (VAdj);
   //g_print ("set to %.2f from %.2f\n", value+amount, value);
    gtk_adjustment_set_value (VAdj, value+amount + 0.5);//0.5 helps prevent slow movements going ahead of the scroll ...
}
static void scroll_to (gdouble amount)
{
    gtk_adjustment_set_value (VAdj, amount);
}


static DenemoScrollPoint *encode (gdouble adjust, gdouble time, gdouble x, gdouble y)
{
    DenemoScrollPoint *sp = g_malloc (sizeof (DenemoScrollPoint));
    sp->adj = adjust;
    sp->time = time;
    sp->x = x;
    sp->y = y;
    return sp;
}
static void decode (DenemoScrollPoint * val, gdouble *adjust, gdouble *time)
{
     *adjust = val->adj;
    *time = val->time;
}
static gboolean playback_redraw (void)
{
    static gdouble last_time;
    if (audio_is_playing ())
            {
                if ((Denemo.project->movement->scroll_points==NULL)&&(ScrollRate<0.001))
                    {
                        gtk_widget_queue_draw (Denemo.playbackview);
                        return TRUE;
                    }

                gdouble time = Denemo.project->movement->playhead;
                static gdouble waiting_time;
                if (last_time < 0.0)
                    waiting_time = time + IntroTime;
                    if (Denemo.project->movement->scroll_points)
                        {
                           GList *g, *start=NULL, *end=NULL;
                           for (g=Denemo.project->movement->scroll_points;g;g=g->next)
                                {
                                    gdouble adj, tm;
                                    decode ((g->data), &adj, &tm);
                                    //g_print ("%.2f %.2f\n", adj, tm);
                                    if(g->next)
                                        {
                                            if (time < tm)
                                                {
                                                    if ((g->prev==NULL))
                                                        scroll_to (adj*(time)/(tm));//,g_print ("case 0");
                                                    else
                                                        scroll_to (adj * time/tm);//,g_print ("case 1");
                                                   break;
                                                } else
                                                {
                                                    gdouble nextadj, nexttm;
                                                    decode ((g->next->data), &nextadj, &nexttm);
                                                    if (time > nexttm)
                                                        continue;
                                                    scroll_to (nextadj + (adj - nextadj)*((nexttm-time)/(nexttm - tm)));//,g_print ("case 2");
                                                    break;
                                                }
                                        }
                                    else
                                        {
                                            if (time >= tm)
                                                {
                                                   break;
                                                }
                                            if (g->prev)
                                                { gdouble prevadj, prevtm;
                                                   decode ((g->prev->data), &prevadj, &prevtm);
                                                   scroll_to (prevadj + (adj - prevadj)*((time-prevtm)/(tm - prevtm)));//,g_print ("case 3");
                                                   break;
                                                }
                                            if (tm>0)
                                                {
                                                scroll_to (adj*(time - waiting_time)/(tm - waiting_time));//,g_print ("case 4");
                                                }
                                            break;
                                        }
                            }
                        }
                        else //no Denemo.project->movement->scroll_points
                            if (last_time > waiting_time)
                                    {
                                        scroll_by (((time -last_time)*ScrollRate));
                                    }
                last_time = time;
                gtk_widget_queue_draw (Denemo.playbackview);
            }
    else // audio not playing
        last_time = -1.0;
    return TRUE;
}

static void list_scroll_points (void) //debug only
{
   GList *g;
   for (g=Denemo.project->movement->scroll_points;g;g=g->next)
        {gdouble adj, tm;
         decode (g->data, &adj, &tm);
         //g_print ("Scroll Point: %0.2f at time %0.2f\n", adj, tm);
     }

}
static void toggle_scroll_point (gdouble adj, gdouble time, gdouble x, gdouble y)
{
    GList *g = Denemo.project->movement->scroll_points;
    DenemoScrollPoint *sp = encode (adj, time, x, y);
    for (;g;g=g->next)
        {
            DenemoScrollPoint *this = (DenemoScrollPoint*)g->data;
            if (fabs(this->time - sp->time) < 0.1)
                {
                    g_free (sp);
                    Denemo.project->movement->scroll_points = g_list_delete_link (Denemo.project->movement->scroll_points, g);

                        return;
                }

            if (this->time > sp->time)
                {
                    Denemo.project->movement->scroll_points = g_list_insert_before (Denemo.project->movement->scroll_points, g, sp);
                    return;
                }
        }
     Denemo.project->movement->scroll_points = g_list_append (Denemo.project->movement->scroll_points, sp);
}
static void button_release (GtkWidget *event_box, GdkEventButton *event)
{
    gint x = event->x;
    gint y = event->y;
    RightButtonPressed = FALSE;
    LeftButtonPressed = FALSE;

    if (Dragging &&   (event->button == 3))
        {
            //g_print ("Store %.2f %.2f\n", gtk_adjustment_get_value (VAdj), ScrollTime);
            call_out_to_guile ("(d-PlayMidiNote 52 255 9 100)");
            toggle_scroll_point (gtk_adjustment_get_value (VAdj), ScrollTime, x/(TheScale), y/(TheScale));
            gtk_widget_set_sensitive (ClearScrollPointsButton, (Denemo.project->movement->scroll_points != NULL));
            //list_scroll_points();
        }
    gtk_widget_queue_draw (Denemo.playbackview);

    Dragging = FALSE;
     if (audio_is_playing ())
        {
            call_out_to_guile ("(DenemoStop)");
            return;
        }

    //g_print ("At %d %d\n", x, y);
    if (event->button == 3)
        return;

     if ((changecount != Denemo.project->movement->changecount) || (Denemo.project->movement->changecount != Denemo.project->movement->smfsync))
        {
		    if (Denemo.printstatus->printpid != GPID_NONE)
				kill_process (Denemo.printstatus->printpid);
			Denemo.printstatus->printpid = GPID_NONE;
		    typeset_current_movement ();
            warningdialog (_("Wait for the Print View to finish typesetting and then choose \"All Parts\" or \"Current Part\" from the Playback View"));
			return;
			
            static gboolean once = TRUE;
            exportmidi (NULL, Denemo.project->movement);
            //g_print ("Now d-changecount %d, d-smfsync %d\n", Denemo.project->movement->changecount, Denemo.project->movement->smfsync);
            if(once)
                infodialog (_("Switching to simple MIDI - re-typeset for full MIDI."));
           once = FALSE;
        }
    GList *g;
    for (g = TheTimings; g;g=g->next)
        {
            Timing *timing = g->data;
            if((x-timing->x*TheScale < PRINTMARKER/(2)) && (y-timing->y*TheScale < PRINTMARKER/(2)))
                {

                    if ((timing->col == Locationx) && (timing->line == Locationy))
                        {//g_print ("Playing from cursor to end or previous bar to cursor for playalong\n");
                            if (!shift_held_down())
								{
										if (control_held_down())
											call_out_to_guile ("(DenemoPlayIntroToEnd)");
										else
											call_out_to_guile ("(d-DenemoPlayCursorToEnd)");
								}
                            else
                                call_out_to_guile ("(d-PlayMidiNote 67 255 9 100)");
                            //g_print ("Found same line %d column %d\n", timing->line, timing->col);
                        }
                    else
                        {
                             if ( (timing->line > Locationy) || ((timing->line==Locationy)&&(timing->col>=Locationx)))
                                {
                                    gboolean found = goto_lilypond_position (timing->line, timing->col);//g_print ("y %d Lefty %d\n", y, LeftButtonY);

                                    call_out_to_guile ("(if (not (d-NextChord)) (d-MoveCursorRight)(d-MoveCursorLeft))(DenemoSetPlaybackEnd)");
                                        //g_print ("Set playback end to %d column %d\n", timing->line, timing->col);
                                    Denemo.project->movement->smfsync = Denemo.project->movement->changecount;
                                    if (shift_held_down())
                                        call_out_to_guile ("(d-OneShotTimer 500 \"(DenemoLoop)\")");
                                    else
                                        call_out_to_guile ("(d-OneShotTimer 500 \"(d-Play)\")");
                                }
                                else
                                    call_out_to_guile ("(d-PlayMidiNote 67 255 9 100)");
                        }

                    break;

                }
           // g_print ("compare %d %d with %.2f, %.2f\n", x, y, timing->x*5.61*TheScale, timing->y*TheScale);
        }
}

static gint
hide_playback_on_delete (void)
{
  set_toggle (TogglePlaybackView_STRING, FALSE);
  return TRUE;
}


static void play_button (void)
{
   if (update_playback_view ())
        {
             if (continuous_typesetting ())
                ;//warningdialog (_("Please turn continuous typsetting off first"));
            else
                warningdialog (_("Please wait while the Playback View is re-typeset then re-try"));
            return;
        }
    Denemo.project->movement->smfsync = Denemo.project->movement->changecount;
    call_out_to_guile ("(d-Performance)");
}



static void part_button (void)
{
	if (Denemo.project->changecount != Denemo.project->lilysync) 
		{
			typeset_current_movement();
			warningdialog ("Wait for Print View to refresh then click again");
			return;
		}
    PartOnly = TRUE;
    if (Denemo.project->movement->smf)
        AllPartsTypeset = confirm ( _("MIDI Already Present"), _("Keep this music while typesetting current part?"));
	gchar *command = get_script (TRUE);
    call_out_to_guile (command);//this installs the temporary directives to typeset svg and thenthen calls LilyPond
	g_free (command);
}
static void movement_button (void)
{
	if (Denemo.project->changecount != Denemo.project->lilysync) 
		{
			typeset_current_movement();
			warningdialog ("Wait for Print View to refresh then click again");
			return;
		}
	
    PartOnly = FALSE;
    gchar *command = get_script (PartOnly);
    call_out_to_guile (command);//this installs the temporary directives to typeset svg and then calls LilyPond
    g_free (command);
}


static gboolean
motion_notify (GtkWidget * window, GdkEventMotion * event)
{//g_print ("Passed %.2f, %.2f\n", event->x, event->y);
  if (Dragging && RightButtonPressed)
    {
        event->x = DragX;
        scroll_by ((gdouble)(LastY - event->y_root));//g_print ("\tLast %d %d\t", LastY,  (gint)event->y_root);
        LastY = event->y_root;
    }
  if (RightButtonPressed || LeftButtonPressed)
    {
        DragX = event->x;
        DragY = event->y;
        gtk_widget_queue_draw (Denemo.playbackview);
    }
  return TRUE;

}



static void scroll_toggle (void)
{

  if (ScrollRate > 0)
    ScrollRate = 0.0;
  else ScrollRate = 10.0;
}


void
install_svgview (GtkWidget * top_vbox)
{
  if (Denemo.playbackview)
        return;


  GtkWidget *main_vbox = gtk_vbox_new (FALSE, 1);
  GtkWidget *main_hbox = gtk_hbox_new (FALSE, 1);
  gtk_box_pack_start (GTK_BOX (main_vbox), main_hbox, FALSE, TRUE, 0);
  GtkWidget *hbox;
  hbox = gtk_hbox_new (FALSE, 1);
  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, FALSE, FALSE, 0);

  GtkWidget *button = (GtkWidget*)gtk_button_new_with_label (_("Play/Stop"));
  gtk_widget_set_tooltip_markup (button, _( "Plays the entire movement with repeats, or stops the playing once started"));
  g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (play_button), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  button = (GtkWidget*)gtk_button_new_with_label (_("All Parts"));
  gtk_widget_set_tooltip_markup (button, _( "Typesets the currrent movement, generating sophisticated MIDI for it."));

  g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (movement_button), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  button = (GtkWidget*)gtk_button_new_with_label (_("Current Part"));
  gtk_widget_set_tooltip_markup (button, _( "Typesets the currrent part with the option to keep the MIDI already generated for all the parts."));

  g_signal_connect_swapped (G_OBJECT (button), "clicked", G_CALLBACK (part_button), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);



    button = (GtkWidget*)gtk_check_button_new_with_label (_("Simple Scrolling"));
    gtk_widget_set_tooltip_markup (button, _( "Sets/Unsets automatic scrolling. The scrolling can still be manually adjusted if it is too fast/slow."));

    gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
    gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
    g_signal_connect_swapped (G_OBJECT (button), "toggled", G_CALLBACK (scroll_toggle), NULL);

    playalong_toggle = (GtkWidget*)gtk_check_button_new_with_label (_("Play Along"));
    gtk_widget_set_tooltip_markup (playalong_toggle, _( "Sets/Unsets Play Along playback."));
	if (Denemo.project && Denemo.project->midi_destination & MIDIPLAYALONG)
		gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (playalong_toggle), TRUE);
	gtk_box_pack_start (GTK_BOX (hbox), playalong_toggle, FALSE, FALSE, 0);
    g_signal_connect_swapped (G_OBJECT (playalong_toggle), "toggled", G_CALLBACK (pb_playalong), get_playalong_button());



  ClearScrollPointsButton = gtk_button_new_with_label (_("Clear Scroll Points"));
  gtk_widget_set_tooltip_markup (ClearScrollPointsButton, _( "Clears scroll points which you have set by right clicking on notes."));

  g_signal_connect (G_OBJECT (ClearScrollPointsButton), "clicked", G_CALLBACK (clear_scroll_points), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), ClearScrollPointsButton, FALSE, FALSE, 0);

  button = gtk_button_new_with_label (_("Help"));
  g_signal_connect (G_OBJECT (button), "clicked", G_CALLBACK (help_scroll_points), NULL);
  gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
  if (top_vbox == NULL)
    {
    top_vbox = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    // if(!Denemo.prefs.manualtypeset)
    //      gtk_window_set_urgency_hint (GTK_WINDOW(Denemo.window), TRUE);//gtk_window_set_transient_for (GTK_WINDOW(top_vbox), GTK_WINDOW(Denemo.window));
    gtk_window_set_title (GTK_WINDOW (top_vbox), _("Denemo Playback View"));
    gtk_window_set_default_size (GTK_WINDOW (top_vbox), 710, 850);
    //g_signal_connect (G_OBJECT (top_vbox), "delete-event", G_CALLBACK (gtk_widget_hide_on_delete), NULL);
    g_signal_connect (G_OBJECT (top_vbox), "delete-event", G_CALLBACK (hide_playback_on_delete), NULL);
    }


  gtk_container_add (GTK_CONTAINER (top_vbox), main_vbox);

  GtkWidget *score_and_scroll_win = gtk_scrolled_window_new (NULL, NULL);
 // Denemo.playbackview = (GtkWidget *) gtk_image_new ();
  
  gchar *filename = g_build_filename (get_system_data_dir (), COMMANDS_DIR, "graphics", "PlaybackView.svg", NULL);
  
#ifdef G_OS_WIN32
    GError *err = NULL;
    err = NULL;
    GdkPixbuf *pb = rsvg_pixbuf_from_file (filename, &err);
    if(pb)
         Denemo.playbackview = gtk_image_new_from_pixbuf (pb);
     else
        g_warning ("\n\nThe rsvg pixbuf load of %s gave error: %s\n\n", filename, err?err->message: "no error return");
#else
        Denemo.playbackview = gtk_image_new_from_file (filename);
#endif 
    g_free (filename);
  
    VAdj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW(score_and_scroll_win));
    // gtk_container_add (GTK_CONTAINER (score_and_scroll_win), Denemo.playbackview);
    //instead use an hbox to prevent the GtkImage widget expanding beyond the image size, which then causes positioning errors.
    hbox = gtk_hbox_new (FALSE, 1);
    GtkWidget *event_box = gtk_event_box_new ();
    gtk_box_pack_start (GTK_BOX (main_vbox), score_and_scroll_win, TRUE, TRUE, 0);
#if ((GTK_MAJOR_VERSION>=3)  && (GTK_MINOR_VERSION>8))
    gtk_container_add (GTK_CONTAINER (score_and_scroll_win), hbox);
#else
    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW(score_and_scroll_win), hbox);
#endif
    gtk_box_pack_start (GTK_BOX (hbox), event_box, FALSE, FALSE, 0);
    gtk_container_add (GTK_CONTAINER (event_box), Denemo.playbackview);

    g_signal_connect (G_OBJECT (event_box), "button_press_event", G_CALLBACK (button_press), NULL);
    g_signal_connect (G_OBJECT (event_box), "button_release_event", G_CALLBACK (button_release), NULL);
    g_signal_connect (G_OBJECT (event_box), "motion-notify-event", G_CALLBACK (motion_notify), NULL);

  if (Denemo.prefs.newbie)
    gtk_widget_set_tooltip_markup (score_and_scroll_win,
                                   _("This window shows the typeset score as one long page. During playback the notes playing are highlighted"));
#if GTK_MAJOR_VERSION != 2
  g_signal_connect_after (G_OBJECT (Denemo.playbackview), "draw", G_CALLBACK (playbackview_draw_event), NULL);
  g_signal_connect (G_OBJECT (Denemo.playbackview), "draw", G_CALLBACK (playbackview_predraw_event), NULL);
#else
  g_signal_connect_after (G_OBJECT (Denemo.playbackview), "expose_event", G_CALLBACK (playbackview_draw_event), NULL);
  g_signal_connect (G_OBJECT (Denemo.playbackview), "expose_event", G_CALLBACK (playbackview_predraw_event), NULL);
#endif
  gtk_widget_show_all (main_vbox);
  gtk_widget_hide (top_vbox);
  static gint id;
  if (!id)
    id = g_timeout_add  (50, (GSourceFunc)playback_redraw, NULL);
}
