
\subsubsection{Idea}

The purpose of this recipe is to implement a view that displays a
calendar. We will attempt to duplicate the look and the functionality
of the Unix {\tt cal} command, whose output looks like this:

{\tt
\begin{verbatim}
    November 2008
Su Mo Tu We Th Fr Sa 
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
\end{verbatim}
}

What the calendar view actually looks like is shown in figure
\ref{fig:calendar}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{figure}
\begin{center}
\includegraphics{misc/calendar/calendar.eps}
\caption{\label{fig:calendar}
Calendar view (buttons not part of the view)
}
\end{center}
\end{figure}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

The calendar view consists of four major components: a popup button to
the left that lets the user choose the month, one to right, that lets
the user choose the year, a text view that displays a string that
represents the current month and year and a set of seven tables, each
one column wide, that display the actual calendar.

The date calculation functions that come with GNUstep in the object
{\tt NSCalendarDate} are very powerful. We will see that we can
concentrate on designing the GUI.

\subsubsection{Calendar view: interface}

The program starts with the necessary headers. The popup button that
displays the year will offer a range from {\tt MINYEAR} to {\tt
  MAXYEAR}. We define a string data type that holds a single
day. Possible values are: the empty string, or strings like {\tt
  ``6''} or {\tt ``15''} up to {\tt ``31''}.

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

#define MINYEAR 1998
#define MAXYEAR 2016

typedef char calday[3];
\end{lstlisting}

The first two instance variables of the view are the two popup buttons
for month and year, respectively. The next three store the current
state of the calendar: day, month and year. They correspond to the
data being displayed. The seven tables (one for each day of the week)
are next. We also store the column names (the days of the week) and
the names of the twelve months of the year. These two arrays are
arrays of strings. The instance variable {\tt monyr} stores the text
view that displays the year and the month in the upper center of the
view. The last variable holds the actual data for the month being
displayed. There are six rows for a maximum of six weeks and seven
columns for seven days of the week.

\begin{lstlisting}{}
@interface Calendar: NSView
{
  NSPopUpButton *monthPopup;
  NSPopUpButton *yearPopup;
  
  int day;
  int month;
  int year;

  NSTableView *calTables[7];
  NSArray *calCols;
  NSArray *monthNames;

  NSText *monyr;

  calday calmatrix[6][7];
}
\end{lstlisting}

The method declarations start with the most important method, the one
that computes the calendar and stores the result in {\tt
  calmatrix}. Next is the standard view initializer that initializes
the view and creates the subviews (popup buttons, text view). There
are two methods that load data into the table. The first one returns
the number of rows, which is always equal to six and the second one
reads the appropriate C strings out of the data matrix ({\tt
  calmatrix}) and returns them as {\tt NSString objects}. The delegate
method for selection changes is used to trigger de-selection of the
previous selection in the other tables when a cell is selected in a
table. There are two action methods for the two popup buttons that are
invoked when the year or the month changes and trigger a re-compute of
the calendar. Finally we declare {\tt drawRect:}. This method does not
do a great deal of drawing, as it is handled mostly by the subviews.

\begin{lstlisting}{}
- computeCalendar;

- initWithFrame:(NSRect)frame;

- (int)numberOfRowsInTableView:(NSTableView *)aTableView;
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:
  (NSTableColumn *)aTableColumn row:(int)rowIndex;
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification;

- (void)yearChanged:(id)sender;
- (void)monthChanged:(id)sender;

- (void)drawRect:(NSRect)aRect;
\end{lstlisting}

The remaining methods let the user query and set the calendar view.
She may query the year, month and day or set a particular date through
year, month and day. The view may also be set from a string date,
which is parsed by {\tt NSCalendarView} according to one of its
formats. Finally, we may select a day of the current month or set the
calendar to today's date.

\begin{lstlisting}{}
- (int)year;
- (int)month;
- (int)day;

- (BOOL)setYear:(int)aYear Month:(int)aMonth Day:(int)aDay;
- (BOOL)setFromString:(NSString *)str format:(NSString *)fmt;

