better CSS parsing & polish

This commit is contained in:
nathan 2017-06-04 21:56:14 +02:00
parent 1cebc72d93
commit 803cea80b7

View File

@ -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<CssNode> sections = getSections(css, nodes);
// collect a list of all of the sections that have what we are interested in
List<String> sections = new ArrayList<String>();
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<String> css = new AtomicReference<String>(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<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")
private static
void removeComments(final StringBuilder stringBuilder) {