gtkfilesel.c 103 KB
Newer Older
Elliot Lee's avatar
Elliot Lee committed
1 2 3 4
/* 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
5
 * modify it under the terms of the GNU Lesser General Public
Elliot Lee's avatar
Elliot Lee committed
6 7 8 9 10 11
 * 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
12
 * Lesser General Public License for more details.
Elliot Lee's avatar
Elliot Lee committed
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15 16 17
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
Elliot Lee's avatar
Elliot Lee committed
18
 */
19 20

/*
21
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
22 23 24 25 26
 * 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/. 
 */

27 28
#include "config.h"

Elliot Lee's avatar
Elliot Lee committed
29 30 31
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
32
#ifdef HAVE_SYS_PARAM_H
Elliot Lee's avatar
Elliot Lee committed
33
#include <sys/param.h>
34
#endif
Elliot Lee's avatar
Elliot Lee committed
35
#include <stdlib.h>
36
#ifdef HAVE_UNISTD_H
Elliot Lee's avatar
Elliot Lee committed
37
#include <unistd.h>
38
#endif
Elliot Lee's avatar
Elliot Lee committed
39 40
#include <string.h>
#include <errno.h>
41
#ifdef HAVE_PWD_H
Elliot Lee's avatar
Elliot Lee committed
42
#include <pwd.h>
43
#endif
44 45 46 47 48 49 50 51 52 53

#include <glib.h>		/* Include early to get G_OS_WIN32 and
				 * G_WITH_CYGWIN */

#if defined(G_OS_WIN32) || defined(G_WITH_CYGWIN)
#include <ctype.h>
#define STRICT
#include <windows.h>
#undef STRICT
#endif /* G_OS_WIN32 || G_WITH_CYGWIN */
54
#ifdef G_OS_WIN32
Tor Lillqvist's avatar
Tor Lillqvist committed
55 56
#include <winsock.h>		/* For gethostname */
#endif
57

Elliot Lee's avatar
Elliot Lee committed
58 59
#include "gdk/gdkkeysyms.h"
#include "gtkbutton.h"
60
#include "gtkcellrenderertext.h"
Elliot Lee's avatar
Elliot Lee committed
61 62 63
#include "gtkentry.h"
#include "gtkfilesel.h"
#include "gtkhbox.h"
64
#include "gtkhbbox.h"
Elliot Lee's avatar
Elliot Lee committed
65
#include "gtklabel.h"
66
#include "gtkliststore.h"
Elliot Lee's avatar
Elliot Lee committed
67
#include "gtkmain.h"
68
#include "gtkprivate.h"
Elliot Lee's avatar
Elliot Lee committed
69
#include "gtkscrolledwindow.h"
70
#include "gtkstock.h"
71 72
#include "gtktreeselection.h"
#include "gtktreeview.h"
Elliot Lee's avatar
Elliot Lee committed
73
#include "gtkvbox.h"
74 75 76 77
#include "gtkmenu.h"
#include "gtkmenuitem.h"
#include "gtkoptionmenu.h"
#include "gtkdialog.h"
78
#include "gtkmessagedialog.h"
Owen Taylor's avatar
Owen Taylor committed
79
#include "gtkintl.h"
80 81
#include "gtkdnd.h"
#include "gtkeventbox.h"
Elliot Lee's avatar
Elliot Lee committed
82

Owen Taylor's avatar
Owen Taylor committed
83
#define WANT_HPANED 1
84
#include "gtkhpaned.h"
85

86
#ifdef G_OS_WIN32
87
#include <direct.h>
88 89
#include <io.h>
#define mkdir(p,m) _mkdir(p)
90 91 92
#ifndef S_ISDIR
#define S_ISDIR(mode) ((mode)&_S_IFDIR)
#endif
93
#endif /* G_OS_WIN32 */
94

95 96 97 98
#ifdef G_WITH_CYGWIN
#include <sys/cygwin.h>		/* For cygwin_conv_to_posix_path */
#endif

99 100 101 102
#define DIR_LIST_WIDTH   180
#define DIR_LIST_HEIGHT  180
#define FILE_LIST_WIDTH  180
#define FILE_LIST_HEIGHT 180
Elliot Lee's avatar
Elliot Lee committed
103

104 105 106 107 108 109 110 111 112 113 114 115
/* The Hurd doesn't define either PATH_MAX or MAXPATHLEN, so we put this
 * in here, since the rest of the code in the file does require some
 * fixed maximum.
 */
#ifndef MAXPATHLEN
#  ifdef PATH_MAX
#    define MAXPATHLEN PATH_MAX
#  else
#    define MAXPATHLEN 2048
#  endif
#endif

116 117 118 119 120 121 122 123 124 125
/* 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;
};

Elliot Lee's avatar
Elliot Lee committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144

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
#define CMPL_ERRNO_TOO_LONG ((1<<16)-1)
145
#define CMPL_ERRNO_DID_NOT_CONVERT ((1<<16)-2)
Elliot Lee's avatar
Elliot Lee committed
146 147 148 149 150 151 152 153 154

/* 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;
155
  dev_t device;
Elliot Lee's avatar
Elliot Lee committed
156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182

  gint entry_count;
  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
{
183
  gboolean is_dir;
Elliot Lee's avatar
Elliot Lee committed
184
  gchar *entry_name;
185
  gchar *sort_key;
Elliot Lee's avatar
Elliot Lee committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
};

struct _CompletionUserDir
{
  gchar *login;
  gchar *homedir;
};

struct _PossibleCompletion
{
  /* accessible fields, all are accessed externally by functions
   * declared above
   */
  gchar *text;
  gint is_a_completion;
201
  gboolean is_directory;
Elliot Lee's avatar
Elliot Lee committed
202 203 204 205 206 207 208 209 210 211 212 213

  /* Private fields
   */
  gint text_alloc;
};

struct _CompletionState
{
  gint last_valid_char;
  gchar *updated_text;
  gint updated_text_len;
  gint updated_text_alloc;
214
  gboolean re_complete;
Elliot Lee's avatar
Elliot Lee committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235

  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;
};

236 237 238
enum {
  PROP_0,
  PROP_SHOW_FILEOPS,
Owen Taylor's avatar
Owen Taylor committed
239 240
  PROP_FILENAME,
  PROP_SELECT_MULTIPLE
241
};
Elliot Lee's avatar
Elliot Lee committed
242

243 244 245 246 247 248 249 250
enum {
  DIR_COLUMN
};

enum {
  FILE_COLUMN
};

Elliot Lee's avatar
Elliot Lee committed
251 252 253 254 255 256 257
/* 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);
258
static const gchar*        cmpl_strerror          (gint);
Elliot Lee's avatar
Elliot Lee committed
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275

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
 */
276
static gboolean            cmpl_is_directory      (PossibleCompletion*);
Elliot Lee's avatar
Elliot Lee committed
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293

/* 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.
 */
294
static gboolean            cmpl_updated_dir        (CompletionState* cmpl_state);
Elliot Lee's avatar
Elliot Lee committed
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310

/* 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.
 */
