// -*- C++ -*-
//
//  This file is part of Siena, a wide-area event notification system.
//  See http://www.cs.colorado.edu/serl/siena/
//
//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
//  See the file AUTHORS for full details. 
//
//  Copyright (C) 1998-2001  University of Colorado
//
//  This program is free software; you can redistribute it and/or
//  modify it under the terms of the GNU General Public License
//  as published by the Free Software Foundation; either version 2
//  of the License, or (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
//  USA, or send email to serl@cs.colorado.edu.
//
//
// $Id: graph.cc,v 1.9 2001/09/02 23:41:52 carzanig Exp carzanig $
//
#include <map>
#include <set>
#include <cstdlib>
#include <sstream>

#include <sys/time.h> // setting random seed 

#include <gtk/gtk.h>

#include <Image.h>
#include <graph.h>

#include <SimpleGraph.h>
#include <Icons.h>
#include <geometry.h>

#ifdef __cplusplus
extern "C" {
#endif
#include "interface.h"
#include "support.h"
#ifdef __cplusplus
};
#endif

GdkPixmap *		pixmap = (GdkPixmap *)0;
GtkDrawingArea *	canvas = (GtkDrawingArea *)0;
GtkWidget *		canvas_w = (GtkWidget *)0;

GtkMenu *		graph_popup;
GtkCheckMenuItem *	popup_option_implicit;
GtkCheckMenuItem *	popup_option_labels;
GtkCheckMenuItem *	popup_option_notifications;
GtkCheckMenuItem *	popup_option_subscriptions;
GtkCheckMenuItem *	popup_option_unsubscriptions;

GtkWidget *		popup_delete_node;

//
// ...to be continued with advertisements ...work in progress...
//

GdkColor		arc_color;
GdkColor		node_color;

GdkFont *		node_font;

GdkGC *			node_gc;
GdkGC *			xor_node_gc;
GdkGC *			arc_gc;
    
GdkCursor *		PointCursor;
GdkCursor *		MoveCursor;

gint16			PWidth = 0;
gint16			PHeight = 0;

//
// upper left corner of the visible area (DrawingArea) 
// within the backing pixmap
//
Point			ViewTopLeft(0,0);

//
// I need this state to do my scrolling and to select a node to delete
//
Point			motion_pos;
SGraph::n_iterator	selected_node_i;

//
// graph data
//
SGraph			G;

//
// function the graph calls when it's done displaying a specific command
//
void			(*DoneDisplayingCallback)();
//
// error logger function
//
void			(*Logger)(const char *, int len);

//
// timeout for displaying commands
//
guint			AnimDuration		= 500;
guint			AnimSteps		= 10;

//
// events
//
static const char *	button_press_event	= "button_press_event";
static const char *	expose_event		= "expose_event";
static const char *	configure_event		= "configure_event";
static const char *	destroy_event		= "destroy_event";
static const char *	realize			= "realize";
static const char *	button_release_event	= "button_release_event";
static const char *	motion_notify_event	= "motion_notify_event";

