使用 Java 开发 GNOME 应用程序
1997 年 GNOME 桌面项目的最初公告声明了以下意图,“使用 GTK/Scheme 绑定来编写小型实用程序和应用程序”。从那时起,GNOME 开发平台提供了使用多种 C 语言替代方案进行开发的工具。官方 GNOME 发行版支持 C、C++、Java、Perl 和 Python。此外,Mono 项目提供了使用 C# 编程语言开发 GNOME 应用程序所需的工具。所有这些选项都变得非常流行。例如,Fedora 项目的许多系统配置工具的 GNOME 界面是用 Python 编写的,并且许多新的应用程序正在用 C# 编写。本文介绍了如何使用 GNU 编译器集合中的免费 Java 编译器创建 GNOME 应用程序。尽管本文重点介绍 Java,但所描述的技术围绕 GLADE 用户界面构建器展开,并且可以与 GNOME 项目支持的任何绑定一起使用。
用于 Java 编程语言的 GNU 编译器 (gcc-java) 是一个在 GNU 通用公共许可证下发布的 Java 开发环境。由于 gcc-java 是自由软件,因此它的开发独立于 Sun Microsystems 的 Java 工作。因此,gcc-java 尚未实现 100% 的 Java 标准。例如,对抽象窗口工具包 (AWT) 的支持尚未完成。尽管目前存在不足,但 gcc-java 作为完全免费的 Java 堆栈的基础显示出巨大的前景,并且它已经可以用于构建许多实际应用程序;请参阅在线资源中的示例。
与许多 Java 编译器不同,gcc-java 可以生成 Java 字节码和特定于平台的本地可执行文件。在后一种情况下,可执行文件与 gcc-java 的 libgcj 链接。 libgcj 是一个包含核心 Java 类库和垃圾回收器的库。此外,libgcj 还包含一个字节码解释器,因此本地编译的 Java 应用程序可以与 Java 字节码库交互。
清单 1. HelloWorld.java
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }
清单 1 中的简单 Java 源代码可以使用以下命令编译为 Java 字节码gcj -C HelloWorld.java并使用以下命令解释gij HelloWorld。相同的源代码可以使用以下命令编译为本地可执行文件gcj --main=HelloWorld -o HelloWorld HelloWorld.java并使用以下命令执行./HelloWorld。本文避免在 Java 代码清单中包含 import 和其他琐碎的语句;完整源代码文件请参阅资源。
清单 2. ExampleAWT.java 片段
public class ExampleAWT extends Frame { ExampleAWT() { super("AWT"); Label msgLabel = new Label("Quit?"); Button yesButton = new Button("Yes"); Button noButton = new Button("No"); Panel buttonbox = new Panel(); buttonbox.setLayout(new FlowLayout()); buttonbox.add(yesButton); buttonbox.add(noButton); Panel msgbox = new Panel(); msgbox.setLayout(new FlowLayout()); msgbox.add(msgLabel); add(msgbox, BorderLayout.NORTH); add(buttonbox, BorderLayout.SOUTH); yesButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); noButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(1); } }); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public static void main(String[] args) { ExampleAWT frame = new ExampleAWT(); frame.pack(); frame.setVisible(true); } }
Sun 提供了两个类层次结构,用于开发具有图形用户界面的 Java 应用程序。第一个是抽象窗口工具包,自 Java 1.0 版本以来就已随 Java 分发。图 1 显示了 gcc-java 编译的 AWT 应用程序的图片。相应的源代码在清单 2 中提供,可以使用以下命令编译
gcj --main=ExampleAWT -o ExampleAWT ExampleAWT.java

