diff --git a/client/src/main/java/com/mirth/connect/client/ui/components/tag/MirthTagField.java b/client/src/main/java/com/mirth/connect/client/ui/components/tag/MirthTagField.java index 2c46de163..81648258a 100644 --- a/client/src/main/java/com/mirth/connect/client/ui/components/tag/MirthTagField.java +++ b/client/src/main/java/com/mirth/connect/client/ui/components/tag/MirthTagField.java @@ -4,11 +4,18 @@ package com.mirth.connect.client.ui.components.tag; +import java.awt.AWTEvent; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; -import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusAdapter; @@ -17,6 +24,8 @@ import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.awt.event.MouseWheelEvent; +import java.awt.event.MouseWheelListener; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -27,16 +36,21 @@ import java.util.TreeMap; import javax.swing.AbstractAction; +import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; +import javax.swing.JViewport; import javax.swing.KeyStroke; import javax.swing.Scrollable; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.border.AbstractBorder; +import javax.swing.border.Border; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; +import javax.swing.plaf.metal.MetalLookAndFeel; import net.miginfocom.swing.MigLayout; @@ -60,6 +74,11 @@ * A single-line field holding a row of tag chips plus a text editor for the tag being typed. * Completions are supplied through {@link #update} and offered by an autocomplete popup installed * on the editor. + *
+ * The editor sits between two chips, or before or after all of them. Typing puts it in edit mode,
+ * shown by an outline, where the arrow, Home, End, Backspace and Delete keys edit its text. Outside
+ * edit mode, those keys move the editor between the chips and delete the chips next to it instead.
+ * Committing the text or pressing Escape leaves edit mode, as does losing focus with no text.
*/
public class MirthTagField extends JPanel {
private static final String TAG_TYPE = "tag";
@@ -70,8 +89,20 @@ public class MirthTagField extends JPanel {
private static final int MAX_TAGS = 10;
private static final int MAX_TAG_LENGTH = 24;
private static final Color DEFAULT_BACKGROUND = new Color(0xEE, 0xEE, 0xEE);
- private static final Font FONT = new Font("Tahoma", Font.PLAIN, 11);
private static final int SCROLL_INCREMENT = 20;
+ /** Sized to its text, so the editor grows as the tag is typed. */
+ private static final String EDITOR_CONSTRAINTS = "";
+ /** While it shows its prompt, the editor takes up the rest of the row. */
+ private static final String PROMPT_EDITOR_CONSTRAINTS = "growx, pushx, w 60::";
+ /** The scroll amount the toolkits report for one step of the wheel. */
+ private static final int WHEEL_SCROLL_AMOUNT = 3;
+ /**
+ * The X11 toolkit reports horizontal scrolling (X buttons 6 and 7) as presses of mouse buttons 4
+ * and 5 instead of as wheel events. On other toolkits those buttons are back and forward.
+ */
+ private static final boolean X11_HORIZONTAL_SCROLL_BUTTONS = "sun.awt.X11.XToolkit".equals(Toolkit.getDefaultToolkit().getClass().getName());
+ /** Room after the text for the caret. */
+ private static final int CARET_ROOM = 2;
/**
* Lays the chips out in a single row that fills the viewport while they fit and scrolls
@@ -108,6 +139,30 @@ public boolean getScrollableTracksViewportHeight() {
}
}
+ /** The edit mode outline: a thin rounded rectangle with the same corners as the chips. */
+ private static class OutlineBorder extends AbstractBorder {
+ private final Color color;
+
+ OutlineBorder(Color color) {
+ this.color = color;
+ }
+
+ @Override
+ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
+ Graphics2D g2 = (Graphics2D) g.create();
+ g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g2.setColor(color);
+ g2.drawRoundRect(x, y, width - 1, height - 1, TagChip.ARC, TagChip.ARC);
+ g2.dispose();
+ }
+
+ @Override
+ public Insets getBorderInsets(Component c, Insets insets) {
+ insets.set(1, 1, 1, 1);
+ return insets;
+ }
+ }
+
/** A tag currently shown in the field. */
private static class Token {
private final String type;
@@ -132,13 +187,36 @@ private static class Token {
private JScrollPane scrollPane;
private ChipRow chipRow;
private JTextField editor;
+ private Border idleBorder;
+ private Border editingBorder;
private IconButton clearButton;
private MouseAdapter focusEditorAdapter;
+ /** Installed while the field is displayable, on X11 only. */
+ private final AWTEventListener horizontalScrollButtonListener = new AWTEventListener() {
+ @Override
+ public void eventDispatched(AWTEvent event) {
+ MouseEvent evt = (MouseEvent) event;
+ int button = evt.getButton();
+
+ if (evt.getID() == MouseEvent.MOUSE_PRESSED && (button == 4 || button == 5) && SwingUtilities.isDescendingFrom(evt.getComponent(), scrollPane)) {
+ // Only marks the press as handled; Swing's mouse listeners still receive it.
+ evt.consume();
+ scrollChipsBy((button == 4 ? -1 : 1) * WHEEL_SCROLL_AMOUNT);
+ }
+ }
+ };
private AutoCompletionProvider provider;
private AutoCompletionDelegate completionDelegate;
private final List
+ * Like a browser tab, a chip is deleted by a left click on its "×" or a middle click anywhere on
+ * it. Either acts on release, and only if the pointer is still over what was pressed.
*/
public class TagChip extends JPanel {
- private static final Font FONT = new Font("Tahoma", Font.PLAIN, 11);
+ /** Also used by {@link MirthTagField}'s editor, so typed text looks like the chips. */
+ static final Font FONT = new Font("Tahoma", Font.PLAIN, 11);
private static final Font CLOSE_FONT = new Font("Tahoma", Font.BOLD, 12);
private static final int HEIGHT = 20;
- private static final int ARC = 6;
+ /** Also used by {@link MirthTagField}'s edit mode outline. */
+ static final int ARC = 6;
private static final float OPACITY = 0.9f;
private static final Color BORDER_COLOR = new Color(0xD9, 0xD9, 0xD9);
@@ -44,14 +50,31 @@ public TagChip(String name, Color background, Color foreground, final Runnable o
nameLabel.setForeground(foreground);
add(nameLabel);
+ // The name label has no listeners of its own, so presses on it land here too.
+ addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseReleased(MouseEvent e) {
+ if (isEnabled() && SwingUtilities.isMiddleMouseButton(e) && contains(e.getPoint())) {
+ onDelete.run();
+ }
+ }
+ });
+
closeLabel = new JLabel("×");
closeLabel.setFont(CLOSE_FONT);
closeLabel.setForeground(foreground);
closeLabel.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
closeLabel.addMouseListener(new MouseAdapter() {
+ /*
+ * Released rather than clicked: a click is dropped if the pointer moves at all while the
+ * button is down, which a trackpoint nearly always does. Other buttons are ignored; they
+ * include horizontal scrolling on X11, which arrives as buttons 4 and 5.
+ */
@Override
- public void mouseClicked(MouseEvent e) {
- if (isEnabled()) {
+ public void mouseReleased(MouseEvent e) {
+ boolean deleteButton = SwingUtilities.isLeftMouseButton(e) || SwingUtilities.isMiddleMouseButton(e);
+
+ if (isEnabled() && deleteButton && closeLabel.contains(e.getPoint())) {
onDelete.run();
}
}
diff --git a/server/docs/thirdparty/THIRD-PARTY-README.txt b/server/docs/thirdparty/THIRD-PARTY-README.txt
index e8a549a15..3d2868086 100644
--- a/server/docs/thirdparty/THIRD-PARTY-README.txt
+++ b/server/docs/thirdparty/THIRD-PARTY-README.txt
@@ -408,37 +408,6 @@ THE SOFTWARE.
--------------------------------------------------------------------------------
-The license below pertains to the bootstrap-tokenfield library.
-
-=================== Beginning of License Information ===================
-
-Copyright (c) 2013 by Sliptree
-https://github.com/sliptree/bootstrap-tokenfield
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
-
-=================== End of License Information ===================
-
-
---------------------------------------------------------------------------------
-
-
The license information below pertains to Flying Saucer version 9.0.1. This
library is covered by the LGPL version 2.1 (see LGPL-2.1.txt) and its source
code is available at