summaryrefslogtreecommitdiffstats
path: root/macosx/tkMacOSXKeyEvent.c
diff options
context:
space:
mode:
authorKevin Walzer <kw@codebykevin.com>2012-01-29 16:36:28 (GMT)
committerKevin Walzer <kw@codebykevin.com>2012-01-29 16:36:28 (GMT)
commit083df11eea2599a5abdb6a213faa2991a5e6ada1 (patch)
tree0928dd1c5453cdab0439e19104a4b93f54e59d57 /macosx/tkMacOSXKeyEvent.c
parentee98c5f67ad4c1b52997428b3af01c4282cd9bfa (diff)
downloadtk-083df11eea2599a5abdb6a213faa2991a5e6ada1.zip
tk-083df11eea2599a5abdb6a213faa2991a5e6ada1.tar.gz
tk-083df11eea2599a5abdb6a213faa2991a5e6ada1.tar.bz2
Fix for serious bugs with input methods, and for display of certain fonts in buttons; thanks to Adrian Robert for extensive patches
Diffstat (limited to 'macosx/tkMacOSXKeyEvent.c')
-rw-r--r--macosx/tkMacOSXKeyEvent.c573
1 files changed, 470 insertions, 103 deletions
diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c
index b3b016e..1d24960 100644
--- a/macosx/tkMacOSXKeyEvent.c
+++ b/macosx/tkMacOSXKeyEvent.c
@@ -6,6 +6,7 @@
*
* Copyright 2001-2009, Apple Inc.
* Copyright (c) 2006-2009 Daniel A. Steffen <das@users.sourceforge.net>
+ * Copyright (c) 2012 Adrian Robert.
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -19,6 +20,8 @@
#define TK_MAC_DEBUG_KEYBOARD
#endif
*/
+#define NS_KEYLOG 0
+
static Tk_Window grabWinPtr = NULL;
/* Current grab window, NULL if no grab. */
@@ -26,9 +29,19 @@ static Tk_Window keyboardGrabWinPtr = NULL;
/* Current keyboard grab window. */
static NSModalSession modalSession = NULL;
+static BOOL processingCompose = NO;
+static BOOL finishedCompose = NO;
+
+static int caret_x = 0, caret_y = 0, caret_height = 0;
+
+static void setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state);
+static unsigned isFunctionKey(unsigned int code);
+
+
#pragma mark TKApplication(TKKeyEvent)
@implementation TKApplication(TKKeyEvent)
+
- (NSEvent *) tkProcessKeyEvent: (NSEvent *) theEvent
{
#ifdef TK_MAC_DEBUG_EVENTS
@@ -36,24 +49,38 @@ static NSModalSession modalSession = NULL;
#endif
NSWindow* w;
NSEventType type = [theEvent type];
- NSUInteger modifiers, len;
+ NSUInteger modifiers, len = 0;
BOOL repeat = NO;
unsigned short keyCode;
NSString *characters = nil, *charactersIgnoringModifiers = nil;
static NSUInteger savedModifiers = 0;
+ static NSMutableArray *nsEvArray;
+
+ if (nsEvArray == nil)
+ {
+ nsEvArray = [[NSMutableArray alloc] initWithCapacity: 1];
+ processingCompose = NO;
+ }
switch (type) {
case NSKeyUp:
+ if (finishedCompose)
+ {
+ // if we were composing, swallow the last release since we already sent
+ finishedCompose = NO;
+ return theEvent;
+ }
case NSKeyDown:
repeat = [theEvent isARepeat];
characters = [theEvent characters];
charactersIgnoringModifiers = [theEvent charactersIgnoringModifiers];
+ len = [charactersIgnoringModifiers length];
case NSFlagsChanged:
modifiers = [theEvent modifierFlags];
keyCode = [theEvent keyCode];
w = [self windowWithWindowNumber:[theEvent windowNumber]];
-#ifdef TK_MAC_DEBUG_EVENTS
- TKLog(@"-[%@(%p) %s] %d %u %@ %@ %u %@", [self class], self, _cmd, repeat, modifiers, characters, charactersIgnoringModifiers, keyCode, w);
+#if defined(TK_MAC_DEBUG_EVENTS) || NS_KEYLOG == 1
+ NSLog(@"-[%@(%p) %s] r=%d mods=%u '%@' '%@' code=%u c=%d %@ %d", [self class], self, _cmd, repeat, modifiers, characters, charactersIgnoringModifiers, keyCode,([charactersIgnoringModifiers length] == 0) ? 0 : [charactersIgnoringModifiers characterAtIndex: 0], w, type);
#endif
break;
@@ -61,122 +88,353 @@ static NSModalSession modalSession = NULL;
return theEvent;
}
- unsigned int state = 0;
+ if (!processingCompose) {
+ unsigned int state = 0;
- if (modifiers & NSAlphaShiftKeyMask) {
- state |= LockMask;
- }
- if (modifiers & NSShiftKeyMask) {
- state |= ShiftMask;
- }
- if (modifiers & NSControlKeyMask) {
- state |= ControlMask;
- }
- if (modifiers & NSCommandKeyMask) {
- state |= Mod1Mask; /* command key */
- }
- if (modifiers & NSAlternateKeyMask) {
- state |= Mod2Mask; /* option key */
- }
- if (modifiers & NSNumericPadKeyMask) {
- state |= Mod3Mask;
- }
- if (modifiers & NSFunctionKeyMask) {
- state |= Mod4Mask;
- }
+ if (modifiers & NSAlphaShiftKeyMask) {
+ state |= LockMask;
+ }
+ if (modifiers & NSShiftKeyMask) {
+ state |= ShiftMask;
+ }
+ if (modifiers & NSControlKeyMask) {
+ state |= ControlMask;
+ }
+ if (modifiers & NSCommandKeyMask) {
+ state |= Mod1Mask; /* command key */
+ }
+ if (modifiers & NSAlternateKeyMask) {
+ state |= Mod2Mask; /* option key */
+ }
+ if (modifiers & NSNumericPadKeyMask) {
+ state |= Mod3Mask;
+ }
+ if (modifiers & NSFunctionKeyMask) {
+ state |= Mod4Mask;
+ }
- /*
- * The focus must be in the FrontWindow on the Macintosh. We then query Tk
- * to determine the exact Tk window that owns the focus.
- */
+ /*
+ * The focus must be in the FrontWindow on the Macintosh. We then query Tk
+ * to determine the exact Tk window that owns the focus.
+ */
- TkWindow *winPtr = TkMacOSXGetTkWindow(w);
- Tk_Window tkwin = (Tk_Window) winPtr;
+ TkWindow *winPtr = TkMacOSXGetTkWindow(w);
+ Tk_Window tkwin = (Tk_Window) winPtr;
- if (!tkwin) {
- TkMacOSXDbgMsg("tkwin == NULL");
- return theEvent;
- }
- tkwin = (Tk_Window) winPtr->dispPtr->focusPtr;
- if (!tkwin) {
- TkMacOSXDbgMsg("tkwin == NULL");
- return theEvent;
+ if (!tkwin) {
+ TkMacOSXDbgMsg("tkwin == NULL");
+ return theEvent;
+ }
+ tkwin = (Tk_Window) winPtr->dispPtr->focusPtr;
+ if (!tkwin) {
+ TkMacOSXDbgMsg("tkwin == NULL");
+ return theEvent;
+ }
+
+ /*
+ * If it's a function key, or we have modifiers other than Shift or Alt,
+ * pass it straight to Tk. Otherwise we'll send for input processing.
+ */
+ int code = (len == 0) ?
+ 0 : [charactersIgnoringModifiers characterAtIndex: 0];
+ if (type != NSKeyDown || isFunctionKey(code)
+ || (len > 0 && state & (ControlMask | Mod1Mask | Mod3Mask | Mod4Mask))) {
+
+ XEvent xEvent;
+ setupXEvent(&xEvent, w, state);
+
+ if (type == NSFlagsChanged) {
+ if (savedModifiers > modifiers) {
+ xEvent.xany.type = KeyRelease;
+ } else {
+ xEvent.xany.type = KeyPress;
+ }
+
+ /*
+ * Use special '-1' to signify a special keycode to our platform
+ * specific code in tkMacOSXKeyboard.c. This is rather like what
+ * happens on Windows.
+ */
+
+ xEvent.xany.send_event = -1;
+
+ /*
+ * Set keycode (which was zero) to the changed modifier
+ */
+
+ xEvent.xkey.keycode = (modifiers ^ savedModifiers);
+ } else {
+ if (type == NSKeyUp || repeat) {
+ xEvent.xany.type = KeyRelease;
+ } else {
+ xEvent.xany.type = KeyPress;
+ }
+
+ /* For command key, take input manager's word so things
+ like dvorak / qwerty layout work. */
+ if ((modifiers & NSCommandKeyMask) == NSCommandKeyMask
+ && (modifiers & NSAlternateKeyMask) != NSAlternateKeyMask
+ && len > 0 && !isFunctionKey(code)) {
+ // head off keycode-based translation in tkMacOSXKeyboard.c
+ xEvent.xkey.nbytes = [characters length]; //len
+ }
+
+ if ([characters length] > 0) {
+ xEvent.xkey.keycode =
+ (keyCode << 16) | (UInt16) [characters characterAtIndex:0];
+ if (![characters getCString:xEvent.xkey.trans_chars
+ maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) {
+ /* prevent SF bug 2907388 (crash on some composite chars) */
+ //PENDING: we might not need this anymore
+ TkMacOSXDbgMsg("characters too long");
+ return theEvent;
+ }
+ }
+
+ if (repeat) {
+ Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
+ xEvent.xany.type = KeyPress;
+ xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
+ }
+ }
+ Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
+ savedModifiers = modifiers;
+ return theEvent;
+ } /* if send straight to TK */
+
+ } /* if not processing compose */
+
+ if (type == NSKeyDown) {
+ if (NS_KEYLOG)
+ fprintf (stderr, "keyDown: %s compose sequence.\n",
+ processingCompose == YES ? "Continue" : "Begin");
+ processingCompose = YES;
+ [nsEvArray addObject: theEvent];
+ [[w contentView] interpretKeyEvents: nsEvArray];
+ [nsEvArray removeObject: theEvent];
+ }
+
+ savedModifiers = modifiers;
+
+ return theEvent;
+}
+@end
+
+
+
+@implementation TKContentView(TKKeyEvent)
+/* <NSTextInput> implementation (called through interpretKeyEvents:]). */
+
+/* <NSTextInput>: called when done composing;
+ NOTE: also called when we delete over working text, followed immed.
+ by doCommandBySelector: deleteBackward: */
+- (void)insertText: (id)aString
+{
+ int i, len = [(NSString *)aString length];
+ XEvent xEvent;
+ TkWindow *winPtr = TkMacOSXGetTkWindow([self window]);
+ Tk_Window tkwin = (Tk_Window) winPtr;
+
+ if (NS_KEYLOG)
+ NSLog (@"insertText '%@'\tlen = %d", aString, len);
+ processingCompose = NO;
+ finishedCompose = YES;
+
+ /* first, clear any working text */
+ if (_workingText != nil)
+ [self deleteWorkingText];
+
+ /* now insert the string as keystrokes */
+ setupXEvent(&xEvent, [self window], 0);
+ xEvent.xany.type = KeyPress;
+
+ for (i =0; i<len; i++)
+ {
+ xEvent.xkey.keycode = (UInt16) [aString characterAtIndex: i];
+ [[aString substringWithRange: NSMakeRange(i,1)]
+ getCString: xEvent.xkey.trans_chars
+ maxLength: XMaxTransChars encoding: NSUTF8StringEncoding];
+ xEvent.xkey.nbytes = strlen(xEvent.xkey.trans_chars);
+ xEvent.xany.type = KeyPress;
+ Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
+
+ xEvent.xany.type = KeyRelease;
+ xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
+ Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
+ xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
}
+}
- XEvent xEvent;
-
- memset(&xEvent, 0, sizeof(XEvent));
- xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
- xEvent.xany.send_event = false;
- xEvent.xany.display = Tk_Display(tkwin);
- xEvent.xany.window = Tk_WindowId(tkwin);
-
- xEvent.xkey.root = XRootWindow(Tk_Display(tkwin), 0);
- xEvent.xkey.subwindow = None;
- xEvent.xkey.time = TkpGetMS();
- xEvent.xkey.state = state;
- xEvent.xkey.same_screen = true;
- xEvent.xkey.trans_chars[0] = 0;
- xEvent.xkey.nbytes = 0;
-
- if (type == NSFlagsChanged) {
- if (savedModifiers > modifiers) {
- xEvent.xany.type = KeyRelease;
- } else {
- xEvent.xany.type = KeyPress;
- }
- /*
- * Use special '-1' to signify a special keycode to our platform
- * specific code in tkMacOSXKeyboard.c. This is rather like what
- * happens on Windows.
- */
+/* <NSTextInput>: inserts display of composing characters */
+- (void)setMarkedText: (id)aString selectedRange: (NSRange)selRange
+{
+ NSString *str = [aString respondsToSelector: @selector (string)] ?
+ [aString string] : aString;
+ if (NS_KEYLOG)
+ NSLog (@"setMarkedText '%@' len =%d range %d from %d", str, [str length],
+ selRange.length, selRange.location);
- xEvent.xany.send_event = -1;
+ if (_workingText != nil)
+ [self deleteWorkingText];
+ if ([str length] == 0)
+ return;
- /*
- * Set keycode (which was zero) to the changed modifier
- */
+ processingCompose = YES;
+ _workingText = [str copy];
- xEvent.xkey.keycode = (modifiers ^ savedModifiers);
- } else {
- if (type == NSKeyUp || repeat) {
- xEvent.xany.type = KeyRelease;
- } else {
- xEvent.xany.type = KeyPress;
- }
+ //PENDING: insert workingText underlined
+}
-/* prevent SF bug 2907388 here (crash on composite characters) */
-if ([characters length] > 0) {
- xEvent.xkey.keycode = (keyCode << 16) | (UInt16)
- [characters characterAtIndex:0];
- if (![characters getCString:xEvent.xkey.trans_chars
- maxLength:XMaxTransChars encoding:NSUTF8StringEncoding]) {
- TkMacOSXDbgMsg("characters too long");
- return theEvent;
- }
+
+/* delete display of composing characters [not in <NSTextInput>] */
+- (void)deleteWorkingText
+{
+ if (_workingText == nil)
+ return;
+ if (NS_KEYLOG)
+ NSLog(@"deleteWorkingText len = %d\n", [_workingText length]);
+ [_workingText release];
+ _workingText = nil;
+ processingCompose = NO;
+
+ //PENDING: delete working text
}
-/* end workaround */
- len = [charactersIgnoringModifiers length];
- if (len) {
- xEvent.xkey.nbytes = [charactersIgnoringModifiers characterAtIndex:0];
- if (len > 1) {
- TkMacOSXDbgMsg("more than one charactersIgnoringModifiers");
- }
- }
- if (repeat) {
- Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
- xEvent.xany.type = KeyPress;
- xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
- }
+
+- (BOOL)hasMarkedText
+{
+ return _workingText != nil;
+}
+
+
+- (NSRange)markedRange
+{
+ NSRange rng = _workingText != nil
+ ? NSMakeRange (0, [_workingText length]) : NSMakeRange (NSNotFound, 0);
+ if (NS_KEYLOG)
+ NSLog (@"markedRange request");
+ return rng;
+}
+
+
+- (void)unmarkText
+{
+ if (NS_KEYLOG)
+ NSLog (@"unmark (accept) text");
+ [self deleteWorkingText];
+ processingCompose = NO;
+}
+
+
+/* used to position char selection windows, etc. */
+- (NSRect)firstRectForCharacterRange: (NSRange)theRange
+{
+ NSRect rect;
+ NSPoint pt;
+
+ pt.x = caret_x;
+ pt.y = caret_y;
+
+ pt = [self convertPoint: pt toView: nil];
+ pt = [[self window] convertBaseToScreen: pt];
+ pt.y -= caret_height;
+
+ rect.origin = pt;
+ rect.size.width = caret_height;
+ rect.size.height = caret_height;
+ return rect;
+}
+
+
+- (NSInteger)conversationIdentifier
+{
+ return (NSInteger)self;
+}
+
+
+- (void)doCommandBySelector: (SEL)aSelector
+{
+ if (NS_KEYLOG)
+ NSLog (@"doCommandBySelector: %@", NSStringFromSelector (aSelector));
+ processingCompose = NO;
+ if (aSelector == @selector (deleteBackward:))
+ {
+ /* happens when user backspaces over an ongoing composition:
+ throw a 'delete' into the event queue */
+ XEvent xEvent;
+ setupXEvent(&xEvent, [self window], 0);
+ xEvent.xany.type = KeyPress;
+ xEvent.xkey.nbytes = 1;
+ xEvent.xkey.keycode = (0x33 << 16) | 0x7F;
+ xEvent.xkey.trans_chars[0] = 0x7F;
+ xEvent.xkey.trans_chars[1] = 0x0;
+ Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
}
- Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
- savedModifiers = modifiers;
+}
- return theEvent;
+
+- (NSArray *)validAttributesForMarkedText
+{
+ static NSArray *arr = nil;
+ if (arr == nil) arr = [NSArray new];
+ /* [[NSArray arrayWithObject: NSUnderlineStyleAttributeName] retain]; */
+ return arr;
}
+
+
+- (NSRange)selectedRange
+{
+ if (NS_KEYLOG)
+ NSLog (@"selectedRange request");
+ return NSMakeRange (NSNotFound, 0);
+}
+
+
+- (NSUInteger)characterIndexForPoint: (NSPoint)thePoint
+{
+ if (NS_KEYLOG)
+ NSLog (@"characterIndexForPoint request");
+ return 0;
+}
+
+
+- (NSAttributedString *)attributedSubstringFromRange: (NSRange)theRange
+{
+ static NSAttributedString *str = nil;
+ if (str == nil) str = [NSAttributedString new];
+ if (NS_KEYLOG)
+ NSLog (@"attributedSubstringFromRange request");
+ return str;
+}
+/* End <NSTextInput> impl. */
@end
+
+
+
+/*
+ * Set up basic fields in xevent for keyboard input.
+ */
+static void
+setupXEvent(XEvent *xEvent, NSWindow *w, unsigned int state)
+{
+ TkWindow *winPtr = TkMacOSXGetTkWindow(w);
+ Tk_Window tkwin = (Tk_Window) winPtr;
+
+ memset(xEvent, 0, sizeof(XEvent));
+ xEvent->xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
+ xEvent->xany.send_event = false;
+ xEvent->xany.display = Tk_Display(tkwin);
+ xEvent->xany.window = Tk_WindowId(tkwin);
+
+ xEvent->xkey.root = XRootWindow(Tk_Display(tkwin), 0);
+ xEvent->xkey.subwindow = None;
+ xEvent->xkey.time = TkpGetMS();
+ xEvent->xkey.state = state;
+ xEvent->xkey.same_screen = true;
+ xEvent->xkey.trans_chars[0] = 0;
+ xEvent->xkey.nbytes = 0;
+}
#pragma mark -
@@ -343,9 +601,118 @@ Tk_SetCaretPos(
int x,
int y,
int height)
-{
+ {
+ TkCaret *caretPtr = &(((TkWindow *) tkwin)->dispPtr->caret);
+
+ /*
+ * Prevent processing anything if the values haven't changed. Windows only
+ * has one display, so we can do this with statics.
+ */
+
+ if ((caretPtr->winPtr == ((TkWindow *) tkwin))
+ && (caretPtr->x == x) && (caretPtr->y == y)) {
+ return;
+ }
+
+ caretPtr->winPtr = ((TkWindow *) tkwin);
+ caretPtr->x = x;
+ caretPtr->y = y;
+ caretPtr->height = height;
+
+ /*
+ * As in Windows, adjust to the toplevel to get the coords right.
+ */
+
+ while (!Tk_IsTopLevel(tkwin)) {
+ x += Tk_X(tkwin);
+ y += Tk_Y(tkwin);
+ tkwin = Tk_Parent(tkwin);
+ if (tkwin == NULL) {
+ return;
+ }
+ }
+
+ /* But adjust for fact that NS uses flipped view. */
+ y = Tk_Height(tkwin) - y;
+
+ caret_x = x;
+ caret_y = y;
+ caret_height = height;
}
+
+static unsigned convert_ns_to_X_keysym[] =
+{
+ NSHomeFunctionKey, 0x50,
+ NSLeftArrowFunctionKey, 0x51,
+ NSUpArrowFunctionKey, 0x52,
+ NSRightArrowFunctionKey, 0x53,
+ NSDownArrowFunctionKey, 0x54,
+ NSPageUpFunctionKey, 0x55,
+ NSPageDownFunctionKey, 0x56,
+ NSEndFunctionKey, 0x57,
+ NSBeginFunctionKey, 0x58,
+ NSSelectFunctionKey, 0x60,
+ NSPrintFunctionKey, 0x61,
+ NSExecuteFunctionKey, 0x62,
+ NSInsertFunctionKey, 0x63,
+ NSUndoFunctionKey, 0x65,
+ NSRedoFunctionKey, 0x66,
+ NSMenuFunctionKey, 0x67,
+ NSFindFunctionKey, 0x68,
+ NSHelpFunctionKey, 0x6A,
+ NSBreakFunctionKey, 0x6B,
+
+ NSF1FunctionKey, 0xBE,
+ NSF2FunctionKey, 0xBF,
+ NSF3FunctionKey, 0xC0,
+ NSF4FunctionKey, 0xC1,
+ NSF5FunctionKey, 0xC2,
+ NSF6FunctionKey, 0xC3,
+ NSF7FunctionKey, 0xC4,
+ NSF8FunctionKey, 0xC5,
+ NSF9FunctionKey, 0xC6,
+ NSF10FunctionKey, 0xC7,
+ NSF11FunctionKey, 0xC8,
+ NSF12FunctionKey, 0xC9,
+ NSF13FunctionKey, 0xCA,
+ NSF14FunctionKey, 0xCB,
+ NSF15FunctionKey, 0xCC,
+ NSF16FunctionKey, 0xCD,
+ NSF17FunctionKey, 0xCE,
+ NSF18FunctionKey, 0xCF,
+ NSF19FunctionKey, 0xD0,
+ NSF20FunctionKey, 0xD1,
+ NSF21FunctionKey, 0xD2,
+ NSF22FunctionKey, 0xD3,
+ NSF23FunctionKey, 0xD4,
+ NSF24FunctionKey, 0xD5,
+
+ NSBackspaceCharacter, 0x08, /* 8: Not on some KBs. */
+ NSDeleteCharacter, 0xFF, /* 127: Big 'delete' key upper right. */
+ NSDeleteFunctionKey, 0x9F, /* 63272: Del forw key off main array. */
+
+ NSTabCharacter, 0x09,
+ 0x19, 0x09, /* left tab->regular since pass shift */
+ NSCarriageReturnCharacter, 0x0D,
+ NSNewlineCharacter, 0x0D,
+ NSEnterCharacter, 0x8D,
+
+ 0x1B, 0x1B /* escape */
+};
+
+
+static unsigned isFunctionKey(unsigned code)
+{
+ const unsigned last_keysym = (sizeof (convert_ns_to_X_keysym)
+ / sizeof (convert_ns_to_X_keysym[0]));
+ unsigned keysym;
+ for (keysym = 0; keysym < last_keysym; keysym += 2)
+ if (code == convert_ns_to_X_keysym[keysym])
+ return 0xFF00 | convert_ns_to_X_keysym[keysym+1];
+ return 0;
+ }
+
/*
* Local Variables:
* mode: objc