| // Copyright (C) 2009 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gwtexpui.globalkey.client; |
| |
| import static java.util.Comparator.comparing; |
| import static java.util.stream.Collectors.toList; |
| |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.event.dom.client.KeyCodes; |
| import com.google.gwt.event.dom.client.KeyDownEvent; |
| import com.google.gwt.event.dom.client.KeyDownHandler; |
| import com.google.gwt.event.dom.client.KeyPressEvent; |
| import com.google.gwt.event.dom.client.KeyPressHandler; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.ui.Anchor; |
| import com.google.gwt.user.client.ui.FlowPanel; |
| import com.google.gwt.user.client.ui.FocusPanel; |
| import com.google.gwt.user.client.ui.Grid; |
| import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; |
| import com.google.gwt.user.client.ui.HasHorizontalAlignment; |
| import com.google.gwt.user.client.ui.PopupPanel; |
| import com.google.gwtexpui.safehtml.client.SafeHtml; |
| import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| public class KeyHelpPopup extends PopupPanel implements KeyPressHandler, KeyDownHandler { |
| private final FocusPanel focus; |
| |
| public KeyHelpPopup() { |
| super(true /* autohide */, true /* modal */); |
| setStyleName(KeyResources.I.css().helpPopup()); |
| |
| final Anchor closer = new Anchor(KeyConstants.I.closeButton()); |
| closer.addClickHandler( |
| new ClickHandler() { |
| @Override |
| public void onClick(ClickEvent event) { |
| hide(); |
| } |
| }); |
| |
| final Grid header = new Grid(1, 3); |
| header.setStyleName(KeyResources.I.css().helpHeader()); |
| header.setText(0, 0, KeyConstants.I.keyboardShortcuts()); |
| header.setWidget(0, 2, closer); |
| |
| final CellFormatter fmt = header.getCellFormatter(); |
| fmt.addStyleName(0, 1, KeyResources.I.css().helpHeaderGlue()); |
| fmt.setHorizontalAlignment(0, 2, HasHorizontalAlignment.ALIGN_RIGHT); |
| |
| final Grid lists = new Grid(0, 7); |
| lists.setStyleName(KeyResources.I.css().helpTable()); |
| populate(lists); |
| lists.getCellFormatter().addStyleName(0, 3, KeyResources.I.css().helpTableGlue()); |
| |
| final FlowPanel body = new FlowPanel(); |
| body.add(header); |
| body.getElement().appendChild(DOM.createElement("hr")); |
| body.add(lists); |
| |
| focus = new FocusPanel(body); |
| focus.getElement().getStyle().setProperty("outline", "0px"); |
| focus.getElement().setAttribute("hideFocus", "true"); |
| focus.addKeyPressHandler(this); |
| focus.addKeyDownHandler(this); |
| add(focus); |
| } |
| |
| @Override |
| public void setVisible(boolean show) { |
| super.setVisible(show); |
| if (show) { |
| focus.setFocus(true); |
| } |
| } |
| |
| @Override |
| public void onKeyPress(KeyPressEvent event) { |
| if (KeyCommandSet.toMask(event) == ShowHelpCommand.INSTANCE.keyMask) { |
| // Block the '?' key from triggering us to show right after |
| // we just hide ourselves. |
| // |
| event.stopPropagation(); |
| event.preventDefault(); |
| } |
| hide(); |
| } |
| |
| @Override |
| public void onKeyDown(KeyDownEvent event) { |
| if (event.getNativeKeyCode() == KeyCodes.KEY_ESCAPE) { |
| hide(); |
| } |
| } |
| |
| private void populate(Grid lists) { |
| int[] end = new int[5]; |
| int column = 0; |
| for (KeyCommandSet set : combinedSetsByName()) { |
| int row = end[column]; |
| row = formatGroup(lists, row, column, set); |
| end[column] = row; |
| if (column == 0) { |
| column = 4; |
| } else { |
| column = 0; |
| } |
| } |
| } |
| |
| /** |
| * @return an ordered collection of KeyCommandSet, combining sets which share the same name, so |
| * that each set name appears at most once. |
| */ |
| private static Collection<KeyCommandSet> combinedSetsByName() { |
| LinkedHashMap<String, KeyCommandSet> byName = new LinkedHashMap<>(); |
| for (KeyCommandSet set : GlobalKey.active.all.getSets()) { |
| KeyCommandSet v = byName.get(set.getName()); |
| if (v == null) { |
| v = new KeyCommandSet(set.getName()); |
| byName.put(v.getName(), v); |
| } |
| v.add(set); |
| } |
| return byName.values(); |
| } |
| |
| private int formatGroup(Grid lists, int row, int col, KeyCommandSet set) { |
| if (set.isEmpty()) { |
| return row; |
| } |
| |
| if (lists.getRowCount() < row + 1) { |
| lists.resizeRows(row + 1); |
| } |
| lists.setText(row, col + 2, set.getName()); |
| lists.getCellFormatter().addStyleName(row, col + 2, KeyResources.I.css().helpGroup()); |
| row++; |
| |
| return formatKeys(lists, row, col, set, null); |
| } |
| |
| private int formatKeys(final Grid lists, int row, int col, KeyCommandSet set, SafeHtml prefix) { |
| final CellFormatter fmt = lists.getCellFormatter(); |
| final List<KeyCommand> keys = sort(set); |
| if (lists.getRowCount() < row + keys.size()) { |
| lists.resizeRows(row + keys.size()); |
| } |
| |
| Map<KeyCommand, Integer> rows = new HashMap<>(); |
| FORMAT_KEYS: |
| for (int i = 0; i < keys.size(); i++) { |
| final KeyCommand k = keys.get(i); |
| if (rows.containsKey(k)) { |
| continue; |
| } |
| |
| if (k instanceof CompoundKeyCommand) { |
| final SafeHtmlBuilder b = new SafeHtmlBuilder(); |
| b.append(k.describeKeyStroke()); |
| row = formatKeys(lists, row, col, ((CompoundKeyCommand) k).getSet(), b); |
| continue; |
| } |
| |
| for (int prior = 0; prior < i; prior++) { |
| if (KeyCommand.same(keys.get(prior), k)) { |
| final int r = rows.get(keys.get(prior)); |
| final SafeHtmlBuilder b = new SafeHtmlBuilder(); |
| b.append(SafeHtml.get(lists, r, col + 0)); |
| b.append(" "); |
| b.append(KeyConstants.I.orOtherKey()); |
| b.append(" "); |
| if (prefix != null) { |
| b.append(prefix); |
| b.append(" "); |
| b.append(KeyConstants.I.thenOtherKey()); |
| b.append(" "); |
| } |
| b.append(k.describeKeyStroke()); |
| SafeHtml.set(lists, r, col + 0, b); |
| rows.put(k, r); |
| continue FORMAT_KEYS; |
| } |
| } |
| |
| SafeHtmlBuilder b = new SafeHtmlBuilder(); |
| String t = k.getHelpText(); |
| if (prefix != null) { |
| b.append(prefix); |
| b.append(" "); |
| b.append(KeyConstants.I.thenOtherKey()); |
| b.append(" "); |
| } |
| b.append(k.describeKeyStroke()); |
| if (k.sibling != null) { |
| b.append(" / ").append(k.sibling.describeKeyStroke()); |
| t += " / " + k.sibling.getHelpText(); |
| rows.put(k.sibling, row); |
| } |
| SafeHtml.set(lists, row, col + 0, b); |
| lists.setText(row, col + 1, ":"); |
| lists.setText(row, col + 2, t); |
| rows.put(k, row); |
| |
| fmt.addStyleName(row, col + 0, KeyResources.I.css().helpKeyStroke()); |
| fmt.addStyleName(row, col + 1, KeyResources.I.css().helpSeparator()); |
| row++; |
| } |
| |
| return row; |
| } |
| |
| private List<KeyCommand> sort(KeyCommandSet set) { |
| return set.getKeys().stream().sorted(comparing(KeyCommand::getHelpText)).collect(toList()); |
| } |
| } |