图 1. AWT 应用程序
清单 3. ExampleSwing.java 片段
public class ExampleSwing { public static void main(String[] args) { JFrame win = new JFrame("Swing"); JLabel msgLabel = new JLabel("Quit?"); JButton yesButton = new JButton("Yes"); JButton noButton = new JButton("No"); win.getContentPane().setLayout (new BorderLayout()); JPanel buttonbox = new JPanel(); buttonbox.setLayout(new FlowLayout()); buttonbox.add(yesButton); buttonbox.add(noButton); win.getContentPane().add(msgLabel, "Center"); win.getContentPane().add(buttonbox, "South"); yesButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); noButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(1); } }); win.pack(); win.show(); } }
第二个系统 Swing 于 Java 1.2 中首次亮相。图 2 是清单 3 中所示的 gcc-java 编译的 Swing 应用程序的图片。清单 3 可以使用以下命令编译gcj --main=ExampleSwing -o ExampleSwing ExampleSwing.java。AWT 使用主机操作系统中的本地 GUI 组件来绘制自身。Swing 使用户可以更精细地控制组件的外观和感觉,并且大部分工作由 Java 执行。

图 2. Swing 应用程序——AWT 和 Swing 的编写方式都使得一个应用程序在任何平台上都以类似的方式运行。
清单 4. ExampleSWT.java 片段
public class ExampleSWT { public static void main(String[] args) { Display display = new Display(); Shell shell = new Shell(display); shell.setLayout(new FillLayout(SWT.VERTICAL)); Composite msgbox = new Composite(shell, SWT.NO_TRIM); RowLayout msglayout = new RowLayout(); msglayout.justify = true; msgbox.setLayout(msglayout); Label label = new Label(msgbox, SWT.NO_TRIM); label.setText("Quit?"); Composite buttonbox = new Composite(shell, SWT.NO_TRIM); RowLayout buttonlayout = new RowLayout(); buttonlayout.justify = true; buttonlayout.pack = true; buttonbox.setLayout(buttonlayout); Button yesButton = new Button(buttonbox, SWT.PUSH); yesButton.setText("Yes"); Button noButton = new Button(buttonbox, SWT.PUSH); noButton.setText("No"); yesButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event) { System.exit(0); } }); noButton.addSelectionListener( new SelectionAdapter() { public void widgetSelected( SelectionEvent event) { System.exit(1); } }); shell.pack(); shell.open(); while (! shell.isDisposed()) { if (! display.readAndDispatch()) display.sleep(); } } }
IBM 赞助了 Eclipse 项目,这是一项旨在生产开源开发环境的工作。该项目的成果之一是标准窗口小部件工具包,它是 AWT 和 Swing 的替代品。SWT 是一个基于对等端的、独立于操作系统的界面,它使用主机操作系统的界面来渲染通用组件。操作系统不支持的组件在 Java 中实现。在 Linux 上,libswt-gtk2 包为 SWT 提供了 GTK 对等端。对等端也存在于其他平台,包括 Solaris 和 Windows。SWT 代码可以在任何具有 SWT 对等端的平台上运行。清单 4 显示了一个示例 SWT 应用程序,可以使用以下命令的变体针对 GTK SWT 对等端进行编译
gcj --CLASSPATH=/usr/lib/libswt-gtk2.jar -lswt-gtk2 -o ExampleSWT --main=ExampleSWT ExampleSWT.java
有关标准窗口小部件工具包的更多信息,请参阅资源。
有了三个现有的 Java GUI 工具包,人们可能会问为什么还需要另一种替代方案。GNOME 的 Java 绑定是独一无二的,因为它们直接绑定到 GNOME。使用 GNOME 的 Java 产品编写的应用程序看起来和行为方式与使用 GNOME 的 C 库编写的应用程序完全相同。它无缝集成到 GNOME 桌面中,并提供与任何其他 GNOME 应用程序相同的功能。原因是 GNOME 的 Java 绑定使用 Java 本地接口将工作直接委派给 GNOME 的 C 库。
目前,GNOME 的 Java 绑定由四个库组成——libgconf-java、libglade-java、libgnome-java 和 libgtk-java。libgtk-java 和 libgnome-java 提供了绑定的 GUI 组件。libglade-java 允许 Java 应用程序读取由 GLADE 创建的图形用户界面描述。对 libgconf-java(GConf 配置系统的 Java 接口)的调查留给读者作为练习。
libgtk-java 和 libgnome-java 类似于 SWT 和 AWT,因为主机代码实现了它们的图形组件。但是,GNOME 库与 AWT、Swing 和 SWT 完全不同——GNOME 库不声称具有平台独立性。用 Java 编写的 GNOME 应用程序仅在 GNOME 环境中运行。任何平台独立性都是整个 GNOME 环境本身是平台独立的的结果。
清单 5. ExampleGNOME.java 片段
public class ExampleGNOME { private LibGlade libglade; private static final String GLADE_FILE = "ExampleGNOME.glade"; public ExampleGNOME () throws IOException { libglade = new LibGlade(GLADE_FILE, this); } public void on_noButton_released(GtkEvent event) { Gtk.mainQuit(); System.exit(1); } public void on_yesButton_released(GtkEvent event) { Gtk.mainQuit(); System.exit(0); } public static void main(String args[]) { ExampleGNOME gui; Gtk.init(args); try { gui = new ExampleGNOME(); } catch (IOException e) { System.err.println(e); System.exit(1); } Gtk.main(); } }
图 3 捕获了一个 gcc-java 编译的 GNOME 应用程序。清单 5 显示了 GNOME 应用程序的源代码,可以使用以下命令编译
gcj --CLASSPATH=/usr/share/java/gtk2.4.jar:\ /usr/share/java/gnome2.8.jar:\ /usr/share/java/glade2.8.jar \ -lgtkjar2.4 -lgnomejar2.8 -lgladejar2.8 \ -o ExampleGNOME --main=ExampleGNOME \ ExampleGNOME.java

