Work for issue #42, detecting colors for the checkbox in linux via CSS

theme, RC theme, and raw GTK values.
This commit is contained in:
nathan 2017-05-10 15:18:18 +02:00
parent 1bcf51c755
commit 736b098e86
10 changed files with 1207 additions and 108 deletions

View File

@ -0,0 +1,48 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
import dorkbox.util.Keep;
@Keep
public
class GParamSpecStruct extends Structure {
public
class ByValue extends GParamSpecStruct implements Structure.ByValue {}
public
class ByReference extends GParamSpecStruct implements Structure.ByReference {}
public GTypeInstanceStruct g_type_instance;
public String name; /* interned string */
// Pointer flags;
// double value_type;
// double owner_type; /* class or interface using this property */
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("g_type_instance", "name");
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2015 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Pointer;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for libgthread
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class GThread {
static {
JnaHelper.register("gthread-2.0", GThread.class);
}
public static native void g_thread_init(Pointer GThreadFunctions);
}

View File

@ -0,0 +1,34 @@
package dorkbox.systemTray.jna.linux;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gnome.org/gdk3/stable/gdk3-Colors.html
*
* GdkColor has been deprecated since version 3.14 and should not be used in newly-written code.
*/
public
class GdkColor extends Structure {
public int pixel;
public short red;
public short green;
public short blue;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("pixel", "red", "green", "blue");
}
public
class ByValue extends GdkColor implements Structure.ByValue {}
public static
class ByReference extends GdkColor implements Structure.ByReference {}
}

View File

@ -0,0 +1,33 @@
package dorkbox.systemTray.jna.linux;
import java.util.Arrays;
import java.util.List;
import com.sun.jna.Structure;
/**
* https://developer.gnome.org/gdk3/stable/gdk3-RGBA-Colors.html#GdkRGBA
*/
public
class GdkRGBAColor extends Structure {
// these are from 0.0 to 1.0 inclusive
public double red;
public double green;
public double blue;
public double alpha;
@Override
protected
List<String> getFieldOrder() {
return Arrays.asList("red", "green", "blue", "alpha");
}
public
class ByValue extends GdkRGBAColor implements Structure.ByValue {}
public
class ByReference extends GdkRGBAColor implements Structure.ByReference {}
}

View File

@ -0,0 +1,57 @@
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Callback;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for glib-2.0
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Glib {
static {
try {
NativeLibrary library = JnaHelper.register("glib-2.0", Glib.class);
if (library == null) {
SystemTray.logger.error("Error loading Glib library, it failed to load.");
}
} catch (Throwable e) {
SystemTray.logger.error("Error loading Glib library, it failed to load {}", e.getMessage());
}
}
public interface GLogLevelFlags {
public static final int RECURSION = 1 << 0;
public static final int FATAL = 1 << 1;
/* GLib log levels */
public static final int ERROR = 1 << 2; /* always fatal */
public static final int CRITICAL = 1 << 3;
public static final int WARNING = 1 << 4;
public static final int MESSAGE = 1 << 5;
public static final int INFO = 1 << 6;
public static final int DEBUG = 1 << 7;
public static final int MASK = ~(RECURSION | FATAL);
}
public interface GLogFunc extends Callback {
void callback (String log_domain, int log_level, String message, Pointer data);
}
public static final Glib.GLogFunc nullLogFunc = new Glib.GLogFunc() {
@Override
public
void callback(final String log_domain, final int log_level, final String message, final Pointer data) {
// do nothing
}
};
public static native int g_log_set_handler(String log_domain, int levels, GLogFunc handler, Pointer user_data);
public static native void g_log_default_handler (String log_domain, int log_level, String message, Pointer unused_data);
public static native GLogFunc g_log_set_default_handler(GLogFunc log_func, Pointer user_data);
public static native void g_log_remove_handler (String log_domain, int handler_id);
}

View File