- (BOOL)selectDay;

- today;

@end
\end{lstlisting}

We need a table view that accepts click-throughs (first mouse),
otherwise the user would have to click twice when selecting a day in a
different column: once to activate the table and second, to select. We
want one click to be sufficient. This is the purpose of {\tt
  NSTableViewCT}.

\begin{lstlisting}{}
@interface NSTableViewCT : NSTableView

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent;

@end

@implementation  NSTableViewCT

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
  return YES;
}

@end
\end{lstlisting}

\subsubsection{Calendar view: implementation}

The method {\tt setMonYr} is a ``private'' method that positions and
sets the text field that displays the month and the year in the upper
center. To do this, it first creates the string to be displayed (like
``August 2005'' or ``June 2006''), stores it in the text view and
sizes the textview to just display this string and nothing else. The x
origin of the textview is half the available space after the width of
the text view has been subtracted. The y origin is computed from the
top. The width of the popups plus some padding is subtracted, as is
the width of the text view itself. Finally we move the textview to the
new origin and display the entire calendar.

\begin{lstlisting}{}
@implementation Calendar

- setMonYr
{
  [monyr 
    setString:
      [NSString stringWithFormat:@"%@ %d",
		[monthNames objectAtIndex:(month-1)],
		year]];
  [monyr sizeToFit];

  float 
    x = ([self bounds].size.width-[monyr bounds].size.width)/2,
    y = [self bounds].size.height
    - ([yearPopup bounds].size.height+5)
    - [monyr bounds].size.height;

  [monyr setFrameOrigin:NSMakePoint(x, y)];

  [self display];

  return self;
}
\end{lstlisting}

The method {\tt computeCalendar} is central to the functioning of the
view. It starts by creating a calendar date object for the current
year and month, which is used to fill the calendar matrix. There are
three phases: fill those days of the first week that belong to the
previous month with blanks, write the data for the current month, and
fill any remaining cells from the next month with blanks. The first
{\tt while} loop sets those cells that correspond to the previous
month to be empty by writing the string terminator ``zero'' into the
first character.

\begin{lstlisting}{}
- computeCalendar
{
  int pos = 0;

  NSCalendarDate *cd =
    [NSCalendarDate 
      dateWithYear:year month:month day:1 
      hour:0 minute:0 second:0
      timeZone:[NSTimeZone timeZoneWithAbbreviation:@"CEST"]];  

  while(pos < [cd dayOfWeek]){
    calmatrix[pos/7][pos%7][0] = 0;
    pos++;
  }
\end{lstlisting}

The {\tt do} loop writes the actual data for the month. To do this, it
writes the current day as a string into the appropriate position of
the matrix. It then uses {\tt NSCalendarDate} to advance a day. This
is done as long as the latter belongs to the current month. The
second {\tt while} loop fills the remaining cells with empty strings,
just like the first one. These remaining cells correspond to days of
the following month.

\begin{lstlisting}{}
  int calday = 1;
  do {
    sprintf(calmatrix[pos/7][pos%7], "%d", calday++);
    pos++;

    cd = [cd dateByAddingYears:0 months:0
	     days:1 hours:0 minutes:0 seconds:0];
  } while([cd monthOfYear]==month);

  while(pos < 6*7){
    calmatrix[pos/7][pos%7][0] = 0;
    pos++;
  }
\end{lstlisting}

Once the data matrix has been initialized with the new values, we
trigger a reload for every table, i.e. column and deselect all cells
of that column. This concludes the core method of the calendar view.

\begin{lstlisting}{}
  int tb;
  for(tb=0; tb<7; tb++){
    [calTables[tb] reloadData];
    [calTables[tb] deselectAll:self];
  }

  return self;
}
\end{lstlisting}

The purpose of {\tt initWithFrame:} is mostly to assemble the
subviews. It starts by allocating the popup button for the month and
sets the position where the menu should appear.