Owen Taylor's avatar
Owen Taylor committed
311
static const gchar*        cmpl_completion_fullname (const gchar*, CompletionState* cmpl_state);
Elliot Lee's avatar
Elliot Lee committed
312 313 314 315 316 317


/* Directory operations. */
static CompletionDir* open_ref_dir         (gchar* text_to_complete,
					    gchar** remaining_text,
					    CompletionState* cmpl_state);
318
#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
319 320 321
static gboolean       check_dir            (gchar *dir_name, 
					    struct stat *result, 
					    gboolean *stat_subdirs);
322
#endif
Elliot Lee's avatar
Elliot Lee committed
323 324
static CompletionDir* open_dir             (gchar* dir_name,
					    CompletionState* cmpl_state);
325
#ifdef HAVE_PWD_H
326
static CompletionDir* open_user_dir        (const gchar* text_to_complete,
Elliot Lee's avatar
Elliot Lee committed
327
					    CompletionState *cmpl_state);
328
#endif
Elliot Lee's avatar
Elliot Lee committed
329 330
static CompletionDir* open_relative_dir    (gchar* dir_name, CompletionDir* dir,
					    CompletionState *cmpl_state);
331 332 333
static CompletionDirSent* open_new_dir     (gchar* dir_name, 
					    struct stat* sbuf,
					    gboolean stat_subdirs);
Elliot Lee's avatar
Elliot Lee committed
334 335 336
static gint           correct_dir_fullname (CompletionDir* cmpl_dir);
static gint           correct_parent       (CompletionDir* cmpl_dir,
					    struct stat *sbuf);
337
#ifndef G_OS_WIN32
Elliot Lee's avatar
Elliot Lee committed
338
static gchar*         find_parent_dir_fullname    (gchar* dirname);
339
#endif
Elliot Lee's avatar
Elliot Lee committed
340 341 342 343 344 345 346 347
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 */
348
#ifdef HAVE_PWD_H
Elliot Lee's avatar
Elliot Lee committed
349 350
static PossibleCompletion* attempt_homedir_completion(gchar* text_to_complete,
						      CompletionState *cmpl_state);
351
#endif
Elliot Lee's avatar
Elliot Lee committed
352 353 354 355 356 357
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);
358
#ifdef HAVE_PWD_H
Elliot Lee's avatar
Elliot Lee committed
359 360
static gint get_pwdb(CompletionState* cmpl_state);
static gint compare_user_dir(const void* a, const void* b);
361 362
#endif
static gint first_diff_index(gchar* pat, gchar* text);
Elliot Lee's avatar
Elliot Lee committed
363 364 365 366 367
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);
368 369 370 371 372 373 374 375
static void gtk_file_selection_set_property  (GObject         *object,
					      guint            prop_id,
					      const GValue    *value,
					      GParamSpec      *pspec);
static void gtk_file_selection_get_property  (GObject         *object,
					      guint            prop_id,
					      GValue          *value,
					      GParamSpec      *pspec);
Elliot Lee's avatar
Elliot Lee committed
376
static void gtk_file_selection_init          (GtkFileSelection      *filesel);
377
static void gtk_file_selection_finalize      (GObject               *object);
Elliot Lee's avatar
Elliot Lee committed
378
static void gtk_file_selection_destroy       (GtkObject             *object);
379
static void gtk_file_selection_map           (GtkWidget             *widget);
Elliot Lee's avatar
Elliot Lee committed
380 381 382
static gint gtk_file_selection_key_press     (GtkWidget             *widget,
					      GdkEventKey           *event,
					      gpointer               user_data);
383 384 385 386 387
static gint gtk_file_selection_insert_text   (GtkWidget             *widget,
					      const gchar           *new_text,
					      gint                   new_text_length,
					      gint                  *position,
					      gpointer               user_data);
388
static void gtk_file_selection_update_fileops (GtkFileSelection     *filesel);
389

390 391 392 393 394 395 396 397 398 399
static void gtk_file_selection_file_activate (GtkTreeView       *tree_view,
					      GtkTreePath       *path,
					      GtkTreeViewColumn *column,
					      gpointer           user_data);
static void gtk_file_selection_file_changed  (GtkTreeSelection  *selection,
					      gpointer           user_data);
static void gtk_file_selection_dir_activate  (GtkTreeView       *tree_view,
					      GtkTreePath       *path,
					      GtkTreeViewColumn *column,
					      gpointer           user_data);
400

Elliot Lee's avatar
Elliot Lee committed
401 402
static void gtk_file_selection_populate      (GtkFileSelection      *fs,
					      gchar                 *rel_path,
403 404
					      gboolean               try_complete,
					      gboolean               reset_entry);
Elliot Lee's avatar
Elliot Lee committed
405
static void gtk_file_selection_abort         (GtkFileSelection      *fs);
406 407 408 409

static void gtk_file_selection_update_history_menu (GtkFileSelection       *fs,
						    gchar                  *current_dir);

410
static void gtk_file_selection_create_dir  (GtkWidget *widget, gpointer data);
411 412
static void gtk_file_selection_delete_file (GtkWidget *widget, gpointer data);
static void gtk_file_selection_rename_file (GtkWidget *widget, gpointer data);
Elliot Lee's avatar
Elliot Lee committed
413

414 415 416 417 418 419 420
static void free_selected_names (GPtrArray *names);

#if !defined(G_OS_WIN32) && !defined(G_WITH_CYGWIN)
#define compare_filenames(a, b) strcmp(a, b)
#else
#define compare_filenames(a, b) g_ascii_strcasecmp(a, b)
#endif
Elliot Lee's avatar
Elliot Lee committed
421 422


423
static GtkWindowClass *parent_class = NULL;
Elliot Lee's avatar
Elliot Lee committed
424 425 426 427

/* Saves errno when something cmpl does fails. */
static gint cmpl_errno;

428
#ifdef G_WITH_CYGWIN
429 430 431 432 433 434 435
/*
 * Take the path currently in the file selection
 * entry field and translate as necessary from
 * a WIN32 style to CYGWIN32 style path.  For
 * instance translate:
 * x:\somepath\file.jpg
 * to:
436
 * /cygdrive/x/somepath/file.jpg
437 438 439 440 441
 *
 * Replace the path in the selection text field.
 * Return a boolean value concerning whether a
 * translation had to be made.
 */
Hans Breuer's avatar
Hans Breuer committed
442
static int
443
translate_win32_path (GtkFileSelection *filesel)
444 445
{
  int updated = 0;
446 447
  const gchar *path;
  gchar newPath[MAX_PATH];
448 449 450 451 452 453

  /*
   * Retrieve the current path
   */
  path = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));

454 455
  cygwin_conv_to_posix_path (path, newPath);
  updated = (strcmp (path, newPath) != 0);
456

457 458
  if (updated)
    gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), newPath);
459 460 461 462 463
    
  return updated;
}
#endif

