[comp.sys.mac.programmer] SFGetFolder

jb@aries5.uucp (10/31/89)

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.

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.

Even if you do not have any code to do this, I am interested in feedback
on the area of user interface.  If there is enough interest I will summarize
results.

Thanks in advance

Jim Bruyn

oster@dewey.soe.berkeley.edu (David Phillip Oster) (11/01/89)

Tim Maroney posted some nice suggestions about selecteing folders a while
back, and TOPs seems to have a decent user interface for this.  Remember,
you need to handle non-HFS volumes such as 400k floppies, and you need to
let the user select the entire drive on HFS volumes.  The latter
requirement makes it hard to just point at a folder in SFGetFile() and say
"that's the one", since drivers aren't listable in the scrollbale part of
SFGetFile().

lsr@Apple.COM (Larry Rosenstein) (11/01/89)

DTS Sample Code #18 shows all the wonderful things you can do with Std 
File, including selecting directories.  I think they implement the same 
interface that MPW uses for its 'GetFilename -d' command.

Larry Rosenstein, Apple Computer, Inc.
Object Specialist

Internet: lsr@Apple.com   UUCP: {nsc, sun}!apple!lsr
AppleLink: Rosenstein1

6600pete@hub.UUCP (11/01/89)

From article <32277@ucbvax.BERKELEY.EDU>, by oster@dewey.soe.berkeley.edu (David Phillip Oster):
> ...you need to
> let the user select the entire drive on HFS volumes.  The latter
> requirement makes it hard to just point at a folder in SFGetFile() and say
> "that's the one", since drivers aren't listable in the scrollbale part of
> SFGetFile().

StuffIt allows you to select entire volumes by making sure nothing is selected
in the file list. I suppose this is done by scrolling to the bottom of the
list and clicking the blank space at the bottom. This is not intuitive, though.

Maybe it's time someone reqqrote Standard File and released the code to the PD.

(Or maybe we should just wait for System 8.0, which may (or may not) unify the
Desktop metaphor and Standard File.)
Pete Gontier   : pete@cavevax.ucsb.edu; outgoing .UUCP addresses bounce
Editor, Macker : Online Macintosh Programming Journal; mail for subscription
Hire this kid  : Mac, DOS, C, Pascal, asm, excellent communication skills
Underground    : Internet BBS via rlogin 128.11.41.100 -l bbs

tim@hoptoad.uucp (Tim Maroney) (11/01/89)

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

jtn@zodiac.ADS.COM (John Nelson) (11/10/89)

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.
>
>The problem that I have with the implementations that I have seen of
>selecting a folder i.e. 'Set Transfer Directory...'

I'm writing an application that basically does just this, however it
doesn't consist of one function call.  You have to write a filter
function for SFPGetFile to handle the directories and some parameter
block routines to stat the files to see if they're files or folders.

It isn't finished yet so I really don't know if it can be done this
way or not.  I'll let you all know what happens when it is done.




John T. Nelson			UUCP: sun!sundc!potomac!jtn
Advanced Decision Systems	Internet:  jtn@potomac.ads.com
1500 Wilson Blvd #512; Arlington, VA 22209-2401		(703) 243-1611

keith@Apple.COM (Keith Rollin) (11/15/89)

In article <9741@zodiac.ADS.COM> jtn@ads.com (John Nelson) writes:
>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.
>>
>>The problem that I have with the implementations that I have seen of
>>selecting a folder i.e. 'Set Transfer Directory...'

MacDTS has released a sample program that shows you how to do this, among other
tricks with StdFile. You can get this from APDA, BBS's and finer FTP sites
everywhere, including ours. Basically, it involves writing a file filter
procedure that always returns TRUE. Here is the relevant code:

FUNCTION FoldersOnly(p:ParmBlkPtr):BOOLEAN;

{ Normally, folders are ALWAYS shown, and aren't even passed to        }
{ this file filter for judgement. Under such circumstances, it is      }
{ only necessary to blindly return TRUE (allow no files whatsoever).   }
{ However, Standard File is not documented in such a manner, and       }
{ this feature may not be TRUE in the future. Therefore, we DO check   }
{ to see if the entry passed to us describes a file or a directory.    }

    BEGIN
        FoldersOnly := TRUE;
        IF BTst(p^.ioFlAttrib,4) THEN FoldersOnly := FALSE;
    END;

Hope this helps,

-- 
------------------------------------------------------------------------------
Keith Rollin  ---  Apple Computer, Inc.  ---  Developer Technical Support
INTERNET: keith@apple.com
    UUCP: {decwrl, hoptoad, nsc, sun, amdahl}!apple!keith
"Argue for your Apple, and sure enough, it's yours" - Keith Rollin, Contusions

silverio@brahms.berkeley.edu (C J Silverio) (11/15/89)

jb@aries5 asks:
   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.

Keith Rollin, Apple DTS whipping boy, kindly replies:
   MacDTS has released a sample program that shows you how to do this,
   among other tricks with StdFile. You can get this from APDA, BBS's and
   finer FTP sites everywhere, including ours.

Here's the rough plan for FTP'ing the stuff from Apple.com. Remember
to be courteous about hogging resources. Also, there is so much code
(over 2 Megs unstuffed) that it might be cheaper and easier to buy it
from APDA.

I haven't had much time to look at all the code, but it seems really
helpful. I only had one system crash (a good one!) in the whole pile.

1 ftp apple.com as anonymous, supply userid as password.
2 cd /pub/dts/mac/sc.
3 get files like unto a crazy person.
4 Use Stuffit! to upack all this. The stuffed files require 3 800K
  floppies. At 1200 baud, this will take 70 quadrillion years
  (subjective time) to download.

Thanks, Apple DTS, for supplying this bounty. It's half of what I need
to be a good Mac programmer.

tim@hoptoad.uucp (Tim Maroney) (11/16/89)

I entered complete source code to do this here.  So far, I have not
received any responses, and the discussion has proceeded without
reference to this source code.  Did it get out?
-- 
Tim Maroney, Mac Software Consultant, sun!hoptoad!tim, tim@toad.com

Feminism that refuses to use the word "patriarchy" is kin to abolitionism
that refuses to use the word "slavery".