gboolean graph_on_button_press(GtkWidget *, GdkEventButton *);
gboolean graph_on_button_release(GtkWidget *, GdkEventButton *);
gboolean graph_on_motion_notify(GtkWidget *, GdkEventMotion *);
gboolean graph_on_expose(GtkWidget *, GdkEventExpose *, gpointer);
gboolean graph_on_configure(GtkWidget *, GdkEventConfigure *, gpointer);
gboolean graph_on_destroy(GtkWidget *, GdkEvent *, gpointer);
//
//
//
gboolean graph_init_after_realized(GtkWidget * c, 
				   GdkEvent *event, 
				   gpointer data) {
    GdkColormap * cmap = gtk_widget_get_colormap(canvas_w);
    GdkColor foreground;

    //
    // graphic contexts arc_gc, node_gc, and xor_node_gc
    //
    
    arc_gc = gdk_gc_new(canvas_w->window);
    gdk_gc_copy(arc_gc, canvas_w->style->black_gc);

    node_gc = gdk_gc_new(canvas_w->window);
    gdk_gc_copy(node_gc, canvas_w->style->black_gc);

    xor_node_gc = gdk_gc_new(canvas_w->window);
    gdk_gc_copy(xor_node_gc, canvas_w->style->black_gc);

    foreground.red = 0x8000;
    foreground.green = 0x8000;
    foreground.blue = 0xdfff;
    if (!gdk_color_alloc(cmap, &foreground)) {
	g_error("couldn't allocate arc color");
    }

    gdk_gc_set_line_attributes(arc_gc, 2, GDK_LINE_SOLID, 
			       GDK_CAP_ROUND, GDK_JOIN_ROUND);
    gdk_gc_set_foreground(arc_gc, &foreground);

    foreground.red = 0x9000;
    foreground.green = 0xffff;
    foreground.blue = 0x9000;
    if (!gdk_color_alloc(cmap, &foreground)) {
	g_error("couldn't allocate node color");
    }
    gdk_gc_set_line_attributes(node_gc, 2, GDK_LINE_SOLID, 
			       GDK_CAP_ROUND, GDK_JOIN_ROUND);

    gdk_gc_set_line_attributes(xor_node_gc, 2, GDK_LINE_SOLID, 
			       GDK_CAP_ROUND, GDK_JOIN_ROUND);
    gdk_gc_set_function(xor_node_gc, GDK_EQUIV);
    gdk_gc_set_foreground(xor_node_gc, &foreground);

    node_font = gdk_font_load("-adobe-helvetica-medium-r-normal-*-10-*-*-*-p-*-iso8859-1");

    PointCursor = gdk_cursor_new(GDK_LEFT_PTR);
    MoveCursor = gdk_cursor_new(GDK_FLEUR);

    gdk_window_set_cursor(canvas_w->window, PointCursor);
    return TRUE;
}

static int graph_init_random_seed() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    srand((unsigned int)tv.tv_sec);
    return 0;
}

void graph_init(GtkDrawingArea * da, void (callback)()) {
    graph_init_random_seed();

    DoneDisplayingCallback = callback;

    canvas = da;
    canvas_w = GTK_WIDGET(canvas);

    gtk_widget_set_events(canvas_w, 
			  GDK_EXPOSURE_MASK
			  | GDK_LEAVE_NOTIFY_MASK
			  | GDK_BUTTON_PRESS_MASK
			  | GDK_BUTTON_RELEASE_MASK
			  | GDK_POINTER_MOTION_MASK
			  | GDK_POINTER_MOTION_HINT_MASK);

    gtk_signal_connect(GTK_OBJECT(canvas_w), button_press_event,
		       GTK_SIGNAL_FUNC(graph_on_button_press), NULL);
    gtk_signal_connect(GTK_OBJECT(canvas_w), expose_event,
		       GTK_SIGNAL_FUNC(graph_on_expose), NULL);
    gtk_signal_connect(GTK_OBJECT(canvas_w), configure_event,
		       GTK_SIGNAL_FUNC(graph_on_configure), NULL);
    gtk_signal_connect(GTK_OBJECT(canvas_w), destroy_event,
		       GTK_SIGNAL_FUNC(graph_on_destroy), NULL);
    gtk_signal_connect(GTK_OBJECT(canvas_w), realize,
		       GTK_SIGNAL_FUNC(graph_init_after_realized), NULL);

    graph_popup = GTK_MENU(create_graph_popup_menu());
    popup_option_implicit = GTK_CHECK_MENU_ITEM(lookup_widget(GTK_WIDGET(graph_popup), "graph_p_implicit"));
    popup_option_labels = GTK_CHECK_MENU_ITEM(lookup_widget(GTK_WIDGET(graph_popup), "graph_p_labels"));
    popup_option_notifications = GTK_CHECK_MENU_ITEM(lookup_widget(GTK_WIDGET(graph_popup), "graph_p_notifications"));
    popup_option_subscriptions = GTK_CHECK_MENU_ITEM(lookup_widget(GTK_WIDGET(graph_popup), "graph_p_subscriptions"));
    popup_option_unsubscriptions = GTK_CHECK_MENU_ITEM(lookup_widget(GTK_WIDGET(graph_popup), "graph_p_unsubscriptions"));

    popup_delete_node = lookup_widget(GTK_WIDGET(graph_popup), 
				      "graph_p_delete_node");
}

void graph_destroy_pixmap() {
    if (pixmap != (GdkPixmap *)0) {
	gdk_pixmap_unref(pixmap);
	pixmap = (GdkPixmap *)0;
    }
}