图 3. Java GNOME 应用程序
乍一看,清单 5 可能看起来比其他清单要稀疏一些。ExampleGNOME 的用户界面在 ExampleGNOME.glade 中定义;因此,应用程序本身没有太多 GUI 代码。相反,libglade-java 读取 ExampleGNOME.glade 并自动创建应用程序的 GUI 组件。GUI 代码通过事件回调方法与我们的代码绑定在一起。这些回调中的两个(其名称和相应的信号在 ExampleGNOME.glade 中定义)是 on_noButton_released 和 on_yesButton_released。清单 6 包含 ExampleGNOME.glade 的一部分内容。
清单 6. ExampleGNOME.glade 片段
<?xml version="1.0" standalone="no"?> <!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd"> <glade-interface> <requires lib="gnome"/> <widget class="GtkWindow" id="ExampleGNOME"> <property name="visible">True</property> <property name="title" translatable="yes"> GNOME</property> <property name="type"> GTK_WINDOW_TOPLEVEL</property> <property name="window_position"> GTK_WIN_POS_NONE</property> <property name="modal">False</property> <property name="resizable">True</property> <property name="destroy_with_parent"> False</property> <property name="decorated">True</property> <property name="skip_taskbar_hint"> False</property> <property name="skip_pager_hint">False</property> <property name="type_hint"> GDK_WINDOW_TYPE_HINT_NORMAL</property> <property name="gravity"> GDK_GRAVITY_NORTH_WEST</property> <child> <widget class="GtkVBox" id="vbox1"> <property name="visible">True</property> <property name="homogeneous">False</property> <property name="spacing">0</property> ... <child> <widget class="GtkHBox" id="hbox1"> <property name="visible">True</property> <property name="homogeneous">False</property> <property name="spacing">0</property> <child> <widget class="GtkButton" id="yesButton"> <property name="visible"> True</property> <property name="can_focus">True</property> <property name="label">gtk-yes</property> <property name="use_stock">True</property> <property name="relief"> GTK_RELIEF_NORMAL</property> <property name="focus_on_click"> True</property> <signal name="released" handler="on_yesButton_released" last_modification_time= "Sun, 21 Nov 2004 19:10:01 GMT"/> </widget> <packing> <property name="padding">0</property> <property name="expand">True</property> <property name="fill">False</property> </packing> </child> ... </child> </widget> </child> </widget> </glade-interface>
GLADE 系统提供了一个用户界面构建器,可以轻松创建像 ExampleGNOME.glade 这样的定义。图 4 显示了一个示例 GLADE 用户界面构建器会话。清单 8 包含正在编辑的某些界面描述。本质上,GLADE 允许您创建一个用户界面组件,命名该组件以便可以通过相应的程序引用它,为组件信号处理程序提供方法名称,并为组件定义各种属性。
使用 GLADE 设计 GUI 并允许 libglade-java 完成繁重的工作,可以显着减少应用程序开发人员的工作量。
清单 7. GnomeSesameFormat.java 片段
public class GnomeSesameFormat { ... private void init() throws IOException { glade = new LibGlade(System.getProperty("GLADE_FILE"), this); // Default values. isDryRun = false; cipher = new AES256(); fs = new Ext3(); passphrase = null; volName = null; // References to various windows used by // application. topLevel = (Window) glade.getWidget("topLevel"); devSelUI = (FileSelection) glade.getWidget("devSelUI"); errUI = (Window) glade.getWidget("errUI"); progressUI = (Window) glade.getWidget("progressUI"); } ... public GnomeSesameFormat() throws IOException { init(); device = null; Label l = (Label) glade.getWidget("displayedDevice"); l.setText("none selected"); } ... public void onFormatButtonClicked(GtkEvent event) { Entry entry; entry = (Entry) glade.getWidget("entryPassphrase"); passphrase = entry.getText(); entry = (Entry) glade.getWidget("entryVolumeName"); volName = entry.getText(); if (topLevelInputOk ()) { ProgressBar p = (ProgressBar) glade.getWidget ("progressBarFormat"); Label l = (Label) glade.getWidget("labelFormat"); ProgressBarUpdater pU = new ProgressBarUpdater(p); topLevel.setSensitive(false); progressUI.show(); if (! isDryRun) { l.setText("Formatting " + device); pU.start(); execSesameFormat(); pU.stopReq(); try { pU.join(); } catch (java.lang.InterruptedException e) {} } else { l.setText("[Simulated] Formatting " + device); pU.start(); try { Thread.sleep(1000); } catch (java.lang.InterruptedException e) {} pU.stopReq(); try { pU.join(); } catch (java.lang.InterruptedException e) {} } progressUI.hide(); topLevel.setSensitive(true); } ... private void error(String msg) { Label l = (Label) glade.getWidget("labelErr"); l.setText(msg); topLevel.setSensitive(false); errUI.show(); } public void onErrOkButtonClicked(GtkEvent event) { errUI.hide(); topLevel.setSensitive(true); } ... public static void main(String args[]) { GnomeSesameFormat gui = null; Gtk.init(args); LongOpt[] longOpt = new LongOpt[2]; longOpt[0] = new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'); longOpt[1] = new LongOpt("dry-run", LongOpt.NO_ARGUMENT, null, 'd'); Getopt g = new Getopt("gnome-sesame-format", args, "hd", longOpt); int c; boolean optDryRun = false; while ((c = g.getopt()) != -1) switch (c) { case 'h': printUsage(0, null, null); case 'd': optDryRun = true; break; default: printUsage(1, null, null); } try { int i = g.getOptind(); if (i == 1) gui = new GnomeSesameFormat(args[i]); else if (i > 1) printUsage(1, null, null); else gui = new GnomeSesameFormat(); gui.setDryRun(optDryRun); Gtk.main(); } catch (Exception e) { System.err.println(e); System.exit(1); } } ...
清单 8. GnomeSesameFormat.glade 片段
... <widget class="GtkButton" id="buttonFormat"> ... <signal name="clicked" handler="onFormatButtonClicked" last_modification_time= "Wed, 02 Feb 2005 19:16:35 GMT"/> ... <widget class="GtkDialog" id="errUI"> <child internal-child="vbox"> <widget class="GtkVBox" id="vboxErr"> ... <child internal-child="action_area"> <widget class="GtkHButtonBox" id="hboxErrOk"> ... <child> <widget class="GtkButton" id="buttonErrOk"> ... <signal name="clicked" handler="onErrOkButtonClicked" last_modification_time= "Wed, 02 Feb 2005 19:22:48 GMT"/> ... </widget> </child> </widget> </child> <child> <widget class="GtkHBox" id="hboxStop"> ... <child> <widget class="GtkImage" id="imageStop"> ... </widget> </child> <child> <widget class="GtkLabel" id="labelErr"> ... </widget> </child> </widget> </child> </widget> </child> </widget>
清单 7 显示了 GnomeSesameFormat 的一些相应 Java 源代码。清单 8 包含 GnomeSesameFormat 界面定义的一部分。
GnomeSesameFormat 是我开发的一个简单应用程序,它的大部分工作是通过执行一个名为 sesame-format 的外部程序来完成的。 sesame-format 格式化磁盘以包含加密文件系统。GnomeSesameFormat 只是为这个命令行工具提供了一个 GUI 包装器。GnomeSesameFormat 可以使用其 --dry-run 选项执行,以方便测试和实验。在撰写本文时,使用此工具格式化磁盘可能不是一个好主意。图 5 显示了 GnomeSesameFormat 的屏幕截图。
GnomeSesameFormat 应用程序在单个类 GnomeSesameFormat 中实现。GnomeSesameFormat 类的 main 函数使用 Gtk.init 方法初始化 GTK 库,创建一个新的 GnomeSesameFormat 实例,并通过调用 Gtk.main 将控制权释放给 GTK 事件循环。
有趣的工作从 GnomeSesameFormat 类的构造函数开始。在构造函数中,实例化了一个 LibGlade 对象。它读取 GLADE 用户界面描述并实例化其对应的对象。可以使用 LibGlade 对象的 getWidget 方法按名称检索对这些对象的引用。一旦我们获得了对界面组件的引用,我们就可以像自己创建它们一样使用它们。GnomeSesameFormat 类还包含 GnomeSesameFormat.glade 中引用的信号处理方法。
在开发 GnomeSesameFormat 时,我使用了上面介绍的四个步骤。例如,使用 GLADE 将一个按钮定义为应用程序 GUI 的一部分(步骤 1)。该按钮被命名为 buttonFormat(步骤 2)。再次使用 GLADE,将方法名称 onButtonFormatClicked 指定为处理按钮的 clicked 信号(步骤 3)。最后,在 GnomeSesameFormat 的 Java 源代码中实现了 onButtonFormatClicked 方法(步骤 4)。
为了进一步操作组件,libglade 可以提供对单个组件的引用。LibGlade 对象的 getWidget 方法提供了此功能。为了说明这一点,我们可以研究 GnomeSesameFormat 的 errUI 组件。errUI 组件是一个 Window,用于向用户显示错误消息。errUI 窗口在 GLADE 中定义(步骤 1)并命名(步骤 2)。因为我们知道 errUI 的名称,所以我们可以通过调用 getWidget(errUI) 来获得对它的引用。一旦我们收到对组件的引用,就可以调用任何 GTK 方法。GnomeSesameFormat 使用 errUI 的 show 和 hide 方法。
GNOME 项目提供了使用 C、C++、Java、Python 和 Perl 开发应用程序的能力。此外,诸如 Mono 之类的外部项目提供了更多的多样性。当与这些替代方案中的几种一起使用时,GLADE 用户界面构建器可以快速编写具有 GNOME 平台图形用户界面的应用程序。一旦定义了图形组件,剩下的就是实现应用程序外壳和信号处理程序。此实现可以使用任何编程语言完成。
本文资源: /article/8274。
Mike Petullo 目前在 WMS Gaming 工作,并在德保罗大学攻读硕士学位。自 1997 年以来,他一直在摆弄 Linux,欢迎您将评论发送至 lj@flyn.org。感谢 Noah Alcantara 帮助审阅本文。