# HG changeset patch # Parent 58c9d079f31811f3f325d4f439084a9ceb36764b # User Jan Horak # Bug 1129873 - Implementation of GtkAppChooserDialog wrapper for GTK3 to enable native application chooser in Linux # Parent 7d4ab4a9febdf66c18d752afd4bd241c41be921f try: -b do -p all -u all -t none diff --git a/toolkit/mozapps/downloads/nsHelperAppDlg.js b/toolkit/mozapps/downloads/nsHelperAppDlg.js --- a/toolkit/mozapps/downloads/nsHelperAppDlg.js +++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js @@ -999,16 +999,44 @@ nsUnknownContentTypeDialog.prototype = { try { return file.bundleDisplayName; } catch (e) {} } #endif return file.leafName; }, + finishChooseApp: function() { + if (this.chosenApp) { + // Show the "handler" menulist since we have a (user-specified) + // application now. + this.dialogElement("modeDeck").setAttribute("selectedIndex", "0"); + + // Update dialog. + var otherHandler = this.dialogElement("otherHandler"); + otherHandler.removeAttribute("hidden"); + otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable)); +#ifdef XP_WIN + otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); +#else + otherHandler.label = this.chosenApp.name; +#endif + this.dialogElement("openHandler").selectedIndex = 1; + this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler"); + + this.dialogElement("mode").selectedItem = this.dialogElement("open"); + } + else { + var openHandler = this.dialogElement("openHandler"); + var lastSelectedID = openHandler.getAttribute("lastSelectedItemID"); + if (!lastSelectedID) + lastSelectedID = "defaultHandler"; + openHandler.selectedItem = this.dialogElement(lastSelectedID); + } + }, // chooseApp: Open file picker and prompt user for application. chooseApp: function() { #ifdef XP_WIN // Protect against the lack of an extension var fileExtension = ""; try { fileExtension = this.mLauncher.MIMEInfo.primaryExtension; } catch(ex) { @@ -1042,17 +1070,33 @@ nsUnknownContentTypeDialog.prototype = { "chrome,modal,centerscreen,titlebar,dialog=yes", params); if (params.handlerApp && params.handlerApp.executable && params.handlerApp.executable.isFile()) { // Remember the file they chose to run. this.chosenApp = params.handlerApp; - + } +#else +#if MOZ_WIDGET_GTK == 3 + var nsIApplicationChooser = Components.interfaces.nsIApplicationChooser; + var appChooser = Components.classes["@mozilla.org/applicationchooser;1"] + .createInstance(nsIApplicationChooser); + appChooser.init(this.mDialog, this.dialogElement("strings").getString("chooseAppFilePickerTitle")); + var contentTypeDialogObj = this; + let appChooserCallback = function appChooserCallback_done(aResult) { + if (aResult) { + contentTypeDialogObj.chosenApp = aResult.QueryInterface(Components.interfaces.nsILocalHandlerApp); + } + contentTypeDialogObj.finishChooseApp(); + }; + appChooser.open(this.mLauncher.MIMEInfo.MIMEType, appChooserCallback); + // The finishChooseApp is called from appChooserCallback + return; #else var nsIFilePicker = Components.interfaces.nsIFilePicker; var fp = Components.classes["@mozilla.org/filepicker;1"] .createInstance(nsIFilePicker); fp.init(this.mDialog, this.dialogElement("strings").getString("chooseAppFilePickerTitle"), nsIFilePicker.modeOpen); @@ -1060,39 +1104,21 @@ nsUnknownContentTypeDialog.prototype = { if (fp.show() == nsIFilePicker.returnOK && fp.file) { // Remember the file they chose to run. var localHandlerApp = Components.classes["@mozilla.org/uriloader/local-handler-app;1"]. createInstance(Components.interfaces.nsILocalHandlerApp); localHandlerApp.executable = fp.file; this.chosenApp = localHandlerApp; -#endif + } +#endif // MOZ_WIDGET_GTK3 - // Show the "handler" menulist since we have a (user-specified) - // application now. - this.dialogElement("modeDeck").setAttribute("selectedIndex", "0"); - - // Update dialog. - var otherHandler = this.dialogElement("otherHandler"); - otherHandler.removeAttribute("hidden"); - otherHandler.setAttribute("path", this.getPath(this.chosenApp.executable)); - otherHandler.label = this.getFileDisplayName(this.chosenApp.executable); - this.dialogElement("openHandler").selectedIndex = 1; - this.dialogElement("openHandler").setAttribute("lastSelectedItemID", "otherHandler"); - - this.dialogElement("mode").selectedItem = this.dialogElement("open"); - } - else { - var openHandler = this.dialogElement("openHandler"); - var lastSelectedID = openHandler.getAttribute("lastSelectedItemID"); - if (!lastSelectedID) - lastSelectedID = "defaultHandler"; - openHandler.selectedItem = this.dialogElement(lastSelectedID); - } +#endif // XP_WIN + this.finishChooseApp(); }, // Turn this on to get debugging messages. debug: false, // Dump text (if debug is on). dump: function( text ) { if ( this.debug ) { diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build --- a/widget/gtk/moz.build +++ b/widget/gtk/moz.build @@ -69,16 +69,17 @@ if CONFIG['ACCESSIBILITY']: if CONFIG['MOZ_ENABLE_GTK2']: UNIFIED_SOURCES += [ 'gtk2drawing.c', ] else: UNIFIED_SOURCES += [ 'gtk3drawing.c', + 'nsApplicationChooser.cpp', ] include('/ipc/chromium/chromium-config.mozbuild') FINAL_LIBRARY = 'xul' LOCAL_INCLUDES += [ '/layout/generic', diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c --- a/widget/gtk/mozgtk/mozgtk.c +++ b/widget/gtk/mozgtk/mozgtk.c @@ -535,16 +535,21 @@ STUB(gtk_style_context_save) STUB(gtk_style_context_set_path) STUB(gtk_style_context_set_state) STUB(gtk_tree_view_column_get_button) STUB(gtk_widget_get_preferred_size) STUB(gtk_widget_get_style_context) STUB(gtk_widget_path_append_type) STUB(gtk_widget_path_new) STUB(gtk_widget_set_visual) +STUB(gtk_app_chooser_dialog_new_for_content_type) +STUB(gtk_app_chooser_get_type) +STUB(gtk_app_chooser_get_app_info) +STUB(gtk_app_chooser_dialog_get_type) +STUB(gtk_app_chooser_dialog_set_heading) #endif #ifdef GTK2_SYMBOLS STUB(gdk_drawable_get_screen) STUB(gdk_rgb_get_colormap) STUB(gdk_rgb_get_visual) STUB(gdk_window_lookup) STUB(gdk_window_set_back_pixmap) diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp new file mode 100644 --- /dev/null +++ b/widget/gtk/nsApplicationChooser.cpp @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Types.h" + +#include + +#include "nsApplicationChooser.h" +#include "WidgetUtils.h" +#include "nsIMIMEInfo.h" +#include "nsCExternalHandlerService.h" +#include "nsGtkUtils.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser) + +nsApplicationChooser::nsApplicationChooser() +{ +} + +nsApplicationChooser::~nsApplicationChooser() +{ +} + +NS_IMETHODIMP +nsApplicationChooser::Init(nsIDOMWindow* aParent, const nsACString& aTitle) +{ + NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE); + mParentWidget = widget::WidgetUtils::DOMWindowToWidget(aParent); + mWindowTitle.Assign(aTitle); + return NS_OK; +} + +NS_IMETHODIMP +nsApplicationChooser::Open(const nsACString& aContentType, nsIApplicationChooserFinishedCallback *aCallback) +{ + MOZ_ASSERT(aCallback); + if (mCallback) { + NS_WARNING("Chooser is already in progress."); + return NS_ERROR_ALREADY_INITIALIZED; + } + mCallback = aCallback; + NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE); + GtkWindow *parent_widget = + GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET)); + + GtkWidget* chooser = + gtk_app_chooser_dialog_new_for_content_type(parent_widget, + (GtkDialogFlags) (GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), + PromiseFlatCString(aContentType).get()); + gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser), mWindowTitle.BeginReading()); + NS_ADDREF_THIS(); + g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this); + g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this); + gtk_widget_show(chooser); + return NS_OK; +} + +/* static */ void +nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data) +{ + static_cast(user_data)->Done(chooser, response_id); +} + +/* static */ void +nsApplicationChooser::OnDestroy(GtkWidget *chooser, gpointer user_data) +{ + static_cast(user_data)->Done(chooser, GTK_RESPONSE_CANCEL); +} + +void nsApplicationChooser::Done(GtkWidget* chooser, gint response) +{ + nsCOMPtr localHandler; + nsresult rv; + switch (response) { + case GTK_RESPONSE_OK: + case GTK_RESPONSE_ACCEPT: + { + localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("Out of memory."); + break; + } + GAppInfo *app_info = gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser)); + + nsCOMPtr localExecutable; + gchar *fileWithFullPath = g_find_program_in_path(g_app_info_get_executable(app_info)); + rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false, getter_AddRefs(localExecutable)); + g_free(fileWithFullPath); + if (NS_FAILED(rv)) { + NS_WARNING("Cannot create local filename."); + localHandler = nullptr; + } else { + localHandler->SetExecutable(localExecutable); + localHandler->SetName(NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info))); + } + g_object_unref(app_info); + } + + break; + case GTK_RESPONSE_CANCEL: + case GTK_RESPONSE_CLOSE: + case GTK_RESPONSE_DELETE_EVENT: + break; + default: + NS_WARNING("Unexpected response"); + break; + } + + // A "response" signal won't be sent again but "destroy" will be. + g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy), this); + gtk_widget_destroy(chooser); + + if (mCallback) { + mCallback->Done(localHandler); + mCallback = nullptr; + } + NS_RELEASE_THIS(); +} + diff --git a/widget/gtk/nsApplicationChooser.h b/widget/gtk/nsApplicationChooser.h new file mode 100644 --- /dev/null +++ b/widget/gtk/nsApplicationChooser.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsApplicationChooser_h__ +#define nsApplicationChooser_h__ + +#include +#include "nsIApplicationChooser.h" + +class nsApplicationChooser : public nsIApplicationChooser +{ +public: + nsApplicationChooser(); + NS_DECL_ISUPPORTS + NS_DECL_NSIAPPLICATIONCHOOSER + void Done(GtkWidget* chooser, gint response); + +private: + ~nsApplicationChooser(); + nsCOMPtr mParentWidget; + nsCString mWindowTitle; + nsCOMPtr mCallback; + static void OnResponse(GtkWidget* chooser, gint response_id, gpointer user_data); + static void OnDestroy(GtkWidget* chooser, gpointer user_data); +}; +#endif diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp --- a/widget/gtk/nsWidgetFactory.cpp +++ b/widget/gtk/nsWidgetFactory.cpp @@ -16,16 +16,19 @@ #include "nsWindow.h" #include "nsTransferable.h" #include "nsHTMLFormatConverter.h" #ifdef MOZ_X11 #include "nsClipboardHelper.h" #include "nsClipboard.h" #include "nsDragService.h" #endif +#if (MOZ_WIDGET_GTK == 3) +#include "nsApplicationChooser.h" +#endif #include "nsColorPicker.h" #include "nsFilePicker.h" #include "nsSound.h" #include "nsBidiKeyboard.h" #include "nsScreenManagerGtk.h" #include "nsGTKToolkit.h" #include "WakeLockListener.h" @@ -147,16 +150,35 @@ nsFilePickerConstructor(nsISupports *aOu if (!picker) { return NS_ERROR_OUT_OF_MEMORY; } return picker->QueryInterface(aIID, aResult); } +#if (MOZ_WIDGET_GTK == 3) +static nsresult +nsApplicationChooserConstructor(nsISupports *aOuter, REFNSIID aIID, + void **aResult) +{ + *aResult = nullptr; + if (aOuter != nullptr) { + return NS_ERROR_NO_AGGREGATION; + } + nsCOMPtr chooser = new nsApplicationChooser; + + if (!chooser) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return chooser->QueryInterface(aIID, aResult); +} +#endif + static nsresult nsColorPickerConstructor(nsISupports *aOuter, REFNSIID aIID, void **aResult) { *aResult = nullptr; if (aOuter != nullptr) { return NS_ERROR_NO_AGGREGATION; } @@ -170,16 +192,19 @@ nsColorPickerConstructor(nsISupports *aO return picker->QueryInterface(aIID, aResult); } NS_DEFINE_NAMED_CID(NS_WINDOW_CID); NS_DEFINE_NAMED_CID(NS_CHILD_CID); NS_DEFINE_NAMED_CID(NS_APPSHELL_CID); NS_DEFINE_NAMED_CID(NS_COLORPICKER_CID); NS_DEFINE_NAMED_CID(NS_FILEPICKER_CID); +#if (MOZ_WIDGET_GTK == 3) +NS_DEFINE_NAMED_CID(NS_APPLICATIONCHOOSER_CID); +#endif NS_DEFINE_NAMED_CID(NS_SOUND_CID); NS_DEFINE_NAMED_CID(NS_TRANSFERABLE_CID); #ifdef MOZ_X11 NS_DEFINE_NAMED_CID(NS_CLIPBOARD_CID); NS_DEFINE_NAMED_CID(NS_CLIPBOARDHELPER_CID); NS_DEFINE_NAMED_CID(NS_DRAGSERVICE_CID); #endif NS_DEFINE_NAMED_CID(NS_HTMLFORMATCONVERTER_CID); @@ -201,16 +226,19 @@ NS_DEFINE_NAMED_CID(NS_GFXINFO_CID); static const mozilla::Module::CIDEntry kWidgetCIDs[] = { { &kNS_WINDOW_CID, false, nullptr, nsWindowConstructor }, { &kNS_CHILD_CID, false, nullptr, nsChildWindowConstructor }, { &kNS_APPSHELL_CID, false, nullptr, nsAppShellConstructor }, { &kNS_COLORPICKER_CID, false, nullptr, nsColorPickerConstructor, Module::MAIN_PROCESS_ONLY }, { &kNS_FILEPICKER_CID, false, nullptr, nsFilePickerConstructor, Module::MAIN_PROCESS_ONLY }, +#if (MOZ_WIDGET_GTK == 3) + { &kNS_APPLICATIONCHOOSER_CID, false, nullptr, nsApplicationChooserConstructor, Module::MAIN_PROCESS_ONLY }, +#endif { &kNS_SOUND_CID, false, nullptr, nsSoundConstructor, Module::MAIN_PROCESS_ONLY }, { &kNS_TRANSFERABLE_CID, false, nullptr, nsTransferableConstructor }, #ifdef MOZ_X11 { &kNS_CLIPBOARD_CID, false, nullptr, nsClipboardConstructor, Module::MAIN_PROCESS_ONLY }, { &kNS_CLIPBOARDHELPER_CID, false, nullptr, nsClipboardHelperConstructor }, { &kNS_DRAGSERVICE_CID, false, nullptr, nsDragServiceConstructor, Module::MAIN_PROCESS_ONLY }, #endif { &kNS_HTMLFORMATCONVERTER_CID, false, nullptr, nsHTMLFormatConverterConstructor }, @@ -234,16 +262,19 @@ static const mozilla::Module::CIDEntry k }; static const mozilla::Module::ContractIDEntry kWidgetContracts[] = { { "@mozilla.org/widget/window/gtk;1", &kNS_WINDOW_CID }, { "@mozilla.org/widgets/child_window/gtk;1", &kNS_CHILD_CID }, { "@mozilla.org/widget/appshell/gtk;1", &kNS_APPSHELL_CID }, { "@mozilla.org/colorpicker;1", &kNS_COLORPICKER_CID, Module::MAIN_PROCESS_ONLY }, { "@mozilla.org/filepicker;1", &kNS_FILEPICKER_CID, Module::MAIN_PROCESS_ONLY }, +#if (MOZ_WIDGET_GTK == 3) + { "@mozilla.org/applicationchooser;1", &kNS_APPLICATIONCHOOSER_CID, Module::MAIN_PROCESS_ONLY }, +#endif { "@mozilla.org/sound;1", &kNS_SOUND_CID, Module::MAIN_PROCESS_ONLY }, { "@mozilla.org/widget/transferable;1", &kNS_TRANSFERABLE_CID }, #ifdef MOZ_X11 { "@mozilla.org/widget/clipboard;1", &kNS_CLIPBOARD_CID, Module::MAIN_PROCESS_ONLY }, { "@mozilla.org/widget/clipboardhelper;1", &kNS_CLIPBOARDHELPER_CID }, { "@mozilla.org/widget/dragservice;1", &kNS_DRAGSERVICE_CID, Module::MAIN_PROCESS_ONLY }, #endif { "@mozilla.org/widget/htmlformatconverter;1", &kNS_HTMLFORMATCONVERTER_CID }, diff --git a/widget/moz.build b/widget/moz.build --- a/widget/moz.build +++ b/widget/moz.build @@ -205,16 +205,20 @@ if toolkit in ('qt', 'gtk2', 'gtk3', 'co UNIFIED_SOURCES += [ 'nsBaseFilePicker.cpp', ] if toolkit in ('qt', 'gtk2', 'gtk3', 'windows', 'cocoa'): UNIFIED_SOURCES += [ 'nsNativeTheme.cpp', ] +if toolkit == 'gtk3': + XPIDL_SOURCES += [ + 'nsIApplicationChooser.idl', + ] if not CONFIG['MOZ_B2G']: DEFINES['MOZ_CROSS_PROCESS_IME'] = True FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') diff --git a/widget/nsIApplicationChooser.idl b/widget/nsIApplicationChooser.idl new file mode 100644 --- /dev/null +++ b/widget/nsIApplicationChooser.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIMIMEInfo.idl" +interface nsIDOMWindow; + +[scriptable, function, uuid(8144404d-e6c7-4861-bcca-47de912ee811)] +interface nsIApplicationChooserFinishedCallback : nsISupports +{ + void done(in nsIHandlerApp handlerApp); +}; + +[scriptable, uuid(8413fc42-d6c4-4d78-bf70-64cd78ebcc5c)] +interface nsIApplicationChooser : nsISupports +{ + /** + * Initialize the application chooser picker widget. The application chooser + * is not valid until this method is called. + * + * @param parent nsIDOMWindow parent. This dialog will be dependent + * on this parent. parent must be non-null. + * @param title The title for the file widget + * + */ + void init(in nsIDOMWindow parent, in ACString title); + + /** + * Open application chooser dialog. + * + * @param contentType content type of file to open + * @param applicationChooserFinishedCallback callback fuction to run when dialog is closed + */ + void open(in ACString contentType, in nsIApplicationChooserFinishedCallback applicationChooserFinishedCallback); +}; + diff --git a/widget/nsWidgetsCID.h b/widget/nsWidgetsCID.h --- a/widget/nsWidgetsCID.h +++ b/widget/nsWidgetsCID.h @@ -19,16 +19,21 @@ { 0xba7de611, 0x6088, 0x11d3, \ { 0xa8, 0x3e, 0x0, 0x10, 0x5a, 0x18, 0x34, 0x19 } } /* bd57cee8-1dd1-11b2-9fe7-95cf4709aea3 */ #define NS_FILEPICKER_CID \ { 0xbd57cee8, 0x1dd1, 0x11b2, \ {0x9f, 0xe7, 0x95, 0xcf, 0x47, 0x09, 0xae, 0xa3} } +/* e221df9b-3d66-4045-9a66-5720949f8d10 */ +#define NS_APPLICATIONCHOOSER_CID \ +{ 0xe221df9b, 0x3d66, 0x4045, \ + {0x9a, 0x66, 0x57, 0x20, 0x94, 0x9f, 0x8d, 0x10} } + /* 0f872c8c-3ee6-46bd-92a2-69652c6b474e */ #define NS_COLORPICKER_CID \ { 0x0f872c8c, 0x3ee6, 0x46bd, \ { 0x92, 0xa2, 0x69, 0x65, 0x2c, 0x6b, 0x47, 0x4e } } /* 2d96b3df-c051-11d1-a827-0040959a28c9 */ #define NS_APPSHELL_CID \ { 0x2d96b3df, 0xc051, 0x11d1, \