Manish Singh's avatar
Manish Singh committed
464
GType
465
gtk_file_selection_get_type (void)
Elliot Lee's avatar
Elliot Lee committed
466
{
Manish Singh's avatar
Manish Singh committed
467
  static GType file_selection_type = 0;
Elliot Lee's avatar
Elliot Lee committed
468 469 470

  if (!file_selection_type)
    {
Manish Singh's avatar
Manish Singh committed
471
      static const GTypeInfo filesel_info =
Elliot Lee's avatar
Elliot Lee committed
472 473
      {
	sizeof (GtkFileSelectionClass),
Manish Singh's avatar
Manish Singh committed
474 475 476 477 478 479 480 481
	NULL,		/* base_init */
	NULL,		/* base_finalize */
	(GClassInitFunc) gtk_file_selection_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data */
	sizeof (GtkFileSelection),
	0,		/* n_preallocs */
	(GInstanceInitFunc) gtk_file_selection_init,
Elliot Lee's avatar
Elliot Lee committed
482 483
      };

Manish Singh's avatar
Manish Singh committed
484 485 486
      file_selection_type =
	g_type_register_static (GTK_TYPE_DIALOG, "GtkFileSelection",
				&filesel_info, 0);
Elliot Lee's avatar
Elliot Lee committed
487 488 489 490 491 492 493 494
    }

  return file_selection_type;
}

static void
gtk_file_selection_class_init (GtkFileSelectionClass *class)
{
495
  GObjectClass *gobject_class;
Elliot Lee's avatar
Elliot Lee committed
496
  GtkObjectClass *object_class;
497
  GtkWidgetClass *widget_class;
Elliot Lee's avatar
Elliot Lee committed
498

499
  gobject_class = (GObjectClass*) class;
Elliot Lee's avatar
Elliot Lee committed
500
  object_class = (GtkObjectClass*) class;
501
  widget_class = (GtkWidgetClass*) class;
Elliot Lee's avatar
Elliot Lee committed
502

Manish Singh's avatar
Manish Singh committed
503
  parent_class = g_type_class_peek_parent (class);
Elliot Lee's avatar
Elliot Lee committed
504

505
  gobject_class->finalize = gtk_file_selection_finalize;
506 507 508 509 510 511 512
  gobject_class->set_property = gtk_file_selection_set_property;
  gobject_class->get_property = gtk_file_selection_get_property;
   
  g_object_class_install_property (gobject_class,
                                   PROP_FILENAME,
                                   g_param_spec_string ("filename",
                                                        _("Filename"),
513
                                                        _("The currently selected filename"),
514 515 516 517 518 519
                                                        NULL,
                                                        G_PARAM_READABLE | G_PARAM_WRITABLE));
  g_object_class_install_property (gobject_class,
				   PROP_SHOW_FILEOPS,
				   g_param_spec_boolean ("show_fileops",
							 _("Show file operations"),
520
							 _("Whether buttons for creating/manipulating files should be displayed"),
521 522 523
							 FALSE,
							 G_PARAM_READABLE |
							 G_PARAM_WRITABLE));
Owen Taylor's avatar
Owen Taylor committed
524 525 526 527
  g_object_class_install_property (gobject_class,
				   PROP_SELECT_MULTIPLE,
				   g_param_spec_boolean ("select_multiple",
							 _("Select multiple"),
528
							 _("Whether to allow multiple files to be selected"),
Owen Taylor's avatar
Owen Taylor committed
529 530 531
							 FALSE,
							 G_PARAM_READABLE |
							 G_PARAM_WRITABLE));
Elliot Lee's avatar
Elliot Lee committed
532
  object_class->destroy = gtk_file_selection_destroy;
533
  widget_class->map = gtk_file_selection_map;
Elliot Lee's avatar
Elliot Lee committed
534 535
}

536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
static void gtk_file_selection_set_property (GObject         *object,
					     guint            prop_id,
					     const GValue    *value,
					     GParamSpec      *pspec)
{
  GtkFileSelection *filesel;

  filesel = GTK_FILE_SELECTION (object);

  switch (prop_id)
    {
    case PROP_FILENAME:
      gtk_file_selection_set_filename (filesel,
                                       g_value_get_string (value));
      break;
    case PROP_SHOW_FILEOPS:
      if (g_value_get_boolean (value))
	 gtk_file_selection_show_fileop_buttons (filesel);
      else
	 gtk_file_selection_hide_fileop_buttons (filesel);
      break;
Owen Taylor's avatar
Owen Taylor committed
557 558 559
    case PROP_SELECT_MULTIPLE:
      gtk_file_selection_set_select_multiple (filesel, g_value_get_boolean (value));
      break;
560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void gtk_file_selection_get_property (GObject         *object,
					     guint            prop_id,
					     GValue          *value,
					     GParamSpec      *pspec)
{
  GtkFileSelection *filesel;

  filesel = GTK_FILE_SELECTION (object);

  switch (prop_id)
    {
    case PROP_FILENAME:
      g_value_set_string (value,
                          gtk_file_selection_get_filename(filesel));
      break;

    case PROP_SHOW_FILEOPS:
      /* This is a little bit hacky, but doing otherwise would require
       * adding a field to the object.
       */
      g_value_set_boolean (value, (filesel->fileop_c_dir && 
				   filesel->fileop_del_file &&
				   filesel->fileop_ren_file));
      break;
Owen Taylor's avatar
Owen Taylor committed
590 591 592
    case PROP_SELECT_MULTIPLE:
      g_value_set_boolean (value, gtk_file_selection_get_select_multiple (filesel));
      break;
593 594 595 596 597 598
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

599 600 601 602 603 604 605
static gboolean
grab_default (GtkWidget *widget)
{
  gtk_widget_grab_default (widget);
  return FALSE;
}
     
Elliot Lee's avatar
Elliot Lee committed
606 607 608 609 610
static void
gtk_file_selection_init (GtkFileSelection *filesel)
{
  GtkWidget *entry_vbox;
  GtkWidget *label;
611
  GtkWidget *list_hbox, *list_container;
612
  GtkWidget *confirm_area;
613
  GtkWidget *pulldown_hbox;
614
  GtkWidget *scrolled_win;
615
  GtkWidget *eventbox;
Owen Taylor's avatar
Owen Taylor committed
616
  GtkWidget *spacer;
617 618
  GtkDialog *dialog;

619 620 621
  GtkListStore *model;
  GtkTreeViewColumn *column;
  
622 623
  gtk_widget_push_composite_child ();

624
  dialog = GTK_DIALOG (filesel);
625

Elliot Lee's avatar
Elliot Lee committed
626
  filesel->cmpl_state = cmpl_init_state ();
627

628
  /* The dialog-sized vertical box  */
629
  filesel->main_vbox = dialog->vbox;
630
  gtk_container_set_border_width (GTK_CONTAINER (filesel), 10);
Elliot Lee's avatar
Elliot Lee committed
631

632
  /* The horizontal box containing create, rename etc. buttons */
633
  filesel->button_area = gtk_hbutton_box_new ();
634
  gtk_button_box_set_layout (GTK_BUTTON_BOX (filesel->button_area), GTK_BUTTONBOX_START);
635
  gtk_box_set_spacing (GTK_BOX (filesel->button_area), 0);
636
  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), filesel->button_area, 
637
		      FALSE, FALSE, 0);
638
  gtk_widget_show (filesel->button_area);
639
  
640
  gtk_file_selection_show_fileop_buttons (filesel);
641

642 643 644 645 646 647 648 649 650 651 652
  /* 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);
    
Elliot Lee's avatar
Elliot Lee committed
653
  /*  The horizontal box containing the directory and file listboxes  */
Owen Taylor's avatar
Owen Taylor committed
654 655 656 657 658 659

  spacer = gtk_hbox_new (FALSE, 0);
  gtk_widget_set_size_request (spacer, -1, 5);
  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0);
  gtk_widget_show (spacer);
  
660
  list_hbox = gtk_hbox_new (FALSE, 5);
Elliot Lee's avatar
Elliot Lee committed
661 662
  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), list_hbox, TRUE, TRUE, 0);
  gtk_widget_show (list_hbox);
