This advice brought to you by a DTS incident.
ls -l:
% ls -l ~/junk/SnapshotRepository.sparseimage -rw-r--r--@ 1 markd markd 135270400 Jul 24 20:38 /Users/markd/junk/SnapshotRepository.sparseimageThat means there's some extended attributes. use
ls -l@ to see them:
% ls -l@ ~/junk/SnapshotRepository.sparseimage -rw-r--r--@ 1 markd markd 135270400 Jul 24 20:38 /Users/markd/junk/SnapshotRepository.sparseimage com.apple.diskimages.fsck 20
// // TapDance.h // Groovilicous // // Created by markd on 7/25/08. // Copyright 2008 __MyCompanyName__. All rights reserved. //With the __MyCompanyName__ placeholder. There is no UI to change this, for obvious reasons. (Why would anyone want to easily and conveniently change something they'll otherwise need to edit in each and every source file they create. That's unpossible). The obvious solution is to drop to the terminal and run the straightforward command:
% defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions '{"ORGANIZATIONNAME" = "Borkware";}'
Seemple, no? Zee trick, she is doone.
% defaults write com.apple.Xcode XCShowUndoPastSaveWarning NO
static void *tempCopyOf(void *data, UInt32 size) {
void *buffer = NULL;
if (data) {
buffer = malloc(size);
if (buffer) {
bcopy(data, buffer, size);
[NSData dataWithBytesNoCopy: buffer length: size freeWhenDone: YES];
}
}
return (buffer);
}
This works in a AutoReleased environment but it is long winded.
static void *tempCopyOf(void *data, NSUInteger size)
{
void *buffer = NULL;
if (data) {
buffer = [[NSMutableData dataWithCapacity: size] mutableData];
bcopy(data, buffer, size);
}
return buffer;
}
You can choose to check the buffer is not null but this avoids the more complicated malloc then alloc call of earlier.
^t is the hotkey, doing ^t ^g will toggle the visible bell on and off.
...
[someWebView setPolicyDelegate: self];
...
- (void) webView:(WebView *)webView
decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id)listener {
NSURL *url = [request URL];
if (url != nil) {
[[NSWorkspace sharedWorkspace] openURL:url];
}
} // decidePolicyForNavigationAction
p4 revert -awill revert all files that haven't actually be edited, leaving the edited opened.
(Thanks to TVL for this one)
p4 edit -t text [file_list] p4 submit(Thanks to TVL for this one)
% sudo tcpdump -Atq -s 0 -i en1
-i en1 will display traffic on your airport card. Use en0 (or nothing, for most systems) for built-in ethernettage.
You can add a host line to limit output to a particular host
% sudo tcpdump -Atq -s 0 -i en1 host zombo.com(thanks to Dan Jalkut for this one)
% gdb build/Debug/Snorkfizzle.app/Contents/MacOS/Snorkfizzle (gdb) set env NSZombieEnabled=YES (gdb) fb -[_NSZombie methodSignatureForSelector:] (gdb) runThen exercise your app and trip the error.
// I am the very modal of a modern major general. int blah = [NSApp runModalForWindow:[self window]];and then elsewhere, like in your OK or cancel button
[NSApp stopModalWithCode:NSOKButton];
% sw_vers -productVersion 10.5.1
- (void) snorgWaffle: (NSString *) start, ... {
va_list argList;
va_start (argList, start);
NSString *string = start;
while (string != nil) {
NSLog (@"Done did saw string '%@'", string);
string = va_arg (argList, NSString *);
}
va_end (argList);
} // snorgWaffle
and can be called like
[self snorgWaffle:@"red", @"planet", @"zeitgeist", nil];
lsregister tool that lives in /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support
[[NSDate date] descriptionWithCalendarFormat: @"%B %e, %Y" timeZone: nil locale: nil](Thanks to Mike Morton for this one)
NSMutableArray *sortedArray = [NSMutableArray arrayWithObjects: @"Alice", @"Beth", @"Carol",@"Ellen",nil];
//Where is "Beth"?
unsigned index = (unsigned)CFArrayBSearchValues((CFArrayRef)sortedArray,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)sortedArray)),
(CFStringRef)@"Beth",
(CFComparatorFunction)CFStringCompare,
NULL);
if (index < [sortedArray count])
{
NSLog(@"Beth was found at index %u", index);
} else {
NSLog(@"Beth was not found (index is beyond the bounds of sortedArray)");
}
//Where should we insert "Debra"?
unsigned insertIndex = (unsigned)CFArrayBSearchValues((CFArrayRef)sortedArray,
CFRangeMake(0, CFArrayGetCount((CFArrayRef)sortedArray)),
(CFStringRef)@"Debra",
(CFComparatorFunction)CFStringCompare,
NULL);
[sortedArray insertObject:@"Debra" atIndex:insertIndex];
NSLog([sortedArray description]);
//note: NSArray indices and counts were typed as unsigned. With the move to 64-bit, they are NSUInteger.
//CFArray indices and counts are CFIndex, which was SInt32 but also will move to 64-bit?
//Why was it ever signed and will it remain so?
Muchos Thankos to James Hober for this one.
Gus Mueller chimed in saying that if you use CFArrayBSearchValues, be sure to sort with CFArraySortValues rather than using the Cocoa sorting routines (or at least the comparison) - they treat diacritic marks differently, leading to strange errors. From Gus, a quick objc addition to NSMutableArray:
- (void) cfStringSort {
CFArraySortValues((CFMutableArrayRef)self, CFRangeMake(0, [self count]), (CFComparatorFunction)CFStringCompare, NULL);
}
% cat /System/Library/Frameworks/Foundation.framework/Headers/*.h > ~/moby % cat /System/Library/Frameworks/AppKit.framework/Headers/*.h >> ~/moby % chmod 444 ~/moby
[themStitches sortUsingSelector: @selector(compareByRowLocation:)];
And this selector on the BWCrossStitch class:
- (NSComparisonResult) compareByRowLocation: (BWCrossStitch *) thing2;Figure out which is lesser or greater, and return one of these. Note that
self is the object's logical value, not the actual value of the self pointer.
NSOrderedAsending if self < thing2
NSOrderedDescending if self > thing2
NSOrderedSame if self == thing2
shift-click
{
$any_click
if @click.simple_name == ""
if @my.right_item == "caduceus"
"\use /off\r"
end if
else if @click.simple_name == @my.simple_name
if @my.right_item != "moonstone"
"\equip \"moonstone\"\r"
end if
"\use 3\r"
else
if @my.right_item != "caduceus"
"\equip \"caduceus\"\r"
end if
"\use " @click.simple_name "\r"
message "* Now healing" @click.name
end if
}
translate to run the powerPC side of the world:
% xcodebuild -configuration Release -target "Test All" -project Tests.xcodeproj NATIVE_ARCH="i386 ppc" % /usr/libexec/oah/translate /Developer/Tools/otest build/Release/Tests.octest
uuidgen, for instance:
C-U M-! ret uuidgen ret
-h flag:
% grep -h chicken ~/Documents/ircLogs/FreeNode/2007*/#macsb*
@implementation NSString (PasteboardGoodies)
- (void) sendToPasteboard
{
[[NSPasteboard generalPasteboard]
declareTypes: [NSArray arrayWithObject: NSStringPboardType]
owner:nil];
[[NSPasteboard generalPasteboard]
setString: self
forType: NSStringPboardType];
} // sendToPasteboard
@end // PasteboardGoodies
Thanks to Dan Jalkut for this tip.
% p4 revert -c default ...
2007-05-05 17:18:00.702 QueenStitcher[2804:117] *** Assertion failure in -[NSColorWell setColor:], NSColorWell.m:497, u suk l0s3r, and then the runloop happily runs again, giving you no clue where the problem is. I tell gdb to always break on Cocoa exceptions:
fb -[NSException raise] fb objc_exception_throw()For maximal enjoyment, add these two lines to your
~/.gdbinit file, so they'll get set no matter how you invoke gdb (no need to add these to every single project, for instance).
I've been told VoiceOver uses exceptions heavily, so if you're doing VoiceOver development, these breaks may cause you pain.
NSArray *chunks = ... get an array, say by splitting it; string = [chunks componentsJoinedByString: @" :-) "];would produce something like
oop :-) ack :-) bork :-) greeble :-) ponies
NSMutableString *mstring = [NSMutableString stringWithString:string];
NSRange wholeShebang = NSMakeRange(0, [string length]);
[mstring replaceOccurrencesOfString: @"\n"
withString: @""
options: 0
range: wholeShebang];
return [NSString stringWithString: mstring];
(this can also be used for generic string manipulations, not just stripping out newlines).
This technique takes half the time (at least) of split/join. But probably not enough to make an impact. In a simple test, split/join took 0.124 seconds to strip 36909 newlines in a 1.5 meg textfile, and 0.071 seconds to do the same.
printf ("hello %.*s\n", 5, "there is no good going on");
NSLog (@"hello %.*s\n", 5, "there is no good going on");
results in
hello there 2007-05-04 09:14:15.575 printf[4914] hello there
Similarly, something like printf ("hello %*.*s\n", 10, 5, "florgsnorgle"); will right-justify 'florg' in a field of 10 characters.
(Thanks to TVL for this goodie)
NSString *string = @"oop:ack:bork:greeble:ponies"; NSArray *chunks = [string componentsSeparatedByString: @":"];
% xmllint -noout kipple.xml
Handy for a quick check after hand-editing a file.
touch. You can specify a specific date or time using the format [[CC]YY]MMDDhhmm[.SS]]. Here's an example with alternating bolds to make it easier to read:
% touch -t 200703141536 snork.waffle % ls -l snork.waffle -rw-r--r-- 1 markd markd 298 Mar 14 15:36 snork.waffle
Open up Internet Connect, select your VPN icon in the toolbar, and choose options from the Connect menu. Uncheck "Disconnect when switching user accounts".
compile sources build phase. If you get info on the source file in other places in Xcode, there's no build tab, and you're left with that "Am I crazy? I know I've seen that setting somewhere before, but where is it? I better go back to listening to Finnish polkas 24/7 and give up this software thing" kind of feeling.
% unzip -l snorgle.zip
static void *tempCopyOf(void *data,UInt32 size) {
void *buffer = NULL;
if (data) {
buffer = malloc(size);
if (buffer) {
bcopy(data,buffer,size);
[NSData dataWithBytesNoCopy: buffer length: size freeWhenDone: YES];
}
}
return (buffer);
}
You can get really fancy and assign buffer to calloc(1, size) if you want to give it "pass NULL to get a zero-filled buffer back" semantics.
Also note this doesn't play well with garbage collection - caveat nerdor. (Thanks to Ken Ferry for that note)
NSString *pathString = ... whatever ...;
FSRef ref;
status = FSPathMakeRef ((const UInt8 *)[pathString fileSystemRepresentation],
&ref, NULL);
if (status != noErr) {
NSLog (@"bummer. couldn't make FSREf from path '%@'",
pathString);
}
% /Developer/Tools/DeRez -useDF ./blarg.rsrc /Developer/SDKs/MacOSX10.4u.sdk/Developer/Headers/FlatCarbon/MacTypes.r > oopack2And the reverse process is like:
% /Developer/Tools/Rez -o blah.rsrc -useDF /Developer/SDKs/MacOSX10.4u.sdk/Developer/Headers/FlatCarbon/MacTypes.r oopack2
% /usr/bin/mdimport -d2 ../Book.pdf >& oopack.txtAnd edit out the little bit of extra metadata output.
% svn switch --relocate http://from-address.flongswozzle.net http://to-address.borkware.comMuchos thankos to Lucas Eckels for the tip.
% defaults write com.borkware.snongflozzle ookook -array thing1 thing2 thing3
In this case, the selector being called takes two arguments, one of which is an object, the other is an NSTimeInterval. The atIndex: jazz starts with 2 so that the self parameter and the selector can be passed to the method.
NSMethodSignature *signature = [target_ methodSignatureForSelector:selector_];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:selector_];
[invocation setTarget:target_];
[invocation setArgument:&self
atIndex:2];
[invocation setArgument:&lastWait_
atIndex:3];
[invocation invoke];
One way to hack around it is to tell the image to use the pixel size of the underlying image representation:
NSImage *image;
image = [[NSImage alloc] initWithContentsOfFile: path];
NSBitmapImageRep *rep = [[image representations] objectAtIndex: 0];
// If you think you might get something other than a bitmap image representation,
// check for it here.
NSSize size = NSMakeSize ([rep pixelsWide], [rep pixelsHigh]);
[image setSize: size];
% /Developer/Tools/otest -SenTest ClassName ./build/Debug/TheUnitTest.octest
[[textfield cell] setLineBreakMode: NSLineBreakByTruncatingMiddle](Thanks to Daniel Jalkut for this one)
% find . -name "*.h" -print -exec cat {} \; > ~/moby-file.h
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
gridType = [defaults objectForKey: @"gridType"];
Will have the value of "flarn" whether you do something like
% defaults write com.borkware.BorkStitch gridType flarnor
% ./build/Debug/BorkStitch.app/Contents/MacOS/BorkStitch -gridType flarn
~/Library/Screen Savers folder? If so, you can make a symbolic link that points to your screen saver bundle, and that will automatically get used whenever you engage the screen saver engine. e.g.
% cd ~/Library/Screen Savers % ln -s ~/Projects/MonkeySaver/build/Debug/MonkeySaver.saver .
% find . -name "*.yuck" -print | xargs tar rvf oop.tar
(global-font-lock-mode -1)
(setq comint-scroll-show-maximum-output nil)
NSData *blah = [xmldocument XMLDataWithOptions: NSXMLNodePrettyPrint];
NSString *snork = [[NSString alloc] initWithData: blah
encoding: NSUTF8StringEncoding];
NSAttributedString *arrrgh;
arrrgh = [[NSAttributedString alloc] initWithString: snork];
[[xmlTextView textStorage]
setAttributedString: [arrrgh autorelease]];
% svn mkdir -m "initial revision" svn+ssh://borkware.com/path/to/svnroot/thingie
% sudo mdutil -i off /
selection, using @count.
float domain[2] = { 0.0, 1.0 }; // 1-in function
float range[8] = { 0.0, 1.0, // N-out, RGBA
0.0, 1.0,
0.0, 1.0,
0.0, 1.0 };
CGFunctionCallbacks callbacks = { 0, evaluate, NULL };
CGFunctionRef shaderFunction;
shaderFunction = CGFunctionCreate (self, // info / rock / context
1, // # of inputs for domain
domain, // domain
4, // # of inputs for range
range, // range
&callbacks);
CGColorSpaceRef deviceRGB;
deviceRGB = CGColorSpaceCreateDeviceRGB ();
CGShadingRef shader;
shader = CGShadingCreateAxial (deviceRGB, // colorspace
cgpoint(start), // start of axis
cgpoint(end), // end of axis
shaderFunction, // shader, 1-n, n-out
NO, // extend start
NO); // extend end
CGContextSaveGState (context); {
NSRect bounds = [self bounds];
CGContextClipToRect (context, cgrect(bounds));
CGContextDrawShading (context, shader);
} CGContextRestoreGState (context);
[self drawSpotAt: start size: 4];
[self drawSpotAt: end size: 4];
CGFunctionRelease (shaderFunction);
CGColorSpaceRelease (deviceRGB);
CGShadingRelease (shader);
And the evaluator function is given an array of inputs and outputs. Use the in value(s) (which run from your domain's start-to-finish values) to generate the out values (which should be in the range's start-to-finish values):
static void evaluate (void *info, const float *in, float *out)
{
float thing;
thing = in[0];
out[0] = thing;
out[1] = thing;
out[2] = thing;
out[3] = 1.0;
} // evaluate
#define cgrect(nsrect) (*(CGRect *)&(nsrect))
- (void) drawRect: (NSRect) rect
{
NSRect bounds = [self bounds];
NSGraphicsContext *cocoaContext = [NSGraphicsContext currentContext];
CGContextRef context = (CGContextRef)[cocoaContext graphicsPort];
CGContextSetLineWidth (context, 5.0);
CGContextBeginPath(context); {
CGContextAddRect (context, cgrect(bounds));
CGContextSetRGBFillColor (context, 1.0, 0.9, 0.8, 1.0);
} CGContextFillPath(context);
} // drawRect
- (void) makeLayerInContext: (CGContextRef) enclosingContext
{
layer = CGLayerCreateWithContext (enclosingContext, CGSizeMake(100, 100),
NULL); // options - unused in Tiger
CGContextRef context;
context = CGLayerGetContext (layer);
// .. and do your drawing
} // makeLayer
And then to draw the layer at a particular point (like replicating it a bunch of different points as done here):
for (i = 0; i < pointCount; i++) {
CGContextDrawLayerAtPoint (context, locations[i], layer);
}
C-x ( : start recording keyboard macroC-x ) : stop recording keyboard macroC-x e : replay current keyboard macro
LANG environment variable to be en_US to make it happier.
% screen -r
There is a screen on:
8395.pts-0.vikki (Multi, attached)
You can boot off that other loser by using:
% screen -d 8395.pts-0.vikkiand then doing
screen-r again
emacs inside of screen is made difficult because both want to use C-A for something important. C-O (AFAIK so far) isn't too useful for my use of emacs, so I use that for screen now:
screen -e^Oo emacs
screen -r
deletechar into backward-delete-char-untabify causes backspace in incremental search to cancel the search, which is annoying.
One option is to set the TERM env var to rxvt:
% setenv TERM rxvtBefore cranking up screen.
insert into blah (id, stuff)
values (nextval('sequence_name'), 'stuff');
.gdbinit and then you can use wchar_print:
define wchar_print
echo "
set $i = 0
while (1 == 1)
set $c = (char)(($arg0)[$i++])
if ($c == '\0')
loop_break
end
printf "%c", $c
end
echo "
end
document wchar_print
wchar_print <wstr>
Print ASCII part of <wstr>, which is a wide character string of type wchar_t*.
end
# svc -d /service/whatever - Bring the server down
# svc -u /service/whatever - Start the server up and leave it in keepalive mode.
# svc -o /service/whatever - Start the server up once. Do not restart it if it stops.
# svc -t /service/whatever - Stop and immediately restart the server.
# svc -k /service/whatever - Sends the server a KILL signal. This is like KILL -9. If svc -t fails to fully kill the process, use this option.
(Thanks to the OpenACS documentation)
tell application "VoodooPad" taunt end tell
Borkdoku(11062,0xcec0600) malloc: *** error for object 0xd109010: incorrect checksum for freed object - object was probably modified after being freed, break at szone_error to debugWhich is fine and dandy, but it lies. I've never gotten
szone_error to actually do anything. Try breaking on malloc_printf instead.
The problem is, if I include the usefulStuff.c in my plugin and my test harness, there are two copies of the static variables, and the test harness can't control the plugin. If I don't include usefulStuff.c in the plugin I get link errors. If I don't declare as weak linked the functions I use, I'll get errors when the final program loads the plugin. Sucks to be me.
Here's one way of doing it (which is kinda hacky, but after spending a day inside of the ld man page, and other pain, the fact it works is good enough for me for right now).
In the source file for the plugin that uses stuff from usefulStuff:
Declare the function to be weak:
extern void BWDebugLog (int blah, char *blah) __attribute__((weak_import));(you can also do this in your header files. In this case, I didn't want to touch the header)
Before you use the function, make sure it's not NULL. Note there's no trailing () after the function name.
if (BWDebugLog != NULL) {
BWDebugLog (23, "stuff");
}
In the Xcode project for the plugin
Add the flags -flat_namespace and -undefined dynamic_lookup to the "Other Linker Flags" (a.k.a. OTHER_LDFLAGS). The flat namespace lets the symbols be found in the test harness when the plugin is loaded. The undefined dynamic_lookup means to suppress warnings about undefined symbols. This could conceivably mask errors, but it's no worse than ZeroLink.
In the Xcode project for the test harness
Add usefulStuff.c, and turn on the "Preserve Private External Symbols" checkbox (a.k.a -keep_private_externs). That turns the symbol BWDebugLog (and others) into exported symbols that'll be visible to the plugin. Otherwise the plugin will never get past that != NULL check earlier.
Once all that's done, my plugin loads into the test harness and can get controlled. It can be loaded into the Real App without undefined symbol errors.
/* For the emacs weenies in the crowd. Local Variables: c-basic-offset: 2 End: */
.octest file of interest:
% find . -name "*.octest" -print ./build/Debug/Tests.octestThen gdb
Tools/otest:
% gdb /Developer/Tools/otestThen run it with the name of the bundle:
(gdb) run ./build/Debug/Tests.octest
$title$ for the page term.
void giveSomeLove ()
{
// give the app some love so it'll update the window
[[NSRunLoop currentRunLoop] runUntilDate: [NSDate distantPast]];
} // giveSomeLove
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setPrompt: @"Stuff for the Choose Button"];
[panel beginSheetForDirectory: nil
file: nil
types: [NSArray arrayWithObject: @"buf"] // or other file types
modalForWindow: window
modalDelegate: self
didEndSelector: @selector(openPanelDidEnd:returnCode:contextInfo:)
contextInfo: nil];
and the didEndSelector implementation looks kinda like this:
- (void) openPanelDidEnd: (NSOpenPanel *) sheet
returnCode: (int) returnCode
contextInfo: (void *) context
{
if (returnCode == NSOKButton) {
NSArray *fileNames = [sheet filenames];
NSLog (@"wooOOooot! %@", [fileNames objectAtIndex: 0]);
}
} // openPanelDidEnd
% gcc -E -dM - </dev/null
libgmalloc puts guard pages at the end of malloc'd blocks of memory, letting you catch buffer overruns. (This will hugely inflate your program's working set, and may lead to swapping) To turn on libgmalloc in gdb, do this:
(gdb) set env DYLD_INSERT_LIBRARIES /usr/lib/libgmalloc.dylib
float blah = FixedToFloat(someFixedvalue);other goodies in
FixMath.h
-e option to the rescue:
% grep -e -H filename
#define runScriptName @"checknewnow"
#define runScriptType @"scpt"
- (IBAction)runScript:(id)sender
{
/* Locate that darn thing*/
NSString *scriptPath = [[NSBundle mainBundle]
pathForResource: runScriptName
ofType: runScriptType];
NSURL *scriptURL = [NSURL fileURLWithPath: scriptPath];
NSAppleScript *as = [[NSAppleScript alloc]
initWithContentsOfURL: scriptURL
error: nil];
[as executeAndReturnError: NULL];
[as release];
}
(Thanks to of Chris "The_Tick" Forsythe)
(gdb) po *(int*)($ebp+8)
'bork'. You can have gdb print them out if you need to look at one or two of them:
(gdb) print/T 1936746868 $4 = 'spit'(thanks to Daniel Jalkut for the print/T trick)
nm outputs a three-column format. I needed to get the set of defined symbols (in particular those that start with __, in tracking down a C++ linking problem), which is the third column. awk is good for that. Use $N to get the nth column (zero-index). This pipeline did the trick:
nm ./oopack.o | awk '{print $2}' | sort | grep __
(Thanks to DougEDoug for the pointer)
ld: multiple definitions of symbol __ZN13CaptureWidgetD2Ev
/Users/blah/blah/build/./capture.build/Debug/capture.build/Objects-normal/ppc/capturewidget-F17F43C0.o definition of __ZN13CaptureWidgetD2Ev in section (__TEXT,__text)
/Users/blah/blah/build/./capture.build/Debug/capture.build/Objects-normal/ppc/capturewidget-F17F43C0.o definition of __ZN13CaptureWidgetD2Ev in section (__TEXT,__text)
This is especially annoying, because the same object file is listed as having a duplicated symbol. If you take out the file from being compiled, you get undefined symbols for that particular source file, and if you put it back in, you get duplicated symbols.
Turn out the problem is that Xcode (somehow) added the C++ file to the "Build Sources" phase of the target. It happily generated two identical object files, and proceded to link them (hence the error), but doesn't get the reporting information correct (hence saying that both symbols come from the same file)
% /Developer/Tools/otest path/to/build/Unittest/TheUnit Test.octest
% find . -type d -print | grep -v CVS | xargs cvs add % find . -type f -print | grep -v CVS | xargs cvs addThe first adds the directories, the second adds the files. Filenames with spaces in them won't be added, and it won't do the Right Thing with
-kb for binary files.
@executable_path/../blah. Sometimes you get a library from someone else (or something generated by a gigantic configure script) and need to change the install name. The install_name_tool will do that for you. Say I had a library, libbork.dylib, that will end up being placed side-by-side with the executable of a cocoa app, this is how I would fix it:
% install_name_tool -id @executable_path/libbork.dylib ./libbork.dylib
So, combine the two for maximal pleasure:
function cavemanLog(message) {
if (window.widget) {
// Won't display an error box, but will show in the console log.
window.alert(message);
} else {
// Send output to the javascript console.
window.console.log(message);
}
} // cavemanLog
alter table pfo_survey_response_2006 add column section text
if (window.console) {
window.console.log ("ook1");
}
(window.console exists in Safari, but not in Dashboard)
#import <Cocoa/Cocoa.h>
#import <stdio.h> // for printf()
@interface NotificationSpy
{
}
+ (void) startSpying;
+ (void) stopSpying;
@end // NotificationSpy
// prevent us from adding ourselves multiple times
static BOOL g_spying;
@implementation NotificationSpy
// turn on listening for all notifications.
+ (void) startSpying
{
if (!g_spying) {
NSNotificationCenter *center;
// first the default notification center, which is all
// notifications that just happen inside of our program
center = [NSNotificationCenter defaultCenter];
[center addObserver: self
selector: @selector(observeDefaultCenterStuff:)
name: nil
object: nil];
// then the NSWorkspace notification center, which tells us things
// like other applications launching, and the machine sleeping
// and waking
center = [[NSWorkspace sharedWorkspace]
notificationCenter];
[center addObserver: self
selector: @selector(observeWorkspaceStuff:)
name: nil
object: nil];
// and lastly the distributed notification center. This is a
// global (to the computer) notification center. You can find
// out when a different program gets focus, or the sound or
// screen brightness changes.
center = [NSDistributedNotificationCenter
notificationCenterForType: NSLocalNotificationCenterType];
[center addObserver: self
selector: @selector(observeDistributedStuff:)
name: nil
object: nil];
g_spying = YES;
}
} // startSpying
// remove us as observers
+ (void) stopSpying
{
if (!g_spying) {
NSNotificationCenter *center;
// take us off the default center for our app
center = [NSNotificationCenter defaultCenter];
[center removeObserver: self];
// and for the workspace
center = [[NSWorkspace sharedWorkspace]
notificationCenter];
[center removeObserver: self];
// and finally off of the machine-wide center
center = [NSDistributedNotificationCenter
notificationCenterForType: NSLocalNotificationCenterType];
[center removeObserver: self];
g_spying = NO;
}
} // stopSpying
+ (void) observeDefaultCenterStuff: (NSNotification *) notification
{
// QuietLog can also be found in the quickies
QuietLog (@"default: %@", [notification name]);
} // observeDefaultCenterStuff
+ (void) observeDistributedStuff: (NSNotification *) notification
{
QuietLog (@"distributed: %@", [notification name]);
} // observeDistributedStuff
+ (void) observeWorkspaceStuff: (NSNotification *) notification
{
QuietLog (@"workspace: %@", [notification name]);
} // observeWorkspaceStuff
@end // NotificationSpy
% defaults write com.apple.Safari WebKitOmitPDFSupport -bool YESif you have the misfortune of having the Acrobat Reader plug-in, you can nuke
/Library/Internet Plug-Ins/AdobePDFViewer.plugin
open it from the terminal, you get asked if you want to install it (which will move your widget to ~/Library/Widgets, which is really annoying when it drags it out of your development area. When faced with the dialog, hold down command and option to get a "Run" button. That'll run the widget in-pace. If you tun on the devmode:
% defaults write com.apple.dashboard devmode YESYou can drag widgets out of the dashboard area and have them on-screen. Cmd-R will reload the widget.
WebArchive *archive = ... get from somewhere...; WebFrame *frame = [webView mainFrame]; [frame loadArchive: webarchive];
observeValueForKeyPath:ofObject:change:context:, (or you do override it but don't handle your callbacks) you'll generally get an exception.
This trains you to do it right and never call super for the callbacks you're handling, or you'll get an exception.
Except
if you've added KVO to a subclass of NSArrayController (and possibly all controller types), and don't call super's observeValueForKeyPath:ofObject:change:context:, bindings won't work at all, with no warning/notice/nothing. (Courtesy of Duncan Wilcox)
defaults write com.apple.Safari IncludeDebugMenu 1
applicationShouldTerminate: message, you're likely to want to returnNSTerminateLater so that you can first gracefully shut down the connection before quitting.
There is a gotcha: returning NSTerminateLater stops the main runloop, so your NSTimers will stop working from that point on. Thus, if you were depending on a timer firing to finish up your shut-down process, you'll never see it, and you'll hang. The solution is to return NSTerminateCancel, then do whatever you need to do and then terminate yourself manually. (Thanks to Larry Gerndt for this one!)
b objc_msgSend comm silent printf "%c[%s %s]\n", $r3&&((id)$r3)->isa->info&2?'+':'-', $r3?((id)$r3)->isa->name:"nil", $r4 cont end b objc_msgSend_rtp comm silent printf "%c[%s %s]\n", $r3&&((id)$r3)->isa->info&2?'+':'-', $r3?((id)$r3)->isa->name:"nil", $r4 cont endAnd you'll get some output like this:
-[NSTableColumn _bindingAdaptor] +[NSBinder binderClassesForObject:] +[NSBinder _allBinderClasses] +[NSDisplayPatternTitleBinder isUsableWithObject:] +[NSBox self](courtesy of Rob Mayoff)
- (NSData *) dataOfType: (NSString *) typeName
error: (NSError **) error
{
*error = nil;
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver;
archiver = [[NSKeyedArchiver alloc]
initForWritingWithMutableData: data];
[archiver setOutputFormat: NSPropertyListXMLFormat_v1_0];
[archiver encodeObject: stitches forKey: @"stitches"];
// and archive other stuff you want
[archiver finishEncoding];
return ([data autorelease]);
} // dataOfType
- (BOOL) readFromData: (NSData *) data
ofType: (NSString *) typeName
error: (NSError **) error
{
*error = nil;
NSKeyedUnarchiver *archiver;
archiver = [[NSKeyedUnarchiver alloc]
initForReadingWithData: data];
[stitches release];
stitches = [archiver decodeObjectForKey: @"stitches"];
// decode other stuff of interest
[stitches retain];
return (YES);
} // readFromData
NSCoding protocol.
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeInt: x forKey: @"x"];
[coder encodeInt: y forKey: @"y"];
[coder encodeObject: color1 forKey: @"color1"];
[coder encodeObject: color2 forKey: @"color2"];
[coder encodeInt: direction forKey: @"direction"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) coder
{
if (self = [super init]) {
x = [coder decodeIntForKey: @"x"];
y = [coder decodeIntForKey: @"y"];
color1 = [coder decodeObjectForKey: @"color1"];
color2 = [coder decodeObjectForKey: @"color2"];
direction = [coder decodeIntForKey: @"direction"];
}
return (self);
} // initWithCoder
unsigned index;
for (index = [indexSet firstIndex];
index != NSNotFound; index = [indexSet indexGreaterThanIndex: index]) {
...
}
(courtesy of mikeash)
- (NSAttributedString *) prettyName
{
NSTextAttachment *attachment;
attachment = [[[NSTextAttachment alloc] init] autorelease];
NSCell *cell = [attachment attachmentCell];
NSImage *icon = [self icon]; // or wherever you are getting your image
[cell setImage: icon];
NSString *name = [self name];
NSAttributedString *attrname;
attrname = [[NSAttributedString alloc] initWithString: name];
NSMutableAttributedString *prettyName;
prettyName = (id)[NSMutableAttributedString attributedStringWithAttachment:
attachment]; // cast to quiet compiler warning
[prettyName appendAttributedString: attrname];
return (prettyName);
} // prettyName
This puts the image at the front of the string. To put the image in the middle of the string, you'll need to create an attributedstring with attachment, and then append that to your final attributed string.
[NSApp beginSheet: saveSheet
modalForWindow: window
modalDelegate: self
didEndSelector: @selector(saveSheetDidEnd:returnCode:contextInfo:)
contextInfo: NULL];
In the controls in the sheet, use something like
[NSApp endSheet: saveSheet returnCode: NSOKButton];To invoke the
didEndSelector. Inside of that method, you can check the return code and decide what to do:
- (void) saveSheetDidEnd: (NSWindow *) sheet
returnCode: (int) returnCode
contextInfo: (void *) contextInfo
{
if (returnCode == NSOKButton) {
// ...
} else if (returnCode == NSCancelButton) {
// ...
} else {
// ...
}
[sheet close];
} // saveSheetDidEnd
NSString *commitMessage; commitMessage = [[commitTextView textStorage] string];
disabledControlTextColor won't draw the text in the disabled color. You need to use the secondarySelectedControlColor, which supposedly is for active controls that don't have focus. Go figure.
To do it yourself, subclass NSTextField and override setEnabled: to change the color:
- (void) setEnabled: (BOOL) flag
{
[super setEnabled: flag];
if (flag == NO) {
[self setTextColor: [NSColor secondarySelectedControlColor]];
} else {
[self setTextColor: [NSColor controlTextColor]];
}
} // setEnabled
This actually kind of a gross workaround for a Cocoa bug - the disabled color is getting made darker rather than lighter. The secondarySelectedControlColor ends up looking disabled by a happy coincidence that it starts out lighter before being darkened. (or something like this. UberDude Dave MacLachlan has done the legwork to figure out the underlying problem.)
[[textView textStorage] setAttributedString: theNewString];
In my NSSegmentedControl category, I have a method to solve this problem:
@interface NSSegmentedControl (BorkwareAdditions)
- (void) unselectAllSegments;
// ... other goodies
@end // NSSegmentedControl
- (void) unselectAllSegments
{
NSSegmentSwitchTracking current;
current = [self trackingMode];
[self setTrackingMode: NSSegmentSwitchTrackingMomentary];
int i;
for (i = 0; i < [self segmentCount]; i++) {
[self setSelected: NO forSegment: i];
}
[self setTrackingMode: current];
} // unselectAllSegments
- (void) observeValueForKeyPath: (NSString *) keyPath
ofObject: (id) object
change: (NSDictionary *) change
context: (void *) context
The NSKeyValueChangeKindKey key in the dictionary tells you if the change was an insertion
(NSKeyValueMinusSetMutation) or a deletion (NSKeyValueIntersectSetMutation)
If it is an insertion, the NSKeyValueChangeIndexesKey is an index set that contains the index of the inserted object. You can query the collection for the object at that index to get the new object.
if it a deletion, the NSKeyValueChangeIndexesKey tells you the index where the object was deleted from, and the NSKeyValueChangeOldKey contains an NSArray of objects which were removed, in case you want to hang on to it, or use it to clean out some of your data structures.
window method, that will force the nib file to be loaded. Something like this:
+ (BWInspector *) sharedInspector
{
static BWInspector *s_inspector;
if (s_inspector == nil) {
s_inspector = [[BWInspector alloc]
initWithWindowNibName: @"BWInspector"];
assert (s_inspector != nil);
// force loading of nib
(void) [s_inspector window];
}
return (s_inspector);
} // sharedInspector
setFrame: for a window, you have to account for the height of the title bar. In the Classic days it was 16 pixels. In Aqua-land, it's currently 22 pixels. But that's not safe to use, so try this instead:
- (float) titleBarHeight
{
NSRect frame = NSMakeRect (0, 0, 100, 100);
NSRect contentRect;
contentRect = [NSWindow contentRectForFrameRect: frame
styleMask: NSTitledWindowMask];
return (frame.size.height - contentRect.size.height);
} // titleBarHeight
Rainer Brockerhoff points out that this might miss any system-suppled window decorations at the bottom of the window. There aren't any now, but Mac OS X 10.37 Sabertooth might, so you may want to take into account the y positions of the original rectangle and the newly calculated content rect.
setFrame:display:animate: to resize a window, and have the window animate between the two sizes. Remember that Cocoa uses a bottom-left origin, which is a pain when dealing with windows. You want the window's top left to be the same between the old and new sizes, so you have to dink with the origin as well as the size:
float delta = ... how much to make the window bigger or smaller ...;
NSRect frame = [window frame];
frame.origin.y -= delta;
frame.size.height += delta;
[window setFrame: frame
display: YES
animate: YES];
NSViewAnimation lets you resize a window and cross-fade some views in one operation. (note I've had problems with the window size getting a couple of pixels off using this. Hopefully either I'm doing something dumb, or Apple fixes a bug). This code resizes the window, and changes the view that lives inside of a box. This is like how the Pages inspector works (except Pages doesn't do the gratuitous animation effect)
NSRect newWindowFrame = ... the new window size;
NSDictionary *windowResize;
windowResize = [NSDictionary dictionaryWithObjectsAndKeys:
window, NSViewAnimationTargetKey,
[NSValue valueWithRect: newWindowFrame],
NSViewAnimationEndFrameKey,
nil];
NSDictionary *oldFadeOut = nil;
if (oldView != nil) {
oldFadeOut = [NSDictionary dictionaryWithObjectsAndKeys:
oldView, NSViewAnimationTargetKey,
NSViewAnimationFadeOutEffect,
NSViewAnimationEffectKey, nil];
}
NSDictionary *newFadeIn;
newFadeIn = [NSDictionary dictionaryWithObjectsAndKeys:
newView, NSViewAnimationTargetKey,
NSViewAnimationFadeInEffect,
NSViewAnimationEffectKey, nil];
NSArray *animations;
animations = [NSArray arrayWithObjects:
windowResize, newFadeIn, oldFadeOut, nil];
NSViewAnimation *animation;
animation = [[NSViewAnimation alloc]
initWithViewAnimations: animations];
[animation setAnimationBlockingMode: NSAnimationBlocking];
[animation setDuration: 0.5]; // or however long you want it for
[animation startAnimation]; // because it's blocking, once it returns, we're done
[animation release];
cc1obj: error: type '<built-in>' does not have a known size(void), a typo that should have been (void *).
NSString *blah = @"Placeholder Pane";
NSSize size = [blah sizeWithAttributes: nil];
NSPoint startPoint;
startPoint.x = bounds.origin.x + bounds.size.width / 2 - size.width / 2;
startPoint.y = bounds.origin.y + bounds.size.height / 2 - size.height / 2;
[blah drawAtPoint: startPoint
withAttributes: nil];
- (void) setDefaultPrefs
{
NSMutableDictionary *dictionary = [NSMutableDictionary dictionary];
[dictionary setObject: NSLocalizedString(@"BorkDown", nil)
forKey: @"menuTitle"];
[dictionary setObject: NSLocalizedString(@"Time has run out.", nil)
forKey: @"alertWindowText"];
// and any other preferences you might have
[[NSUserDefaults standardUserDefaults]
registerDefaults: dictionary];
} // setDefaultPrefs
poseAsClass: to hook yourself into the matrix during the +load method:
@interface BorkSpellChecker : NSSpellChecker
{
}
@end // BorkSpellChecker
@implementation BorkSpellChecker
+ (void) load
{
NSLog (@"posing");
[self poseAsClass: [NSSpellChecker class]];
} // load
- (void) ignoreWord: (NSString *) word inSpellDocumentWithTag: (int) tag
{
NSLog (@"ignore word: %@ intag: %d",
word, tag);
[super ignoreWord: word
inSpellDocumentWithTag: tag];
} // ignoreWord
- (NSParagraphStyle *) paragraphStyleInTextView: (NSTextView *) textView
atIndex: (int) index
{
NSTextStorage *storage = [textView textStorage];
NSDictionary *attributes;
NSRange effectiveRange;
attributes = [storage attributesAtIndex: index
effectiveRange: &effectiveRange];
NSParagraphStyle *style;
style = [attributes valueForKey: NSParagraphStyleAttributeName];
return (style);
} // paragraphStyleInTextView
NSRange selectedRange = [textview selectedRange];
NSTextStorage *storage = [textview textStorage];
effectiveRange = [[storage string] paragraphRangeForRange: selectedRange];
Growl.framework into your application bundle.
Pick a class to be the contact point with Growl. Your AppController class is a good place. Import <Growl/Growl.h>
Set the delegate to the GrowlApplicationBridge:
[GrowlApplicationBridge setGrowlDelegate: self];
Doing this will eventually have the registrationDictionaryForGrowl delegate message called. Return a dictionary with two arrays (Which can be the same). These are the names of the alerts you will be posting. These are human-readable, so you'll want to use a localized string (which I've already set in the global variable here:
- (NSDictionary *) registrationDictionaryForGrowl
{
NSArray *notifications;
notifications = [NSArray arrayWithObject: g_timesUpString];
NSDictionary *dict;
dict = [NSDictionary dictionaryWithObjectsAndKeys:
notifications, GROWL_NOTIFICATIONS_ALL,
notifications, GROWL_NOTIFICATIONS_DEFAULT, nil];
return (dict);
} // registrationDictionaryForGrowl
And use this to post a notification:
[GrowlApplicationBridge notifyWithTitle: @"Woop! Time has expired!"
description: @"You have been waiting for 37 minutes"
notificationName: g_timesUpString
iconData: nil
priority: 0
isSticky: NO
clickContext: nil];
Consult the SDK documentation for more explanations of the features, but they are pretty self-explanitory.
// NSLog() writes out entirely too much stuff. Most of the time I'm
// not interested in the program name, process ID, and current time
// down to the subsecond level.
// This takes an NSString with printf-style format, and outputs it.
// regular old printf can't be used instead because it doesn't
// support the '%@' format option.
void QuietLog (NSString *format, ...) {
va_list argList;
va_start (argList, format);
printf ("%s\n", [[NSString stringWithFormat: format, argList] UTF8String]);
va_end (argList);
} // QuietLog
(Thanks to Quinn Taylor for a shorter version)
NSWindowController:
@interface BWInspector : NSWindowController
{
// ivars, IBOutlets, etc
}
// IBActions, etc
+ (BWInspector *) sharedInspector;
@end // BWInspector
Then make a nib file with the window, the controls, and other fun stuff you want. This one lives in English.lproj/BWInspector.nib
Then in that class method load the window:
+ (BWInspector *) sharedInspector
{
static BWInspector *g_inspector;
if (g_inspector == nil) {
g_inspector = [[BWInspector alloc]
initWithWindowNibName: @"BWInspector"];
assert (g_inspector != nil); // or other error handling
[g_inspector showWindow: self];
}
return (g_inspector);
} // sharedInspector
That will load the nib file, set up the connections and then open the window for display
Localizable.strings that lives in your English.lproj directory (or whatever localization directory is appropriate). It has this syntax:
"BorkDown" = "BorkDown"; "Start Timer" = "Start Timer"; "Stop Timer" = "Stop Timer";That is, a key followed by a localized value.
In your code, you can then use NSLocalizedString() or one of its variants:
[statusItem setTitle: NSLocalizedString(@"BorkDown", nil)];The second argument is ignored by the function. Obstensively it is a
/* comment */ in the strings file so that you can match the key back to what it is supposed to actually be.
svn status. Add this to your ~/.subversion/config file.
[miscellany] global-ignores = build *.mode* *.pbxuser *~.nib .DS_Store *~Now it won't bug me about the build directory the -per-user Xcode files, nib backup files, the #$&!! .DS_Store file that the Finder litters everywhere, and also don't bug me about emacs backup files.
You can search for global-ignores in that file to see some info about the setting. You might want to check out Spring Cleaning with Subversion too.
% cd '/Library/Application Support/Apple/Developer Tools/Project Templates'
% find . -name "*.pbxproj" -exec perl -pi -e "s/ZERO_LINK = YES/ZERO_LINK = NO/g" {} \; -print
- (void) iterateAttributesForString: (NSAttributedString *) string
{
NSDictionary *attributeDict;
NSRange effectiveRange = { 0, 0 };
do {
NSRange range;
range = NSMakeRange (NSMaxRange(effectiveRange),
[string length] - NSMaxRange(effectiveRange));
attributeDict = [string attributesAtIndex: range.location
longestEffectiveRange: &effectiveRange
inRange: range];
NSLog (@"Range: %@ Attributes: %@",
NSStringFromRange(effectiveRange), attributeDict);
} while (NSMaxRange(effectiveRange) < [string length]);
} // iterateAttributesForString
In Interface Builder 2: If you hold down the control key while resizing, though, the contents obey their spring configurations and will resize accordingly. Makes the process a whole lot easier.
In Interface Builder 3: If you hold down command while resizing, the contents obey their spring configurations and will resize accordingly. (This avoids the annoying process of resizing each widget within the windows afterwards.) Holding option while resizing will display the pixel values of the padding. Holding command-option while dragging displays both.
(muchos thankos to Quinn Taylor at the BYU CocoaHeads for the IB3 update)
setValue:forKeyPath: to stick the value back into the
bound object.
The bindings and path are ivars:
id selectionRectBinding;
NSString *selectionRectKeyPath;
In bind:toObject:withKeyPath:options: hang on to the binding
object and the key path and set up observing:
// hold on to the binding info
selectionRectBinding = observableObject;
selectionRectKeyPath = [observableKeyPath copy];
// connect KVO
[valuePathBinding addObserver: self
forKeyPath: selectionRectKeyPath
options: nil
context: NULL];
// new binding, needs to redraw
[self setNeedsDisplay: YES];
And in the mouseUp: handler, set the value back into the bound object:
// figure out the selection rectangle
NSRect selectionRect = [self normalizedSelectionRect];
// wrap in a value and tell the bound object the new value
NSValue *value;
value = [NSValue valueWithRect: selectionRect];
[selectionRectBinding setValue: value
forKeyPath: selectionRectKeyPath];
awakeFromInsert:
- (void) awakeFromInsert
{
[super awakeFromInsert];
NSDate *date = [NSDate date];
[self setValue: date forKey: @"when"];
} // awakeFromInsert
Put this in a convenient place:
+ (void) initialize
{
[NSTextFieldCell setDefaultPlaceholder: @""
forMarker: NSNotApplicableMarker
withBinding: NSValueBinding];
} // initialize
In Interface Builder, drag over the NSSearchField, and bind the predicate like this:
what contains[c] $value (assuming the attribute you're filtering is called what)
% svn copy file:///usr/local/svnroot/HackDie/trunk
file:///usr/local/svnroot/HackDie/tags/stable-1
-m "tag for HackDie internal stable release #1"
radius
- (float) radius
{
[self willAccessValueForKey: @"radius"];
float f = radius;
[self didAccessvalueForKey: @"radius"];
return (f);
} // radius
- (void) setRadius: (float) newRadius
{
[self willChangeValueForKey: @"radius"];
radius = newRadius;
[self didChangeValueForKey: @"radius"];
} // setRadius
- (NSString *) name
{
[self willAccessValueForKey: @"name"];
NSString *string = [self primitiveValueForKey: @"name"];
[self didAccessValueForKey: @"name"];
} // name
- (void) setName: (NSString *) x
{
[self willChangeValueForKey: @"name"];
[self setPrimitiveValue: x forKey: @"name"];
[self didChangeValueForKey: @"name"];
} // setName
self has methods to get the managed object context and managed object model (Apple's CoreData Application template does)
Thanks to Evan Moseman who pointed me at the new easy way to do it:
NSManagedObjectContext *moc = [self managedObjectContext];
NSManagedObject *obj = [NSEntityDescription
insertNewObjectForEntityForName :@"Condition"
inManagedObjectContext: context];
And the older, more manual way if you need some more control over the process:
NSManagedObjectContext *moc = [self managedObjectContext];
NSManagedObjectModel *mom = [self managedObjectModel];
NSEntityDescription *entity;
entity = [[mom entitiesByName] objectForKey: @"Condition"];
NSString *className;
className = [entity managedObjectClassName];
NSManagedObject *obj;
obj = [[NSClassFromString(className) alloc]
initWithEntity: entity
insertIntoManagedObjectContext: moc];
[obj autorelease];
[obj setValue: @"nook" forKey: @"name"];
[obj setValue: [NSNumber numberWithInt: 23] forKey: @"ranking"];
mutableSetValueForKey: This returns a proxy that mutates the relationship and does KVO notifications. Think of the name as "[NS]MutableSet" "valueForKey" rather than "mutableSetValue" "forKey", because it returns a mutable set that you manipulate
NSMutableSet *employees; employees = [department mutableSetValueForKey: @"employees"]; [employees addObject: newEmployee]; [employees removeObject: sackedEmployee];
NSSortDescriptor *sorter;
sorter = [[NSSortDescriptor alloc]
initWithKey: @"when"
ascending: YES];
[fetchRequest setSortDescriptors:
[NSArray arrayWithObject: sortDescriptor]];
- (void) setWhenSortDescriptors: (NSArray *) descriptors
{
} // setWhenSortDescriptors
- (NSArray *) whenSortDescriptors
{
NSSortDescriptor *sorter;
sorter = [[[NSSortDescriptor alloc]
initWithKey: @"when"
ascending: NO] autorelease];
return ([NSArray arrayWithObject: sorter]);
} // whenSortDescriptors
This is for a 'permanent' sorting, not allowing the user to change the sorting. Also, new/changed objects added to the collection don't appear to be placed in sorted order.
Don't use the XML store, but instead use the SQLite store
If an entity has a large data blob as an attribute, you should make a separate entity for that attribute.
e.g. a Person has a 'photo' attribute. Create a Photo entity with a single attribute (the data) and a relationship back to the person (if it makes sense, usually it does), and a relationship from the Person to the Photo entity.
This means that the photo data will only be loaded from the persistent store if you actually use it.
(markd: this also has the nice side effect of letting the db put all the blob data elsewhere, rather than in with the other data, giving you better locality of reference to your Person data without slogging through all the Photo jazz if you don't need it)
This
% cd '/Library/Application Support/Apple/Developer Tools/Project Templates'
% find . -name "*.pbxproj" -exec perl -pi -e "s/ZERO_LINK = YES/ZERO_LINK = NO/g" {} \; -print
% svn log -v -r 373 ------------------------------------------------------------------------ r373 | markd | 2005-06-22 16:05:38 -0400 (Wed, 22 Jun 2005) | 1 line Changed paths: A /xyz/trunk/docs/bugreports/4158233-nsbrowser A /xyz/trunk/docs/bugreports/4158233-nsbrowser/browser-rows.png A /xyz/trunk/docs/bugreports/4158233-nsbrowser/browserdraw.tar.gz A /xyz/trunk/docs/bugreports/4158233-nsbrowser/bug.txt initial revision ------------------------------------------------------------------------and you can look repository-wide too:
% svn log -v -r 373 file:///usr/local/svnroot/
environment method
~/.MacOSX/environment.plist. Make the root a Dictionary, and add the key/value pairs (all strings) to it. Don't forget to logout and back in.
Set your Versioning System (VERSIONING_SYSTEM) to "apple-generic".
Set your Current Project Version (CURRENT_PROJECT_VERSION) to some floating point value that you'll increment every build. You could just start at 1, unless you already have builds out there with other numbers.
This will generate and build a source file automatically that defines two globals whenever you build your project. One is a double corresponding to CURRENT_PROJECT_VERSION, the other is a string. The file is a derived source file; it won't be added to your project, but it will be built with it.
If you're building a framework, there are other build settings you'll probably want to set as well, such as Current Version (DYLIB_CURRENT_VERSION), Compatibility Version (DYLIB_COMPATIBILITY_VERSION), and VERSION_INFO_PREFIX.
To update the version number, you can use agvtool next-version or agvtool new-version.
% defaults write com.apple.mail PreferPlainText -bool TRUE
% tar cf - ./stuff | ssh theOtherMachine "tar xf -"
M-x narrow-to-region
Hides everything not in the current region.
M-x widen
% svn mkdir -m "initial revision" file:///usr/local/svnroot/MilleBorks
% mkdir DungeonBorkventure % cd DungeonBorkventure % mkdir branches tags trunk # put source files into trunk % svn import . file:///usr/local/svnroot -m "initial checkin" % cd .. % rm -rf DungeonBorkventure # or move it aside % svn checkout file:///usr/local/svnroot/trunk DungeonBorkventure % cd DungeonBorkventure # and get to work
First, make the directories in the repository:
% svn mkdir -m "initial revision" file:///usr/local/svnroot/DungeonBorkventure % svn mkdir -m "initial revision" file:///usr/local/svnroot/MilleBorksThen import the different code bases:
% cd /path/to/DungeonBorkventure % svn import -m "initial revision" . file:///usr/local/svnroot/DungeonBorkventure % cd /path/to/MilleBorks % svn import -m "initial revision" . file:///usr/local/svnroot/MilleBorksThen checkout working copies of the projects:
% cd /work/and/play/area % svn checkout file:///usr/local/svnroot/MilleBorks/trunk MilleBorks % svn checkout file:///usr/local/svnroot/DungeonBorkventure/trunk DungeonBorkventure
% svnadmin create --fs-type fsfs /usr/local/svnroot/
% svn status --show-updates
You can specify files and directories of interest.
-d option. Otherwise CVS will just ignore any directories added in the branch.
% cvs update -Ad #switch to the trunk (if you haven't already) % cvs update -d -j branch_name [resolve conflicts] % cvs commit -m "whatever"
(Courtesy of Jeremy Stein)
% awk '{ for (f=1; f <= NF; f++) { if (f != 1 && f != 9) printf("%s ", $f);} printf("
");}' < ook.txt
Or you can put this at the end of a pipeline instead of directing a text file into the command. (courtesy of DougEDoug)
(gdb) call (id) objc_getClass("NSBundle")
$1 = (struct objc_object *) 0xa0a051d8
gdb) call (id)[$1 bundleWithPath:@"/blah/blah/PoseAsClassBundle.bundle"]
$2 = (struct objc_object *) 0x51467e0
(gdb) call (BOOL)[$2 load]
Reading symbols for shared libraries . done
NSUserName() or NSFullUserName().
Thanks to Peter Hosey for letting us know about a better API for getting these.
flagsChanged: (NSEvent *) event and look at the event there. (flagsChanged: comes from NSResponder, so any responder in the responder chain can react to it)
[searchArrayController addObserver: self
forKeyPath: @"selectionIndexes"
options: NSKeyValueObservingOptionNew
context: NULL];
This makes self an observer of the searchArrayController. We'll get notified when the selectionIndexes value changes, and we'll be notified with the new value.
When the notification happens, this method (invoked against the self used earlier) is invoked:
- (void) observeValueForKeyPath: (NSString *) keyPath
ofObject: (id) object
change: (NSDictionary *) change
context: (void *) context
{
} // observeValueForKeyPath
and you can poke around the arguments to see wha'happened.
- (oneway void) release;
{
// special case when count is 3, we are being retained twice by the object controller...
if ( [self retainCount] == 3 )
{
[super release];
[filesOwnerProxy setContent:nil];
return;
}
[super release];
}
observeValueForKeyPath: method. Register your observer like this:
[searchArrayController addObserver: self
forKeyPath: @"selectionIndexes"
options: NSKeyValueObservingOptionNew
context: NULL];
So now self's observeValue method will get invoked when selectionIndexes changes in the array controller.
@interface Blah : NSView
{
NSPoint grabOrigin;
NSPoint scrollOrigin;
}
@end // Blah
...
@implementation Blah
...
- (void) mouseDown: (NSEvent *) event
{
// deal in window coordinates. there is a scrolling problem
// if using view coordinates because view coordinates
// can get transformed
grabOrigin = [event locationInWindow];
NSClipView *contentView;
contentView = (NSClipView*)[layerView superview];
scrollOrigin = [contentView bounds].origin;
} // mouseDown
- (void) mouseDragged: (NSEvent *) event
{
NSPoint mousePoint;
mousePoint = [event locationInWindow];
float deltaX, deltaY;
deltaX = grabOrigin.x - mousePoint.x;
deltaY = mousePoint.y - grabOrigin.y;
NSPoint newOrigin;
newOrigin = NSMakePoint (scrollOrigin.x + deltaX,
scrollOrigin.y + deltaY);
[layerView scrollPoint: newOrigin];
} // mouseDragged
...
@end // Blah
You can be fancy and check for the option key by look at [event modifierFlags] and looking for NSAlternateKeyMask, and also use the NSCursor open/closedHandCursor.
- (void) keyDown: (NSEvent *) event
{
NSString *chars = [event characters];
unichar character = [chars characterAtIndex: 0];
if (character == 27) {
NSLog (@"ESCAPE!");
}
} // keyDown
NSDeleteCharacter in the event's character string:
- (void) keyDown: (NSEvent *) event
{
NSString *chars = [event characters];
unichar character = [chars characterAtIndex: 0];
if (character == NSDeleteCharacter) {
NSLog (@"Delete!");
}
} // keyDown
- (NSArray *) layers
{
return (layers);
} // layers
- (void) insertObject: (id) obj
inLayersAtIndex: (unsigned) index
{
[layers insertObject: obj atIndex: index];
} // insertObjectInLayers
- (void) removeObjectFromLayersAtIndex: (unsigned) index
{
[layers removeObjectAtIndex: index];
} // removeObjectFromLayersAtIndex
[self performSelector: @selector(whatever) withObject: nil afterDelay: 0];
The selector gets posted after a sub event loop finishes. You can use this for finding out when a live slider is done being manipulated, for instance
First, in the +initialize for the class that's going to be observed:
+ (void) initialize
{
NSArray *keys;
keys = [NSArray arrayWithObjects: @"showMajorLines", @"minorWeight",
@"minorColor", @"minorWeightIndex", @"minorColorIndex",
@"majorWeightIndex", @"majorColorIndex", nil];
[BWGridAttributes
setKeys: keys
triggerChangeNotificationsForDependentKey: @"gridAttributeChange"];
} // initialize
So now when "showMajorLines" changes, "gridAttributeChange" will also be observed. KVO requires there actually must exist a gridAttributeChange method (or ivar I presume) before it'll do the notification to observing objects, so there needs to be a do-nothing method:
- (BOOL) gridAttributeChange
{
return (YES);
} // gridAttributeChange
So now the view can do this:
- (void) setGridAttributes: (BWGridAttributes *) a
{
[attributes removeObserver: self
forKeyPath: @"gridAttributeChange"];
[a retain];
[attributes release];
attributes = a;
[a addObserver: self
forKeyPath: @"gridAttributeChange"
options: NSKeyValueObservingOptionNew
context: NULL];
} // setGridAttributes
And will get updated whenever an individual attribute changes.
First step was to make the tools inherit from NSResponder:
@interface BWTool : NSResponder
{
// blah
}
// ... more blah
@end // BWTool
Then, in the view class where the tool gets set, put the tool in the responder chain by setting its next responder to be the view's current next responder. If a different tool gets set, take the current tool's next responder and give it to the new tool. Otherwise the view gets its original next responder back:
- (void) setTool: (BWTool *) newTool
{
NSResponder *nextResponder;
// this is the next element in the chain
if (currentTool != nil) {
nextResponder = [currentTool nextResponder];
} else {
nextResponder = [self nextResponder];
}
// decide who gets to point to the next responder
if (newTool != nil) {
// stick the tool into the chain
[self setNextResponder: newTool];
[newTool setNextResponder: nextResponder];
} else {
// cut the tool out of the chain (if there was one)
[self setNextResponder: nextResponder];
}
[newTool retain];
[currentTool release];
currentTool = newTool;
} // setDrawTool
And now tools can have action methods, and menu items that enable and disable appropriately.
To make editing end, you need to subclass NSTableView and add code to catch the textDidEndEditing delegate notification, massage the text movement value to be something other than the return and tab text movement, and then let NSTableView handle things.
// make return and tab only end editing, and not cause other cells to edit
- (void) textDidEndEditing: (NSNotification *) notification
{
NSDictionary *userInfo = [notification userInfo];
int textMovement = [[userInfo valueForKey:@"NSTextMovement"] intValue];
if (textMovement == NSReturnTextMovement
|| textMovement == NSTabTextMovement
|| textMovement == NSBacktabTextMovement) {
NSMutableDictionary *newInfo;
newInfo = [NSMutableDictionary dictionaryWithDictionary: userInfo];
[newInfo setObject: [NSNumber numberWithInt: NSIllegalTextMovement]
forKey: @"NSTextMovement"];
notification =
[NSNotification notificationWithName: [notification name]
object: [notification object]
userInfo: newInfo];
}
[super textDidEndEditing: notification];
[[self window] makeFirstResponder:self];
} // textDidEndEditing
(Thanks to Steven Jacowski for a tweak that ends editing on clicks on different cells)
- (BOOL) tableView: (NSTableView *) view
writeRows: (NSArray *) rows
toPasteboard: (NSPasteboard *) pboard
in your table view data source (and don't forget to hook up the datasource if you use bindings to populate the tableview)
-setDropRow:dropOperation:, like so:
- (NSDragOperation) tableView: (NSTableView *) view
validateDrop: (id ) info
proposedRow: (int) row
proposedDropOperation: (NSTableViewDropOperation) op
{
// have the table highlight on-row / between-row correctly
[view setDropRow: row
dropOperation: op];
// or use whatever drag operation is appropriate
NSDragOperation dragOp = NSDragOperationCopy;
return (dragOp);
} // validateDrop
and in the acceptDrop method, look at the operation:
- (BOOL) tableView: (NSTableView *) view
acceptDrop: (id ) info
row: (int) row
dropOperation: (NSTableViewDropOperation) op
{
if (op == NSTableViewDropOn) {
// replace existing
} else if (op == NSTableViewDropAbove) {
// add new
} else {
NSLog (@"unexpected operation (%d) in %s",
op, __FUNCTION__);
}
// documentation doesn't actually say what this signifies
return (YES);
} // acceptDrop
StitchEdit[3522] *** class error for 'BWRawPath': class not loaded
In a convenient place (like in the +load method of your class), use NSUnarchiver's decodeClassName:asClassName:
@implementation BWSymbol
+ (void) load
{
[NSUnarchiver decodeClassName: @"BWRawPath"
asClassName: @"BWSymbol"];
// No need to [super load] - the superclass +load has already
// been invoked automatically by the runtime.
} // load
Note that this won't help you if you're wanting to rename something that was added via encodeValueOfObjCType:. You'll have to write some code to unarchive your data using the old @encode(oldName) and then re-archive it as @encode(newName)
(Thanks to Greg Miller for spotting an error in this quickie)