1. /*
  2. * @(#)GTKLookAndFeel.java 1.57 04/01/13
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package com.sun.java.swing.plaf.gtk;
  8. import java.awt.*;
  9. import java.io.File;
  10. import java.security.AccessController;
  11. import java.security.PrivilegedAction;
  12. import java.util.HashMap;
  13. import javax.swing.*;
  14. import javax.swing.plaf.*;
  15. import javax.swing.text.DefaultEditorKit;
  16. import java.io.IOException;
  17. import sun.security.action.GetPropertyAction;
  18. /**
  19. * @version 1.57, 01/13/04
  20. * @author Scott Violet
  21. */
  22. public class GTKLookAndFeel extends SynthLookAndFeel {
  23. private static final boolean IS_22;
  24. /**
  25. * Font to use in places where there is no widget.
  26. */
  27. private Font fallbackFont;
  28. static {
  29. // Backup for specifying the version, this isn't currently documented.
  30. // If you pass in anything but 2.2 you got the 2.0 colors/look.
  31. String version = (String)java.security.AccessController.doPrivileged(
  32. new GetPropertyAction("swing.gtk.version"));
  33. if (version != null) {
  34. IS_22 = version.equals("2.2");
  35. }
  36. else {
  37. IS_22 = true;
  38. }
  39. }
  40. /**
  41. * Returns true if running on system containing at least 2.2.
  42. */
  43. static boolean is2_2() {
  44. // NOTE: We're currently hard coding to use 2.2.
  45. // If we want to support both GTK 2.0 and 2.2, we'll
  46. // need to get the major/minor/micro version from the .so.
  47. // Refer to bug 4912613 for details.
  48. return IS_22;
  49. }
  50. /**
  51. * Maps a swing constant to a GTK constant.
  52. */
  53. static int SwingOrientationConstantToGTK(int side) {
  54. switch (side) {
  55. case SwingConstants.LEFT:
  56. return GTKConstants.LEFT;
  57. case SwingConstants.RIGHT:
  58. return GTKConstants.RIGHT;
  59. case SwingConstants.TOP:
  60. return GTKConstants.TOP;
  61. case SwingConstants.BOTTOM:
  62. return GTKConstants.BOTTOM;
  63. }
  64. assert false : "Unknowning orientation: " + side;
  65. return side;
  66. }
  67. /**
  68. * Maps from a Synth state to the corresponding GTK state.
  69. * The GTK states are named differently than Synth's states, the
  70. * following gives the mapping:
  71. * <table><tr><td>Synth<td>GTK
  72. * <tr><td>SynthConstants.PRESSED<td>ACTIVE
  73. * <tr><td>SynthConstants.SELECTED<td>SELECTED
  74. * <tr><td>SynthConstants.MOUSE_OVER<td>PRELIGHT
  75. * <tr><td>SynthConstants.DISABLED<td>INACTIVE
  76. * <tr><td>SynthConstants.ENABLED<td>NORMAL
  77. * </table>
  78. * Additionally some widgets are special cased.
  79. */
  80. static int synthStateToGTKState(Region region, int state) {
  81. int orgState = state;
  82. if ((state & SynthConstants.PRESSED) != 0) {
  83. if (region == Region.RADIO_BUTTON
  84. || region == Region.CHECK_BOX
  85. || region == Region.TOGGLE_BUTTON
  86. || region == Region.MENU
  87. || region == Region.MENU_ITEM
  88. || region == Region.RADIO_BUTTON_MENU_ITEM
  89. || region == Region.CHECK_BOX_MENU_ITEM
  90. || region == Region.SPLIT_PANE) {
  91. state = SynthConstants.MOUSE_OVER;
  92. } else {
  93. state = SynthConstants.PRESSED;
  94. }
  95. }
  96. else if ((state & SynthConstants.SELECTED) != 0) {
  97. if (region == Region.MENU) {
  98. state = SynthConstants.MOUSE_OVER;
  99. } else if (region == Region.RADIO_BUTTON ||
  100. region == Region.TOGGLE_BUTTON ||
  101. region == Region.RADIO_BUTTON_MENU_ITEM ||
  102. region == Region.CHECK_BOX_MENU_ITEM ||
  103. region == Region.CHECK_BOX ||
  104. region == Region.BUTTON) {
  105. // If the button is SELECTED and is PRELIGHT we need to
  106. // make the state MOUSE_OVER otherwise we don't paint the
  107. // PRELIGHT.
  108. if ((state & SynthConstants.MOUSE_OVER) != 0) {
  109. state = SynthConstants.MOUSE_OVER;
  110. } else {
  111. state = SynthConstants.PRESSED;
  112. }
  113. } else if (region == Region.TABBED_PANE_TAB) {
  114. state = SynthConstants.ENABLED;
  115. } else {
  116. state = SynthConstants.SELECTED;
  117. }
  118. }
  119. else if ((state & SynthConstants.MOUSE_OVER) != 0) {
  120. state = SynthConstants.MOUSE_OVER;
  121. }
  122. else if ((state & SynthConstants.DISABLED) != 0) {
  123. state = SynthConstants.DISABLED;
  124. }
  125. else {
  126. if (region == Region.SLIDER_TRACK) {
  127. state = SynthConstants.PRESSED;
  128. } else if (region == Region.TABBED_PANE_TAB) {
  129. state = SynthConstants.PRESSED;
  130. } else {
  131. state = SynthConstants.ENABLED;
  132. }
  133. }
  134. return state;
  135. }
  136. static boolean isText(Region region) {
  137. // These Regions treat FOREGROUND as TEXT.
  138. return (region == Region.TEXT_FIELD ||
  139. region == Region.FORMATTED_TEXT_FIELD ||
  140. region == Region.LIST ||
  141. region == Region.PASSWORD_FIELD ||
  142. region == Region.SPINNER ||
  143. region == Region.TABLE ||
  144. region == Region.TEXT_AREA ||
  145. region == Region.TEXT_FIELD ||
  146. region == Region.TEXT_PANE ||
  147. region == Region.TREE);
  148. }
  149. public UIDefaults getDefaults() {
  150. // We need to call super for basic's properties file.
  151. UIDefaults table = super.getDefaults();
  152. table.put("FileChooserUI", "com.sun.java.swing.plaf.gtk.GTKLookAndFeel");
  153. table.addResourceBundle(
  154. "com.sun.java.swing.plaf.gtk.resources.gtk" );
  155. Object tempBorder = new GTKStyle.GTKLazyValue(
  156. "com.sun.java.swing.plaf.gtk.GTKPainter$ListTableFocusBorder");
  157. table.put("List.focusCellHighlightBorder", tempBorder);
  158. table.put("Table.focusCellHighlightBorder", tempBorder);
  159. // These exist for better backward compatability with existing apps
  160. // that use these directly and don't check for null.
  161. table.put("Table.gridColor", new ColorUIResource(Color.gray));
  162. table.put("Table.selectionForeground",
  163. new ColorUIResource(Color.BLACK));
  164. table.put("Table.selectionBackground", new ColorUIResource(0x000080));
  165. table.put("control", new ColorUIResource(0xC0C0C0));
  166. if (fallbackFont != null) {
  167. table.put("TitledBorder.font", fallbackFont);
  168. }
  169. table.put("TitledBorder.titleColor", new ColorUIResource(Color.BLACK));
  170. table.put("TitledBorder.border", new UIDefaults.ProxyLazyValue(
  171. "javax.swing.plaf.BorderUIResource",
  172. "getEtchedBorderUIResource"));
  173. return table;
  174. }
  175. /**
  176. * Creates the GTK look and feel class for the passed in Component.
  177. */
  178. public static ComponentUI createUI(JComponent c) {
  179. String key = c.getUIClassID().intern();
  180. if (key == "FileChooserUI") {
  181. return GTKFileChooserUI.createUI(c);
  182. }
  183. // PENDING: this is only necessary while gtk and synth are in the
  184. // same package.
  185. return SynthLookAndFeel.createUI(c);
  186. }
  187. public void initialize() {
  188. loadStylesFromThemeFiles();
  189. }
  190. public boolean isSupportedLookAndFeel() {
  191. return true;
  192. }
  193. public boolean isNativeLookAndFeel() {
  194. return false;
  195. }
  196. public String getDescription() {
  197. return "GTK look and feel";
  198. }
  199. public String getName() {
  200. return "GTK look and feel";
  201. }
  202. public String getID() {
  203. return "GTK";
  204. }
  205. private void loadStylesFromThemeFiles() {
  206. AccessController.doPrivileged(new PrivilegedAction() {
  207. public Object run() {
  208. GTKParser parser = new GTKParser();
  209. // GTK rc file parsing:
  210. // First, attempts to load the file specified in the
  211. // swing.gtkthemefile system property.
  212. // RC files come from one of the following locations:
  213. // 1 - environment variable GTK2_RC_FILES, which is colon
  214. // separated list of rc files or
  215. // 2 - SYSCONFDIR/gtk-2.0/gtkrc and ~/.gtkrc-2.0
  216. //
  217. // Additionally the default Theme file is parsed last. The default
  218. // theme name comes from the desktop property gnome.Net/ThemeName
  219. // Default theme is looked for in ~/.themes/THEME/gtk-2.0/gtkrc
  220. // and env variable GTK_DATA_PREFIX/THEME/gtkrc or
  221. // GTK_DATA_PREFIX/THEME/gtk-2.0/gtkrc
  222. // (or compiled GTK_DATA_PREFIX) GTK_DATA_PREFIX is
  223. // /usr/share/themes on debian,
  224. // /usr/sfw/share/themes on Solaris.
  225. // Lastly key bindings are supposed to come from a different theme
  226. // with the path built as above, using the desktop property
  227. // named gnome.Gtk/KeyThemeName.
  228. // Try system property override first:
  229. String filename = System.getProperty("swing.gtkthemefile");
  230. if (filename == null || !parseThemeFile(filename, parser)) {
  231. // Try to load user's theme first
  232. String userHome = System.getProperty("user.home");
  233. if (userHome != null) {
  234. parseThemeFile(userHome + "/.gtkrc-2.0", parser);
  235. }
  236. // Now try to load "Default" theme
  237. String themeName = (String)Toolkit.getDefaultToolkit().
  238. getDesktopProperty("gnome.Net/ThemeName");
  239. if (themeName == null) {
  240. themeName = "Default";
  241. }
  242. if (!parseThemeFile(userHome + "/.themes/" + themeName +
  243. "/gtk-2.0/gtkrc", parser)) {
  244. String themeDirName =
  245. System.getProperty("swing.gtkthemedir");
  246. if (themeDirName == null) {
  247. String[] dirs = new String[] {
  248. "/usr/share/themes", // Redhat/Debian/Solaris
  249. "/opt/gnome2/share/themes" // SUSE
  250. };
  251. // Find the first existing rc file in the list.
  252. for (int i = 0; i < dirs.length; i++) {
  253. if (new File(dirs[i] + "/" + themeName +
  254. "/gtk-2.0/gtkrc").canRead()) {
  255. themeDirName = dirs[i];
  256. break;
  257. }
  258. }
  259. }
  260. if (themeDirName != null) {
  261. parseThemeFile(themeDirName + "/" + themeName +
  262. "/gtk-2.0/gtkrc", parser);
  263. }
  264. }
  265. }
  266. setStyleFactory(handleParsedData(parser));
  267. parser.clearParser();
  268. return null;
  269. }
  270. });
  271. }
  272. private boolean parseThemeFile(String fileName, GTKParser parser) {
  273. File file = new File(fileName);
  274. if (file.canRead()) {
  275. try {
  276. parser.parseFile(file, fileName);
  277. } catch (IOException ioe) {
  278. System.err.println("error: (" + ioe.toString()
  279. + ") while parsing file: \""
  280. + fileName
  281. + "\"");
  282. }
  283. return true;
  284. }
  285. return false; // file doesn't exist
  286. }
  287. /**
  288. * This method is responsible for handling the data that was parsed.
  289. * One of it's jobs is to fetch and deal with the GTK settings stored
  290. * in the parser. It's other job is to create a style factory with an
  291. * appropriate default style, load into it the styles from the parser,
  292. * and return that factory.
  293. */
  294. private GTKStyleFactory handleParsedData(GTKParser parser) {
  295. HashMap settings = parser.getGTKSettings();
  296. /*
  297. * The following is a list of the settings that GTK supports and their meanings.
  298. * Currently, we only support a subset ("gtk-font-name" and "gtk-icon-sizes"):
  299. *
  300. * "gtk-can-change-accels" : Whether menu accelerators can be changed
  301. * by pressing a key over the menu item.
  302. * "gtk-color-palette" : Palette to use in the color selector.
  303. * "gtk-cursor-blink" : Whether the cursor should blink.
  304. * "gtk-cursor-blink-time" : Length of the cursor blink cycle, in milleseconds.
  305. * "gtk-dnd-drag-threshold" : Number of pixels the cursor can move before dragging.
  306. * "gtk-double-click-time" : Maximum time allowed between two clicks for them
  307. * to be considered a double click (in milliseconds).
  308. * "gtk-entry-select-on-focus" : Whether to select the contents of an entry when it
  309. * is focused.
  310. * "gtk-font-name" : Name of default font to use.
  311. * "gtk-icon-sizes" : List of icon sizes (gtk-menu=16,16:gtk-button=20,20...
  312. * "gtk-key-theme-name" : Name of key theme RC file to load.
  313. * "gtk-menu-bar-accel" : Keybinding to activate the menu bar.
  314. * "gtk-menu-bar-popup-delay" : Delay before the submenus of a menu bar appear.
  315. * "gtk-menu-popdown-delay" : The time before hiding a submenu when the pointer is
  316. * moving towards the submenu.
  317. * "gtk-menu-popup-delay" : Minimum time the pointer must stay over a menu item
  318. * before the submenu appear.
  319. * "gtk-split-cursor" : Whether two cursors should be displayed for mixed
  320. * left-to-right and right-to-left text.
  321. * "gtk-theme-name" : Name of theme RC file to load.
  322. * "gtk-toolbar-icon-size" : Size of icons in default toolbars.
  323. * "gtk-toolbar-style" : Whether default toolbars have text only, text and icons,
  324. * icons only, etc.
  325. */
  326. Object iconSizes = settings.get("gtk-icon-sizes");
  327. if (iconSizes instanceof String) {
  328. if (!configIconSizes((String)iconSizes)) {
  329. System.err.println("Error parsing gtk-icon-sizes string: '" + iconSizes + "'");
  330. }
  331. }
  332. // Desktop property appears to have preference over rc font.
  333. Object fontName = Toolkit.getDefaultToolkit().getDesktopProperty(
  334. "gnome.Gtk/FontName");
  335. if (!(fontName instanceof String)) {
  336. fontName = settings.get("gtk-font-name");
  337. if (!(fontName instanceof String)) {
  338. fontName = "sans 10";
  339. }
  340. }
  341. Font defaultFont = PangoFonts.lookupFont((String)fontName);
  342. GTKStyle defaultStyle = new GTKStyle(defaultFont);
  343. GTKStyleFactory factory = new GTKStyleFactory(defaultStyle);
  344. parser.loadStylesInto(factory);
  345. fallbackFont = defaultFont;
  346. return factory;
  347. }
  348. private boolean configIconSizes(String sizeString) {
  349. String[] sizes = sizeString.split(":");
  350. for (int i = 0; i < sizes.length; i++) {
  351. String[] splits = sizes[i].split("=");
  352. if (splits.length != 2) {
  353. return false;
  354. }
  355. String size = splits[0].trim().intern();
  356. if (size.length() < 1) {
  357. return false;
  358. }
  359. splits = splits[1].split(",");
  360. if (splits.length != 2) {
  361. return false;
  362. }
  363. String width = splits[0].trim();
  364. String height = splits[1].trim();
  365. if (width.length() < 1 || height.length() < 1) {
  366. return false;
  367. }
  368. int w = 0;
  369. int h = 0;
  370. try {
  371. w = Integer.parseInt(width);
  372. h = Integer.parseInt(height);
  373. } catch (NumberFormatException nfe) {
  374. return false;
  375. }
  376. if (w > 0 && h > 0) {
  377. // Only allow resizing of existing icon sizes.
  378. if (GTKStyle.getIconSize(size) != null) {
  379. GTKStyle.setIconSize(size, w, h);
  380. }
  381. } else {
  382. System.err.println("Invalid size in gtk-icon-sizes: " + w + "," + h);
  383. }
  384. }
  385. return true;
  386. }
  387. }