\subsubsection{Idea}

The program {\tt df} reports file system disk space usage. It displays
these statistics in human-readable form when invoked with the {\tt -h}
option. Its output might look like this:

\begin{center}
\begin{verbatim}
Filesystem            Size  Used Avail Use% Mounted on
/dev/hda3             7.7G  5.8G  1.6G  79% /
/dev/hda1              61M  4.8M   53M   9% /boot
shmfs                 126M     0  126M   0% /dev/shm
/dev/loop0             93K   54K   34K  62% /tmp/mountpoint
/dev/fd0              1.4M  500K  840K  38% /media/floppy
\end{verbatim}
\end{center}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{figure}
\begin{center}
\includegraphics[height=8cm]{tasks/gdf/gdf.eps}
\caption{\label{fig:gdf}
Graphical frontend to {\tt df} displays file system disk usage.
}
\end{center}
\end{figure}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


The goal is to have a graphic frontend to {\tt df} that displays the
disk space usage for every disk in a graphical format like that shown
in Fig.\ref{fig:gdf}. It should update from time to time in order to
reflect dynamic changes in disk space usage.

\subsubsection{Implementation}

The program consists of two components: the first is the class {\tt
PercentageView}, which provides the functionality to display a couple
of lines of data above a pie chart that represents the current
percentage. We will instantiate one {\tt PercentageView} for every
disk. The second component is a controller that acts as the
application's delegate, runs {\tt df} at one-second intervals and
updates the percentages accordingly.

Start with some basic definitions that collect constants at the start
of the program.

\begin{lstlisting}{}
#import <Foundation/Foundation.h>
#import <AppKit/AppKit.h>

// skeleton by Fred Kiefer

#define FONTSIZE        11
#define SEPARATOR        4

#define DFBIN   @"/bin/df"

#define PWIDTH         160
#define PHEIGHT        100 
\end{lstlisting}

These settings specify the font size to use, the distance from the pie
to the margin, where {\tt df} is to be found and the size of a single
{\tt PercentageView}.

A {\tt PercentageView} needs to remember the percentage and the lines
that make up the title, which we store in an {\tt NSArray}.

\begin{lstlisting}{}
@interface PercentageView : NSView
{
    NSArray *titles;
    int percentage;
}

- initWithTitles:(NSArray *)ts andFrame:(NSRect)frame;
- setTitles:(NSArray *)newts;
- setPercentage:(int)newpc;

- (void)drawRect:(NSRect)aRect;

@end
\end{lstlisting}

The interface declares the variables that hold the titles and the
percentage. The first three methods permit to initialize a {\tt
PercentageView} with a given frame and a set of titles, as well as to
set the percentage and the titles.

\begin{lstlisting}{}
@implementation PercentageView

- initWithTitles:(NSArray *)ts andFrame:(NSRect)frame
{
    [super initWithFrame:frame];
    titles = ts;
    [titles retain];

    percentage = 0;

    return self;
}

- setTitles:(NSArray *)newts
{
    [titles release];

    titles = newts;
    [titles retain];
    [self setNeedsDisplay:YES];

    return self;
}

- setPercentage:(int)newpc
{
    if(newpc==percentage){
        return self;
    }

    percentage = newpc;
    [self setNeedsDisplay:YES];

    return self;
}
\end{lstlisting}

The core of this class lies is the method {\tt drawRect:}, which
actually draws the pie chart and the titles. We read the bounds
rectangle so that we can use it to compute the coordinates of the
components (text, chart). We get the font of the desired size and set
the dictionary that contains the attributes of the title strings to
contain just one entry, namely the font that we obtained. We need to
iterate over the titles so we ask for the appropriate enumerator and
use the variable {\tt title} to hold a single line of the title
section. The variable {\tt height} is decremented by the height of a
line every time we draw a line. It starts at the top of the view and
moves downwards. The remaining variables pertain to the pie chart:
they store the radius, what angle of the pie is occupied, and where
the center of the chart lies. The variable {\tt slice} is used to hold
three different bezier paths that are used to draw the chart.

