diff --git a/app-core/src/main/java/com/mercury/platform/core/MercuryConstants.java b/app-core/src/main/java/com/mercury/platform/core/MercuryConstants.java index d4c2ef8a..fece7bdb 100644 --- a/app-core/src/main/java/com/mercury/platform/core/MercuryConstants.java +++ b/app-core/src/main/java/com/mercury/platform/core/MercuryConstants.java @@ -1,7 +1,7 @@ package com.mercury.platform.core; public class MercuryConstants { - public static final String APP_VERSION = "1.1.8"; + public static final String APP_VERSION = "1.1.9"; public static final String SERVER_HOST = "exslims.ddns.net"; public static final int PORT = 5555; } diff --git a/app-core/src/main/java/com/mercury/platform/shared/SwingUtilitiesMorph.java b/app-core/src/main/java/com/mercury/platform/shared/SwingUtilitiesMorph.java new file mode 100644 index 00000000..0044cb5e --- /dev/null +++ b/app-core/src/main/java/com/mercury/platform/shared/SwingUtilitiesMorph.java @@ -0,0 +1,376 @@ +package com.mercury.platform.shared; + +import sun.awt.SunToolkit; +import sun.font.CharToGlyphMapper; +import sun.print.ProxyPrintGraphics; +import sun.swing.PrintColorUIResource; + +import javax.swing.*; +import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; +import java.awt.print.PrinterGraphics; +import java.util.HashMap; +import java.util.Map; + +import static java.awt.RenderingHints.*; + +public class SwingUtilitiesMorph { + private static final int CHAR_BUFFER_SIZE = 100; + private static final Object charsBufferLock = new Object(); + private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE]; + + public static void drawString(JComponent c, Graphics g, String text, + int x, int y) { + // c may be null + + // All non-editable widgets that draw strings call into this + // methods. By non-editable that means widgets like JLabel, JButton + // but NOT JTextComponents. + if (text == null || text.length() <= 0) { //no need to paint empty strings + return; + } + if (isPrinting(g)) { + Graphics2D g2d = getGraphics2D(g); + if (g2d != null) { + /* The printed text must scale linearly with the UI. + * Calculate the width on screen, obtain a TextLayout with + * advances for the printer graphics FRC, and then justify + * it to fit in the screen width. This distributes the spacing + * more evenly than directly laying out to the screen advances. + */ + String trimmedText = trimTrailingSpaces(text); + if (!trimmedText.isEmpty()) { + float screenWidth = (float) g2d.getFont().getStringBounds + (trimmedText, getFontRenderContext(c)).getWidth(); + TextLayout layout = createTextLayout(c, text, g2d.getFont(), + g2d.getFontRenderContext()); + + // If text fits the screenWidth, then do not need to justify + if (SwingUtilitiesMorph.stringWidth(c, g2d.getFontMetrics(), + trimmedText) > screenWidth) { + layout = layout.getJustifiedLayout(screenWidth); + } + /* Use alternate print color if specified */ + Color col = g2d.getColor(); + if (col instanceof PrintColorUIResource) { + g2d.setColor(((PrintColorUIResource) col).getPrintColor()); + } + + layout.draw(g2d, x, y); + + g2d.setColor(col); + } + + return; + } + } + + // If we get here we're not printing + if (g instanceof Graphics2D) { + AATextInfo info = drawTextAntialiased(c); + Graphics2D g2 = (Graphics2D) g; + + boolean needsTextLayout = ((c != null) && + (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); + + if (needsTextLayout) { + synchronized (charsBufferLock) { + int length = syncCharsBuffer(text); + needsTextLayout = isComplexLayout(charsBuffer, 0, length); + } + } + + if (info != null) { + Object oldContrast = null; + Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); + if (info.aaHint != oldAAValue) { + g2.setRenderingHint(KEY_TEXT_ANTIALIASING, info.aaHint); + } else { + oldAAValue = null; + } + if (info.lcdContrastHint != null) { + oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); + if (info.lcdContrastHint.equals(oldContrast)) { + oldContrast = null; + } else { + g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, + info.lcdContrastHint); + } + } + + if (needsTextLayout) { + TextLayout layout = createTextLayout(c, text, g2.getFont(), + g2.getFontRenderContext()); + layout.draw(g2, x, y); + } else { + g.drawString(text, x, y); + } + + if (oldAAValue != null) { + g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue); + } + if (oldContrast != null) { + g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast); + } + + return; + } + + if (needsTextLayout) { + TextLayout layout = createTextLayout(c, text, g2.getFont(), + g2.getFontRenderContext()); + layout.draw(g2, x, y); + return; + } + } + + g.drawString(text, x, y); + } + + public static class AATextInfo { + + private static AATextInfo getAATextInfoFromMap(Map hints) { + + Object aaHint = hints.get(KEY_TEXT_ANTIALIASING); + Object contHint = hints.get(KEY_TEXT_LCD_CONTRAST); + + if (aaHint == null || + aaHint == VALUE_TEXT_ANTIALIAS_OFF || + aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) { + return null; + } else { + return new AATextInfo(aaHint, (Integer) contHint); + } + } + + public static AATextInfo getAATextInfo(boolean lafCondition) { + SunToolkit.setAAFontSettingsCondition(lafCondition); + Toolkit tk = Toolkit.getDefaultToolkit(); + Object map = tk.getDesktopProperty(SunToolkit.DESKTOPFONTHINTS); + if (map instanceof Map) { + return getAATextInfoFromMap((Map) map); + } else { + return null; + } + } + + Object aaHint; + Integer lcdContrastHint; + FontRenderContext frc; + + /* These are rarely constructed objects, and only when a complete + * UI is being updated, so the cost of the tests here is minimal + * and saves tests elsewhere. + * We test that the values are ones we support/expect. + */ + public AATextInfo(Object aaHint, Integer lcdContrastHint) { + if (aaHint == null) { + throw new InternalError("null not allowed here"); + } + if (aaHint == VALUE_TEXT_ANTIALIAS_OFF || + aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) { + throw new InternalError("AA must be on"); + } + this.aaHint = aaHint; + this.lcdContrastHint = lcdContrastHint; + this.frc = new FontRenderContext(null, aaHint, + VALUE_FRACTIONALMETRICS_DEFAULT); + } + } + + public static AATextInfo drawTextAntialiased(JComponent c) { + if (c != null) { + /* a non-null property implies some form of AA requested */ + return (AATextInfo) c.getClientProperty(AA_TEXT_PROPERTY_KEY); + } + // No component, assume aa is off + return null; + } + + + public static final Object AA_TEXT_PROPERTY_KEY = + new StringBuffer("AATextInfoPropertyKey"); + + static boolean isPrinting(Graphics g) { + return (g instanceof PrinterGraphics || g instanceof PrintGraphics); + } + + public static Graphics2D getGraphics2D(Graphics g) { + if (g instanceof Graphics2D) { + return (Graphics2D) g; + } else if (g instanceof ProxyPrintGraphics) { + return (Graphics2D) (((ProxyPrintGraphics) g).getGraphics()); + } else { + return null; + } + } + + private static String trimTrailingSpaces(String s) { + int i = s.length() - 1; + while (i >= 0 && Character.isWhitespace(s.charAt(i))) { + i--; + } + return s.substring(0, i + 1); + } + + public static FontRenderContext getFontRenderContext(Component c) { + assert c != null; + if (c == null) { + return DEFAULT_FRC; + } else { + return c.getFontMetrics(c.getFont()).getFontRenderContext(); + } + } + + /** + * A convenience method to get FontRenderContext. + * Returns the FontRenderContext for the passed in FontMetrics or + * for the passed in Component if FontMetrics is null + */ + private static FontRenderContext getFontRenderContext(Component c, FontMetrics fm) { + assert fm != null || c != null; + return (fm != null) ? fm.getFontRenderContext() + : getFontRenderContext(c); + } + + public static final FontRenderContext DEFAULT_FRC = + new FontRenderContext(null, false, false); + + private static TextLayout createTextLayout(JComponent c, String s, + Font f, FontRenderContext frc) { + Object shaper = (c == null ? + null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING)); + if (shaper == null) { + return new TextLayout(s, f, frc); + } else { + Map a = new HashMap(); + a.put(TextAttribute.FONT, f); + a.put(TextAttribute.NUMERIC_SHAPING, shaper); + return new TextLayout(s, a, frc); + } + } + + private static int syncCharsBuffer(String s) { + int length = s.length(); + if ((charsBuffer == null) || (charsBuffer.length < length)) { + charsBuffer = s.toCharArray(); + } else { + s.getChars(0, length, charsBuffer, 0); + } + return length; + } + + public static final boolean isComplexLayout(char[] text, int start, int limit) { + return isComplexText(text, start, limit); + } + + public static final int MIN_LAYOUT_CHARCODE = 0x0300; + public static final int MAX_LAYOUT_CHARCODE = 0x206F; + + public static boolean isNonSimpleChar(char ch) { + return + isComplexCharCode(ch) || + (ch >= CharToGlyphMapper.HI_SURROGATE_START && + ch <= CharToGlyphMapper.LO_SURROGATE_END); + } + + public static boolean isComplexText(char[] chs, int start, int limit) { + + for (int i = start; i < limit; i++) { + if (chs[i] < MIN_LAYOUT_CHARCODE) { + continue; + } else if (isNonSimpleChar(chs[i])) { + return true; + } + } + return false; + } + + public static boolean isComplexCharCode(int code) { + + if (code < MIN_LAYOUT_CHARCODE || code > MAX_LAYOUT_CHARCODE) { + return false; + } else if (code <= 0x036f) { + // Trigger layout for combining diacriticals 0x0300->0x036f + return true; + } else if (code < 0x0590) { + // No automatic layout for Greek, Cyrillic, Armenian. + return false; + } else if (code <= 0x06ff) { + // Hebrew 0590 - 05ff + // Arabic 0600 - 06ff + return true; + } else if (code < 0x0900) { + return false; // Syriac and Thaana + } else if (code <= 0x0e7f) { + // if Indic, assume shaping for conjuncts, reordering: + // 0900 - 097F Devanagari + // 0980 - 09FF Bengali + // 0A00 - 0A7F Gurmukhi + // 0A80 - 0AFF Gujarati + // 0B00 - 0B7F Oriya + // 0B80 - 0BFF Tamil + // 0C00 - 0C7F Telugu + // 0C80 - 0CFF Kannada + // 0D00 - 0D7F Malayalam + // 0D80 - 0DFF Sinhala + // 0E00 - 0E7F if Thai, assume shaping for vowel, tone marks + return true; + } else if (code < 0x0f00) { + return false; + } else if (code <= 0x0fff) { // U+0F00 - U+0FFF Tibetan + return true; + } else if (code < 0x1100) { + return false; + } else if (code < 0x11ff) { // U+1100 - U+11FF Old Hangul + return true; + } else if (code < 0x1780) { + return false; + } else if (code <= 0x17ff) { // 1780 - 17FF Khmer + return true; + } else if (code < 0x200c) { + return false; + } else if (code <= 0x200d) { // zwj or zwnj + return true; + } else if (code >= 0x202a && code <= 0x202e) { // directional control + return true; + } else if (code >= 0x206a && code <= 0x206f) { // directional control + return true; + } + return false; + } + + public static int stringWidth(JComponent c, FontMetrics fm, String string) { + if (string == null || string.equals("")) { + return 0; + } + boolean needsTextLayout = ((c != null) && + (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); + if (needsTextLayout) { + synchronized (charsBufferLock) { + int length = syncCharsBuffer(string); + needsTextLayout = isComplexLayout(charsBuffer, 0, length); + } + } + if (needsTextLayout) { + TextLayout layout = createTextLayout(c, string, + fm.getFont(), fm.getFontRenderContext()); + return (int) layout.getAdvance(); + } else { + return fm.stringWidth(string); + } + } + + public static FontMetrics getFontMetrics(JComponent c, Graphics g, + Font font) { + if (c != null) { + // Note: We assume that we're using the FontMetrics + // from the widget to layout out text, otherwise we can get + // mismatches when printing. + return c.getFontMetrics(font); + } + return Toolkit.getDefaultToolkit().getFontMetrics(font); + } +} diff --git a/app-core/src/main/java/com/mercury/platform/shared/config/MercuryConfigManager.java b/app-core/src/main/java/com/mercury/platform/shared/config/MercuryConfigManager.java index 4d22767c..54c81d66 100644 --- a/app-core/src/main/java/com/mercury/platform/shared/config/MercuryConfigManager.java +++ b/app-core/src/main/java/com/mercury/platform/shared/config/MercuryConfigManager.java @@ -177,7 +177,9 @@ public void load() { this.services.add((BaseConfigurationService) this.adrConfigurationService); this.services.add((BaseConfigurationService) this.iconBundleConfigurationService); - this.services.forEach(BaseConfigurationService::validate); + for (BaseConfigurationService item : this.services) { + item.validate(); + } this.jsonHelper.writeListObject(this.profileDescriptors, new TypeToken>() { diff --git a/app-core/src/main/java/com/mercury/platform/shared/config/json/JSONHelper.java b/app-core/src/main/java/com/mercury/platform/shared/config/json/JSONHelper.java index c2d3534c..9f2568b0 100644 --- a/app-core/src/main/java/com/mercury/platform/shared/config/json/JSONHelper.java +++ b/app-core/src/main/java/com/mercury/platform/shared/config/json/JSONHelper.java @@ -8,11 +8,13 @@ import com.mercury.platform.shared.config.descriptor.adr.AdrTrackerGroupDescriptor; import com.mercury.platform.shared.config.json.deserializer.AdrComponentJsonAdapter; import com.mercury.platform.shared.config.json.deserializer.AdrTrackerGroupDeserializer; +import com.mercury.platform.shared.config.json.deserializer.ColorJsonAdapter; import com.mercury.platform.shared.entity.message.MercuryError; import com.mercury.platform.shared.store.MercuryStoreCore; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.awt.*; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @@ -104,6 +106,7 @@ public void writeListObject(List object, TypeToken> typeToken) { try { Gson gson = new GsonBuilder() .registerTypeAdapter(AdrComponentDescriptor.class, new AdrComponentJsonAdapter()) + .registerTypeAdapter(Color.class, new ColorJsonAdapter()) .create(); try (JsonWriter writer = new JsonWriter(new FileWriter(dataSource))) { gson.toJson(object, typeToken.getType(), writer); @@ -118,6 +121,7 @@ public List getJsonAsObjectFromFile(String filePath) { try { Gson gson = new GsonBuilder() .registerTypeAdapter(AdrComponentDescriptor.class, new AdrComponentJsonAdapter()) + .registerTypeAdapter(Color.class, new ColorJsonAdapter()) .create(); JsonParser jsonParser = new JsonParser(); try (JsonReader reader = new JsonReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(filePath)))) { diff --git a/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrComponentJsonAdapter.java b/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrComponentJsonAdapter.java index 3027f165..5eb66875 100644 --- a/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrComponentJsonAdapter.java +++ b/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrComponentJsonAdapter.java @@ -3,6 +3,7 @@ import com.google.gson.*; import com.mercury.platform.shared.config.descriptor.adr.*; +import java.awt.*; import java.lang.reflect.Type; @@ -12,6 +13,7 @@ public AdrComponentDescriptor deserialize(JsonElement jsonElement, Type type, Js JsonPrimitive jsonObj = jsonElement.getAsJsonObject().getAsJsonPrimitive("type"); Gson gson = new GsonBuilder() .registerTypeAdapter(AdrTrackerGroupDescriptor.class, new AdrTrackerGroupDeserializer()) + .registerTypeAdapter(Color.class, new ColorJsonAdapter()) .create(); switch (AdrComponentType.valueOf(jsonObj.getAsString())) { case TRACKER_GROUP: { @@ -32,7 +34,7 @@ public AdrComponentDescriptor deserialize(JsonElement jsonElement, Type type, Js @Override public JsonElement serialize(AdrComponentDescriptor descriptor, Type type, JsonSerializationContext jsonSerializationContext) { - Gson gson = new Gson(); + Gson gson = new GsonBuilder().registerTypeAdapter(Color.class, new ColorJsonAdapter()).create(); switch (descriptor.getType()) { case TRACKER_GROUP: { return gson.toJsonTree(descriptor, AdrTrackerGroupDescriptor.class); diff --git a/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrTrackerGroupDeserializer.java b/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrTrackerGroupDeserializer.java index 6ee40a3a..0d5a9608 100644 --- a/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrTrackerGroupDeserializer.java +++ b/app-core/src/main/java/com/mercury/platform/shared/config/json/deserializer/AdrTrackerGroupDeserializer.java @@ -7,6 +7,7 @@ import com.mercury.platform.shared.config.descriptor.adr.AdrTrackerGroupContentType; import com.mercury.platform.shared.config.descriptor.adr.AdrTrackerGroupDescriptor; +import java.awt.*; import java.lang.reflect.Type; import java.util.List; @@ -15,7 +16,7 @@ public class AdrTrackerGroupDeserializer implements JsonDeserializer, JsonSerializer { + + @Override + public Color deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + int i = ((JsonObject) jsonElement).get("value").getAsInt(); + double alpha = ((JsonObject) jsonElement).get("falpha").getAsDouble(); + Color color = new Color(i); + return new Color(color.getRed(), color.getGreen(), color.getBlue(), (int) alpha); + } + + @Override + public JsonElement serialize(Color color, Type type, JsonSerializationContext jsonSerializationContext) { + JsonObject obj = new JsonObject(); + obj.addProperty("value", color.getRGB()); + obj.addProperty("falpha", color.getAlpha()); + return obj; + } +} diff --git a/app-ui/src/main/java/com/mercury/platform/ui/adr/components/panel/ui/BasicMercuryIconTrackerUI.java b/app-ui/src/main/java/com/mercury/platform/ui/adr/components/panel/ui/BasicMercuryIconTrackerUI.java index d44082b6..9ce6875e 100644 --- a/app-ui/src/main/java/com/mercury/platform/ui/adr/components/panel/ui/BasicMercuryIconTrackerUI.java +++ b/app-ui/src/main/java/com/mercury/platform/ui/adr/components/panel/ui/BasicMercuryIconTrackerUI.java @@ -1,11 +1,11 @@ package com.mercury.platform.ui.adr.components.panel.ui; +import com.mercury.platform.shared.SwingUtilitiesMorph; import com.mercury.platform.shared.config.Configuration; import com.mercury.platform.shared.config.configration.IconBundleConfigurationService; import com.mercury.platform.shared.config.descriptor.adr.AdrComponentOrientation; import com.mercury.platform.shared.config.descriptor.adr.AdrDurationComponentDescriptor; import lombok.Setter; -import sun.swing.SwingUtilities2; import javax.swing.*; import javax.swing.plaf.ComponentUI; @@ -52,7 +52,7 @@ protected void paintString(Graphics g, int x, int y, int width, int height, int Point renderLocation = getStringPlacement(g2, progressString, x, y, width, height); g2.setColor(this.getColorByValue()); - SwingUtilities2.drawString(tracker, g2, progressString, + SwingUtilitiesMorph.drawString(tracker, g2, progressString, renderLocation.x, renderLocation.y); if (this.descriptor.getOutlineThickness() > 0) { FontRenderContext frc = g2.getFontRenderContext(); @@ -98,9 +98,9 @@ private Color getColorByValue() { protected Point getStringPlacement(Graphics g, String progressString, int x, int y, int width, int height) { - FontMetrics fontSizer = SwingUtilities2.getFontMetrics(tracker, g, + FontMetrics fontSizer = SwingUtilitiesMorph.getFontMetrics(tracker, g, tracker.getFont()); - int stringWidth = SwingUtilities2.stringWidth(tracker, fontSizer, + int stringWidth = SwingUtilitiesMorph.stringWidth(tracker, fontSizer, progressString); if (descriptor.getOrientation() == AdrComponentOrientation.HORIZONTAL) { diff --git a/app-ui/src/main/java/com/mercury/platform/ui/frame/experimental/AdrTesting.java b/app-ui/src/main/java/com/mercury/platform/ui/frame/experimental/AdrTesting.java index 8245a1b8..d8c5ce2d 100644 --- a/app-ui/src/main/java/com/mercury/platform/ui/frame/experimental/AdrTesting.java +++ b/app-ui/src/main/java/com/mercury/platform/ui/frame/experimental/AdrTesting.java @@ -1,9 +1,9 @@ package com.mercury.platform.ui.frame.experimental; +import com.mercury.platform.shared.SwingUtilitiesMorph; import com.mercury.platform.ui.components.ComponentsFactory; import com.mercury.platform.ui.components.fields.font.FontStyle; import com.mercury.platform.ui.misc.AppThemeColor; -import sun.swing.SwingUtilities2; import javax.imageio.ImageIO; import javax.swing.*; @@ -114,18 +114,18 @@ protected void paintString(Graphics g, int x, int y, int width, int height, int x, y, width, height); Rectangle oldClip = g2.getClipBounds(); g2.setColor(getSelectionForeground()); - SwingUtilities2.drawString(progressBar, g2, progressString, + SwingUtilitiesMorph.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); g2.setColor(getSelectionForeground()); g2.clipRect(width, y, amountFull, height); - SwingUtilities2.drawString(progressBar, g2, progressString, + SwingUtilitiesMorph.drawString(progressBar, g2, progressString, renderLocation.x, renderLocation.y); g2.setClip(oldClip); } @Override public void paint(Graphics g, JComponent c) { - super.paint(g,c); + super.paint(g, c); //public void paintDeterminate(Graphics g, JComponent c) { Insets b = progressBar.getInsets(); // area for border int barRectWidth = progressBar.getWidth() - b.right - b.left; @@ -208,4 +208,5 @@ public void propertyChange(PropertyChangeEvent e) { progressBar.setValue(progress); } } + } diff --git a/app-ui/src/main/resources/notes/patch/patch-notes-new.json b/app-ui/src/main/resources/notes/patch/patch-notes-new.json index 759c6c19..aef46a52 100644 --- a/app-ui/src/main/resources/notes/patch/patch-notes-new.json +++ b/app-ui/src/main/resources/notes/patch/patch-notes-new.json @@ -1,4 +1,15 @@ [ + { + "version": "1.1.9", + "fix": [ + { + "changed": "Fixed errors on higher jdk than 1.8 (tested on 17)" + }, + { + "changed": "Fixed error which prevented to use openjdk (You still need to use java 1.8)" + } + ] + }, { "version": "1.1.8", "features": [