blob: 05738094ab598d95c71a98caaa3e39ce01a26843 [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 *
31 * @param {HTMLDocument} document The document to use when creating elements.
32 */
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 *
90 * @param {Array<Array<string, function(Event)>>} items The menu entries.
91 */
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 *
104 * @param {Event} e The event triggering this display.
105 * @param {hterm.Terminal=} terminal The terminal object to get style info from.
106 */
107hterm.ContextMenu.prototype.show = function(e, terminal) {
108 // If there are no menu entries, then don't try to show anything.
109 if (this.menu_.length == 0) {
110 return;
111 }
112
113 // If we have the terminal, sync the style preferences over.
114 if (terminal) {
115 this.element_.style.backgroundColor = terminal.getBackgroundColor();
116 this.element_.style.color = terminal.getForegroundColor();
117 this.element_.style.fontSize = terminal.getFontSize();
118 this.element_.style.fontFamily = terminal.getFontFamily();
119 }
120
121 this.element_.style.top = `${e.clientY}px`;
122 this.element_.style.left = `${e.clientX}px`;
123 const docSize = hterm.getClientSize(this.document_.body);
124
125 this.element_.style.display = 'block';
126
127 // We can't calculate sizes until after it's displayed.
128 const eleSize = hterm.getClientSize(this.element_);
129 // Make sure the menu isn't clipped outside of the current element.
130 const minY = Math.max(0, docSize.height - eleSize.height);
131 const minX = Math.max(0, docSize.width - eleSize.width);
132 if (minY < e.clientY) {
133 this.element_.style.top = `${minY}px`;
134 }
135 if (minX < e.clientX) {
136 this.element_.style.left = `${minX}px`;
137 }
138};
139
140/**
141 * Hide the context menu.
142 */
143hterm.ContextMenu.prototype.hide = function() {
144 if (!this.element_) {
145 return;
146 }
147
148 this.element_.style.display = 'none';
149};