\begin{lstlisting}{}
- initWithFrame:(NSRect)frame
{
  [super initWithFrame:frame];

  monthPopup = 
    [[NSPopUpButton alloc] 
      initWithFrame:NSMakeRect(0,0, 100, 20)];
  [monthPopup setPreferredEdge:NSMinYEdge];
\end{lstlisting}

Next we declare and initialize the array of month names. After that we
iterate over all the names and add an item for each month to the
popup, setting its tag to the correct numeric value of the month. The
target of each item its the calendar view itself and the action is the
selector {\tt monthChanged:}. We size the popup to be the right size
for the menu of the twelve months.

\begin{lstlisting}{}
  monthNames =
    [NSArray 
      arrayWithObjects:
	@"January", @"February", @"March",
      @"April", @"May", @"June", @"July",
      @"August", @"September", @"October",
      @"November", @"December", nil];
  RETAIN(monthNames);

  int index;
  
  for(index=0; index<12; index++){
    [monthPopup 
      addItemWithTitle:
	[monthNames objectAtIndex:index]];

    id item = [monthPopup itemAtIndex:index];
    [item setTag:(index+1)];

    [item setTarget:self];
    [item setAction:@selector(monthChanged:)];
  }

  [monthPopup sizeToFit];
\end{lstlisting}

The x origin of the popup is the left margin, i.e. zero. The y origin
corresponds to the upper left corner of the view. We set the frame and
make the popup a subview of the calendar. We may now proceed to the
popup for the year.

\begin{lstlisting}{}
  NSRect mframe = [monthPopup frame];
  mframe.origin.x = 0;
  mframe.origin.y = frame.size.height-mframe.size.height;

  [monthPopup setFrame:mframe];

  [self addSubview:monthPopup];
\end{lstlisting}

We allocate and initialize the popup for the year and set the
preferred edge for the menu display.

\begin{lstlisting}{}
  yearPopup = 
    [[NSPopUpButton alloc] 
      initWithFrame:NSMakeRect(0,0, 100, 20)];
  [yearPopup setPreferredEdge:NSMinYEdge];
\end{lstlisting}

Next we iterate over the years from {\tt MINYEAR} to {\tt MAXYEAR} and
add them to the popup, setting the tag of each item to the numeric
value of the year it represents. The target of all items is the
calendar view and the action is the selector {\tt
  yearChanged:}. Finally we size it to be just the right size.

\begin{lstlisting}{}
  for(index=MINYEAR; index<=MAXYEAR; index++){
    [yearPopup 
      addItemWithTitle:
	[NSString stringWithFormat:@"%d", index]];

    id item = [yearPopup itemAtIndex:(index-MINYEAR)];
    [item setTag:index];

    [item setTarget:self];
    [item setAction:@selector(yearChanged:)];
  }

  [yearPopup sizeToFit];
\end{lstlisting}

It remains to compute the origin of the popup, which should be in the
upper right corner. Hence the x origin is obtained by subtracting the
width of the popup from the width of the calendar, similarly for the y
origin. We set the frame and make the popup a subview.

\begin{lstlisting}{}
  NSRect yframe = [yearPopup frame];
  yframe.origin.x = frame.size.width-yframe.size.width;
  yframe.origin.y = frame.size.height-yframe.size.height;

  [yearPopup setFrame:yframe];

  [self addSubview:yearPopup];
\end{lstlisting}

The assembly of the two popups is completed by setting them to the
current month and the current year as provided by {\tt
  NSCalendarDate}.

\begin{lstlisting}{}
  NSCalendarDate *cd =
    [NSCalendarDate calendarDate];

  month = [cd monthOfYear];
  [monthPopup 
    selectItemAtIndex:month-1];
  
  year = [cd yearOfCommonEra];
  [yearPopup
    selectItemAtIndex:year-MINYEAR];
\end{lstlisting}

The next section is concerned with the seven one-column tables that
show the actual calendar. We start by computing the width of a single
column (and hence, table). There are seven columns, one for each day
of the week. Next we initialize the array of column names.

\begin{lstlisting}{}
  int width = (frame.size.width-2)/7;

  NSRect cframe =
    NSMakeRect(1, 1,width,
	       frame.size.height*0.8);

  calCols =
    [NSArray arrayWithObjects:@"Sun", 
	     @"Mon", @"Tue", @"Wed", @"Thu",
	     @"Fri", @"Sat", nil];
  RETAIN(calCols);
\end{lstlisting}

We prepare an enumerator so that we may iterate over the column
names. The first task of each iteration is to create a click-through
table and store it in the {\tt calTables} instance variable. Its data
source is the calendar itself, and so is its delegate. We do not allow
column selection.

\begin{lstlisting}{}
  NSEnumerator *colEnum = [calCols objectEnumerator];
  NSString *colName; int ind = 0;

  while((colName = [colEnum nextObject]) != nil){
    NSTableView *calTable = 
      [[NSTableViewCT alloc] initWithFrame:cframe];

    calTables[ind] = calTable;
    [calTable setDataSource:self];
    [calTable setDelegate:self];
    [calTable setAllowsColumnSelection:NO];
\end{lstlisting}

The second task is to create the single column that fills every
table. We create the column, set it to be not editable, set its header
cell to be the name of the weekday and its width to be a seventh of
the available space. We add it to the table, shift the table to the
right to its proper position and make it a subview.

\begin{lstlisting}{}
    NSTableColumn *column =
      [(NSTableColumn *)[NSTableColumn alloc] 
			initWithIdentifier:colName];
    [column setEditable:NO];
    [[column headerCell] setStringValue:colName];
    [column setWidth:(frame.size.width-2)/7];

    [calTable addTableColumn:column];

    [calTable 
      setFrameOrigin:
	NSMakePoint(1 + ind*width, 1)];
    [self addSubview:calTable];
\end{lstlisting}

The assembly is completed by shifting the header view of the table
upwards and to the left to its proper position. We get its frame,
and shift the x origin to the left and the y origin upwards. We set
the frame of the header and add it as a subview.

\begin{lstlisting}{}
    NSTableHeaderView *calHeader = [calTable headerView];

    NSRect hframe = [calHeader frame];
    hframe.origin.x = ind*width;
    hframe.origin.y += [calTable frame].size.height;
    [calHeader setFrame:hframe];

    [self addSubview:calHeader];

    ind++;
  }
\end{lstlisting}

The last task is to create the text view for the month and the year in
the upper center region. We create it and set it to be neither
editable nor selectable. It is resizable, as it changes size when the
parameters change. It does not draw its background. We set an
appropriate font, initialize it with today's date, which we read
earlier, and make it a subview.

\begin{lstlisting}{}
  monyr = 
    [[NSText alloc] 
      initWithFrame:NSMakeRect(0, 0, 10, 10)];
  [monyr setEditable:NO];
  [monyr setSelectable:NO];

  [monyr setVerticallyResizable:YES];
  [monyr setHorizontallyResizable:YES];

  [monyr setDrawsBackground:NO];

  [monyr setFont:[NSFont labelFontOfSize:24]];

  [self setMonYr];
  [self addSubview:monyr];
\end{lstlisting}

This concludes the initializer. We set the day to today's date and return.

\begin{lstlisting}{}
  day = [cd dayOfMonth];
  [self selectDay];

  return self;
}
\end{lstlisting}

