3.3 - New Time Zone Dialog#
In this section, we will create a dialog and add it to our clock application.

You will learn:
Creating a Panel#
A Panel is a special kind of window. Read the Cocoa document for more details: Windows and Panels.
Since I have a clock already, I want to know the time in different time
zones. My idea is that when I click the title of the NSBox, a panel will
show up and ask the time zone. Once the time zone is inputted, it will
display the time in that area. Since NSPanel is a subclass of NSWindow,
the usage of NSPanel is similar to NSWindow. Again, I need a
“controller” to control the “view”, which is NSPanel in this case.
The “view” is generated by Gorm, and I need to write the “controller” by
myself. In this example, I’ll show how to load the gorm file. There are
many built-in panel in GNUstep. I also use one in this example.
First, we need to build the interface for the panel. Open Gorm, and choose the menu item “Document → New Module → New Empty”. Look at the palettes. There is one for “Panel”.
Figure 4-22. Panel in Gorm

Drag the Panel out of Palettes. Build the interface as below.
Figure 4-23. Interface of time zone panel

You can change the size of panel in the inspector. Here are the attributes of the panel.
Figure 4-24. Panel attributes

Controlling the panel#
Now, I have the “view”. Then where is the “controller”? Generally, you can
write a new class as the controller of this view, but this is a small
program, so you don’t have to write a class just for the controller.
So I decide to use the class TimeView as the controller
for this panel. So TimeView acts as the custom view for that
main window interface, and as the controller for the panel. Since
TimeView is the controller of this panel, I need to connect the
outlets and actions. Therefore, I need to create the class TimeView
again in this gorm file, even though there is already one in
TimeMachine.gorm file.
Editor’s note
This seems like a bad idea, but changing it would require a lot of refactoring of this tutorial.
You already know how to create the class TimeView. I add two outlets,
zonePanel and zoneField, and two actions, okAction: and
cancelAction:.
Figure 4-25. Outlets for time zone panel

Figure 4-26. Actions for time zone panel

But rather than creating an instance to connect to the panel, let’s set the
owner of this panel to our TimeView. This reduces the amount of instances in our project.
Select the NSOwner in Gorm main window, then select class TimeView in
the Attributes tab of the inspector.
Figure 4-27. Set NSOwner to TimeView class


By this way, I can connect the panel to the NSOwner, which is an
instance of class TimeView. Connect the two buttons to the actions in
NSOwner, the outlet zoneField to the NSTextField in the panel, and the
outlet zonePanel to the panel. Pay attention to how the NSOwner
connects to the panel.
Figure 4-28. Connect outlet


Save this interface as TimeZonePanel.gorm, and quit Gorm. Don’t
generate the files for class TimeView because I already have the
files. GNUstep can figure out where the classes are and where the
outlets/actions are.
The code#
Now, I need to add the new outlets and actions into the files of
TimeView. Here is the header.
TimeView.h:
#import <AppKit/AppKit.h>
#import "ClockView.h"
@interface TimeView : NSControl
{
id zonePanel;
id zoneField;
NSBox* box;
NSTextField *labelDate, *labelTime;
NSTextField *localDate, *localTime;
NSCalendarDate* date;
ClockView* clockView;
}
- (NSCalendarDate *) date;
- (void) setDate: (NSCalendarDate *) date;
- (void) okAction: (id) sender;
- (void) cancelAction: (id) sender;
@end
I add the outlets and actions by myself.
TimeView.m:
- (void) mouseDown: (NSEvent *) event {
NSRect titleFrame = [box titleRect];
NSPoint windowLocation = [event locationInWindow];
NSPoint viewLocation = [self convertPoint: windowLocation fromView: [self superview]];
BOOL status = NSMouseInRect(viewLocation, titleFrame, NO);
if (status == YES) {
[NSBundle loadNibNamed: @"TimeZonePanel.gorm" owner: self];
[NSApp runModalForWindow: self->zonePanel];
}
}
The method -mouseDown: is called when mouse is clicked within this view.
Here, we calculate whether the mouse is clicked in the area of the title
of the NSBox. If so, we use [NSBundle loadNibNamed: owner:] to load the
window, and [NSApp runModalForWindow] to display it. Read Cocoa’s
document about “How Modal Windows
Work”.
Now, I just need to finish the actions part in TimeView.m.
TimeView.m:
- (void) cancelAction: (id) sender
{
[NSApp abortModal];
[self->zonePanel close];
}
- (void) okAction: (id) sender
{
NSTimeZone* tempZone;
tempZone = [NSTimeZone timeZoneWithName: [zoneField stringValue]];
[NSApp stopModal];
[zonePanel close];
if (tempZone == nil) {
NSRunAlertPanel(@"Warning!",
@"Wrong Time Zone !!",
@"OK", nil, nil);
} else {
[date setTimeZone: tempZone];
[box setTitle: [tempZone description]];
[self setDate: date];
}
}
In method -okAction:, I use a built-in panel, NSRunAlertPanel. There are
several built-in panels in GNUstep ready to use. Now, you can display
the current time in different time zone.
Better keyboard access#
It is inconvenient to use this pop-up panel because you have to click
the NSTextField before typing. Sometimes, it is more convenient to
control the user interface via keyboard rather than mouse.
When a window pop-up, it is the “First Responder” – the first object
to receive events and keypresses. But usually we want some
other objects in this window to receive the keypresses. Therefore, we need
to change the “first responder” of this window by
using [NSWindow makeFirstResponder:].
When I want to use Tab key to switch between different views in the
window, you need to assign the nextKeyView to the next view when the Tab
key is pressed so that the Application Kit knows where the responder should
be.
Finally, when I finish typing in the NSTextField, I want to hit the
Return key to click so that I
don’t need to move my hand out of the keyboard. In this case, since
NSTextField is also a subclass of NSControl, I can set the target and
action of NSTextField to be the same as the NSButton . Therefore, when I hit Return, it is equivalent to clicking on the button.
These are small tune-ups for the application, but it makes it easier to use the application.
First, let’s set the “first responder” of the window to the NSTextField:
TimeView.m:
- (void) mouseDown: (NSEvent *) event
{
NSRect titleFrame = [self->box titleRect];
NSPoint windowLocation = [event locationInWindow];
NSPoint viewLocation = [self convertPoint: windowLocation fromView: [self superview]];
BOOL status = NSMouseInRect(viewLocation, titleFrame, NO);
if (status == YES) {
[NSBundle loadNibNamed: @"TimeZonePanel.gorm" owner: self];
[self->zonePanel makeFirstResponder: zoneField];
[NSApp runModalForWindow: self->zonePanel];
}
}
Only one line is enough. Now, when this panel shows up, the cursor will
automatically be in the NSTextField.
Second, let’s set the target and action of NSTextField to be the same as the NSButton . Open the TimeZonePanel.gorm, and connect the NSTextField to the method -okAction: of the NSOwner. That’s it. Whenever you hit the Return key in the NSTextField, the method -okAction: is
called.
Figure 4-29. Connection NSTextField action



Third, we need to connect the nextKeyView outlet between the views in
the window. I’ll connect the nextKeyView of NSTextField to the NSButton , the nextKeyView outlet of the NSButton to the NSButton ,
and the nextKeyView outlet of the NSButton to the NSTextField. By
doing that, I can switch between these views using the Tab key. Here, I just
show how the nextKeyView of NSTextField connects to the NSButton .
Figure 4-30. Connect nextKeyView


Source code: Panel-src.tar.gz.