Getting the menu image size for linux is now correct

This commit is contained in:
nathan 2017-07-03 20:30:59 +02:00
parent 923ff023b0
commit 5368c6db75
2 changed files with 296 additions and 91 deletions

View File

@ -0,0 +1,22 @@
package dorkbox.systemTray.jna.linux;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gimp.org/api/2.0/gtk/GtkWidget.html#GtkRequisition
*/
public
class GtkRequisition extends Structure {
public int width;
public int height;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("width", "height");
}
}

View File

@ -23,6 +23,7 @@ import dorkbox.systemTray.util.CssParser;
import dorkbox.systemTray.util.CssParser.Css;
import dorkbox.systemTray.util.CssParser.CssNode;
import dorkbox.systemTray.util.CssParser.Entry;
import dorkbox.systemTray.util.SizeAndScalingUtil;
import dorkbox.util.FileUtil;
import dorkbox.util.MathUtil;
import dorkbox.util.OS;
@ -46,7 +47,6 @@ class GtkTheme {
private static final boolean DEBUG_VERBOSE = false;
// CSS nodes that we care about, in oder of preference from left to right.
// GtkPopover is a bubble-like context window, primarily meant to provide context-dependent information or options.
private static final
String[] cssNodes = new String[] {".menuitem", ".entry", "*"};
@ -66,14 +66,39 @@ class GtkTheme {
// ink pixel size is how much exact space it takes on the screen
PangoRectangle ink = new PangoRectangle();
Gtk.pango_layout_get_pixel_extents(pangoLayout, ink.getPointer(), null);
ink.read();
Rectangle size = new Rectangle(ink.width, ink.height);
Gtk.gtk_widget_destroy(item);
Gtk.gtk_widget_destroy(offscreen);
return size;
}
public static
Rectangle getLogicalTextHeight(String text) {
// have to use pango to get the size of text (for the checkmark size)
Pointer offscreen = Gtk.gtk_offscreen_window_new();
// we use the size of "X" as the checkmark
Pointer item = Gtk.gtk_image_menu_item_new_with_mnemonic(text);
Gtk.gtk_container_add(offscreen, item);
// get the text widget (GtkAccelLabel) from inside the GtkMenuItem
Pointer textLabel = Gtk.gtk_bin_get_child(item);
Pointer pangoLayout = Gtk.gtk_label_get_layout(textLabel);
// logical pixel size (ascent + descent)
PangoRectangle logical = new PangoRectangle();
Gtk.pango_layout_get_pixel_extents(pangoLayout, ink.getPointer(), logical.getPointer());
ink.read();
// logical.read();
Gtk.pango_layout_get_pixel_extents(pangoLayout, null, logical.getPointer());
logical.read();
Rectangle size = new Rectangle(ink.width, ink.height);
Rectangle size = new Rectangle(logical.width, logical.height);
Gtk.gtk_widget_destroy(item);
Gtk.gtk_widget_destroy(offscreen);
@ -150,21 +175,197 @@ class GtkTheme {
right += tmp.right;
Gtk3.gtk_style_context_restore (context);
} else {
GtkStyle.ByReference style = Gtk.gtk_widget_get_style(item);
top += style.ythickness;
bottom += style.ythickness;
left += style.xthickness;
right += style.xthickness;
}
GtkStyle.ByReference style = Gtk.gtk_widget_get_style(item);
top += style.ythickness;
bottom += style.ythickness;
left += style.xthickness;
right += style.xthickness;
Gtk.gtk_widget_destroy(item);
Gtk.gtk_widget_destroy(offscreen);
if (top == 0 && bottom == 0 && left == 0 && right == 0) {
// sometimes GTK2 cannot get this info!!
// parse if from CSS...
Css css = getCss();
if (css != null) {
// collect a list of all of the sections that have what we are interested in.
List<CssNode> sections = CssParser.getSections(css, cssNodes, null);
List<Entry> paddingEntries = CssParser.getAttributeFromSections(sections, "padding", true);
String padding = CssParser.selectMostRelevantAttribute(cssNodes, paddingEntries);
// can be padding: 2px 4px;
// can be padding-bottom: 3px;
if (padding != null) {
String[] split = padding.split(" ");
if (split.length == 4) {
// padding:10px 5px 15px 20px;
// top padding is 10px
// right padding is 5px
// bottom padding is 15px
// left padding is 20px
top = MathUtil.stripTrailingNonDigits(split[0]);
right = MathUtil.stripTrailingNonDigits(split[1]);
bottom = MathUtil.stripTrailingNonDigits(split[2]);
left = MathUtil.stripTrailingNonDigits(split[3]);
}
else if (split.length == 3) {
// padding:10px 5px 15px;
// top padding is 10px
// right and left padding are 5px
// bottom padding is 15px
top = MathUtil.stripTrailingNonDigits(split[0]);
right = left = MathUtil.stripTrailingNonDigits(split[1]);
bottom = MathUtil.stripTrailingNonDigits(split[2]);
}
else if (split.length == 2) {
// padding:10px 5px;
// top and bottom padding are 10px
// right and left padding are 5px
top = bottom = MathUtil.stripTrailingNonDigits(split[0]);
right = left = MathUtil.stripTrailingNonDigits(split[1]);
}
else if (split.length == 1) {
// padding:10px;
// all four paddings are 10px
top = bottom = right = left = MathUtil.stripTrailingNonDigits(split[0]);
}
}
else {
// it's explicit padding sizes.
paddingEntries = CssParser.getAttributeFromSections(sections, "padding-top", true);
padding = CssParser.selectMostRelevantAttribute(cssNodes, paddingEntries);
top = MathUtil.stripTrailingNonDigits(padding);
paddingEntries = CssParser.getAttributeFromSections(sections, "padding-right", true);
padding = CssParser.selectMostRelevantAttribute(cssNodes, paddingEntries);
right = MathUtil.stripTrailingNonDigits(padding);
paddingEntries = CssParser.getAttributeFromSections(sections, "padding-bottom", true);
padding = CssParser.selectMostRelevantAttribute(cssNodes, paddingEntries);
bottom = MathUtil.stripTrailingNonDigits(padding);
paddingEntries = CssParser.getAttributeFromSections(sections, "padding-left", true);
padding = CssParser.selectMostRelevantAttribute(cssNodes, paddingEntries);
left = MathUtil.stripTrailingNonDigits(padding);
}
List<Entry> borderEntries = CssParser.getAttributeFromSections(sections, "border-width", true);
String border = CssParser.selectMostRelevantAttribute(cssNodes, borderEntries);
// can be border-width, etc
// can be border-bottom-width, etc
if (border != null) {
String[] split = border.split(" ");
if (split.length == 4) {
top += MathUtil.stripTrailingNonDigits(split[0]);
right += MathUtil.stripTrailingNonDigits(split[1]);
bottom += MathUtil.stripTrailingNonDigits(split[2]);
left += MathUtil.stripTrailingNonDigits(split[3]);
}
else if (split.length == 3) {
top += MathUtil.stripTrailingNonDigits(split[0]);
int borderX = MathUtil.stripTrailingNonDigits(split[1]);
right += borderX;
left += borderX;
bottom += MathUtil.stripTrailingNonDigits(split[2]);
}
else if (split.length == 2) {
int borderY = MathUtil.stripTrailingNonDigits(split[0]);
int borderX = MathUtil.stripTrailingNonDigits(split[1]);
top += borderY;
bottom += borderY;
right += borderX;
left += borderX;
}
else if (split.length == 1) {
int b = MathUtil.stripTrailingNonDigits(split[0]);
top += b;
bottom += b;
right += b;
left += b;
}
}
else {
// it's explicit border sizes.
borderEntries = CssParser.getAttributeFromSections(sections, "border-top-width", true);
border = CssParser.selectMostRelevantAttribute(cssNodes, borderEntries);
top += MathUtil.stripTrailingNonDigits(border);
borderEntries = CssParser.getAttributeFromSections(sections, "border-right-width", true);
border = CssParser.selectMostRelevantAttribute(cssNodes, borderEntries);
right += MathUtil.stripTrailingNonDigits(border);
borderEntries = CssParser.getAttributeFromSections(sections, "border-bottom-width", true);
border = CssParser.selectMostRelevantAttribute(cssNodes, borderEntries);
bottom += MathUtil.stripTrailingNonDigits(border);
borderEntries = CssParser.getAttributeFromSections(sections, "border-left-width", true);
border = CssParser.selectMostRelevantAttribute(cssNodes, borderEntries);
left += MathUtil.stripTrailingNonDigits(border);
}
}
}
return new Insets(top, left, bottom, right);
}
/**
* @return the size of the GTK menu entry's IMAGE, as best as we can tell, for determining how large of icons to use for the menu entry
*/
public static
int getMenuEntryImageSize() {
final AtomicReference<Integer> imageHeight = new AtomicReference<Integer>();
Gtk.dispatchAndWait(new Runnable() {
@Override
public
void run() {
Pointer offscreen = Gtk.gtk_offscreen_window_new();
// get the default icon size for the "paste" icon.
Pointer item = Gtk2.gtk_image_menu_item_new_from_stock("gtk-paste", null);
Gtk.gtk_container_add(offscreen, item);
PointerByReference r = new PointerByReference();
Gobject.g_object_get(item, "image", r.getPointer(), null);
Pointer imageWidget = r.getValue();
GtkRequisition gtkRequisition = new GtkRequisition();
Gtk.gtk_widget_size_request(imageWidget, gtkRequisition.getPointer());
gtkRequisition.read();
imageHeight.set(gtkRequisition.height);
}
});
int height = imageHeight.get();
if (height > 0) {
return height;
}
else {
return 16; // who knows?
}
}
/**
* Gets the system tray indicator size.
* - AppIndicator: will properly scale the image if it's not the correct size
@ -172,6 +373,27 @@ class GtkTheme {
*/
public static
int getIndicatorSize(final Class<? extends Tray> trayType) {
// double scalingFactor = getLinuxScalingFactor();
//
// if (scalingFactor > 1) {
// return scalingFactor;
// }
//
// // fedora 23+ has a different size for the indicator (NOT default 16px)
// int fedoraVersion = OSUtil.Linux.getFedoraVersion();
//
// if (fedoraVersion >= 23) {
// System.err.println("FEDORA SCALE? ");
//// if (SystemTray.DEBUG) {
//// SystemTray.logger.debug("Adjusting tray/menu scaling for FEDORA " + fedoraVersion);
//// }
//
// return 2;
//// trayScalingFactor = 2;
// }
//
// return 0;
if (OSUtil.DesktopEnv.isKDE()) {
// KDE
/*
@ -191,28 +413,31 @@ class GtkTheme {
*/
}
else {
final AtomicInteger screenScale = new AtomicInteger();
final AtomicReference<Double> screenDPI = new AtomicReference<Double>();
Gtk.dispatchAndWait(new Runnable() {
@Override
public
void run() {
// screen DPI
Pointer screen = Gtk.gdk_screen_get_default();
if (screen != null) {
// this call makes NO SENSE, but reading the documentation shows it is the CORRECT call.
screenDPI.set(Gtk.gdk_screen_get_resolution(screen));
}
double linuxScalingFactor = SizeAndScalingUtil.getLinuxScalingFactor();
if (Gtk.isGtk3) {
Pointer window = Gtk.gdk_get_default_root_window();
if (window != null) {
screenScale.set(Gtk3.gdk_window_get_scale_factor(window));
}
}
}
});
// final AtomicInteger screenScale = new AtomicInteger();
// final AtomicReference<Double> screenDPI = new AtomicReference<Double>();
//
// Gtk.dispatchAndWait(new Runnable() {
// @Override
// public
// void run() {
// // screen DPI
// Pointer screen = Gtk.gdk_screen_get_default();
// if (screen != null) {
// // this call makes NO SENSE, but reading the documentation shows it is the CORRECT call.
// screenDPI.set(Gtk.gdk_screen_get_resolution(screen));
// }
//
// if (Gtk.isGtk3) {
// Pointer window = Gtk.gdk_get_default_root_window();
// if (window != null) {
// screenScale.set(Gtk3.gdk_window_get_scale_factor(window));
// }
// }
// }
// });
// TODO: what to do with screen DPI? 72 is default? 96 is default (yes, i think)?
OSUtil.DesktopEnv.Env env = OSUtil.DesktopEnv.get();
@ -220,15 +445,12 @@ class GtkTheme {
if (OSUtil.Linux.isUbuntu() && env == OSUtil.DesktopEnv.Env.Unity) {
// if we measure on ubuntu unity using a screen shot (using swing, so....) , the max size was 24, HOWEVER this goes from
// the top->bottom of the indicator bar -- and since it was swing, it uses a different rendering method and it (honestly)
// looks weird, because there is no padding on the icon. The official AppIndicator size is hardcoded...
// looks weird, because there is no padding for the icon. The official AppIndicator size is hardcoded...
// http://bazaar.launchpad.net/~indicator-applet-developers/libindicator/trunk.16.10/view/head:/libindicator/indicator-image-helper.c
return 22;
}
else {
// Swing or AWT. While not "normal", this is absolutely a possible combination.
// NOTE: On linux, if using Swing -- the icon will look HORRID! The background is not rendered correctly!
// xfce is easy, because it's not a GTK setting for the size (xfce notification area maximum icon size)
if (env == OSUtil.DesktopEnv.Env.XFCE) {
String properties = OSUtil.DesktopEnv.queryXfce("xfce4-panel", null);
@ -301,29 +523,35 @@ class GtkTheme {
int i = traySize.get();
System.err.println("TRAY SIZE: " + traySize.get());
System.err.println("SCREEN DPI: " + screenDPI.get());
System.err.println("SCREEN SCALE: " + screenScale.get());
// System.err.println("SCREEN DPI: " + screenDPI.get());
// System.err.println("SCREEN SCALE: " + screenScale.get());
if (i != 0) {
// xfce gtk status icon is also size 22, measured manually
return i;
}
}
}
// void
// gtk_widget_get_preferred_height (GtkWidget *widget,
// gint *minimum_height,
// gint *natural_height);
// g_param_spec_int ("check-icon-size",
// "Check icon size",
// "Check icon size",
// -1, G_MAXINT, 40,
// G_PARAM_READWRITE));
// -GtkCheckButton-indicator-size: 15;
// -GtkCheckMenuItem-indicator-size: 14;
// https://wiki.gnome.org/HowDoI/HiDpi
// String css = getCss();
// System.err.println(css);
//
// -GtkCheckButton-indicator-size: 16;
// -GtkCheckMenuItem-indicator-size: 16;
//
// xdpyinfo | grep dots reported 96x96 dots
@ -366,28 +594,6 @@ class GtkTheme {
// return 32;
// if we are an AppIndicator -- the size is hardcoded to 22
// http://bazaar.launchpad.net/~indicator-applet-developers/libindicator/trunk.16.10/view/head:/libindicator/indicator-image-helper.c
// sane default
return 22;
}
@ -1155,27 +1361,4 @@ class GtkTheme {
return themeName.get();
}
// have to strip anything that is not a number.
public static
int stripNonDigits(final String value) {
if (value == null || value.isEmpty()) {
return 0;
}
int numberIndex = 0;
int length = value.length();
while (numberIndex < length && Character.isDigit(value.charAt(numberIndex))) {
numberIndex++;
}
String substring = value.substring(0, numberIndex);
try {
return Integer.parseInt(substring);
} catch (Exception ignored) {
}
return 0;
}
}