diff --git a/src/org/openlcb/cdi/swing/CdiPanel.java b/src/org/openlcb/cdi/swing/CdiPanel.java index 53fdcc4e..80cca959 100644 --- a/src/org/openlcb/cdi/swing/CdiPanel.java +++ b/src/org/openlcb/cdi/swing/CdiPanel.java @@ -615,6 +615,9 @@ private void runUpdateComplete() { private final List allEntries = new ArrayList<>(); private final Map entriesByKey = new HashMap<>(); private final Map tabsByKey = new HashMap<>(); + private final Map segmentTitlesByKey = new HashMap<>(); + private final Map groupTitlesByKey = new HashMap<>(); + private final Map collapsibleGroupTitlesByKey = new HashMap<>(); // These need to be executed when closing the window. private final ArrayList cleanupTasks = new ArrayList<>(); // Deferred callbacks that came during the rendering of the window. @@ -983,6 +986,43 @@ private void performTabColorRefresh(long requestTick) { boolean isDirty = false; boolean isInvalid = false; + private void colorTitle(String key, boolean dirty, boolean invalid) { + Color color = invalid ? COLOR_INVALID : dirty ? COLOR_EDITED : null; + CollapsiblePanel segment = segmentTitlesByKey.get(key); + if (segment != null) segment.setTitleBackground(color); + CollapsiblePanel collapsibleGroup = collapsibleGroupTitlesByKey.get(key); + if (collapsibleGroup != null) collapsibleGroup.setTitleBackground(color); + GroupPane group = groupTitlesByKey.get(key); + if (group != null && group.titleBorder != null) { + group.titleBorder.setTitleBackground(color); + group.repaint(); + } + } + + @Override + public void visitSegment(ConfigRepresentation.SegmentEntry e) { + boolean oldDirty = isDirty; + boolean oldInvalid = isInvalid; + isDirty = false; + isInvalid = false; + super.visitSegment(e); + colorTitle(e.key, isDirty, isInvalid); + isDirty |= oldDirty; + isInvalid |= oldInvalid; + } + + @Override + public void visitGroup(ConfigRepresentation.GroupEntry e) { + boolean oldDirty = isDirty; + boolean oldInvalid = isInvalid; + isDirty = false; + isInvalid = false; + super.visitGroup(e); + colorTitle(e.key, isDirty, isInvalid); + isDirty |= oldDirty; + isInvalid |= oldInvalid; + } + @Override public void visitGroupRep(ConfigRepresentation.GroupRep e) { boolean oldDirty = isDirty; @@ -1085,6 +1125,7 @@ public void visitSegment(ConfigRepresentation.SegmentEntry e) { String name = "Segment" + (e.getName() != null ? (": " + e.getName()) : ""); util.CollapsiblePanel cPanel = new util.CollapsiblePanel(name, currentPane); + segmentTitlesByKey.put(e.key, cPanel); segmentPanels.add(cPanel); addNavigationActions(cPanel); // cPanel.setBorder(BorderFactory.createLineBorder(java.awt.Color.RED)); //debugging @@ -1103,6 +1144,7 @@ public void visitGroup(ConfigRepresentation.GroupEntry e) { JTabbedPane oldTabbed = currentTabbedPane; GroupPane groupPane = new GroupPane(e); + groupTitlesByKey.put(e.key, groupPane); currentPane = groupPane; if (e.group.getReplication() > 1) { currentTabbedPane = new JTabbedPane(); @@ -1132,6 +1174,7 @@ public void visitGroup(ConfigRepresentation.GroupEntry e) { if (oldPane instanceof SegmentPane || e.isHideable()) { // we only make toplevel groups collapsible unless hint requests groupPane.setBorder(null); CollapsiblePanel cPanel = new CollapsiblePanel(groupPane.getName(), groupPane); + collapsibleGroupTitlesByKey.put(e.key, cPanel); // cPanel.setBorder(BorderFactory.createLineBorder(java.awt.Color.RED)); //debugging cPanel.setAlignmentY(Component.TOP_ALIGNMENT); cPanel.setAlignmentX(Component.LEFT_ALIGNMENT); @@ -1892,13 +1935,15 @@ void attachParent(JPanel parentPane, JTextComponent output) { public class GroupPane extends JPanel { private final ConfigRepresentation.GroupEntry entry; private final CdiRep.Item item; + private final CdiTitleBorder titleBorder; GroupPane(ConfigRepresentation.GroupEntry e) { entry = e; item = e.getCdiItem(); setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setAlignmentX(Component.LEFT_ALIGNMENT); String name = (item.getName() != null ? (item.getName()) : "Group"); - setBorder(BorderFactory.createTitledBorder(name)); + titleBorder = new CdiTitleBorder(name); + setBorder(titleBorder); setName(name); createDescriptionPane(this, item.getDescription()); @@ -1912,6 +1957,36 @@ public class GroupPane extends JPanel { } } + /** Titled border that can fill the title area to match repeated-group tabs. */ + private static class CdiTitleBorder extends javax.swing.border.TitledBorder { + private Color titleBackground; + + CdiTitleBorder(String title) { + super(title); + } + + void setTitleBackground(Color color) { + titleBackground = color; + } + + @Override + public void paintBorder(Component component, java.awt.Graphics graphics, + int x, int y, int width, int height) { + if (titleBackground != null) { + java.awt.Graphics2D g = (java.awt.Graphics2D) graphics.create(); + Font font = getTitleFont() != null ? getTitleFont() : component.getFont(); + g.setFont(font); + java.awt.FontMetrics metrics = g.getFontMetrics(); + int titleX = x + 9; + int titleY = y + 1; + g.setColor(titleBackground); + g.fillRect(titleX, titleY, metrics.stringWidth(getTitle()) + 8, metrics.getHeight()); + g.dispose(); + } + super.paintBorder(component, graphics, x, y, width, height); + } + } + Font countAreaFont; // font for the character count fields { Font existingFont = UIManager.getFont("TextArea.font"); diff --git a/src/util/CollapsiblePanel.java b/src/util/CollapsiblePanel.java index 4e36ba92..58045d47 100644 --- a/src/util/CollapsiblePanel.java +++ b/src/util/CollapsiblePanel.java @@ -37,6 +37,7 @@ public class CollapsiblePanel extends JPanel { HeaderPanel headerPanel_; // The width given by the parent which we should try to set as a maximum width for the layout. private int setWidth_ = Integer.MAX_VALUE; + private Color titleBackground; private class HeaderPanel extends JPanel implements MouseListener, FocusListener, KeyListener { /** Comment for serialVersionUID. */ @@ -85,6 +86,12 @@ protected void paintComponent(Graphics g) { float height = lm.getAscent() + lm.getDescent(); float x = OFFSET; float y = (h + height) / 2 - lm.getDescent(); + if (titleBackground != null) { + g2.setColor(titleBackground); + g2.fillRect((int) x - 3, (int) (y - lm.getAscent()), + (int) font.getStringBounds(text_, frc).getWidth() + 6, (int) height); + } + g2.setColor(UIManager.getColor("Label.foreground")); g2.drawString(text_, x, y); } @@ -162,6 +169,12 @@ public JPanel getHeader() { return headerPanel_; } + /** Sets the background color behind the panel title, or null for no fill. */ + public void setTitleBackground(Color color) { + titleBackground = color; + headerPanel_.repaint(); + } + public void toggleSelection() { selected = !selected; contentPanel_.setVisible(selected);