1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is the Mozilla GTK2 File Chooser.
16 : *
17 : * The Initial Developer of the Original Code is Red Hat, Inc.
18 : * Portions created by the Initial Developer are Copyright (C) 2004
19 : * the Initial Developer. All Rights Reserved.
20 : *
21 : * Contributor(s):
22 : * Christopher Aillon <caillon@redhat.com>
23 : *
24 : * Alternatively, the contents of this file may be used under the terms of
25 : * either the GNU General Public License Version 2 or later (the "GPL"), or
26 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 : * in which case the provisions of the GPL or the LGPL are applicable instead
28 : * of those above. If you wish to allow use of your version of this file only
29 : * under the terms of either the GPL or the LGPL, and not to allow others to
30 : * use your version of this file under the terms of the MPL, indicate your
31 : * decision by deleting the provisions above and replace them with the notice
32 : * and other provisions required by the GPL or the LGPL. If you do not delete
33 : * the provisions above, a recipient may use your version of this file under
34 : * the terms of any one of the MPL, the GPL or the LGPL.
35 : *
36 : * ***** END LICENSE BLOCK ***** */
37 :
38 : #include "mozilla/Util.h"
39 :
40 : #include <gtk/gtk.h>
41 :
42 : #include "nsIFileURL.h"
43 : #include "nsIURI.h"
44 : #include "nsIWidget.h"
45 : #include "nsILocalFile.h"
46 : #include "nsIStringBundle.h"
47 :
48 : #include "nsArrayEnumerator.h"
49 : #include "nsMemory.h"
50 : #include "nsEnumeratorUtils.h"
51 : #include "nsNetUtil.h"
52 : #include "nsReadableUtils.h"
53 : #include "mozcontainer.h"
54 :
55 : #include "prmem.h"
56 : #include "prlink.h"
57 :
58 : #include "nsFilePicker.h"
59 :
60 : #if (MOZ_PLATFORM_MAEMO == 5)
61 : #include <hildon-fm-2/hildon/hildon-file-chooser-dialog.h>
62 : #endif
63 :
64 : using namespace mozilla;
65 :
66 : #define MAX_PREVIEW_SIZE 180
67 :
68 : nsILocalFile *nsFilePicker::mPrevDisplayDirectory = nsnull;
69 :
70 : // XXXdholbert -- this function is duplicated in nsPrintDialogGTK.cpp
71 : // and needs to be unified in some generic utility class.
72 : static GtkWindow *
73 0 : get_gtk_window_for_nsiwidget(nsIWidget *widget)
74 : {
75 : // Get native GdkWindow
76 0 : GdkWindow *gdk_win = GDK_WINDOW(widget->GetNativeData(NS_NATIVE_WIDGET));
77 0 : if (!gdk_win)
78 0 : return NULL;
79 :
80 : // Get the container
81 0 : gpointer user_data = NULL;
82 0 : gdk_window_get_user_data(gdk_win, &user_data);
83 0 : if (!user_data)
84 0 : return NULL;
85 :
86 : // Make sure its really a container
87 0 : MozContainer *parent_container = MOZ_CONTAINER(user_data);
88 0 : if (!parent_container)
89 0 : return NULL;
90 :
91 : // Get its toplevel
92 0 : return GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(parent_container)));
93 : }
94 :
95 : void
96 1387 : nsFilePicker::Shutdown()
97 : {
98 1387 : NS_IF_RELEASE(mPrevDisplayDirectory);
99 1387 : }
100 :
101 : static GtkFileChooserAction
102 0 : GetGtkFileChooserAction(PRInt16 aMode)
103 : {
104 : GtkFileChooserAction action;
105 :
106 0 : switch (aMode) {
107 : case nsIFilePicker::modeSave:
108 0 : action = GTK_FILE_CHOOSER_ACTION_SAVE;
109 0 : break;
110 :
111 : case nsIFilePicker::modeGetFolder:
112 0 : action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
113 0 : break;
114 :
115 : case nsIFilePicker::modeOpen:
116 : case nsIFilePicker::modeOpenMultiple:
117 0 : action = GTK_FILE_CHOOSER_ACTION_OPEN;
118 0 : break;
119 :
120 : default:
121 0 : NS_WARNING("Unknown nsIFilePicker mode");
122 0 : action = GTK_FILE_CHOOSER_ACTION_OPEN;
123 0 : break;
124 : }
125 :
126 0 : return action;
127 : }
128 :
129 :
130 : static void
131 0 : UpdateFilePreviewWidget(GtkFileChooser *file_chooser,
132 : gpointer preview_widget_voidptr)
133 : {
134 0 : GtkImage *preview_widget = GTK_IMAGE(preview_widget_voidptr);
135 0 : char *image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
136 :
137 0 : if (!image_filename) {
138 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
139 0 : return;
140 : }
141 :
142 0 : gint preview_width = 0;
143 0 : gint preview_height = 0;
144 : GdkPixbufFormat *preview_format = gdk_pixbuf_get_file_info(image_filename,
145 : &preview_width,
146 0 : &preview_height);
147 0 : if (!preview_format) {
148 0 : g_free(image_filename);
149 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
150 0 : return;
151 : }
152 :
153 : GdkPixbuf *preview_pixbuf;
154 : // Only scale down images that are too big
155 0 : if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
156 : preview_pixbuf = gdk_pixbuf_new_from_file_at_size(image_filename,
157 : MAX_PREVIEW_SIZE,
158 0 : MAX_PREVIEW_SIZE, NULL);
159 : }
160 : else {
161 0 : preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, NULL);
162 : }
163 :
164 0 : g_free(image_filename);
165 :
166 0 : if (!preview_pixbuf) {
167 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
168 0 : return;
169 : }
170 :
171 : // This is the easiest way to do center alignment without worrying about containers
172 : // Minimum 3px padding each side (hence the 6) just to make things nice
173 0 : gint x_padding = (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
174 0 : gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
175 :
176 0 : gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
177 0 : g_object_unref(preview_pixbuf);
178 0 : gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
179 : }
180 :
181 : static nsCAutoString
182 0 : MakeCaseInsensitiveShellGlob(const char* aPattern) {
183 : // aPattern is UTF8
184 0 : nsCAutoString result;
185 0 : unsigned int len = strlen(aPattern);
186 :
187 0 : for (unsigned int i = 0; i < len; i++) {
188 0 : if (!g_ascii_isalpha(aPattern[i])) {
189 : // non-ASCII characters will also trigger this path, so unicode
190 : // is safely handled albeit case-sensitively
191 0 : result.Append(aPattern[i]);
192 0 : continue;
193 : }
194 :
195 : // add the lowercase and uppercase version of a character to a bracket
196 : // match, so it matches either the lowercase or uppercase char.
197 0 : result.Append('[');
198 0 : result.Append(g_ascii_tolower(aPattern[i]));
199 0 : result.Append(g_ascii_toupper(aPattern[i]));
200 0 : result.Append(']');
201 :
202 : }
203 :
204 : return result;
205 : }
206 :
207 0 : NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker)
208 :
209 0 : nsFilePicker::nsFilePicker()
210 : : mMode(nsIFilePicker::modeOpen),
211 : mSelectedType(0),
212 0 : mAllowURLs(false)
213 : {
214 0 : }
215 :
216 0 : nsFilePicker::~nsFilePicker()
217 : {
218 0 : }
219 :
220 : void
221 0 : ReadMultipleFiles(gpointer filename, gpointer array)
222 : {
223 0 : nsCOMPtr<nsILocalFile> localfile;
224 0 : nsresult rv = NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
225 : false,
226 0 : getter_AddRefs(localfile));
227 0 : if (NS_SUCCEEDED(rv)) {
228 0 : nsCOMArray<nsILocalFile>& files = *static_cast<nsCOMArray<nsILocalFile>*>(array);
229 0 : files.AppendObject(localfile);
230 : }
231 :
232 0 : g_free(filename);
233 0 : }
234 :
235 : void
236 0 : nsFilePicker::ReadValuesFromFileChooser(GtkWidget *file_chooser)
237 : {
238 0 : mFiles.Clear();
239 :
240 0 : if (mMode == nsIFilePicker::modeOpenMultiple) {
241 0 : mFileURL.Truncate();
242 :
243 0 : GSList *list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
244 0 : g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
245 0 : g_slist_free(list);
246 : } else {
247 0 : gchar *filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
248 0 : mFileURL.Assign(filename);
249 0 : g_free(filename);
250 : }
251 :
252 0 : GtkFileFilter *filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
253 0 : GSList *filter_list = gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
254 :
255 0 : mSelectedType = static_cast<PRInt16>(g_slist_index(filter_list, filter));
256 0 : g_slist_free(filter_list);
257 :
258 : // Remember last used directory.
259 0 : nsCOMPtr<nsILocalFile> file;
260 0 : GetFile(getter_AddRefs(file));
261 0 : if (file) {
262 0 : nsCOMPtr<nsIFile> dir;
263 0 : file->GetParent(getter_AddRefs(dir));
264 0 : nsCOMPtr<nsILocalFile> localDir(do_QueryInterface(dir));
265 0 : if (localDir) {
266 0 : localDir.swap(mPrevDisplayDirectory);
267 : }
268 : }
269 0 : }
270 :
271 : void
272 0 : nsFilePicker::InitNative(nsIWidget *aParent,
273 : const nsAString& aTitle,
274 : PRInt16 aMode)
275 : {
276 0 : mParentWidget = aParent;
277 0 : mTitle.Assign(aTitle);
278 0 : mMode = aMode;
279 0 : }
280 :
281 : NS_IMETHODIMP
282 0 : nsFilePicker::AppendFilters(PRInt32 aFilterMask)
283 : {
284 0 : mAllowURLs = !!(aFilterMask & filterAllowURLs);
285 0 : return nsBaseFilePicker::AppendFilters(aFilterMask);
286 : }
287 :
288 : NS_IMETHODIMP
289 0 : nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter)
290 : {
291 0 : if (aFilter.EqualsLiteral("..apps")) {
292 : // No platform specific thing we can do here, really....
293 0 : return NS_OK;
294 : }
295 :
296 0 : nsCAutoString filter, name;
297 0 : CopyUTF16toUTF8(aFilter, filter);
298 0 : CopyUTF16toUTF8(aTitle, name);
299 :
300 0 : mFilters.AppendElement(filter);
301 0 : mFilterNames.AppendElement(name);
302 :
303 0 : return NS_OK;
304 : }
305 :
306 : NS_IMETHODIMP
307 0 : nsFilePicker::SetDefaultString(const nsAString& aString)
308 : {
309 0 : mDefault = aString;
310 :
311 0 : return NS_OK;
312 : }
313 :
314 : NS_IMETHODIMP
315 0 : nsFilePicker::GetDefaultString(nsAString& aString)
316 : {
317 : // Per API...
318 0 : return NS_ERROR_FAILURE;
319 : }
320 :
321 : NS_IMETHODIMP
322 0 : nsFilePicker::SetDefaultExtension(const nsAString& aExtension)
323 : {
324 0 : mDefaultExtension = aExtension;
325 :
326 0 : return NS_OK;
327 : }
328 :
329 : NS_IMETHODIMP
330 0 : nsFilePicker::GetDefaultExtension(nsAString& aExtension)
331 : {
332 0 : aExtension = mDefaultExtension;
333 :
334 0 : return NS_OK;
335 : }
336 :
337 : NS_IMETHODIMP
338 0 : nsFilePicker::GetFilterIndex(PRInt32 *aFilterIndex)
339 : {
340 0 : *aFilterIndex = mSelectedType;
341 :
342 0 : return NS_OK;
343 : }
344 :
345 : NS_IMETHODIMP
346 0 : nsFilePicker::SetFilterIndex(PRInt32 aFilterIndex)
347 : {
348 0 : mSelectedType = aFilterIndex;
349 :
350 0 : return NS_OK;
351 : }
352 :
353 : NS_IMETHODIMP
354 0 : nsFilePicker::GetFile(nsILocalFile **aFile)
355 : {
356 0 : NS_ENSURE_ARG_POINTER(aFile);
357 :
358 0 : *aFile = nsnull;
359 0 : nsCOMPtr<nsIURI> uri;
360 0 : nsresult rv = GetFileURL(getter_AddRefs(uri));
361 0 : if (!uri)
362 0 : return rv;
363 :
364 0 : nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
365 0 : NS_ENSURE_SUCCESS(rv, rv);
366 :
367 0 : nsCOMPtr<nsIFile> file;
368 0 : rv = fileURL->GetFile(getter_AddRefs(file));
369 0 : NS_ENSURE_SUCCESS(rv, rv);
370 :
371 0 : return CallQueryInterface(file, aFile);
372 : }
373 :
374 : NS_IMETHODIMP
375 0 : nsFilePicker::GetFileURL(nsIURI **aFileURL)
376 : {
377 0 : *aFileURL = nsnull;
378 0 : return NS_NewURI(aFileURL, mFileURL);
379 : }
380 :
381 : NS_IMETHODIMP
382 0 : nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles)
383 : {
384 0 : NS_ENSURE_ARG_POINTER(aFiles);
385 :
386 0 : if (mMode == nsIFilePicker::modeOpenMultiple) {
387 0 : return NS_NewArrayEnumerator(aFiles, mFiles);
388 : }
389 :
390 0 : return NS_ERROR_FAILURE;
391 : }
392 :
393 : NS_IMETHODIMP
394 0 : nsFilePicker::Show(PRInt16 *aReturn)
395 : {
396 0 : NS_ENSURE_ARG_POINTER(aReturn);
397 :
398 0 : nsXPIDLCString title;
399 0 : title.Adopt(ToNewUTF8String(mTitle));
400 :
401 0 : GtkWindow *parent_widget = get_gtk_window_for_nsiwidget(mParentWidget);
402 :
403 0 : GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
404 : const gchar *accept_button = (action == GTK_FILE_CHOOSER_ACTION_SAVE)
405 0 : ? GTK_STOCK_SAVE : GTK_STOCK_OPEN;
406 : #if (MOZ_PLATFORM_MAEMO == 5)
407 : GtkWidget *file_chooser =
408 : hildon_file_chooser_dialog_new_with_properties(parent_widget,
409 : "action", action,
410 : "open-button-text", accept_button,
411 : NULL);
412 : gtk_window_set_title(GTK_WINDOW(file_chooser), title);
413 : #else
414 : GtkWidget *file_chooser =
415 : gtk_file_chooser_dialog_new(title, parent_widget, action,
416 : GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
417 : accept_button, GTK_RESPONSE_ACCEPT,
418 0 : NULL);
419 0 : gtk_dialog_set_alternative_button_order(GTK_DIALOG(file_chooser),
420 : GTK_RESPONSE_ACCEPT,
421 : GTK_RESPONSE_CANCEL,
422 0 : -1);
423 0 : if (mAllowURLs) {
424 0 : gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
425 : }
426 : #endif
427 :
428 0 : if (action == GTK_FILE_CHOOSER_ACTION_OPEN || action == GTK_FILE_CHOOSER_ACTION_SAVE) {
429 0 : GtkWidget *img_preview = gtk_image_new();
430 0 : gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser), img_preview);
431 0 : g_signal_connect(file_chooser, "update-preview", G_CALLBACK(UpdateFilePreviewWidget), img_preview);
432 : }
433 :
434 0 : if (parent_widget && parent_widget->group) {
435 0 : gtk_window_group_add_window(parent_widget->group, GTK_WINDOW(file_chooser));
436 : }
437 :
438 0 : NS_ConvertUTF16toUTF8 defaultName(mDefault);
439 0 : switch (mMode) {
440 : case nsIFilePicker::modeOpenMultiple:
441 0 : gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser), TRUE);
442 0 : break;
443 : case nsIFilePicker::modeSave:
444 0 : gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
445 0 : defaultName.get());
446 0 : break;
447 : }
448 :
449 0 : nsCOMPtr<nsIFile> defaultPath;
450 0 : if (mDisplayDirectory) {
451 0 : mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
452 0 : } else if (mPrevDisplayDirectory) {
453 0 : mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
454 : }
455 :
456 0 : if (defaultPath) {
457 0 : if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
458 : // Try to select the intended file. Even if it doesn't exist, GTK still switches
459 : // directories.
460 0 : defaultPath->AppendNative(defaultName);
461 0 : nsCAutoString path;
462 0 : defaultPath->GetNativePath(path);
463 0 : gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
464 : } else {
465 0 : nsCAutoString directory;
466 0 : defaultPath->GetNativePath(directory);
467 0 : gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
468 0 : directory.get());
469 : }
470 : }
471 :
472 0 : gtk_dialog_set_default_response(GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT);
473 :
474 0 : PRInt32 count = mFilters.Length();
475 0 : for (PRInt32 i = 0; i < count; ++i) {
476 : // This is fun... the GTK file picker does not accept a list of filters
477 : // so we need to split out each string, and add it manually.
478 :
479 0 : char **patterns = g_strsplit(mFilters[i].get(), ";", -1);
480 0 : if (!patterns) {
481 0 : return NS_ERROR_OUT_OF_MEMORY;
482 : }
483 :
484 0 : GtkFileFilter *filter = gtk_file_filter_new();
485 0 : for (int j = 0; patterns[j] != NULL; ++j) {
486 0 : nsCAutoString caseInsensitiveFilter = MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
487 0 : gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
488 : }
489 :
490 0 : g_strfreev(patterns);
491 :
492 0 : if (!mFilterNames[i].IsEmpty()) {
493 : // If we have a name for our filter, let's use that.
494 0 : const char *filter_name = mFilterNames[i].get();
495 0 : gtk_file_filter_set_name(filter, filter_name);
496 : } else {
497 : // If we don't have a name, let's just use the filter pattern.
498 0 : const char *filter_pattern = mFilters[i].get();
499 0 : gtk_file_filter_set_name(filter, filter_pattern);
500 : }
501 :
502 0 : gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
503 :
504 : // Set the initially selected filter
505 0 : if (mSelectedType == i) {
506 0 : gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
507 : }
508 : }
509 :
510 0 : gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser), TRUE);
511 0 : gint response = gtk_dialog_run(GTK_DIALOG(file_chooser));
512 :
513 0 : switch (response) {
514 : case GTK_RESPONSE_OK:
515 : case GTK_RESPONSE_ACCEPT:
516 0 : ReadValuesFromFileChooser(file_chooser);
517 0 : *aReturn = nsIFilePicker::returnOK;
518 0 : if (mMode == nsIFilePicker::modeSave) {
519 0 : nsCOMPtr<nsILocalFile> file;
520 0 : GetFile(getter_AddRefs(file));
521 0 : if (file) {
522 0 : bool exists = false;
523 0 : file->Exists(&exists);
524 0 : if (exists)
525 0 : *aReturn = nsIFilePicker::returnReplace;
526 : }
527 : }
528 0 : break;
529 :
530 : case GTK_RESPONSE_CANCEL:
531 : case GTK_RESPONSE_CLOSE:
532 : case GTK_RESPONSE_DELETE_EVENT:
533 0 : *aReturn = nsIFilePicker::returnCancel;
534 0 : break;
535 :
536 : default:
537 0 : NS_WARNING("Unexpected response");
538 0 : *aReturn = nsIFilePicker::returnCancel;
539 0 : break;
540 : }
541 :
542 0 : gtk_widget_destroy(file_chooser);
543 :
544 0 : return NS_OK;
545 : }
|