void graph_create_pixmap(guint16 width, guint16 height) {
    if (pixmap != (GdkPixmap *)0 && PWidth == width && PHeight == height)
	return;

    graph_destroy_pixmap();
    PWidth = width;
    PHeight = height;

    pixmap = gdk_pixmap_new(canvas_w->window, 
			    PWidth, PHeight, -1);

    ViewTopLeft.move_to(0,0);

    gdk_draw_rectangle(pixmap, canvas_w->style->white_gc,
		       TRUE, 0, 0, PWidth, PHeight);
}

void graph_draw_edge(const Edge & e) {
    gdk_draw_line(pixmap, arc_gc, 
		  (*e.source).second.x, (*e.source).second.y, 
		  (*e.target).second.x, (*e.target).second.y);
}

//
// re-draws the given rectangle of the canvas
// the rectangle starting at x,y and extending by dx,dy
// 
static void graph_expose_rectangle(gint16 x, gint16 y,
				   guint16 dx, guint16 dy) {
    gint16 xp = ViewTopLeft.x + x;
    gint16 yp = ViewTopLeft.y + y;

    if (xp > PWidth || yp > PHeight) {
	gdk_draw_rectangle(canvas_w->window,
			   canvas_w->style->bg_gc[GTK_WIDGET_STATE(canvas_w)],
			   TRUE, x, y, dx, dy);
	return;
    }

    gint16 dxp = PWidth - xp;
    gint16 dyp = PHeight - yp;

    if (dxp < dx) {
	gdk_draw_rectangle(canvas_w->window,
			   canvas_w->style->bg_gc[GTK_WIDGET_STATE(canvas_w)],
			   TRUE, 
			   x + dxp, y,
			   dx - dxp, dy);
	dx = dxp;
    }
    if (dyp < dy) {
	gdk_draw_rectangle(canvas_w->window,
			   canvas_w->style->bg_gc[GTK_WIDGET_STATE(canvas_w)],
			   TRUE, 
			   x, y + dyp,
			   dx, dy - dyp);
	dy = dyp;
    }
    gdk_draw_pixmap(canvas_w->window, 
		    canvas_w->style->fg_gc[GTK_WIDGET_STATE(canvas_w)],
		    pixmap, xp, yp, x, y, dx, dy);
}

void graph_draw_edge(const Edge & e, GdkGC * gc = NULL) {
    gdk_draw_line(pixmap, (gc == NULL) ? arc_gc : gc, 
		  (*e.source).second.x, (*e.source).second.y, 
		  (*e.target).second.x, (*e.target).second.y);
}

void graph_draw_node(SGraph::n_const_iterator ni, GdkGC * gc = NULL) {
    const Node & n = (*ni).second;
    const string & text = (*ni).first;

    GdkGC * use_gc =  (gc == NULL) ? node_gc : gc;
    GdkRectangle r_image;

    n.image->draw(pixmap, use_gc, n, &r_image);
    if (popup_option_labels->active) {
	guint16 twidth = gdk_string_measure(node_font, text.c_str());
	guint16 theight = gdk_string_height(node_font, text.c_str());
	
	Point p(r_image.x, r_image.y);
	p.x += (r_image.width - twidth) / 2;
	p.y += r_image.height + theight + 2;
	
	gdk_draw_string(pixmap, node_font, use_gc , p.x, p.y, text.c_str());
    }
}

void graph_expose_pixamp_rectangle(GdkRectangle *r) {
    GdkRectangle r_canvas;
    r_canvas.x = ViewTopLeft.x;
    r_canvas.y = ViewTopLeft.y;
    r_canvas.width = canvas_w->allocation.width;
    r_canvas.height = canvas_w->allocation.height;

    GdkRectangle visible;
    if (gdk_rectangle_intersect(r, &r_canvas, &visible)) {
	gdk_draw_pixmap(canvas_w->window, 
			canvas_w->style->fg_gc[GTK_WIDGET_STATE(canvas_w)],
			pixmap, visible.x, visible.y, 
			visible.x - ViewTopLeft.x, visible.y - ViewTopLeft.y,
			visible.width, visible.height);
    }
}

void graph_draw_node_and_label_immediate(SGraph::n_const_iterator ni, 
					 GdkGC * gc = NULL) {
    const Node & n = (*ni).second;
    const string & text = (*ni).first;

    GdkGC * use_gc =  (gc == NULL) ? node_gc : gc;
    GdkRectangle r_image;

    n.image->draw(pixmap, use_gc, n, &r_image);

    guint16 twidth = gdk_string_measure(node_font, text.c_str());
    guint16 theight = gdk_string_height(node_font, text.c_str());

    Point p(r_image.x, r_image.y);
    p.x += (r_image.width - twidth) / 2;
    p.y += r_image.height + theight + 2;

    gdk_draw_string(pixmap, node_font, use_gc , p.x, p.y, text.c_str());
    //
    //    r_image.x .................
    //
}

