/* * Merge 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 "mergeview.h" #include "merge-widget.h" #include "twopane-widget.h" #include "threepane-widget.h" #include "dtextmap.h" #include "merge-rmenu.h" #include "guimisc.h" /* Constant number */ enum { ARG_0, ARG_BASEPANE, ARG_AUXPANE }; /* signals */ enum { SCROLLUP, SCROLLDOWN, LAST_SIGNAL }; /* Private function declarations */ static void gdiff_mergeview_class_init(GdiffMergeViewClass *klass); static void gdiff_mergeview_set_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_mergeview_get_arg(GtkObject *object, GtkArg *arg, guint arg_id); static void gdiff_mergeview_init(GdiffMergeView *mergeview); static void gdiff_mergeview_finalize(GtkObject *object); static void gdiff_mergeview_scrollup(GdiffMergeView *mergeview); static void gdiff_mergeview_scrolldown(GdiffMergeView *mergeview); static void gdiff_mergeview_set_basepane(GdiffMergeView *mergeview, GdiffBasePane *basepane); static void gdiff_mergeview_set_auxpane(GdiffMergeView *mergeview, GdiffBasePane *auxpane); static void create_panes(GdiffMergeView *mergeview, DiffDir *diffdir, DiffFiles *dfiles); static void create_overview(GdiffMergeView *mergeview, DiffFiles *dfiles); static GtkWidget* create_vscrollbar(GdiffMergeView *mergeview, DiffFiles *dfiles); static void vscrollbar_vchanged(GtkAdjustment *vadj, gpointer data); static void update_vscrollboth(GdiffMergeView *mergeview); static void create_rmenu(GdiffMergeView *mergeview); static void merge_move_cb(GdiffMerge *merge, MoveDiff mv_diff, gpointer data); static void multipane_move_cb(GdiffBasePane *multipane, MoveDiff mv_diff, gpointer data); static void merge_select_dl_cb(GdiffMerge *merge, WhichFile whichfile, int ln, gpointer data); static void multipane_select_dl_cb(GdiffBasePane *multipane, WhichFile whichfile, int ln, gpointer data); static void update_statusbar(GdiffMergeView *mergeview); static GtkHBoxClass *parent_class = NULL; static guint mergeview_signals[LAST_SIGNAL] = { 0 }; GtkType gdiff_mergeview_get_type(void) { static GtkType mergeview_type = 0; if (!mergeview_type) { static const GtkTypeInfo mergeview_info = { "GdiffMergeView", sizeof(GdiffMergeView), sizeof(GdiffMergeViewClass), (GtkClassInitFunc)gdiff_mergeview_class_init, (GtkObjectInitFunc)gdiff_mergeview_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; mergeview_type = gtk_type_unique(GTK_TYPE_HBOX, &mergeview_info); } return mergeview_type; } static void gdiff_mergeview_class_init(GdiffMergeViewClass *klass) { GtkObjectClass *object_class; GtkWidgetClass *widget_class; gtk_object_add_arg_type("GdiffMergeView::basepane", GDIFF_TYPE_MERGE, GTK_ARG_READWRITE, ARG_BASEPANE); gtk_object_add_arg_type("GdiffMergeView::auxpane", GDIFF_TYPE_BASEPANE, GTK_ARG_READWRITE, ARG_AUXPANE); object_class = (GtkObjectClass*)klass; widget_class = (GtkWidgetClass*)klass; parent_class = gtk_type_class(GTK_TYPE_HBOX); mergeview_signals[SCROLLUP] = gtk_signal_new("scrollup", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffMergeViewClass, scrollup), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); mergeview_signals[SCROLLDOWN] = gtk_signal_new("scrolldown", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffMergeViewClass, scrolldown), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals(object_class, mergeview_signals, LAST_SIGNAL); object_class->finalize = gdiff_mergeview_finalize; object_class->set_arg = gdiff_mergeview_set_arg; object_class->get_arg = gdiff_mergeview_get_arg; klass->scrollup = gdiff_mergeview_scrollup; klass->scrolldown = gdiff_mergeview_scrolldown; } static void gdiff_mergeview_set_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffMergeView *mergeview; mergeview = GDIFF_MERGEVIEW(object); switch (arg_id) { case ARG_BASEPANE: gdiff_mergeview_set_basepane(mergeview, GTK_VALUE_POINTER(*arg)); break; case ARG_AUXPANE: gdiff_mergeview_set_auxpane(mergeview, GTK_VALUE_POINTER(*arg)); break; default: break; } } static void gdiff_mergeview_get_arg(GtkObject *object, GtkArg *arg, guint arg_id) { GdiffMergeView *mergeview; mergeview = GDIFF_MERGEVIEW(object); switch (arg_id) { case ARG_BASEPANE: GTK_VALUE_POINTER(*arg) = mergeview->merge; break; case ARG_AUXPANE: GTK_VALUE_POINTER(*arg) = mergeview->multipane; break; default: arg->type = GTK_TYPE_INVALID; break; } } static void gdiff_mergeview_init(GdiffMergeView *mergeview) { mergeview->vpaned = NULL; mergeview->merge = NULL; mergeview->multipane = NULL; mergeview->overview = NULL; mergeview->gdwin = NULL; mergeview->is_under_dir = FALSE; mergeview->outfile = NULL; } static void gdiff_mergeview_finalize(GtkObject *object) { GdiffMergeView *mergeview; mergeview = GDIFF_MERGEVIEW(object); if (mergeview->outfile) { g_free(mergeview->outfile); } (*GTK_OBJECT_CLASS(parent_class)->finalize)(object); } GtkWidget* gdiff_mergeview_new(DiffDir *diffdir, DiffFiles *dfiles, gboolean is_under_dir) { GdiffMergeView *mergeview; mergeview = gtk_type_new(GDIFF_TYPE_MERGEVIEW); create_panes(mergeview, diffdir, dfiles); create_overview(mergeview, dfiles); create_rmenu(mergeview); mergeview->is_under_dir = is_under_dir; return GTK_WIDGET(mergeview); } void gdiff_mergeview_set_outfile(GdiffMergeView *mergeview, const char *outfile) { if (outfile && outfile[0]) { if (mergeview->outfile) g_free(mergeview->outfile); mergeview->outfile = g_strdup(outfile); } } /** Internal functions **/ static void gdiff_mergeview_scrollup(GdiffMergeView *mergeview) { GtkVScrollbar *vs = GDIFF_MERGEVIEW(mergeview)->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_mergeview_scrolldown(GdiffMergeView *mergeview) { GtkVScrollbar *vs = GDIFF_MERGEVIEW(mergeview)->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_mergeview_set_basepane(GdiffMergeView *mergeview, GdiffBasePane *basepane) { g_return_if_fail(mergeview != NULL); g_return_if_fail(GDIFF_IS_MERGEVIEW(mergeview)); g_return_if_fail(mergeview->merge == NULL); if (basepane) g_return_if_fail(GDIFF_IS_MERGE(basepane)); mergeview->merge = basepane; } static void gdiff_mergeview_set_auxpane(GdiffMergeView *mergeview, GdiffBasePane *auxpane) { g_return_if_fail(mergeview != NULL); g_return_if_fail(GDIFF_IS_MERGEVIEW(mergeview)); g_return_if_fail(mergeview->multipane == NULL); if (auxpane) g_return_if_fail(GDIFF_IS_TWOPANE(auxpane) || GDIFF_IS_THREEPANE(auxpane)); mergeview->multipane = auxpane; } static void create_panes(GdiffMergeView *mergeview, DiffDir *diffdir, DiffFiles *dfiles) { GtkWidget *vpaned; GtkWidget *hbox; GtkWidget *merge; GtkWidget *multipane; GtkWidget *vscrollbar; vpaned = gtk_vpaned_new(); mergeview->vpaned = GTK_VPANED(vpaned); gtk_box_pack_start(GTK_BOX(mergeview), vpaned, TRUE, TRUE, 0); merge = gdiff_merge_new(diffdir, dfiles); gdiff_mergeview_set_basepane(mergeview, GDIFF_BASEPANE(merge)); gtk_paned_pack1(GTK_PANED(vpaned), merge, TRUE, TRUE); mergeview->merge_move_handler = gtk_signal_connect(GTK_OBJECT(merge), "move_diff", GTK_SIGNAL_FUNC(merge_move_cb), mergeview); mergeview->merge_select_dl_handler = gtk_signal_connect(GTK_OBJECT(merge), "select_dlines", GTK_SIGNAL_FUNC(merge_select_dl_cb), mergeview); hbox = gtk_hbox_new(FALSE, 0); gtk_paned_pack2(GTK_PANED(vpaned), hbox, TRUE, TRUE); if (GDIFF_MERGEVIEW_NUM_FILES(mergeview) == 2) { multipane = gdiff_twopane_new(diffdir, dfiles); } else { multipane = gdiff_threepane_new(diffdir, dfiles); } mergeview->multipane = GDIFF_BASEPANE(multipane); gtk_box_pack_start(GTK_BOX(hbox), multipane, TRUE, TRUE, 0); mergeview->multip_move_handler = gtk_signal_connect(GTK_OBJECT(multipane), "move_diff", GTK_SIGNAL_FUNC(multipane_move_cb), mergeview); mergeview->multip_select_dl_handler = gtk_signal_connect(GTK_OBJECT(multipane), "select_dlines", GTK_SIGNAL_FUNC(multipane_select_dl_cb), mergeview); vscrollbar = create_vscrollbar(mergeview, dfiles); mergeview->vscrollboth = GTK_VSCROLLBAR(vscrollbar); gtk_box_pack_end(GTK_BOX(hbox), vscrollbar, FALSE, FALSE, 0); gtk_widget_show_all(vpaned); } /** * create_overview: * Create GdiffOverview widget. * See "gdiffoverview.[ch]" about GdiffOverview widget. **/ static void create_overview(GdiffMergeView *mergeview, DiffFiles *dfiles) { GtkWidget *overview; GdiffOnePane *merge = GDIFF_ONEPANE(mergeview->merge); DTextMap *dtmap = merge->dtmap; GtkAdjustment *adj_array[MAX_NUM_COMPARE_FILES]; GdkColor *fg_array[MAX_NUM_COMPARE_FILES]; GdkColor *bg_array[MAX_NUM_COMPARE_FILES]; int n; for (n = 0; n < GDIFF_MERGEVIEW_NUM_FILES(mergeview); n++) { adj_array[n] = GTK_TEXT(merge->text)->vadj; /* XXX */ fg_array[n] = &PANE_PREF(merge).diff_bg[n]; bg_array[n] = NULL; } overview = gdiff_overview_new(GDIFF_MERGEVIEW_NUM_FILES(mergeview), adj_array); mergeview->overview = GDIFF_OVERVIEW(overview); gdiff_overview_set_color(GDIFF_OVERVIEW(overview), fg_array, bg_array); /* initialize overview widget */ if (dtmap->total_nl != 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 < GDIFF_MERGEVIEW_NUM_FILES(mergeview); n++) { /* Use local variables for ease to read */ int begin, end, tbegin, tend; begin = dlines->between[n].begin; end = dlines->between[n].end; /* Map to text */ tbegin = dtmap_map_b2t(dtmap, begin); tend = dtmap_map_b2t(dtmap, end); begin_array[n] = (gdouble)tbegin/dtmap->total_nl; end_array[n] = (gdouble)tend/dtmap->total_nl; } gdiff_overview_insert_paintrange(GDIFF_OVERVIEW(overview), begin_array, end_array); } } gtk_box_pack_start(GTK_BOX(mergeview), overview, FALSE, FALSE, 0); gtk_widget_show(overview); } /** * create_vscrollbar: * Create a vertical scrollbar to control both files. **/ static GtkWidget* create_vscrollbar(GdiffMergeView *mergeview, 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); gtk_signal_connect(GTK_OBJECT(vadj), "value_changed", GTK_SIGNAL_FUNC(vscrollbar_vchanged), mergeview); return vscrollbar; } /** * vscrollbar_vchanged: * Called when a user changes the value of scrollbar to control both files. **/ static void vscrollbar_vchanged(GtkAdjustment *vadj, gpointer data) { GdiffMergeView *mergeview = data; GdiffBasePane *multipane = mergeview->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_MERGEVIEW_NUM_FILES(mergeview); n++) { if (GDIFF_MERGEVIEW_NUM_FILES(mergeview) == 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]); } } static void update_vscrollboth(GdiffMergeView *mergeview) { GdiffBasePane *multipane = mergeview->multipane; GtkVScrollbar *vscrollboth = mergeview->vscrollboth; GtkAdjustment *adj1; GtkAdjustment *vadj;/* adj of vscrollboth */ gdouble max_nlines; gdouble f1_max; if (GDIFF_MERGEVIEW_NUM_FILES(mergeview) == 2) { adj1 = GTK_TEXT(GDIFF_TWOPANE(multipane)->text[FIRST_FILE])->vadj; } else { adj1 = GTK_TEXT(GDIFF_THREEPANE(multipane)->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), mergeview); 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), mergeview); } /* right-click menu */ static void create_rmenu(GdiffMergeView *mergeview) { GdiffOnePane *merge = GDIFF_ONEPANE(mergeview->merge); GtkWidget *rmenu; rmenu = merge_rmenu_create(mergeview); gnome_popup_menu_attach(rmenu, merge->text, mergeview); } static void merge_move_cb(GdiffMerge *merge, MoveDiff mv_diff, gpointer data) { GdiffMergeView *mergeview = data; GdiffBasePane *multipane = mergeview->multipane; /* sync multipane widget */ gtk_signal_handler_block(GTK_OBJECT(multipane), mergeview->multip_move_handler); gdiff_basepane_move_diff(GDIFF_BASEPANE(multipane), mv_diff); gtk_signal_handler_unblock(GTK_OBJECT(multipane), mergeview->multip_move_handler); update_vscrollboth(mergeview); update_statusbar(mergeview); } static void multipane_move_cb(GdiffBasePane *multipane, MoveDiff mv_diff, gpointer data) { GdiffMergeView *mergeview = data; GdiffMerge *merge = GDIFF_MERGE(mergeview->merge); /* sync merge widget */ gtk_signal_handler_block(GTK_OBJECT(merge), mergeview->merge_move_handler); gdiff_basepane_move_diff(GDIFF_BASEPANE(merge), mv_diff); gtk_signal_handler_unblock(GTK_OBJECT(merge), mergeview->merge_move_handler); update_vscrollboth(mergeview); } static void merge_select_dl_cb(GdiffMerge *merge, WhichFile whichfile, int ln, gpointer data) { GdiffMergeView *mergeview = data; GdiffBasePane *multipane = mergeview->multipane; /* sync multipane widget */ gtk_signal_handler_block(GTK_OBJECT(multipane), mergeview->multip_select_dl_handler); gdiff_basepane_select_dlines(multipane, whichfile, ln); gtk_signal_handler_unblock(GTK_OBJECT(multipane), mergeview->multip_select_dl_handler); update_statusbar(mergeview); } static void multipane_select_dl_cb(GdiffBasePane *multipane, WhichFile whichfile, int ln, gpointer data) { GdiffMergeView *mergeview = data; GdiffMerge *merge = GDIFF_MERGE(mergeview->merge); /* sync merge widget */ gtk_signal_handler_block(GTK_OBJECT(merge), mergeview->merge_select_dl_handler); gdiff_basepane_select_dlines(GDIFF_BASEPANE(merge), whichfile, ln); gtk_signal_handler_unblock(GTK_OBJECT(merge), mergeview->merge_select_dl_handler); } static void update_statusbar(GdiffMergeView *mergeview) { GdiffBasePane *merge = GDIFF_BASEPANE(mergeview->merge); 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 (merge->cur_dlines_node) { const DiffLines *dlines; dlines = merge->cur_dlines_node->data; for (n = 0; n < GDIFF_MERGEVIEW_NUM_FILES(mergeview); n++) { fnames[n] = GDIFF_MERGEVIEW_FILENAME(mergeview, n); begins[n] = dlines->between[n].begin; ends[n] = dlines->between[n].end; } sbar_msg = sbar_create_msg(GDIFF_MERGEVIEW_NUM_FILES(mergeview), fnames, begins, ends); } if (sbar_msg) { statusbar_update(mergeview->gdwin, sbar_msg); g_free(sbar_msg); } }