/**
* The toolbar part of an {@link XMLEditor}.
*/
class ToolBar extends HTMLElement {
constructor() {
super();
this._xmlEditor = null;
this._grid = null;
this._gridRows = null;
this._toolSets = [];
this._commandButtons = [];
this._resizeObserver = new ResizeObserver(this.onResize.bind(this));
this._resizeTimeout = this.resizeTimeout.bind(this);
this._resizeTimeoutId = null;
this._resizeWidth = -1;
this._widthAfterResize = -1;
}
// ----------------------------------
// onResize
// ----------------------------------
onResize(entries, observer) {
if (entries.length > 0) {
const entry = entries[0];
if (entry.target === this) {
let toolBarW = -1;
if (Array.isArray(entry.contentBoxSize) &&
entry.contentBoxSize.length > 0) {
toolBarW = entry.contentBoxSize[0].inlineSize;
} else if (entry.contentRect) {
toolBarW = entry.contentRect.width; // No padding or border.
}
if (toolBarW <= 0) {
// Give up.
this.cancelResizeTimeout();
} else {
this.newResizeTimeout(toolBarW);
}
}
}
}
cancelResizeTimeout() {
this._resizeWidth = -1;
const resizeTimoutId = this._resizeTimeoutId;
if (resizeTimoutId !== null) {
this._resizeTimeoutId = null;
clearTimeout(resizeTimoutId);
}
}
newResizeTimeout(toolBarW) {
this._resizeWidth = toolBarW;
if (this._resizeTimeoutId === null) {
this._resizeTimeoutId = setTimeout(this._resizeTimeout, 100 /*ms*/);
}
}
resizeTimeout() {
this._resizeTimeoutId = null;
const toolBarW = this._resizeWidth;
this._resizeWidth = -1;
if (toolBarW > 0) {
this.resizeToolSets(toolBarW);
}
}
resizeToolSets(toolBarW=-1) {
XUI.Dialogs.closeAllPopups();
// ---
if (toolBarW <= 0) { // Force resize.
this._widthAfterResize = -1;
toolBarW = this.clientWidth; // Includes padding, not border.
toolBarW -= XUI.Util.getPxProperty(this, "padding-left");
toolBarW -= XUI.Util.getPxProperty(this, "padding-right");
if (isNaN(toolBarW) || toolBarW <= 0) {
// Give up.
console.error(`ToolBar.resizeToolSets: \
cannot determine toolbar width (this.clientWidth=${this.clientWidth})`);
return;
}
}
if (toolBarW === this._widthAfterResize) {
// Nothing to do.
return;
}
this._widthAfterResize = toolBarW;
// ---
let toolSetW = 0;
for (let toolSet of this._toolSets) {
toolSetW += toolSet.width;
ToolBar.expandToolSet(toolSet, this._gridRows);
}
let i = this._toolSets.length-1;
while (toolSetW > toolBarW && i >= 0) {
let toolSet = this._toolSets[i];
ToolBar.collapseToolSet(toolSet, this._gridRows);
toolSetW -= toolSet.width;
--i;
}
}
static expandToolSet(toolSet, rows) {
if (toolSet.collapsedCell.parentNode === null) {
// Already expanded.
return;
}
rows[0].removeChild(toolSet.collapsedCell);
// Insert all cells but last one which is a separator before this
// separator cell.
const cellLists = toolSet.cellLists;
for (let i = 0; i < 3; ++i) {
const row = rows[i];
const cellList = cellLists[i];
const last = cellList.length-1;
const separCell = cellList[last];
for (let i = 0; i < last; ++i) {
row.insertBefore(cellList[i], separCell);
}
}
}
static collapseToolSet(toolSet, rows) {
if (toolSet.collapsedCell.parentNode !== null) {
// Already collapsed.
return;
}
// Do not remove the separator cell at the right of the toolset.
ToolBar.detachToolSet(toolSet, rows, /*includingSepar*/ false);
// Insert collapsedCell before the first separator cell at the right
// of the toolset.
const firstCellList = toolSet.cellLists[0];
rows[0].insertBefore(toolSet.collapsedCell,
firstCellList[firstCellList.length-1]);
}
static detachToolSet(toolSet, rows, includingSepar) {
const cellLists = toolSet.cellLists;
for (let i = 0; i < 3; ++i) {
const row = rows[i];
const cellList = cellLists[i];
let last = cellList.length-1; // separ index
if (!includingSepar) {
--last;
}
for (let i = last; i >= 0; --i) {
row.removeChild(cellList[i]);
}
}
}
static attachToolSet(toolSet, rows, includingSepar) {
const cellLists = toolSet.cellLists;
for (let i = 0; i < 3; ++i) {
const row = rows[i];
const cellList = cellLists[i];
let last = cellList.length-1; // separ index
if (!includingSepar) {
--last;
}
for (let i = 0; i <= last; ++i) {
row.appendChild(cellList[i]);
}
}
}
// -----------------------------------------------------------------------
// Custom element
// -----------------------------------------------------------------------
connectedCallback() {
if (this.firstChild === null) {
this._gridRows = new Array(3);
this._grid = ToolBar.createGrid(this._gridRows);
this.appendChild(this._grid);
this.addToolSet(ToolBar.EDIT_TOOL_SET);
}
// Otherwise, already connected.
this._resizeTimeoutId = null;
this._resizeWidth = -1;
this._widthAfterResize = -1;
this._resizeObserver.observe(this);
}
static createGrid(rows) {
let table = document.createElement("table");
table.setAttribute("class", "xui-control xxe-tlbr-grid");
let tbody = document.createElement("tbody");
table.appendChild(tbody);
rows[0] = document.createElement("tr");
tbody.appendChild(rows[0]);
rows[1] = document.createElement("tr");
tbody.appendChild(rows[1]);
rows[2] = document.createElement("tr");
tbody.appendChild(rows[2]);
return table;
}
disconnectedCallback() {
this.cancelResizeTimeout();
this._resizeObserver.unobserve(this);
this._widthAfterResize = -1;
}
// -----------------------------------------------------------------------
// Populating the toolbar
// -----------------------------------------------------------------------
// ----------------------------------
// addToolSet
// ----------------------------------
addToolSet(toolSetSpec) {
let toolSet = this.createToolSet(toolSetSpec, this._toolSets.length);
this._toolSets.push(toolSet);
let oldW = this._grid.getBoundingClientRect().width;
ToolBar.attachToolSet(toolSet, this._gridRows, /*includingSepar*/ true);
let newW = this._grid.getBoundingClientRect().width;
toolSet.width = newW - oldW;
}
createToolSet(toolSetSpec, toolSetIndex) {
const cellLists = [[], [], []];
const toolSet = { index: toolSetIndex, cellLists: cellLists };
const rowLengths = [0, 0, 1];
let whichRow = 0;
const addSepar = () => {
let paddingCellCount = 0;
if (rowLengths[0] < rowLengths[1]) {
whichRow = 0;
paddingCellCount = rowLengths[1] - rowLengths[0];
} else if (rowLengths[0] > rowLengths[1]) {
whichRow = 1;
paddingCellCount = rowLengths[0] - rowLengths[1];
}
while (paddingCellCount > 0) {
cellLists[whichRow].push(ToolBar.createCell(null, 1));
++rowLengths[whichRow];
--paddingCellCount;
}
let td = ToolBar.createCell("xxe-tlbr-cell-separ", 1);
cellLists[0].push(td);
++rowLengths[0];
td = ToolBar.createCell("xxe-tlbr-cell-separ", 1);
cellLists[1].push(td);
++rowLengths[1];
whichRow = 0;
};
if (Array.isArray(toolSetSpec.tools) && toolSetSpec.tools.length > 0) {
for (let tool of toolSetSpec.tools) {
switch (ToolBar.toolType(tool)) {
case "separator":
addSepar();
break;
case "button":
case "checkbox":
case "menu":
case "checkboxmenu":
{
let iconSize = [0];
let button = this.createButton(tool, iconSize);
if (button === null) {
continue;
}
ToolBar.setButtonToolSet(button, toolSetIndex);
let td = ToolBar.createCell("xxe-tlbr-cell", 1);
td.appendChild(button);
if (iconSize[0] > 16) {
let colSpan1 = 0;
let colSpan2 = 0;
if (rowLengths[0] < rowLengths[1]) {
colSpan1 = rowLengths[1] - rowLengths[0];
} else if (rowLengths[0] > rowLengths[1]) {
colSpan2 = rowLengths[0] + rowLengths[1];
}
if (colSpan1 > 0) {
let td = ToolBar.createCell(null, colSpan1);
cellLists[0].push(td);
rowLengths[0] += colSpan1;
}
if (colSpan2 > 0) {
let td = ToolBar.createCell(null, colSpan2);
cellLists[1].push(td);
rowLengths[1] += colSpan2;
}
whichRow = 0;
td.setAttribute("rowspan", "2");
cellLists[0].push(td);
++rowLengths[0];
++rowLengths[1];
} else {
cellLists[whichRow].push(td);
++rowLengths[whichRow];
if (whichRow === 0) {
whichRow = 1;
} else {
whichRow = 0;
}
}
}
break;
case "spacer":
case "span":
{
let toolSpan = null;
if (Array.isArray(tool.items) &&
tool.items.length > 0) {
toolSpan =
this.createToolSpan(tool.items, toolSetIndex);
}
// Otherwise, just a spacer.
let td = ToolBar.createCell("xxe-tlbr-cell", 1);
if (toolSpan !== null) {
td.appendChild(toolSpan);
}
cellLists[whichRow].push(td);
++rowLengths[whichRow];
if (whichRow === 0) {
whichRow = 1;
} else {
whichRow = 0;
}
}
break;
}
}
}
if (rowLengths[0] === 0) {
// No tools. Just a menu? Add one empty cell before adding the
// separator below.
cellLists[0].push(ToolBar.createCell(null, 1));
rowLengths[0] = 1;
}
// The separator at the right is part of the toolset.
addSepar();
// ---
let td = ToolBar.createCell("xxe-tlbr-footer-cell", rowLengths[0]-1);
cellLists[2].push(td);
const div = document.createElement("div");
td.appendChild(div);
let span = document.createElement("span");
span.setAttribute("class", "xxe-tlbr-footer-label");
let toolSetLabel = !toolSetSpec.label? "\u00A0" : toolSetSpec.label;
span.appendChild(document.createTextNode(toolSetLabel));
div.appendChild(span);
if (Array.isArray(toolSetSpec.menu)) {
const button = document.createElement("span");
button.setAttribute("class",
"xxe-tool-button xxe-tlbr-footer-button");
button.setAttribute("title", "More commands...");
ToolBar.setButtonToolSet(button, toolSetIndex);
button.textContent = XUI.StockIcon["triangle-1-se"];
div.appendChild(button);
this.attachMenu(toolSetSpec.menu, /*pos*/ "comboboxmenu", button);
}
// The separator at the right is part of the toolset.
cellLists[2].push(ToolBar.createCell("xxe-tlbr-cell-separ", 1));
// ---
td = ToolBar.createCell("xxe-tlbr-collapsed-cell", 1);
td.setAttribute("rowspan", "3");
span = document.createElement("span");
span.setAttribute("class", "xxe-tlbr-collapsed-label");
if (toolSetLabel.length > 10) {
toolSetLabel = XUI.Util.shortenText(toolSetLabel, 10);
}
// Black Left-Pointing Small Triangle.
span.appendChild(document.createTextNode("\u25C2 " + toolSetLabel));
td.appendChild(span);
toolSet.collapsedCell = td;
const onclick = (event) => {
XUI.Util.consumeEvent(event);
this.showToolSetPopup(toolSet);
};
td.onclick = onclick;
td.oncontextmenu = onclick;
return toolSet;
}
static toolType(tool) {
let type = tool.type;
if (!type) {
if (Object.keys(tool).length === 0) {
type = "separator";
} else if ("items" in tool) {
type = "menu";
} else {
type = "button";
}
}
return type;
}
static createCell(classes, colSpan) {
let td = document.createElement("td");
if (classes !== null) {
td.setAttribute("class", classes);
}
if (colSpan > 1) {
td.setAttribute("colspan", String(colSpan));
}
return td;
}
static setButtonToolSet(button, toolSetIndex) {
button.setAttribute("data-toolset", String(toolSetIndex));
}
getButtonToolSet(button) {
let toolSet = null;
let toolSetIndex = parseInt(button.getAttribute("data-toolset"));
if (toolSetIndex >= 0 && toolSetIndex < this._toolSets.length) {
toolSet = this._toolSets[toolSetIndex];
}
return toolSet;
}
// ----------------------------------
// createToolSpan
// ----------------------------------
createToolSpan(toolSpecs, toolSetIndex) {
let toolSpan = document.createElement("div");
toolSpan.setAttribute("class", "xxe-tlbr-span");
for (let toolSpec of toolSpecs) {
const toolType = ToolBar.toolType(toolSpec);
switch (toolType) {
case "separator":
case "spacer":
{
let separ = document.createElement("div");
separ.setAttribute("class", "xxe-tlbr-span-" + toolType);
if (toolType === "separator" &&
("line" in toolSpec) && !toolSpec.line) {
separ.setAttribute("style", "border-style:none;");
}
toolSpan.appendChild(separ);
}
break;
case "button":
case "checkbox":
case "menu":
case "checkboxmenu":
{
let button = this.createButton(toolSpec, /*small!*/ [16]);
if (button !== null) {
ToolBar.setButtonToolSet(button, toolSetIndex);
toolSpan.appendChild(button);
}
}
break;
}
}
return toolSpan;
}
// ----------------------------------
// createButton
// ----------------------------------
createButton(tool, iconSize) {
let button = document.createElement("div");
button.setAttribute("class", "xxe-tlbr-button");
if (tool.tooltip) {
button.setAttribute("title", tool.tooltip);
}
if (!tool.iconData && !tool.iconName) {
iconSize[0] = 0;
} else {
if (iconSize[0] <= 0) {
iconSize[0] = !tool.iconSize? 16 : tool.iconSize;
}
// Otherwise, icon size forced by invoker.
if (iconSize[0] < 16) {
iconSize[0] = 16;
} else if (iconSize[0] > 16) {
iconSize[0] = 24;
}
}
let label = !tool.label? null : tool.label;
let childElem = (iconSize[0] > 16 && label !== null)? "div" : "span";
const items = (Array.isArray(tool.items) && tool.items.length > 0)?
tool.items : null;
if (iconSize[0] > 0) {
let buttonIcon = document.createElement(childElem);
buttonIcon.setAttribute("class", "xxe-tlbr-button-icon");
button.appendChild(buttonIcon);
let icon;
let iconName = null;
if (tool.iconData) {
icon = XUI.MenuItem.createIcon("url(" + tool.iconData + ")",
iconSize[0]);
} else {
iconName = ToolBar.iconNameWithFallback(tool);
icon = XUI.MenuItem.createEditIcon(iconName, iconSize[0]);
}
buttonIcon.appendChild(icon);
if (items !== null &&
(iconName === null || iconName !== "menu")) {
let combo = document.createElement("span");
// vertical-align depends on icon size.
combo.setAttribute("class",
"xui-small-icon xxe-tlbr-button-combo" +
"-" + iconSize[0]);
combo.textContent = XUI.StockIcon["down-dir"];
buttonIcon.appendChild(combo);
}
}
if (label !== null) {
let buttonLabel = document.createElement(childElem);
buttonLabel.setAttribute("class", "xxe-tlbr-button-label");
buttonLabel.appendChild(document.createTextNode(label));
button.appendChild(buttonLabel);
}
if (items !== null) {
let attachedMenu = this.attachMenu(items, /*pos*/ "menu", button);
if (attachedMenu !== null && tool.type === "checkboxmenu") {
ToolBar.setCheckboxMenuCmdString(button, items);
}
} else {
const cmdString = ToolBar.itemSpecCmdString(tool);
if (cmdString === null) {
button = null; // Unusable.
} else {
button.setAttribute("data-command", cmdString);
const onclick = (event) => {
XUI.Util.consumeEvent(event);
if (!button.classList.contains("xui-control-disabled")) {
this.closeToolSetPopup(button);
this.performAction(cmdString);
}
};
button.onclick = onclick;
button.oncontextmenu = onclick;
}
}
return button;
}
static iconNameWithFallback(itemSpec) {
let iconName = itemSpec.iconName;
if (!iconName || !XUI.EditIcon[iconName]) {
iconName = "fallback";
}
return iconName;
}
static itemSpecCmdString(itemSpec) {
// Returns null if no cmdName.
return Command.joinCmdString(
!itemSpec.cmdName? null : itemSpec.cmdName,
!itemSpec.cmdParam? null : itemSpec.cmdParam,
XMLEditor.CMD_STRING_SEPAR);
}
static setCheckboxMenuCmdString(button, itemSpecs) {
let cmdStrings = [];
for (let itemSpec of itemSpecs) {
switch (ToolBar.toolType(itemSpec)) {
case "separator":
break;
case "checkbox":
{
let cmdString = ToolBar.itemSpecCmdString(itemSpec);
if (cmdString === null) {
continue; // Unusable.
}
cmdStrings.push(cmdString);
}
break;
default:
// Anything else is not supported.
return;
}
}
if (cmdStrings.length > 0) {
button.setAttribute("data-command",
cmdStrings.join(ToolBar.CMD_STRINGS_SEPAR));
}
}
performAction(cmdString) {
const docView = this.activeDocumentView;
let [cmdName, cmdParam] =
Command.splitCmdString(cmdString, ToolBar.CMD_STRING_SEPAR);
if (cmdName.endsWith("()")) { // Pseudo-command.
const functionName = cmdName.substring(0, cmdName.length-2);
this[functionName](/*getState*/ false, docView, cmdParam);
} else {
if (docView !== null) {
docView.executeCommand(EXECUTE_NORMAL, cmdName, cmdParam);
}
}
}
get activeDocumentView() {
if (this._xmlEditor === null || !this._xmlEditor.documentIsOpened) {
return null;
} else {
return this._xmlEditor.documentView;
}
}
// ----------------------------------
// attachMenu
// ----------------------------------
attachMenu(itemSpecs, position, button) {
const menuItems = this.createMenuItems(itemSpecs);
if (menuItems.length === 0) {
return null;
}
const menu = XUI.Menu.create(menuItems);
menu.addEventListener("menuitemselected", (event) => {
this.closeToolSetPopup(button);
this.performAction(event.xuiMenuItem.name);
});
const onclick = (event) => {
XUI.Util.consumeEvent(event);
this.showingToolSetMenu(menu, button);
this.showMenu(menu, position, button);
};
button.onclick = onclick;
button.oncontextmenu = onclick;
return menu;
}
closeToolSetPopup(button) {
let toolSet = this.getButtonToolSet(button);
if (toolSet !== null && toolSet.popup) {
XUI.Dialogs.close(toolSet.popup);
}
}
showingToolSetMenu(menu, button) {
// If the toolset popup has several menus, show a single menu at a
// time (as if the toolset were not a popup).
let toolSet = this.getButtonToolSet(button);
if (toolSet !== null && toolSet.popup) {
XUI.Dialogs.closeSubsequentPopups(toolSet.popup);
}
}
createMenuItems(itemSpecs) {
let menuItems = [];
let addSepar = false;
for (let itemSpec of itemSpecs) {
const type = ToolBar.toolType(itemSpec);
switch (type) {
case "separator":
addSepar = true;
break;
case "button":
case "checkbox":
case "menu":
case "checkboxmenu":
{
let menuItem = {};
let keyboardShortcut = null;
if (type === "menu" || type === "checkboxmenu") {
if (!Array.isArray(itemSpec.items)) {
continue;
}
let submenuItems = this.createMenuItems(itemSpec.items);
if (submenuItems.length === 0) {
continue;
}
menuItem.type = "submenu";
menuItem.items = submenuItems;
} else {
// "button" or "checkbox" ---
menuItem.type = type;
menuItem.name = ToolBar.itemSpecCmdString(itemSpec);
if (menuItem.name === null) {
continue; // Unusable.
}
}
if (itemSpec.iconData) {
menuItem.icon = "url(" + itemSpec.iconData + ")";
} else if (itemSpec.iconName) {
menuItem.icon = ToolBar.iconNameWithFallback(itemSpec);
}
// itemSpec.iconSize is always 16 for menu items.
if (itemSpec.label) {
menuItem.text = itemSpec.label;
}
if (itemSpec.tooltip) {
menuItem.tooltip = itemSpec.tooltip;
}
if (addSepar) {
addSepar = false;
menuItem.separator = true;
}
menuItems.push(menuItem);
}
break;
}
}
return menuItems;
}
showMenu(menu, position, reference) {
this.updateMenuItems(menu);
menu.open(position, reference);
}
updateMenuItems(menu) {
const docView = this.activeDocumentView;
for (let menuItem of menu.getAllItems()) {
const name = menuItem.name;
if (name === null) {
let submenu = menuItem.submenu;
if (submenu !== null) {
this.updateMenuItems(submenu);
}
} else {
let enabled = false;
let tooltip = null;
let inSelectedState = null;
let [cmdName, cmdParam] =
Command.splitCmdString(name, ToolBar.CMD_STRING_SEPAR);
let state =
this.getCommandState(docView, cmdName, cmdParam);
if (state !== null) {
enabled = state[0];
tooltip = state[1];
inSelectedState = state[2];
}
menuItem.enabled = enabled;
if (inSelectedState !== null &&
inSelectedState !== menuItem.selected) {
menuItem.selected = inSelectedState;
}
ToolBar.updateMenuItemTooltip(menuItem, tooltip);
ToolBar.setMenuItemShortcut(
menuItem,
ToolBar.getKeyboardShortcut(docView, cmdName, cmdParam));
}
}
}
getCommandState(docView, cmdName, cmdParam) {
if (cmdName.endsWith("()")) { // Pseudo-command.
const functionName = cmdName.substring(0, cmdName.length-2);
return this[functionName](/*getState*/ true, docView, cmdParam);
} else {
if (docView === null) {
return [/*enabled*/ false, /*tooltip*/ null, /*checked*/ null];
} else {
return docView.getCommandState(cmdName, cmdParam);
}
}
}
static updateMenuItemTooltip(menuItem, info) {
switch (menuItem.name) {
case "undo":
case "redo":
case "repeat":
{
let tooltip = menuItem.getOption("tooltip");
tooltip = ToolBar.replaceTooltipInfo(tooltip,
ToolBar.TOOLTIP_INFO_BEGIN,
ToolBar.TOOLTIP_INFO_END,
info);
// Even a menu item having a text and not just and icon
// may be given a tooltip.
menuItem.setOption("tooltip", tooltip);
}
break;
}
}
static replaceTooltipInfo(tooltip, begin, end, info) {
if (tooltip !== null) {
let pos1 = tooltip.indexOf(begin);
if (pos1 >= 0) {
let pos2 = tooltip.indexOf(end, pos1 + begin.length);
if (pos2 >= 0) {
tooltip = tooltip.substring(0, pos1) +
tooltip.substring(pos2 + end.length);
}
}
}
if (info !== null) {
if (tooltip === null) {
tooltip = "";
}
let insertPos = tooltip.indexOf(ToolBar.KEYBOARD_SHORTCUT_BEGIN);
if (insertPos >= 0) {
tooltip = tooltip.substring(0, insertPos) +
begin + info + end + tooltip.substring(insertPos);
} else {
tooltip += begin + info + end;
}
}
// Note that String.trim also trims '\u00A0'!
if (tooltip !== null && tooltip.length === 0) {
tooltip = null;
}
return tooltip;
}
static getKeyboardShortcut(docView, cmdName, cmdParam) {
let keyboardShortcut = null;
if (docView !== null) {
let binding = docView.getBindingForCommand(cmdName, cmdParam);
if (binding !== null) {
keyboardShortcut = binding.getUserInputLabel();
}
}
return keyboardShortcut;
}
static setMenuItemShortcut(menuItem, keyboardShortcut) {
if (menuItem.getOption("text") !== null) {
menuItem.setOption("detail", keyboardShortcut);
} else {
let tooltip = menuItem.getOption("tooltip");
tooltip = ToolBar.replaceTooltipInfo(tooltip,
ToolBar.KEYBOARD_SHORTCUT_BEGIN,
ToolBar.KEYBOARD_SHORTCUT_END,
keyboardShortcut);
menuItem.setOption("tooltip", tooltip);
}
}
// ----------------------------------
// showToolSetPopup
// ----------------------------------
showToolSetPopup(toolSet) {
// closeAllPopups also invokes the "dialogclosed" handler below to
// clear toolSet.popup.
XUI.Dialogs.closeAllPopups();
let div = document.createElement("div");
div.setAttribute("class", "xxe-tlbr-tlst-popup");
let gridRows = new Array(3);
let grid = ToolBar.createGrid(gridRows);
div.appendChild(grid);
ToolBar.attachToolSet(toolSet, gridRows, /*includingSepar*/ false);
toolSet.popup = XUI.Dialogs.open({ form: div, type: "popup",
position: "menu",
reference: toolSet.collapsedCell });
toolSet.popup.addEventListener("dialogclosed", (event) => {
ToolBar.detachToolSet(toolSet, gridRows, /*includingSepar*/ false);
delete toolSet.popup;
});
}
// -----------------------------------------------------------------------
// Pseudo-commands
//
// Possibly invoked when docView=null.
// -----------------------------------------------------------------------
toggleSearchReplace(getState, docView, param) {
let checked = this._xmlEditor.searchReplaceIsVisible;
if (getState) {
return [/*enabled*/ true, /*tooltip*/ null, checked];
} else {
this._xmlEditor.showSearchReplace(!checked);
let button = this.getCommandButton("toggleSearchReplace()", null);
if (button !== null) {
this.commandStateChanged(docView, button,
/*toolSetsChanged*/ false);
}
// Otherwise, button not found: should not happen.
return null;
}
}
getCommandButton(cmdName, cmdParam) {
const cmdString = Command.joinCmdString(cmdName, cmdParam);
for (let button of this._commandButtons) {
if (button.getAttribute("data-command") === cmdString) {
return button;
}
}
return null;
}
// -----------------------------------------------------------------------
// Used by XMLEditor
// -----------------------------------------------------------------------
set xmlEditor(editor) {
this._xmlEditor = editor;
}
toolSetsChanged(toolSetSpecs) {
this.removeConfigSpecificToolSets();
if (toolSetSpecs !== null) {
for (let toolSetSpec of toolSetSpecs) {
this.addToolSet(toolSetSpec);
}
}
this._commandButtons =
[...this._grid.querySelectorAll("[data-command]")];
this.resizeToolSets();
this.commandStatesChanged(/*toolSetsChanged*/ true);
}
removeConfigSpecificToolSets() {
XUI.Dialogs.closeAllPopups();
const toolSets = this._toolSets;
for (let i = toolSets.length-1; i >= 0; --i) {
const toolSet = toolSets[i];
// detachToolSet requires the toolset to be expanded.
ToolBar.expandToolSet(toolSet, this._gridRows);
ToolBar.detachToolSet(toolSet, this._gridRows,
/*includingSepar*/ true);
}
this._toolSets = [];
if (toolSets.length > 0) {
const toolSet = toolSets[0]; // ToolBar.EDIT_TOOL_SET.
this._toolSets.push(toolSet);
ToolBar.attachToolSet(toolSet, this._gridRows,
/*includingSepar*/ true);
}
}
undoStateChanged() {
const docView = this.activeDocumentView;
for (let button of this._commandButtons) {
const cmdString = button.getAttribute("data-command");
switch (cmdString) {
case "undo":
case "redo":
this.commandStateChanged(docView, button);
break;
}
}
}
commandStatesChanged(toolSetsChanged=false) {
const docView = this.activeDocumentView;
for (let button of this._commandButtons) {
this.commandStateChanged(docView, button, toolSetsChanged);
}
}
commandStateChanged(docView, button, toolSetsChanged=false) {
// "checkboxmenu" button ---
const cmdStrings = button.getAttribute("data-command");
if (cmdStrings.indexOf(ToolBar.CMD_STRINGS_SEPAR) >= 0) {
button.classList.remove("xxe-tlbr-button-checked");
for (let cmdString of
cmdStrings.split(ToolBar.CMD_STRINGS_SEPAR)) {
let [cmdName, cmdParam] =
Command.splitCmdString(cmdString,
ToolBar.CMD_STRING_SEPAR);
let state =
this.getCommandState(docView, cmdName, cmdParam);
if (state !== null) {
let inSelectedState = state[2];
if (inSelectedState !== null && inSelectedState) {
button.classList.add("xxe-tlbr-button-checked");
// Done.
break;
}
}
}
// Done with this "checkboxmenu" button.
return;
}
// Other buttons ---
let enabled = false;
let tooltip = null;
let inSelectedState = null;
let [cmdName, cmdParam] =
Command.splitCmdString(cmdStrings, ToolBar.CMD_STRING_SEPAR);
let state = this.getCommandState(docView, cmdName, cmdParam);
if (state !== null) {
enabled = state[0];
tooltip = state[1];
inSelectedState = state[2];
}
if (enabled) {
button.classList.remove("xui-control-disabled");
} else {
button.classList.add("xui-control-disabled");
}
if (inSelectedState !== null) {
if (inSelectedState) {
button.classList.add("xxe-tlbr-button-checked");
} else {
button.classList.remove("xxe-tlbr-button-checked");
}
}
ToolBar.updateButtonTooltip(button, tooltip);
if (toolSetsChanged) {
ToolBar.setButtonShortcut(
button,
ToolBar.getKeyboardShortcut(docView, cmdName, cmdParam));
}
}
static updateButtonTooltip(button, info) {
const cmdString = button.getAttribute("data-command");
switch (cmdString) {
case "undo":
case "redo":
case "repeat":
{
let tooltip = button.getAttribute("title");
tooltip = ToolBar.replaceTooltipInfo(tooltip,
ToolBar.TOOLTIP_INFO_BEGIN,
ToolBar.TOOLTIP_INFO_END,
info);
if (tooltip === null) {
button.removeAttribute("title");
} else {
// Even a button having a label and not just and icon
// may be given a tooltip.
button.setAttribute("title", tooltip);
}
}
break;
}
}
static setButtonShortcut(button, keyboardShortcut) {
let tooltip = button.getAttribute("title");
tooltip = ToolBar.replaceTooltipInfo(tooltip,
ToolBar.KEYBOARD_SHORTCUT_BEGIN,
ToolBar.KEYBOARD_SHORTCUT_END,
keyboardShortcut);
if (tooltip === null) {
button.removeAttribute("title");
} else {
button.setAttribute("title", tooltip);
}
}
}
// A command name may contain space characters, e.g. "{DITA Map}promote".
// (BMP PUA: U+E000..U+F8FF)
ToolBar.CMD_STRING_SEPAR = '\uF876';
ToolBar.CMD_STRINGS_SEPAR = '\uF765';
ToolBar.TOOLTIP_INFO_BEGIN = "\u00A0\u201C"; // Left Double Quotation Mark
ToolBar.TOOLTIP_INFO_END = "\u201D"; // Right Double Quotation Mark
ToolBar.KEYBOARD_SHORTCUT_BEGIN = "\u00A0\u00A0\u00A0\u00A0[";
ToolBar.KEYBOARD_SHORTCUT_END = "]";
// ======================================================================
// IF MODIFIED, UPDATE ACCORDINGLY ../editor/DefaultContextualCommands.js
// ======================================================================
// type=button|separator|menu|checkbox|checkboxmenu; default=dynamic:
// if empty, separator else if .items, menu else button
// iconData|iconName
// iconSize; default=16
// label
// tooltip
// cmdName
// cmdParam; default=null
// items
ToolBar.EDIT_TOOL_SET = {
label: "Edit",
menu: [
{ iconName: "repeat", tooltip: "Repeat", cmdName: "repeat" },
{},
{ iconName: "commandHistory", tooltip: "Command History...",
cmdName: "listRepeatable" },
{},
{ label: "Copy as Text",
cmdName: "copyChars", cmdParam: "[separateParagraphs]" },
{},
{ iconName: "split", label: "Split", cmdName: "split" },
{ iconName: "join", label: "Join", cmdName: "join" },
{},
{ label: "Comment", items: [
{ label: "Insert Comment Before", cmdName: "insertNode",
cmdParam: "commentBefore[implicitElement]" },
{ label: "Insert Comment",
cmdName: "insertNode", cmdParam: "commentInto" },
{ label: "Insert Comment After", cmdName: "insertNode",
cmdParam: "commentAfter[implicitElement]" },
{},
{ label: "Comment Out", cmdName: "commentOut" },
{ label: "Uncomment",
cmdName: "commentOut", cmdParam: "uncomment[implicitNode]" }
] },
{ label: "Processing Instruction", items: [
{ label: "Insert Processing Instruction Before",
cmdName: "insertNode", cmdParam: "piBefore[implicitElement]" },
{ label: "Insert Processing Instruction",
cmdName: "insertNode", cmdParam: "piInto" },
{ label: "Insert Processing Instruction After",
cmdName: "insertNode", cmdParam: "piAfter[implicitElement]" },
{},
{ label: "Change Processing Instruction Target...",
cmdName: "editPITarget", cmdParam: "[implicitNode]" }
] },
{},
{ label: "Remark", items: [
{ iconName: "insertOrEditRemark", label: "Insert or Edit Remark",
cmdName: "remark" },
{},
{ iconName: "deleteRemark", label: "Delete Remark",
cmdName: "remark", cmdParam: "delete" },
{ iconName: "deleteAllRemarks", label: "Delete All Remarks",
cmdName: "remark", cmdParam: "deleteAll" },
{},
{ iconName: "previousRemark", label: "Select Preceding Remark",
cmdName: "remark", cmdParam: "previous" },
{ iconName: "nextRemark", label: "Select Following Remark",
cmdName: "remark", cmdParam: "next" }
] },
{},
{ iconName: "declareNamespace", label: "Declare Namespace...",
cmdName: "declareNamespace" },
{},
{ iconName: "styles", label: "Change Stylesheet...",
cmdName: "setStyleSheet" }
],
tools: [
{ iconName: "undo", tooltip: "Undo", cmdName: "undo" },
{ iconName: "redo", tooltip: "Redo", cmdName: "redo" },
{},
{ iconName: "copy", tooltip: "Copy",
cmdName: "copy", cmdParam: "[implicitElement]" },
{ iconName: "pasteBefore", tooltip: "Paste Before",
cmdName: "paste", cmdParam: "before[implicitElement]" },
{ iconName: "cut", tooltip: "Cut",
cmdName: "cut", cmdParam: "[implicitElement]" },
{ iconName: "paste", tooltip: "Paste",
cmdName: "paste", cmdParam: "toOrInto" },
{ iconName: "delete", tooltip: "Delete",
cmdName: "delete", cmdParam: "[implicitElement]" },
{ iconName: "pasteAfter", tooltip: "Paste After",
cmdName: "paste", cmdParam: "after[implicitElement]" },
{},
{ iconName: "replace", tooltip: "Replace...",
cmdName: "replace", cmdParam: "[implicitElement]" },
{ iconName: "insertBefore", tooltip: "Insert Before...",
cmdName: "insert", cmdParam: "before[implicitElement]" },
{ iconName: "convert", tooltip: "Convert...",
cmdName: "convert", cmdParam: "[implicitElement]" },
{ iconName: "insert", tooltip: "Insert...",
cmdName: "insert", cmdParam: "into" },
{ iconName: "wrap", tooltip: "Wrap...",
cmdName: "wrap", cmdParam: "[implicitElement]" },
{ iconName: "insertAfter", tooltip: "Insert After...",
cmdName: "insert", cmdParam: "after[implicitElement]" },
{},
{ iconName: "editAttributes", tooltip: "Edit Attributes...",
cmdName: "editAttributes", cmdParam: "[implicitElement]" },
{ iconName: "textSearchReplace", tooltip: "Search/Replace Text",
cmdName: "toggleSearchReplace()" }
]
}
window.customElements.define("xxe-tool-bar", ToolBar);