Skip to content

Commit

Permalink
Improves UI on Linux
Browse files Browse the repository at this point in the history
  • Loading branch information
emmanue1 committed Aug 8, 2015
1 parent a15a493 commit 169e5fd
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.jd.gui.service.indexer.IndexerService
import org.jd.gui.service.mainpanel.PanelFactoryService
import org.jd.gui.service.pastehandler.PasteHandlerService
import org.jd.gui.service.actions.ContextualActionsFactoryService
import org.jd.gui.service.platform.PlatformService
import org.jd.gui.service.preferencespanel.PreferencesPanelService
import org.jd.gui.service.sourcesaver.SourceSaverService
import org.jd.gui.service.treenode.TreeNodeFactoryService
Expand All @@ -42,6 +43,7 @@ import org.jd.gui.spi.TreeNodeFactory
import org.jd.gui.spi.TypeFactory
import org.jd.gui.spi.UriLoader
import org.jd.gui.util.net.UriUtil
import org.jd.gui.util.swing.SwingUtil

import javax.swing.Action
import javax.swing.Icon
Expand Down Expand Up @@ -90,6 +92,12 @@ class MainController implements API {
MainController(SwingBuilder swing, Configuration configuration) {
this.swing = swing
this.configuration = configuration

if (PlatformService.instance.isLinux) {
// Fix for GTKLookAndFeel
SwingUtil.installGtkPopupBugWorkaround()
}

// Create main frame
mainView = new MainView(
swing,
Expand Down
136 changes: 136 additions & 0 deletions app/src/main/groovy/org/jd/gui/util/swing/SwingUtil.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2008-2015 Emmanuel Dupuy
* This program is made available under the terms of the GPLv3 License.
*/

package org.jd.gui.util.swing

import groovy.transform.CompileStatic

import javax.swing.JComponent
import javax.swing.JPopupMenu
import javax.swing.JSeparator
import javax.swing.LookAndFeel
import javax.swing.UIManager
import java.lang.reflect.Field
import java.lang.reflect.Method

/**
* See: https://www.ailis.de/~k/archives/67-Workaround-for-borderless-Java-Swing-menus-on-Linux.html
*/
@CompileStatic
class SwingUtil {
/*
* This is free and unencumbered software released into the public domain.
*
* Anyone is free to copy, modify, publish, use, compile, sell, or
* distribute this software, either in source code form or as a compiled
* binary, for any purpose, commercial or non-commercial, and by any
* means.
*
* In jurisdictions that recognize copyright laws, the author or authors
* of this software dedicate any and all copyright interest in the
* software to the public domain. We make this dedication for the benefit
* of the public at large and to the detriment of our heirs and
* successors. We intend this dedication to be an overt act of
* relinquishment in perpetuity of all present and future rights to this
* software under copyright law.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* For more information, please refer to <http://unlicense.org/>
*/

/**
* Swing menus are looking pretty bad on Linux when the GTK LaF is used (See
* bug #6925412). It will most likely never be fixed anytime soon so this
* method provides a workaround for it. It uses reflection to change the GTK
* style objects of Swing so popup menu borders have a minimum thickness of
* 1 and menu separators have a minimum vertical thickness of 1.
*/
public static void installGtkPopupBugWorkaround() {
// Get current look-and-feel implementation class
LookAndFeel laf = UIManager.getLookAndFeel();
Class<?> lafClass = laf.getClass();

// Do nothing when not using the problematic LaF
if (!lafClass.getName().equals("com.sun.java.swing.plaf.gtk.GTKLookAndFeel")) return;

// We do reflection from here on. Failure is silently ignored. The
// workaround is simply not installed when something goes wrong here
try {
// Access the GTK style factory
Field field = lafClass.getDeclaredField("styleFactory");
boolean accessible = field.isAccessible();
field.setAccessible(true);
Object styleFactory = field.get(laf);
field.setAccessible(accessible);

// Fix the horizontal and vertical thickness of popup menu style
Object style = getGtkStyle(styleFactory, new JPopupMenu(), "POPUP_MENU");
fixGtkThickness(style, "yThickness");
fixGtkThickness(style, "xThickness");

// Fix the vertical thickness of the popup menu separator style
style = getGtkStyle(styleFactory, new JSeparator(), "POPUP_MENU_SEPARATOR");
fixGtkThickness(style, "yThickness");
} catch (Exception e) {
// Silently ignored. Workaround can't be applied.
}
}

/**
* Called internally by installGtkPopupBugWorkaround to fix the thickness
* of a GTK style field by setting it to a minimum value of 1.
*
* @param style
* The GTK style object.
* @param fieldName
* The field name.
* @throws Exception
* When reflection fails.
*/
private static void fixGtkThickness(Object style, String fieldName) throws Exception {
Field field = style.getClass().getDeclaredField(fieldName);
boolean accessible = field.isAccessible();
field.setAccessible(true);
field.setInt(style, Math.max(1, field.getInt(style)));
field.setAccessible(accessible);
}

/**
* Called internally by installGtkPopupBugWorkaround. Returns a specific
* GTK style object.
*
* @param styleFactory
* The GTK style factory.
* @param component
* The target component of the style.
* @param regionName
* The name of the target region of the style.
* @return The GTK style.
* @throws Exception
* When reflection fails.
*/
private static Object getGtkStyle(Object styleFactory, JComponent component, String regionName) throws Exception {
// Create the region object
Class<?> regionClass = Class.forName("javax.swing.plaf.synth.Region");
Field field = regionClass.getField(regionName);
Object region = field.get(regionClass);

// Get and return the style
Class<?> styleFactoryClass = styleFactory.getClass();
Method method = styleFactoryClass.getMethod("getStyle", JComponent.class, regionClass);
boolean accessible = method.isAccessible();
method.setAccessible(true);
Object style = method.invoke(styleFactory, component, region);
method.setAccessible(accessible);
return style;
}
}
2 changes: 2 additions & 0 deletions app/src/main/groovy/org/jd/gui/view/PreferencesView.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ class PreferencesView implements PreferencesPanel.PreferencesPanelChangeListener
preferredHeight = maxHeight
preferencesScrollPane.preferredSize = new Dimension(400, preferredHeight)

minimumSize = new Dimension(300, 200)

pack()
locationRelativeTo = parent
}
Expand Down

0 comments on commit 169e5fd

Please sign in to comment.