\begin{lstlisting}{}
- (void)drawRect:(NSRect)aRect
{
    NSRect bounds = [self bounds];
    
    NSFont *font = 
        [NSFont userFixedPitchFontOfSize:FONTSIZE];
    NSDictionary *attrs = 
        [NSDictionary dictionaryWithObjectsAndKeys:
                          font, NSFontAttributeName, nil];

    NSEnumerator *titleEnum = 
        [titles objectEnumerator];
    NSString *title;
    int height = 0;

    float radius;
    int angle = 360*percentage/100;
    NSPoint center;

    NSBezierPath *slice;
\end{lstlisting}

A {\tt PercentageView} has a white background and a black boundary,
which we draw first.

\begin{lstlisting}{}
    [[NSColor whiteColor] set];
    [NSBezierPath fillRect:bounds];

    [[NSColor blackColor] set];
    [NSBezierPath strokeRect:bounds];
\end{lstlisting}

The next step is to iterate over the titles starting at the top of the
view and draw each line in turn. We compute the dimensions of each
line and use the width to center the string and the height to
decrement the variable {\tt height}, i.e. the current position. Move
the statement
\begin{center}
      {\tt height -= size.height;}
\end{center}
to before the assignment of {\tt loc.x} and {\tt loc.y} if you have
the corrected version of
\begin{center}
      {\tt drawAtPoint:withAttributes:}, 
\end{center}
which works like {\tt PSmoveto()} and {\tt PSshow().}

\begin{lstlisting}{}
    while((title = [titleEnum nextObject])!=nil){
        NSSize size = [title sizeWithAttributes:attrs];
        NSPoint loc;

        loc.x = (bounds.size.width-size.width)/2;
        loc.y = height;
        [title drawAtPoint:loc withAttributes:attrs];

        height -= size.height;
    }
\end{lstlisting}

The lower part of the view forms a box that will contain the chart. It
is as wide as the view itself. Its height is given by the difference
between the height of the view and the height of the titles that were
drawn, i.e. the current value of the variable {\tt height} gives the
height of the box. We use the smaller of these two to determine the
radius of the pie, so that it is sure to fit inside the view. The
center of the pie lies in the middle of the x-axis and {\tt
radius+SEPARATOR} units below the last title that was drawn.

\begin{lstlisting}{}
    radius = (height < bounds.size.width ?
              height : bounds.size.width)/2
        - SEPARATOR;

    center.x = bounds.size.width/2;
    center.y = height-radius-SEPARATOR;
\end{lstlisting}

It remains to draw the pie chart. We draw three slices, starting with
a red one that shows the part of the disk that is occupied, which is
proportional to {\tt angle}. The slice starts at ``noon'' and ends
after it covers {\tt angle} degrees going clockwise.

\begin{lstlisting}{}
    slice = [NSBezierPath bezierPath];
    [slice moveToPoint:center];
    [slice appendBezierPathWithArcWithCenter:center
           radius:radius 
           startAngle:90 endAngle:90-angle
           clockwise:YES];
    [slice closePath];
    
    [[NSColor redColor] set];
    [slice fill];
\end{lstlisting}

The second slice shows the free space in blue. The start and end
angles are the same, except that we now draw in the opposite
direction.

\begin{lstlisting}{}
    slice = [NSBezierPath bezierPath];
    [slice moveToPoint:center];
    [slice appendBezierPathWithArcWithCenter:center
           radius:radius 
           startAngle:90 endAngle:90-angle
           clockwise:NO];
    [slice closePath];

    [[NSColor blueColor] set];
    [slice fill];
\end{lstlisting}

The last step is to draw a black boundary around the chart. There are
several ways to do this; we could use the code that we used to draw
the red and the blue slice. Here is a different approach.

\begin{lstlisting}{}
    slice = 
        [NSBezierPath 
            bezierPathWithOvalInRect:
                NSMakeRect(center.x-radius, center.y-radius,
                           2*radius, 2*radius)];

    [[NSColor blackColor] set];
    [slice stroke];
}

@end
\end{lstlisting}

We could have used PostScript operators to draw the view, but this
recipe is intended to provide examples of drawing with the application
kit.

