/* * Multi-pane (file)compare view widget module * * Copyright INOUE Seiichiro , licensed under the GPL. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "diff.h" #include "gui.h" #include "multipaneview.h" #include "twopane-widget.h" #include "threepane-widget.h" #include "rmenu.h" /* Constant number */ enum { ARG_0, ARG_BASEPANE }; /* signals */ enum { SCROLLUP, SCROLLDOWN, LAST_SIGNAL }; /* Private function declarations */ static void gdiff_multipview_class_init(GdiffMultipViewClass *klass); static void gdiff_multipview_set_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_multipview_get_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_multipview_init(GdiffMultipView *multipview); static void gdiff_multipview_destroy(GtkObject *object); static void gdiff_multipview_scrollup(GdiffMultipView *multipview); static void gdiff_multipview_scrolldown(GdiffMultipView *multipview); static void gdiff_multipview_set_basepane(GdiffMultipView *multipview, GdiffBasePane *basepane); static void create_multipane(GdiffMultipView *multipview, DiffDir *diffdir, DiffFiles *dfiles); static void create_overview(GdiffMultipView *multipview, DiffFiles *dfiles); static void create_vscrollbar(GdiffMultipView *multipview, DiffFiles *dfiles); static void vscrollbar_vchanged(GtkAdjustment *vadj, gpointer data); static void create_rmenu(GdiffMultipView *multipview); static void move_diff_cb(GdiffBasePane *basepane, MoveDiff mv_diff, gpointer data); static void update_statusbar(GdiffMultipView *multipview); static GtkHBoxClass *parent_class = NULL; static guint multipview_signals[LAST_SIGNAL] = { 0 }; GtkType gdiff_multipview_get_type(void) { static GtkType multipview_type = 0; if (!multipview_type) { static const GtkTypeInfo multipview_info = { "GdiffMultipView", sizeof(GdiffMultipView), sizeof(GdiffMultipViewClass), (GtkClassInitFunc)gdiff_multipview_class_init, (GtkObjectInitFunc)gdiff_multipview_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; multipview_type = gtk_type_unique(GTK_TYPE_HBOX, &multipview_info); } return multipview_type; } static void gdiff_multipview_class_init(GdiffMultipViewClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; gtk_object_add_arg_type("GdiffMultipView::basepane", GDIFF_TYPE_BASEPANE, GTK_ARG_READWRITE, ARG_BASEPANE); object_class = (GtkObjectClass*)klass; widget_class = (GtkWidgetClass*)klass; parent_class = gtk_type_class(GTK_TYPE_HBOX); multipview_signals[SCROLLUP] = gtk_signal_new("scrollup", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffMultipViewClass, scrollup), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); multipview_signals[SCROLLDOWN] = gtk_signal_new("scrolldown", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffMultipViewClass, scrolldown), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals(object_class, multipview_signals, LAST_SIGNAL); object_class->set_arg = gdiff_multipview_set_arg; object_class->get_arg = gdiff_multipview_get_arg; object_class->destroy = gdiff_multipview_destroy; klass->scrollup = gdiff_multipview_scrollup; klass->scrolldown = gdiff_multipview_scrolldown; } static void gdiff_multipview_set_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffMultipView *multipview; multipview = GDIFF_MULTIPVIEW(object); switch (arg_id) { case ARG_BASEPANE: gdiff_multipview_set_basepane(multipview, GTK_VALUE_POINTER(*arg)); break; default: break; } } static void gdiff_multipview_get_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffMultipView *multipview; multipview = GDIFF_MULTIPVIEW(object); switch (arg_id) { case ARG_BASEPANE: GTK_VALUE_POINTER(*arg) = multipview->multipane; break; default: arg->type = GTK_TYPE_INVALID; break; } } static void gdiff_multipview_init(GdiffMultipView *multipview) { multipview->multipane = NULL; multipview->overview = NULL; multipview->vscrollboth = NULL; multipview->rmenu = NULL; multipview->gdwin = NULL; multipview->is_under_dir = FALSE; } GtkWidget* gdiff_multipview_new(DiffDir *diffdir, DiffFiles *dfiles, gboolean is_under_dir) { GdiffMultipView *multipview; multipview = gtk_type_new(GDIFF_TYPE_MULTIPVIEW); create_multipane(multipview, diffdir, dfiles); create_overview(multipview, dfiles); create_vscrollbar(multipview, dfiles); create_rmenu(multipview); multipview->is_under_dir = is_under_dir; return GTK_WIDGET(multipview); } static void gdiff_multipview_destroy(GtkObject *object) { GdiffMultipView *multipview; g_return_if_fail(object != NULL); g_return_if_fail(GDIFF_IS_MULTIPVIEW(object)); multipview = GDIFF_MULTIPVIEW(object); gtk_signal_disconnect_by_data(GTK_OBJECT(multipview->multipane), multipview); GTK_OBJECT_CLASS(parent_class)->destroy(object); } /** Internal functions **/ static void gdiff_multipview_scrollup(GdiffMultipView *multipview) { GtkVScrollbar *vs = GDIFF_MULTIPVIEW(multipview)->vscrollboth; GtkAdjustment *adj = GTK_RANGE(vs)->adjustment; if (adj->value > adj->lower) { if (adj->value - adj->page_size > adj->lower) adj->value -= adj->page_size; else adj->value = adj->lower; gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed"); } } static void gdiff_multipview_scrolldown(GdiffMultipView *multipview) { GtkVScrollbar *vs = GDIFF_MULTIPVIEW(multipview)->vscrollboth; GtkAdjustment *adj = GTK_RANGE(vs)->adjustment; if (adj->value < adj->upper) { if (adj->value + adj->page_size < adj->upper) adj->value += adj->page_size; else adj->value = adj->upper; gtk_signal_emit_by_name(GTK_OBJECT(adj), "value_changed"); } } static void gdiff_multipview_set_basepane(GdiffMultipView *multipview, GdiffBasePane *basepane) { g_return_if_fail(multipview != NULL); g_return_if_fail(GDIFF_IS_MULTIPVIEW(multipview)); g_return_if_fail(multipview->multipane == NULL); if (basepane) g_return_if_fail(GDIFF_IS_TWOPANE(basepane) || GDIFF_IS_THREEPANE(basepane)); multipview->multipane = basepane; } static void create_multipane(GdiffMultipView *multipview, DiffDir *diffdir, DiffFiles *dfiles) { GtkWidget *multipane; if (!dfiles->is_diff3) { multipane = gdiff_twopane_new(diffdir, dfiles); } else { multipane = gdiff_threepane_new(diffdir, dfiles); } gdiff_multipview_set_basepane(multipview, GDIFF_BASEPANE(multipane)); gtk_box_pack_start(GTK_BOX(multipview), multipane, TRUE, TRUE, 0); gtk_signal_connect(GTK_OBJECT(multipane), "move_diff", GTK_SIGNAL_FUNC(move_diff_cb), multipview); gtk_widget_show(multipane); } /** * create_overview: * Create GdiffOverview widget. * See "gdiffoverview.[ch]" about GdiffOverview widget. **/ static void create_overview(GdiffMultipView *multipview, DiffFiles *dfiles) { GtkWidget *overview; const FileInfo *fi[MAX_NUM_COMPARE_FILES]; GdiffBasePane *multipane = multipview->multipane; GtkAdjustment *adj_array[MAX_NUM_COMPARE_FILES]; GdkColor *fg_array[MAX_NUM_COMPARE_FILES]; GdkColor *bg_array[MAX_NUM_COMPARE_FILES]; int num_files = GDIFF_MULTIPVIEW_NUM_FILES(multipview); int n; for (n = 0; n < num_files; n++) { fi[n] = dfiles_get_fileinfo(dfiles, n, TRUE); if (GDIFF_MULTIPVIEW_NUM_FILES(multipview) == 2) adj_array[n] = GTK_TEXT(GDIFF_TWOPANE(multipane)->text[n])->vadj; else adj_array[n] = GTK_TEXT(GDIFF_THREEPANE(multipane)->text[n])->vadj; } overview = gdiff_overview_new(num_files, adj_array); multipview->overview = GDIFF_OVERVIEW(overview); for (n = 0; n < num_files; n++) { fg_array[n] = &PANE_PREF(multipane).diff_bg[n]; bg_array[n] = NULL; } gdiff_overview_set_color(GDIFF_OVERVIEW(overview), fg_array, bg_array); /* initialize overview widget */ if (fi[0]->nlines != 0 && fi[1]->nlines != 0 && (num_files == 2 || (num_files == 3 && fi[2]->nlines != 0))) { GList *node;/* node of DiffLines list */ for (node = dfiles->dlines_list; node; node = node->next) { const DiffLines *dlines = node->data; gdouble begin_array[MAX_NUM_COMPARE_FILES]; gdouble end_array[MAX_NUM_COMPARE_FILES]; for (n = 0; n < num_files; n++) { begin_array[n] = (gdouble)(dlines->between[n].begin)/fi[n]->nlines; end_array[n] = (gdouble)(dlines->between[n].end)/fi[n]->nlines; } gdiff_overview_insert_paintrange(GDIFF_OVERVIEW(overview), begin_array, end_array); } } gtk_box_pack_start(GTK_BOX(multipview), overview, FALSE, FALSE, 0); gtk_widget_show(overview); } /** * create_vscrollbar: * Create a vertical scrollbar to control both files. **/ static void create_vscrollbar(GdiffMultipView *multipview, DiffFiles *dfiles) { #define PAGE_RATIO 10.0 /* XXX hard-coded is good? */ GtkWidget *vscrollbar; GtkAdjustment *vadj;/* adj of vscrollboth */ int max_nlines; max_nlines = dfiles_get_max_nlines(dfiles); vadj = GTK_ADJUSTMENT(gtk_adjustment_new(0, 0, max_nlines, 1, max_nlines/PAGE_RATIO, 1)); vscrollbar = gtk_vscrollbar_new(vadj); multipview->vscrollboth = GTK_VSCROLLBAR(vscrollbar); gtk_box_pack_start(GTK_BOX(multipview), vscrollbar, FALSE, FALSE, 0); gtk_signal_connect(GTK_OBJECT(vadj), "value_changed", GTK_SIGNAL_FUNC(vscrollbar_vchanged), multipview); gtk_widget_show(vscrollbar); } /** * vscrollbar_vchanged: * Called when a user changes the value of scrollbar to control both files. **/ static void vscrollbar_vchanged(GtkAdjustment *vadj, gpointer data) { GdiffMultipView *multipview = data; GdiffBasePane *multipane = multipview->multipane; GtkAdjustment *adj[MAX_NUM_COMPARE_FILES]; gdouble max_val[MAX_NUM_COMPARE_FILES]; gdouble max_nlines = vadj->upper; gdouble value = vadj->value; int n; g_assert(max_nlines != 0); for (n = 0; n < GDIFF_MULTIPVIEW_NUM_FILES(multipview); n++) { if (GDIFF_MULTIPVIEW_NUM_FILES(multipview) == 2) adj[n] = GTK_TEXT(GDIFF_TWOPANE(multipane)->text[n])->vadj; else adj[n] = GTK_TEXT(GDIFF_THREEPANE(multipane)->text[n])->vadj; max_val[n] = adj[n]->upper - adj[n]->lower - adj[n]->page_size; gtk_adjustment_set_value(adj[n], value / max_nlines * max_val[n]); } } /* right-click menu */ static void create_rmenu(GdiffMultipView *multipview) { GdiffBasePane *multipane = multipview->multipane; GtkWidget *rmenu; int n; for (n = 0; n < GDIFF_MULTIPVIEW_NUM_FILES(multipview); n++) { rmenu = rmenu_create(GTK_WIDGET(multipview), GINT_TO_POINTER(n)); if (GDIFF_MULTIPVIEW_NUM_FILES(multipview) == 2) gnome_popup_menu_attach(rmenu, GDIFF_TWOPANE(multipane)->text[n], multipview); else gnome_popup_menu_attach(rmenu, GDIFF_THREEPANE(multipane)->text[n], multipview); } } /** * move_diff_cb: * Callback for "move_diff" signal of pane widget(GdiffTwoPane or GdiffThreePane). * "move_diff" itself is handled by the pane widget. * GdiffMultipView widget's responsibility is to take care of vscrollbar * and statusbar. * Theoretically, it doesn't matter which one I use adj1 or adj2 * for vscrollbar calculation. * I'm using adj1 here. * The calculation is reverse of vscrollbar_vchanged()'s one. **/ static void move_diff_cb(GdiffBasePane *basepane, MoveDiff mv_diff, gpointer data) { GdiffMultipView *multipview = data; GtkVScrollbar *vscrollboth = multipview->vscrollboth; GtkAdjustment *adj1; GtkAdjustment *vadj;/* adj of vscrollboth */ gdouble max_nlines; gdouble f1_max; if (GDIFF_MULTIPVIEW_NUM_FILES(multipview) == 2) { adj1 = GTK_TEXT(GDIFF_TWOPANE(basepane)->text[FIRST_FILE])->vadj; } else { adj1 = GTK_TEXT(GDIFF_THREEPANE(basepane)->text[FIRST_FILE])->vadj; } f1_max = adj1->upper - adj1->lower - adj1->page_size; if (f1_max == 0) return; vadj = gtk_range_get_adjustment(GTK_RANGE(vscrollboth)); max_nlines = vadj->upper; gtk_signal_handler_block_by_func( GTK_OBJECT(vadj), GTK_SIGNAL_FUNC(vscrollbar_vchanged), multipview); gtk_adjustment_set_value(vadj, adj1->value / f1_max * max_nlines); gtk_signal_handler_unblock_by_func( GTK_OBJECT(vadj), GTK_SIGNAL_FUNC(vscrollbar_vchanged), multipview); update_statusbar(multipview); } static void update_statusbar(GdiffMultipView *multipview) { GdiffBasePane *multipane = GDIFF_BASEPANE(multipview->multipane); char *sbar_msg = NULL;/* status-bar message */ const char *fnames[MAX_NUM_COMPARE_FILES]; int begins[MAX_NUM_COMPARE_FILES]; int ends[MAX_NUM_COMPARE_FILES]; int n; if (multipane->cur_dlines_node) { const DiffLines *dlines; dlines = multipane->cur_dlines_node->data; for (n = 0; n < GDIFF_MULTIPVIEW_NUM_FILES(multipview); n++) { fnames[n] = GDIFF_MULTIPVIEW_FILENAME(multipview, n); begins[n] = dlines->between[n].begin; ends[n] = dlines->between[n].end; } sbar_msg = sbar_create_msg(GDIFF_MULTIPVIEW_NUM_FILES(multipview), fnames, begins, ends); } if (sbar_msg) { statusbar_update(multipview->gdwin, sbar_msg); g_free(sbar_msg); } }