Owen Taylor's avatar
Owen Taylor committed
663
  if (WANT_HPANED)
664 665 666
    list_container = g_object_new (GTK_TYPE_HPANED,
				   "visible", TRUE,
				   "parent", list_hbox,
Owen Taylor's avatar
Owen Taylor committed
667
				   "border_width", 0,
668 669 670
				   NULL);
  else
    list_container = list_hbox;
Elliot Lee's avatar
Elliot Lee committed
671

Owen Taylor's avatar
Owen Taylor committed
672 673 674 675 676
  spacer = gtk_hbox_new (FALSE, 0);
  gtk_widget_set_size_request (spacer, -1, 5);
  gtk_box_pack_start (GTK_BOX (filesel->main_vbox), spacer, FALSE, FALSE, 0);  
  gtk_widget_show (spacer);
  
677 678 679 680 681 682
  /* The directories list */

  model = gtk_list_store_new (1, G_TYPE_STRING);
  filesel->dir_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
  g_object_unref (model);

683
  column = gtk_tree_view_column_new_with_attributes (_("Folders"),
684 685 686
						     gtk_cell_renderer_text_new (),
						     "text", DIR_COLUMN,
						     NULL);
687
  label = gtk_label_new_with_mnemonic (_("Fol_ders"));
688 689 690 691 692 693
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->dir_list);
  gtk_widget_show (label);
  gtk_tree_view_column_set_widget (column, label);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->dir_list), column);

Manish Singh's avatar
Manish Singh committed
694 695
  gtk_widget_set_size_request (filesel->dir_list,
			       DIR_LIST_WIDTH, DIR_LIST_HEIGHT);
696 697 698 699
  g_signal_connect (filesel->dir_list, "row_activated",
		    G_CALLBACK (gtk_file_selection_dir_activate), filesel);

  /*  gtk_clist_column_titles_passive (GTK_CLIST (filesel->dir_list)); */
700 701

  scrolled_win = gtk_scrolled_window_new (NULL, NULL);
702
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);  
703 704 705
  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);
706
  gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0);
707 708 709 710
  if (GTK_IS_PANED (list_container))
    gtk_paned_pack1 (GTK_PANED (list_container), scrolled_win, TRUE, TRUE);
  else
    gtk_container_add (GTK_CONTAINER (list_container), scrolled_win);
Elliot Lee's avatar
Elliot Lee committed
711
  gtk_widget_show (filesel->dir_list);
712
  gtk_widget_show (scrolled_win);
Elliot Lee's avatar
Elliot Lee committed
713

714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
  /* The files list */
  model = gtk_list_store_new (1, G_TYPE_STRING);
  filesel->file_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model));
  g_object_unref (model);

  column = gtk_tree_view_column_new_with_attributes (_("Files"),
						     gtk_cell_renderer_text_new (),
						     "text", FILE_COLUMN,
						     NULL);
  label = gtk_label_new_with_mnemonic (_("_Files"));
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), filesel->file_list);
  gtk_widget_show (label);
  gtk_tree_view_column_set_widget (column, label);
  gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
  gtk_tree_view_append_column (GTK_TREE_VIEW (filesel->file_list), column);

Manish Singh's avatar
Manish Singh committed
730 731
  gtk_widget_set_size_request (filesel->file_list,
			       FILE_LIST_WIDTH, FILE_LIST_HEIGHT);
732 733 734 735 736 737
  g_signal_connect (filesel->file_list, "row_activated",
		    G_CALLBACK (gtk_file_selection_file_activate), filesel);
  g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (filesel->file_list)), "changed",
		    G_CALLBACK (gtk_file_selection_file_changed), filesel);

  /* gtk_clist_column_titles_passive (GTK_CLIST (filesel->file_list)); */
738 739

  scrolled_win = gtk_scrolled_window_new (NULL, NULL);
740
  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_win), GTK_SHADOW_IN);
741 742 743
  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);
744
  gtk_container_set_border_width (GTK_CONTAINER (scrolled_win), 0);
745
  gtk_container_add (GTK_CONTAINER (list_container), scrolled_win);
Elliot Lee's avatar
Elliot Lee committed
746
  gtk_widget_show (filesel->file_list);
747
  gtk_widget_show (scrolled_win);
Elliot Lee's avatar
Elliot Lee committed
748

749 750 751 752 753 754 755
  /* 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 */
756
  confirm_area = dialog->action_area;
Elliot Lee's avatar
Elliot Lee committed
757

758 759 760 761
  /*  The Cancel button  */
  filesel->cancel_button = gtk_dialog_add_button (dialog,
                                                  GTK_STOCK_CANCEL,
                                                  GTK_RESPONSE_CANCEL);
Elliot Lee's avatar
Elliot Lee committed
762
  /*  The OK button  */
763
  filesel->ok_button = gtk_dialog_add_button (dialog,
764
                                              GTK_STOCK_OK,
765 766
                                              GTK_RESPONSE_OK);
  
Elliot Lee's avatar
Elliot Lee committed
767 768 769 770
  gtk_widget_grab_default (filesel->ok_button);

  /*  The selection entry widget  */
  entry_vbox = gtk_vbox_new (FALSE, 2);
771
  gtk_box_pack_end (GTK_BOX (filesel->main_vbox), entry_vbox, FALSE, FALSE, 2);
Elliot Lee's avatar
Elliot Lee committed
772
  gtk_widget_show (entry_vbox);
773 774
  
  eventbox = gtk_event_box_new ();
Elliot Lee's avatar
Elliot Lee committed
775 776
  filesel->selection_text = label = gtk_label_new ("");
  gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
777 778
  gtk_container_add (GTK_CONTAINER (eventbox), label);
  gtk_box_pack_start (GTK_BOX (entry_vbox), eventbox, FALSE, FALSE, 0);
Elliot Lee's avatar
Elliot Lee committed
779
  gtk_widget_show (label);
780
  gtk_widget_show (eventbox);
Elliot Lee's avatar
Elliot Lee committed
781 782

  filesel->selection_entry = gtk_entry_new ();
Manish Singh's avatar
Manish Singh committed
783 784 785 786
  g_signal_connect (filesel->selection_entry, "key_press_event",
		    G_CALLBACK (gtk_file_selection_key_press), filesel);
  g_signal_connect (filesel->selection_entry, "insert_text",
		    G_CALLBACK (gtk_file_selection_insert_text), NULL);
787 788
  g_signal_connect_swapped (filesel->selection_entry, "changed",
			    G_CALLBACK (gtk_file_selection_update_fileops), filesel);