The second main component is the controller. It stores the views in a
dictionary, where the keys are strings, i.e. the devices being
represented.

\begin{lstlisting}{}
@interface Controller : NSObject
{
    NSMutableDictionary *devices;
}

- (NSMutableDictionary *)runDF;
- (NSArray *)infoToTitles:(NSArray *)devInf;
- update;
- (void)applicationDidFinishLaunching:(NSNotification *)notif;

@end
\end{lstlisting}

The method {\tt runDF} is the heart of the controller. It runs {\tt
df} and returns a dictionary whose keys are the devices. Every key
points to an array that contains the output from {\tt df} for that
device. The output entry in the dictionary is an array obtained by
splitting the output line into fields, where fields are separated by
spaces. The method {\tt infoToTitles} turns such an array into an
array of titles suitable for display in a {\tt PercentageView}. The
method {\tt update} is invoked by a timer and invokes {\tt runDF} in
turn. It records and displays the changes. The last method is invoked
when the application finishes launching. It assembles the window and
displays an initial set of data.

The first thing {\tt runDF} does is to create the dictionary that will
hold the output lines for the devices. It declares an enumerator that
iterates over the raw lines that we obtain from {\tt df}. We  will
also need a character set containing spaces so that we can split lines
into fields.

\begin{lstlisting}{}
@implementation Controller

- (NSMutableDictionary *)runDF
{
    NSMutableDictionary *info =
        [NSMutableDictionary dictionaryWithCapacity:1];
    NSEnumerator *lineEnum;

    NSCharacterSet *space =
        [NSCharacterSet whitespaceAndNewlineCharacterSet];
\end{lstlisting}

The next step is to declare everything that we need to run {\tt df}
and obtain its output. The variables {\tt length} and {\tt bytes} will
be set to the respective values that describe the data. We need a task
object for {\tt df} and a pipe from its standard output. The file
handle {\tt reader} enables us to read from the pipe. The data is read
in chunks and appended to a mutable data object. The variable {\tt
line} holds a single line during processing of the output. We create a
pipe and obtain the file handle for reading from that pipe.

\begin{lstlisting}{}
    unsigned length;
    const char *bytes;

    NSTask *dfTask = [NSTask new];
    NSPipe *outputPipe;
    NSFileHandle *reader;

    NSData *chunk;
    NSMutableData
        *data = [NSMutableData dataWithCapacity:1024];
    NSString *line;

    outputPipe = [NSPipe pipe];
    reader = [outputPipe fileHandleForReading];
\end{lstlisting}

We must prepare the task before we can launch it. Therefore we set the
actual binary that will be executed and add {\tt -h} as an argument so
that we get human-readable output from {\tt df}. We set standard
output and standard error to our pipe in order to read those data.

\begin{lstlisting}{}
    [dfTask setLaunchPath:DFBIN];
    [dfTask setArguments:[NSArray arrayWithObject:@"-h"]];

    [dfTask setStandardOutput:outputPipe];
    [dfTask setStandardError:outputPipe];
\end{lstlisting}

Now we launch the task and read chunk after chunk until there are no
more data. Actually, the output from {\tt df} probably fits into a
single chunk. We wait for the task to exit and close the file
descriptor that is associated with the read end of the pipe so that it
becomes available for re-use. Recall that we will invoke this method
many times so as to present an up-to-date picture of file system
usage.

\begin{lstlisting}{}
    [dfTask launch];
    while((chunk = [reader availableData]) && 
          [chunk length]){
        [data appendData:chunk];
    }
    [dfTask waitUntilExit];

    close([reader fileDescriptor]);
\end{lstlisting}

We turn the data into a string, split it into lines and obtain an
enumerator of those lines.

\begin{lstlisting}{}
    length = [data length];
    bytes  = [data bytes];

    lineEnum =
        [[[NSString stringWithCString:bytes length:length] 
             componentsSeparatedByString:@"\n"]
            objectEnumerator];
\end{lstlisting}

The next step is to parse the data. We iterate over the array of lines
and process those lines that begin with the string {\tt /dev/},
i.e. refer to actual devices. We want to split the line into fields,
so we create a scanner from the current line and prepare an empty
array of fields. The variable {\tt field} holds a single field, of
which there are several per line.

\begin{lstlisting}{}
    while((line = [lineEnum nextObject])!=nil){
        if([line hasPrefix:@"/dev/"]){
            NSScanner *scn = 
                [NSScanner scannerWithString:line];
            NSMutableArray *fields =
                [NSMutableArray arrayWithCapacity:6];
            NSString *field;
\end{lstlisting}

We scan the fields in turn, until the scanner reaches the end. We skip
leading whitespace, read a single field and store it in the array of
fields for the current line. We record the fields in the dictionary
when the entire line has been scanned.

\begin{lstlisting}{}
            while([scn isAtEnd]==NO){
                [scn scanCharactersFromSet:space  
                     intoString:NULL];
                [scn scanUpToCharactersFromSet:space
                     intoString:&field];
                [fields addObject:field];
            }

            [info setObject:fields 
                  forKey:[fields objectAtIndex:0]];
        }
    }
\end{lstlisting}

The routine checks if any data have successfully been read and raises
an exception if there weren't any. It returns the dictionary whose
keys are the devices and whose values hold the fields from the
corresponding output line produced by {\tt df}.

\begin{lstlisting}{}
    if(![info count]){
        [NSException raise:NSGenericException
                     format:@"no data from %@", DFBIN];
    }

    return info;
}
\end{lstlisting}

