Customizing the E-Junkie Shopping Cart: Translation

Following up on my post on how to fully customize the E-Junkie cart visually, this post describes how to translate the E-Junkie shopping cart.
Comments (2) | Trackback

How to Open Rapid Weaver in Terminal

You use RapidWeaver. You’ve downloaded the latest greatest plugin and can’t wait to try it out. You install it …and… nothing. After contacting the developer, and they go through basic trouble shooting with you. That goes no-where. Eager for more information on the issue, the developer asks you to please open RapidWeaver in the Terminal, and copy-paste any output to them.

This article describes exactly how to do this.

First, you’re going to open Terminal. It is located within the “Utilities” sub-folder of your standard “Applications” directory.

Terminal Location in OS X

Next, open Terminal.

Now find RapidWeaver, and drag it into the open Terminal window.

Drag RapidWeaver into the open Terminal window

Next, paste the following onto the end the line in Terminal that was created when you dropped RapidWeaver onto it:

/Contents/MacOS/RapidWeaver

Make sure that there is no space between “RapidWeaver.app” and the extra text you’ve pasted. It should look something like this:

Terminal Ready

Now, click inside the Terminal and press Enter. Notice that RapidWeaver will start, and some (possibly a lot!) text will appear in the Terminal window. This text is gold for developers.

If whatever issue you’re experiencing requires certain actions to replicate, perform those actions – RapidWeaver might be printing information related to the issue in the Terminal. After you’ve replicated the issue, select all of the text inside the Terminal window and paste it into an email, then send it off to whoever you’re working with.

Comments (2) | Trackback

More Usability-Oriented Method for Disabling an NSTabViewItem

Jump to tutorial

I was unable to find a way to easily disable an NSTabViewItem – before implementing the method described below, I achieved this by simply returning “NO” from the “shouldSelectTabViewItem” delegate method. I don’t like this method as I feel that from the user’s perspective:

  • The reason they can’t click the tab item isn’t clear – is it disabled? Has something broken?
  • If they do guess that the tab has been disabled, there is no way short of trial and error for them to discover how they may enable it

Instead of using this opaque method for disabling a given tab, I opted for a more complicated yet informative approach:

When the user clicks a disabled tab, a modal dialog opens, informing them that the tab has been disabled and why, and offers two options: ‘Continue’, and ‘Enable’. In my case, clicking enable simply checks a checkbox and switches to the disabled tab.

From a usability perspective, this approach is superior for two reasons:

  1. The user is informed of something that is otherwise not immediately apparent
  2. The user is given a ‘one click’ option to perform whatever action is required to remedy the situation

Howto:

Make sure your controller is the delegate for your NSTabView. Either programatically, as shown here in the class implementation or through XCode’s GUI.

AppController.m:

3
4
5
- (void) awakeFromNib {
    [yourTabView setDelegate:self];
}

Then make sure the same class implements the ‘shouldSelectTabViewItem’ delegate method, as below:

This is called when a user attempts to change tabs, and is where we detect the tab they’re attempting to change to, and if that tab is disabled, prevent the app from switching to the tab and show the user a message.

AppController.m:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem {
	if(![tabViewItem identifier] == yourIdentifier && isDisabled){
 
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Enable"];
 
            [alert setInformativeText:@"Tab Disabled"];
            [alert setMessageText:@"This tab has been disabled"];
 
            [alert setAlertStyle:NSWarningAlertStyle];
 
            [alert beginSheetModalForWindow:[NSApp mainWindow] 
                              modalDelegate:self 
                             didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                                contextInfo:@"Tab Disabled"];
		return NO;	
	} else {
		return YES;	
	}
}

Now we need to implement the ‘alertDidEnd’ delegate method in the same class, which is called when the user clicks one of the buttons we added to the NSAlert above.

AppController.m:

1
2
3
4
5
6
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == @"Tab Disabled" && returnCode == NSAlertSecondButtonReturn) {
         // Perform whatever action is required to enable your tab here, and switch the user to that tab
        [tabsView selectTabViewItemWithIdentifier:yourIdentifier];
    }
}

And that’s it!

Bare-bones header & implementations below:

1
2
3
4
5
6
7
8
9
10
11
12
13
#import
 
