diff --git a/Ghidra/Framework/Generic/src/main/java/resources/MultiIconBuilder.java b/Ghidra/Framework/Generic/src/main/java/resources/MultiIconBuilder.java index 5b2d0c8fb2..d96bfb8383 100644 --- a/Ghidra/Framework/Generic/src/main/java/resources/MultiIconBuilder.java +++ b/Ghidra/Framework/Generic/src/main/java/resources/MultiIconBuilder.java @@ -15,6 +15,10 @@ */ package resources; +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.TextLayout; +import java.awt.image.BufferedImage; import java.util.Objects; import javax.swing.Icon; @@ -24,13 +28,12 @@ import resources.icons.TranslateIcon; /** * A builder to allow for easier creation of an icon that is composed of a base icon, with - * other icons overlayed. The {@link #build()} method returns an {@link ImageIcon}, as this - * allows Java's buttons to create analogue disabled icons correctly. + * other icons overlaid. The {@link #build()} method returns an {@link ImageIcon}, as this + * allows Java's buttons to automatically create disabled icons correctly. * *

Note: this class is a work-in-progress. Add more methods for locating overlays as needed. */ public class MultiIconBuilder { - private MultiIcon multiIcon; private String description; @@ -38,6 +41,28 @@ public class MultiIconBuilder { this.multiIcon = new MultiIcon(Objects.requireNonNull(baseIcon)); } + /** + * Adds the specified icon as an overlay to the base icon, possibly scaled according + * to the specified width and height, in the specified quadrant corner. + * + * @param icon the icon to overlay + * @param w width of the overlaid icon + * @param h height of the overlaid icon + * @param quandrant corner to place the overlay on + * @return this builder (for chaining) + */ + public MultiIconBuilder addIcon(Icon icon, int w, int h, QUADRANT quandrant) { + ImageIcon scaled = ResourceManager.getScaledIcon(icon, w, h); + + int x = (multiIcon.getIconWidth() - scaled.getIconWidth()) * quandrant.x; + int y = (multiIcon.getIconHeight() - scaled.getIconHeight()) * quandrant.y; + + TranslateIcon txIcon = new TranslateIcon(scaled, x, y); + multiIcon.addIcon(txIcon); + return this; + + } + /** * Adds the given icon as an overlay to the base icon, to the lower-right * @@ -45,7 +70,7 @@ public class MultiIconBuilder { * @return this builder */ public MultiIconBuilder addLowerRightIcon(Icon icon) { - return addLowerRightIcon(icon, icon.getIconWidth(), icon.getIconHeight()); + return addIcon(icon, icon.getIconWidth(), icon.getIconHeight(), QUADRANT.LR); } /** @@ -58,14 +83,7 @@ public class MultiIconBuilder { * @return this builder */ public MultiIconBuilder addLowerRightIcon(Icon icon, int w, int h) { - - ImageIcon scaled = ResourceManager.getScaledIcon(icon, w, h); - - int x = multiIcon.getIconWidth() - scaled.getIconWidth(); - int y = multiIcon.getIconHeight() - scaled.getIconHeight(); - TranslateIcon txIcon = new TranslateIcon(scaled, x, y); - multiIcon.addIcon(txIcon); - return this; + return addIcon(icon, w, h, QUADRANT.LR); } /** @@ -75,7 +93,7 @@ public class MultiIconBuilder { * @return this builder */ public MultiIconBuilder addLowerLeftIcon(Icon icon) { - return addLowerLeftIcon(icon, icon.getIconWidth(), icon.getIconHeight()); + return addIcon(icon, icon.getIconWidth(), icon.getIconHeight(), QUADRANT.LL); } /** @@ -88,14 +106,34 @@ public class MultiIconBuilder { * @return this builder */ public MultiIconBuilder addLowerLeftIcon(Icon icon, int w, int h) { + return addIcon(icon, w, h, QUADRANT.LL); + } - ImageIcon scaled = ResourceManager.getScaledIcon(icon, w, h); + /** + * Add text overlaid on the base icon, aligned to the specified quadrant. + * + * @param text Text string to write onto the icon. Probably can only fit a letter or two + * @param font The font to use to render the text. You know the size of the base icon, so + * you should be able to figure out the size of the font to use for the text + * @param color The color to use when rendering the text + * @param quandrant The {@link QUADRANT} to align the text to different parts of the icon + * @return this builder (for chaining) + */ + public MultiIconBuilder addText(String text, Font font, Color color, QUADRANT quandrant) { - int x = 0; - int y = multiIcon.getIconHeight() - scaled.getIconHeight(); - TranslateIcon txIcon = new TranslateIcon(scaled, x, y); - multiIcon.addIcon(txIcon); - return this; + FontRenderContext frc = new FontRenderContext(null, true, true); + TextLayout tl = new TextLayout(text, font, frc); + + BufferedImage bi = new BufferedImage((int) Math.ceil(tl.getAdvance()), + (int) Math.ceil(tl.getAscent() + tl.getDescent()), BufferedImage.TYPE_INT_ARGB); + + Graphics2D g2d = (Graphics2D) bi.getGraphics(); + g2d.setFont(font); + g2d.setColor(color); + tl.draw(g2d, 0, tl.getAscent()); + g2d.dispose(); + + return addIcon(new ImageIcon(bi), bi.getWidth(), bi.getHeight(), quandrant); } /** diff --git a/Ghidra/Framework/Generic/src/main/java/resources/QUADRANT.java b/Ghidra/Framework/Generic/src/main/java/resources/QUADRANT.java new file mode 100644 index 0000000000..c3010d87a2 --- /dev/null +++ b/Ghidra/Framework/Generic/src/main/java/resources/QUADRANT.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * 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 resources; + +/** + * Enum specifying the quadrant of an overlay, either upper left, upper right, lower left, lower right. + */ +public enum QUADRANT { + UL(0, 0), UR(1, 0), LL(0, 1), LR(1, 1); + + QUADRANT(int x, int y) { + this.x = x; + this.y = y; + } + + int x, y; + + /** + * String to enum. + * + * @param s string of either "UL", "UR", "LL", "LR" + * @param defaultValue value to return if string is invalid + * @return QUADRANT enum + */ + public static QUADRANT valueOf(String s, QUADRANT defaultValue) { + if (s != null) { + try { + return QUADRANT.valueOf(s.toUpperCase()); + } + catch (IllegalArgumentException iae) { + // ignore + } + } + return defaultValue; + } +} diff --git a/Ghidra/Framework/Generic/src/test/java/resources/icons/MultiIconBuilderTest.java b/Ghidra/Framework/Generic/src/test/java/resources/icons/MultiIconBuilderTest.java new file mode 100644 index 0000000000..67564899e9 --- /dev/null +++ b/Ghidra/Framework/Generic/src/test/java/resources/icons/MultiIconBuilderTest.java @@ -0,0 +1,118 @@ +/* ### + * IP: GHIDRA + * + * 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 resources.icons; + +import java.awt.*; +import java.awt.image.BufferedImage; + +import javax.swing.ImageIcon; +import javax.swing.JOptionPane; + +import org.junit.Test; + +import resources.MultiIconBuilder; +import resources.QUADRANT; + +/** + * Minimal tests for MultiIconBuilder. Doesn't test the produced images, just that it didn't cause an exception. + *

+ * The showXYZ() methods are present so a human can run them as a test and see the output + */ +public class MultiIconBuilderTest { + + private ImageIcon makeEmptyIcon(int w, int h, Color color) { + BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + if (color != null) { + Graphics g = bi.getGraphics(); + g.setColor(color); + g.fillRect(0, 0, w, h); + g.dispose(); + } + return new ImageIcon(bi); + } + + private ImageIcon makeQuandrantIcon(int w, int h, Color bgColor, Color lineColor) { + BufferedImage bi = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics g = bi.getGraphics(); + g.setColor(bgColor); + g.fillRect(0, 0, w, h); + g.setColor(lineColor); + g.drawLine(w / 2, 0, w / 2, h); + g.drawLine(0, h / 2, w, h / 2); + g.dispose(); + return new ImageIcon(bi); + } + + private Font font = new Font("Monospaced", Font.PLAIN, 12); + + //@Test + public void showIconText() { + for (QUADRANT quad : QUADRANT.values()) { + ImageIcon icon = + new MultiIconBuilder(makeQuandrantIcon(32, 32, Color.gray, Color.white)) + .addText("Abcfg", font, Color.red, quad) + .build(); + JOptionPane.showMessageDialog(null, "" + quad + " aligned", "Icon text overlay test", + JOptionPane.OK_OPTION, icon); + } + } + + //@Test + public void showIconOverlay() { + for (QUADRANT quad : QUADRANT.values()) { + ImageIcon icon = new MultiIconBuilder(makeEmptyIcon(32, 32, Color.gray)) + .addIcon(makeEmptyIcon(8, 8, Color.red), 8, 8, quad) + .build(); + JOptionPane.showMessageDialog(null, "" + quad + " aligned", "Icon_icon overlay test", + JOptionPane.OK_OPTION, icon); + } + } + + //@Test + public void showScaledIconOverlay() { + for (QUADRANT quad : QUADRANT.values()) { + ImageIcon icon = new MultiIconBuilder(makeEmptyIcon(32, 32, Color.gray)) + .addIcon(makeQuandrantIcon(32, 32, Color.red, Color.black), 14, 14, quad) + .build(); + JOptionPane.showMessageDialog(null, "" + quad + " aligned", + "Scaled icon_icon overlay test", + JOptionPane.OK_OPTION, icon); + } + } + + @Test + public void testIconOverlay() { + // doesn't verify anything other than it doesn't fall down go boom + for (QUADRANT quad : QUADRANT.values()) { + ImageIcon icon = new MultiIconBuilder(makeEmptyIcon(32, 32, Color.gray)) + .addIcon(makeQuandrantIcon(32, 32, Color.red, Color.black), 14, 14, quad) + .build(); + icon.getDescription(); + } + } + + @Test + public void testIconText() { + // doesn't verify anything other than it doesn't fall down go boom + for (QUADRANT quad : QUADRANT.values()) { + ImageIcon icon = + new MultiIconBuilder(makeQuandrantIcon(32, 32, Color.gray, Color.white)) + .addText("Abcfg", font, Color.red, quad) + .build(); + icon.getDescription(); + } + } +}