/* * Three-pane widget module * * Naming convention: * buf: internal (mmap'd) buffer, which is static. * text: GtkText widget data, which is variable. * * Copyright INOUE Seiichiro , licensed under the GPL. */ /* XXX: This could be merged with (or derived from?) twopane-widget.c? */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "diff.h" #include "gui.h" #include "dtextmap.h" #include "threepane-widget.h" #include "misc.h" #include "gtktext-support.h" #include "linenum.h" #include "style.h" #include "actions.h" /* Keys for gtk_object_[get|set]_data(). I need this, because three text widgets share the same callback function. */ #define WHICH_TEXT_KEY "which" /* Private function declarations */ static void gdiff_threepane_class_init(GdiffThreePaneClass *klass); static void gdiff_threepane_init(GdiffThreePane *threepane); static void gdiff_threepane_finalize(GtkObject *object); static void gdiff_threepane_display(GdiffBasePane *basepane); static void gdiff_threepane_show_linenum(GdiffBasePane *basepane, gboolean to_show); static void gdiff_threepane_show_fill(GdiffBasePane *basepane, gboolean to_show); static gboolean gdiff_threepane_toggle_textwrap(GdiffBasePane *basepane); static void gdiff_threepane_set_highlight(GdiffBasePane *basepane, gboolean to_highlight); static void gdiff_threepane_move_diff(GdiffBasePane *basepane, MoveDiff mv_diff); static void gdiff_threepane_select_dlines(GdiffBasePane *basepane, WhichFile whichfile, int ln); static gboolean gdiff_threepane_search_string(GdiffBasePane *basepane, const char *string, WhichFile whichfile); static void guts_threepane_display(GdiffThreePane *threepane); static void draw_text(GdiffThreePane *threepane, WhichFile whichfile); static void show_hide_numbers(GdiffThreePane *threepane, WhichFile whichfile, gboolean b_show); static void calc_ln_columns(GdiffThreePane *threepane, WhichFile whichfile, int nlines); static void show_fill(GdiffThreePane *threepane, WhichFile whichfile); static void hide_fill(GdiffThreePane *threepane, WhichFile whichfile); static void guts_move_diff(GdiffThreePane *threepane, MoveDiff mv_diff); static void change_lines_bgcolor(GdiffThreePane *threepane, const DiffLines *dlines, WhichFile whichfile, GdkColor *bg_color); static gint text_click_cb(GtkWidget *text, GdkEventButton *event, gpointer data); static gboolean guts_search_string(GdiffThreePane *threepane, const char *string, WhichFile whichfile); static void centering_text(GdiffThreePane *threepane, WhichFile whichfile, int tln); /* Macros to avoid useless dtmap_map function */ #define TEXT_LN(threepane, dtmap, bln) ((PANE_PREF(threepane).show_fill == TRUE) ? dtmap_map_b2t(dtmap, bln) : bln) #define TEXT_TOTAL_NL(threepane, dtmap, fi) ((PANE_PREF(threepane).show_fill == TRUE) ? dtmap->total_nl : fi->nlines) #define BUF_LN(threepane, dtmap, tln) ((PANE_PREF(threepane).show_fill == TRUE) ? dtmap_map_t2b(dtmap, tln) : tln) static GdiffBasePaneClass *parent_class = NULL; GtkType gdiff_threepane_get_type(void) { static GtkType threepane_type = 0; if (!threepane_type) { static const GtkTypeInfo threepane_info = { "GdiffThreePane", sizeof(GdiffThreePane), sizeof(GdiffThreePaneClass), (GtkClassInitFunc)gdiff_threepane_class_init, (GtkObjectInitFunc)gdiff_threepane_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; threepane_type = gtk_type_unique(GDIFF_TYPE_BASEPANE, &threepane_info); } return threepane_type; } static void gdiff_threepane_class_init(GdiffThreePaneClass *klass) { GtkObjectClass *object_class; GdiffBasePaneClass *basepane_class; object_class = (GtkObjectClass*)klass; basepane_class = (GdiffBasePaneClass*)klass; parent_class = gtk_type_class(GDIFF_TYPE_BASEPANE); object_class->finalize = gdiff_threepane_finalize; basepane_class->display = gdiff_threepane_display; basepane_class->show_linenum = gdiff_threepane_show_linenum; basepane_class->show_fill = gdiff_threepane_show_fill; basepane_class->toggle_textwrap = gdiff_threepane_toggle_textwrap; basepane_class->set_highlight = gdiff_threepane_set_highlight; basepane_class->move_diff = gdiff_threepane_move_diff; basepane_class->select_dlines = gdiff_threepane_select_dlines; basepane_class->search_string = gdiff_threepane_search_string; } static void gdiff_threepane_init(GdiffThreePane *threepane) { GdiffBasePane *basepane; GtkBin *bin; GtkWidget *hpaned1; GtkWidget *hpaned2; int n; basepane = GDIFF_BASEPANE(threepane); bin = GTK_BIN(basepane); hpaned1 = gtk_hpaned_new(); hpaned2 = gtk_hpaned_new(); gtk_container_add(GTK_CONTAINER(bin), hpaned1); gtk_paned_pack2(GTK_PANED(hpaned1), hpaned2, TRUE, TRUE); gtk_widget_show(hpaned1); gtk_widget_show(hpaned2); for (n = 0; n < 3; n++) { GtkWidget *text; GtkWidget *scrollwin; scrollwin = gtk_scrolled_window_new(NULL, NULL); /* XXX: I can't use horizontal scrollbar, because of the current GtkText's limitation. */ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); if (n == FIRST_FILE) gtk_paned_pack1(GTK_PANED(hpaned1), scrollwin, TRUE, TRUE); else if (n == SECOND_FILE) gtk_paned_pack1(GTK_PANED(hpaned2), scrollwin, TRUE, TRUE); else if (n == THIRD_FILE) gtk_paned_pack2(GTK_PANED(hpaned2), scrollwin, TRUE, TRUE); gtk_widget_show(scrollwin); text = gtk_text_new(NULL, NULL); threepane->text[n] = text; style_set_text(text); gtext_set_editable(text, FALSE); gtext_set_word_wrap(text, FALSE); gtext_set_line_wrap(text, PANE_PREF(threepane).line_wrap); gtk_container_add(GTK_CONTAINER(scrollwin), text); gtk_widget_show(text); threepane->search_ln[n] = 0; threepane->search_tln[n] = 0; threepane->search_tpoint[n] = 0; threepane->search_tindex[n] = 0; } PANE_PREF(threepane).view_type = MULTIPANE3_VIEW; } static void gdiff_threepane_finalize(GtkObject *object) { GdiffThreePane *threepane; int n; g_return_if_fail(object != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(object)); threepane = GDIFF_THREEPANE(object); for (n = 0; n < 3; n++) { dtmap_delete(threepane->dtmap[n]); } (*GTK_OBJECT_CLASS(parent_class)->finalize)(object); } GtkWidget* gdiff_threepane_new(DiffDir *diffdir, DiffFiles *dfiles) { GdiffThreePane *threepane; int n; threepane = gtk_type_new(GDIFF_TYPE_THREEPANE); _gdiff_basepane_set_backend(GDIFF_BASEPANE(threepane), diffdir, dfiles);/*XXX*/ /* Three-pane internal data */ for (n = 0; n < 3; n++) { const FileInfo *fi = dfiles_get_fileinfo(dfiles, n, TRUE); threepane->dtmap[n] = dtmap_new(fi->nlines); threepane->n_col[n] = -1; /* workaround */ gtk_widget_ensure_style(threepane->text[n]); } guts_threepane_display(threepane); for (n = 0; n < 3; n++) { gtk_object_set_data(GTK_OBJECT(threepane->text[n]), WHICH_TEXT_KEY, GINT_TO_POINTER(n)); gtk_signal_connect_after(GTK_OBJECT(threepane->text[n]), "button_press_event", GTK_SIGNAL_FUNC(text_click_cb), threepane); } return GTK_WIDGET(threepane); } /** Interface **/ static void gdiff_threepane_display(GdiffBasePane *basepane) { GdiffThreePane *threepane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); guts_threepane_display(threepane); } /** * gdiff_threepane_show_linenum: * Show(Hide) the line numbers on text widget. * Input: * gboolean to_show; TRUE implies to show. FALSE implies to hide. **/ static void gdiff_threepane_show_linenum(GdiffBasePane *basepane, gboolean to_show) { GdiffThreePane *threepane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); if (to_show != PANE_PREF(threepane).show_line_num) { int n; for (n = 0; n < 3; n++) { show_hide_numbers(threepane, n, to_show); } PANE_PREF(threepane).show_line_num = to_show; } } /** * gdiff_threepane_show_fill: * Show(Hide) the fill parts on text widget. * Input: * gboolean to_show; TRUE implies to show. FALSE implies to hide. **/ static void gdiff_threepane_show_fill(GdiffBasePane *basepane, gboolean to_show) { GdiffThreePane *threepane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); if (to_show != PANE_PREF(threepane).show_fill) { int n; for (n = 0; n < 3; n++) { if (to_show == TRUE) show_fill(threepane, n); else hide_fill(threepane, n); } PANE_PREF(threepane).show_fill = to_show; } } /** * gdiff_threepane_toggle_textwrap: * Return TRUE if it is wrapped. **/ static gboolean gdiff_threepane_toggle_textwrap(GdiffBasePane *basepane) { GdiffThreePane *threepane; gboolean b_wrap; int n; g_return_val_if_fail(basepane != NULL, FALSE); g_return_val_if_fail(GDIFF_IS_THREEPANE(basepane), FALSE); threepane = GDIFF_THREEPANE(basepane); b_wrap = !(GTK_TEXT(threepane->text[FIRST_FILE])->line_wrap); for (n = 0; n < 3; n++) { gtext_set_line_wrap(threepane->text[n], b_wrap); } PANE_PREF(threepane).line_wrap = b_wrap; return b_wrap; } /** * gdiff_threepane_set_highlight: * Take care of the current highlight. * Input: * gboolean to_highlight; TRUE implies to enable highlight. FALSE implies to disable. **/ static void gdiff_threepane_set_highlight(GdiffBasePane *basepane, gboolean to_highlight) { GdiffThreePane *threepane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); if (to_highlight != PANE_PREF(threepane).highlight) { if (GDIFF_BASEPANE(threepane)->cur_dlines_node) { int n; const DiffLines *dlines = GDIFF_BASEPANE(threepane)->cur_dlines_node->data; for (n = 0; n < 3; n++) { GdkColor *bg_color; if (to_highlight == TRUE) { bg_color = &PANE_PREF(threepane).diff_hl[n]; change_lines_bgcolor(threepane, dlines, n, bg_color); } else { bg_color = &PANE_PREF(threepane).diff_bg[n]; change_lines_bgcolor(threepane, dlines, n, bg_color); } } } PANE_PREF(threepane).highlight = to_highlight; } } /** * gdiff_threepane_move_diff: * Move to a difference, such as next, previous, first or last. **/ static void gdiff_threepane_move_diff(GdiffBasePane *basepane, MoveDiff mv_diff) { GdiffThreePane *threepane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); guts_move_diff(threepane, mv_diff); } /** * gdiff_threepane_select_dlines: * Typically, called when a user click a button on text widget. **/ static void gdiff_threepane_select_dlines(GdiffBasePane *basepane, WhichFile whichfile, int ln) { GdiffThreePane *threepane; DiffFiles *dfiles; const GList *dlines_node; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_THREEPANE(basepane)); threepane = GDIFF_THREEPANE(basepane); dfiles = PANE_DFILES(threepane); dlines_node = dfiles_find_includel(dfiles, whichfile, ln); if (dlines_node == NULL && ln == 1) dlines_node = dfiles_find_rel_curl(dfiles, whichfile, 0);/* exception */ if (dlines_node) { if (PANE_PREF(threepane).highlight == TRUE) {/* revert the current one */ if (GDIFF_BASEPANE(threepane)->cur_dlines_node) { int n; const DiffLines *dlines = GDIFF_BASEPANE(threepane)->cur_dlines_node->data; for (n = 0; n < 3; n++) { GdkColor *bg_color; bg_color = &PANE_PREF(threepane).diff_bg[n]; change_lines_bgcolor(threepane, dlines, n, bg_color); } } } GDIFF_BASEPANE(threepane)->cur_dlines_node = dlines_node; gtk_signal_emit_by_name(GTK_OBJECT(threepane), "move_diff", MOVED_CUR_NOSCROLL); } } /** * gdiff_threepane_search_string: * @string is null-byte-terminated. Return TRUE if found. **/ static gboolean gdiff_threepane_search_string(GdiffBasePane *basepane, const char *string, WhichFile whichfile) { GdiffThreePane *threepane; g_return_val_if_fail(basepane != NULL, FALSE); g_return_val_if_fail(GDIFF_IS_THREEPANE(basepane), FALSE); if (string == NULL || string[0] == '\0') return FALSE; threepane = GDIFF_THREEPANE(basepane); return guts_search_string(threepane, string, whichfile); } /** Internal functions **/ /** * guts_threepane_display: * Show the diff result in three-pane mode. **/ static void guts_threepane_display(GdiffThreePane *threepane) { int n; for (n = 0; n < 3; n++) { draw_text(threepane, n); if (PANE_PREF(threepane).show_fill == TRUE) { show_fill(threepane, n); } if (PANE_PREF(threepane).show_line_num == TRUE) { show_hide_numbers(threepane, n, TRUE); } } } /* difftype condition macros */ /* Case: All different */ #define IS_CHANGE_DT(dtype, whichfile) \ ((dtype & CHANGE) \ || ((whichfile == FIRST_FILE) && (dtype & (F12ADD | F31ADD))) \ || ((whichfile == SECOND_FILE) && (dtype & (F12ADD | F23ADD))) \ || ((whichfile == THIRD_FILE) && (dtype & (F23ADD | F31ADD)))) /* Case: Only this file is changed */ #define IS_ONLY_CHANGE_DT(dtype, whichfile) \ ((dtype & ONLY_CHANGE) \ && \ (((whichfile == FIRST_FILE) && (dtype & F1ONLY)) \ || ((whichfile == SECOND_FILE) && (dtype & F2ONLY)) \ || ((whichfile == THIRD_FILE) && (dtype & F3ONLY)))) /* Case: Only another file is changed */ #define IS_O_ONLY_CHANGE_DT(dtype, whichfile) \ ((dtype & ONLY_CHANGE) \ && \ (((whichfile == FIRST_FILE) && !(dtype & F1ONLY)) \ || ((whichfile == SECOND_FILE) && !(dtype & F2ONLY)) \ || ((whichfile == THIRD_FILE) && !(dtype & F3ONLY)))) /* Case: Only this file has additional portions */ #define IS_ONLY_DT(dtype, whichfile) \ ((dtype & ONLY_ADD) \ && \ (((whichfile == FIRST_FILE) && (dtype & F1ONLY)) \ || ((whichfile == SECOND_FILE) && (dtype & F2ONLY)) \ || ((whichfile == THIRD_FILE) && (dtype & F3ONLY)))) /* Case: Only another file has additional portions */ #define IS_O_ONLY_DT(dtype, whichfile) \ (!(dtype & ONLY_CHANGE) \ && \ (((whichfile == FIRST_FILE) && ((dtype & (F2ONLY|F3ONLY)) || (dtype & F23ADD))) \ || ((whichfile == SECOND_FILE) && ((dtype & (F1ONLY|F3ONLY)) || (dtype & F31ADD))) \ || ((whichfile == THIRD_FILE) && ((dtype & (F1ONLY|F2ONLY)) || (dtype & F12ADD))))) /** * draw_text: * Draw text with coloring different parts. * During drawing, update dtmap. **/ static void draw_text(GdiffThreePane *threepane, WhichFile whichfile) { DiffFiles *dfiles = PANE_DFILES(threepane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); MBuffer *mbuf = PANE_MBUF(threepane, whichfile); DTextMap *dtmap = threepane->dtmap[whichfile]; const char *buf_pt; int buf_ln; int buf_lenb; GtkWidget *text = threepane->text[whichfile]; GdkFont *font = text->style->font; GList *node;/* node of DiffLines list */ FontProp fprop; GdkColor *fg_color = &PANE_PREF(threepane).diff_fg[whichfile]; GdkColor *bg_color = &PANE_PREF(threepane).diff_bg[whichfile]; int oldpos = 0; int pos; if (fi->buf == NULL) /* Maybe, the file has been deleted. */ return; fprop.font = font; mbuf_goto_top(mbuf); gtext_freeze(text); for (node = dfiles->dlines_list; node; node = node->next) { const DiffLines *dlines = node->data; /* Use local variables for readability */ int begin = dlines->between[whichfile].begin; int end = dlines->between[whichfile].end; DispAttr attr = 0; fprop.fg = fprop.bg = NULL; buf_ln = mbuf->cur_ln; buf_pt = mbuf->cur_pt; buf_lenb = mbuf_goto_line(mbuf, begin) - buf_pt; pos = gtext_insert_buf(text, &fprop, buf_pt, buf_lenb); dtmap_append_displn(dtmap, DA_COMMON, oldpos, pos - oldpos, buf_pt, buf_lenb, begin - buf_ln); oldpos = pos; if (IS_CHANGE_DT(dlines->difftype, whichfile) || (dlines->difftype & ONLY_CHANGE)) { fprop.fg = fg_color; fprop.bg = bg_color; attr = DA_CHANGE; } else if (IS_ONLY_DT(dlines->difftype, whichfile)) { fprop.fg = fg_color; fprop.bg = bg_color; attr = DA_ONLY; } else if (IS_O_ONLY_DT(dlines->difftype, whichfile)) { fprop.fg = fprop.bg = NULL; attr = DA_O_ONLY; } buf_pt = mbuf->cur_pt; buf_lenb = mbuf_goto_line(mbuf, end + 1) - buf_pt; pos = gtext_insert_buf(text, &fprop, buf_pt, buf_lenb); dtmap_append_displn(dtmap, attr, oldpos, pos - oldpos, buf_pt, buf_lenb, end+1 - begin); oldpos = pos; } /* Draw the remained part */ fprop.fg = fprop.bg = NULL; buf_ln = mbuf->cur_ln; buf_pt = mbuf->cur_pt; buf_lenb = mbuf_goto_line(mbuf, fi->nlines + 1) - buf_pt; pos = gtext_insert_buf(text, &fprop, buf_pt, buf_lenb); dtmap_append_displn(dtmap, DA_COMMON, oldpos, pos - oldpos, buf_pt, buf_lenb, mbuf->cur_ln - buf_ln); gtext_thaw(text); } /* Routines for line numbers */ /** * show_hide_numbers: * Show(Hide) line numbers on the heads of each line. **/ static void show_hide_numbers(GdiffThreePane *threepane, WhichFile whichfile, gboolean b_show) { DiffFiles *dfiles = PANE_DFILES(threepane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; GdkFont *font = text->style->font; FontProp fprop; GdkColor *fg_color = &PANE_PREF(threepane).diff_fg[whichfile]; GdkColor *bg_color = &PANE_PREF(threepane).diff_bg[whichfile]; GdkColor *fill_color = &PANE_PREF(threepane).diff_fill;/* fill parts */ LineFormat lformat; DispLines *displn; int ln = 1; if (fi->buf == NULL) /* Maybe, the file has been deleted. */ return; if (threepane->n_col[whichfile] < 0) calc_ln_columns(threepane, whichfile, fi->nlines); lformat.n_col = threepane->n_col[whichfile]; fprop.font = font; gtext_freeze(text); for (displn = dtmap_first_displn(dtmap, DA_ANY); displn; displn = dtmap_next_displn(dtmap, DA_ANY)) { if (displn->attr & (DA_COMMON | DA_O_ONLY)) { fprop.fg = fprop.bg = NULL; lformat.format = threepane->format_common; } else if (displn->attr & DA_DIFF) { fprop.fg = fg_color; fprop.bg = bg_color; lformat.format = threepane->format_diff[whichfile]; } else if (displn->attr & DA_FILL) { fprop.fg = fprop.bg = fill_color; lformat.format = NULL; } else { g_assert_not_reached(); } mbuf_goto_top(MBUFFER(displn)); insert_remove_line_numbers(text, b_show, displn->pos, &fprop, MBUFFER(displn), ln, ln + MBUFFER(displn)->nl, &lformat); if (displn->attr & DA_BASE) ln += MBUFFER(displn)->nl; if (b_show == TRUE) { dtmap_inc_displn(dtmap, displn, MBUFFER(displn)->nl * threepane->ln_col_size[whichfile]); } else { dtmap_dec_displn(dtmap, displn, MBUFFER(displn)->nl * threepane->ln_col_size[whichfile]); } } gtext_thaw(text); } /* calculate column size of line numbers, and store it. Once the value is stored, this is not called any more. */ static void calc_ln_columns(GdiffThreePane *threepane, WhichFile whichfile, int nlines) { int n_col; const char *which_mark = NULL; n_col = calc_number_places(nlines); threepane->n_col[whichfile] = n_col; g_snprintf(threepane->format_common, sizeof(threepane->format_common), "%%%dd%s", n_col, MARK_COMMON);/* e.g. "%4d " */ if (whichfile == FIRST_FILE) which_mark = MARK_FILE1; else if (whichfile == SECOND_FILE) which_mark = MARK_FILE2; else if (whichfile == THIRD_FILE) which_mark = MARK_FILE3; g_snprintf(threepane->format_diff[whichfile], sizeof(threepane->format_diff[whichfile]), "%%%dd%s", n_col, which_mark);/* e.g. "%4d< " */ /* To keep the column size */ threepane->ln_col_size[whichfile] = n_col + MARK_LENGTH; } /** * show_fill: **/ static void determine_otherfiles(WhichFile *otherfiles, WhichFile whichfile) { switch (whichfile) { case FIRST_FILE: otherfiles[0] = SECOND_FILE; otherfiles[1] = THIRD_FILE; break; case SECOND_FILE: otherfiles[0] = FIRST_FILE; otherfiles[1] = THIRD_FILE; break; case THIRD_FILE: otherfiles[0] = FIRST_FILE; otherfiles[1] = SECOND_FILE; break; } } static void show_fill(GdiffThreePane *threepane, WhichFile whichfile) { DiffFiles *dfiles = PANE_DFILES(threepane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); WhichFile otherfiles[2]; MBuffer *mbuf = PANE_MBUF(threepane, whichfile); MBuffer *mbuf_o[2];/* mbufs of other files */ DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; int pos = 0;/* position in text widget */ GdkFont *font = text->style->font; GList *node;/* node of DiffLines list */ FontProp fprop; GdkColor *fill_color = &PANE_PREF(threepane).diff_fill; static const char fill_ln_str[] = " ";/* fill parts corresponding to line numbers */ int n; if (fi->buf == NULL) /* Maybe, the file has been deleted. */ return; g_assert(threepane->ln_col_size[whichfile] < sizeof(fill_ln_str)-1); determine_otherfiles(otherfiles, whichfile); for (n = 0; n < 2; n++) { mbuf_o[n] = PANE_MBUF(threepane, otherfiles[n]); } fprop.font = font; fprop.fg = fprop.bg = fill_color; mbuf_goto_top(mbuf); for (n = 0; n < 2; n++) { mbuf_goto_top(mbuf_o[n]); } gtext_freeze(text); for (node = dfiles->dlines_list; node; node = node->next) { const DiffLines *dlines = node->data; /* Use local variables for readability */ int begin = dlines->between[whichfile].begin; int end = dlines->between[whichfile].end; int begin_o[2]; int end_o[2]; const DispLines *displn; const char *buf_pt_o = NULL; int pos_i; /* position to insert */ int fill_nl = 0; int ln; WhichFile larger_o; /* to fill, use a larger one among other files */ for (n = 0; n < 2; n++) { begin_o[n] = dlines->between[otherfiles[n]].begin; end_o[n] = dlines->between[otherfiles[n]].end; } /* Position to insert in text widget */ displn = dtmap_lookup_by_bufln(dtmap, begin); if (displn == NULL) continue; pos_i = displn->pos + displn->len; gtext_set_point(text, pos_i); if ((end_o[1] - begin_o[1]) > (end_o[0] - begin_o[0])) larger_o = 1; else larger_o = 0; if (IS_O_ONLY_DT(dlines->difftype, whichfile)) { buf_pt_o = mbuf_goto_line(mbuf_o[larger_o], begin_o[larger_o]); fill_nl = end_o[larger_o] + 1 - begin_o[larger_o]; } else if (IS_CHANGE_DT(dlines->difftype, whichfile) || IS_O_ONLY_CHANGE_DT(dlines->difftype, whichfile) || IS_ONLY_CHANGE_DT(dlines->difftype, whichfile)) { fill_nl = (end_o[larger_o] - begin_o[larger_o]) - (end - begin); if (fill_nl > 0) { buf_pt_o = mbuf_goto_line(mbuf_o[larger_o], end_o[larger_o] - fill_nl + 1); } } for (ln = 0; ln < fill_nl; ln++) { const char *fill_pt; if (PANE_PREF(threepane).show_line_num == TRUE) { gtext_insert_buf(text, &fprop, fill_ln_str, threepane->ln_col_size[whichfile]); } fill_pt = mbuf_o[larger_o]->cur_pt; mbuf_next_line(mbuf_o[larger_o], 1); pos = gtext_insert_buf(text, &fprop, fill_pt, mbuf_o[larger_o]->cur_pt - fill_pt); } if (fill_nl > 0) dtmap_insert_displn(dtmap, DA_FILL, pos_i, pos - pos_i, buf_pt_o, mbuf_o[larger_o]->cur_pt - buf_pt_o, fill_nl); } gtext_thaw(text); } /** * hide_fill: **/ static void hide_fill(GdiffThreePane *threepane, WhichFile whichfile) { DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; DispLines *displn; gtext_freeze(text); for (displn = dtmap_first_displn(dtmap, DA_FILL); displn; displn = dtmap_next_displn(dtmap, DA_FILL)) { gtext_set_point(text, displn->pos); gtext_forward_delete(text, displn->len); dtmap_remove_displn(dtmap, displn); } gtext_thaw(text); } /** * guts_move_diff: * Find the difference(DiffLines), * and adjust text widget to display it near the center of the widget. **/ static void guts_move_diff(GdiffThreePane *threepane, MoveDiff mv_diff) { DiffFiles *dfiles = PANE_DFILES(threepane); DTextMap **dtmap = threepane->dtmap; GtkWidget **text = threepane->text; int cur_line1; const GList *dlines_node; const DiffLines *dlines = NULL; int n; if ((PANE_PREF(threepane).highlight == TRUE)/* revert the current one */ && (mv_diff != MOVED_CURRENT || mv_diff != MOVED_CUR_NOSCROLL)) { if (GDIFF_BASEPANE(threepane)->cur_dlines_node) { const DiffLines *dlines = GDIFF_BASEPANE(threepane)->cur_dlines_node->data; for (n = 0; n < 3; n++) { GdkColor *bg_color; bg_color = &PANE_PREF(threepane).diff_bg[n]; change_lines_bgcolor(threepane, dlines, n, bg_color); } } } if (mv_diff == MOVED_REL_NEXT) { /* Relative moves are special features. */ /* Driven by the first file's different position. */ cur_line1 = gtext_guess_visible_bottom_line(text[FIRST_FILE], dtmap[FIRST_FILE]->total_nl); dlines_node = dfiles_find_rel_nextl(dfiles, FIRST_FILE, cur_line1); dlines = dlines_node ? dlines_node->data : NULL; } else if (mv_diff == MOVED_REL_PREV) { cur_line1 = gtext_guess_visible_top_line(text[FIRST_FILE], dtmap[FIRST_FILE]->total_nl); dlines_node = dfiles_find_rel_prevl(dfiles, FIRST_FILE, cur_line1); dlines = dlines_node ? dlines_node->data : NULL; } else { /* findfn_table is defined in basepane-widget.c */ int i; for (i = 0; i < NUM_FTABLE; i++) { if (findfn_table[i].mv_diff == mv_diff) break; } g_assert(findfn_table[i].find_fn != NULL); /* find the node */ dlines_node = (findfn_table[i].find_fn)(dfiles, GDIFF_BASEPANE(threepane)->cur_dlines_node); dlines = dlines_node ? dlines_node->data : NULL; GDIFF_BASEPANE(threepane)->cur_dlines_node = dlines_node; } if (mv_diff != MOVED_CUR_NOSCROLL && dlines) { const FileInfo *fi[MAX_NUM_COMPARE_FILES]; /* Use local variables for readability */ int begin[MAX_NUM_COMPARE_FILES]; int tbegin[MAX_NUM_COMPARE_FILES]; int total_nl[MAX_NUM_COMPARE_FILES]; for (n = 0; n < 3; n++) { fi[n] = dfiles_get_fileinfo(dfiles, n, TRUE); begin[n] = dlines->between[n].begin; tbegin[n] = TEXT_LN(threepane, dtmap[n], begin[n]); total_nl[n] = TEXT_TOTAL_NL(threepane, dtmap[n], fi[n]); } if (PANE_PREF(threepane).line_wrap == TRUE) { gtext_gotoline_wrap(text[FIRST_FILE], tbegin[FIRST_FILE], total_nl[FIRST_FILE]); /* use the same value to synchronize each position */ if (PANE_PREF(threepane).show_fill) { for (n = 1; n < 3; n++) { gtext_gotoline_wrap(text[n], tbegin[FIRST_FILE], total_nl[n]); } } else { for (n = 1; n < 3; n++) { gtext_gotoline_wrap(text[n], tbegin[n], total_nl[n]); } } } else { gtext_gotoline_nonwrap(text[FIRST_FILE], tbegin[FIRST_FILE], total_nl[FIRST_FILE]); /* use the same value to synchronize each position */ if (PANE_PREF(threepane).show_fill) { for (n = 1; n < 3; n++) { gtext_gotoline_nonwrap(text[n], tbegin[FIRST_FILE], total_nl[n]); } } else { for (n = 1; n < 3; n++) { gtext_gotoline_nonwrap(text[n], tbegin[n], total_nl[n]); } } } } if (PANE_PREF(threepane).highlight == TRUE) { if (dlines) { for (n = 0; n < 3; n++) { GdkColor *bg_color; bg_color = &PANE_PREF(threepane).diff_hl[n]; change_lines_bgcolor(threepane, dlines, n, bg_color); } } } } /* Currently, used for highlight */ static void change_lines_bgcolor(GdiffThreePane *threepane, const DiffLines *dlines, WhichFile whichfile, GdkColor *bg_color) { DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; DispLines *displn = dtmap_lookup_by_bufln(dtmap, dlines->between[whichfile].begin); const char *buf_pt; GdkFont *font = text->style->font; FontProp fprop; GdkColor *fg_color = &PANE_PREF(threepane).diff_fg[whichfile]; fprop.font = font; fprop.fg = fg_color; fprop.bg = bg_color; if (displn && !(displn->attr & DA_HIDE)) { gtext_freeze(text); gtext_set_point(text, displn->pos); gtext_forward_delete(text, displn->len); buf_pt = mbuf_goto_top(MBUFFER(displn)); gtext_insert_buf(text, &fprop, buf_pt, mbuf_goto_bottom(MBUFFER(displn)) - buf_pt); if (PANE_PREF(threepane).show_line_num == TRUE) { int ln = dtmap_bufln_by_displn(dtmap, displn); LineFormat lformat; lformat.n_col = threepane->n_col[whichfile]; lformat.format = threepane->format_diff[whichfile]; mbuf_goto_top(MBUFFER(displn)); insert_remove_line_numbers(text, TRUE, displn->pos, &fprop, MBUFFER(displn), ln, ln + MBUFFER(displn)->nl, &lformat); } gtext_thaw(text); } } /** * text_click_cb: **/ static gint text_click_cb(GtkWidget *text, GdkEventButton *event, gpointer data) { GdiffThreePane *threepane = data; DiffFiles *dfiles = PANE_DFILES(threepane); WhichFile whichfile; DTextMap *dtmap; const GList *dlines_node; int index, tln, bln; if (event->button != 1) return FALSE; whichfile = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(text), WHICH_TEXT_KEY)); dtmap = GDIFF_THREEPANE(threepane)->dtmap[whichfile]; index = gtext_get_cursor_pos(text); tln = gtext_line_from_index(text, index); bln = BUF_LN(threepane, dtmap, tln); #ifdef DEBUG g_print("tln=%d, bln=%d\n", tln, bln); #endif dlines_node = dfiles_find_includel(dfiles, whichfile, bln); if (dlines_node == NULL && bln == 1) dlines_node = dfiles_find_rel_curl(dfiles, whichfile, 0);/* exception */ if (dlines_node) { if (GDIFF_BASEPANE(threepane)->cur_dlines_node == dlines_node) { /* selected lines are clicked => insert them Ugly. 'parent->parent->parent' depends on the mergeview's implementation. See create_panes() in mergeview.c. */ act_merge_insert_remove(GTK_WIDGET(threepane)->parent->parent->parent, whichfile, TRUE); } else { gtk_signal_emit_by_name(GTK_OBJECT(threepane), "select_dlines", whichfile, bln); } } return FALSE; } /** * guts_search_string: * More complicated than expected. * Note: * mbuf takes care of only line number. * So, after search in mbuf, I rescan the corresponding line in text widget. **/ static gboolean guts_search_string(GdiffThreePane *threepane, const char *string, WhichFile whichfile) { MBuffer *mbuf = PANE_MBUF(threepane, whichfile); DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; int lenb = strlen(string); int ln; /* line number in buf */ int tln; /* line number in text */ int cached_tindex; int cached_tln; int cached_tpoint; /* At first, search in the current line from the next index. */ cached_tln = threepane->search_tln[whichfile]; cached_tpoint = threepane->search_tpoint[whichfile]; cached_tindex = threepane->search_tindex[whichfile] + 1; if (gtext_search_string(text, string, lenb, cached_tln, cached_tln, &cached_tpoint, &cached_tindex) == TRUE) { threepane->search_tindex[whichfile] = cached_tindex;/* store for the next search */ centering_text(threepane, whichfile, cached_tln); return TRUE; } /* If not found in the current line, continue searching from the next line */ /* First, search in mbuf */ ln = threepane->search_ln[whichfile] + 1; ln = mbuf_search_string(mbuf, ln, string, lenb); threepane->search_ln[whichfile] = ln;/* store for the next search */ if (ln == 0) {/* not found */ gdk_beep(); return FALSE; } else { /* Found in mbuf, try to find it in text widget */ tln = TEXT_LN(threepane, dtmap, ln); cached_tindex = 0;/* dummy */ if (gtext_search_string(text, string, lenb, tln, cached_tln, &cached_tpoint, &cached_tindex) == FALSE) g_warning("guts_search_string wrong result."); /* store these for the next search */ threepane->search_tln[whichfile] = tln; threepane->search_tpoint[whichfile] = cached_tpoint; threepane->search_tindex[whichfile] = cached_tindex; centering_text(threepane, whichfile, tln); return TRUE; } } static void centering_text(GdiffThreePane *threepane, WhichFile whichfile, int tln) { DiffFiles *dfiles = PANE_DFILES(threepane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); DTextMap *dtmap = threepane->dtmap[whichfile]; GtkWidget *text = threepane->text[whichfile]; int total_nl; total_nl = TEXT_TOTAL_NL(threepane, dtmap, fi); if (PANE_PREF(threepane).line_wrap == TRUE) { gtext_gotoline_wrap(text, tln, total_nl); } else { gtext_gotoline_nonwrap(text, tln, total_nl); } }