Path: utzoo!utgpu!watmath!att!ucbvax!hoptoad!tim From: tim@hoptoad.uucp (Tim Maroney) Newsgroups: comp.sys.mac.programmer Subject: Re: SFGetFolder Message-ID: <8858@hoptoad.uucp> Date: 31 Oct 89 20:56:51 GMT References: <709@maytag.waterloo.edu> Reply-To: tim@hoptoad.UUCP (Tim Maroney) Organization: Eclectic Software, San Francisco Lines: 224 In article <709@maytag.waterloo.edu> jb@aries5.UUCP () writes: >Has anybody written a SFGetFolder/SFPutFolder routine -- What I want to be > able to do is select or create a folder in a nice manner. This is the second most common question here, after "How do I get a full file name?" Apple, are you listening? How about a couple of new traps in System 7.0? Anyway, the answer is yes. LSC source code at end of message. >The problem that I have with the implementations that I have seen of >selecting a folder i.e. 'Set Transfer Directory...' in Telnet is that it >is not intuitive whether I am selecting the directory of the folder >that is hilighted or the directory that I am currently in. That's very perceptive. The solution is not to allow the user to click on the "Use Folder" (or whatever) button when a folder is selected, and to put a sentence in the dialog explaining which folder is being dealt with just to make sure there's no confusion. Hardly anyone does this in practice, but everyone should. Here goes with the code. static Boolean good, noSys, needWrite, allowFloppy, allowDesktop; static SFReply reply; pascal Boolean FolderFilter(pb) FileParam *pb; { return true; } pascal short FolderItems(item, dlog) short item; DialogPtr dlog; { if (item == 2) { good = true; item = 3; } return item; } pascal void FolderEvents(dialog, event, item) DialogPtr dialog; EventRecord *event; short *item; { ControlHandle ch; short type; Rect r; HVolumeParam vp; /* disable if a directory is selected in the list */ GetDItem(dialog, 2, &type, &ch, &r); if (reply.fType) { HiliteControl(ch, 255); return; } /* get information on the volume */ vp.ioNamePtr = (StringPtr)0; vp.ioVRefNum = -SFSaveDisk; vp.ioVolIndex = 0; if (PBHGetVInfo(&vp, false)) HiliteControl(ch, 255); else if (vp.ioVSigWord != 0x4244) /* HFS? */ HiliteControl(ch, 255); else if (vp.ioVDRefNum >= 0 || vp.ioVDrvInfo == 0) /* ejected? */ HiliteControl(ch, 255); else if (needWrite && (vp.ioVAtrb & 0x8080)) /* locked? */ HiliteControl(ch, 255); else if (!allowFloppy && vp.ioVDRefNum == -5) /* floppy? */ HiliteControl(ch, 255); else if (!allowDesktop && CurDirStore == 2) /* desktop? */ HiliteControl(ch, 255); else if (noSys && CurDirStore == vp.ioVFndrInfo[0]) /* blessed? */ HiliteControl(ch, 255); else HiliteControl(ch, 0); } GetFolder(name, volume, folder, writeable, system, floppy, desktop) char *name; short *volume; long *folder; Boolean writeable, system, floppy, desktop; { short oldvol = -SFSaveDisk; long oldfolder = CurDirStore; Point where; SetPt(&where, 55, 55); good = false; if (*volume && *folder) { SFSaveDisk = -*volume; CurDirStore = *folder; } needWrite = writeable, noSys = !system, allowFloppy = floppy; allowDesktop = desktop; SFPGetFile(where, (char *)0, FolderFilter, -1, 0, FolderItems, &reply, 14, FolderEvents); if (!good) return false; *volume = -SFSaveDisk; *folder = CurDirStore; FullFileName(name, "", -SFSaveDisk, CurDirStore); SFSaveDisk = -oldvol; CurDirStore = oldfolder; return true; } Note -- you need dialog 14 for this to work. This is a slightly hacked version of Standard File. Here's a Rez listing made by DeRez: resource 'DLOG' (14, "Standard File for a Folder") { {55, 78, 312, 439}, dBoxProc, invisible, noGoAway, 0x0, 14, "" }; resource 'DITL' (14, "Standard File for a Folder") { { /* array DITLarray: 11 elements */ /* [1] */ {33, 507, 51, 587}, Button { enabled, "Open" }, /* [2] */ {135, 256, 153, 336}, Button { enabled, "Use Folder" }, /* [3] */ {161, 256, 179, 336}, Button { enabled, "Cancel" }, /* [4] */ {40, 247, 62, 348}, UserItem { disabled }, /* [5] */ {69, 256, 87, 336}, Button { enabled, "Eject" }, /* [6] */ {95, 256, 113, 336}, Button { enabled, "Drive" }, /* [7] */ {40, 15, 185, 246}, UserItem { enabled }, /* [8] */ {40, 229, 185, 246}, UserItem { enabled }, /* [9] */ {124, 251, 125, 339}, UserItem { disabled }, /* [10] */ {97, 606, 198, 702}, StaticText { disabled, "" }, /* [11] */ {196, 15, 246, 345}, StaticText { disabled, "Move until ^0 is shown in the small rect" "angle at the top, above the list of file" "s and folders. Then click \"Use Folder\"." } } }; The "^0" is filled in by the caller of GetFolder, using ParamText. Granted, that's not the cleanest solution, but GetFolder already has too many parameters. If you are only using this for one thing in your software, then you can always just fill it in in the dialog item in the resource file instead. No flames from symbolic-constant freaks. It's just as easy to change a number in a routine as it is to change it in #defines, if it only appears once. And the dialog items are not going to change; their order is mandated by Standard File. I do apologize for the use of globals, but as all Standard File hackers know, the system doesn't give you the ability to do without them easily. If your filter procs were passed a pointer to the reply record, you could embed it in a structure and use its pointer as a structure pointer, but it doesn't get passed. And touching the dialog refCon causes an explosion. If you really can't cope with globals, you're stuck with using a button refCon or something, and that's a bit of a mess. (Also, I don't think I'd figured out that kind of devious solution back when I wrote this.) The one great omission in this code is a "New" button. One day I'll get around to adding it. -- Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com FROM THE FOOL FILE: "Women's wages are 56% of men's -- but that's not necessarily evidence of discrimination in employment." -- Clayton Cramer in news.groups and soc.women