The next three methods are concerned with managing the data for the
seven table views. All tables have six rows.

\begin{lstlisting}{}
- (int)numberOfRowsInTableView:(NSTableView *)aTableView
{
  return 6;
}
\end{lstlisting}

We must be able to provide an object value for a given row and column
when asked for it. To do this, we iterate over the column tables until
we find the right table (the one being queried). We read the value for
the appropriate row out of the {\tt calmatrix} instance variable and
convert it into an Objective C string object. This is the required
datum, which we return.

\begin{lstlisting}{}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:
  (NSTableColumn *)aTableColumn row:(int)rowIndex
{
  int col = 0;

  for(col=0; col<7; col++){
    if(calTables[col] == aTableView){
      break;
    }
  }
  
  // NSLog(@"%d %d %s", rowIndex, col, calmatrix[rowIndex][col]);
  return [NSString stringWithFormat:@"%s", calmatrix[rowIndex][col]];
}
\end{lstlisting}

We must coordinate the selection among the seven tables. When the user
clicks on a cell in some column, we must de-select the previously
selected cell. Therefore we implement the delegate method ``selection
did change''. It is invoked at two levels: first, when a new date is
selected in a table, and second, recursively, when the remaining
tables resign their selection on being required to do so in a loop
from the first level. This second case starts the method. If the
selection has been resigned (second level), then there is nothing more
to be done.

