diff --git a/src/dorkbox/systemTray/jna/linux/GtkTheme.java b/src/dorkbox/systemTray/jna/linux/GtkTheme.java index 695cecc..37dc52e 100644 --- a/src/dorkbox/systemTray/jna/linux/GtkTheme.java +++ b/src/dorkbox/systemTray/jna/linux/GtkTheme.java @@ -4,6 +4,7 @@ import java.awt.Color; import java.io.File; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -11,9 +12,6 @@ import com.sun.jna.Pointer; import com.sun.jna.ptr.PointerByReference; import dorkbox.systemTray.SystemTray; -import dorkbox.systemTray.Tray; -import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray; -import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray; import dorkbox.util.FileUtil; /** @@ -31,11 +29,88 @@ class GtkTheme { private static final boolean DEBUG_SHOW_CSS = false; private static final boolean DEBUG_VERBOSE = false; + // size of GTK checkbox + // + /* + + -GtkCheckMenuItem-indicator-size: 14; + +.entry { + padding: 3px; + border-width: 1px; + border-style: solid; + border-top-color: shade(@theme_bg_color, 0.6); + border-right-color: shade(@theme_bg_color, 0.7); + border-left-color: shade(@theme_bg_color, 0.7); + border-bottom-color: shade(@theme_bg_color, 0.72); + border-radius: 3px; + background-color: @theme_base_color; + background-image: linear-gradient(to bottom, + shade(@theme_base_color, 0.99), + @theme_base_color + ); + + color: @theme_text_color; +} + + +.menuitem .entry { + border-color: shade(@menu_bg_color, 0.7); + background-color: @menu_bg_color; + background-image: none; + color: @menu_fg_color; +} + +GtkPopover { + margin: 10px; + padding: 2px; + border-radius: 3px; + border-color: shade(@menu_bg_color, 0.8); + border-width: 1px; + border-style: solid; + background-clip: border-box; + background-image: none; + background-color: @menu_bg_color; + color: @menu_fg_color; + box-shadow: 0 2px 3px alpha(black, 0.5); +} + +GtkPopover .entry { + border-color: mix(@menu_bg_color, @menu_fg_color, 0.12); + background-color: @menu_bg_color; + background-image: none; + color: @menu_fg_color; +} + + */ + public static + int getTextHeight() { + String css = getCss(); + if (css != null) { + + } + + + + return 9; + } + + + public static + int getTextPadding() { + return 9; + } + + + + + + /** * @return the widget color of text for the current theme, or black. It is important that this is called AFTER GTK has been initialized. */ public static - Color getCurrentThemeTextColor() { + Color getTextColor() { // NOTE: when getting CSS, we redirect STDERR to null (via GTK), so that we won't spam the console if there are parse errors. // this is a horrid hack, but the only way to work around the errors we have no control over. The parse errors, if bad enough // just mean that we are unable to get the CSS as we want. @@ -193,84 +268,21 @@ class GtkTheme { */ private static Color getFromCss() { - String css = getGtkThemeCss(); + String css = getCss(); if (css != null) { if (DEBUG_SHOW_CSS) { System.err.println(css); } - String[] nodes; - Tray tray = (Tray) SystemTray.get() - .getMenu(); + // in order from left to right, try to get the color value + String[] nodes = new String[] {"GtkPopover", "unity-panel", "gnome-panel-menu-bar", "PanelMenuBar", ".menuitem", ".entry"}; - // we care about the following CSS head nodes, and account for multiple versions, in order of preference. - if (tray instanceof _GtkStatusIconNativeTray) { - nodes = new String[] {"GtkPopover", "gnome-panel-menu-bar", "unity-panel", "PanelMenuBar", ".check"}; - } - else if (tray instanceof _AppIndicatorNativeTray) { - nodes = new String[] {"GtkPopover", "unity-panel", "gnome-panel-menu-bar", "PanelMenuBar", ".check"}; - } - else { - // not supported for other types - return null; - } + // collect a list of all of the sections that have what we are interested in. + List sections = getSections(css, nodes); - // collect a list of all of the sections that have what we are interested in - List sections = new ArrayList(); - - String colorString = null; - - // now check the css nodes to see if they contain a combination of what we are looking for. - colorCheck: - for (String node : nodes) { - int i = 0; - while (i != -1) { - i = css.indexOf(node, i); - if (i > -1) { - int endOfNodeLabels = css.indexOf("{", i); - int endOfSection = css.indexOf("}", endOfNodeLabels + 1) + 1; - int endOfSectionTest = css.indexOf("}", i) + 1; - - // this makes sure that weird parsing errors don't happen as a result of node keywords appearing in node sections - if (endOfSection != endOfSectionTest) { - // advance the index - i = endOfSection; - continue; - } - - String nodeLabel = css.substring(i, endOfNodeLabels); - String nodeSection = css.substring(endOfNodeLabels, endOfSection); - - int j = nodeSection.indexOf(" color"); - if (j > -1) { - sections.add(nodeLabel + " " + nodeSection); - } - - // advance the index - i = endOfSection; - } - } - } - - if (DEBUG_VERBOSE) { - for (String section : sections) { - System.err.println("--------------"); - System.err.println(section); - System.err.println("--------------"); - } - } - - if (!sections.isEmpty()) { - String section = sections.get(0); - int start = section.indexOf("{"); - int colorIndex = section.indexOf(" color", start); - - int startOfColorDef = section.indexOf(":", colorIndex) + 1; - int endOfColorDef = section.indexOf(";", startOfColorDef); - colorString = section.substring(startOfColorDef, endOfColorDef) - .trim(); - } + // + String colorString = getAttributeFromSections(sections, nodes, "color"); // hopefully we found it. if (colorString != null) { @@ -328,11 +340,12 @@ class GtkTheme { return null; } + /** * @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized. */ public static - String getGtkThemeCss() { + String getCss() { if (Gtk.isGtk3) { final AtomicReference css = new AtomicReference(null); @@ -376,7 +389,8 @@ class GtkTheme { } else { // GTK2 has to get the GTK3 theme text a different way (parsing it from disk). SOMETIMES, the app must be GTK2, even though - // the system is GTK3. This works around the API restriction if we are an APP in GTK2 mode. + // the system is GTK3. This works around the API restriction if we are an APP in GTK2 mode. This is not done ALL the time, + // because this is not as accurate as using the GTK3 API. return getGtk3ThemeCssViaFile(); } } @@ -858,6 +872,175 @@ class GtkTheme { return themeName; } + private static class CssNode { + String label; + List attributes; + + CssNode(final String label, final List attributes) { + this.label = label; + this.attributes = attributes; + } + + @Override + public + String toString() { + return label + ' ' + Arrays.toString(attributes.toArray()); + } + } + + private static class CssAttribute { + String key; + String value; + + public + CssAttribute(final String key, final String value) { + this.key = key; + this.value = value; + } + } + + /** + * Gets the sections of text, of the specified CSS nodes. + */ + private static + List getSections(String css, String[] nodes) { + // collect a list of all of the sections that have what we are interested in + List sections = new ArrayList(); + + // now check the css nodes to see if they contain a combination of what we are looking for. + colorCheck: + for (String node : nodes) { + int i = 0; + while (i != -1) { + i = css.indexOf(node, i); + if (i > -1) { + int endOfNodeLabels = css.indexOf("{", i); + int endOfSection = css.indexOf("}", endOfNodeLabels + 1) + 1; + int endOfSectionTest = css.indexOf("}", i) + 1; + + // this makes sure that weird parsing errors don't happen as a result of node keywords appearing in node sections + if (endOfSection != endOfSectionTest) { + // advance the index + i = endOfSection; + continue; + } + + String nodeLabel = css.substring(i, endOfNodeLabels); + + List attributes = new ArrayList(); + + // split the section into an arrayList, one per item. Split by attribute element + String nodeSection = css.substring(endOfNodeLabels, endOfSection); + int start = nodeSection.indexOf('{')+1; + while (start != -1) { + int end = nodeSection.indexOf(';', start); + if (end != -1) { + int seperator = nodeSection.indexOf(':', start); + + if (seperator < end) { + String key = nodeSection.substring(start, seperator).trim(); + String value = nodeSection.substring(seperator+1, end).trim(); + + attributes.add(new CssAttribute(key, value)); + } + start = end+1; + } else { + break; + } + } + + sections.add(new CssNode(nodeLabel, attributes)); + + // advance the index + i = endOfSection; + } + } + } + + if (DEBUG_VERBOSE) { + for (CssNode section : sections) { + System.err.println("--------------"); + System.err.println(section); + System.err.println("--------------"); + } + } + + return sections; + } + + // find an attribute name from the list of sections. The incoming sections will all be related to one of the nodes, we just have to + // prioritize them on WHO has the attribute we are looking for. + private static + String getAttributeFromSections(final List sections, final String[] nodes, final String attributeName) { + // a list of sections that contains the exact attribute we are looking for + List sectionsWithAttribute = new ArrayList(); + + + for (CssNode cssNode : sections) { + for (CssAttribute attribute : cssNode.attributes) { + if (attribute.key.equals(attributeName)) { + sectionsWithAttribute.add(cssNode); + } + } + } + + // if we only have 1, then return that one + if (sectionsWithAttribute.size() == 1) { + CssNode cssNode = sectionsWithAttribute.get(0); + for (CssAttribute attribute : cssNode.attributes) { + if (attribute.key.equals(attributeName)) { + return attribute.value; + } + } + + return null; + } + + + // now we need to narrow down which sections have the attribute. + // This is because a section can override another, and we want to reflect that info. + // If a section has more than one node as it's label, then it has a higher priority, and overrides ones that only have a single label. + // IE: "GtkPopover .entry" overrides ".entry" + + + int maxValue = -1; + CssNode maxNode = null; // guaranteed to be non-null + + // not the CLEANEST way to do this, but it works by only choosing css nodes that are important + for (CssNode cssNode : sectionsWithAttribute) { + int count = 0; + for (String node : nodes) { + String label = cssNode.label; + boolean startsWith = label.startsWith(node); + boolean contains = label.contains(node); + + if (startsWith) { + count+=1; + } else if (contains) { + // if it has MORE than just one node label (that we care about), it's more important that one that is by itself. + count+=2; + } + } + + if (count > maxValue) { + maxValue = count; + maxNode = cssNode; + } + } + + + // get the attribute from the highest scoring node + //noinspection ConstantConditions + for (CssAttribute attribute : maxNode.attributes) { + if (attribute.key.equals(attributeName)) { + return attribute.value; + } + } + + return null; + } + + @SuppressWarnings("Duplicates") private static void removeComments(final StringBuilder stringBuilder) {