void graph_draw_node_immediate(const Node & n, GdkGC * gc = NULL) {
    GdkRectangle r_image;

    n.image->draw(pixmap, (gc == NULL) ? node_gc : gc, n, &r_image);
    graph_expose_pixamp_rectangle(&r_image);
}

void graph_redraw() {
    gdk_draw_rectangle(pixmap, canvas_w->style->white_gc,
		       TRUE, 0, 0, PWidth, PHeight);

    for(SGraph::e_const_iterator i = G.edges_begin(); i != G.edges_end(); ++i)
	gdk_draw_line(pixmap, arc_gc, 
		      (*(*i).source).second.x, (*(*i).source).second.y, 
		      (*(*i).target).second.x, (*(*i).target).second.y);

    for(SGraph::n_const_iterator i = G.nodes_begin(); 
	i != G.nodes_end(); ++i) 
	graph_draw_node(i);

    graph_expose_rectangle(0, 0,
			   canvas_w->allocation.width,
			   canvas_w->allocation.height);
}

gboolean graph_on_button_press(GtkWidget *widget, GdkEventButton *event) {
    motion_pos.move_to((gint16)event->x, (gint16)event->y);
    switch (event->button) {
    case 1:
	selected_node_i = G.find_node(motion_pos + ViewTopLeft);
	if (selected_node_i == G.nodes_end()) 
	    break;
    case 2:
	gtk_signal_connect(GTK_OBJECT(canvas_w), motion_notify_event,
			   GTK_SIGNAL_FUNC(graph_on_motion_notify), NULL);
	gtk_signal_connect(GTK_OBJECT(canvas_w), button_release_event,
			   GTK_SIGNAL_FUNC(graph_on_button_release), NULL);
	gdk_window_set_cursor(canvas_w->window, MoveCursor);
	break;
    case 3:
	selected_node_i = G.find_node(motion_pos + ViewTopLeft);
	if (selected_node_i == G.nodes_end()) {
	    gtk_widget_set_sensitive(popup_delete_node, 0);
	} else {
	    gtk_widget_set_sensitive(popup_delete_node, 1);
	}
	gtk_menu_popup(graph_popup, NULL, NULL, NULL, NULL, 0, 0);
    default:
	break;
    }
    return TRUE;
}

inline void graph_get_motion_state(Point & p, GdkModifierType & state,
				   GdkEventMotion *event) {
    if (event->is_hint) {
	gint x,y;
	gdk_window_get_pointer(event->window, &x, &y, &state);
	p = Point(x,y);
    } else {
	p = Point((gint)event->x, (gint)event->y);
	state = (GdkModifierType)event->state;
    }
}

gboolean graph_on_button_release(GtkWidget *widget, GdkEventButton *event) {
    switch (event->button) {
    case 1:
	graph_redraw();
    case 2:
	gtk_signal_disconnect_by_func(GTK_OBJECT(canvas_w), 
				      GTK_SIGNAL_FUNC(graph_on_motion_notify),
				      NULL);
	gtk_signal_disconnect_by_func(GTK_OBJECT(canvas_w), 
				      GTK_SIGNAL_FUNC(graph_on_button_release),
				      NULL);
	gdk_window_set_cursor(canvas_w->window, PointCursor);
	break;
    case 3:
    default:
	break;
    }
    return TRUE;
}

gboolean graph_on_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
    Point p;
    GdkModifierType state;
 
    graph_get_motion_state(p, state, event);
   
    if (state & GDK_BUTTON1_MASK) {
	//
	// move node
	//
	graph_draw_node_immediate((*selected_node_i).second, xor_node_gc);
	(*selected_node_i).second += p - motion_pos;
	graph_draw_node_immediate((*selected_node_i).second, xor_node_gc);
    }

    if (state & GDK_BUTTON2_MASK) {
	//
	// scroll
	//
	ViewTopLeft += motion_pos - p;
	//
	// adjust X
	//
	if (PWidth < canvas_w->allocation.width || ViewTopLeft.x < 0) {
	    ViewTopLeft.x = 0;
	} else if (ViewTopLeft.x + canvas_w->allocation.width > PWidth) {
	    ViewTopLeft.x = PWidth - canvas_w->allocation.width;
	}

	if (PHeight < canvas_w->allocation.height || ViewTopLeft.y < 0) {
	    ViewTopLeft.y = 0;
	} else if (ViewTopLeft.y + canvas_w->allocation.height > PHeight) {
	    ViewTopLeft.y = PHeight - canvas_w->allocation.height;
	}
	graph_expose_rectangle(0, 0,
			       canvas_w->allocation.width,
			       canvas_w->allocation.height);
	motion_pos = p;
    }
    
    motion_pos = p;
    return TRUE;
}