\begin{lstlisting}{}
- (void)tableViewSelectionDidChange:(NSNotification *)aNotification
{
  NSTableView *tb = [aNotification object];

  int row = [tb selectedRow];
  if(row==-1){
    return;
  }
\end{lstlisting}

The first level is a loop that iterates over the seven column
tables. If a table is not the one that has newly been selected, then
it is asked to resign its selection, if any (which triggers the
recursive call on level two). Otherwise, if the user has not clicked
an empty cell, that cell's value becomes the new day value of the
calendar.

\begin{lstlisting}{}
  day = -1;

  int col;
  for(col=0; col<7; col++){
    if(tb != calTables[col]){
      [calTables[col] deselectAll:self];
    }
    else{
      if(strcmp(calmatrix[row][col], "")){
	day = atoi(calmatrix[row][col]);
      }
    }
  }
}
\end{lstlisting}

The action method {\tt yearChanged} reads the new year from the tag of
the sender, sets the day to be void and recomputes the calendar.

\begin{lstlisting}{}
- (void)yearChanged:(id)sender
{
  year = [sender tag];
  [self setMonYr];

  day = -1;
  [self computeCalendar];
}
\end{lstlisting}

The action method {\tt monthChanged} reads the new month from the tag
of the sender, sets the day to be void and recomputes the calendar.

\begin{lstlisting}{}
- (void)monthChanged:(id)sender
{
  month = [sender tag];
  [self setMonYr];

  day = -1;
  [self computeCalendar];
}
\end{lstlisting}

The method {\tt drawRect:} draws a tiled rectangle as the boundary of
the view. The rest of the drawing is handled by the subviews.

\begin{lstlisting}{}
- (void)drawRect:(NSRect)aRect;
{
  NSRectEdge sides[] = {
    NSMinXEdge, NSMaxXEdge, NSMinYEdge, NSMaxYEdge};
  float grays[] = {
    NSWhite, NSBlack, NSBlack, NSWhite};

  NSDrawTiledRects([self bounds], [self bounds], sides, grays, 4);  
}
\end{lstlisting}

There are three simple accessors, one for the year, another for the
month, and a third for the day.

\begin{lstlisting}{}
- (int)year
{
  return year;
}

- (int)month
{
  return month;
}

- (int)day
{
  return day;
}
\end{lstlisting}

The setter method {\tt setYear:Month:Day:} sets the calendar to a
given date. It checks first whether the year and the month hold
acceptable values.

\begin{lstlisting}{}
- (BOOL)setYear:(int)aYear Month:(int)aMonth Day:(int)aDay;
{
  if(aYear<MINYEAR || aYear>MAXYEAR){
    return NO;
  }

  if(month<1 || month>12){
    return NO;
  }
\end{lstlisting}

Next it tries to re-compute the calendar, while remembering the
previous values, so the change may be undone if necessary. If the
given day cannot be selected for the new month/year pair, the calendar
reverts to the current date.

\begin{lstlisting}{}
  int prevYear = year, prevMonth = month, prevDay = day;

  year = aYear; month = aMonth; day = aDay;
  [self computeCalendar];

  if([self selectDay]==NO){
    year  = prevYear;
    month = prevMonth;
    day   = prevDay;

    [self computeCalendar];
    [self selectDay];

    return NO;
  }
\end{lstlisting}

Otherwise the change has been successful and we update the two popups
to reflect the new data, as well as the text field in the upper
center.

\begin{lstlisting}{}
  [monthPopup 
    selectItemAtIndex:month-1];
  
  [yearPopup
    selectItemAtIndex:year-MINYEAR];

  [self setMonYr];

  return YES;
}
\end{lstlisting}

