/* * Two-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. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "diff.h" #include "gui.h" #include "dtextmap.h" #include "twopane-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 two text widgets share the same callback function. */ #define WHICH_TEXT_KEY "which" /* Private function declarations */ static void gdiff_twopane_class_init(GdiffTwoPaneClass *klass); static void gdiff_twopane_init(GdiffTwoPane *twopane); static void gdiff_twopane_finalize(GtkObject *object); static void gdiff_twopane_display(GdiffBasePane *basepane); static void gdiff_twopane_show_linenum(GdiffBasePane *basepane, gboolean to_show); static void gdiff_twopane_show_fill(GdiffBasePane *basepane, gboolean to_show); static gboolean gdiff_twopane_toggle_textwrap(GdiffBasePane *basepane); static void gdiff_twopane_set_highlight(GdiffBasePane *basepane, gboolean to_highlight); static void gdiff_twopane_move_diff(GdiffBasePane *basepane, MoveDiff mv_diff); static void gdiff_twopane_select_dlines(GdiffBasePane *basepane, WhichFile whichfile, int ln); static gboolean gdiff_twopane_search_string(GdiffBasePane *basepane, const char *string, WhichFile whichfile); static void guts_twopane_display(GdiffTwoPane *twopane); static void draw_text(GdiffTwoPane *twopane, WhichFile whichfile); static void show_hide_numbers(GdiffTwoPane *twopane, WhichFile whichfile, gboolean b_show); static void calc_ln_columns(GdiffTwoPane *twopane, WhichFile whichfile, int nlines); static void show_fill(GdiffTwoPane *twopane, WhichFile whichfile); static void hide_fill(GdiffTwoPane *twopane, WhichFile whichfile); static void guts_move_diff(GdiffTwoPane *twopane, MoveDiff mv_diff); static void change_lines_bgcolor(GdiffTwoPane *twopane, const DiffLines *dlines, WhichFile whichfile, GdkColor *bg_color); static gint text_click_cb(GtkWidget *text, GdkEventButton *event, gpointer data); static gboolean guts_search_string(GdiffTwoPane *twopane, const char *string, WhichFile whichfile); static void centering_text(GdiffTwoPane *twopane, WhichFile whichfile, int tln); /* Macros to avoid useless dtmap_map function */ #define TEXT_LN(twopane, dtmap, bln) ((PANE_PREF(twopane).show_fill == TRUE) ? dtmap_map_b2t(dtmap, bln) : bln) #define TEXT_TOTAL_NL(twopane, dtmap, fi) ((PANE_PREF(twopane).show_fill == TRUE) ? dtmap->total_nl : fi->nlines) #define BUF_LN(twopane, dtmap, tln) ((PANE_PREF(twopane).show_fill == TRUE) ? dtmap_map_t2b(dtmap, tln) : tln) static GdiffBasePaneClass *parent_class = NULL; GtkType gdiff_twopane_get_type(void) { static GtkType twopane_type = 0; if (!twopane_type) { static const GtkTypeInfo twopane_info = { "GdiffTwoPane", sizeof(GdiffTwoPane), sizeof(GdiffTwoPaneClass), (GtkClassInitFunc)gdiff_twopane_class_init, (GtkObjectInitFunc)gdiff_twopane_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; twopane_type = gtk_type_unique(GDIFF_TYPE_BASEPANE, &twopane_info); } return twopane_type; } static void gdiff_twopane_class_init(GdiffTwoPaneClass *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_twopane_finalize; basepane_class->display = gdiff_twopane_display; basepane_class->show_linenum = gdiff_twopane_show_linenum; basepane_class->show_fill = gdiff_twopane_show_fill; basepane_class->toggle_textwrap = gdiff_twopane_toggle_textwrap; basepane_class->set_highlight = gdiff_twopane_set_highlight; basepane_class->move_diff = gdiff_twopane_move_diff; basepane_class->select_dlines = gdiff_twopane_select_dlines; basepane_class->search_string = gdiff_twopane_search_string; } static void gdiff_twopane_init(GdiffTwoPane *twopane) { GdiffBasePane *basepane; GtkBin *bin; GtkWidget *hpaned; int n; basepane = GDIFF_BASEPANE(twopane); bin = GTK_BIN(basepane); hpaned = gtk_hpaned_new(); gtk_container_add(GTK_CONTAINER(bin), hpaned); gtk_widget_show(hpaned); for (n = 0; n < 2; 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(hpaned), scrollwin, TRUE, TRUE); else if (n == SECOND_FILE) gtk_paned_pack2(GTK_PANED(hpaned), scrollwin, TRUE, TRUE); else g_assert_not_reached(); gtk_widget_show(scrollwin); text = gtk_text_new(NULL, NULL); twopane->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(twopane).line_wrap); gtk_container_add(GTK_CONTAINER(scrollwin), text); gtk_widget_show(text); twopane->search_ln[n] = 0; twopane->search_tln[n] = 0; twopane->search_tpoint[n] = 0; twopane->search_tindex[n] = 0; } PANE_PREF(twopane).view_type = MULTIPANE2_VIEW; } static void gdiff_twopane_finalize(GtkObject *object) { GdiffTwoPane *twopane; int n; g_return_if_fail(object != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(object)); twopane = GDIFF_TWOPANE(object); for (n = 0; n < 2; n++) { dtmap_delete(twopane->dtmap[n]); } (*GTK_OBJECT_CLASS(parent_class)->finalize)(object); } GtkWidget* gdiff_twopane_new(DiffDir *diffdir, DiffFiles *dfiles) { GdiffTwoPane *twopane; int n; twopane = gtk_type_new(GDIFF_TYPE_TWOPANE); _gdiff_basepane_set_backend(GDIFF_BASEPANE(twopane), diffdir, dfiles);/*XXX*/ /* Two-pane internal data */ for (n = 0; n < 2; n++) { const FileInfo *fi = dfiles_get_fileinfo(dfiles, n, TRUE); twopane->dtmap[n] = dtmap_new(fi->nlines); twopane->n_col[n] = -1; /* workaround */ gtk_widget_ensure_style(twopane->text[n]); } guts_twopane_display(twopane); for (n = 0; n < 2; n++) { gtk_object_set_data(GTK_OBJECT(twopane->text[n]), WHICH_TEXT_KEY, GINT_TO_POINTER(n)); gtk_signal_connect_after(GTK_OBJECT(twopane->text[n]), "button_press_event", GTK_SIGNAL_FUNC(text_click_cb), twopane); } return GTK_WIDGET(twopane); } /** Interfaces **/ static void gdiff_twopane_display(GdiffBasePane *basepane) { GdiffTwoPane *twopane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); guts_twopane_display(twopane); } /** * gdiff_twopane_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_twopane_show_linenum(GdiffBasePane *basepane, gboolean to_show) { GdiffTwoPane *twopane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); if (to_show != PANE_PREF(twopane).show_line_num) { int n; for (n = 0; n < 2; n++) { show_hide_numbers(twopane, n, to_show); } PANE_PREF(twopane).show_line_num = to_show; } } /** * gdiff_twopane_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_twopane_show_fill(GdiffBasePane *basepane, gboolean to_show) { GdiffTwoPane *twopane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); if (to_show != PANE_PREF(twopane).show_fill) { int n; for (n = 0; n < 2; n++) { if (to_show == TRUE) show_fill(twopane, n); else hide_fill(twopane, n); } PANE_PREF(twopane).show_fill = to_show; } } /** * gdiff_twopane_toggle_textwrap: * Return TRUE if it is wrapped. **/ static gboolean gdiff_twopane_toggle_textwrap(GdiffBasePane *basepane) { GdiffTwoPane *twopane; gboolean b_wrap; int n; g_return_val_if_fail(basepane != NULL, FALSE); g_return_val_if_fail(GDIFF_IS_TWOPANE(basepane), FALSE); twopane = GDIFF_TWOPANE(basepane); b_wrap = !(GTK_TEXT(twopane->text[FIRST_FILE])->line_wrap); for (n = 0; n < 2; n++) { gtext_set_line_wrap(twopane->text[n], b_wrap); } PANE_PREF(twopane).line_wrap = b_wrap; return b_wrap; } /** * gdiff_twopane_set_highlight: * Take care of the current highlight. * Input: * gboolean to_highlight; TRUE implies to enable highlight. FALSE implies to disable. **/ static void gdiff_twopane_set_highlight(GdiffBasePane *basepane, gboolean to_highlight) { GdiffTwoPane *twopane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); if (to_highlight != PANE_PREF(twopane).highlight) { if (GDIFF_BASEPANE(twopane)->cur_dlines_node) { int n; const DiffLines *dlines = GDIFF_BASEPANE(twopane)->cur_dlines_node->data; for (n = 0; n < 2; n++) { GdkColor *bg_color; if (to_highlight == TRUE) { bg_color = &PANE_PREF(twopane).diff_hl[n]; change_lines_bgcolor(twopane, dlines, n, bg_color); } else { bg_color = &PANE_PREF(twopane).diff_bg[n]; change_lines_bgcolor(twopane, dlines, n, bg_color); } } } PANE_PREF(twopane).highlight = to_highlight; } } /** * gdiff_twopane_move_diff: * Move to a difference, such as next, previous, first or last. **/ static void gdiff_twopane_move_diff(GdiffBasePane *basepane, MoveDiff mv_diff) { GdiffTwoPane *twopane; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); guts_move_diff(twopane, mv_diff); } /** * gdiff_twopane_select_dlines: * Typically, called when a user click a button on text widget. **/ static void gdiff_twopane_select_dlines(GdiffBasePane *basepane, WhichFile whichfile, int ln) { GdiffTwoPane *twopane; DiffFiles *dfiles; const GList *dlines_node; g_return_if_fail(basepane != NULL); g_return_if_fail(GDIFF_IS_TWOPANE(basepane)); twopane = GDIFF_TWOPANE(basepane); dfiles = PANE_DFILES(twopane); 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(twopane).highlight == TRUE) {/* revert the current one */ GdkColor *bg_color; if (GDIFF_BASEPANE(twopane)->cur_dlines_node) { const DiffLines *dlines = GDIFF_BASEPANE(twopane)->cur_dlines_node->data; bg_color = &PANE_PREF(twopane).diff_bg[FIRST_FILE]; change_lines_bgcolor(twopane, dlines, FIRST_FILE, bg_color); bg_color = &PANE_PREF(twopane).diff_bg[SECOND_FILE]; change_lines_bgcolor(twopane, dlines, SECOND_FILE, bg_color); } } GDIFF_BASEPANE(twopane)->cur_dlines_node = dlines_node; gtk_signal_emit_by_name(GTK_OBJECT(twopane), "move_diff", MOVED_CUR_NOSCROLL); } } /** * gdiff_twopane_search_string: * @string is null-byte-terminated. Return TRUE if found. **/ static gboolean gdiff_twopane_search_string(GdiffBasePane *basepane, const char *string, WhichFile whichfile) { GdiffTwoPane *twopane; g_return_val_if_fail(basepane != NULL, FALSE); g_return_val_if_fail(GDIFF_IS_TWOPANE(basepane), FALSE); if (string == NULL || string[0] == '\0') return FALSE; twopane = GDIFF_TWOPANE(basepane); return guts_search_string(twopane, string, whichfile); } /** Internal functions **/ /** * guts_twopane_display: * Show the diff result in two-pane mode. **/ static void guts_twopane_display(GdiffTwoPane *twopane) { int n; for (n = 0; n < 2; n++) { draw_text(twopane, n); if (PANE_PREF(twopane).show_fill == TRUE) { show_fill(twopane, n); } if (PANE_PREF(twopane).show_line_num == TRUE) { show_hide_numbers(twopane, n, TRUE); } } } /* difftype condition macros */ /* Case: All different */ #define IS_CHANGE_DT(dtype, whichfile) \ (dtype & CHANGE) /* 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) \ (((whichfile == FIRST_FILE) && (dtype & F1ONLY)) \ || ((whichfile == SECOND_FILE) && (dtype & F2ONLY))) /* Case: Only another file has additional portions */ #define IS_O_ONLY_DT(dtype, whichfile) \ (((whichfile == FIRST_FILE) && (dtype & F2ONLY)) \ || ((whichfile == SECOND_FILE) && (dtype & F1ONLY))) /** * draw_text: * Draw text with coloring different parts. * During drawing, update dtmap. **/ static void draw_text(GdiffTwoPane *twopane, WhichFile whichfile) { DiffFiles *dfiles = PANE_DFILES(twopane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); MBuffer *mbuf = PANE_MBUF(twopane, whichfile); DTextMap *dtmap = twopane->dtmap[whichfile]; const char *buf_pt; int buf_ln; int buf_lenb; GtkWidget *text = twopane->text[whichfile]; GdkFont *font = text->style->font; GList *node;/* node of DiffLines list */ FontProp fprop; GdkColor *fg_color = &PANE_PREF(twopane).diff_fg[whichfile]; GdkColor *bg_color = &PANE_PREF(twopane).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)) { 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(GdiffTwoPane *twopane, WhichFile whichfile, gboolean b_show) { DiffFiles *dfiles = PANE_DFILES(twopane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->text[whichfile]; GdkFont *font = text->style->font; FontProp fprop; GdkColor *fg_color = &PANE_PREF(twopane).diff_fg[whichfile]; GdkColor *bg_color = &PANE_PREF(twopane).diff_bg[whichfile]; GdkColor *fill_color = &PANE_PREF(twopane).diff_fill;/* fill parts */ LineFormat lformat; DispLines *displn; int ln = 1; if (fi->buf == NULL) /* Maybe, the file has been deleted. */ return; if (twopane->n_col[whichfile] < 0) calc_ln_columns(twopane, whichfile, fi->nlines); lformat.n_col = twopane->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 = twopane->format_common; } else if (displn->attr & DA_DIFF) { fprop.fg = fg_color; fprop.bg = bg_color; lformat.format = twopane->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 * twopane->ln_col_size[whichfile]); } else { dtmap_dec_displn(dtmap, displn, MBUFFER(displn)->nl * twopane->ln_col_size[whichfile]); } } gtext_thaw(text); } /* calculate column size of line numbers, and store it */ static void calc_ln_columns(GdiffTwoPane *twopane, WhichFile whichfile, int nlines) { int n_col; n_col = calc_number_places(nlines); twopane->n_col[whichfile] = n_col; g_snprintf(twopane->format_common, sizeof(twopane->format_common), "%%%dd%s", n_col, MARK_COMMON);/* e.g. "%4d " */ if (whichfile == FIRST_FILE) g_snprintf(twopane->format_diff[whichfile], sizeof(twopane->format_diff[whichfile]), "%%%dd%s", n_col, MARK_FILE1);/* e.g. "%4d< " */ else g_snprintf(twopane->format_diff[whichfile], sizeof(twopane->format_diff[whichfile]), "%%%dd%s", n_col, MARK_FILE2);/* e.g. "%4d> " */ /* To keep the column size */ twopane->ln_col_size[whichfile] = n_col + MARK_LENGTH; } /** * show_fill: **/ static void show_fill(GdiffTwoPane *twopane, WhichFile whichfile) { DiffFiles *dfiles = PANE_DFILES(twopane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); WhichFile otherfile = (whichfile == FIRST_FILE ? SECOND_FILE : FIRST_FILE);/*XXX*/ MBuffer *mbuf = PANE_MBUF(twopane, whichfile); MBuffer *mbuf_o = PANE_MBUF(twopane, otherfile); DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->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(twopane).diff_fill; static const char fill_ln_str[] = " ";/* fill parts corresponding to line numbers */ if (fi->buf == NULL) /* Maybe, the file has been deleted. */ return; g_assert(twopane->ln_col_size[whichfile] < sizeof(fill_ln_str)-1); fprop.font = font; fprop.fg = fprop.bg = fill_color; mbuf_goto_top(mbuf); mbuf_goto_top(mbuf_o); 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 = dlines->between[otherfile].begin; int end_o = dlines->between[otherfile].end; const DispLines *displn; const char *buf_pt_o = NULL; int pos_i; /* position to insert */ int fill_nl = 0; int ln; /* 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 (IS_O_ONLY_DT(dlines->difftype, whichfile)) { buf_pt_o = mbuf_goto_line(mbuf_o, begin_o); fill_nl = end_o + 1 - begin_o; } else if (IS_CHANGE_DT(dlines->difftype, whichfile)) { fill_nl = (end_o - begin_o) - (end - begin); if (fill_nl > 0) { buf_pt_o = mbuf_goto_line(mbuf_o, end_o - fill_nl + 1); } } for (ln = 0; ln < fill_nl; ln++) { const char *fill_pt; if (PANE_PREF(twopane).show_line_num == TRUE) { gtext_insert_buf(text, &fprop, fill_ln_str, twopane->ln_col_size[whichfile]); } fill_pt = mbuf_o->cur_pt; mbuf_next_line(mbuf_o, 1); pos = gtext_insert_buf(text, &fprop, fill_pt, mbuf_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->cur_pt - buf_pt_o, fill_nl); } gtext_thaw(text); } /** * hide_fill: **/ static void hide_fill(GdiffTwoPane *twopane, WhichFile whichfile) { DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->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(GdiffTwoPane *twopane, MoveDiff mv_diff) { DiffFiles *dfiles = PANE_DFILES(twopane); DTextMap **dtmap = twopane->dtmap; GtkWidget **text = twopane->text; int cur_line1; const GList *dlines_node; const DiffLines *dlines = NULL; if ((PANE_PREF(twopane).highlight == TRUE)/* revert the current one */ && (mv_diff != MOVED_CURRENT || mv_diff != MOVED_CUR_NOSCROLL)) { if (GDIFF_BASEPANE(twopane)->cur_dlines_node) { GdkColor *bg_color; const DiffLines *dlines = GDIFF_BASEPANE(twopane)->cur_dlines_node->data; bg_color = &PANE_PREF(twopane).diff_bg[FIRST_FILE]; change_lines_bgcolor(twopane, dlines, FIRST_FILE, bg_color); bg_color = &PANE_PREF(twopane).diff_bg[SECOND_FILE]; change_lines_bgcolor(twopane, dlines, SECOND_FILE, 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(twopane)->cur_dlines_node); dlines = dlines_node ? dlines_node->data : NULL; GDIFF_BASEPANE(twopane)->cur_dlines_node = dlines_node; } if (mv_diff != MOVED_CUR_NOSCROLL && dlines) { const FileInfo *fi1 = dfiles_get_fileinfo(dfiles, FIRST_FILE, TRUE); const FileInfo *fi2 = dfiles_get_fileinfo(dfiles, SECOND_FILE, TRUE); /* Use local variables for readability */ int begin1 = dlines->between[FIRST_FILE].begin; int begin2 = dlines->between[SECOND_FILE].begin; int tbegin1 = TEXT_LN(twopane, dtmap[FIRST_FILE], begin1); int tbegin2 = TEXT_LN(twopane, dtmap[SECOND_FILE], begin2); int total_nl1 = TEXT_TOTAL_NL(twopane, dtmap[FIRST_FILE], fi1); int total_nl2 = TEXT_TOTAL_NL(twopane, dtmap[SECOND_FILE], fi2); if (PANE_PREF(twopane).line_wrap == TRUE) { gtext_gotoline_wrap(text[FIRST_FILE], tbegin1, total_nl1); /* use the same value to synchronize each position */ if (PANE_PREF(twopane).show_fill) gtext_gotoline_wrap(text[SECOND_FILE], tbegin1, total_nl2); else gtext_gotoline_wrap(text[SECOND_FILE], tbegin2, total_nl2); } else { gtext_gotoline_nonwrap(text[FIRST_FILE], tbegin1, total_nl1); /* use the same value to synchronize each position */ if (PANE_PREF(twopane).show_fill) gtext_gotoline_nonwrap(text[SECOND_FILE], tbegin1, total_nl2); else gtext_gotoline_nonwrap(text[SECOND_FILE], tbegin2, total_nl2); } } if (PANE_PREF(twopane).highlight == TRUE) { GdkColor *bg_color; if (dlines) { bg_color = &PANE_PREF(twopane).diff_hl[FIRST_FILE]; change_lines_bgcolor(twopane, dlines, FIRST_FILE, bg_color); bg_color = &PANE_PREF(twopane).diff_hl[SECOND_FILE]; change_lines_bgcolor(twopane, dlines, SECOND_FILE, bg_color); } } } /* Currently, used for highlight */ static void change_lines_bgcolor(GdiffTwoPane *twopane, const DiffLines *dlines, WhichFile whichfile, GdkColor *bg_color) { DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->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(twopane).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(twopane).show_line_num == TRUE) { int ln = dtmap_bufln_by_displn(dtmap, displn); LineFormat lformat; lformat.n_col = twopane->n_col[whichfile]; lformat.format = twopane->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) { GdiffTwoPane *twopane = data; DiffFiles *dfiles = PANE_DFILES(twopane); 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_TWOPANE(twopane)->dtmap[whichfile]; index = gtext_get_cursor_pos(text); tln = gtext_line_from_index(text, index); bln = BUF_LN(twopane, 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(twopane)->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(twopane)->parent->parent->parent, whichfile, TRUE); } else { gtk_signal_emit_by_name(GTK_OBJECT(twopane), "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(GdiffTwoPane *twopane, const char *string, WhichFile whichfile) { MBuffer *mbuf = PANE_MBUF(twopane, whichfile); DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->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 = twopane->search_tln[whichfile]; cached_tpoint = twopane->search_tpoint[whichfile]; cached_tindex = twopane->search_tindex[whichfile] + 1; if (gtext_search_string(text, string, lenb, cached_tln, cached_tln, &cached_tpoint, &cached_tindex) == TRUE) { twopane->search_tindex[whichfile] = cached_tindex;/* store for the next search */ centering_text(twopane, whichfile, cached_tln); return TRUE; } /* If not found in the current line, continue searching from the next line */ /* First, search in mbuf */ ln = twopane->search_ln[whichfile] + 1; ln = mbuf_search_string(mbuf, ln, string, lenb); twopane->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(twopane, 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 */ twopane->search_tln[whichfile] = tln; twopane->search_tpoint[whichfile] = cached_tpoint; twopane->search_tindex[whichfile] = cached_tindex; centering_text(twopane, whichfile, tln); return TRUE; } } static void centering_text(GdiffTwoPane *twopane, WhichFile whichfile, int tln) { DiffFiles *dfiles = PANE_DFILES(twopane); const FileInfo *fi = dfiles_get_fileinfo(dfiles, whichfile, TRUE); DTextMap *dtmap = twopane->dtmap[whichfile]; GtkWidget *text = twopane->text[whichfile]; int total_nl; total_nl = TEXT_TOTAL_NL(twopane, dtmap, fi); if (PANE_PREF(twopane).line_wrap == TRUE) { gtext_gotoline_wrap(text, tln, total_nl); } else { gtext_gotoline_nonwrap(text, tln, total_nl); } }