gboolean graph_on_expose(GtkWidget *widget, 
			 GdkEventExpose *event, 
			 gpointer user_data) {
    graph_expose_rectangle(event->area.x, event->area.y,
			   event->area.width, event->area.height);  
    return TRUE;
}

gboolean graph_on_configure(GtkWidget *widget,
			    GdkEventConfigure *event,
			    gpointer user_data) {
    if (pixmap == (GdkPixmap *)0) 
	graph_create_pixmap(widget->allocation.width,
			    widget->allocation.height);

    graph_expose_rectangle(0, 0,
			   widget->allocation.width,
			   widget->allocation.height);
    
    return TRUE;
}

gboolean graph_on_destroy(GtkWidget *widget,
			  GdkEvent *event,
			  gpointer user_data) {
    graph_destroy_pixmap();
    return TRUE;
}

void graph_set_logger_function(void (logger)(const char *, int)) {
    Logger = logger;
}

void graph_set_anim_params(guint duration, guint steps) {
    AnimDuration = duration;
    AnimSteps = steps;
}

void graph_set_size(gint width, gint height) {
    gtk_drawing_area_size(canvas, PWidth, PHeight);
    GdkPixmap * new_pixmap = gdk_pixmap_new(canvas_w->window, 
					    PWidth, PWidth, -1);
    if (new_pixmap == (GdkPixmap *)0) return;
    gdk_draw_rectangle(new_pixmap, canvas_w->style->white_gc,
		       TRUE, 0, 0, width, height);

    if (pixmap != (GdkPixmap *)0) {
	gdk_draw_pixmap(new_pixmap, 
			canvas_w->style->fg_gc[GTK_WIDGET_STATE(canvas_w)],
			pixmap, 0, 0, 0, 0, 
			(width < PWidth) ? width : PWidth,
			(height < PHeight) ? height : PHeight);

	gdk_pixmap_unref(pixmap);
    }
    pixmap = new_pixmap;
    PWidth = width;
    PHeight = height;
    graph_expose_rectangle(0, 0,
			   canvas_w->allocation.width,
			   canvas_w->allocation.height);
}

void graph_get_size(gint & width, gint & height) {
    width = PWidth;
    height = PHeight;
}

static gint graph_closeitup(gpointer data) {
    (*DoneDisplayingCallback)();
    return 0;
}

class Animation {
public:
			Animation();
    void		assign(const Point &, const Point &, const Image *);
    void		animate();

private:
    bool		active;
    Point		curr_pos;
    Point		src;
    Point		vec;
    guint		total_steps;
    guint		steps;
    guint		dt;
    const Image *	img;

    static gint		static_animate(Animation *);
};

Animation::Animation() : active(false), steps(0), total_steps(0) {}

void Animation::assign(const Point & s, const Point & d, const Image * i) {
    active = false; 
    src = s; 
    img = i; 
    steps = 0; 
    total_steps = AnimSteps; 
    dt = AnimDuration / AnimSteps;
    vec = d - s;
}

void Animation::animate() {
    GdkRectangle r_image;
    if (active) {
	img->draw(pixmap, xor_node_gc, curr_pos, &r_image);
	graph_expose_pixamp_rectangle(&r_image);
	active = false;
    } 
    if (steps < total_steps) {
	++steps;
	curr_pos = src + vec * steps / total_steps;
	img->draw(pixmap, xor_node_gc, curr_pos, &r_image);
	graph_expose_pixamp_rectangle(&r_image);
	active = true;
	gtk_timeout_add(dt, (gint (*)(void*))Animation::static_animate, this);
    } else {
	(*DoneDisplayingCallback)();
    }
}

gint Animation::static_animate(Animation * a) {
    a->animate();
    return 0;
}

