blob: da07332b951846a40ff5739e2b5f25c949a3d907 [file] [log] [blame]
Mike Frysingercc114512017-09-11 21:39:17 -04001// Copyright 2018 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5'use strict';
6
7/**
8 * @fileoverview Context menu handling.
9 */
10
11/**
12 * Manage the context menu usually shown when right clicking.
13 */
14hterm.ContextMenu = function() {
15 // The document that contains this context menu.
16 this.document_ = null;
17 // The generated context menu (i.e. HTML elements).
18 this.element_ = null;
19 // The structured menu (i.e. JS objects).
20 this.menu_ = [];
21};
22
23/**
24 * Constant to add a separator to the context menu.
25 */
26hterm.ContextMenu.SEPARATOR = {};
27
28/**
29 * Bind context menu to a specific document element.
30 *
Joel Hockey0f933582019-08-27 18:01:51 -070031 * @param {!Document} document The document to use when creating elements.
Mike Frysingercc114512017-09-11 21:39:17 -040032 */
33hterm.ContextMenu.prototype.setDocument = function(document) {
34 if (this.element_) {
35 this.element_.remove();
36 this.element_ = null;
37 }
38 this.document_ = document;
39 this.regenerate_();
40 this.document_.body.appendChild(this.element_);
41};
42
43/**
44 * Regenerate the HTML elements based on internal menu state.
45 */
46hterm.ContextMenu.prototype.regenerate_ = function() {
47 if (!this.element_) {
48 this.element_ = this.document_.createElement('menu');
49 this.element_.id = 'hterm:context-menu';
50 this.element_.style.cssText = `
51 display: none;
52 border: solid 1px;
53 position: absolute;
54 `;
55 } else {
56 this.hide();
57 }
58
59 // Clear out existing menu entries.
60 while (this.element_.firstChild) {
61 this.element_.removeChild(this.element_.firstChild);
62 }
63
64 this.menu_.forEach(([name, action]) => {
65 const menuitem = this.document_.createElement('menuitem');
66 if (name === hterm.ContextMenu.SEPARATOR) {
67 menuitem.innerHTML = '<hr>';
68 menuitem.className = 'separator';
69 } else {
70 menuitem.innerText = name;
71 menuitem.addEventListener('mousedown', function(e) {
72 e.preventDefault();
73 action(e);
74 });
75 }
76 this.element_.appendChild(menuitem);
77 });
78};
79
80/**
81 * Set all the entries in the context menu.
82 *
83 * This is an array of arrays. The first element in the array is the string to
84 * display while the second element is the function to call.
85 *
86 * The first element may also be the SEPARATOR constant to add a separator.
87 *
88 * This resets all existing menu entries.
89 *
Joel Hockey0f933582019-08-27 18:01:51 -070090 * @param {!Array<!Array<string, function(!Event)>>} items The menu entries.
Mike Frysingercc114512017-09-11 21:39:17 -040091 */
92hterm.ContextMenu.prototype.setItems = function(items) {
93 this.menu_ = items;
94 this.regenerate_();
95};
96
97/**
98 * Show the context menu.
99 *
100 * The event is used to determine where to show the menu.
101 *
102 * If no menu entries are defined, then nothing will be shown.
103 *
Joel Hockey0f933582019-08-27 18:01:51 -0700104 * @param {!Event} e The event triggering this display.
105 * @param {!hterm.Terminal=} terminal The terminal object to get style info
106 * from.
Mike Frysingercc114512017-09-11 21:39:17 -0400107 */
108hterm.ContextMenu.prototype.show = function(e, terminal) {
109 // If there are no menu entries, then don't try to show anything.
110 if (this.menu_.length == 0) {
111 return;
112 }
113
114 // If we have the terminal, sync the style preferences over.
115 if (terminal) {
116 this.element_.style.backgroundColor = terminal.getBackgroundColor();
117 this.element_.style.color = terminal.getForegroundColor();
118 this.element_.style.fontSize = terminal.getFontSize();
119 this.element_.style.fontFamily = terminal.getFontFamily();
120 }
121
122 this.element_.style.top = `${e.clientY}px`;
123 this.element_.style.left = `${e.clientX}px`;
124 const docSize = hterm.getClientSize(this.document_.body);
125
126 this.element_.style.display = 'block';
127
128 // We can't calculate sizes until after it's displayed.
129 const eleSize = hterm.getClientSize(this.element_);
130 // Make sure the menu isn't clipped outside of the current element.
131 const minY = Math.max(0, docSize.height - eleSize.height);
132 const minX = Math.max(0, docSize.width - eleSize.width);
133 if (minY < e.clientY) {
134 this.element_.style.top = `${minY}px`;
135 }
136 if (minX < e.clientX) {
137 this.element_.style.left = `${minX}px`;
138 }
139};
140
141/**
142 * Hide the context menu.
143 */
144hterm.ContextMenu.prototype.hide = function() {
145 if (!this.element_) {
146 return;
147 }
148
149 this.element_.style.display = 'none';
150};