農民曆目前還是常常會用到. 在這裡製做一個計算農曆的程式, 主要是介紹 NSMatrix 的用法.
在這裡做一個顯示日期的 View, 如下:
太陽曆轉農曆不是這裡的重點, 有興趣人可以參考程式碼. 在這裡只列出其 Interface.
LunarCalendarDate.h
#ifndef _LunarCalendarDate_ #define _LunarCalendarDate_ #include <Foundation/NSObject.h> @interface LunarCalendarDate: NSObject { int lunarDay, lunarMonth; } - (void) setDate: (NSCalendarDate *) date; - (int) dayOfMonth; - (int) monthOfYear; @end #endif /* _LunarCalendarDate_ */
在 LunarCalendarDate 中設定日期, 即可得知農曆的月, 日. 這個轉換程式不能保證完全正確. 只能計算 1998-2003 年的日期.
接著, 使用 Gorm 建立如下的使用者介面:
繼承自 NSView, 製做一個 CalendarView 類別. 使用這個類別做為 Custom Class 的類別. 設定大小為寬 240, 高 270. 在最上面加個 NSTextField, 用來顯示農曆.
在 CalendarView 中加入一個 "label" outlet.
連結 "label" outlet 至 NSTextField
將檔案存成 LunarCalendar.gorm. 接著要實作 CalendarView.
CalendarView.h
#ifndef _CalendarView_ #define _CalendarView_ #include <AppKit/AppKit.h> @interface CalendarView : NSView { NSBox *calendarBox; NSTextField *yearLabel; NSButton *lastYearButton, *nextYearButton; NSMatrix *monthMatrix, *dayMatrix; NSCalendarDate *date; NSArray *monthArray; /* Outlet */ id label; } - (NSCalendarDate *) date; - (void) setDate: (NSCalendarDate *)date; /* Used by interface */ - (void) updateDate: (id) sender; @end #endif /* _CalendarView_ */
CalendarDate.m
Setup basic header and functions
#include "CalendarView.h" #include "LunarCalendarDate.h" @implementation CalendarView #define isLeapYear(year) (((year % 4) == 0 && ((year % 100) != 0)) || (year % 400) == 0) static short numberOfDaysInMonth[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 3 1};
在這裡使用 NSMatrix 來顯示月份及日期.
- (id) initWithFrame: (NSRect) rect { int i, j, count=0; NSImage *rightArrow, *leftArrow; NSButtonCell *monthCell, *dayCell, *tempCell; NSArray *weekArray; [super initWithFrame: rect];
增加一個 NSBox, 等下所有的圖形介面都會加入這個 NSBox.
calendarBox = [[NSBox alloc] initWithFrame: NSMakeRect(0, 0, 240, 270)]; [calendarBox setBorderType: NSGrooveBorder]; [calendarBox setTitlePosition: NSAtTop]; [calendarBox setTitle: @"Calendar"];
使用兩個按鈕及一個字串來顯示及調整年份. 其中的圖檔是 GNUstep 附加的. 當使用者按下按鈕時, -updateDate: 會被呼叫.
yearLabel = [[NSTextField alloc] initWithFrame: NSMakeRect(85, 220, 60, 20)]; [yearLabel setStringValue: @"This Year"]; [yearLabel setBezeled: NO]; [yearLabel setBackgroundColor: [NSColor windowBackgroundColor]]; [yearLabel setEditable: NO]; [yearLabel setSelectable: NO]; [yearLabel setAlignment: NSCenterTextAlignment]; leftArrow = [NSImage imageNamed: @"common_ArrowLeft.tiff"]; rightArrow = [NSImage imageNamed: @"common_ArrowRight.tiff"]; lastYearButton = [[NSButton alloc] initWithFrame: NSMakeRect(10, 220, 22, 22)]; [lastYearButton setImage: leftArrow]; [lastYearButton setImagePosition: NSImageOnly]; [lastYearButton setBordered: NO]; nextYearButton = [[NSButton alloc] initWithFrame: NSMakeRect(198, 220, 22, 22)]; [nextYearButton setImage: rightArrow]; [nextYearButton setImagePosition: NSImageOnly]; [nextYearButton setBordered: NO]; [lastYearButton setTarget: self]; [lastYearButton setAction: @selector(updateDate:)]; [nextYearButton setTarget: self]; [nextYearButton setAction: @selector(updateDate:)]; [calendarBox addSubview: yearLabel]; [calendarBox addSubview: lastYearButton]; [calendarBox addSubview: nextYearButton]; RELEASE(yearLabel); RELEASE(lastYearButton); RELEASE(nextYearButton);
Matrix 可以將 NSCell 排成棋盤狀. NSCell 是精簡版的 NSView, 可參考 Introduction to Controls and Cells. 首先是要定義 NSMatrix 中的 Cell 的屬性, NSMatrix 會用這個 Cell 來顯示所有的 Cell. 每個 Cell 都有個標箋 (Tag), 以用來區份各個 Cell.
monthArray = [[NSArray alloc] initWithObjects: @"Jan", @"Feb", @"Mar", @"Apr", @"May", @"Jun", @"Jul", @"Aug", @"Sep", @"Oct", @"Nov", @"Dec", nil]; monthCell = [[NSButtonCell alloc] initTextCell: @""]; [monthCell setBordered: NO]; [monthCell setShowsStateBy: NSOnOffButton]; [monthCell setAlignment: NSCenterTextAlignment]; monthMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(10, 165, 210, 50) mode: NSRadioModeMatrix prototype: monthCell numberOfRows: 2 numberOfColumns: 6]; for (i = 0; i < 2; i++) for (j = 0; j < 6; j++) { tempCell = [monthMatrix cellAtRow: i column: j]; [tempCell setTag: count]; [tempCell setTitle: [monthArray objectAtIndex: count]]; count++; } RELEASE(monthCell); weekArray = [NSArray arrayWithObjects: @"Sun", @"Mon" @"Tue", @"Wed", @"Thr", @"Fri", @"Sat", nil]; dayCell = [[NSButtonCell alloc] initTextCell: @""]; [dayCell setBordered: NO]; [dayCell setShowsStateBy: NSOnOffButton]; [dayCell setAlignment: NSCenterTextAlignment]; dayMatrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(10, 20, 210, 120) mode: NSRadioModeMatrix prototype: dayCell numberOfRows: 7 numberOfColumns: 7]; for (j = 0; j < 7; j++) { tempCell = [dayMatrix cellAtRow: 0 column: j]; [tempCell setTitle: [weekArray objectAtIndex: j]]; [tempCell setAlignment: NSCenterTextAlignment]; [tempCell setEnabled: NO]; } RELEASE(dayCell); count = 0; for (i = 1; i < 7; i++) for (j = 0; j < 7; j++) { [[dayMatrix cellAtRow: i column: j] setTag: count++]; }
當 NSMatrix 被按下時, 會呼叫 Matrix 的 action, 在這裡設為 -updateDate:.
[monthMatrix setTarget: self]; [monthMatrix setAction: @selector(updateDate:)]; [dayMatrix setTarget: self]; [dayMatrix setAction: @selector(updateDate:)]; [calendarBox addSubview: monthMatrix]; [calendarBox addSubview: dayMatrix]; RELEASE(monthMatrix); RELEASE(dayMatrix); [self addSubview: calendarBox]; RELEASE(calendarBox); return self; }
在 dayMatrix 的內容因月份而改變, 所以當日期變時, 其日期的顯示要隨之改變.
- (void) setDate: (NSCalendarDate *) newDate { int i, currentDay, currentMonth, currentYear; int daysInMonth, startDayOfWeek, day; NSCalendarDate *firstDayOfMonth; NSButtonCell *tempCell; LunarCalendarDate *lunarDate;
在這裡保留日期, 在 -dealloc 中釋放.
ASSIGN(date, newDate);
更新年份.
[yearLabel setStringValue: [date descriptionWithCalendarFormat: @"%Y"]];
更新月份.
currentMonth = [date monthOfYear]; [monthMatrix selectCellWithTag: currentMonth-1];
更新日期.
currentYear = [date yearOfCommonEra]; firstDayOfMonth = [NSCalendarDate dateWithYear: currentYear month: currentMonth day: 1 hour: 0 minute: 0 second: 0 timeZone: [NSTimeZone localTimeZone]]; daysInMonth = numberOfDaysInMonth[currentMonth - 1]; if ((currentMonth == 2) && (isLeapYear(currentYear))) daysInMonth++; startDayOfWeek = [firstDayOfMonth dayOfWeek]; day = 1; for (i = 0; i < 42; i++) { tempCell = [dayMatrix cellWithTag: i]; if (i < startDayOfWeek || i >= (daysInMonth + startDayOfWeek)) { [tempCell setEnabled: NO]; [tempCell setTitle: @""]; } else { [tempCell setEnabled: YES]; [tempCell setTitle: [NSString stringWithFormat: @"%d", day++]]; } } currentDay = [date dayOfMonth]; [dayMatrix selectCellWithTag: startDayOfWeek + currentDay - 1];
使用 LunarCalendarDate 計算農曆日期, 並在 label 中更新.
/* Update label */ lunarDate = [LunarCalendarDate new]; [lunarDate setDate: date]; [label setStringValue: [NSString stringWithFormat: @"%@ %d", [monthArray objectA tIndex: [lunarDate monthOfYear]-1], [lunarDate dayOfMonth]]]; RELEASE(lunarDate); }
當這個程式開始時, 需要預設成當日時間. 但 CalendarView 不是 NSApp 的代理者, 所以無法知道程式何時始動. 但當 Gorm 被載入時, 會呼叫 -awakeFromNib, 因此可以使用 -awakeFromNib 來知道程式開始了.
- (void) awakeFromNib { [self setDate: [NSCalendarDate calendarDate]]; }
最後, 要處理使用者輸入的部份. 所有使用者輸入都會進到 -updateDate:, 所以要依 sender 來做區別.
- (void) updateDate: (id) sender { int i=0, j=0, k=0; NSCalendarDate *newDate; if (sender == lastYearButton) { i = -1; } else if (sender == nextYearButton) { i = 1; } else if (sender == monthMatrix) { j = [[[sender selectedCells] lastObject] tag] + 1 - [date monthOfYear]; } else if (sender == dayMatrix) { k = [[[[sender selectedCells] lastObject] stringValue] intValue] - [date dayO fMonth]; } newDate = [date addYear: i month: j day: k hour: 0 minute: 0 second: 0];
LunarCalendarDate 只支援 1998-2031, 因為超過的年份不與反應.
if (([newDate yearOfCommonEra] > 1998) && ([newDate yearOfCommonEra] < 2031)) [self setDate: newDate]; }
程式碼在此: LunarCalendar-src.tar.gz