1: Eclipse中用SWT和JFace开发入门 ;2:在Eclipse中使用SWT进行界面设计......
[搜集整理]Eclipse+SWT开发入门
目录
?
1: Eclipse中用SWT和JFace开发入门
2:在Eclipse中使用SWT进行界面设计
3:使用SWT开发基于Java的图形化用户界面
4:SWT Examples
?
2005-06-14 08:38 作者: Barry 出处: blog 责任编辑:方舟
可以使用标准窗口小部件工具箱(Standard Widget Toolkit,SWT)和 JFace 库来开发用于 Eclipse 环境的图形用户界面,而且还可以将它们用于开发单独的 GUI 本机应用程序。在本文中,我将介绍一些基本的 SWT(基本 GUI 对象的名称)类型,并展示如何综合使用它们来创建有用的应用程序。
关于 Eclipse、SWT 和 JFace
正如 Eclipse 的 Web 站点上所提到的,Eclipse 是一种通用工具平台。它是一个开放的、可用于任何东西的可扩展 IDE,没什么特别之处,它为工具开发人员提供了灵活性以及对软件技术的控制。
Eclipse 为开发人员提供了生产大量 GUI 驱动的工具和应用程序的基础。而这项功能的基础就是 GUI 库 SWT 和 JFace。
SWT 是一个库,它创建了Java 版的本地主机操作系统 GUI 控件。它依赖于本机实现。这意味着基于 SWT 的应用程序具有以下几个关键特性:
? 它们的外观、行为和执行类似于“本机”应用程序。
? 所提供的窗口小部件(widget)反映了主机操作系统上提供的窗口小部件(组件和控件)。
? 主机 GUI 库的任何特殊行为都在 SWT GUI 中得到反映。
这些目标使得 SWT 不同于 Java 技术的 Swing,Swing 的设计目标是消除操作系统的差异。
SWT 库反映了主机操作系统的基本窗口小部件。在许多环境下,这种方法太低级。JFace 库有助于向 SWT 应用程序中添加大量服务。JFace 并没有隐藏 SWT,它只是扩展了 SWT。正如您将在这一系列的后面部分中看到的,SWT 最重要的扩展之一是,将应用程序的数据模型与显示及更改它的 GUI 隔离开来。
在开始之前,我需要介绍一些 SWT 术语:
? Widget —— 基本的 SWT GUI 组件(类似于 Java AWT 中的 Component 和 Swing 中的 JComponent)。Widget 是一个抽象类。
? Control —— 拥有操作系统的对等物的窗口小部件(换句话说,在操作系统中具有同一身份)。Control 是一个抽象类。
? Composite —— 包含其他控件的控件(类似于 Java AWT 中的 Container 和 Swing 中的 JPanel)。
? Item —— 其他控件包含的窗口小部件(该控件可能不是复合控件),比如列表和表。注意,包含一些项的控件很少包含其他控件,反之亦然。Item 是一个抽象类。
这些窗口小部件被安排在继承层次结构中。参见图 1、图 2 和图 3,了解它们是如何安排的。在图 2 中,Basic1 类是来自本文的类,而其他所有类都是标准的 SWT 窗口小部件。
图 1. SWT Widget 树
图 2. SWT Composite 树
图 3. SWT Item 列表
注意,Eclipse 具有跨平台特性(因此可以在许多操作平台上运行),本文基于 Eclipse 的 Microsoft? Windows? 版本。因此,本文包含的每个例子都应该能够不加任何更改地在其他平台上使用。还要注意的是,本文是基于 Eclipse V3.0 的。Eclipse V3.1 中添加了少许 GUI 窗口小部件类型和特性。
基本控件
几乎所有 SWT GUI 都是从某些基础部分开始创建的。所有 SWT 窗口小部件都可以在 org.eclipse.swt.widget 或 org.eclipse.swt.custom 包中找到。(一些 Eclipse 插件还在其他包中提供了定制的窗口小部件。)窗口小部件包中包含一些基于操作系统控件的控件,而定制包中则包含一些超出操作系统控件集之外的控件。一些定制的软件包控件类似于窗口小部件包中的控件。为了避免命名冲突,定制控件的名称都是以“C”开始的(例如,比较 CLabel 与 Label)。
在 SWT 中,所有控件(除了一些高级控件,比如 shell,将在后面进行讨论)在创建的时候都必须有一个父控件(一个复合实例)。在创建的时候,这些控件被自动“添加”到父控件中,这与必须明确添加到父控件中的 AWT/Swing 中的控件有所不同,自动添加产生了一种“自上而下”地构造 GUI 的方法。这样,所有控件都可以采用一个复合父控件(或者一个子类)作为构造函数的参数。
大多数控件都有一些必须在创建时设置的标记选项。因此,大多数控件还有另外一个构造函数参数,我们通常称之为样式,该参数提供了设置这些选项的标记。所有这些参数值都是 static final int,并且都是在 org.eclipse.swt 包的 SWT 类中定义的。如果不需要任何参数,则可以使用 SWT.NONE 值。
标签
标签可能是最简单的控件,标签 被用于显示纯文本(没有颜色、特殊字体或样式的文本)或称为图标的小图像。标签不接受焦点(换句话说,用户不能通过 Tab 键或鼠标移动到标签),因此,标签无法产生输入事件。
清单 1 展示了如何创建一个简单的文本标签。
清单 1. 创建一个带文本的标签
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a center aligned label Label label = new Label(parent, SWT.CENTER); label.setText("This is the label text"); |
注意,该文本是采用不同于构造函数的单独的方法设置的。这是所有 SWT 控件的一个典型象征。只有父控件和样式是在构造函数中设置的,其他所有属性都是在已创建的对象上设置的。
由于平台的限制,标准标签控件不能同时拥有文本和图标。为了支持同时拥有文本和图标,可以使用 CLabel 控件,如清单 2 中所示。
清单 2. 创建一个包含文本和图像的标签
import com.eclipse.swt.graphics.*; import org.eclipse.swt.widget.*; import org.eclipse.swt.custom.*; : Composite parent = ...; Image image = ...; : // create a left aligned label with an icon CLabel Clabel = new CLabel(parent, SWT.LEFT); label.setText("This is the imaged label text""); label.setImage(image); |
文本
在标签显示文本的同时,您时常还想允许用户插入文本。文本 控件就是用于此目的的。文本可以是单行的(一个文本字段),也可以是多行的(一个文本区域)。文本还可以是只读的。文本字段中没有描述,因此,常常通过标签控件处理它们,以确定它们的用途。文本控件还可以包含一个“工具提示”,提供关于控件用途的信息(所有控件都支持这一特性)。
清单 3 显示了如何使用允许使用的有限数量的特性来创建一个简单的文本字段。选择默认文本是为了便于擦除。
清单 3. 创建一个包含选定的默认文本和一个限制条件的文本
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a text field Text name = new Text(parent, SWT.SINGLE); name.setText("<none>"); name.setTextLimit(50); name.setToolTipText("Enter your name -- Last, First"); name.selectAll();? // enable fast erase |
按钮
通常,您希望用户指出应该何时进行某项操作。最常见的做法是使用按钮 控件。存在以下几种样式的按钮:
· ARROW —— 显示为一个指向上、下、左、右方向的箭头。
· CHECK —— 已标记的复选标记。
· FLAT —— 没有凸起外观的按钮。
· PUSH —— 瞬时按钮(最常见的事件源)。
· RADIO —— 具有排他性的粘性标记(sticky mark),其他所有单选按钮都在相同的组中。
· TOGGLE —— 一个粘性按钮。
清单 4 创建了一个“Clear”按钮:
清单 4. 创建一个按钮
import org.eclipse.swt.widget.*; : Composite parent = ...; : // create a push button Button clear = new Button(parent, SWT.PUSH); clear.setText("Clea&r"); |
名称中的 & 导致利用紧接着的一个字母创建一个加速器,允许通过 Ctrl+<字母> 顺序的方式按下按钮(控件顺序由主机操作系统决定)。
事件监听器
通常,您可能想在选择按钮(特别是某种推式按钮)的时候执行一些操作。您可以通过向该按钮添加一个 SelectionListener(在 org.eclipse.swt.events 包中)做到这一点。当按钮状态发生改变时(通常是按钮被按下),就会生成事件。清单 5 在单击 Clear 按钮时输出一条消息。
清单 5. 按钮事件处理程序
import org.eclipse.swt.events.*; : // Clear button pressed event handler clear.addSelectionListener(new SelectionListener() { public void widgetelected(selectionEvent e) { System.out.println("Clear pressed!"); } public void widgetDefaultSelected(selectionEvent e) { widgetelected(e); } }); |
此代码使用了一个匿名的内部类,但您还可以使用指定的内部类或单独的类作为监听器。多数包含两个或更多方法的 ...Listener 类还有一个类似的 ...Adapter 类,这个类提供了一些空的方法实现,并且可以减少您需要编写的代码数量。例如,还有一个 SelectionAdapter 类,这个类实现了 SelectionListener。
注意,在这些方法中执行的操作必须快速完成(通常不足一秒时间),或者说 GUI 的反应将是迟钝的。更长时间的操作(比如访问文件)需要单独的线程,但那是以后某期文章的主题。
复合控件
至此,我们已经讨论了一些单独的控件。在多数 GUI 中,许多控件被组合在一起以提供丰富的用户体验。在 SWT 中,这种组合是通过 Composite 类实现的。
复合控件可以在任何级别上进行嵌套,并且可以混合和匹配控件,将它们作为子控件进行组合。这样做可以极大地减少 GUI 开发的复杂性,并为 GUI 代码重用(通过封装内部 GUI)创造了机会。复合控件可以是有边界的,并且这些边界很容易在视觉上产生混淆,或者它们也可以是无边界的,无缝集成到更大的组中。
清单 6. 创建一个有边界的复合控件
import org.eclipse.swt.widget.*; : Composite parent = ...; : Composite border = new Composite(parent, SWT.BORDER); |
除了边界之外,Group 复合子类还支持标题。在定义排他性按钮集合时,组通常被用来包含单选类型的按钮。
清单 7. 创建一个有边界的组
import org.eclipse.swt.widget.*; : Composite parent = ...; : Group border = new Group(parent, SWT.SHADOW_OUT); border.setText("Group Description"); |
shell
shell 是一种可能没有父复合控件的复合控件(框架或窗口);此外,它还有一个作为父控件的 Display,这通常也是默认设置。shell 有很多种样式,但最常见的样式是 SWT.SHELL_TRIM 或 SWT.DIALOG_TRIM。shell 可以是模态的,也可以是非模态的。模态 shell 常常用于对话框,防止父 GUI(如果有的话)在关闭子 shell 之前被处理。
清单 8 创建了一个框架样式的顶级非模态 shell。
清单 8. 创建一个顶级 shell
import org.eclipse.swt.widget.*; : Shell frame = new Shell(SWT.SHELL_TRIM); : |
shell 可以有子 shell。这些子 shell 是与父 shell 相关的独立桌面窗口(也就是说,如果父 shell 关闭,那么其所有子 shell 也将关闭)。
清单 9 创建了一个对话框样式的子 shell。
清单 9. 创建一个对话框 shell
: Shell dialog = new Shell(frame, SWT.DIALOG_TRIM); : |
参见图 4 中具有 SWT.SHELL_TRIMSee 的 shell,以及图 5 中具有 SWT.DIALOG_TRIM 的 shell,了解这些值如何影响 shell 的整洁性。
图 4. 具有 SWT.SHELL_TRIM 的 shell
图 5. 具有 SWT.DIALOG_TRIM 的 shell
布局管理器
复合控件常常包含多个控件。可以使用以下两种方法安排这些控件:
- 绝对定位 —— 为每个控件设置明确的 X 和 Y 位置,并通过代码设置一定的宽度和高度。
- 托管定位 —— 每个控件的 X、Y、宽度和高度都是通过LayoutManager 设置的。
在多数情况下,应该选择使用 LayoutManagers,因为很容易调整它们来适应可变大小的 GUI。SWT 也提供了一些布局管理器供您使用;在这一期的系列文章中,我们将讨论两种基本的布局管理器:FillLayout 和 GridLayout。在这两种情况下,每当重新设置复合控件的大小,都需要进行定位。
一些布局管理器常常是专为某一个复合控件分配的。一些布局管理器只使用它们自身的参数就可以控制,而另一些布局管理器还需要其他参数 —— LayoutData,该参数是在它们管理的复合控件中的每个控件上指定的。
FillLayout
FillLayout 以行或列的形式安排控件。每个控件所设置的大小将与填充该复合控件所需的宽度和高度相同,在这些控件之间,空间是平均分配的。一种特殊情况是:在仅有一个子控件时,该控件的大小被设置为填充整个父复合控件的大小。
清单 10. 使用 FillLayout 创建一列控件
import org.eclipse.swt.widget.*; import org.eclipse.swt.layouts.*; : Composite composite = ...; FillLayout fillLayout = new FillLayout(SWT.VERTICAL); composite.setLayout(fillLayout); |
GridLayout
GridLayout 提供了一个功能更强大的布局方法,该方法类似于使用 HTML 表的方法。它创建了 2-D 网格的单元格。可以将控件放置在一个或多个单元格中(可以称之为单元格跨越)。单元格的大小可以是相等的,或者是网格宽度或高度的某个给定可变百分比。可以将控件添加到某一行的下一个可用列中,如果这一行中没有更多的列,那么该控件将移动到下一行的第一列中。
清单 11 创建了一个复合控件,该控件有两行和两个列,其中包含两个已标记的文本字段。这些列可以有不同的宽度。
清单 11. 创建一个控件表
import org.eclipse.swt.widget.*; import org.eclipse.swt.layouts.*; : Composite composite = ...; GridLayout gridLayout = new GridLayout(2, false); composite.setLayout(gridLayout); Label l1 = new Label(composite, SWT.LEFT); l1.settext("First Name: "); Text first = new Text(composite, SWT.SINGLE); Label l1 = new Label(composite, SWT.LEFT); l2.setText("Last Name: "); Text last = new Text(composite, SWT.SINGLE); |
GridData
考虑一下这种情况:您需要指定每个控件如何使用其单元格中的剩余空间。为了给每个单元格提供这种精确控制,添加到 GridLayout 的托管复合控件的控件可以拥有 GridData 实例(LayoutData 的子类)。
清单 12 设置了这些文本字段,以便采用所有可用的剩余空间(根据前面的清单)。
清单 12. 配置一个扩展到所有可用空间的布局
first.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); last.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
构建一个运行程序
现在是时候来看一下我们已经在简单的可执行例子 Basic1 中讨论过的所有 SWT 控件了。请参阅 参考资料,以获得该应用程序的完整源代码。
SWT GUI 需要一个已配置好的环境来运行。这个环境是通过一个显示实例提供的,该实例提供了对主机操作系统显示设备的访问。这个显示实例允许您处理每个用户输入(鼠标或键盘)来处理您的 GUI。
清单 13 创建了一个环境和一个 GUI,并显示了这个 GUI。
清单 13. 创建一个 GUI 应用程序并启动它
import org.eclipse.swt.widget.*; : Display display = new Display(); Shell shell = new Shell(display); shell.setText("Shell Title");
// *** construct Shell children here ***
shell.open();?????? // open shell for user access
// process all user input events while(!shell.isDisposed()) { // process the next event, wait when none available if(!display.readAndDispatch()) { display.sleep(); } } display.dispose();? // must always clean up |
此代码创建了一个类似于图 6 的窗口。
图 6. 示例应用程序
结束语
在 SWT 和 JFace 系列的第一期中,我们介绍了大多数基本 SWT 窗口小部件控件:标签、文本、按钮、复合控件和 shell。这些控件,与显示类(display class)相结合,允许创建全功能的 GUI。
? | ||
[ 2004-10-11 00:00:57 ] | 作者:yipsilon | 责任编辑:linjixiong |
1. 为什么要使用SWT?
SWT是一个IBM开发的跨平台GUI开发工具包。至于为什么IBM要费劲自己另起炉灶开发一个GUI工具包,而不是使用Sun现有的由AWT, Swing, Java 2D, Java 3D等构成的Java GUI框架,那就说来话长了。(记得在一个BBS上读过一个关于SWT起源的调侃类的帖子)。
在SWT之前,Sun已经提供了一个跨平台GUI开发工具包AWT (Abstract Windowing Toolkit). 虽然AWT框架也使用的是原生窗口部件(native widgets),但是它一直未能突破LCD问题。LCD问题导致了一些主要平台特征的遗失。如果你不明白的话(其实我也没明白),换句话说,如果平台A有窗口部件(widgets)1–40,而平台B有窗口部件(widgets)20–25,那么跨平台的AWT框架只能提供这两个窗口部件集的交集。
为解决这个问题,Sun又创建了一个新的框架。这个框架不再使用原生窗口部件,而是使用仿真窗口部件(emulated widgets)。这个方法虽然解决了LCD问题,并且提供了丰富的窗口部件集,但是它也带来了新的问题。例如,Swing应用程序的界面外观不再和原生应用程序的外观相似。 虽然在JVM中这些Swing应用程序已经得到了最大程度的性能改善,但是它们还是存在着其原生对应物所不具有的性能问题。并且,Swing应用程序消耗太多的内存,这样Swing不适于一些小设备,如PDA和移动电话等。
IBM进行了尝试以彻底解决AWT和Swing框架带来的上述问题。最终,IBM创建了一个新的GUI库,这就是SWT。SWT框架通过JNI来访问原生窗口部件。如果在宿主(host)平台上无法找到一个窗口部件,SWT就会自动地模拟它。
2. SWT应用程序的组成
一个SWT应用程序的基本组成部分为显示界面(Display)、命令界面(Shell,使命令进入并使运行初始化)和窗口部件(Widgets)。Display负责管理事件循环和控制UI线程和其他线程之间的通讯。Shell是应用程序中被操作系统窗口管理器管理的窗口。每个SWT应用程序至少需要一个Display和大于等于1个的Shell实例。
图1:从不同的角度看SWT应用程序
图1从不同的角度展示了SWT应用程序。左侧的图是一个简化的UI对象的继承图。中间的图展示了UI对象的容器结构(containment structure)。右侧的图则是创建后的UI外观。
如果一个应用程序使用了多个线程,那么每个线程都使用的是Display对象分配给它自己的实例。程序员可以使用静态方法Display.getCurent()来得到Display对象的当前活动的实例。
Shell用于在特定的操作系统中表现窗口。Shell可以最大化、最小化或正常化。Shell有两种类型。第1种是高层shell,它是Display的子窗口,同时它也是一个主窗口。第2类是对话shell,这种shell要依赖于其他的shell窗口存在。shell窗口最终成为上述那种类型,要看在创建shell时传递给shell构造函数的是什么风格位(style bits)。一个shell的默认值是DialogShell。也就是说,如果不带参数,那默认就是一个对话shell。而如果给参数赋予了一个Display对象,则该shell将是一个高层shell。
一些窗口部件的属性必须在创建它们的初期就要被设置。这些窗口部件的属性就是前面所说的风格位(style bits)。在SWT的类中,风格位被定义为常数。例如,Button button = new Button( shell, <styleBits> )。可以使用或(OR)操作符“|”来设置多个风格位。例如,如果想设置一个带边界的压下按钮,需要传递SWT.PUSH | SWT.BORDER作为风格位参数。
3. 进行SWT开发前的环境设置
为了进行SWT应用开发,你需要把SWT库添加到类路径(classpath)上,并设置必要的环境变量。
首先,你要在ECLIPSE_HOMEeclipsepluginsorg.eclipse.swt.win32_2.1.0wswin32目录下找到swt.jar库文件。注意这里的“org.eclipse.swt.win32_2.1.0”目录是和Eclipse的版本有关的。实在找不到你就用文件搜索功能吧。然后依次打开下面窗口Project->Properies->JavaBuildPath->Libraries->Add Variable -> Eclipse Home ->Extend将swt.jar文件加到类路径中。
接着,你肯定想编译这个SWT应用了。但是会出现下面所示的运行异常。出现这个异常的原因是swt.jar库使用的是原生库。你需要设置java.library.path环境变量来使用Java中的原生库。
控制台(Console )的输出如下:
| java.lang.UnsatisfiedLinkError: no swt-win32-2136 in java.library.path |
按下面的步骤设置java.library.path变量:依次打开Run-> Run...-> Java Applicaton-> "Project" ->Arguments -> VM Arguments。在“VM Arguments”中输入
| -Djava.library.path=c:eclipsepluginsorg.eclipse.swt.win32_2.1.0oswin32x86 |
注意要输入你自己的swt.jar所在的路径。
| 小技巧:加载原生库: |
2007-07-13 点击: 1519 | ||
| ||
? |
SWT Examples |
The following SWT examples can be downloaded from the eclipse download page:
- ControlExample
- CustomControlExample
- AddressBook
- BrowserExample
- ClipboardExample
- DNDExample (Drag and Drop)
- FileViewer
- GraphicsExample
- HelloWorld [1-5]
- HoverHelp
- ImageAnalyzer
- JavaViewer
- LayoutExample
- PaintExample
- TextEditor
- OLEExample (win32 only, OLE)
- OleWebBrowser (win32 only, OLE)
The ControlExample, CustomControlExample, LayoutExample, and PaintExample are also available as Eclipse plugins, and the following examples are only available as Eclipse plugins:
- BrowserDemo in the org.eclipse.swt.examples.browser.demos project
- WebBrowser in the org.eclipse.swt.examples.browser project
There are several ways to download and run the SWT examples:
- Run inside eclipse, either in an example view or from the SWT Example Launcher view.
- Launch as standalone applications from within eclipse.
- Run as standalone applications outside of eclipse.
- To get the examples, go to the eclipse download page: http://download.eclipse.org/eclipse/downloads/index.php
- Click on the eclipse build that you would like examples for (i.e. the eclipse build that you are running; typically the latest Stable Build).
- Scroll down until you see "Example Plug-ins". Read the paragraph on installing them, and select the download for your platform.
- After installing the examples, the source for them will be in a src.zip file in the appropriate subdirectory of: eclipse/plugins/org.eclipse.sdk.examples.source_<version>/src/
For example, the 3.1 source for the standalone examples in org.eclipse.swt.examples is in: org.eclipse.sdk.examples.source_3.1.0/src/org.eclipse.swt.examples_3.1.0/swtexamplessrc.zip.
The examples in package org.eclipse.swt.examples run standalone, and the examples in org.eclipse.swt.examples.* packages are eclipse plug-ins. - To run the plug-in SWT examples, restart eclipse, go to Window > Show View... > Other... and expand "SWT Examples".
Alternatively, you can use these (more detailed) steps from the eclipse ISV doc to download, install, and run the eclipse SWT Examples:
- Installing the examples
- Running the SWT examples as views or with the SWT Example Launcher
- Running the SWT standalone examples
- SWT Examples Overview
- Load SWT into your workspace.
- Load the SWT examples into your workspace by one of the methods listed above.
- Run a standalone example by selecting the main class (e.g. org.eclipse.swt.examples.controlexample.ControlExample) and selecting
Run > Run As > SWT Application (note: as of Eclipse 3.3 you should use Run > Run As > Java Application instead).
(Note that these instructions will only work for eclipse 3.1 and newer). To run the ControlExample and CustomControlExample standalone:
- Go to the download page for the eclipse version you are running. For example, for eclipse 3.3 this is:
http://download.eclipse.org/eclipse/downloads/drops/R-3.3-200706251500/index.php. - Scroll down to the "Example Plug-ins" section, download the examples zip for your platform, and unzip it to the same place that you originally unzipped eclipse. Let's say that you unzip both eclipse and its examples to d: (answer Yes when it asks if you want to overwrite the license files).
- (If you are using eclipse 3.3 or newer then you can skip this step) Open the file d:eclipsepluginsorg.eclipse.swt.win32.win32.x86_3.X.X.jar in winzip and extract the following 3 files into d:eclipseplugins
- swt-awt-win32-XXXX.dll
- swt-gdip-win32-XXXX.dll
- swt-win32-XXXX.dll
- Open a DOS window and type:
d:
cd d:eclipsepluginsorg.eclipse.swt.examples_3.X.X
- Type the following line: (if you are using eclipse 3.3 or newer then the
-Djava.library.path
argument can be omitted)
<pathToYourJRE>
binjava -classpath .;..org.eclipse.swt_3.X.X.jar;..org.eclipse.swt.win32.win32.x86_3.X.X.jar;.swtexamples.jar -Djava.library.path=.. org.eclipse.swt.examples.controlexample.ControlExample
This is the ControlExample. There is a tab for each control, and you can change the various styles, etc, to see what changes in the controls. To run the CustomControlExample simply use the class name CustomControlExample instead of ControlExample in step 5.