Manish Singh's avatar
Manish Singh committed
789 790 791 792 793 794
  g_signal_connect_swapped (filesel->selection_entry, "focus_in_event",
			    G_CALLBACK (grab_default),
			    filesel->ok_button);
  g_signal_connect_swapped (filesel->selection_entry, "activate",
			    G_CALLBACK (gtk_button_clicked),
			    filesel->ok_button);
795
  
Elliot Lee's avatar
Elliot Lee committed
796 797 798
  gtk_box_pack_start (GTK_BOX (entry_vbox), filesel->selection_entry, TRUE, TRUE, 0);
  gtk_widget_show (filesel->selection_entry);

799 800 801
  gtk_label_set_mnemonic_widget (GTK_LABEL (filesel->selection_text),
				 filesel->selection_entry);

Elliot Lee's avatar
Elliot Lee committed
802 803 804 805
  if (!cmpl_state_okay (filesel->cmpl_state))
    {
      gchar err_buf[256];

806
      sprintf (err_buf, _("Folder unreadable: %s"), cmpl_strerror (cmpl_errno));
Elliot Lee's avatar
Elliot Lee committed
807

808
      gtk_label_set_text (GTK_LABEL (filesel->selection_text), err_buf);
Elliot Lee's avatar
Elliot Lee committed
809 810 811
    }
  else
    {
812
      gtk_file_selection_populate (filesel, "", FALSE, TRUE);
Elliot Lee's avatar
Elliot Lee committed
813 814 815
    }

  gtk_widget_grab_focus (filesel->selection_entry);
816 817

  gtk_widget_pop_composite_child ();
Elliot Lee's avatar
Elliot Lee committed
818 819
}

820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
static gchar *
uri_list_extract_first_uri (const gchar* uri_list)
{
  const gchar *p, *q;
  
  g_return_val_if_fail (uri_list != NULL, NULL);
  
  p = uri_list;
  /* We don't actually try to validate the URI according to RFC
   * 2396, or even check for allowed characters - we just ignore
   * comments and trim whitespace off the ends.  We also
   * allow LF delimination as well as the specified CRLF.
   *
   * We do allow comments like specified in RFC 2483.
   */
  while (p)
    {
      if (*p != '#')
	{
	  while (g_ascii_isspace(*p))
	    p++;
	  
	  q = p;
	  while (*q && (*q != '\n') && (*q != '\r'))
	    q++;
	  
	  if (q > p)
	    {
	      q--;
	      while (q > p && g_ascii_isspace (*q))
		q--;

	      if (q > p)
		return g_strndup (p, q - p + 1);
	    }
	}
      p = strchr (p, '\n');
      if (p)
	p++;
    }
  return NULL;
}

static void
dnd_really_drop  (GtkWidget *dialog, gint response_id, GtkFileSelection *fs)
{
  gchar *filename;
  
  if (response_id == GTK_RESPONSE_YES)
    {
      filename = g_object_get_data (G_OBJECT (dialog), "gtk-fs-dnd-filename");

      gtk_file_selection_set_filename (fs, filename);
    }
  
  gtk_widget_destroy (dialog);
}


static void
filenames_dropped (GtkWidget        *widget,
		   GdkDragContext   *context,
		   gint              x,
		   gint              y,
		   GtkSelectionData *selection_data,
		   guint             info,
		   guint             time)
{
  char *uri = NULL;
  char *filename = NULL;
  char *hostname;
  char this_hostname[257];
  int res;
  GError *error = NULL;
	
  if (!selection_data->data)
    return;

  uri = uri_list_extract_first_uri ((char *)selection_data->data);
  
  if (!uri)
    return;

  filename = g_filename_from_uri (uri, &hostname, &error);
  g_free (uri);
  
  if (!filename)
    {
      g_warning ("Error getting dropped filename: %s\n",
		 error->message);
      g_error_free (error);
      return;
    }

  res = gethostname (this_hostname, 256);
  this_hostname[256] = 0;
  
  if ((hostname == NULL) ||
      (res == 0 && strcmp (hostname, this_hostname) == 0) ||
      (strcmp (hostname, "localhost") == 0))
    gtk_file_selection_set_filename (GTK_FILE_SELECTION (widget),
				     filename);
  else
    {
      GtkWidget *dialog;
925 926 927 928 929 930 931
      gchar *filename_utf8;

      /* Conversion back to UTF-8 should always succeed for the result
       * of g_filename_from_uri()
       */
      filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
      g_assert (filename_utf8);
932 933 934 935 936
      
      dialog = gtk_message_dialog_new (GTK_WINDOW (widget),
				       GTK_DIALOG_DESTROY_WITH_PARENT,
				       GTK_MESSAGE_QUESTION,
				       GTK_BUTTONS_YES_NO,
Abigail Brady's avatar
Abigail Brady committed
937
				       _("The file \"%s\" resides on another machine (called %s) and may not be available to this program.\n"
938 939
					 "Are you sure that you want to select it?"), filename_utf8, hostname);
      g_free (filename_utf8);
940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971

      g_object_set_data_full (G_OBJECT (dialog), "gtk-fs-dnd-filename", g_strdup (filename), g_free);
      
      g_signal_connect_data (dialog, "response",
			     (GCallback) dnd_really_drop, 
			     widget, NULL, 0);
      
      gtk_widget_show (dialog);
    }

  g_free (hostname);
  g_free (filename);
}

enum
{
  TARGET_URILIST,
  TARGET_UTF8_STRING,
  TARGET_STRING,
  TARGET_TEXT,
  TARGET_COMPOUND_TEXT
};


static void
filenames_drag_get (GtkWidget        *widget,
		    GdkDragContext   *context,
		    GtkSelectionData *selection_data,
		    guint             info,
		    guint             time,
		    GtkFileSelection *filesel)
{
972
  const gchar *file;
973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002
  gchar *uri_list;
  char hostname[256];
  int res;
  GError *error;

  file = gtk_file_selection_get_filename (filesel);

  if (file)
    {
      if (info == TARGET_URILIST)
	{
	  res = gethostname (hostname, 256);
	  
	  error = NULL;
	  uri_list = g_filename_to_uri (file, (!res)?hostname:NULL, &error);
	  if (!uri_list)
	    {
	      g_warning ("Error getting filename: %s\n",
			 error->message);
	      g_error_free (error);
	      return;
	    }
	  
	  gtk_selection_data_set (selection_data,
				  selection_data->target, 8,
				  (void *)uri_list, strlen((char *)uri_list));
	  g_free (uri_list);
	}
      else
	{
1003 1004 1005 1006
	  gchar *filename_utf8 = g_filename_to_utf8 (file, -1, NULL, NULL, NULL);
	  g_assert (filename_utf8);
	  gtk_selection_data_set_text (selection_data, filename_utf8, -1);
	  g_free (filename_utf8);
1007 1008 1009 1010 1011 1012 1013 1014
	}
    }
}