//
// I can easily have multiple animations (at the same time) by having
// multiple status variables, e.g., in a static array
//
Animation animation;

static SGraph::n_iterator graph_add_node(const char * name, 
					 const Image * img) {

#define RAND_D 20
#define RAND_X ((1+(int)(1.0*PWidth*rand()/RAND_D/(RAND_MAX+1.0)))*RAND_D)
#define RAND_Y ((1+(int)(1.0*PHeight*rand()/RAND_D/(RAND_MAX+1.0)))*RAND_D)

    if (img == NULL) {
	img = IMG_siena;
    }
    SGraph::n_iterator nn;
    nn = G.add_node(name, img, RAND_X , RAND_Y);
    graph_draw_node_immediate((*nn).second);
    return nn;
}

static void graph_run_animation(const Image * img, 
				const string & src,
				const string & dest) {
    SGraph::n_iterator isrc = G.find_node(src);
    if (isrc == G.nodes_end()) {
	if (popup_option_implicit->active) {
	    isrc = graph_add_node(src.c_str(), NULL);
	} else {
	    ostringstream os;
	    os << "graph_run_animation: couldn't find source node " << src;
	    (*Logger)(os.str().data(), os.str().size());
	    (*DoneDisplayingCallback)();
	    return;
	}
    }
    SGraph::n_iterator idest = G.find_node(dest);
    if (idest == G.nodes_end()) {
	if (popup_option_implicit->active) {
	    idest = graph_add_node(dest.c_str(), NULL);
	} else {
	    ostringstream os;
	    os << "graph_run_animation: couldn't find destination node " << dest;
	    (*Logger)(os.str().data(), os.str().size());
	    (*DoneDisplayingCallback)();
	    return;
	}
    }
    if (G.connect(isrc, idest))
	graph_redraw();

    animation.assign((*isrc).second, (*idest).second, img);
    animation.animate();
}




void graph_display_command(int argc, char *argv[]) {
    if (strcmp(argv[0], "add_node")==0 && argc > 1) {
	const Image * img = NULL;
	if (argc > 2) {
	    img = icons_find(argv[2]);
	}
	graph_add_node(argv[1], img);
    } else if (strcmp(argv[0], "remove_node")==0 && argc > 1) {
	if (G.remove_node(argv[1]))
	    graph_redraw();
    } else if (strcmp(argv[0], "connect")==0 && argc > 2) {
	if (G.connect(string(argv[1]), string(argv[2])))
	    graph_redraw();
    } else if (strcmp(argv[0], "disconnect")==0 && argc > 2) {
	if (G.disconnect(string(argv[1]), string(argv[2]))) {
	    graph_redraw();
	}
    } else if (strcmp(argv[0], "notify")==0 && argc > 2) {
	if (popup_option_notifications->active) {
	    graph_run_animation(IMG_notif, string(argv[1]), string(argv[2]));
	} else {
	    (*DoneDisplayingCallback)();
	}
	return;
    } else if (strcmp(argv[0], "subscribe")==0 && argc > 2) {
	if (popup_option_subscriptions->active) {
	    graph_run_animation(IMG_sub, string(argv[1]), string(argv[2]));
	} else {
	    (*DoneDisplayingCallback)();
	}
	return;
    } else if (strcmp(argv[0], "unsubscribe")==0 && argc > 2) {
	if (popup_option_unsubscriptions->active) {
	    graph_run_animation(IMG_unsub, string(argv[1]), string(argv[2]));
	} else {
	    (*DoneDisplayingCallback)();
	}
	return;
    } else {
	ostringstream os;
	os << "graph_display_command: unknown command " << argv[0];
	(*Logger)(os.str().data(), os.str().size());
	(*DoneDisplayingCallback)();
    }
    gtk_timeout_add(AnimDuration, graph_closeitup, NULL);
}

extern "C" {
    void on_graph_p_redraw_activate(GtkMenuItem *menuitem, 
				    gpointer user_data) {
	graph_redraw();
    }

    void on_graph_p_delete_node_activate(GtkMenuItem *menuitem, 
					 gpointer user_data) {
	G.remove_node(selected_node_i);
	graph_redraw();
    }


    void on_graph_p_clear_graph_activate(GtkMenuItem *menuitem, 
					 gpointer user_data) {
	G.clear();
	graph_redraw();
    }

    void on_graph_p_labels_activate(GtkMenuItem *menuitem, 
				    gpointer user_data) {
	graph_redraw();
    }
}