#define MODAL_TAB_DISABLED =      @"Tab Disabled"
#define DISABLED_TAB_IDENTIFIER   @"Disabled Tab"
 
@interface AppController : NSObject {
     IBOutlet NSTabView *tabView;
}
 
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem;
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo;
 
@end
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
#import <Cocoa/Cocoa.h>
#import <AppController.h>
 
@implementation AppController
 
-(void)awakeFromNib {
     [tabView setDelegate:self];    // Set self as the tab view's delegate
}
 
- (bool) tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem {
	if(![tabViewItem identifier] == DISABLED_TAB_IDENTIFIER && isDisabled){
 
            // Create and configure an alert
            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
            [alert addButtonWithTitle:@"OK"];
            [alert addButtonWithTitle:@"Enable"];
 
            [alert setInformativeText:@"Tab Disabled"];
            [alert setMessageText:@"This tab has been disabled"];
 
            [alert setAlertStyle:NSWarningAlertStyle];
 
            [alert beginSheetModalForWindow:[NSApp mainWindow] 
                              modalDelegate:self 
                             didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:) 
                                contextInfo:MODAL_TAB_DISABLED];
		return NO;	
	} else {
		return YES;	
	}
}
 
- (void) alertDidEnd:(NSAlert *)alert returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo {
    if (contextInfo == MODAL_TAB_DISABLED && returnCode == NSAlertSecondButtonReturn) {
         // Perform whatever action is required to enable your tab here, and switch the user to that tab
        [tabsView selectTabViewItemWithIdentifier:yourIdentifier];
    }
}
 
@end
No comments | Trackback

Tile an NSImage Within an NSBezierPath

Another snippet I had to write during the great Slider rebuild.

I needed to be able to take an NSImage supplied by the user and tile it within an NSBezierPath defined by more user-set values.

The below example uses a rounded rectangle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSBitmapImageRep *theImage = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:imageSize.width pixelsHigh:imageSize.height
 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
 colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:0];
 
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:theImage]];
 
//bgImage can be any NSImage
NSColor *c = [NSColor colorWithPatternImage:bgImage];
[c set];
[path appendBezierPathWithRoundedRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) xRadius:10.0 yRadius:10.0];
[path fill];
 
[NSGraphicsContext restoreGraphicsState];
No comments | Trackback

Fill a Path With A Scaled Image, Ignoring Image Proportions

While continuing to rebuild Slider’s background image features, I was forced to rewrite the code that scales an image up/down to Slider’s portal size, ignoring the image’s original proportions. The image also had to be painted within a path, as Slider may have differently shaped corners compared to the image to be scaled.

The below code accomplishes the scaling and painting of the image.

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
//theImage = the resulting image, that will have a scaled (proportions ignored) version of "bgImage" painted onto it
NSBitmapImageRep *theImage = [[NSBitmapImageRep alloc] initWithBitmapDataPlanes:NULL pixelsWide:imageSize.width pixelsHigh:imageSize.height
 bitsPerSample:8 samplesPerPixel:4 hasAlpha:YES isPlanar:NO
 colorSpaceName:NSDeviceRGBColorSpace bytesPerRow:0 bitsPerPixel:0];
 
[NSGraphicsContext saveGraphicsState];
[NSGraphicsContext setCurrentContext:[NSGraphicsContext graphicsContextWithBitmapImageRep:theImage]];
 
NSImage bg = [[NSImage alloc] initWithSize: imageSize];
 
//bgImage can be any NSImage
NSSize originalSize = [bgImage size];
[bg lockFocus];
[bgImage drawInRect: NSMakeRect(0, 0, imageSize.width, imageSize.height) 
fromRect: NSMakeRect(0, 0, originalSize.width, originalSize.height) 
operation: NSCompositeSourceOver fraction: 1.0];
 
[bg unlockFocus];
 
NSColor *c = [NSColor colorWithPatternImage:bg];
[c set];
[path appendBezierPathWithRoundedRect:NSMakeRect(0, 0, imageSize.width, imageSize.height) xRadius:10.0 yRadius:10.0];
[aPath fill];
 
[NSGraphicsContext restoreGraphicsState];

“path” could be any NSBezierPath.

Comments (1) | Trackback