static void
file_selection_setup_dnd (GtkFileSelection *filesel)
{
  GtkWidget *eventbox;
1015
  static const GtkTargetEntry drop_types[] = {
1016 1017 1018
    { "text/uri-list", 0, TARGET_URILIST}
  };
  static gint n_drop_types = sizeof(drop_types)/sizeof(drop_types[0]);
1019
  static const GtkTargetEntry drag_types[] = {
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
    { "text/uri-list", 0, TARGET_URILIST},
    { "UTF8_STRING", 0, TARGET_UTF8_STRING },
    { "STRING", 0, 0 },
    { "TEXT",   0, 0 }, 
    { "COMPOUND_TEXT", 0, 0 }
  };
  static gint n_drag_types = sizeof(drag_types)/sizeof(drag_types[0]);

  gtk_drag_dest_set (GTK_WIDGET (filesel),
		     GTK_DEST_DEFAULT_ALL,
		     drop_types, n_drop_types,
		     GDK_ACTION_COPY);

Manish Singh's avatar
Manish Singh committed
1033 1034
  g_signal_connect (filesel, "drag_data_received",
		    G_CALLBACK (filenames_dropped), NULL);
1035 1036 1037 1038 1039 1040 1041

  eventbox = gtk_widget_get_parent (filesel->selection_text);
  gtk_drag_source_set (eventbox,
		       GDK_BUTTON1_MASK,
		       drag_types, n_drag_types,
		       GDK_ACTION_COPY);

Manish Singh's avatar
Manish Singh committed
1042 1043
  g_signal_connect (eventbox, "drag_data_get",
		    G_CALLBACK (filenames_drag_get), filesel);
1044 1045
}

Elliot Lee's avatar
Elliot Lee committed
1046 1047 1048 1049 1050
GtkWidget*
gtk_file_selection_new (const gchar *title)
{
  GtkFileSelection *filesel;

Manish Singh's avatar
Manish Singh committed
1051
  filesel = g_object_new (GTK_TYPE_FILE_SELECTION, NULL);
Elliot Lee's avatar
Elliot Lee committed
1052
  gtk_window_set_title (GTK_WINDOW (filesel), title);
1053
  gtk_dialog_set_has_separator (GTK_DIALOG (filesel), FALSE);
Elliot Lee's avatar
Elliot Lee committed
1054

1055 1056
  file_selection_setup_dnd (filesel);
  
Elliot Lee's avatar
Elliot Lee committed
1057 1058 1059
  return GTK_WIDGET (filesel);
}

1060 1061 1062 1063 1064 1065 1066 1067
void
gtk_file_selection_show_fileop_buttons (GtkFileSelection *filesel)
{
  g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
    
  /* delete, create directory, and rename */
  if (!filesel->fileop_c_dir) 
    {
1068
      filesel->fileop_c_dir = gtk_button_new_with_mnemonic (_("_New Folder"));
Manish Singh's avatar
Manish Singh committed
1069 1070 1071
      g_signal_connect (filesel->fileop_c_dir, "clicked",
			G_CALLBACK (gtk_file_selection_create_dir),
			filesel);
1072 1073 1074 1075 1076 1077 1078
      gtk_box_pack_start (GTK_BOX (filesel->button_area), 
			  filesel->fileop_c_dir, TRUE, TRUE, 0);
      gtk_widget_show (filesel->fileop_c_dir);
    }
	
  if (!filesel->fileop_del_file) 
    {
1079
      filesel->fileop_del_file = gtk_button_new_with_mnemonic (_("De_lete File"));
Manish Singh's avatar
Manish Singh committed
1080 1081 1082
      g_signal_connect (filesel->fileop_del_file, "clicked",
			G_CALLBACK (gtk_file_selection_delete_file),
			filesel);
1083 1084 1085 1086 1087 1088 1089
      gtk_box_pack_start (GTK_BOX (filesel->button_area), 
			  filesel->fileop_del_file, TRUE, TRUE, 0);
      gtk_widget_show (filesel->fileop_del_file);
    }

  if (!filesel->fileop_ren_file)
    {
1090
      filesel->fileop_ren_file = gtk_button_new_with_mnemonic (_("_Rename File"));
Manish Singh's avatar
Manish Singh committed
1091 1092 1093
      g_signal_connect (filesel->fileop_ren_file, "clicked",
			G_CALLBACK (gtk_file_selection_rename_file),
			filesel);
1094 1095 1096 1097
      gtk_box_pack_start (GTK_BOX (filesel->button_area), 
			  filesel->fileop_ren_file, TRUE, TRUE, 0);
      gtk_widget_show (filesel->fileop_ren_file);
    }
1098 1099 1100
  
  gtk_file_selection_update_fileops (filesel);
  
1101
  g_object_notify (G_OBJECT (filesel), "show_fileops");
1102 1103 1104 1105 1106 1107 1108
}

void       
gtk_file_selection_hide_fileop_buttons (GtkFileSelection *filesel)
{
  g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
    
1109 1110 1111 1112 1113
  if (filesel->fileop_ren_file)
    {
      gtk_widget_destroy (filesel->fileop_ren_file);
      filesel->fileop_ren_file = NULL;
    }
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125

  if (filesel->fileop_del_file)
    {
      gtk_widget_destroy (filesel->fileop_del_file);
      filesel->fileop_del_file = NULL;
    }

  if (filesel->fileop_c_dir)
    {
      gtk_widget_destroy (filesel->fileop_c_dir);
      filesel->fileop_c_dir = NULL;
    }
1126
  g_object_notify (G_OBJECT (filesel), "show_fileops");
1127 1128 1129 1130
}



1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142
/**
 * gtk_file_selection_set_filename:
 * @filesel: a #GtkFileSelection.
 * @filename:  a string to set as the default file name.
 * 
 * Sets a default path for the file requestor. If @filename includes a
 * directory path, then the requestor will open with that path as its
 * current working directory.
 *
 * The encoding of @filename is the on-disk encoding, which
 * may not be UTF-8. See g_filename_from_utf8().
 **/
Elliot Lee's avatar
Elliot Lee committed
1143 1144 1145 1146
void
gtk_file_selection_set_filename (GtkFileSelection *filesel,
				 const gchar      *filename)
{
1147
  gchar *buf;
Elliot Lee's avatar
Elliot Lee committed
1148
  const char *name, *last_slash;
1149
  char *filename_utf8;
Elliot Lee's avatar
Elliot Lee committed
1150 1151 1152 1153

  g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
  g_return_if_fail (filename != NULL);

1154
  filename_utf8 = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
1155
  g_return_if_fail (filename_utf8 != NULL);
1156

1157
  last_slash = strrchr (filename_utf8, G_DIR_SEPARATOR);
Elliot Lee's avatar
Elliot Lee committed
1158 1159 1160

  if (!last_slash)
    {
1161
      buf = g_strdup ("");
1162
      name = filename_utf8;
Elliot Lee's avatar
Elliot Lee committed
1163 1164 1165
    }
  else
    {
1166 1167
      buf = g_strdup (filename_utf8);
      buf[last_slash - filename_utf8 + 1] = 0;
Elliot Lee's avatar
Elliot Lee committed
1168 1169 1170
      name = last_slash + 1;
    }

1171
  gtk_file_selection_populate (filesel, buf, FALSE, TRUE);
Elliot Lee's avatar
Elliot Lee committed
1172 1173 1174

  if (filesel->selection_entry)
    gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), name);