@ -15,13 +15,13 @@
*/
package dorkbox.systemTray.jna.linux;
import static dorkbox.systemTray.SystemTray.logger;
import com.sun.jna.Callback;
import com.sun.jna.NativeLibrary;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.PointerByReference;
import dorkbox.systemTray.jna.JnaHelper;
import dorkbox.systemTray.SystemTray;
import dorkbox.util.jna.JnaHelper;
/**
* bindings for libgobject-2.0
@ -35,10 +35,10 @@ class Gobject {
try {
NativeLibrary library = JnaHelper.register("gobject-2.0", Gobject.class);
if (library == null) {
logger.error("Error loading GObject library, it failed to load.");
SystemTray.logger.error("Error loading GObject library, it failed to load.");
}
} catch (Throwable e) {
logger.error("Error loading GObject library, it failed to load {}", e.getMessage());
SystemTray.logger.error("Error loading GObject library, it failed to load {}", e.getMessage());
}
}
@ -54,4 +54,25 @@ class Gobject {
public static native void g_signal_handler_block(Pointer instance, long handlerId);
public static native void g_signal_handler_unblock(Pointer instance, long handlerId);
public static native void g_object_get(Pointer instance, String property_name, PointerByReference value, Pointer terminator);
// Types are here https://developer.gnome.org/gobject/stable/gobject-Type-Information.html
public static native void g_value_init(Pointer gvalue, double type);
/**
* Clears the current value in value (if any) and "unsets" the type, this releases all resources associated with this GValue. An unset value is the same as an uninitialized (zero-filled) GValue structure.
* @param gvalue
*/
public static native void g_value_unset(Pointer gvalue);
public static native String g_value_get_string(Pointer gvalue);
public static native int g_value_get_int(Pointer gvalue);
public static native Pointer g_type_class_ref(Pointer widgetType);
public static native void g_type_class_unref(Pointer widgetClass);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,72 @@
/*
* Copyright 2017 dorkbox, llc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dorkbox.systemTray.jna.linux;
import com.sun.jna.Pointer;
/**
* bindings for GTK+ 3.
*
* Direct-mapping, See: https://github.com/java-native-access/jna/blob/master/www/DirectMapping.md
*/
public
class Gtk3 {
// objdump -T /usr/lib/x86_64-linux-gnu/libgtk-3.so.0 | grep gtk
// objdump -T /usr/local/lib/libgtk-3.so.0 | grep gtk
/**
* Loads a theme from the usual theme paths
*
* @param name A theme name
* @param variant variant to load, for example, "dark", or NULL for the default.
*
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
* @since 3.0
*/
public static native Pointer gtk_css_provider_get_named(String name, String variant);
/**
* Returns the provider containing the style settings used as a fallback for all widgets.
*
* @return a GtkCssProvider with the theme loaded. This memory is owned by GTK+, and you must not free it.
* @since 3.0
*/
public static native Pointer gtk_css_provider_get_default();
/**
* Converts the provider into a string representation in CSS format.
*
* Using gtk_css_provider_load_from_data() with the return value from this function on a new provider created with
* gtk_css_provider_new() will basically create a duplicate of this provider .
*
* @since 3.2 (released in 2011)
*/
public static native String gtk_css_provider_to_string(Pointer provider);
/**
* Gets the foreground color for a given state.
*
* @since 3.0
*/
public static native void gtk_style_context_get_color(Pointer context, int stateFlags, Pointer color);
/**
* Returns the state used for style matching.
*
* @since 3.0
*/
public static native int gtk_style_context_get_state(Pointer context);
}

View File

