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.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) {
|
||||
|
Loading…
Reference in New Issue
Block a user