1175
  g_free (buf);
1176
  g_object_notify (G_OBJECT (filesel), "filename");
1177 1178

  g_free (filename_utf8);
Elliot Lee's avatar
Elliot Lee committed
1179 1180
}

1181 1182 1183 1184
/**
 * gtk_file_selection_get_filename:
 * @filesel: a #GtkFileSelection
 * 
Owen Taylor's avatar
Owen Taylor committed
1185
 * This function returns the selected filename in the on-disk encoding
1186
 * (see g_filename_from_utf8()), which may or may not be the same as that
1187 1188 1189
 * used by GTK+ (UTF-8). To convert to UTF-8, call g_filename_to_utf8().
 * The returned string points to a statically allocated buffer and
 * should be copied if you plan to keep it around.
Owen Taylor's avatar
Owen Taylor committed
1190 1191
 *
 * If no file is selected then the selected directory path is returned.
1192
 * 
Owen Taylor's avatar
Owen Taylor committed
1193
 * Return value: currently-selected filename in the on-disk encoding.
1194 1195
 **/
G_CONST_RETURN gchar*
Elliot Lee's avatar
Elliot Lee committed
1196 1197
gtk_file_selection_get_filename (GtkFileSelection *filesel)
{
1198
  static const gchar nothing[2] = "";
1199
  static gchar something[MAXPATHLEN*2];
1200
  char *sys_filename;
1201
  const char *text;
Elliot Lee's avatar
Elliot Lee committed
1202 1203 1204

  g_return_val_if_fail (GTK_IS_FILE_SELECTION (filesel), nothing);

1205
#ifdef G_WITH_CYGWIN
1206
  translate_win32_path (filesel);
1207
#endif
Elliot Lee's avatar
Elliot Lee committed
1208 1209 1210
  text = gtk_entry_get_text (GTK_ENTRY (filesel->selection_entry));
  if (text)
    {
1211
      sys_filename = g_filename_from_utf8 (cmpl_completion_fullname (text, filesel->cmpl_state), -1, NULL, NULL, NULL);
1212
      if (!sys_filename)
1213
	return nothing;
1214 1215
      strncpy (something, sys_filename, sizeof (something));
      g_free (sys_filename);
1216
      return something;
Elliot Lee's avatar
Elliot Lee committed
1217 1218 1219 1220 1221
    }

  return nothing;
}

1222 1223 1224 1225 1226 1227 1228 1229 1230
void
gtk_file_selection_complete (GtkFileSelection *filesel,
			     const gchar      *pattern)
{
  g_return_if_fail (GTK_IS_FILE_SELECTION (filesel));
  g_return_if_fail (pattern != NULL);

  if (filesel->selection_entry)
    gtk_entry_set_text (GTK_ENTRY (filesel->selection_entry), pattern);
1231
  gtk_file_selection_populate (filesel, (gchar*) pattern, TRUE, TRUE);
1232 1233
}

Elliot Lee's avatar
Elliot Lee committed
1234 1235 1236 1237
static void
gtk_file_selection_destroy (GtkObject *object)
{
  GtkFileSelection *filesel;
1238 1239
  GList *list;
  HistoryCallbackArg *callback_arg;
1240
  
Elliot Lee's avatar
Elliot Lee committed
1241
  g_return_if_fail (GTK_IS_FILE_SELECTION (object));
1242
  
Elliot Lee's avatar
Elliot Lee committed
1243
  filesel = GTK_FILE_SELECTION (object);
1244 1245
  
  if (filesel->fileop_dialog)
1246 1247 1248 1249
    {
      gtk_widget_destroy (filesel->fileop_dialog);
      filesel->fileop_dialog = NULL;
    }
1250
  
1251 1252 1253 1254 1255 1256 1257
  if (filesel->history_list)
    {
      list = filesel->history_list;
      while (list)
	{
	  callback_arg = list->data;
	  g_free (callback_arg->directory);
1258
	  g_free (callback_arg);
1259 1260 1261 1262
	  list = list->next;
	}
      g_list_free (filesel->history_list);
      filesel->history_list = NULL;
1263
    }
Elliot Lee's avatar
Elliot Lee committed
1264

1265 1266 1267 1268 1269
  if (filesel->cmpl_state)
    {
      cmpl_free_state (filesel->cmpl_state);
      filesel->cmpl_state = NULL;
    }
1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
 
  if (filesel->selected_names)
    {
      free_selected_names (filesel->selected_names);
      filesel->selected_names = NULL;
    } 

  if (filesel->last_selected)
    {
      g_free (filesel->last_selected);
      filesel->last_selected = NULL;
    }

1283
  GTK_OBJECT_CLASS (parent_class)->destroy (object);
Elliot Lee's avatar
Elliot Lee committed
1284 1285
}

1286 1287 1288 1289 1290 1291
static void
gtk_file_selection_map (GtkWidget *widget)
{
  GtkFileSelection *filesel = GTK_FILE_SELECTION (widget);

  /* Refresh the contents */
1292
  gtk_file_selection_populate (filesel, "", FALSE, FALSE);
1293 1294 1295 1296
  
  GTK_WIDGET_CLASS (parent_class)->map (widget);
}

1297 1298 1299 1300 1301 1302
static void
gtk_file_selection_finalize (GObject *object)
{
  GtkFileSelection *filesel = GTK_FILE_SELECTION (object);

  g_free (filesel->fileop_file);
1303 1304

  G_OBJECT_CLASS (parent_class)->finalize (object);
1305 1306
}

1307 1308 1309
/* Begin file operations callbacks */

static void
1310 1311
gtk_file_selection_fileop_error (GtkFileSelection *fs,
				 gchar            *error_message)
1312 1313
{
  GtkWidget *dialog;
1314
    
1315 1316
  g_return_if_fail (error_message != NULL);

1317 1318 1319 1320 1321 1322
  /* main dialog */
  dialog = gtk_message_dialog_new (GTK_WINDOW (fs),
				   GTK_DIALOG_DESTROY_WITH_PARENT,
				   GTK_MESSAGE_ERROR,
				   GTK_BUTTONS_CLOSE,
				   "%s", error_message);
1323 1324 1325

  /* yes, we free it */
  g_free (error_message);
1326 1327

  gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1328
 
Manish Singh's avatar
Manish Singh committed
1329 1330 1331
  g_signal_connect_swapped (dialog, "response",
			    G_CALLBACK (gtk_widget_destroy),
			    dialog);
1332 1333 1334 1335 1336

  gtk_widget_show (dialog);
}

static void
1337 1338
gtk_file_selection_fileop_destroy (GtkWidget *widget,
				   gpointer   data)
1339 1340 1341 1342 1343 1344 1345 1346
{
  GtkFileSelection *fs = data;

  g_return_if_fail (GTK_IS_FILE_SELECTION (fs));
  
  fs->fileop_dialog = NULL;
}

1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360
static gboolean
entry_is_empty (GtkEntry *entry)
{
  const gchar *text = gtk_entry_get_text (entry);
  
  return *text == '\0';
}