@ -28,6 +28,7 @@ import dorkbox.systemTray.Menu;
import dorkbox.systemTray.MenuItem;
import dorkbox.systemTray.Separator;
import dorkbox.systemTray.Status;
import dorkbox.systemTray.SystemTray;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.peer.MenuPeer;
@ -215,7 +216,6 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
if (entry instanceof Menu) {
// some implementations of appindicator, do NOT like having a menu added, which has no menu items yet.
// see: https://bugs.launchpad.net/glipper/+bug/1203888
GtkMenu item = new GtkMenu(GtkMenu.this);
add(item, index);
((Menu) entry).bind(item, parentMenu, parentMenu.getSystemTray());
@ -229,9 +229,8 @@ class GtkMenu extends GtkBaseMenuItem implements MenuPeer {
// Additionally, we can ask the SystemTray WHAT KIND of tray it is, since it will know by this point in time.
// necessary because of bad layout decisions by AppIndicators for checkbox items
// WIP. The checkbox (if appIndicator) is always black. This could cause problems depending on theme
// boolean isAppIndicator = SystemTray.get().getMenu() instanceof _AppIndicatorNativeTray;
GtkMenuItemCheckbox item = new GtkMenuItemCheckbox(GtkMenu.this, false);
boolean isAppIndicator = SystemTray.get().getMenu() instanceof _AppIndicatorNativeTray;
GtkMenuItemCheckbox item = new GtkMenuItemCheckbox(GtkMenu.this, isAppIndicator);
add(item, index);
((Checkbox) entry).bind(item, parentMenu, parentMenu.getSystemTray());
}

View File

@ -15,8 +15,15 @@
*/
package dorkbox.systemTray.nativeUI;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JMenuItem;
import com.sun.jna.Pointer;
@ -26,7 +33,8 @@ import dorkbox.systemTray.jna.linux.GCallback;
import dorkbox.systemTray.jna.linux.Gobject;
import dorkbox.systemTray.jna.linux.Gtk;
import dorkbox.systemTray.peer.CheckboxPeer;
import dorkbox.systemTray.util.ImageUtils;
import dorkbox.systemTray.util.ImageResizeUtil;
import dorkbox.util.SwingUtil;
// ElementaryOS shows the checkbox on the right, everyone else is on the left. With eOS, we CANNOT show the spacer image. It does not work
class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCallback {
@ -52,7 +60,12 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
* called from inside dispatch thread. ONLY creates the menu item, but DOES NOT attach it!
* this is a FLOATING reference. See: https://developer.gnome.org/gobject/stable/gobject-The-Base-Object-Type.html#floating-ref
*
* note: AppIndicator tray's DO NOT show the spacer image for checkboxes so they are "shifted left", which looks awkward.
* Because AppIndicator checkbox's DO NOT align correctly (on ubuntu), we use an image_menu_item (instead of a check_menu_item), so that
* the alignment is correct for the menu item (with a check_menu_item, they are shifted left - which looks pretty bad)
*
* For AppIndicators, this is not possible to fix, because we cannot control how the menu's are rendered (this is by design)
* Specifically, since it's implementation was copied from GTK, GtkCheckButton and GtkRadioButton allocate only the minimum size
* necessary for its child. This causes the child alignment to fail. There is no fix we can apply - so we don't use them.
*/
GtkMenuItemCheckbox(final GtkMenu parent, final boolean isAppIndicator) {
super(isAppIndicator ? Gtk.gtk_image_menu_item_new_with_mnemonic("") : Gtk.gtk_check_menu_item_new_with_mnemonic(""));
@ -62,9 +75,46 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
handlerId = Gobject.g_signal_connect_object(_native, "activate", this, null, 0);
if (checkedFile == null) {
// from Brankic1979, public domain
checkedFile = ImageUtils.resizeAndCache(ImageUtils.ENTRY_SIZE, ImageUtils.class.getResource("checked_32.png")).getAbsolutePath();
uncheckedFile = ImageUtils.getTransparentImage(ImageUtils.ENTRY_SIZE).getAbsolutePath();
Color color = Gtk.getCurrentThemeTextColor();
try {
int iconSize = 32;
final File newFile = new File(ImageResizeUtil.TEMP_DIR, iconSize + "_checkMark_" + color.getRGB() + ".png").getAbsoluteFile();
if (!newFile.canRead()) {
JMenuItem jMenuItem = new JMenuItem();
// do the same modifications that would also happen (if specified) for the actual displayed menu items
if (SystemTray.SWING_UI != null) {
jMenuItem.setUI(SystemTray.SWING_UI.getItemUI(jMenuItem, null));
}
// make sure the directory exists
if (!newFile.getParentFile()
.isDirectory()) {
boolean mkdirs = newFile.getParentFile()
.mkdirs();
if (!mkdirs) {
SystemTray.logger.error("Unable to create directory for check-mark image at: {}", newFile);
}
}
// get the largest font (based on the orig font) that can be used WITHOUT changing the size of the JMenuItem
Font fontForSpecificHeight = SwingUtil.getFontForSpecificHeight(jMenuItem.getFont(), iconSize);
BufferedImage fontAsImage = SwingUtil.getFontAsImage(fontForSpecificHeight, "", color); // or ""
ImageIO.write(fontAsImage, "png", newFile);
}
checkedFile = newFile.getAbsolutePath();
// here, it doesn't matter what size the image is, as long as there is an image, the text in the menu will be shifted correctly
uncheckedFile = ImageResizeUtil.getTransparentImage().getAbsolutePath();
} catch(Exception e) {
SystemTray.logger.error("Error creating check-mark image.", e);
}
}
if (isAppIndicator) {
@ -215,6 +265,7 @@ class GtkMenuItemCheckbox extends GtkBaseMenuItem implements CheckboxPeer, GCall
} else {
checkedImage = Gtk.gtk_image_new_from_file(uncheckedFile);
}
Gtk.gtk_image_menu_item_set_image(_native, checkedImage);
// must always re-set always-show after setting the image