The method {\tt infoToTitles} is very simple. It creates three title
lines from the array of fields: the name of the device, the mountpoint
and the current usage. The latter shows the current usage, the
capacity and the percentage.

\begin{lstlisting}{}
- (NSArray *)infoToTitles:(NSArray *)devInf
{
    NSString *devname, *mountpoint, *current;

    devname = [devInf objectAtIndex:0];
    mountpoint = [devInf objectAtIndex:5];

    current = [NSString stringWithFormat:@"%@ (%@) %@",
                        [devInf objectAtIndex:2],
                        [devInf objectAtIndex:1],
                        [devInf objectAtIndex:4]];

    return 
        [NSArray arrayWithObjects:devname, mountpoint,
                 current, nil];
}
\end{lstlisting}

The method {\tt update} is invoked by the timer. It runs {\tt df} to
obtain the dictionary of lines split into fields and prepares an
enumerator to iterate over the keys of the dictionary. The string {\tt
device} holds the current device.

\begin{lstlisting}{}
- update
{
    NSMutableDictionary *info = [self runDF];
    NSEnumerator *devEnum;
    NSString *device;

    devEnum = [[info allKeys] objectEnumerator];
\end{lstlisting}

The actual iteration is next. For each device, do the following:
extract the array of fields and look for the device in the dictionary
that maps devices to views. If there is an entry for the device then
there is a {\tt PercentageView} for it that must be updated. We set
the percentage from the fifth field and the set of titles as formatted
by {\tt infoToTitles}. This operation marks the view as needing
redisplay. The method returns when all updates have been recorded.

\begin{lstlisting}{}
    while((device = [devEnum nextObject])!=nil){
        NSArray *item = [info objectForKey:device];
        PercentageView *pview =
            [devices objectForKey:device];

        if(pview!=nil){
            [pview setPercentage:
                       [[item objectAtIndex:4] intValue]];
            [pview setTitles:[self infoToTitles:item]];
        }
    }
    
    return self;
}
\end{lstlisting}

The last method of the controller needs to prepare everything once the
application has finished launching. It declares variables to hold the
window that contains the percentage views, the main menu of the
application, the dimensions of the window, and its content view. It
invokes {\tt runDF} to obtain the data that it needs to get
started. It declares an enumerator in order to iterate over the
devices and a string to hold the current device name. The variable
{\tt dcount} tracks the number of views that have been created. There
is an invocation for use with the timer.

