使用 Java 开发 GNOME 应用程序

作者:Mike Petullo

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
Developing GNOME Applications with 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 执行。

Developing GNOME Applications with 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

Developing GNOME Applications with 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 允许您创建一个用户界面组件,命名该组件以便可以通过相应的程序引用它,为组件信号处理程序提供方法名称,并为组件定义各种属性。

Developing GNOME Applications with Java

图 4. 在 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 的屏幕截图。

Developing GNOME Applications with Java

图 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 帮助审阅本文。

加载 Disqus 评论