static void
gtk_file_selection_fileop_entry_changed (GtkEntry   *entry,
					 GtkWidget  *button)
{
  gtk_widget_set_sensitive (button, !entry_is_empty (entry));
}
1361 1362

static void
1363 1364
gtk_file_selection_create_dir_confirmed (GtkWidget *widget,
					 gpointer   data)
1365 1366
{
  GtkFileSelection *fs = data;
1367
  const gchar *dirname;
1368 1369
  gchar *path;
  gchar *full_path;
1370
  gchar *sys_full_path;
1371
  gchar *buf;
1372
  GError *error = NULL;
1373 1374 1375 1376 1377 1378 1379 1380
  CompletionState *cmpl_state;
  
  g_return_if_fail (GTK_IS_FILE_SELECTION (fs));

  dirname = gtk_entry_get_text (GTK_ENTRY (fs->fileop_entry));
  cmpl_state = (CompletionState*) fs->cmpl_state;
  path = cmpl_reference_position (cmpl_state);
  
1381
  full_path = g_strconcat (path, G_DIR_SEPARATOR_S, dirname, NULL);
1382 1383 1384 1385
  sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
  if (error)
    {
      if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
1386
	buf = g_strdup_printf (_("The folder name \"%s\" contains symbols that are not allowed in filenames"), dirname);
1387
      else
1388
	buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n%s"), dirname, error->message,
1389 1390 1391 1392 1393 1394
			       _("You probably used symbols not allowed in filenames."));
      gtk_file_selection_fileop_error (fs, buf);
      g_error_free (error);
      goto out;
    }

1395
  if (mkdir (sys_full_path, 0755) < 0) 
1396
    {
1397
      buf = g_strdup_printf (_("Error creating folder \"%s\": %s\n"), dirname,
1398
			     g_strerror (errno));
1399
      gtk_file_selection_fileop_error (fs, buf);
1400
    }
1401 1402

 out:
1403
  g_free (full_path);
1404
  g_free (sys_full_path);
1405 1406
  
  gtk_widget_destroy (fs->fileop_dialog);
1407
  gtk_file_selection_populate (fs, "", FALSE, FALSE);
1408 1409 1410
}
  
static void
1411 1412
gtk_file_selection_create_dir (GtkWidget *widget,
			       gpointer   data)
1413 1414 1415 1416 1417 1418 1419 1420 1421 1422
{
  GtkFileSelection *fs = data;
  GtkWidget *label;
  GtkWidget *dialog;
  GtkWidget *vbox;
  GtkWidget *button;

  g_return_if_fail (GTK_IS_FILE_SELECTION (fs));

  if (fs->fileop_dialog)
1423
    return;
1424 1425
  
  /* main dialog */
1426 1427
  dialog = gtk_dialog_new ();
  fs->fileop_dialog = dialog;
Manish Singh's avatar
Manish Singh committed
1428 1429 1430
  g_signal_connect (dialog, "destroy",
		    G_CALLBACK (gtk_file_selection_fileop_destroy),
		    fs);
1431
  gtk_window_set_title (GTK_WINDOW (dialog), _("New Folder"));
1432
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE);
1433
  gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (fs));
1434 1435 1436

  /* If file dialog is grabbed, grab option dialog */
  /* When option dialog is closed, file dialog will be grabbed again */
1437 1438
  if (GTK_WINDOW (fs)->modal)
      gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
1439

1440 1441 1442
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 8);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox), vbox,
1443
		     FALSE, FALSE, 0);
1444
  gtk_widget_show( vbox);
1445
  
1446
  label = gtk_label_new_with_mnemonic (_("_Folder name:"));
1447 1448 1449
  gtk_misc_set_alignment(GTK_MISC (label), 0.0, 0.0);
  gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 5);
  gtk_widget_show (label);
1450 1451 1452

  /*  The directory entry widget  */
  fs->fileop_entry = gtk_entry_new ();
1453
  gtk_label_set_mnemonic_widget (GTK_LABEL (label), fs->fileop_entry);
1454 1455
  gtk_box_pack_start (GTK_BOX (vbox), fs->fileop_entry, 
		      TRUE, TRUE, 5);
1456
  GTK_WIDGET_SET_FLAGS (fs->fileop_entry, GTK_CAN_DEFAULT);
1457 1458 1459
  gtk_widget_show (fs->fileop_entry);
  
  /* buttons */
1460
  button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);
Manish Singh's avatar
Manish Singh committed
1461 1462 1463
  g_signal_connect_swapped (button, "clicked",
			    G_CALLBACK (gtk_widget_destroy),
			    dialog);
1464
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
1465
		     button, TRUE, TRUE, 0);
1466 1467
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_widget_grab_default (button);
1468
  gtk_widget_show (button);
1469

1470 1471
  gtk_widget_grab_focus (fs->fileop_entry);

1472 1473
  button = gtk_button_new_with_mnemonic (_("C_reate"));
  gtk_widget_set_sensitive (button, FALSE);
Manish Singh's avatar
Manish Singh committed
1474 1475 1476
  g_signal_connect (button, "clicked",
		    G_CALLBACK (gtk_file_selection_create_dir_confirmed),
		    fs);
1477 1478 1479 1480
  g_signal_connect (fs->fileop_entry, "changed",
                    G_CALLBACK (gtk_file_selection_fileop_entry_changed),
		    button);

1481 1482 1483 1484 1485
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
		     button, TRUE, TRUE, 0);
  GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
  gtk_widget_show (button);
  
1486
  gtk_widget_show (dialog);
1487 1488 1489
}

static void
1490 1491 1492
gtk_file_selection_delete_file_response (GtkDialog *dialog, 
                                         gint       response_id,
                                         gpointer   data)
1493 1494 1495 1496 1497
{
  GtkFileSelection *fs = data;
  CompletionState *cmpl_state;
  gchar *path;
  gchar *full_path;
1498
  gchar *sys_full_path;
1499
  GError *error = NULL;
1500 1501 1502 1503
  gchar *buf;
  
  g_return_if_fail (GTK_IS_FILE_SELECTION (fs));

1504 1505 1506 1507 1508 1509
  if (response_id != GTK_RESPONSE_OK)
    {
      gtk_widget_destroy (GTK_WIDGET (dialog));
      return;
    }

1510 1511 1512
  cmpl_state = (CompletionState*) fs->cmpl_state;
  path = cmpl_reference_position (cmpl_state);
  
1513
  full_path = g_strconcat (path, G_DIR_SEPARATOR_S, fs->fileop_file, NULL);
1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529
  sys_full_path = g_filename_from_utf8 (full_path, -1, NULL, NULL, &error);
  if (error)
    {
      if (g_error_matches (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
	buf = g_strdup_printf (_("The filename \"%s\" contains symbols that are not allowed in filenames"),
			       fs->fileop_file);
      else
	buf = g_strdup_printf (_("Error deleting file \"%s\": %s\n%s"),
			       fs->fileop_file, error->message,
			       _("It probably contains symbols not allowed in filenames."));
      
      gtk_file_selection_fileop_error (fs, buf);
      g_error_free (error);
      goto out;
    }

1530
  if (unlink (sys_full_path) < 0) 
1531
    {