Listing Methods at Runtime in Objective C

Before getting into creating my next Rapid Weaver plugin, I needed to do some experimentation to make sure what I wanted to achieve was possible. Wanting to manipulate some aspects of RWPage objects, I first required a list of methods for the RWPage class.

Thanks to Matt Gallagher’s IMP of the current method entry, I was able to generate my method list with the following code:

#import "objc/objc-class.h"
 
// Iterate over the class and all superclasses
Class currentClass = [RWPage class];
while (currentClass) {
    // Iterate over all instance methods for this class
    unsigned int methodCount;
    Method *methodList = class_copyMethodList(currentClass, &methodCount);
    unsigned int i = 0;
    for (; i < methodCount; i++) {
        NSLog(@"%@ - %@", [NSString stringWithCString:class_getName(currentClass) encoding:NSUTF8StringEncoding], [NSString stringWithCString:sel_getName(method_getName(methodList[i])) encoding:NSUTF8StringEncoding]);
    }
 
    free(methodList);
    currentClass = class_getSuperclass(currentClass);
}

Method list acquired, the process of whittling it down until I found the one I wanted began.

Comments (5) | Trackback

Sliding Modal Dialogs in Cocoa Applications & Bundles

Example project available on GitHub – CIModalSheetDialog

One thing I wanted to be able to use during the development of Slider 2 was modal dialogs that slide out from the top of Rapid Weaver. This was troublesome for me as Slider 2 is a bundle that runs within Rapid Weaver – not as a standalone application.

Googling and experimentation allowed me to find a solution for problem that I find acceptable. Below is my implementation of a modal sheet dialog, further down one may find some usage examples.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
//  SModalSheetDialogs.h
//
//  Created by Michael Robinson on 7/08/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
#import <Foundation/Foundation.h>
 
@interface SModalSheetDialogs : NSObject
 
+ (void) showModalMessage:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style fromSender:(id)sender;
 
+ (void) showModalRequest:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style andButtons:(NSArray *)buttons 
           andContextInfo:(void *)contextInfo fromSender:(id)sender;
 
@end

And the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//
//  SModalSheetDialogs.m
//{filelink=1}
//  Created by Michael Robinson on 7/08/11.
//  Copyright 2011 Code of Interest. All rights reserved.
//
 
#import "SModalSheetDialogs.h"
 
@implementation SModalSheetDialogs
 
+ (void) showModalMessage:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style fromSender:(id)sender {
 
    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 
    [alert addButtonWithTitle:@"OK"];
 
    [alert setInformativeText:informativeText];
    [alert setMessageText:message];
 
    [alert setAlertStyle:style];
 
    [alert beginSheetModalForWindow:[NSApp mainWindow] 
                      modalDelegate:sender 
                     didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                        contextInfo:nil];
}
 
+ (void) showModalRequest:(NSString *)message withInformativeText:(NSString *)informativeText 
                withStyle:(NSAlertStyle)style andButtons:(NSArray *)buttons 
           andContextInfo:(void *)contextInfo fromSender:(id)sender {
 
    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
 
    NSEnumerator *e = [buttons objectEnumerator];
    id object;
    while (object = [e nextObject]) {
        [alert addButtonWithTitle:(NSString*)object];
    }
 
    [alert setInformativeText:informativeText];
    [alert setMessageText:message];
 
    [alert setAlertStyle:style];
 
    [alert beginSheetModalForWindow:[NSApp mainWindow] 
                      modalDelegate:sender 
                     didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                        contextInfo:contextInfo];
 
}
 
@end

Showing a dialog is simple, example below.

1
2
3
4
[CIModalSheetDialog showModalMessage:@"Dialog title" 
                     withInformativeText:@"Dialog content" 
                               withStyle:NSInformationalAlertStyle 
                              fromSender:self];

This is fine for cases where we’re just informing the user that something happened – in these cases we don’t care what the user does, as the only option they have is to acknowledge the message. In more complex cases, where the user is presented with a choice, we need to have a way to catch that choice.

As the object handling the user’s choice may differ across use cases, we set the sender (as above). It is expected that this sender implements the following delegate method:

- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo

An example, first of showing a more complex dialog, with two buttons:

1
2
3
4
5
6
[CIModalSheetDialog showModalRequest:@"Dialog title" 
                     withInformativeText:@"Dialog content" 
                               withStyle:NSInformationalAlertStyle
                              andButtons:[NSArray arrayWithObjects:@"OK", @"Cancel", nil]
                          andContextInfo:@"Some identifying information"
                              fromSender:self];

And our implentation of alertDidEnd:

1
2
3
4
5
6
7
8
9
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == @"Some identifying information") {
        if (returnCode == NSAlertSecondButtonReturn) { // OK button was clicked
            // Handle OK
        } else {
            // Handle Cancel
        }
    }
}
No comments | Trackback

Lesson #1: InvalidDnDOperationException Ruins Sanity

A friend with whom I am currently working on a programming contract called me today to tell me that the latest build wasn’t working. At all.

This irritated and confused me, as I hadn’t worked on that particular project for a week or so, and couldn’t remember having any issues with it.

I booted into Windows (I use Linux mostly), downloaded Eclipse and imported the project.

Sure enough:

java.awt.dnd.InvalidDnDOperationException: Drag and drop in progress
	at sun.awt.dnd.SunDragSourceContextPeer.setDragDropInProgress(Unknown Source)
	at java.awt.dnd.DragSource.startDrag(Unknown Source)

This error had attacked me before, but a quick investigation revealed that the issue was different this time. Joyous news.

I poked about in the (too many) classes that make up this project, but discovered nothing of use.

After some fruitless Googling, it occurred to me that during the great Refactoring Storm of August, I may have changed some of the DragGestureRecognizers’ DnDConstants to ACTION_COPY instead of ACTION_COPY_OR_MOVE.

A short, smaller storm of refactoring esued, during which all ACTION_COPY_OR_MOVE DnDConstants were changed to ACTION_COPY.

Why was this an issue at all? I was using evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE); to accept the drops, which I guess I must have assumed would accept an ACTION_COPY drop. Seemed to in Ubuntu, anyway. Maybe the Windows JRE is more strict (re: mental)?

Why this issue hid itself until now, I don’t know. Perhaps Ubuntu just likes me more.

Lesson: test software on the target system BEFORE giving it to your team-members.

No comments | Trackback

Write Your Own Listener Interface (you know you want to)

This tutorial was all I needed to quickly implement my listener: Listeners in Java

It’s clear, succinct and comprehensive. When you need to implement your custom listener interface, check it out.

Comments (3) | Trackback

Java: repaint() Waking Nightmare

I wanted the little app I’m making to display a green tick or a red cross depending on whether the user had entered valid credentials. Simple? Yeah. Everything went really well, I extended a JPanel to handle the tick/cross display area (tickCross), got the POST code going, strung together the rest of the GUI elements.

The problem was that tickCross.repaint() wasn’t reliably repainting. about 30% of the time the image wouldn’t be painted. The method was being called, but nothing was happening. I tried Google, re-read the related Sun Java docs and was reminded that: “repaint() does not actually paint. It calls the peer repaint which enqueues a request in some platform-dependent way inside the native GUI for a repaint.” – MindProd. Great. My OS was deciding when I was allowed to draw.

No comments | Trackback