/* * Directory view widget module * * Copyright INOUE Seiichiro , licensed under the GPL. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "diff.h" #include "misc.h" #include "gui.h" #include "dirview.h" #include "hide.h" /* Constant numbers */ /* Column definitions */ enum { COL_FILENAME1 = 0, COL_FILENAME2 = 1, COL_TYPE = 2, COL_NLINES1 = 3, COL_NLINES2 = 4 }; /* Private data structure definitions */ /* used for CList sort */ typedef struct { char *title; GtkCListCompareFunc cmp_func; /* sort */ } DirViewCList; /* Row hide filter functions */ static const struct { RowHideMask rh_mask; RowHideFunc rh_func; } rh_func_table[] = { { ROW_HIDE_EMACS, hide_emacs_backup }, { ROW_HIDE_OBJ, hide_obj_file }, }; #define NUM_RH_FUNCS (sizeof(rh_func_table) / sizeof(rh_func_table[0])) /* signals */ enum { SELECT_FILE, SCROLLUP, SCROLLDOWN, LAST_SIGNAL }; /* Private function declarations */ static void gdiff_dirview_class_init(GdiffDirViewClass *klass); static void gdiff_dirview_init(GdiffDirView *dirview); static void gdiff_dirview_destroy(GtkObject *object); static void gdiff_dirview_scrollup(GdiffDirView *dirview); static void gdiff_dirview_scrolldown(GdiffDirView *dirview); static void draw_clist(GdiffDirView *dirview); static gboolean check_row_hide(GdiffDirView *dirview, const char *fname); static void select_row_invoke_sig(GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data); static void select_row_search_helper(GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data); static void click_column(GtkCList *clist, gint column, gpointer data); static gint number_compare(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2); static gboolean guts_search_string(GdiffDirView *dirview, const char *string, WhichFile whichfile); static void guts_checksum(GdiffDirView *dirview); /* Private variables */ static const DirViewCList dview_clist[] = { {N_("file name(from)"), NULL}, {N_("file name(to)"), NULL}, {N_("diff type"), NULL}, {N_("lines(from)"), number_compare}, {N_("lines(to)"), number_compare}, }; #define NUM_DVIEW_COLUMNS (sizeof(dview_clist) / sizeof(dview_clist[0])) static GtkScrolledWindowClass *parent_class = NULL; static guint dirview_signals[LAST_SIGNAL] = { 0 }; GtkType gdiff_dirview_get_type(void) { static GtkType dirview_type = 0; if (!dirview_type) { static const GtkTypeInfo dirview_info = { "GdiffDirView", sizeof(GdiffDirView), sizeof(GdiffDirViewClass), (GtkClassInitFunc)gdiff_dirview_class_init, (GtkObjectInitFunc)gdiff_dirview_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc)NULL, }; dirview_type = gtk_type_unique(GTK_TYPE_SCROLLED_WINDOW, &dirview_info); } return dirview_type; } static void gdiff_dirview_class_init(GdiffDirViewClass *klass) { GtkObjectClass *object_class; object_class = (GtkObjectClass*)klass; parent_class = gtk_type_class(GTK_TYPE_SCROLLED_WINDOW); dirview_signals[SELECT_FILE] = gtk_signal_new("select_file", GTK_RUN_LAST, object_class->type, GTK_SIGNAL_OFFSET(GdiffDirViewClass, select_file), gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1, GTK_TYPE_POINTER); dirview_signals[SCROLLUP] = gtk_signal_new("scrollup", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffDirViewClass, scrollup), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); dirview_signals[SCROLLDOWN] = gtk_signal_new("scrolldown", GTK_RUN_FIRST, object_class->type, GTK_SIGNAL_OFFSET(GdiffDirViewClass, scrolldown), gtk_marshal_NONE__NONE, GTK_TYPE_NONE, 0); gtk_object_class_add_signals(object_class, dirview_signals, LAST_SIGNAL); object_class->destroy = gdiff_dirview_destroy; klass->select_file = NULL; klass->scrollup = gdiff_dirview_scrollup; klass->scrolldown = gdiff_dirview_scrolldown; } static void gdiff_dirview_init(GdiffDirView *dirview) { GtkScrolledWindow *scrollwin; GtkWidget *clist; int n; char *titles[NUM_DVIEW_COLUMNS]; scrollwin = GTK_SCROLLED_WINDOW(dirview); /* XXX: Is this a proper way? */ gtk_scrolled_window_set_hadjustment(scrollwin, NULL); gtk_scrolled_window_set_vadjustment(scrollwin, NULL); gtk_object_constructed(GTK_OBJECT(scrollwin));; for (n = 0; n < NUM_DVIEW_COLUMNS; n++) { titles[n] = _(dview_clist[n].title); } clist = gtk_clist_new_with_titles(NUM_DVIEW_COLUMNS, titles); dirview->clist = GTK_CLIST(clist); gtk_clist_column_titles_active(GTK_CLIST(clist)); gtk_signal_connect(GTK_OBJECT(clist), "select_row", GTK_SIGNAL_FUNC(select_row_invoke_sig), dirview); gtk_signal_connect(GTK_OBJECT(clist), "select_row", GTK_SIGNAL_FUNC(select_row_search_helper), dirview); gtk_signal_connect(GTK_OBJECT(clist), "click_column", GTK_SIGNAL_FUNC(click_column), dirview); gtk_container_add(GTK_CONTAINER(scrollwin), clist); gtk_widget_grab_focus(clist); gtk_widget_show(clist); dirview->diffdir = NULL; for (n = 0; n < MAX_NUM_COMPARE_FILES; n++) { dirview->dirname[n] = NULL; } dirview->b_dirty = FALSE; dirview->cur_selected_row = -1;/* kludge: before zero */ dirview->pref = g_pref.dvpref;/* copy */ dirview->gdwin = NULL; } GtkWidget* gdiff_dirview_new(DiffDir *diffdir) { GdiffDirView *dirview; DiffFiles *dfiles; int n; dirview = gtk_type_new(GDIFF_TYPE_DIRVIEW); dirview->diffdir = diffdir; diffdir_ref(diffdir);/* own it */ /* the first node has directory info */ dfiles = g_slist_nth_data(dirview->diffdir->dfiles_list, 0); g_assert(dfiles_get_status(dfiles) == DIRECTORIES); for (n = 0; n < GDIFF_DIRVIEW_NUM_DIRS(dirview); n++) { const FileInfo *fi; fi = dfiles_get_fileinfo(dfiles, n, FALSE); dirview->dirname[n] = g_strdup(fi->fname); } draw_clist(dirview); return GTK_WIDGET(dirview); } static void gdiff_dirview_destroy(GtkObject *object) { GdiffDirView *dirview; int n; g_return_if_fail(object != NULL); g_return_if_fail(GDIFF_IS_DIRVIEW(object)); dirview = GDIFF_DIRVIEW(object); gtk_signal_disconnect_by_data(GTK_OBJECT(dirview->clist), dirview); for (n = 0; n < GDIFF_DIRVIEW_NUM_DIRS(dirview); n++) { g_free(dirview->dirname[n]); } diffdir_unref(dirview->diffdir);/* leave it */ GTK_OBJECT_CLASS(parent_class)->destroy(object); } /** Interfaces **/ static void gdiff_dirview_scrollup(GdiffDirView *dirview) { GtkWidget *scrollwin = GTK_WIDGET(dirview->clist)->parent; GtkWidget *vs = GTK_SCROLLED_WINDOW(scrollwin)->vscrollbar; 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_dirview_scrolldown(GdiffDirView *dirview) { GtkWidget *scrollwin = GTK_WIDGET(dirview->clist)->parent; GtkWidget *vs = GTK_SCROLLED_WINDOW(scrollwin)->vscrollbar; 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"); } } /** * gdiff_dirview_redisplay: **/ void gdiff_dirview_redisplay(GdiffDirView *dirview) { GtkCList *clist; g_return_if_fail(dirview != NULL); g_return_if_fail(GDIFF_IS_DIRVIEW(dirview)); clist = dirview->clist; gtk_clist_freeze(clist); gtk_clist_clear(clist); gtk_clist_thaw(clist); draw_clist(dirview); dirview->b_dirty = FALSE; } /** * gdiff_dirview_adjust_column: * Adjust column sizes. **/ void gdiff_dirview_adjust_column(GdiffDirView *dirview, int total_width) { GtkCList *clist; g_return_if_fail(dirview != NULL); g_return_if_fail(GDIFF_IS_DIRVIEW(dirview)); clist = dirview->clist; /* XXX: a very heuristic way to calculate columns size. Try to assign wider areas to file name columns. */ gtk_clist_set_column_width(clist, COL_FILENAME1, total_width/3); gtk_clist_set_column_width(clist, COL_FILENAME2, total_width/3); gtk_clist_set_column_width(clist, COL_NLINES1, 30); gtk_clist_set_column_width(clist, COL_NLINES2, 30); } /** * gdiff_dirview_search_string: * @string is null-byte-terminated. * Search @string in GtkCList column's text. Return TRUE if found. **/ gboolean gdiff_dirview_search_string(GdiffDirView *dirview, const char *string, WhichFile whichfile) { GtkCList *clist; g_return_val_if_fail(dirview != NULL, FALSE); g_return_val_if_fail(GDIFF_IS_DIRVIEW(dirview), FALSE); if (string == NULL || string[0] == '\0') return FALSE; clist = dirview->clist; return guts_search_string(dirview, string, whichfile); } /** * gdiff_dirview_checksum: * Calculate check sum of selected files and show the result on status bar. **/ void gdiff_dirview_checksum(GdiffDirView *dirview) { g_return_if_fail(dirview != NULL); g_return_if_fail(GDIFF_IS_DIRVIEW(dirview)); guts_checksum(dirview); } /** Internal functions **/ /** * draw_clist: * Draw directory view's CList, * using the information from back-end data(DiffDir *diffdir). **/ static void draw_clist(GdiffDirView *dirview) { static const char CAPTION_DIFFERENT[] = N_("Different"); static const char CAPTION_ONLYONE[] = N_("Only"); static const char CAPTION_BINARY[] = N_("Binary(different)"); static const char CAPTION_IDENTICAL[] = N_("Identical"); const DiffDir *diffdir = dirview->diffdir; GSList *list; GtkCList *clist = dirview->clist; int key = 1;/* when a row is clicked, this is used to access the internal data. */ int row = 0; int n; int dirlen[MAX_NUM_COMPARE_FILES];/* length of dirname */ for (n = 0; n < GDIFF_DIRVIEW_NUM_DIRS(dirview); n++) { int len = strlen(dirview->dirname[n]); if (len && dirview->dirname[n][len-1] != '/') len++; dirlen[n] = len; } gtk_clist_freeze(clist); /* Ignore the first node, which has directory names. */ for (list = diffdir->dfiles_list->next; list; list = list->next, key++) { DiffFiles *dfiles = list->data; char *rowtext[NUM_DVIEW_COLUMNS]; FilesStatus fsst; int n; char buf1[32]; char buf2[32]; const FileInfo *fi1 = dfiles_get_fileinfo(dfiles, FIRST_FILE, FALSE); const FileInfo *fi2 = dfiles_get_fileinfo(dfiles, SECOND_FILE, FALSE); fsst = dfiles_get_status(dfiles); /* Filter to hide row */ if (fsst & dirview->pref.row_hide_stat_mask) continue; if (dirview->pref.row_hide_func_mask && fi1->fname && fi1->fname[0]) { if (check_row_hide(dirview, fi1->fname) == TRUE) continue; } if (dirview->pref.row_hide_func_mask && fi2->fname && fi2->fname[0]) { if (check_row_hide(dirview, fi2->fname) == TRUE) continue; } switch (fsst) { case DIRECTORIES: g_warning("XXX: weird in display_dirs %d\n", fsst); continue; case IDENTICAL_FILES: rowtext[COL_TYPE] = _(CAPTION_IDENTICAL); break; case BINARY_FILES: rowtext[COL_TYPE] = _(CAPTION_BINARY); break; case ONLY_FILE1_EXISTS: case ONLY_FILE2_EXISTS: rowtext[COL_TYPE] = _(CAPTION_ONLYONE); break; case DIFFERENT_FILES: rowtext[COL_TYPE] = _(CAPTION_DIFFERENT); break; } switch (dirview->pref.show_path) { case SHOW_PATH_FILE: rowtext[COL_FILENAME1] = get_file_name(fi1->fname); rowtext[COL_FILENAME2] = get_file_name(fi2->fname); break; case SHOW_PATH_ORIG: rowtext[COL_FILENAME1] = fi1->fname; rowtext[COL_FILENAME2] = fi2->fname; break; case SHOW_PATH_REL: rowtext[COL_FILENAME1] = get_rel_file_name(fi1->fname, dirlen[0]); rowtext[COL_FILENAME2] = get_rel_file_name(fi2->fname, dirlen[1]); break; } n = dfiles_calc_total_nlines(dfiles, FIRST_FILE); g_snprintf(buf1, sizeof(buf1), "%d", n); rowtext[COL_NLINES1] = buf1; n = dfiles_calc_total_nlines(dfiles, SECOND_FILE); g_snprintf(buf2, sizeof(buf2), "%d", n); rowtext[COL_NLINES2] = buf2; gtk_clist_append(clist, rowtext); gtk_clist_set_row_data(GTK_CLIST(clist), row, GINT_TO_POINTER(key)); row++; } gtk_clist_thaw(clist); } /** * check_row_hide: * Using RowHideFunc funtions, check whether the row should be hidden. * See hide.[ch] about the details. * Input: * const char *fname; file name of the row. * Output: * Return value; TRUE if it should be hidden. **/ static gboolean check_row_hide(GdiffDirView *dirview, const char *fname) { int i; RowHideMask rh_mask = dirview->pref.row_hide_func_mask; for (i = 0; i < NUM_RH_FUNCS; i++) { if (rh_func_table[i].rh_mask & rh_mask) { RowHideFunc rh_func = rh_func_table[i].rh_func; if (rh_func(fname) == TRUE) { return TRUE; } } } return FALSE; } /** * select_row_invoke_sig: * Called when a user select a row on directory view. * Emit "select_file" signal. **/ static void select_row_invoke_sig(GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data) { GdiffDirView *dirview = data; DiffDir *diffdir = dirview->diffdir; DiffFiles *dfiles; int key; key = GPOINTER_TO_INT(gtk_clist_get_row_data(clist, row)); dfiles = g_slist_nth_data(diffdir->dfiles_list, key); gtk_signal_emit(GTK_OBJECT(dirview), dirview_signals[SELECT_FILE], dfiles); } /** * select_row_search_helper: * Called when a user select a row on directory view. * Update the internal variable, @cur_selected_row for search feature.. **/ static void select_row_search_helper(GtkCList *clist, gint row, gint column, GdkEvent *event, gpointer data) { GdiffDirView *dirview = data; dirview->cur_selected_row = row; } /** * click_column: * Called when a user clicks a title to sort CList. **/ static void click_column(GtkCList *clist, gint column, gpointer data) { if (column == clist->sort_column) { if (clist->sort_type == GTK_SORT_ASCENDING) clist->sort_type = GTK_SORT_DESCENDING; else clist->sort_type = GTK_SORT_ASCENDING; } else { gtk_clist_set_sort_column(clist, column); gtk_clist_set_compare_func(clist, dview_clist[column].cmp_func); } gtk_clist_sort (clist); } /** * number_compare: * Compare function to sort according to arithmetic value. * This is derived from default_compare() in gtkclist.c(GTK+1.2.0). **/ static gint number_compare(GtkCList *clist, gconstpointer ptr1, gconstpointer ptr2) { int num1; int num2; char *text1 = NULL; char *text2 = NULL; GtkCListRow *row1 = (GtkCListRow*)ptr1; GtkCListRow *row2 = (GtkCListRow*)ptr2; switch (row1->cell[clist->sort_column].type) { case GTK_CELL_TEXT: text1 = GTK_CELL_TEXT(row1->cell[clist->sort_column])->text; break; case GTK_CELL_PIXTEXT: text1 = GTK_CELL_PIXTEXT(row1->cell[clist->sort_column])->text; break; default: break; } switch (row2->cell[clist->sort_column].type) { case GTK_CELL_TEXT: text2 = GTK_CELL_TEXT(row2->cell[clist->sort_column])->text; break; case GTK_CELL_PIXTEXT: text2 = GTK_CELL_PIXTEXT(row2->cell[clist->sort_column])->text; break; default: break; } if (!text2) return (text1 != NULL); if (!text1) return -1; num1 = atoi(text1); num2 = atoi(text2); return num1 - num2; } static gboolean guts_search_string(GdiffDirView *dirview, const char *string, WhichFile whichfile) { GtkCList *clist = dirview->clist; int col = whichfile;/* strictly, should be mapped to COL_FILENAME1 or COL_FILENAME2 */ int rows = clist->rows; int old_row = dirview->cur_selected_row; int row; for (row = old_row + 1; row < rows; row++) { gchar *str; gtk_clist_get_text(clist, row, col, &str); if (strstr(str, string)) { gtk_signal_handler_block_by_func(GTK_OBJECT(clist), GTK_SIGNAL_FUNC(select_row_invoke_sig), dirview); gtk_clist_select_row(clist, row, col); gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), GTK_SIGNAL_FUNC(select_row_invoke_sig), dirview); return TRUE; } } /* Again, from the beginning */ for (row = 0; row <= old_row; row++) { gchar *str; gtk_clist_get_text(clist, row, col, &str); if (strstr(str, string)) { gtk_signal_handler_block_by_func(GTK_OBJECT(clist), GTK_SIGNAL_FUNC(select_row_invoke_sig), dirview); gtk_clist_select_row(clist, row, col); gtk_signal_handler_unblock_by_func(GTK_OBJECT(clist), GTK_SIGNAL_FUNC(select_row_invoke_sig), dirview); return TRUE; } } return FALSE; } static void guts_checksum(GdiffDirView *dirview) { DiffDir *diffdir = dirview->diffdir; DiffFiles *dfiles; const FileInfo *fi1; const FileInfo *fi2; int key; char *cksum_path;/* TODO: capability to customize */ char *cksum_arg = ""; FILE *result_fp; char buf[BUFSIZ]; int ret; if (dirview->cur_selected_row < 0) return; key = GPOINTER_TO_INT(gtk_clist_get_row_data(dirview->clist, dirview->cur_selected_row)); dfiles = g_slist_nth_data(diffdir->dfiles_list, key); fi1 = dfiles_get_fileinfo(dfiles, FIRST_FILE, FALSE); fi2 = dfiles_get_fileinfo(dfiles, SECOND_FILE, FALSE); cksum_path = gnome_is_program_in_path("cksum"); if (cksum_path == NULL) { cksum_path = gnome_is_program_in_path("sum"); if (cksum_path == NULL) return; } /* XXX: diff3 is not supported yet */ result_fp = spawn_prog(cksum_path, cksum_arg, fi1->fname, fi2->fname, NULL); ret = fread(buf, sizeof(char), BUFSIZ, result_fp); if (ret > 0) { if (buf[ret - 1] == '\n') buf[ret - 1] = '\0'; else buf[ret] = '\0'; statusbar_update(dirview->gdwin, buf); } fclose(result_fp); }