📄 gtkfilesel.c
字号:
/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. *//* * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/param.h>#include <dirent.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <errno.h>#include <pwd.h>#include "fnmatch.h"#include "gdk/gdkkeysyms.h"#include "gtkbutton.h"#include "gtkentry.h"#include "gtkfilesel.h"#include "gtkhbox.h"#include "gtkhbbox.h"#include "gtklabel.h"#include "gtklist.h"#include "gtklistitem.h"#include "gtkmain.h"#include "gtkscrolledwindow.h"#include "gtksignal.h"#include "gtkvbox.h"#include "gtkmenu.h"#include "gtkmenuitem.h"#include "gtkoptionmenu.h"#include "gtkclist.h"#include "gtkdialog.h"#include "gtkintl.h"#define DIR_LIST_WIDTH 180#define DIR_LIST_HEIGHT 180#define FILE_LIST_WIDTH 180#define FILE_LIST_HEIGHT 180/* I've put this here so it doesn't get confused with the * file completion interface */typedef struct _HistoryCallbackArg HistoryCallbackArg;struct _HistoryCallbackArg{ gchar *directory; GtkWidget *menu_item;};typedef struct _CompletionState CompletionState;typedef struct _CompletionDir CompletionDir;typedef struct _CompletionDirSent CompletionDirSent;typedef struct _CompletionDirEntry CompletionDirEntry;typedef struct _CompletionUserDir CompletionUserDir;typedef struct _PossibleCompletion PossibleCompletion;/* Non-external file completion decls and structures *//* A contant telling PRCS how many directories to cache. Its actually * kept in a list, so the geometry isn't important. */#define CMPL_DIRECTORY_CACHE_SIZE 10/* A constant used to determine whether a substring was an exact * match by first_diff_index() */#define PATTERN_MATCH -1/* The arguments used by all fnmatch() calls below */#define FNMATCH_FLAGS (FNM_PATHNAME | FNM_PERIOD)#define CMPL_ERRNO_TOO_LONG ((1<<16)-1)/* This structure contains all the useful information about a directory * for the purposes of filename completion. These structures are cached * in the CompletionState struct. CompletionDir's are reference counted. */struct _CompletionDirSent{ ino_t inode; time_t mtime; dev_t device; gint entry_count; gchar *name_buffer; /* memory segment containing names of all entries */ struct _CompletionDirEntry *entries;};struct _CompletionDir{ CompletionDirSent *sent; gchar *fullname; gint fullname_len; struct _CompletionDir *cmpl_parent; gint cmpl_index; gchar *cmpl_text;};/* This structure contains pairs of directory entry names with a flag saying * whether or not they are a valid directory. NOTE: This information is used * to provide the caller with information about whether to update its completions * or try to open a file. Since directories are cached by the directory mtime, * a symlink which points to an invalid file (which will not be a directory), * will not be reevaluated if that file is created, unless the containing * directory is touched. I consider this case to be worth ignoring (josh). */struct _CompletionDirEntry{ gint is_dir; gchar *entry_name;};struct _CompletionUserDir{ gchar *login; gchar *homedir;};struct _PossibleCompletion{ /* accessible fields, all are accessed externally by functions * declared above */ gchar *text; gint is_a_completion; gint is_directory; /* Private fields */ gint text_alloc;};struct _CompletionState{ gint last_valid_char; gchar *updated_text; gint updated_text_len; gint updated_text_alloc; gint re_complete; gchar *user_dir_name_buffer; gint user_directories_len; gchar *last_completion_text; gint user_completion_index; /* if >= 0, currently completing ~user */ struct _CompletionDir *completion_dir; /* directory completing from */ struct _CompletionDir *active_completion_dir; struct _PossibleCompletion the_completion; struct _CompletionDir *reference_dir; /* initial directory */ GList* directory_storage; GList* directory_sent_storage; struct _CompletionUserDir *user_directories;};/* File completion functions which would be external, were they used * outside of this file. */static CompletionState* cmpl_init_state (void);static void cmpl_free_state (CompletionState *cmpl_state);static gint cmpl_state_okay (CompletionState* cmpl_state);static gchar* cmpl_strerror (gint);static PossibleCompletion* cmpl_completion_matches(gchar *text_to_complete, gchar **remaining_text, CompletionState *cmpl_state);/* Returns a name for consideration, possibly a completion, this name * will be invalid after the next call to cmpl_next_completion. */static char* cmpl_this_completion (PossibleCompletion*);/* True if this completion matches the given text. Otherwise, this * output can be used to have a list of non-completions. */static gint cmpl_is_a_completion (PossibleCompletion*);/* True if the completion is a directory */static gint cmpl_is_directory (PossibleCompletion*);/* Obtains the next completion, or NULL */static PossibleCompletion* cmpl_next_completion (CompletionState*);/* Updating completions: the return value of cmpl_updated_text() will * be text_to_complete completed as much as possible after the most * recent call to cmpl_completion_matches. For the present * application, this is the suggested replacement for the user's input * string. You must CALL THIS AFTER ALL cmpl_text_completions have * been received. */static gchar* cmpl_updated_text (CompletionState* cmpl_state);/* After updating, to see if the completion was a directory, call * this. If it was, you should consider re-calling completion_matches. */static gint cmpl_updated_dir (CompletionState* cmpl_state);/* Current location: if using file completion, return the current * directory, from which file completion begins. More specifically, * the cwd concatenated with all exact completions up to the last * directory delimiter('/'). */static gchar* cmpl_reference_position (CompletionState* cmpl_state);/* backing up: if cmpl_completion_matches returns NULL, you may query * the index of the last completable character into cmpl_updated_text. */static gint cmpl_last_valid_char (CompletionState* cmpl_state);/* When the user selects a non-directory, call cmpl_completion_fullname * to get the full name of the selected file. */static gchar* cmpl_completion_fullname (gchar*, CompletionState* cmpl_state);/* Directory operations. */static CompletionDir* open_ref_dir (gchar* text_to_complete, gchar** remaining_text, CompletionState* cmpl_state);static gboolean check_dir (gchar *dir_name, struct stat *result, gboolean *stat_subdirs);static CompletionDir* open_dir (gchar* dir_name, CompletionState* cmpl_state);static CompletionDir* open_user_dir (gchar* text_to_complete, CompletionState *cmpl_state);static CompletionDir* open_relative_dir (gchar* dir_name, CompletionDir* dir, CompletionState *cmpl_state);static CompletionDirSent* open_new_dir (gchar* dir_name, struct stat* sbuf, gboolean stat_subdirs);static gint correct_dir_fullname (CompletionDir* cmpl_dir);static gint correct_parent (CompletionDir* cmpl_dir, struct stat *sbuf);static gchar* find_parent_dir_fullname (gchar* dirname);static CompletionDir* attach_dir (CompletionDirSent* sent, gchar* dir_name, CompletionState *cmpl_state);static void free_dir_sent (CompletionDirSent* sent);static void free_dir (CompletionDir *dir);static void prune_memory_usage(CompletionState *cmpl_state);/* Completion operations */static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete, CompletionState *cmpl_state);static PossibleCompletion* attempt_file_completion(CompletionState *cmpl_state);static CompletionDir* find_completion_dir(gchar* text_to_complete, gchar** remaining_text, CompletionState* cmpl_state);static PossibleCompletion* append_completion_text(gchar* text, CompletionState* cmpl_state);static gint get_pwdb(CompletionState* cmpl_state);static gint first_diff_index(gchar* pat, gchar* text);static gint compare_user_dir(const void* a, const void* b);static gint compare_cmpl_dir(const void* a, const void* b);static void update_cmpl(PossibleCompletion* poss, CompletionState* cmpl_state);static void gtk_file_selection_class_init (GtkFileSelectionClass *klass);static void gtk_file_selection_init (GtkFileSelection *filesel);static void gtk_file_selection_destroy (GtkObject *object);static gint gtk_file_selection_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data);static void gtk_file_selection_file_button (GtkWidget *widget, gint row, gint column, GdkEventButton *bevent, gpointer user_data);static void gtk_file_selection_dir_button (GtkWidget *widget, gint row, gint column, GdkEventButton *bevent, gpointer data);static void gtk_file_selection_populate (GtkFileSelection *fs, gchar *rel_path, gint try_complete);static void gtk_file_selection_abort (GtkFileSelection *fs);static void gtk_file_selection_update_history_menu (GtkFileSelection *fs, gchar *current_dir);static void gtk_file_selection_create_dir (GtkWidget *widget, gpointer data);static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);static GtkWindowClass *parent_class = NULL;/* Saves errno when something cmpl does fails. */static gint cmpl_errno;GtkTypegtk_file_selection_get_type (void){ static GtkType file_selection_type = 0; if (!file_selection_type) { static const GtkTypeInfo filesel_info = { "GtkFileSelection", sizeof (GtkFileSelection), sizeof (GtkFileSelectionClass), (GtkClassInitFunc) gtk_file_selection_class_init, (GtkObjectInitFunc) gtk_file_selection_init, /* reserved_1 */ NULL, /* reserved_2 */ NULL, (GtkClassInitFunc) NULL, }; file_selection_type = gtk_type_unique (GTK_TYPE_WINDOW, &filesel_info); } return file_selection_type;}static voidgtk_file_selection_class_init (GtkFileSelectionClass *class){ GtkObjectClass *object_class; object_class = (GtkObjectClass*) class; parent_class = gtk_type_class (GTK_TYPE_WINDOW); object_class->destroy = gtk_file_selection_destroy;}static voidgtk_file_selection_init (GtkFileSelection *filesel){ GtkWidget *entry_vbox; GtkWidget *label; GtkWidget *list_hbox; GtkWidget *confirm_area; GtkWidget *pulldown_hbox; GtkWidget *scrolled_win; char *dir_title [2]; char *file_title [2]; filesel->cmpl_state = cmpl_init_state (); /* The dialog-sized vertical box */ filesel->main_vbox = gtk_vbox_new (FALSE, 10); gtk_container_set_border_width (GTK_CONTAINER (filesel), 10); gtk_container_add (GTK_CONTAINER (filesel), filesel->main_vbox); gtk_widget_show (filesel->main_vbox); /* The horizontal box containing create, rename etc. buttons */ filesel->button_area = gtk_hbutton_box_new (); gtk_button_box_set_layout(GTK_BUTTON_BOX(filesel->button_area), GTK_BUTTONBOX_START); gtk_button_box_set_spacing(GTK_BUTTON_BOX(filesel->button_area), 0); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, FALSE, FALSE, 0); gtk_widget_show (filesel->button_area); gtk_file_selection_show_fileop_buttons(filesel); /* hbox for pulldown menu */ pulldown_hbox = gtk_hbox_new (TRUE, 5); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), pulldown_hbox, FALSE, FALSE, 0); gtk_widget_show (pulldown_hbox); /* Pulldown menu */ filesel->history_pulldown = gtk_option_menu_new (); gtk_widget_show (filesel->history_pulldown); gtk_box_pack_start (GTK_BOX (pulldown_hbox), filesel->history_pulldown, FALSE, FALSE, 0); /* The horizontal box containing the directory and file listboxes */ list_hbox = gtk_hbox_new (FALSE, 5); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0); gtk_widget_show (list_hbox); /* The directories clist */ dir_title[0] = _("Directories"); dir_title[1] = NULL; filesel->dir_list = gtk_clist_new_with_titles (1, (gchar**) dir_title); gtk_widget_set_usize (filesel->dir_list, DIR_LIST_WIDTH, DIR_LIST_HEIGHT); gtk_signal_connect (GTK_OBJECT (filesel->dir_list), "select_row", (GtkSignalFunc) gtk_file_selection_dir_button, (gpointer) filesel); gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); scrolled_win = gtk_scrolled_window_new (NULL, NULL); gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->dir_list); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); gtk_widget_show (filesel->dir_list); gtk_widget_show (scrolled_win); /* The files clist */ file_title[0] = _("Files"); file_title[1] = NULL; filesel->file_list = gtk_clist_new_with_titles (1, (gchar**) file_title); gtk_widget_set_usize (filesel->file_list, FILE_LIST_WIDTH, FILE_LIST_HEIGHT); gtk_signal_connect (GTK_OBJECT (filesel->file_list), "select_row", (GtkSignalFunc) gtk_file_selection_file_button, (gpointer) filesel); gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); scrolled_win = gtk_scrolled_window_new (NULL, NULL); gtk_container_add (GTK_CONTAINER (scrolled_win), filesel->file_list); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_win), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 5); gtk_box_pack_start (GTK_BOX (list_hbox), scrolled_win, TRUE, TRUE, 0); gtk_widget_show (filesel->file_list); gtk_widget_show (scrolled_win); /* action area for packing buttons into. */ filesel->action_area = gtk_hbox_new (TRUE, 0); gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->action_area, FALSE, FALSE, 0); gtk_widget_show (filesel->action_area); /* The OK/Cancel button area */ confirm_area = gtk_hbutton_box_new (); gtk_button_box_set_layout(GTK_BUTTON_BOX(confirm_area), GTK_BUTTONBOX_END); gtk_button_box_set_spacing(GTK_BUTTON_BOX(confirm_area), 5); gtk_box_pack_end (GTK_BOX (filesel->main_vbox), confirm_area, FALSE, FALSE, 0); gtk_widget_show (confirm_area); /* The OK button */ filesel->ok_button = gtk_button_new_with_label (_("OK")); GTK_WIDGET_SET_FLAGS (filesel->ok_button, GTK_CAN_DEFAULT); gtk_box_pack_start (GTK_BOX (confirm_area), filesel->ok_button, TRUE, TRUE, 0); gtk_widget_grab_default (filesel->ok_button); gtk_widget_show (filesel->ok_button); /* The Cancel button */ filesel->cancel_button = gtk_button_new_with_label (_("Cancel")); GTK_WIDGET_SET_FLAGS (filesel->cancel_button, GTK_CAN_DEFAULT); gtk_box_pack_start (GTK_BOX (confirm_area), filesel->cancel_button, TRUE, TRUE, 0); gtk_widget_show (filesel->cancel_button); /* The selection entry widget */ entry_vbox = gtk_vbox_new (FALSE, 2); gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 0); gtk_widget_show (entry_vbox);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -