forked from dorkbox/SystemTray
better CSS parsing & polish
This commit is contained in:
parent
1cebc72d93
commit
803cea80b7
@ -4,6 +4,7 @@ import java.awt.Color;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
@ -11,9 +12,6 @@ import com.sun.jna.Pointer;
|
|||||||
import com.sun.jna.ptr.PointerByReference;
|
import com.sun.jna.ptr.PointerByReference;
|
||||||
|
|
||||||
import dorkbox.systemTray.SystemTray;
|
import dorkbox.systemTray.SystemTray;
|
||||||
import dorkbox.systemTray.Tray;
|
|
||||||
import dorkbox.systemTray.nativeUI._AppIndicatorNativeTray;
|
|
||||||
import dorkbox.systemTray.nativeUI._GtkStatusIconNativeTray;
|
|
||||||
import dorkbox.util.FileUtil;
|
import dorkbox.util.FileUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -31,11 +29,88 @@ class GtkTheme {
|
|||||||
private static final boolean DEBUG_SHOW_CSS = false;
|
private static final boolean DEBUG_SHOW_CSS = false;
|
||||||
private static final boolean DEBUG_VERBOSE = 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.
|
* @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
|
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.
|
// 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
|
// 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.
|
// just mean that we are unable to get the CSS as we want.
|
||||||
@ -193,84 +268,21 @@ class GtkTheme {
|
|||||||
*/
|
*/
|
||||||
private static
|
private static
|
||||||
Color getFromCss() {
|
Color getFromCss() {
|
||||||
String css = getGtkThemeCss();
|
String css = getCss();
|
||||||
if (css != null) {
|
if (css != null) {
|
||||||
if (DEBUG_SHOW_CSS) {
|
if (DEBUG_SHOW_CSS) {
|
||||||
System.err.println(css);
|
System.err.println(css);
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] nodes;
|
// in order from left to right, try to get the color value
|
||||||
Tray tray = (Tray) SystemTray.get()
|
String[] nodes = new String[] {"GtkPopover", "unity-panel", "gnome-panel-menu-bar", "PanelMenuBar", ".menuitem", ".entry"};
|
||||||
.getMenu();
|
|
||||||
|
|
||||||
|
|
||||||
// we care about the following CSS head nodes, and account for multiple versions, in order of preference.
|
// collect a list of all of the sections that have what we are interested in.
|
||||||
if (tray instanceof _GtkStatusIconNativeTray) {
|
List<CssNode> sections = getSections(css, nodes);
|
||||||
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<String> sections = new ArrayList<String>();
|
String colorString = getAttributeFromSections(sections, nodes, "color");
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// hopefully we found it.
|
// hopefully we found it.
|
||||||
if (colorString != null) {
|
if (colorString != null) {
|
||||||
@ -328,11 +340,12 @@ class GtkTheme {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized.
|
* @return the CSS for the current theme or null. It is important that this is called AFTER GTK has been initialized.
|
||||||
*/
|
*/
|
||||||
public static
|
public static
|
||||||
String getGtkThemeCss() {
|
String getCss() {
|
||||||
if (Gtk.isGtk3) {
|
if (Gtk.isGtk3) {
|
||||||
final AtomicReference<String> css = new AtomicReference<String>(null);
|
final AtomicReference<String> css = new AtomicReference<String>(null);
|
||||||
|
|
||||||
@ -376,7 +389,8 @@ class GtkTheme {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// GTK2 has to get the GTK3 theme text a different way (parsing it from disk). SOMETIMES, the app must be GTK2, even though
|
// 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();
|
return getGtk3ThemeCssViaFile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -858,6 +872,175 @@ class GtkTheme {
|
|||||||
return themeName;
|
return themeName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class CssNode {
|
||||||
|
String label;
|
||||||
|
List<CssAttribute> attributes;
|
||||||
|
|
||||||
|
CssNode(final String label, final List<CssAttribute> 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<CssNode> getSections(String css, String[] nodes) {
|
||||||
|
// collect a list of all of the sections that have what we are interested in
|
||||||
|
List<CssNode> sections = new ArrayList<CssNode>();
|
||||||
|
|
||||||
|
// 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<CssAttribute> attributes = new ArrayList<CssAttribute>();
|
||||||
|
|
||||||
|
// 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<CssNode> sections, final String[] nodes, final String attributeName) {
|
||||||
|
// a list of sections that contains the exact attribute we are looking for
|
||||||
|
List<CssNode> sectionsWithAttribute = new ArrayList<CssNode>();
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
@SuppressWarnings("Duplicates")
|
||||||
private static
|
private static
|
||||||
void removeComments(final StringBuilder stringBuilder) {
|
void removeComments(final StringBuilder stringBuilder) {
|
||||||
|
Loading…
Reference in New Issue
Block a user