\begin{lstlisting}{}
- (void)applicationDidFinishLaunching:(NSNotification *)notif
{
    NSWindow *dfWin;
    NSMenu *menu = [NSMenu new];

    NSRect winRect;
    NSView *cview;

    NSMutableDictionary *info = [self runDF];
    NSEnumerator *devEnum;
    NSString *device;
    int dcount = 0;

    NSInvocation *inv;
\end{lstlisting}

The intial setup is straightforward: the menu contains just one entry,
namely the item ``quit.'' The width of the window's content view is
the same as that of a {\tt PercentageView}. The height of the window
is the number of device entries times the height of a single {\tt
PercentageView}. We intialize the content rectangle of the window and
allocate the window, set its title, and obtain its content view, to
which we'll be adding subviews.

\begin{lstlisting}{}
    [menu addItemWithTitle: @"Quit"
          action:@selector(terminate:)
          keyEquivalent:@"q"];
    [NSApp setMainMenu:menu];

    winRect = NSMakeRect(0, 0, PWIDTH, PHEIGHT*[info count]);
    dfWin = [[NSWindow alloc] 
                initWithContentRect:winRect
                styleMask: NSTitledWindowMask
                backing: NSBackingStoreBuffered
                defer: NO];
    [dfWin setTitle:@"gdf"];
    cview = [dfWin contentView];
\end{lstlisting}

The next step is important: we initialize the variable {\tt devices},
which contains the dictionary that maps devices to views. Next we
obtain an enumerator that iterates over the devices sorted
alphabetically, but in reverse order, since we build the subviews with
the lowest, i.e. last view first.

\begin{lstlisting}{}
    devices = 
        [NSMutableDictionary 
            dictionaryWithCapacity:[info count]];
    devEnum = 
        [[[info allKeys] 
             sortedArrayUsingSelector:@selector(compare:)]
            reverseObjectEnumerator];
\end{lstlisting}

The {\tt while} loop creates the views that go into the window. It
stores the array of fields for each device in the variable {\tt items}
and allocates the view. The frame rectangle has the standard width and
height and is positioned precisely above the previous frame rectangle
(we start at the bottom of the window). The titles are created with
{\tt infoToTitles}, as in the method {\tt update} that we discussed
earlier. We set the percentage once the view has been allocated and
the titles have been initialized. 

\begin{lstlisting}{}
    while((device = [devEnum nextObject])!=nil){
        NSArray *item = [info objectForKey:device];
        PercentageView *pview =
            [[PercentageView  alloc]
                initWithTitles:[self infoToTitles:item]
                andFrame:NSMakeRect(0, dcount*PHEIGHT,
                                    PWIDTH, PHEIGHT)];
        [pview setPercentage:
                   [[item objectAtIndex:4] intValue]];
\end{lstlisting}

We record the new view in the dictionary {\tt devices} and add it as a
subview to the content view of the window. We increment the counter
that determines the vertical position of the current view in the
window. We center the window once we are done creating subviews and
cause it to be displayed on the screen.

\begin{lstlisting}{}
        [devices setObject:pview forKey:device];
        [cview addSubview:pview];

        dcount++;
    }
    [devices retain];

    [dfWin center];
    [dfWin makeKeyAndOrderFront:nil];
\end{lstlisting}

It remains to set up the timer. We create an invocation that captures
the controller and the method {\tt update} for this purpose. The
invocation must persist throughout the lifetime of the application, so
we retain it. The last step is to start the timer, which fires once a
second.

\begin{lstlisting}{}
    inv = [NSInvocation 
              invocationWithMethodSignature: 
                  [self methodSignatureForSelector: 
                            @selector(update)]];
    [inv setSelector:@selector(update)];
    [inv setTarget:self];
    [inv retain];

    [NSTimer scheduledTimerWithTimeInterval:1
             invocation:inv
             repeats:YES];
}

@end
\end{lstlisting}

The main routine of this program is very simple. It creates an
autorelease pool, an application instance and the controller, which is
made the delegate of the application, and starts the application's run
loop.

\begin{lstlisting}{}
int main(int argc, char** argv, char **env)
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];
    NSApplication *app;
    
    app = [NSApplication sharedApplication];
    [app setDelegate:[Controller new]];
    [app run];

    [pool release];
    exit(0);
}
\end{lstlisting}

