Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
- Fixed Accept/Decline and Yes/No button colors to respect the IDE Look and Feel theme (dark themes no longer show clashing hardcoded colors)
- Added session management when opening a session from the toolbar and when closing a tab via the Close button
- Added configurable session mode: choose to resume the last session or start a new one

Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.github.nbclaudecodegui</groupId>
<artifactId>netbeans-claude-code-gui</artifactId>
<version>0.21.12-SNAPSHOT</version>
<version>0.21.14-SNAPSHOT</version>
<packaging>nbm</packaging>
<name>Netbeans Claude Code GUI</name>
<description>NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.</description>
Expand Down
20 changes: 4 additions & 16 deletions src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.nbclaudecodegui.model.ChoiceMenuModel;
import io.github.nbclaudecodegui.ui.common.DecoratedTextField;
import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyEvent;
Expand Down Expand Up @@ -117,23 +118,10 @@ public void show(ChoiceMenuModel model, Consumer<String> callback) {
String display = opt.display().trim();
String response = opt.response();
String label = display.equalsIgnoreCase("Yes")
? FileDiffPermissionPanel.ICON_ACCEPT + " " + display
: FileDiffPermissionPanel.ICON_DECLINE + " " + display;
? UiUtils.ICON_CHECK + " " + display
: UiUtils.ICON_CROSS + " " + display;
JButton btn = new JButton(label);
Color baseColor = display.equalsIgnoreCase("Yes")
? new Color(34, 139, 34) : new Color(178, 34, 34);
Color focusColor = baseColor.brighter();
btn.setBackground(baseColor);
btn.setForeground(Color.WHITE);
btn.setOpaque(true);
btn.addFocusListener(new java.awt.event.FocusAdapter() {
@Override public void focusGained(java.awt.event.FocusEvent e) {
btn.setBackground(focusColor);
}
@Override public void focusLost(java.awt.event.FocusEvent e) {
btn.setBackground(baseColor);
}
});
UiUtils.applyActionStyle(btn, display.equalsIgnoreCase("Yes"));
btn.addActionListener(e -> {
LOG.info("[ChoiceMenuPanel] yes/no clicked: \"" + display + "\" → \"" + response + "\"");
submitAnswer(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.github.nbclaudecodegui.settings.ClaudeCodePreferences;
import io.github.nbclaudecodegui.ui.common.AtPathHighlighter;
import io.github.nbclaudecodegui.ui.common.DecoratedTextArea;
import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
Expand Down Expand Up @@ -134,8 +135,8 @@ public ClaudePromptPanel(Consumer<String> onSend,
// Install @-path highlighter (DecoratedTextArea implements RangeHighlightable)
pathHighlighter = AtPathHighlighter.install(inputArea);

sendButton = new JButton("<html><font color='#228B22'>" + ICON_SEND + "</font> Send</html>");
cancelButton = new JButton("<html><font color='#B22222'>" + ICON_CANCEL + "</font> Cancel</html>");
sendButton = new JButton("<html><font color='" + UiUtils.toHex(UiUtils.getPositiveColor()) + "'>" + ICON_SEND + "</font> Send</html>");
cancelButton = new JButton("<html><font color='" + UiUtils.toHex(UiUtils.getNegativeColor()) + "'>" + ICON_CANCEL + "</font> Cancel</html>");
sendButton.setEnabled(false);
cancelButton.setEnabled(false);
sendButton.addActionListener(e -> doSend());
Expand All @@ -151,7 +152,7 @@ public ClaudePromptPanel(Consumer<String> onSend,
buttonCol.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));

JButton historyButton = new JButton("<html>" + ICON_HISTORY + " History</html>");
JButton favoritesButton = new JButton("<html><font color='#CC6600'>" + ICON_FAVORITES + "</font> Favorites</html>");
JButton favoritesButton = new JButton("<html><font color='" + UiUtils.toHex(UiUtils.getWarningColor()) + "'>" + ICON_FAVORITES + "</font> Favorites</html>");
historyButton.setAlignmentX(Component.CENTER_ALIGNMENT);
favoritesButton.setAlignmentX(Component.CENTER_ALIGNMENT);
historyButton.setToolTipText("Browse prompt history");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.nbclaudecodegui.ui;

import io.github.nbclaudecodegui.ui.common.DecoratedTextField;
import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
Expand Down Expand Up @@ -40,14 +41,8 @@ public final class FileDiffPermissionPanel extends JPanel {
/** Placeholder text shown in the decline reason field when it is empty. */
static final String REASON_HINT = "Decline reason (Optional)";

/** \u2713 CHECK MARK — used on confirmation buttons (Accept, Yes) */
public static final String ICON_ACCEPT = "\u2713";

/** Double check mark — used on AcceptAll button */
private static final String ICON_ACCEPT_ALL = ICON_ACCEPT + ICON_ACCEPT;

/** \u2717 BALLOT X — used on decline buttons (Decline, No) */
public static final String ICON_DECLINE = "\u2717";
private static final String ICON_ACCEPT_ALL = UiUtils.ICON_CHECK + UiUtils.ICON_CHECK;

/**
* Creates the panel.
Expand All @@ -72,34 +67,14 @@ public FileDiffPermissionPanel(Runnable onAccept, Consumer<String> onDecline,
acceptAllBtn.setEnabled(onAcceptAll != null);

// Accept button (green)
JButton acceptBtn = new JButton(ICON_ACCEPT + " Accept");
JButton acceptBtn = new JButton(UiUtils.ICON_CHECK + " Accept");
acceptBtn.setToolTipText("Accept this change");
acceptBtn.setOpaque(true);
acceptBtn.setBackground(new Color(34, 139, 34));
acceptBtn.setForeground(Color.WHITE);
acceptBtn.addFocusListener(new java.awt.event.FocusAdapter() {
@Override public void focusGained(java.awt.event.FocusEvent e) {
acceptBtn.setBackground(new Color(34, 139, 34).brighter());
}
@Override public void focusLost(java.awt.event.FocusEvent e) {
acceptBtn.setBackground(new Color(34, 139, 34));
}
});
UiUtils.applyActionStyle(acceptBtn, true);

// Decline button (red)
JButton declineBtn = new JButton(ICON_DECLINE + " Decline");
JButton declineBtn = new JButton(UiUtils.ICON_CROSS + " Decline");
declineBtn.setToolTipText("Decline this change");
declineBtn.setOpaque(true);
declineBtn.setBackground(new Color(178, 34, 34));
declineBtn.setForeground(Color.WHITE);
declineBtn.addFocusListener(new java.awt.event.FocusAdapter() {
@Override public void focusGained(java.awt.event.FocusEvent e) {
declineBtn.setBackground(new Color(178, 34, 34).brighter());
}
@Override public void focusLost(java.awt.event.FocusEvent e) {
declineBtn.setBackground(new Color(178, 34, 34));
}
});
UiUtils.applyActionStyle(declineBtn, false);

// Reason field
DecoratedTextField reasonField = new DecoratedTextField(() -> wdFinal);
Expand Down
79 changes: 79 additions & 0 deletions src/main/java/io/github/nbclaudecodegui/ui/common/UiUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package io.github.nbclaudecodegui.ui.common;

import java.awt.Color;
import javax.swing.JButton;
import javax.swing.UIManager;

/** Shared UI utility helpers for the plugin. */
public final class UiUtils {

/** ✓ CHECK MARK — used on confirmation/positive action buttons. */
public static final String ICON_CHECK = "\u2713";

/** ✗ BALLOT X — used on decline/negative action buttons. */
public static final String ICON_CROSS = "\u2717";

private UiUtils() {}

/**
* Resolves a theme-aware color: tries the FlatLaf {@code UIManager} key first;
* falls back to {@code light} or {@code dark} based on the luminance of
* {@code Panel.background} (sum of RGB components &lt; 384 → dark theme).
*
* @param flatLafKey UIManager key to try first (e.g. {@code "Actions.Green"})
* @param light color to use in light themes when the key is absent
* @param dark color to use in dark themes when the key is absent
* @return resolved {@link Color}, never {@code null}
*/
public static Color resolveColor(String flatLafKey, Color light, Color dark) {
Color resolved = UIManager.getColor(flatLafKey);
if (resolved != null) return resolved;
Color panelBg = UIManager.getColor("Panel.background");
boolean isDark = panelBg != null
&& (panelBg.getRed() + panelBg.getGreen() + panelBg.getBlue()) < 384;
return isDark ? dark : light;
}

/** Returns the theme-aware positive/confirm color (green). */
public static Color getPositiveColor() {
return resolveColor("Actions.Green", new Color(34, 139, 34), new Color(60, 160, 60));
}

/** Returns the theme-aware negative/cancel color (red). */
public static Color getNegativeColor() {
return resolveColor("Actions.Red", new Color(178, 34, 34), new Color(180, 60, 60));
}

/** Returns the theme-aware warning/accent color (orange). */
public static Color getWarningColor() {
return resolveColor("Actions.Yellow", new Color(180, 100, 0), new Color(200, 130, 0));
}

/**
* Formats a {@link Color} as an HTML hex string (e.g. {@code "#228B22"}).
* Useful for {@code <font color='...'>} in HTML-rendered Swing labels/buttons.
*
* @param c the color to format
* @return six-digit uppercase hex string prefixed with {@code #}
*/
public static String toHex(Color c) {
return String.format("#%02X%02X%02X", c.getRed(), c.getGreen(), c.getBlue());
}

/**
* Applies a theme-aware green ({@code positive=true}) or red ({@code positive=false})
* background style to a button: sets opaque background, white foreground, and a
* brighter focus highlight. Uses {@link #getPositiveColor()} / {@link #getNegativeColor()}.
*/
public static void applyActionStyle(JButton btn, boolean positive) {
final Color base = positive ? getPositiveColor() : getNegativeColor();
final Color focus = base.brighter();
btn.setOpaque(true);
btn.setBackground(base);
btn.setForeground(Color.WHITE);
btn.addFocusListener(new java.awt.event.FocusAdapter() {
@Override public void focusGained(java.awt.event.FocusEvent e) { btn.setBackground(focus); }
@Override public void focusLost(java.awt.event.FocusEvent e) { btn.setBackground(base); }
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import io.github.nbclaudecodegui.model.ChoiceMenuModel;
import io.github.nbclaudecodegui.model.ChoiceMenuModel.Option;
import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
Expand Down Expand Up @@ -44,8 +45,8 @@ void testShowWithOptionsCreatesButtons() throws Exception {
assertTrue(panel.getComponentCount() > 0, "panel should have components after show()");

List<String> btnLabels = collectButtonLabels(panel);
String yesLabel = FileDiffPermissionPanel.ICON_ACCEPT + " Yes";
String noLabel = FileDiffPermissionPanel.ICON_DECLINE + " No";
String yesLabel = UiUtils.ICON_CHECK + " Yes";
String noLabel = UiUtils.ICON_CROSS + " No";
assertTrue(btnLabels.contains(yesLabel), "should have '" + yesLabel + "' button");
assertTrue(btnLabels.contains(noLabel), "should have '" + noLabel + "' button");
});
Expand All @@ -59,7 +60,7 @@ void testYesButtonIsGreen() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, answer -> {});

JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
JButton btn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(btn, "Yes button must exist");
assertEquals(new Color(34, 139, 34), btn.getBackground(), "Yes button should be green");
});
Expand All @@ -73,7 +74,7 @@ void testNoButtonIsRed() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, answer -> {});

JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_DECLINE + " No");
JButton btn = findButton(panel, UiUtils.ICON_CROSS + " No");
assertNotNull(btn, "No button must exist");
assertEquals(new Color(178, 34, 34), btn.getBackground(), "No button should be red");
});
Expand Down Expand Up @@ -183,7 +184,7 @@ void testButtonClickInvokesCallbackWithResponse() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, received::add);

JButton yesBtn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
JButton yesBtn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(yesBtn, "Yes button must exist");
yesBtn.doClick();
});
Expand All @@ -199,7 +200,7 @@ void testHideAfterButtonClick() throws Exception {
ChoiceMenuModel model = new ChoiceMenuModel("Go?", List.of(new Option("Yes", "1")), -1);
panel.show(model, answer -> {});

JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
JButton btn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(btn);
btn.doClick();

Expand Down
Loading