menuItemActionItem.ts 5.3 KB
Newer Older
1 2 3 4 5 6 7
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

J
Johannes Rieken 已提交
8 9 10 11
import { localize } from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IMenu, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IMessageService } from 'vs/platform/message/common/message';
12
import Severity from 'vs/base/common/severity';
J
Johannes Rieken 已提交
13 14 15 16 17
import { IAction } from 'vs/base/common/actions';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { domEvent } from 'vs/base/browser/event';
import { Emitter } from 'vs/base/common/event';
18

19

J
Johannes Rieken 已提交
20
export function fillInActions(menu: IMenu, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }): void {
21 22
	const groups = menu.getActions();
	if (groups.length === 0) {
23 24 25
		return;
	}

26
	for (let tuple of groups) {
27 28
		let [group, actions] = tuple;
		if (group === 'navigation') {
29 30 31 32 33 34 35 36 37 38

			const head = Array.isArray<IAction>(target) ? target : target.primary;

			// split contributed actions at the point where order
			// changes form lt zero to gte
			let pivot = 0;
			for (; pivot < actions.length; pivot++) {
				if ((<MenuItemAction>actions[pivot]).order >= 0) {
					break;
				}
39
			}
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
			// prepend contributed actions with order lte zero
			head.unshift(...actions.slice(0, pivot));

			// find the first separator which marks the end of the
			// navigation group - might be the whole array length
			let sep = 0;
			while (sep < head.length) {
				if (head[sep] instanceof Separator) {
					break;
				}
				sep++;
			}
			// append contributed actions with order gt zero
			head.splice(sep, 0, ...actions.slice(pivot));

55 56 57 58 59 60 61 62 63 64 65
		} else {
			if (Array.isArray<IAction>(target)) {
				target.push(new Separator(), ...actions);
			} else {
				target.secondary.push(new Separator(), ...actions);
			}
		}
	}
}


66
export function createActionItem(action: IAction, keybindingService: IKeybindingService, messageService: IMessageService): ActionItem {
67
	if (action instanceof MenuItemAction) {
68
		return new MenuItemActionItem(action, keybindingService, messageService);
69 70 71
	}
}

72 73 74 75 76 77

const _altKey = new class extends Emitter<boolean> {

	private _subscriptions: IDisposable[] = [];

	constructor() {
78 79 80 81 82
		super();

		this._subscriptions.push(domEvent(document.body, 'keydown')(e => this.fire(e.altKey)));
		this._subscriptions.push(domEvent(document.body, 'keyup')(e => this.fire(false)));
		this._subscriptions.push(domEvent(document.body, 'mouseleave')(e => this.fire(false)));
83
		this._subscriptions.push(domEvent(document.body, 'blur')(e => this.fire(false)));
84 85
	}

86 87 88
	dispose() {
		super.dispose();
		this._subscriptions = dispose(this._subscriptions);
89 90 91
	}
};

92 93
class MenuItemActionItem extends ActionItem {

94
	private _wantsAltCommand: boolean = false;
95 96 97

	constructor(
		action: MenuItemAction,
98 99
		@IKeybindingService private _keybindingService: IKeybindingService,
		@IMessageService private _messageService: IMessageService
100 101 102 103
	) {
		super(undefined, action, { icon: !!action.command.iconClass, label: !action.command.iconClass });
	}

104
	private get _command() {
105
		const {command, altCommand} = <MenuItemAction>this._action;
106
		return this._wantsAltCommand && altCommand || command;
107 108 109 110 111 112
	}

	onClick(event: MouseEvent): void {
		event.preventDefault();
		event.stopPropagation();

113
		(<MenuItemAction>this._action).run(this._wantsAltCommand).done(undefined, err => {
114 115
			this._messageService.show(Severity.Error, err);
		});
116 117 118 119 120
	}

	render(container: HTMLElement): void {
		super.render(container);

121 122
		let mouseOver = false;
		let altDown = false;
123

124 125 126 127
		const updateAltState = () => {
			const wantsAltCommand = mouseOver && altDown;
			if (wantsAltCommand !== this._wantsAltCommand) {
				this._wantsAltCommand = wantsAltCommand;
128 129 130
				this._updateLabel();
				this._updateTooltip();
				this._updateClass();
131 132
			}
		};
133

134 135 136 137 138 139 140 141 142 143 144 145 146
		this._callOnDispose.push(_altKey.event(value => {
			altDown = value;
			updateAltState();
		}));

		this._callOnDispose.push(domEvent(container, 'mouseleave')(_ => {
			mouseOver = false;
			updateAltState();
		}));

		this._callOnDispose.push(domEvent(container, 'mouseenter')(e => {
			mouseOver = true;
			updateAltState();
147 148 149 150 151
		}));
	}

	_updateLabel(): void {
		if (this.options.label) {
152
			this.$e.text(this._command.title);
153 154 155 156 157
		}
	}

	_updateTooltip(): void {
		const element = this.$e.getHTMLElement();
158
		const keybinding = this._keybindingService.lookupKeybindings(this._command.id)[0];
159 160 161
		const keybindingLabel = keybinding && this._keybindingService.getLabelFor(keybinding);

		element.title = keybindingLabel
162 163
			? localize('titleAndKb', "{0} ({1})", this._command.title, keybindingLabel)
			: this._command.title;
164 165 166 167 168
	}

	_updateClass(): void {
		if (this.options.icon) {
			const element = this.$e.getHTMLElement();
169 170 171 172 173 174 175
			const {command, altCommand} = (<MenuItemAction>this._action);
			if (this._command !== command) {
				element.classList.remove(command.iconClass);
			} else if (altCommand) {
				element.classList.remove(altCommand.iconClass);
			}
			element.classList.add('icon', this._command.iconClass);
176 177 178
		}
	}
}