From c2163d7b6217c51969b9a2a0a35b634e8eb29982 Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Thu, 2 Jul 2026 23:10:22 -0600 Subject: [PATCH 1/3] Removal Tooltip "Shift-click to remove task and children" added as an alternative tooltip if holding shift while hovering over a row. This functionality previously had no indication of ever existing until now. --- .../goaltracker/ui/TaskItemContent.java | 90 +++++++++++++++++-- 1 file changed, 85 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java index 6315c00..8652e6a 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java +++ b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java @@ -13,10 +13,7 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.util.Locale; import static com.toofifty.goaltracker.utils.Constants.STATUS_TO_COLOR; @@ -33,7 +30,15 @@ public final class TaskItemContent extends JPanel implements Refreshable private final Task task; private final Goal goal; private final TaskIconService iconService; - private final JLabel titleLabel = new JLabel(); + private final JLabel titleLabel = new JLabel() { + @Override + public String getToolTipText(MouseEvent event) { + if (event != null && (event.getModifiersEx() & java.awt.event.InputEvent.SHIFT_DOWN_MASK) != 0) { + return "Shift-click to remove task and children"; + } + return super.getToolTipText(); + } + }; private final JTextField titleEdit = new JTextField(); private final JPanel titleStack = new JPanel(new CardLayout()); private final JLabel iconLabel = new JLabel(); @@ -134,6 +139,81 @@ private void showMenuIfNeeded(MouseEvent e) titleLabel.addMouseListener(contextMenuListener); titleEdit.addMouseListener(contextMenuListener); iconLabel.addMouseListener(contextMenuListener); + + // Track the Shift key toggling entirely within the Swing thread context + KeyEventDispatcher shiftWatcher = new KeyEventDispatcher() + { + private boolean shiftWasDown = false; + + @Override + public boolean dispatchKeyEvent(KeyEvent e) + { + if (e.getKeyCode() == KeyEvent.VK_SHIFT) + { + boolean isShiftDown = e.isShiftDown(); + // Only fire an update when the state actually cuts over (press or release) + if (isShiftDown != shiftWasDown) + { + shiftWasDown = isShiftDown; + // Pass the exact shift state safely to the EDT loop + SwingUtilities.invokeLater(() -> forceTooltipUpdate(isShiftDown)); + } + } + return false; // Pass the event along so other text inputs function normally + } + }; + + + // Bind the listener when this component is physically displayed on screen, and unbind to prevent leaks + this.addAncestorListener(new javax.swing.event.AncestorListener() { + @Override + public void ancestorAdded(javax.swing.event.AncestorEvent event) { + KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(shiftWatcher); + } + + @Override + public void ancestorRemoved(javax.swing.event.AncestorEvent event) { + KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(shiftWatcher); + } + + @Override + public void ancestorMoved(javax.swing.event.AncestorEvent event) {} + }); + } + private void forceTooltipUpdate(boolean isShiftActive) + { + PointerInfo pointerInfo = MouseInfo.getPointerInfo(); + if (pointerInfo == null) return; + + Point mousePos = pointerInfo.getLocation(); + SwingUtilities.convertPointFromScreen(mousePos, titleLabel); + + // Only process if the cursor is resting inside our text label coordinates + if (titleLabel.contains(mousePos)) + { + long now = System.currentTimeMillis(); + int modifiers = isShiftActive ? java.awt.event.InputEvent.SHIFT_DOWN_MASK : 0; + + // 1. Explicitly mutate the underlying text property string + // This forces Swing to register a change in state and clear its internal raster cache + if (isShiftActive) { + titleLabel.setToolTipText("Shift-click to remove task and children"); + } else { + String full = task.toString(); + titleLabel.setToolTipText((full == null || full.isEmpty()) ? null : full); + } + + // 2. Synthesize a live mouse movement event at the cursor's current position + // We pass the exact shift modifier mask down into the tracking system + MouseEvent moveEvent = new MouseEvent( + titleLabel, MouseEvent.MOUSE_MOVED, now, + modifiers, mousePos.x, mousePos.y, 0, false + ); + + // 3. Command the active manager instance to evaluate the position + // Since the text property just changed, the manager is forced to immediately update the visual bubble + ToolTipManager.sharedInstance().mouseMoved(moveEvent); + } } public void setActionHistory(ActionHistory history) From 1db6b54d169d01d1e984422f49f580fecf7c91fd Mon Sep 17 00:00:00 2001 From: Darkforge317 Date: Fri, 3 Jul 2026 00:09:06 -0600 Subject: [PATCH 2/3] Trash Icon Display a trash icon when holding shift over a task. Makes it more obvious and is friendlier UX. --- .../goaltracker/ui/TaskItemContent.java | 83 +++++++++++++++--- src/main/resources/trash.png | Bin 0 -> 716 bytes 2 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 src/main/resources/trash.png diff --git a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java index 8652e6a..4f463f3 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java +++ b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java @@ -48,6 +48,22 @@ public String getToolTipText(MouseEvent event) { private final GoalTrackerPlugin plugin; private ActionHistory actionHistory; + // Custom Trash Cursor Assets + private static final Cursor DEFAULT_PANEL_CURSOR = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR); + private static final Cursor TRASH_CURSOR; + + static { + Cursor tempCursor; + try { + java.awt.image.BufferedImage trashImg = net.runelite.client.util.ImageUtil.loadImageResource(TaskItemContent.class, "/trash.png"); + Point hotspot = new Point(0, 0); + tempCursor = java.awt.Toolkit.getDefaultToolkit().createCustomCursor(trashImg, hotspot, "TrashCursor"); + } catch (Exception e) { + tempCursor = Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR); + } + TRASH_CURSOR = tempCursor; + } + TaskItemContent(GoalTrackerPlugin plugin, Goal goal, Task task) { super(new BorderLayout()); @@ -159,7 +175,7 @@ public boolean dispatchKeyEvent(KeyEvent e) SwingUtilities.invokeLater(() -> forceTooltipUpdate(isShiftDown)); } } - return false; // Pass the event along so other text inputs function normally + return false; } }; @@ -179,7 +195,56 @@ public void ancestorRemoved(javax.swing.event.AncestorEvent event) { @Override public void ancestorMoved(javax.swing.event.AncestorEvent event) {} }); + + // Ensure that cursor styles actively adapt whenever the mouse slides onto or off a row layout + MouseAdapter rowHoverCursorAdapter = new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + boolean isShiftCurrentlyDown = (e.getModifiersEx() & java.awt.event.InputEvent.SHIFT_DOWN_MASK) != 0; + if (isShiftCurrentlyDown) { + updateAllChildCursors(TRASH_CURSOR); + } else { + updateAllChildCursors(DEFAULT_PANEL_CURSOR); + } + } + + @Override + public void mouseExited(MouseEvent e) { + PointerInfo pi = MouseInfo.getPointerInfo(); + if (pi != null) { + Point mousePos = pi.getLocation(); + SwingUtilities.convertPointFromScreen(mousePos, TaskItemContent.this); + + if (!TaskItemContent.this.contains(mousePos)) { + updateAllChildCursors(DEFAULT_PANEL_CURSOR); + } + } else { + updateAllChildCursors(DEFAULT_PANEL_CURSOR); + } + } + }; + + this.addMouseListener(rowHoverCursorAdapter); + titleStack.addMouseListener(rowHoverCursorAdapter); + titleLabel.addMouseListener(rowHoverCursorAdapter); + titleEdit.addMouseListener(rowHoverCursorAdapter); + iconLabel.addMouseListener(rowHoverCursorAdapter); + if (iconWrapper != null) { + iconWrapper.addMouseListener(rowHoverCursorAdapter); + } } + + private void updateAllChildCursors(Cursor cursor) { + this.setCursor(cursor); + titleStack.setCursor(cursor); + titleLabel.setCursor(cursor); + titleEdit.setCursor(cursor); + iconLabel.setCursor(cursor); + if (iconWrapper != null) { + iconWrapper.setCursor(cursor); + } + } + private void forceTooltipUpdate(boolean isShiftActive) { PointerInfo pointerInfo = MouseInfo.getPointerInfo(); @@ -193,25 +258,15 @@ private void forceTooltipUpdate(boolean isShiftActive) { long now = System.currentTimeMillis(); int modifiers = isShiftActive ? java.awt.event.InputEvent.SHIFT_DOWN_MASK : 0; - - // 1. Explicitly mutate the underlying text property string - // This forces Swing to register a change in state and clear its internal raster cache if (isShiftActive) { titleLabel.setToolTipText("Shift-click to remove task and children"); + updateAllChildCursors(TRASH_CURSOR); } else { String full = task.toString(); titleLabel.setToolTipText((full == null || full.isEmpty()) ? null : full); + updateAllChildCursors(DEFAULT_PANEL_CURSOR); } - - // 2. Synthesize a live mouse movement event at the cursor's current position - // We pass the exact shift modifier mask down into the tracking system - MouseEvent moveEvent = new MouseEvent( - titleLabel, MouseEvent.MOUSE_MOVED, now, - modifiers, mousePos.x, mousePos.y, 0, false - ); - - // 3. Command the active manager instance to evaluate the position - // Since the text property just changed, the manager is forced to immediately update the visual bubble + MouseEvent moveEvent = new MouseEvent(titleLabel, MouseEvent.MOUSE_MOVED, now, modifiers, mousePos.x, mousePos.y, 0, false); ToolTipManager.sharedInstance().mouseMoved(moveEvent); } } diff --git a/src/main/resources/trash.png b/src/main/resources/trash.png new file mode 100644 index 0000000000000000000000000000000000000000..2bd4d25a31ad6fc3220d74290376d3c228310b30 GIT binary patch literal 716 zcmV;-0yF)IP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!TXTf@c+G+VSr&^ruETcA1DcywA!dEjT;-cMps&+?)(gX0KbPDmzt>2g|V@1tVvy% z5Z{&BVk#x2&>3Xr&I|+daA8qM&+07xzngPTF2R1~a=FA*K7ZS`tnuZQmFK6Xr-j1Q z$p>A}`(l{p{l%r_&%JD~NUs@EluI|~uP+WMDzR+476hTM>Dn;>*weIym#-GC0O<5% z)@z)0J2IZhT#%&15k*l`s;Z_A4||&Lf?Y;n?;zfO2oz9>&|%)z>EH zp6j+D%PGVq35u#f6h&~0!*MJ$>h%(UZwIiPl%oWMwA&6kt_#C3U>XKY&OrzPr4)>^ zSpS{=Vc+)|Wt2rl5rhzgA>g_$0Dw^n+jih~yYB#a2k@^-Wu05rrXUEQj6oDd5c>C? zp6|glOlVDgsh1zX05?#JT^L?RKyq9US#0Hf8yp59#B+}`=ommhp-qg*b=VzG+T yc4jqQPw(#TqFSw@{B!GZ@#Cj^Ubpk1KmP|g-Uck%ZLN_20000 Date: Fri, 3 Jul 2026 00:18:47 -0600 Subject: [PATCH 3/3] Import Additions for Cleanup Cleaned up by adding imports for things that had direct references. --- .../goaltracker/ui/TaskItemContent.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java index 4f463f3..88184aa 100644 --- a/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java +++ b/src/main/java/com/toofifty/goaltracker/ui/TaskItemContent.java @@ -12,8 +12,11 @@ import javax.swing.*; import javax.swing.border.EmptyBorder; +import javax.swing.event.AncestorEvent; +import javax.swing.event.AncestorListener; import java.awt.*; import java.awt.event.*; +import java.awt.image.BufferedImage; import java.util.Locale; import static com.toofifty.goaltracker.utils.Constants.STATUS_TO_COLOR; @@ -33,7 +36,7 @@ public final class TaskItemContent extends JPanel implements Refreshable private final JLabel titleLabel = new JLabel() { @Override public String getToolTipText(MouseEvent event) { - if (event != null && (event.getModifiersEx() & java.awt.event.InputEvent.SHIFT_DOWN_MASK) != 0) { + if (event != null && (event.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0) { return "Shift-click to remove task and children"; } return super.getToolTipText(); @@ -55,7 +58,7 @@ public String getToolTipText(MouseEvent event) { static { Cursor tempCursor; try { - java.awt.image.BufferedImage trashImg = net.runelite.client.util.ImageUtil.loadImageResource(TaskItemContent.class, "/trash.png"); + BufferedImage trashImg = net.runelite.client.util.ImageUtil.loadImageResource(TaskItemContent.class, "/trash.png"); Point hotspot = new Point(0, 0); tempCursor = java.awt.Toolkit.getDefaultToolkit().createCustomCursor(trashImg, hotspot, "TrashCursor"); } catch (Exception e) { @@ -101,8 +104,8 @@ public String getToolTipText(MouseEvent event) { @Override public void mouseClicked(MouseEvent e) { enterEdit(); } }); titleEdit.addActionListener(e -> exitEdit(true)); - titleEdit.addFocusListener(new java.awt.event.FocusAdapter() { - @Override public void focusLost(java.awt.event.FocusEvent e) { exitEdit(true); } + titleEdit.addFocusListener(new FocusAdapter() { + @Override public void focusLost(FocusEvent e) { exitEdit(true); } }); } @@ -181,26 +184,26 @@ public boolean dispatchKeyEvent(KeyEvent e) // Bind the listener when this component is physically displayed on screen, and unbind to prevent leaks - this.addAncestorListener(new javax.swing.event.AncestorListener() { + this.addAncestorListener(new AncestorListener() { @Override - public void ancestorAdded(javax.swing.event.AncestorEvent event) { + public void ancestorAdded(AncestorEvent event) { KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(shiftWatcher); } @Override - public void ancestorRemoved(javax.swing.event.AncestorEvent event) { + public void ancestorRemoved(AncestorEvent event) { KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(shiftWatcher); } @Override - public void ancestorMoved(javax.swing.event.AncestorEvent event) {} + public void ancestorMoved(AncestorEvent event) {} }); // Ensure that cursor styles actively adapt whenever the mouse slides onto or off a row layout MouseAdapter rowHoverCursorAdapter = new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { - boolean isShiftCurrentlyDown = (e.getModifiersEx() & java.awt.event.InputEvent.SHIFT_DOWN_MASK) != 0; + boolean isShiftCurrentlyDown = (e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) != 0; if (isShiftCurrentlyDown) { updateAllChildCursors(TRASH_CURSOR); } else { @@ -257,7 +260,7 @@ private void forceTooltipUpdate(boolean isShiftActive) if (titleLabel.contains(mousePos)) { long now = System.currentTimeMillis(); - int modifiers = isShiftActive ? java.awt.event.InputEvent.SHIFT_DOWN_MASK : 0; + int modifiers = isShiftActive ? InputEvent.SHIFT_DOWN_MASK : 0; if (isShiftActive) { titleLabel.setToolTipText("Shift-click to remove task and children"); updateAllChildCursors(TRASH_CURSOR);