widget/gtk/nsFilePicker.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 #include "mozilla/Types.h"
     8 #include <gtk/gtk.h>
    10 #include "nsGtkUtils.h"
    11 #include "nsIFileURL.h"
    12 #include "nsIURI.h"
    13 #include "nsIWidget.h"
    14 #include "nsIFile.h"
    15 #include "nsIStringBundle.h"
    17 #include "nsArrayEnumerator.h"
    18 #include "nsMemory.h"
    19 #include "nsEnumeratorUtils.h"
    20 #include "nsNetUtil.h"
    21 #include "nsReadableUtils.h"
    22 #include "mozcontainer.h"
    24 #include "nsFilePicker.h"
    26 using namespace mozilla;
    28 #define MAX_PREVIEW_SIZE 180
    30 nsIFile *nsFilePicker::mPrevDisplayDirectory = nullptr;
    32 void
    33 nsFilePicker::Shutdown()
    34 {
    35   NS_IF_RELEASE(mPrevDisplayDirectory);
    36 }
    38 static GtkFileChooserAction
    39 GetGtkFileChooserAction(int16_t aMode)
    40 {
    41   GtkFileChooserAction action;
    43   switch (aMode) {
    44     case nsIFilePicker::modeSave:
    45     action = GTK_FILE_CHOOSER_ACTION_SAVE;
    46     break;
    48     case nsIFilePicker::modeGetFolder:
    49     action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
    50     break;
    52     case nsIFilePicker::modeOpen:
    53     case nsIFilePicker::modeOpenMultiple:
    54     action = GTK_FILE_CHOOSER_ACTION_OPEN;
    55     break;
    57     default:
    58     NS_WARNING("Unknown nsIFilePicker mode");
    59     action = GTK_FILE_CHOOSER_ACTION_OPEN;
    60     break;
    61   }
    63   return action;
    64 }
    67 static void
    68 UpdateFilePreviewWidget(GtkFileChooser *file_chooser,
    69                         gpointer preview_widget_voidptr)
    70 {
    71   GtkImage *preview_widget = GTK_IMAGE(preview_widget_voidptr);
    72   char *image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
    74   if (!image_filename) {
    75     gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
    76     return;
    77   }
    79   gint preview_width = 0;
    80   gint preview_height = 0;
    81   GdkPixbufFormat *preview_format = gdk_pixbuf_get_file_info(image_filename,
    82                                                              &preview_width,
    83                                                              &preview_height);
    84   if (!preview_format) {
    85     g_free(image_filename);
    86     gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
    87     return;
    88   }
    90   GdkPixbuf *preview_pixbuf;
    91   // Only scale down images that are too big
    92   if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
    93     preview_pixbuf = gdk_pixbuf_new_from_file_at_size(image_filename,
    94                                                       MAX_PREVIEW_SIZE,
    95                                                       MAX_PREVIEW_SIZE,
    96                                                       nullptr);
    97   }
    98   else {
    99     preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr);
   100   }
   102   g_free(image_filename);
   104   if (!preview_pixbuf) {
   105     gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
   106     return;
   107   }
   109 #if GTK_CHECK_VERSION(2,12,0)
   110   GdkPixbuf *preview_pixbuf_temp = preview_pixbuf;
   111   preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp);
   112   g_object_unref(preview_pixbuf_temp);
   113 #endif
   115   // This is the easiest way to do center alignment without worrying about containers
   116   // Minimum 3px padding each side (hence the 6) just to make things nice
   117   gint x_padding = (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
   118   gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
   120   gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
   121   g_object_unref(preview_pixbuf);
   122   gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
   123 }
   125 static nsAutoCString
   126 MakeCaseInsensitiveShellGlob(const char* aPattern) {
   127   // aPattern is UTF8
   128   nsAutoCString result;
   129   unsigned int len = strlen(aPattern);
   131   for (unsigned int i = 0; i < len; i++) {
   132     if (!g_ascii_isalpha(aPattern[i])) {
   133       // non-ASCII characters will also trigger this path, so unicode
   134       // is safely handled albeit case-sensitively
   135       result.Append(aPattern[i]);
   136       continue;
   137     }
   139     // add the lowercase and uppercase version of a character to a bracket
   140     // match, so it matches either the lowercase or uppercase char.
   141     result.Append('[');
   142     result.Append(g_ascii_tolower(aPattern[i]));
   143     result.Append(g_ascii_toupper(aPattern[i]));
   144     result.Append(']');
   146   }
   148   return result;
   149 }
   151 NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
   153 nsFilePicker::nsFilePicker()
   154   : mSelectedType(0),
   155     mRunning(false),
   156     mAllowURLs(false)
   157 {
   158 }
   160 nsFilePicker::~nsFilePicker()
   161 {
   162 }
   164 void
   165 ReadMultipleFiles(gpointer filename, gpointer array)
   166 {
   167   nsCOMPtr<nsIFile> localfile;
   168   nsresult rv = NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
   169                                       false,
   170                                       getter_AddRefs(localfile));
   171   if (NS_SUCCEEDED(rv)) {
   172     nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
   173     files.AppendObject(localfile);
   174   }
   176   g_free(filename);
   177 }
   179 void
   180 nsFilePicker::ReadValuesFromFileChooser(GtkWidget *file_chooser)
   181 {
   182   mFiles.Clear();
   184   if (mMode == nsIFilePicker::modeOpenMultiple) {
   185     mFileURL.Truncate();
   187     GSList *list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
   188     g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
   189     g_slist_free(list);
   190   } else {
   191     gchar *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
   192     mFileURL.Assign(filename);
   193     g_free(filename);
   194   }
   196   GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
   197   GSList *filter_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
   199   mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter));
   200   g_slist_free(filter_list);
   202   // Remember last used directory.
   203   nsCOMPtr<nsIFile> file;
   204   GetFile(getter_AddRefs(file));
   205   if (file) {
   206     nsCOMPtr<nsIFile> dir;
   207     file->GetParent(getter_AddRefs(dir));
   208     if (dir) {
   209       dir.swap(mPrevDisplayDirectory);
   210     }
   211   }
   212 }
   214 void
   215 nsFilePicker::InitNative(nsIWidget *aParent,
   216                          const nsAString& aTitle)
   217 {
   218   mParentWidget = aParent;
   219   mTitle.Assign(aTitle);
   220 }
   222 NS_IMETHODIMP
   223 nsFilePicker::AppendFilters(int32_t aFilterMask)
   224 {
   225   mAllowURLs = !!(aFilterMask & filterAllowURLs);
   226   return nsBaseFilePicker::AppendFilters(aFilterMask);
   227 }
   229 NS_IMETHODIMP
   230 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
   231 {
   232   if (aFilter.EqualsLiteral("..apps")) {
   233     // No platform specific thing we can do here, really....
   234     return NS_OK;
   235   }
   237   nsAutoCString filter, name;
   238   CopyUTF16toUTF8(aFilter, filter);
   239   CopyUTF16toUTF8(aTitle, name);
   241   mFilters.AppendElement(filter);
   242   mFilterNames.AppendElement(name);
   244   return NS_OK;
   245 }
   247 NS_IMETHODIMP
   248 nsFilePicker::SetDefaultString(const nsAString& aString)
   249 {
   250   mDefault = aString;
   252   return NS_OK;
   253 }
   255 NS_IMETHODIMP
   256 nsFilePicker::GetDefaultString(nsAString& aString)
   257 {
   258   // Per API...
   259   return NS_ERROR_FAILURE;
   260 }
   262 NS_IMETHODIMP
   263 nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
   264 {
   265   mDefaultExtension = aExtension;
   267   return NS_OK;
   268 }
   270 NS_IMETHODIMP
   271 nsFilePicker::GetDefaultExtension(nsAString& aExtension)
   272 {
   273   aExtension = mDefaultExtension;
   275   return NS_OK;
   276 }
   278 NS_IMETHODIMP
   279 nsFilePicker::GetFilterIndex(int32_t *aFilterIndex)
   280 {
   281   *aFilterIndex = mSelectedType;
   283   return NS_OK;
   284 }
   286 NS_IMETHODIMP
   287 nsFilePicker::SetFilterIndex(int32_t aFilterIndex)
   288 {
   289   mSelectedType = aFilterIndex;
   291   return NS_OK;
   292 }
   294 NS_IMETHODIMP
   295 nsFilePicker::GetFile(nsIFile **aFile)
   296 {
   297   NS_ENSURE_ARG_POINTER(aFile);
   299   *aFile = nullptr;
   300   nsCOMPtr<nsIURI> uri;
   301   nsresult rv = GetFileURL(getter_AddRefs(uri));
   302   if (!uri)
   303     return rv;
   305   nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
   306   NS_ENSURE_SUCCESS(rv, rv);
   308   nsCOMPtr<nsIFile> file;
   309   rv = fileURL->GetFile(getter_AddRefs(file));
   310   NS_ENSURE_SUCCESS(rv, rv);
   312   file.forget(aFile);
   313   return NS_OK;
   314 }
   316 NS_IMETHODIMP
   317 nsFilePicker::GetFileURL(nsIURI **aFileURL)
   318 {
   319   *aFileURL = nullptr;
   320   return NS_NewURI(aFileURL, mFileURL);
   321 }
   323 NS_IMETHODIMP
   324 nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
   325 {
   326   NS_ENSURE_ARG_POINTER(aFiles);
   328   if (mMode == nsIFilePicker::modeOpenMultiple) {
   329     return NS_NewArrayEnumerator(aFiles, mFiles);
   330   }
   332   return NS_ERROR_FAILURE;
   333 }
   335 NS_IMETHODIMP
   336 nsFilePicker::Show(int16_t *aReturn)
   337 {
   338   NS_ENSURE_ARG_POINTER(aReturn);
   340   nsresult rv = Open(nullptr);
   341   if (NS_FAILED(rv))
   342     return rv;
   344   while (mRunning) {
   345     g_main_context_iteration(nullptr, TRUE);
   346   }
   348   *aReturn = mResult;
   349   return NS_OK;
   350 }
   352 NS_IMETHODIMP
   353 nsFilePicker::Open(nsIFilePickerShownCallback *aCallback)
   354 {
   355   // Can't show two dialogs concurrently with the same filepicker
   356   if (mRunning)
   357     return NS_ERROR_NOT_AVAILABLE;
   359   nsXPIDLCString title;
   360   title.Adopt(ToNewUTF8String(mTitle));
   362   GtkWindow *parent_widget =
   363     GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
   365   GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
   366   const gchar *accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE)
   367                                ? GTK_STOCK_SAVE : GTK_STOCK_OPEN;
   368   GtkWidget *file_chooser =
   369       gtk_file_chooser_dialog_new(title, parent_widget, action,
   370                                   GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
   371                                   accept_button, GTK_RESPONSE_ACCEPT,
   372                                   nullptr);
   373   gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser),
   374                                           GTK_RESPONSE_ACCEPT,
   375                                           GTK_RESPONSE_CANCEL,
   376                                           -1);
   377   if (mAllowURLs) {
   378     gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
   379   }
   381   if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) {
   382     GtkWidget *img_preview = gtk_image_new();
   383     gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview);
   384     g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview);
   385   }
   387   GtkWindow *window = GTK_WINDOW(file_chooser);
   388   gtk_window_set_modal(window, TRUE);
   389   if (parent_widget) {
   390     gtk_window_set_destroy_with_parent(window, TRUE);
   391   }
   393   NS_ConvertUTF16toUTF8 defaultName(mDefault);
   394   switch (mMode) {
   395     case nsIFilePicker::modeOpenMultiple:
   396       gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE);
   397       break;
   398     case nsIFilePicker::modeSave:
   399       gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
   400                                         defaultName.get());
   401       break;
   402   }
   404   nsCOMPtr<nsIFile> defaultPath;
   405   if (mDisplayDirectory) {
   406     mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
   407   } else if (mPrevDisplayDirectory) {
   408     mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
   409   }
   411   if (defaultPath) {
   412     if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
   413       // Try to select the intended file. Even if it doesn't exist, GTK still switches
   414       // directories.
   415       defaultPath->AppendNative(defaultName);
   416       nsAutoCString path;
   417       defaultPath->GetNativePath(path);
   418       gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
   419     } else {
   420       nsAutoCString directory;
   421       defaultPath->GetNativePath(directory);
   422       gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
   423                                           directory.get());
   424     }
   425   }
   427   gtk_dialog_set_default_response(GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT);
   429   int32_t count = mFilters.Length();
   430   for (int32_t i = 0; i < count; ++i) {
   431     // This is fun... the GTK file picker does not accept a list of filters
   432     // so we need to split out each string, and add it manually.
   434     char **patterns = g_strsplit(mFilters[i].get(), ";", -1);
   435     if (!patterns) {
   436       return NS_ERROR_OUT_OF_MEMORY;
   437     }
   439     GtkFileFilter *filter = gtk_file_filter_new();
   440     for (int j = 0; patterns[j] != nullptr; ++j) {
   441       nsAutoCString caseInsensitiveFilter = MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
   442       gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
   443     }
   445     g_strfreev(patterns);
   447     if (!mFilterNames[i].IsEmpty()) {
   448       // If we have a name for our filter, let's use that.
   449       const char *filter_name = mFilterNames[i].get();
   450       gtk_file_filter_set_name(filter, filter_name);
   451     } else {
   452       // If we don't have a name, let's just use the filter pattern.
   453       const char *filter_pattern = mFilters[i].get();
   454       gtk_file_filter_set_name(filter, filter_pattern);
   455     }
   457     gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
   459     // Set the initially selected filter
   460     if (mSelectedType == i) {
   461       gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
   462     }
   463   }
   465   gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE);
   467   mRunning = true;
   468   mCallback = aCallback;
   469   NS_ADDREF_THIS();
   470   g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
   471   g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
   472   gtk_widget_show(file_chooser);
   474   return NS_OK;
   475 }
   477 /* static */ void
   478 nsFilePicker::OnResponse(GtkWidget* file_chooser, gint response_id,
   479                          gpointer user_data)
   480 {
   481   static_cast<nsFilePicker*>(user_data)->
   482     Done(file_chooser, response_id);
   483 }
   485 /* static */ void
   486 nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data)
   487 {
   488   static_cast<nsFilePicker*>(user_data)->
   489     Done(file_chooser, GTK_RESPONSE_CANCEL);
   490 }
   492 void
   493 nsFilePicker::Done(GtkWidget* file_chooser, gint response)
   494 {
   495   mRunning = false;
   497   int16_t result;
   498   switch (response) {
   499     case GTK_RESPONSE_OK:
   500     case GTK_RESPONSE_ACCEPT:
   501     ReadValuesFromFileChooser(file_chooser);
   502     result = nsIFilePicker::returnOK;
   503     if (mMode == nsIFilePicker::modeSave) {
   504       nsCOMPtr<nsIFile> file;
   505       GetFile(getter_AddRefs(file));
   506       if (file) {
   507         bool exists = false;
   508         file->Exists(&exists);
   509         if (exists)
   510           result = nsIFilePicker::returnReplace;
   511       }
   512     }
   513     break;
   515     case GTK_RESPONSE_CANCEL:
   516     case GTK_RESPONSE_CLOSE:
   517     case GTK_RESPONSE_DELETE_EVENT:
   518     result = nsIFilePicker::returnCancel;
   519     break;
   521     default:
   522     NS_WARNING("Unexpected response");
   523     result = nsIFilePicker::returnCancel;
   524     break;
   525   }
   527   // A "response" signal won't be sent again but "destroy" will be.
   528   g_signal_handlers_disconnect_by_func(file_chooser,
   529                                        FuncToGpointer(OnDestroy), this);
   531   // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
   532   // OnDestroy, the widget would be destroyed anyway but it is fine if
   533   // gtk_widget_destroy is called more than once.  gtk_widget_destroy has
   534   // requests that any remaining references be released, but the reference
   535   // count will not be decremented again if GtkWindow's reference has already
   536   // been released.
   537   gtk_widget_destroy(file_chooser);
   539   if (mCallback) {
   540     mCallback->Done(result);
   541     mCallback = nullptr;
   542   } else {
   543     mResult = result;
   544   }
   545   NS_RELEASE_THIS();
   546 }

mercurial