There is a second setter method, namely {\tt setFromString:format:},
which attempts to compute a valid calendar date corresponding to the
string and the format. If this fails, the setter does
nothing. Otherwise it extracts year, month and day and invokes  {\tt
  setYear:Month:Day:}.

\begin{lstlisting}{}
- (BOOL)setFromString:(NSString *)str format:(NSString *)fmt;
{
  NSCalendarDate *cd =
    [NSCalendarDate 
      dateWithString:str calendarFormat:fmt];

  if(cd==nil){
    return NO;
  }

  return
    [self 
      setYear:[cd yearOfCommonEra]
      Month:[cd monthOfYear]
      Day:[cd dayOfMonth]];
}
\end{lstlisting}

The method {\tt selectDay} attempts to select the right cell in the
appropriate table corresponding to the current value of the instance
variable {\tt day}. To do this, it iterates over all positions,
sweeping from left to right and top to down. If it finds the string
corresponding to the current date, it selects the corresponding cell
in the its table. Comparisons are done via strings and {\tt dstr}
holds a string representing the value of {\tt day}.

\begin{lstlisting}{}
- (BOOL)selectDay
{
  char dstr[4]; sprintf(dstr, "%d", day);
  int pos;
  for(pos=0; pos<6*7; pos++){
    if(!strcmp(calmatrix[pos/7][pos%7], dstr)){
      [calTables[(pos%7)] 
		selectRow:(pos/7)
		byExtendingSelection:NO];
      return YES;
    }
  }

  return NO;
}
\end{lstlisting}

The implementation of the calendar view concludes with the method {\tt
  today}, which sets it to the current date. It computes the current
date and invokes {\tt setYear:Month:Day:} with the corresponding
arguments.

\begin{lstlisting}{}
- today
{
  NSCalendarDate *cd =
    [NSCalendarDate calendarDate];

  [self 
    setYear:[cd yearOfCommonEra]
    Month:[cd monthOfYear]
    Day:[cd dayOfMonth]];

  return self;
}

@end
\end{lstlisting}

The remainder of the recipe consists in basic tasks that set up a test
application for the calendar view. Start by defining the width and
height of the calendar view. Create an autorelease pool, an
application and a trivial menu with a quit button.

\begin{lstlisting}{}
#define CAL_WIDTH  300
#define CAL_HEIGHT 180

int main(int argc, char** argv, char **env)
{
  NSAutoreleasePool *pool = [NSAutoreleasePool new];
           
  NSApplication *app;
  app = [NSApplication sharedApplication];

  NSMenu *menu = [NSMenu new];
  [menu addItemWithTitle: @"Quit"
        action:@selector(terminate:)
        keyEquivalent:@"q"];
  [NSApp setMainMenu:menu];
\end{lstlisting}

Next create a window for the calendar with the right dimensions. Set
its title to be ``calendar''. Create the calendar and attach it to the
window. Center the window and order it to the front.

\begin{lstlisting}{}
  NSWindow *calwin =
    [[NSWindow alloc] 
      initWithContentRect:
        NSMakeRect(100, 100, CAL_WIDTH, CAL_HEIGHT)
      styleMask:NSTitledWindowMask
      backing:NSBackingStoreRetained
      defer:NO];
  [calwin setTitle:@"Calendar"];

  Calendar *cal =
    [[Calendar alloc]
      initWithFrame:
        NSMakeRect(0, 0, CAL_WIDTH, CAL_HEIGHT)];

  [calwin setContentView:cal];
  [calwin center];
  [calwin orderFront:nil];
\end{lstlisting}

Last, run the application and when it is done, release the autorelease
pool and exit.

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