feat: add support for share menu on macOS (#25629)
This commit is contained in:
parent
89c04b3c6c
commit
6b6ffbdd10
17 changed files with 316 additions and 6 deletions
|
@ -14,7 +14,7 @@ See [`Menu`](menu.md) for examples.
|
||||||
* `menuItem` MenuItem
|
* `menuItem` MenuItem
|
||||||
* `browserWindow` [BrowserWindow](browser-window.md) | undefined - This will not be defined if no window is open.
|
* `browserWindow` [BrowserWindow](browser-window.md) | undefined - This will not be defined if no window is open.
|
||||||
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
||||||
* `role` String (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
|
* `role` String (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
|
||||||
`click` property will be ignored. See [roles](#roles).
|
`click` property will be ignored. See [roles](#roles).
|
||||||
* `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
|
* `type` String (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
|
||||||
`radio`.
|
`radio`.
|
||||||
|
@ -31,6 +31,7 @@ See [`Menu`](menu.md) for examples.
|
||||||
menu items.
|
menu items.
|
||||||
* `registerAccelerator` Boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered
|
* `registerAccelerator` Boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered
|
||||||
with the system, but it will still be displayed. Defaults to true.
|
with the system, but it will still be displayed. Defaults to true.
|
||||||
|
* `sharingItem` SharingItem (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
|
||||||
* `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified
|
* `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified
|
||||||
for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted.
|
for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted.
|
||||||
If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using
|
If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using
|
||||||
|
@ -112,6 +113,7 @@ The following additional roles are available on _macOS_:
|
||||||
* `services` - The submenu is a ["Services"](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) menu. This is only intended for use in the Application Menu and is *not* the same as the "Services" submenu used in context menus in macOS apps, which is not implemented in Electron.
|
* `services` - The submenu is a ["Services"](https://developer.apple.com/documentation/appkit/nsapplication/1428608-servicesmenu?language=objc) menu. This is only intended for use in the Application Menu and is *not* the same as the "Services" submenu used in context menus in macOS apps, which is not implemented in Electron.
|
||||||
* `recentDocuments` - The submenu is an "Open Recent" menu.
|
* `recentDocuments` - The submenu is an "Open Recent" menu.
|
||||||
* `clearRecentDocuments` - Map to the `clearRecentDocuments` action.
|
* `clearRecentDocuments` - Map to the `clearRecentDocuments` action.
|
||||||
|
* `shareMenu` - The submenu is [share menu][ShareMenu]. The `sharingItem` property must also be set to indicate the item to share.
|
||||||
|
|
||||||
When specifying a `role` on macOS, `label` and `accelerator` are the only
|
When specifying a `role` on macOS, `label` and `accelerator` are the only
|
||||||
options that will affect the menu item. All other options will be ignored.
|
options that will affect the menu item. All other options will be ignored.
|
||||||
|
@ -200,6 +202,12 @@ system or just displayed.
|
||||||
|
|
||||||
This property can be dynamically changed.
|
This property can be dynamically changed.
|
||||||
|
|
||||||
|
#### `menuItem.sharingItem` _macOS_
|
||||||
|
|
||||||
|
A `SharingItem` indicating the item to share when the `role` is `shareMenu`.
|
||||||
|
|
||||||
|
This property can be dynamically changed.
|
||||||
|
|
||||||
#### `menuItem.commandId`
|
#### `menuItem.commandId`
|
||||||
|
|
||||||
A `Number` indicating an item's sequential unique id.
|
A `Number` indicating an item's sequential unique id.
|
||||||
|
@ -207,3 +215,5 @@ A `Number` indicating an item's sequential unique id.
|
||||||
#### `menuItem.menu`
|
#### `menuItem.menu`
|
||||||
|
|
||||||
A `Menu` that the item is a part of.
|
A `Menu` that the item is a part of.
|
||||||
|
|
||||||
|
[ShareMenu]: https://developer.apple.com/design/human-interface-guidelines/macos/extensions/share-extensions/
|
||||||
|
|
45
docs/api/share-menu.md
Normal file
45
docs/api/share-menu.md
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
## Class: ShareMenu
|
||||||
|
|
||||||
|
> Create share menu on macOS.
|
||||||
|
|
||||||
|
Process: [Main](../glossary.md#main-process)
|
||||||
|
|
||||||
|
The `ShareMenu` class creates [Share Menu][share-menu] on macOS, which can be
|
||||||
|
used to share information from the current context to apps, social media
|
||||||
|
accounts, and other services.
|
||||||
|
|
||||||
|
For including the share menu as a submenu of other menus, please use the
|
||||||
|
`shareMenu` role of [`MenuItem`](menu-item.md).
|
||||||
|
|
||||||
|
### `new ShareMenu(sharingItem)`
|
||||||
|
|
||||||
|
* `sharingItem` SharingItem - The item to share.
|
||||||
|
|
||||||
|
Creates a new share menu.
|
||||||
|
|
||||||
|
### Instance Methods
|
||||||
|
|
||||||
|
The `shareMenu` object has the following instance methods:
|
||||||
|
|
||||||
|
#### `shareMenu.popup([options])`
|
||||||
|
|
||||||
|
* `options` PopupOptions (optional)
|
||||||
|
* `browserWindow` [BrowserWindow](browser-window.md) (optional) - Default is the focused window.
|
||||||
|
* `x` Number (optional) - Default is the current mouse cursor position.
|
||||||
|
Must be declared if `y` is declared.
|
||||||
|
* `y` Number (optional) - Default is the current mouse cursor position.
|
||||||
|
Must be declared if `x` is declared.
|
||||||
|
* `positioningItem` Number (optional) _macOS_ - The index of the menu item to
|
||||||
|
be positioned under the mouse cursor at the specified coordinates. Default
|
||||||
|
is -1.
|
||||||
|
* `callback` Function (optional) - Called when menu is closed.
|
||||||
|
|
||||||
|
Pops up this menu as a context menu in the [`BrowserWindow`](browser-window.md).
|
||||||
|
|
||||||
|
#### `shareMenu.closePopup([browserWindow])`
|
||||||
|
|
||||||
|
* `browserWindow` [BrowserWindow](browser-window.md) (optional) - Default is the focused window.
|
||||||
|
|
||||||
|
Closes the context menu in the `browserWindow`.
|
||||||
|
|
||||||
|
[share-menu]: https://developer.apple.com/design/human-interface-guidelines/macos/extensions/share-extensions/
|
5
docs/api/structures/sharing-item.md
Normal file
5
docs/api/structures/sharing-item.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# SharingItem Object
|
||||||
|
|
||||||
|
* `texts` String[] (optional) - An array of text to share.
|
||||||
|
* `filePaths` String[] (optional) - An array of files to share.
|
||||||
|
* `urls` String[] (optional) - An array of URLs to share.
|
|
@ -49,6 +49,7 @@ auto_filenames = {
|
||||||
"docs/api/screen.md",
|
"docs/api/screen.md",
|
||||||
"docs/api/service-workers.md",
|
"docs/api/service-workers.md",
|
||||||
"docs/api/session.md",
|
"docs/api/session.md",
|
||||||
|
"docs/api/share-menu.md",
|
||||||
"docs/api/shell.md",
|
"docs/api/shell.md",
|
||||||
"docs/api/structures",
|
"docs/api/structures",
|
||||||
"docs/api/synopsis.md",
|
"docs/api/synopsis.md",
|
||||||
|
@ -119,6 +120,7 @@ auto_filenames = {
|
||||||
"docs/api/structures/serial-port.md",
|
"docs/api/structures/serial-port.md",
|
||||||
"docs/api/structures/service-worker-info.md",
|
"docs/api/structures/service-worker-info.md",
|
||||||
"docs/api/structures/shared-worker-info.md",
|
"docs/api/structures/shared-worker-info.md",
|
||||||
|
"docs/api/structures/sharing-item.md",
|
||||||
"docs/api/structures/shortcut-details.md",
|
"docs/api/structures/shortcut-details.md",
|
||||||
"docs/api/structures/size.md",
|
"docs/api/structures/size.md",
|
||||||
"docs/api/structures/task.md",
|
"docs/api/structures/task.md",
|
||||||
|
@ -219,6 +221,7 @@ auto_filenames = {
|
||||||
"lib/browser/api/protocol.ts",
|
"lib/browser/api/protocol.ts",
|
||||||
"lib/browser/api/screen.ts",
|
"lib/browser/api/screen.ts",
|
||||||
"lib/browser/api/session.ts",
|
"lib/browser/api/session.ts",
|
||||||
|
"lib/browser/api/share-menu.ts",
|
||||||
"lib/browser/api/system-preferences.ts",
|
"lib/browser/api/system-preferences.ts",
|
||||||
"lib/browser/api/touch-bar.ts",
|
"lib/browser/api/touch-bar.ts",
|
||||||
"lib/browser/api/tray.ts",
|
"lib/browser/api/tray.ts",
|
||||||
|
|
|
@ -6,7 +6,7 @@ const isLinux = process.platform === 'linux';
|
||||||
|
|
||||||
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
||||||
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' | 'startspeaking' | 'stopspeaking' |
|
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' | 'startspeaking' | 'stopspeaking' |
|
||||||
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu'
|
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
|
||||||
interface Role {
|
interface Role {
|
||||||
label: string;
|
label: string;
|
||||||
accelerator?: string;
|
accelerator?: string;
|
||||||
|
@ -261,6 +261,11 @@ export const roleList: Record<RoleId, Role> = {
|
||||||
{ role: 'close' }
|
{ role: 'close' }
|
||||||
] as MenuItemConstructorOptions[])
|
] as MenuItemConstructorOptions[])
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
// Share submenu
|
||||||
|
sharemenu: {
|
||||||
|
label: 'Share',
|
||||||
|
submenu: []
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,12 @@ Menu.prototype._shouldRegisterAcceleratorForCommandId = function (id) {
|
||||||
return this.commandsMap[id] ? this.commandsMap[id].registerAccelerator : false;
|
return this.commandsMap[id] ? this.commandsMap[id].registerAccelerator : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
Menu.prototype._getSharingItemForCommandId = function (id) {
|
||||||
|
return this.commandsMap[id] ? this.commandsMap[id].sharingItem : null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
Menu.prototype._executeCommand = function (event, id) {
|
Menu.prototype._executeCommand = function (event, id) {
|
||||||
const command = this.commandsMap[id];
|
const command = this.commandsMap[id];
|
||||||
if (!command) return;
|
if (!command) return;
|
||||||
|
|
|
@ -26,6 +26,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||||
{ name: 'protocol', loader: () => require('./protocol') },
|
{ name: 'protocol', loader: () => require('./protocol') },
|
||||||
{ name: 'screen', loader: () => require('./screen') },
|
{ name: 'screen', loader: () => require('./screen') },
|
||||||
{ name: 'session', loader: () => require('./session') },
|
{ name: 'session', loader: () => require('./session') },
|
||||||
|
{ name: 'ShareMenu', loader: () => require('./share-menu') },
|
||||||
{ name: 'systemPreferences', loader: () => require('./system-preferences') },
|
{ name: 'systemPreferences', loader: () => require('./system-preferences') },
|
||||||
{ name: 'TouchBar', loader: () => require('./touch-bar') },
|
{ name: 'TouchBar', loader: () => require('./touch-bar') },
|
||||||
{ name: 'Tray', loader: () => require('./tray') },
|
{ name: 'Tray', loader: () => require('./tray') },
|
||||||
|
|
|
@ -29,6 +29,7 @@ export const browserModuleNames = [
|
||||||
'protocol',
|
'protocol',
|
||||||
'screen',
|
'screen',
|
||||||
'session',
|
'session',
|
||||||
|
'ShareMenu',
|
||||||
'systemPreferences',
|
'systemPreferences',
|
||||||
'TouchBar',
|
'TouchBar',
|
||||||
'Tray',
|
'Tray',
|
||||||
|
|
19
lib/browser/api/share-menu.ts
Normal file
19
lib/browser/api/share-menu.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { BrowserWindow, Menu, SharingItem, PopupOptions } from 'electron/main';
|
||||||
|
|
||||||
|
class ShareMenu {
|
||||||
|
private menu: Menu;
|
||||||
|
|
||||||
|
constructor (sharingItem: SharingItem) {
|
||||||
|
this.menu = new (Menu as any)({ sharingItem });
|
||||||
|
}
|
||||||
|
|
||||||
|
popup (options?: PopupOptions) {
|
||||||
|
this.menu.popup(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
closePopup (browserWindow?: BrowserWindow) {
|
||||||
|
this.menu.closePopup(browserWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ShareMenu;
|
|
@ -12,12 +12,39 @@
|
||||||
#include "shell/browser/native_window.h"
|
#include "shell/browser/native_window.h"
|
||||||
#include "shell/common/gin_converters/accelerator_converter.h"
|
#include "shell/common/gin_converters/accelerator_converter.h"
|
||||||
#include "shell/common/gin_converters/callback_converter.h"
|
#include "shell/common/gin_converters/callback_converter.h"
|
||||||
|
#include "shell/common/gin_converters/file_path_converter.h"
|
||||||
|
#include "shell/common/gin_converters/gurl_converter.h"
|
||||||
#include "shell/common/gin_converters/image_converter.h"
|
#include "shell/common/gin_converters/image_converter.h"
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/gin_helper/object_template_builder.h"
|
#include "shell/common/gin_helper/object_template_builder.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
#include "ui/base/models/image_model.h"
|
#include "ui/base/models/image_model.h"
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
|
||||||
|
namespace gin {
|
||||||
|
|
||||||
|
using SharingItem = electron::ElectronMenuModel::SharingItem;
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct Converter<SharingItem> {
|
||||||
|
static bool FromV8(v8::Isolate* isolate,
|
||||||
|
v8::Local<v8::Value> val,
|
||||||
|
SharingItem* out) {
|
||||||
|
gin_helper::Dictionary dict;
|
||||||
|
if (!ConvertFromV8(isolate, val, &dict))
|
||||||
|
return false;
|
||||||
|
dict.GetOptional("texts", &(out->texts));
|
||||||
|
dict.GetOptional("filePaths", &(out->file_paths));
|
||||||
|
dict.GetOptional("urls", &(out->urls));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace gin
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
namespace api {
|
namespace api {
|
||||||
|
@ -26,6 +53,15 @@ gin::WrapperInfo Menu::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||||
|
|
||||||
Menu::Menu(gin::Arguments* args) : model_(new ElectronMenuModel(this)) {
|
Menu::Menu(gin::Arguments* args) : model_(new ElectronMenuModel(this)) {
|
||||||
model_->AddObserver(this);
|
model_->AddObserver(this);
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
gin_helper::Dictionary options;
|
||||||
|
if (args->GetNext(&options)) {
|
||||||
|
ElectronMenuModel::SharingItem item;
|
||||||
|
if (options.Get("sharingItem", &item))
|
||||||
|
model_->SetSharingItem(std::move(item));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Menu::~Menu() {
|
Menu::~Menu() {
|
||||||
|
@ -81,6 +117,19 @@ bool Menu::ShouldRegisterAcceleratorForCommandId(int command_id) const {
|
||||||
command_id);
|
command_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
bool Menu::GetSharingItemForCommandId(
|
||||||
|
int command_id,
|
||||||
|
ElectronMenuModel::SharingItem* item) const {
|
||||||
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||||
|
v8::HandleScope handle_scope(isolate);
|
||||||
|
v8::Local<v8::Value> val =
|
||||||
|
gin_helper::CallMethod(isolate, const_cast<Menu*>(this),
|
||||||
|
"_getSharingItemForCommandId", command_id);
|
||||||
|
return gin::ConvertFromV8(isolate, val, item);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void Menu::ExecuteCommand(int command_id, int flags) {
|
void Menu::ExecuteCommand(int command_id, int flags) {
|
||||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||||
v8::HandleScope scope(isolate);
|
v8::HandleScope scope(isolate);
|
||||||
|
|
|
@ -64,6 +64,11 @@ class Menu : public gin::Wrappable<Menu>,
|
||||||
bool use_default_accelerator,
|
bool use_default_accelerator,
|
||||||
ui::Accelerator* accelerator) const override;
|
ui::Accelerator* accelerator) const override;
|
||||||
bool ShouldRegisterAcceleratorForCommandId(int command_id) const override;
|
bool ShouldRegisterAcceleratorForCommandId(int command_id) const override;
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
bool GetSharingItemForCommandId(
|
||||||
|
int command_id,
|
||||||
|
ElectronMenuModel::SharingItem* item) const override;
|
||||||
|
#endif
|
||||||
void ExecuteCommand(int command_id, int event_flags) override;
|
void ExecuteCommand(int command_id, int event_flags) override;
|
||||||
void OnMenuWillShow(ui::SimpleMenuModel* source) override;
|
void OnMenuWillShow(ui::SimpleMenuModel* source) override;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ class ElectronMenuModel;
|
||||||
// allow for hierarchical menus). The tag is the index into that model for
|
// allow for hierarchical menus). The tag is the index into that model for
|
||||||
// that particular item. It is important that the model outlives this object
|
// that particular item. It is important that the model outlives this object
|
||||||
// as it only maintains weak references.
|
// as it only maintains weak references.
|
||||||
@interface ElectronMenuController : NSObject <NSMenuDelegate> {
|
@interface ElectronMenuController
|
||||||
|
: NSObject <NSMenuDelegate, NSSharingServiceDelegate> {
|
||||||
@protected
|
@protected
|
||||||
base::WeakPtr<electron::ElectronMenuModel> model_;
|
base::WeakPtr<electron::ElectronMenuModel> model_;
|
||||||
base::scoped_nsobject<NSMenu> menu_;
|
base::scoped_nsobject<NSMenu> menu_;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#import "shell/browser/ui/cocoa/electron_menu_controller.h"
|
#import "shell/browser/ui/cocoa/electron_menu_controller.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "base/logging.h"
|
#include "base/logging.h"
|
||||||
|
@ -14,8 +15,11 @@
|
||||||
#include "base/task/post_task.h"
|
#include "base/task/post_task.h"
|
||||||
#include "content/public/browser/browser_task_traits.h"
|
#include "content/public/browser/browser_task_traits.h"
|
||||||
#include "content/public/browser/browser_thread.h"
|
#include "content/public/browser/browser_thread.h"
|
||||||
|
#include "net/base/mac/url_conversions.h"
|
||||||
#include "shell/browser/mac/electron_application.h"
|
#include "shell/browser/mac/electron_application.h"
|
||||||
|
#include "shell/browser/native_window.h"
|
||||||
#include "shell/browser/ui/electron_menu_model.h"
|
#include "shell/browser/ui/electron_menu_model.h"
|
||||||
|
#include "shell/browser/window_list.h"
|
||||||
#include "ui/base/accelerators/accelerator.h"
|
#include "ui/base/accelerators/accelerator.h"
|
||||||
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
#include "ui/base/accelerators/platform_accelerator_cocoa.h"
|
||||||
#include "ui/base/l10n/l10n_util_mac.h"
|
#include "ui/base/l10n/l10n_util_mac.h"
|
||||||
|
@ -24,6 +28,7 @@
|
||||||
#include "ui/strings/grit/ui_strings.h"
|
#include "ui/strings/grit/ui_strings.h"
|
||||||
|
|
||||||
using content::BrowserThread;
|
using content::BrowserThread;
|
||||||
|
using SharingItem = electron::ElectronMenuModel::SharingItem;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -86,6 +91,24 @@ NSMenu* MakeEmptySubmenu() {
|
||||||
return submenu.autorelease();
|
return submenu.autorelease();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert an SharingItem to an array of NSObjects.
|
||||||
|
NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||||
|
NSMutableArray* result = [NSMutableArray array];
|
||||||
|
if (item.texts) {
|
||||||
|
for (const std::string& str : *item.texts)
|
||||||
|
[result addObject:base::SysUTF8ToNSString(str)];
|
||||||
|
}
|
||||||
|
if (item.file_paths) {
|
||||||
|
for (const base::FilePath& path : *item.file_paths)
|
||||||
|
[result addObject:base::mac::FilePathToNSURL(path)];
|
||||||
|
}
|
||||||
|
if (item.urls) {
|
||||||
|
for (const GURL& url : *item.urls)
|
||||||
|
[result addObject:net::NSURLWithGURL(url)];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// This class stores a base::WeakPtr<electron::ElectronMenuModel> as an
|
// This class stores a base::WeakPtr<electron::ElectronMenuModel> as an
|
||||||
|
@ -267,6 +290,31 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||||
recentDocumentsMenuItem_.reset([item retain]);
|
recentDocumentsMenuItem_.reset([item retain]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fill the menu with Share Menu items.
|
||||||
|
- (NSMenu*)createShareMenuForItem:(const SharingItem&)item {
|
||||||
|
NSArray* items = ConvertSharingItemToNS(item);
|
||||||
|
if ([items count] == 0)
|
||||||
|
return MakeEmptySubmenu();
|
||||||
|
base::scoped_nsobject<NSMenu> menu([[NSMenu alloc] init]);
|
||||||
|
NSArray* services = [NSSharingService sharingServicesForItems:items];
|
||||||
|
for (NSSharingService* service in services)
|
||||||
|
[menu addItem:[self menuItemForService:service withItems:items]];
|
||||||
|
return menu.autorelease();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a menu item that calls |service| when invoked.
|
||||||
|
- (NSMenuItem*)menuItemForService:(NSSharingService*)service
|
||||||
|
withItems:(NSArray*)items {
|
||||||
|
base::scoped_nsobject<NSMenuItem> item([[NSMenuItem alloc]
|
||||||
|
initWithTitle:service.menuItemTitle
|
||||||
|
action:@selector(performShare:)
|
||||||
|
keyEquivalent:@""]);
|
||||||
|
[item setTarget:self];
|
||||||
|
[item setImage:service.image];
|
||||||
|
[item setRepresentedObject:@{@"service" : service, @"items" : items}];
|
||||||
|
return item.autorelease();
|
||||||
|
}
|
||||||
|
|
||||||
// Adds an item or a hierarchical menu to the item at the |index|,
|
// Adds an item or a hierarchical menu to the item at the |index|,
|
||||||
// associated with the entry in the model identified by |modelIndex|.
|
// associated with the entry in the model identified by |modelIndex|.
|
||||||
- (void)addItemToMenu:(NSMenu*)menu
|
- (void)addItemToMenu:(NSMenu*)menu
|
||||||
|
@ -300,6 +348,12 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||||
NSMenu* submenu = [[[NSMenu alloc] initWithTitle:label] autorelease];
|
NSMenu* submenu = [[[NSMenu alloc] initWithTitle:label] autorelease];
|
||||||
[item setSubmenu:submenu];
|
[item setSubmenu:submenu];
|
||||||
[NSApp setServicesMenu:submenu];
|
[NSApp setServicesMenu:submenu];
|
||||||
|
} else if (role == base::ASCIIToUTF16("sharemenu")) {
|
||||||
|
SharingItem sharing_item;
|
||||||
|
model->GetSharingItemAt(index, &sharing_item);
|
||||||
|
[item setTarget:nil];
|
||||||
|
[item setAction:nil];
|
||||||
|
[item setSubmenu:[self createShareMenuForItem:sharing_item]];
|
||||||
} else if (type == electron::ElectronMenuModel::TYPE_SUBMENU &&
|
} else if (type == electron::ElectronMenuModel::TYPE_SUBMENU &&
|
||||||
model->IsVisibleAt(index)) {
|
model->IsVisibleAt(index)) {
|
||||||
// We need to specifically check that the submenu top-level item has been
|
// We need to specifically check that the submenu top-level item has been
|
||||||
|
@ -372,6 +426,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||||
// radio, etc) of each item in the menu.
|
// radio, etc) of each item in the menu.
|
||||||
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
|
||||||
SEL action = [item action];
|
SEL action = [item action];
|
||||||
|
if (action == @selector(performShare:))
|
||||||
|
return YES;
|
||||||
if (action != @selector(itemSelected:))
|
if (action != @selector(itemSelected:))
|
||||||
return NO;
|
return NO;
|
||||||
|
|
||||||
|
@ -405,14 +461,30 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Performs the share action using the sharing service represented by |sender|.
|
||||||
|
- (void)performShare:(NSMenuItem*)sender {
|
||||||
|
NSDictionary* object =
|
||||||
|
base::mac::ObjCCastStrict<NSDictionary>([sender representedObject]);
|
||||||
|
NSSharingService* service =
|
||||||
|
base::mac::ObjCCastStrict<NSSharingService>(object[@"service"]);
|
||||||
|
NSArray* items = base::mac::ObjCCastStrict<NSArray>(object[@"items"]);
|
||||||
|
[service setDelegate:self];
|
||||||
|
[service performWithItems:items];
|
||||||
|
}
|
||||||
|
|
||||||
- (NSMenu*)menu {
|
- (NSMenu*)menu {
|
||||||
if (menu_)
|
if (menu_)
|
||||||
return menu_.get();
|
return menu_.get();
|
||||||
|
|
||||||
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
|
if (model_ && model_->GetSharingItem()) {
|
||||||
|
NSMenu* menu = [self createShareMenuForItem:*model_->GetSharingItem()];
|
||||||
|
menu_.reset([menu retain]);
|
||||||
|
} else {
|
||||||
|
menu_.reset([[NSMenu alloc] initWithTitle:@""]);
|
||||||
|
if (model_)
|
||||||
|
[self populateWithModel:model_.get()];
|
||||||
|
}
|
||||||
[menu_ setDelegate:self];
|
[menu_ setDelegate:self];
|
||||||
if (model_)
|
|
||||||
[self populateWithModel:model_.get()];
|
|
||||||
return menu_.get();
|
return menu_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,4 +511,18 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NSSharingServiceDelegate
|
||||||
|
|
||||||
|
- (NSWindow*)sharingService:(NSSharingService*)service
|
||||||
|
sourceWindowForShareItems:(NSArray*)items
|
||||||
|
sharingContentScope:(NSSharingContentScope*)scope {
|
||||||
|
// Return the current active window.
|
||||||
|
const auto& list = electron::WindowList::GetWindows();
|
||||||
|
for (electron::NativeWindow* window : list) {
|
||||||
|
if (window->IsFocused())
|
||||||
|
return window->GetNativeWindow().GetNativeNSWindow();
|
||||||
|
}
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
|
@ -4,10 +4,18 @@
|
||||||
|
|
||||||
#include "shell/browser/ui/electron_menu_model.h"
|
#include "shell/browser/ui/electron_menu_model.h"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#include "base/stl_util.h"
|
#include "base/stl_util.h"
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
ElectronMenuModel::SharingItem::SharingItem() = default;
|
||||||
|
ElectronMenuModel::SharingItem::SharingItem(SharingItem&&) = default;
|
||||||
|
ElectronMenuModel::SharingItem::~SharingItem() = default;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool ElectronMenuModel::Delegate::GetAcceleratorForCommandId(
|
bool ElectronMenuModel::Delegate::GetAcceleratorForCommandId(
|
||||||
int command_id,
|
int command_id,
|
||||||
ui::Accelerator* accelerator) const {
|
ui::Accelerator* accelerator) const {
|
||||||
|
@ -79,6 +87,23 @@ bool ElectronMenuModel::WorksWhenHiddenAt(int index) const {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
bool ElectronMenuModel::GetSharingItemAt(int index, SharingItem* item) const {
|
||||||
|
if (delegate_)
|
||||||
|
return delegate_->GetSharingItemForCommandId(GetCommandIdAt(index), item);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ElectronMenuModel::SetSharingItem(SharingItem item) {
|
||||||
|
sharing_item_.emplace(std::move(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
const base::Optional<ElectronMenuModel::SharingItem>&
|
||||||
|
ElectronMenuModel::GetSharingItem() const {
|
||||||
|
return sharing_item_;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void ElectronMenuModel::MenuWillClose() {
|
void ElectronMenuModel::MenuWillClose() {
|
||||||
ui::SimpleMenuModel::MenuWillClose();
|
ui::SimpleMenuModel::MenuWillClose();
|
||||||
for (Observer& observer : observers_) {
|
for (Observer& observer : observers_) {
|
||||||
|
|
|
@ -6,16 +6,34 @@
|
||||||
#define SHELL_BROWSER_UI_ELECTRON_MENU_MODEL_H_
|
#define SHELL_BROWSER_UI_ELECTRON_MENU_MODEL_H_
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "base/files/file_path.h"
|
||||||
#include "base/memory/weak_ptr.h"
|
#include "base/memory/weak_ptr.h"
|
||||||
#include "base/observer_list.h"
|
#include "base/observer_list.h"
|
||||||
#include "base/observer_list_types.h"
|
#include "base/observer_list_types.h"
|
||||||
|
#include "base/optional.h"
|
||||||
#include "ui/base/models/simple_menu_model.h"
|
#include "ui/base/models/simple_menu_model.h"
|
||||||
|
#include "url/gurl.h"
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
class ElectronMenuModel : public ui::SimpleMenuModel {
|
class ElectronMenuModel : public ui::SimpleMenuModel {
|
||||||
public:
|
public:
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
struct SharingItem {
|
||||||
|
SharingItem();
|
||||||
|
SharingItem(SharingItem&&);
|
||||||
|
SharingItem(const SharingItem&) = delete;
|
||||||
|
~SharingItem();
|
||||||
|
|
||||||
|
base::Optional<std::vector<std::string>> texts;
|
||||||
|
base::Optional<std::vector<GURL>> urls;
|
||||||
|
base::Optional<std::vector<base::FilePath>> file_paths;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
class Delegate : public ui::SimpleMenuModel::Delegate {
|
class Delegate : public ui::SimpleMenuModel::Delegate {
|
||||||
public:
|
public:
|
||||||
~Delegate() override {}
|
~Delegate() override {}
|
||||||
|
@ -30,6 +48,11 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
|
||||||
|
|
||||||
virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0;
|
virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0;
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
virtual bool GetSharingItemForCommandId(int command_id,
|
||||||
|
SharingItem* item) const = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// ui::SimpleMenuModel::Delegate:
|
// ui::SimpleMenuModel::Delegate:
|
||||||
bool GetAcceleratorForCommandId(
|
bool GetAcceleratorForCommandId(
|
||||||
|
@ -65,6 +88,13 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
|
||||||
ui::Accelerator* accelerator) const;
|
ui::Accelerator* accelerator) const;
|
||||||
bool ShouldRegisterAcceleratorAt(int index) const;
|
bool ShouldRegisterAcceleratorAt(int index) const;
|
||||||
bool WorksWhenHiddenAt(int index) const;
|
bool WorksWhenHiddenAt(int index) const;
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
// Return the SharingItem of menu item.
|
||||||
|
bool GetSharingItemAt(int index, SharingItem* item) const;
|
||||||
|
// Set/Get the SharingItem of this menu.
|
||||||
|
void SetSharingItem(SharingItem item);
|
||||||
|
const base::Optional<SharingItem>& GetSharingItem() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
// ui::SimpleMenuModel:
|
// ui::SimpleMenuModel:
|
||||||
void MenuWillClose() override;
|
void MenuWillClose() override;
|
||||||
|
@ -80,6 +110,10 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
|
||||||
private:
|
private:
|
||||||
Delegate* delegate_; // weak ref.
|
Delegate* delegate_; // weak ref.
|
||||||
|
|
||||||
|
#if defined(OS_MAC)
|
||||||
|
base::Optional<SharingItem> sharing_item_;
|
||||||
|
#endif
|
||||||
|
|
||||||
std::map<int, base::string16> toolTips_; // command id -> tooltip
|
std::map<int, base::string16> toolTips_; // command id -> tooltip
|
||||||
std::map<int, base::string16> roles_; // command id -> role
|
std::map<int, base::string16> roles_; // command id -> role
|
||||||
std::map<int, base::string16> sublabels_; // command id -> sublabel
|
std::map<int, base::string16> sublabels_; // command id -> sublabel
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
#define SHELL_COMMON_GIN_HELPER_DICTIONARY_H_
|
#define SHELL_COMMON_GIN_HELPER_DICTIONARY_H_
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#include "base/optional.h"
|
||||||
#include "gin/dictionary.h"
|
#include "gin/dictionary.h"
|
||||||
#include "shell/common/gin_converters/std_converter.h"
|
#include "shell/common/gin_converters/std_converter.h"
|
||||||
#include "shell/common/gin_helper/function_template.h"
|
#include "shell/common/gin_helper/function_template.h"
|
||||||
|
@ -59,6 +61,18 @@ class Dictionary : public gin::Dictionary {
|
||||||
return !result.IsNothing() && result.FromJust();
|
return !result.IsNothing() && result.FromJust();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Like normal Get but put result in an base::Optional.
|
||||||
|
template <typename T>
|
||||||
|
bool GetOptional(base::StringPiece key, base::Optional<T>* out) const {
|
||||||
|
T ret;
|
||||||
|
if (Get(key, &ret)) {
|
||||||
|
out->emplace(std::move(ret));
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
bool GetHidden(base::StringPiece key, T* out) const {
|
bool GetHidden(base::StringPiece key, T* out) const {
|
||||||
v8::Local<v8::Context> context = isolate()->GetCurrentContext();
|
v8::Local<v8::Context> context = isolate()->GetCurrentContext();
|
||||||
|
|
1
typings/internal-electron.d.ts
vendored
1
typings/internal-electron.d.ts
vendored
|
@ -108,6 +108,7 @@ declare namespace Electron {
|
||||||
_isCommandIdVisible(id: string): boolean;
|
_isCommandIdVisible(id: string): boolean;
|
||||||
_getAcceleratorForCommandId(id: string, useDefaultAccelerator: boolean): Accelerator | undefined;
|
_getAcceleratorForCommandId(id: string, useDefaultAccelerator: boolean): Accelerator | undefined;
|
||||||
_shouldRegisterAcceleratorForCommandId(id: string): boolean;
|
_shouldRegisterAcceleratorForCommandId(id: string): boolean;
|
||||||
|
_getSharingItemForCommandId(id: string): SharingItem | null;
|
||||||
_callMenuWillShow(): void;
|
_callMenuWillShow(): void;
|
||||||
_executeCommand(event: any, id: number): void;
|
_executeCommand(event: any, id: number): void;
|
||||||
_menuWillShow(): void;
|
_menuWillShow(): void;
|
||||||
|
|
Loading…
Reference in a new issue