Codebase list agenda.app / 6a35e6d
Import upstream agenda.app version 0.36 Imported SimpleAgenda-0.36 into pkg-gnustep-maintainers@lists.alioth.debian.org--debian/agenda-app--head--1.0 git-archimport-id: pkg-gnustep-maintainers@lists.alioth.debian.org--debian/agenda-app--head--1.0--patch-1 Yavor Doganov 14 years ago
74 changed file(s) with 6767 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3
4 #define SADataChangedInStore @"DataDidChangedInStore"
5
6 @class Element;
7
8 @protocol MemoryStore <NSObject>
9 + (id)storeNamed:(NSString *)name;
10 - (id)initWithName:(NSString *)name;
11 + (BOOL)registerWithName:(NSString *)name;
12 + (NSString *)storeTypeName;
13 - (NSArray *)events;
14 - (NSArray *)tasks;
15 - (void)fillWithElements:(NSSet *)set;
16 - (void)add:(Element *)evt;
17 - (void)remove:(Element *)elt;
18 - (void)update:(Element *)evt;
19 - (BOOL)contains:(Element *)elt;
20 - (BOOL)modified;
21 - (void)setModified:(BOOL)modified;
22 - (BOOL)writable;
23 - (void)setWritable:(BOOL)writable;
24 - (NSColor *)eventColor;
25 - (void)setEventColor:(NSColor *)color;
26 - (NSColor *)textColor;
27 - (void)setTextColor:(NSColor *)color;
28 - (BOOL)displayed;
29 - (void)setDisplayed:(BOOL)state;
30 @end
31
32 @protocol StoreBackend
33 - (BOOL)read;
34 - (BOOL)write;
35 @end
36
37 @protocol AgendaStore <MemoryStore, StoreBackend>
38 @end
0 /* emacs objective-c mode -*- objc -*- */
1
2 #import "AgendaStore.h"
3 #import "AppointmentEditor.h"
4 #import "TaskEditor.h"
5 #import "CalendarView.h"
6 #import "DayView.h"
7 #import "Element.h"
8 #import "Event.h"
9 #import "PreferencesController.h"
10 #import "DataTree.h"
11
12 @interface AppController : NSObject <AgendaDataSource>
13 {
14 IBOutlet CalendarView *calendar;
15 IBOutlet DayView *dayView;
16 IBOutlet NSOutlineView *summary;
17 IBOutlet NSTextField *search;
18 IBOutlet NSTableView *taskView;
19 IBOutlet NSWindow *window;
20 IBOutlet NSTabView *tabs;
21
22 PreferencesController *_pc;
23 AppointmentEditor *_editor;
24 TaskEditor *_taskEditor;
25 StoreManager *_sm;
26 Event *_selection;
27 Element *_clickedElement;
28 BOOL _deleteSelection;
29 Date *_selectedDay;
30 DataTree *_summaryRoot;
31 DataTree *_today;
32 DataTree *_tomorrow;
33 DataTree *_soon;
34 DataTree *_results;
35 DataTree *_tasks;
36 }
37
38 - (void)copy:(id)sender;
39 - (void)cut:(id)sender;
40 - (void)paste:(id)sender;
41 - (void)editAppointment:(id)sender;
42 - (void)delAppointment:(id)sender;
43 - (void)exportAppointment:(id)sender;
44 - (void)saveAll:(id)sender;
45 - (void)reloadAll:(id)sender;
46 - (void)showPrefPanel:(id)sender;
47 - (void)addAppointment:(id)sender;
48 - (void)addTask:(id)sender;
49 - (void)editAppointment:(id)sender;
50 - (void)delAppointment:(id)sender;
51 - (void)exportAppointment:(id)sender;
52 - (void)doSearch:(id)sender;
53 - (void)clearSearch:(id)sender;
54 - (void)updateSummaryData;
55 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "AppointmentEditor.h"
4 #import "StoreManager.h"
5 #import "AppController.h"
6 #import "Event.h"
7 #import "Task.h"
8 #import "PreferencesController.h"
9 #import "iCalTree.h"
10
11 NSComparisonResult compareAppointments(id a, id b, void *data)
12 {
13 return [[a startDate] compareTime:[b startDate]];
14 }
15
16 NSComparisonResult compareDataTreeElements(id a, id b, void *context)
17 {
18 return [[[a valueForKey:@"object"] startDate] compareTime:[[b valueForKey:@"object"] startDate]];
19 }
20
21 @implementation AppController
22 - (void)registerForServices
23 {
24 NSArray *sendTypes = [NSArray arrayWithObjects:NSStringPboardType, NSFilenamesPboardType, nil];
25 NSArray *returnTypes = [NSArray arrayWithObjects:nil];
26 [NSApp registerServicesMenuSendTypes: sendTypes returnTypes: returnTypes];
27 }
28
29 - (void)initSummary
30 {
31 _today = [DataTree dataTreeWithAttributes:[NSDictionary dictionaryWithObject:@"Today" forKey:@"title"]];
32 _tomorrow = [DataTree dataTreeWithAttributes:[NSDictionary dictionaryWithObject:@"Tomorrow" forKey:@"title"]];
33 _soon = [DataTree dataTreeWithAttributes:[NSDictionary dictionaryWithObject:@"Soon" forKey:@"title"]];
34 _results = [DataTree dataTreeWithAttributes:[NSDictionary dictionaryWithObject:@"Search results" forKey:@"title"]];
35 _tasks = [DataTree dataTreeWithAttributes:[NSDictionary dictionaryWithObject:@"Open tasks" forKey:@"title"]];
36 _summaryRoot = [DataTree new];
37 [_summaryRoot addChild:_today];
38 [_summaryRoot addChild:_tomorrow];
39 [_summaryRoot addChild:_soon];
40 [_summaryRoot addChild:_results];
41 [_summaryRoot addChild:_tasks];
42 }
43
44 - (NSDictionary *)attributesFrom:(Event *)event and:(Date *)date
45 {
46 Date *today = [Date today];
47 NSMutableDictionary *attributes = [NSMutableDictionary new];
48 NSString *details;
49 NSString *title;
50
51 [date setMinute:[[event startDate] minuteOfDay]];
52 [attributes setValue:event forKey:@"object"];
53 [attributes setValue:AUTORELEASE([date copy]) forKey:@"date"];
54 if ([today daysUntil:date] > 0 || [today daysSince:date] > 0)
55 details = [[date calendarDate] descriptionWithCalendarFormat:[[NSUserDefaults standardUserDefaults] objectForKey:NSShortDateFormatString]];
56 else
57 details = [[date calendarDate] descriptionWithCalendarFormat:@"%H:%M"];
58 title = [NSString stringWithFormat:@"%@ : %@", details, [event summary]];
59 [attributes setValue:title forKey:@"title"];
60 return AUTORELEASE(attributes);
61 }
62
63 - (NSDictionary *)attributesFromTask:(Task *)task
64 {
65 return [NSMutableDictionary dictionaryWithObjectsAndKeys:task, @"object", [task summary], @"title", nil, nil];
66 }
67
68 - (void)updateSummaryData
69 {
70 Date *today = [Date today];
71 Date *tomorrow = [Date today];
72 Date *soonStart = [Date today];
73 Date *soonEnd = [Date today];
74 NSEnumerator *enumerator = [[_sm allEvents] objectEnumerator];
75 NSEnumerator *dayEnumerator;
76 Event *event;
77 Date *day;
78 Task *task;
79
80 [_today removeChildren];
81 [_tomorrow removeChildren];
82 [_soon removeChildren];
83 [tomorrow incrementDay];
84 [soonStart changeDayBy:2];
85 [soonEnd changeDayBy:5];
86 while ((event = [enumerator nextObject])) {
87 if ([event isScheduledForDay:today])
88 [_today addChild:[DataTree dataTreeWithAttributes:[self attributesFrom:event and:today]]];
89 if ([event isScheduledForDay:tomorrow])
90 [_tomorrow addChild:[DataTree dataTreeWithAttributes:[self attributesFrom:event and:tomorrow]]];
91 dayEnumerator = [soonStart enumeratorTo:soonEnd];
92 while ((day = [dayEnumerator nextObject])) {
93 if ([event isScheduledForDay:day])
94 [_soon addChild:[DataTree dataTreeWithAttributes:[self attributesFrom:event and:day]]];
95 }
96 }
97 [_today sortChildrenUsingFunction:compareDataTreeElements context:nil];
98 [_tomorrow sortChildrenUsingFunction:compareDataTreeElements context:nil];
99 [_soon sortChildrenUsingFunction:compareDataTreeElements context:nil];
100 [_tasks removeChildren];
101 enumerator = [[_sm allTasks] objectEnumerator];
102 while ((task = [enumerator nextObject])) {
103 if ([task state] != TK_COMPLETED)
104 [_tasks addChild:[DataTree dataTreeWithAttributes:[self attributesFromTask:task]]];
105 }
106 [summary reloadData];
107 }
108
109 - (id)init
110 {
111 self = [super init];
112 if (self) {
113 ASSIGNCOPY(_selectedDay, [Date today]);
114 _selection = nil;
115 _editor = [AppointmentEditor new];
116 _taskEditor = [TaskEditor new];
117 _sm = [StoreManager new];
118 _pc = [[PreferencesController alloc] initWithStoreManager:_sm];
119 [self initSummary];
120 [self registerForServices];
121 }
122 return self;
123 }
124
125 - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
126 {
127 NSPopUpButtonCell *cell = [NSPopUpButtonCell new];
128 [cell addItemsWithTitles:[Task stateNamesArray]];
129 [[taskView tableColumnWithIdentifier:@"state"] setDataCell:cell];
130 [taskView setAutoresizesAllColumnsToFit:YES];
131 /*
132 * FIXME : this shouldn't be needed but I can't make it
133 * work by editing the interface with Gorm...
134 * [[taskView superview] superview] is the ScrollView
135 */
136 [[[[taskView superview] superview] superview] setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
137 [taskView setUsesAlternatingRowBackgroundColors:YES];
138 [[taskView tableColumnWithIdentifier:@"state"] setMaxWidth:92];
139 [taskView setTarget:self];
140 [taskView setDoubleAction:@selector(editAppointment:)];
141 [summary setTarget:self];
142 [summary setDoubleAction:@selector(editAppointment:)];
143 [window setFrameAutosaveName:@"mainWindow"];
144 [[tabs tabViewItemAtIndex:[tabs indexOfTabViewItemWithIdentifier:@"Day"]] setLabel:[[_selectedDay calendarDate] descriptionWithCalendarFormat:@"%e %b"]];
145 }
146
147 - (void)applicationDidFinishLaunching:(NSNotification *)not
148 {
149 [self updateSummaryData];
150 [dayView reloadData];
151 [NSApp setServicesProvider: self];
152 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:SADataChanged object:nil];
153 }
154
155 - (void)applicationWillTerminate:(NSNotification*)aNotification
156 {
157 [[NSNotificationCenter defaultCenter] removeObserver:self];
158 [_summaryRoot release];
159 [_today release];
160 [_tomorrow release];
161 [_soon release];
162 [_results release];
163 [_pc release];
164 /*
165 * Ugly workaround : [_sm release] should force the
166 * modified stores to synchronise their data but it
167 * doesn't work. We're leaking a object reference.
168 */
169 [_sm synchronise];
170 [_sm release];
171 [_editor release];
172 [_taskEditor release];
173 RELEASE(_selectedDay);
174 }
175
176
177 /* Called when user opens an .ics file in GWorkspace */
178 - (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename
179 {
180 NSFileManager *fm = [NSFileManager defaultManager];
181 NSEnumerator *eventEnum;
182 id <AgendaStore> store;
183 Element *elt;
184 iCalTree *tree;
185
186 if ([fm isReadableFileAtPath:filename]) {
187 tree = [iCalTree new];
188 [tree parseString:[NSString stringWithContentsOfFile:filename]];
189 eventEnum = [[tree components] objectEnumerator];
190 while ((elt = [eventEnum nextObject])) {
191 store = [_sm storeContainingElement:elt];
192 if (store)
193 [store update:elt];
194 else
195 [[_sm defaultStore] add:elt];
196 }
197 [tree release];
198 return YES;
199 }
200 return NO;
201 }
202
203 - (void)showPrefPanel:(id)sender
204 {
205 [_pc showPreferences];
206 }
207
208 - (int)_sensibleStartForDuration:(int)duration
209 {
210 int minute = [dayView firstHour] * 60;
211 NSEnumerator *enumerator = [[self scheduledAppointmentsForDay:nil] objectEnumerator];
212 Event *apt;
213
214 while ((apt = [enumerator nextObject])) {
215 if (minute + duration <= [[apt startDate] minuteOfDay])
216 return minute;
217 minute = [[apt startDate] minuteOfDay] + [apt duration];
218 }
219 if (minute < [dayView lastHour] * 60)
220 return minute;
221 return [dayView firstHour] * 60;
222 }
223
224 - (void)addAppointment:(id)sender
225 {
226 Date *date = [[calendar date] copy];
227 [date setMinute:[self _sensibleStartForDuration:60]];
228 Event *apt = [[Event alloc] initWithStartDate:date
229 duration:60
230 title:@"edit title..."];
231 if (apt && [_editor editAppointment:apt withStoreManager:_sm])
232 [tabs selectTabViewItemWithIdentifier:@"Day"];
233 [date release];
234 [apt release];
235 }
236
237 - (void)addTask:(id)sender
238 {
239 Task *task = [[Task alloc] initWithSummary:@"edit summary..."];
240 if (task && [_taskEditor editTask:task withStoreManager:_sm])
241 [tabs selectTabViewItemWithIdentifier:@"Tasks"];
242 [task release];
243 }
244
245 - (void)newTask:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString **)error
246 {
247 NSString *aString;
248 NSArray *allTypes;
249 Task *task;
250
251 allTypes = [pboard types];
252 if (![allTypes containsObject: NSStringPboardType]) {
253 *error = @"No string type supplied on pasteboard";
254 return;
255 }
256 aString = [pboard stringForType: NSStringPboardType];
257 if (aString == nil) {
258 *error = @"No string value supplied on pasteboard";
259 return;
260 }
261 task = [Task new];
262 if ([aString length] > 40) {
263 [task setSummary:@"New note"];
264 [task setText:AUTORELEASE([[NSAttributedString alloc ] initWithString:aString])];
265 } else
266 [task setSummary:aString];
267 if (task && [_taskEditor editTask:task withStoreManager:_sm])
268 [tabs selectTabViewItemWithIdentifier:@"Tasks"];
269 [task release];
270 }
271
272 - (void)editAppointment:(id)sender
273 {
274 if (_clickedElement) {
275 if ([_clickedElement isKindOfClass:[Event class]])
276 [_editor editAppointment:(Event *)_clickedElement withStoreManager:_sm];
277 else if ([_clickedElement isKindOfClass:[Task class]])
278 [_taskEditor editTask:(Task *)_clickedElement withStoreManager:_sm];
279 }
280 }
281
282 - (void)delAppointment:(id)sender
283 {
284 if (_clickedElement) {
285 [[_clickedElement store] remove:_clickedElement];
286 _clickedElement = nil;
287 }
288 }
289
290 - (void)exportAppointment:(id)sender;
291 {
292 NSSavePanel *panel = [NSSavePanel savePanel];
293 NSString *str;
294 iCalTree *tree;
295
296 if (_clickedElement) {
297 [panel setRequiredFileType:@"ics"];
298 [panel setTitle:@"Export as"];
299 if ([panel runModalForDirectory:nil file:[_clickedElement summary]] == NSOKButton) {
300 tree = [iCalTree new];
301 [tree add:_clickedElement];
302 str = [tree iCalTreeAsString];
303 if (![str writeToFile:[panel filename] atomically:NO])
304 NSLog(@"Unable to write to file %@", [panel filename]);
305 [tree release];
306 }
307 }
308 }
309
310 - (void)saveAll:(id)sender
311 {
312 [_sm synchronise];
313 }
314
315 - (void)reloadAll:(id)sender
316 {
317 [_sm refresh];
318 }
319
320 - (void)copy:(id)sender
321 {
322 _selection = (Event *)_clickedElement;
323 _deleteSelection = NO;
324 }
325
326 - (void)cut:(id)sender
327 {
328 _selection = (Event *)_clickedElement;
329 _deleteSelection = YES;
330 }
331
332 - (void)paste:(id)sender
333 {
334 if (_selection) {
335 Date *date = [[calendar date] copy];
336 if (_deleteSelection) {
337 [date setMinute:[self _sensibleStartForDuration:[_selection duration]]];
338 [_selection setStartDate:date];
339 [[_selection store] update:_selection];
340 _selection = nil;
341 } else {
342 Event *new = [_selection copy];
343 [date setMinute:[self _sensibleStartForDuration:[new duration]]];
344 [new setStartDate:date];
345 [[_selection store] add:new];
346 [new release];
347 }
348 [date release];
349 }
350 }
351
352 - (void)performSearch
353 {
354 NSEnumerator *enumerator;
355 Event *event;
356
357 [_results removeChildren];
358 if ([[search stringValue] length] > 0) {
359 enumerator = [[_sm allEvents] objectEnumerator];
360 while ((event = [enumerator nextObject])) {
361 if ([event contains:[search stringValue]])
362 [_results addChild:[DataTree dataTreeWithAttributes:[self attributesFrom:event and:[event startDate]]]];
363 }
364 [_results setValue:[NSString stringWithFormat:@"%d item(s)", [[_results children] count]] forKey:@"details"];;
365 [_results sortChildrenUsingFunction:compareDataTreeElements context:nil];
366 [summary expandItem:_results];
367 }
368 }
369
370 - (void)doSearch:(id)sender
371 {
372 [self performSearch];
373 [summary reloadData];
374 [window makeFirstResponder:search];
375 }
376
377 - (void)clearSearch:(id)sender
378 {
379 [search setStringValue:@""];
380 [_results removeChildren];
381 [_results setValue:@"" forKey:@"details"];;
382 [summary reloadData];
383 [window makeFirstResponder:search];
384 }
385
386 - (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
387 {
388 BOOL itemSelected = _clickedElement != nil;
389 SEL action = [menuItem action];
390
391 if (sel_eq(action, @selector(copy:)))
392 return itemSelected && [_clickedElement isKindOfClass:[Event class]] && [[_clickedElement store] writable];
393 if (sel_eq(action, @selector(cut:)))
394 return itemSelected && [_clickedElement isKindOfClass:[Event class]] && [[_clickedElement store] writable];
395 if (sel_eq(action, @selector(editAppointment:)))
396 return itemSelected;
397 if (sel_eq(action, @selector(delAppointment:)))
398 return itemSelected;
399 if (sel_eq(action, @selector(exportAppointment:)))
400 return itemSelected;
401 if (sel_eq(action, @selector(paste:)))
402 return _selection != nil && [[_selection store] writable];
403 return YES;
404 }
405
406 /* DayViewDataSource protocol */
407 - (NSSet *)scheduledAppointmentsForDay:(Date *)day
408 {
409 NSMutableSet *dayEvents = [NSMutableSet setWithCapacity:8];
410 NSEnumerator *enumerator = [[_sm allEvents] objectEnumerator];
411 Event *event;
412
413 if (day == nil)
414 day = _selectedDay;
415 while ((event = [enumerator nextObject]))
416 if ([event isScheduledForDay:day])
417 [dayEvents addObject:event];
418 return dayEvents;
419 }
420
421 - (void)dataChanged:(NSNotification *)not
422 {
423 _clickedElement = nil;
424 [dayView reloadData];
425 [taskView reloadData];
426 [self performSearch];
427 [self updateSummaryData];
428 }
429
430 - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
431 {
432 if (_clickedElement && (!sendType || [sendType isEqual:NSFilenamesPboardType] || [sendType isEqual:NSStringPboardType]))
433 return self;
434 return nil;
435 }
436 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pboard types:(NSArray *)types
437 {
438 NSString *ical;
439 NSString *filename;
440 iCalTree *tree;
441 NSFileWrapper *fw;
442 BOOL written;
443
444 if (!_clickedElement)
445 return NO;
446 NSAssert([types count] == 1, @"It seems our assumption was wrong");
447 tree = AUTORELEASE([iCalTree new]);
448 [tree add:_clickedElement];
449 ical = [tree iCalTreeAsString];
450
451 if ([types containsObject:NSFilenamesPboardType]) {
452 fw = [[NSFileWrapper alloc] initRegularFileWithContents:[ical dataUsingEncoding:NSUTF8StringEncoding]];
453 if (!fw) {
454 NSLog(@"Unable to encode into NSFileWrapper");
455 return NO;
456 }
457 filename = [NSString stringWithFormat:@"%@/%@.ics", NSTemporaryDirectory(), [_clickedElement summary]];
458 written = [fw writeToFile:filename atomically:YES updateFilenames:YES];
459 [fw release];
460 if (!written) {
461 NSLog(@"Unable to write to file %@", filename);
462 return NO;
463 }
464 [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
465 return [pboard setPropertyList:[NSArray arrayWithObject:filename] forType:NSFilenamesPboardType];
466 }
467 if ([types containsObject:NSStringPboardType]) {
468 [pboard declareTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil];
469 return [pboard setString:ical forType:NSStringPboardType];
470 }
471 return NO;
472 }
473 @end
474
475 @implementation AppController(NSOutlineViewDataSource)
476 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
477 {
478 if (item == nil)
479 return [[_summaryRoot children] count];
480 return [[item children] count];
481 }
482 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
483 {
484 return [[item children] count] > 0;
485 }
486 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
487 {
488 if (item == nil)
489 return [[_summaryRoot children] objectAtIndex:index];
490 return [[item children] objectAtIndex:index];
491 }
492 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
493 {
494 return [item valueForKey:[tableColumn identifier]];
495 }
496 @end
497
498 @implementation AppController(NSOutlineViewDelegate)
499 - (BOOL)outlineView:(NSOutlineView *)outlineView shouldSelectItem:(id)item
500 {
501 id object = [item valueForKey:@"object"];
502 NSString *tabIdentifier = [[tabs selectedTabViewItem] identifier];;
503
504 if (object && [object isKindOfClass:[Event class]]) {
505 [calendar setDate:[item valueForKey:@"date"]];
506 if (![tabIdentifier isEqualToString:@"Day"])
507 [tabs selectTabViewItemWithIdentifier:@"Day"];
508 _clickedElement = object;
509 return YES;
510 }
511 if (object && [object isKindOfClass:[Task class]]) {
512 if (![tabIdentifier isEqualToString:@"Tasks"])
513 [tabs selectTabViewItemWithIdentifier:@"Tasks"];
514 _clickedElement = object;
515 return YES;
516 }
517 return NO;
518 }
519 @end
520
521 @implementation AppController(CalendarViewDelegate)
522 - (void)calendarView:(CalendarView *)cs selectedDateChanged:(Date *)date
523 {
524 NSTabViewItem *dayTab = [tabs tabViewItemAtIndex:[tabs indexOfTabViewItemWithIdentifier:@"Day"]];
525 _clickedElement = nil;
526 ASSIGNCOPY(_selectedDay, date);
527 [dayView reloadData];
528 [dayTab setLabel:[[date calendarDate] descriptionWithCalendarFormat:@"%e %b"]];
529 /* FIXME : this, somehow, prevents the selected month to show up in the popup */
530 /* [tabs selectTabViewItem:dayTab]; */
531 [tabs setNeedsDisplay:YES];
532 }
533 - (void)calendarView:(CalendarView *)cs currentDateChanged:(Date *)date
534 {
535 [self updateSummaryData];
536 }
537 - (void)calendarView:(CalendarView *)cs userActionForDate:(Date *)date
538 {
539 [self addAppointment:self];
540 }
541 @end
542
543 @implementation AppController(DayViewDelegate)
544 - (void)dayView:(DayView *)dayview editEvent:(Event *)event;
545 {
546 _clickedElement = event;
547 [_editor editAppointment:event withStoreManager:_sm];
548 }
549 - (void)dayView:(DayView *)dayview modifyEvent:(Event *)event
550 {
551 [[event store] update:event];
552 }
553 - (void)dayView:(DayView *)dayview createEventFrom:(int)start to:(int)end
554 {
555 Date *date = [[calendar date] copy];
556 [date setMinute:start];
557 Event *apt = [[Event alloc] initWithStartDate:date
558 duration:end - start
559 title:@"edit title..."];
560 if (apt)
561 [_editor editAppointment:apt withStoreManager:_sm];
562 [date release];
563 [apt release];
564 }
565 - (void)dayView:(DayView *)dayview selectEvent:(Event *)event
566 {
567 _clickedElement = event;
568 }
569 @end
570
571 @implementation AppController(NSTableDataSource)
572 - (int)numberOfRowsInTableView:(NSTableView *)aTableView
573 {
574 return [[_sm allTasks] count];
575 }
576 - (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id <NSDraggingInfo>)info row:(int)row dropOperation:(NSTableViewDropOperation)operation
577 {
578 return NO;
579 }
580 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
581 {
582 Task *task = [[_sm allTasks] objectAtIndex:rowIndex];
583
584 if ([[aTableColumn identifier] isEqualToString:@"summary"])
585 return [task summary];
586 return [NSNumber numberWithInt:[task state]];
587 }
588 - (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
589 {
590 Task *task = [[_sm allTasks] objectAtIndex:rowIndex];
591
592 if ([[task store] writable]) {
593 [task setState:[anObject intValue]];
594 [[task store] update:task];
595 }
596 }
597 - (void)tableView:(NSTableView *)aTableView willDisplayCell:(id)aCell forTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
598 {
599 Task *task;
600
601 if ([[aTableColumn identifier] isEqualToString:@"state"]) {
602 task = [[_sm allTasks] objectAtIndex:rowIndex];
603 [aCell setEnabled:[[task store] writable]];
604 }
605 }
606 - (void)tableViewSelectionDidChange:(NSNotification *)aNotification
607 {
608 int index = [taskView selectedRow];
609 if (index > -1)
610 _clickedElement = [[_sm allTasks] objectAtIndex:index];
611 }
612 @end
613
614 /* FIXME : gross hack around selection problem */
615 @implementation AppController(NSTabViewDelegate)
616 - (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
617 {
618 if ([[tabViewItem identifier] isEqualToString:@"Day"])
619 [taskView deselectAll:self];
620 if ([[tabViewItem identifier] isEqualToString:@"Tasks"])
621 [dayView deselectAll:self];
622 _clickedElement = nil;
623 }
624 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "Event.h"
3 #import "StoreManager.h"
4
5 @interface AppointmentEditor : NSObject
6 {
7 id window;
8 id description;
9 id title;
10 id duration;
11 id durationText;
12 id repeat;
13 id endDate;
14 id location;
15 id store;
16 id allDay;
17 id ok;
18 id until;
19 Date *startDate;
20 }
21
22 - (BOOL)editAppointment:(Event *)data withStoreManager:(StoreManager *)sm;
23 - (void)validate:(id)sender;
24 - (void)cancel:(id)sender;
25 - (void)selectFrequency:(id)sender;
26 - (void)toggleUntil:(id)sender;
27 - (void)toggleAllDay:(id)sender;
28 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "AppointmentEditor.h"
4 #import "HourFormatter.h"
5 #import "AgendaStore.h"
6 #import "ConfigManager.h"
7 #import "defines.h"
8
9 @implementation AppointmentEditor
10 -(id)init
11 {
12 HourFormatter *formatter;
13 NSDateFormatter *dateFormatter;
14
15 self = [super init];
16 if (self) {
17 if (![NSBundle loadNibNamed:@"Appointment" owner:self])
18 return nil;
19 /* FIXME : shouldn't be needed but Gorm just won't set it */
20 [duration setContinuous:YES];
21 formatter = [[[HourFormatter alloc] init] autorelease];
22 dateFormatter = [[[NSDateFormatter alloc] initWithDateFormat:[[NSUserDefaults standardUserDefaults] objectForKey:NSShortDateFormatString] allowNaturalLanguage:NO] autorelease];
23 [durationText setFormatter:formatter];
24 [endDate setFormatter:dateFormatter];
25 [endDate setObjectValue:[NSDate date]];
26 }
27 return self;
28 }
29
30 -(BOOL)editAppointment:(Event *)data withStoreManager:(StoreManager *)sm
31 {
32 NSEnumerator *list = [sm storeEnumerator];
33 id <MemoryStore> aStore;
34 id <MemoryStore> originalStore;
35 int ret;
36
37 [title setStringValue:[data summary]];
38 [duration setFloatValue:[data duration] / 60.0];
39 [durationText setFloatValue:[data duration] / 60.0];
40 [repeat selectItemAtIndex:[data interval]];
41 [location setStringValue:[data location]];
42 [allDay setState:[data allDay]];
43
44 [[description textStorage] deleteCharactersInRange:NSMakeRange(0, [[description textStorage] length])];
45 [[description textStorage] appendAttributedString:[data text]];
46
47 [window makeFirstResponder:title];
48
49 originalStore = [data store];
50 if (!originalStore)
51 [data setStore:[sm defaultStore]];
52 else if (![originalStore writable])
53 [ok setEnabled:NO];
54
55 [store removeAllItems];
56 while ((aStore = [list nextObject])) {
57 if ([aStore writable] || aStore == originalStore)
58 [store addItemWithTitle:[aStore description]];
59 }
60 [store selectItemWithTitle:[[data store] description]];
61 startDate = [data startDate];
62 [until setEnabled:([data interval] != 0)];
63 if ([data interval] != 0 && [data endDate]) {
64 [until setState:YES];
65 [endDate setObjectValue:[[data endDate] calendarDate]];
66 } else {
67 [until setState:NO];
68 [endDate setObjectValue:nil];
69 }
70 [endDate setEnabled:[until state]];
71 ret = [NSApp runModalForWindow:window];
72 [window close];
73 if (ret == NSOKButton) {
74 [data setSummary:[title stringValue]];
75 [data setDuration:[duration floatValue] * 60.0];
76 [data setInterval:[repeat indexOfSelectedItem]];
77 if ([repeat indexOfSelectedItem] != 0) {
78 [data setFrequency:1];
79 if ([until state] && [endDate objectValue])
80 [data setEndDate:AUTORELEASE([[Date alloc] initWithCalendarDate:[endDate objectValue] withTime:NO])];
81 else
82 [data setEndDate:nil];
83 }
84 /* FIXME : why do we copy one and not the other ? */
85 [data setText:[[description textStorage] copy]];
86 [data setLocation:[location stringValue]];
87 [data setAllDay:[allDay state]];
88
89 aStore = [sm storeForName:[store titleOfSelectedItem]];
90 if (!originalStore)
91 [aStore add:data];
92 else if (originalStore == aStore)
93 [aStore update:data];
94 else {
95 [originalStore remove:data];
96 [aStore add:data];
97 }
98 return YES;
99 }
100 return NO;
101 }
102
103 -(void)validate:(id)sender
104 {
105 [NSApp stopModalWithCode: NSOKButton];
106 }
107
108 -(void)cancel:(id)sender
109 {
110 [NSApp stopModalWithCode: NSCancelButton];
111 }
112
113 - (void)controlTextDidChange:(NSNotification *)aNotification
114 {
115 id end = [endDate objectValue];
116
117 if (end == nil || ![end isKindOfClass:[NSDate class]])
118 [ok setEnabled:NO];
119 else {
120 [ok setEnabled:YES];
121 }
122 }
123
124 - (void)selectFrequency:(id)sender
125 {
126 int index = [repeat indexOfSelectedItem];
127 [until setEnabled:!!index];
128 if (!index)
129 [until setState:NO];
130 [self toggleUntil:nil];
131 }
132
133 - (void)toggleUntil:(id)sender
134 {
135 [endDate setEnabled:[until state]];
136 if ([until state]) {
137 Date *futur = [startDate copy];
138 int selected = [repeat indexOfSelectedItem];
139 switch (selected) {
140 case 1:
141 case 2:
142 /* Daily and weekly : 1 month by default */
143 [futur changeDayBy:[futur numberOfDaysInMonth]];
144 break;
145 case 3:
146 /* Monthly : 1 year */
147 [futur changeYearBy:1];
148 break;
149 case 4:
150 /* Yearly : 10 years */
151 [futur changeYearBy:10];
152 break;
153 }
154 [endDate setObjectValue:[futur calendarDate]];
155 [futur release];
156 } else
157 [endDate setObjectValue:nil];
158 }
159
160 - (void)toggleAllDay:(id)sender
161 {
162 if ([allDay state]) {
163 [duration setEnabled:NO];
164 [duration setIntValue:0];
165 [durationText setIntValue:0];
166 } else {
167 [duration setEnabled:YES];
168 [duration setFloatValue:1];
169 [durationText setFloatValue:1];
170 [startDate setMinute:[[ConfigManager globalConfig] integerForKey:FIRST_HOUR] * 60];
171 }
172 }
173 @end
0 GNU GENERAL PUBLIC LICENSE
1 Version 2, June 1991
2
3 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
4 675 Mass Ave, Cambridge, MA 02111, USA
5 Everyone is permitted to copy and distribute verbatim copies
6 of this license document, but changing it is not allowed.
7
8 Preamble
9
10 The licenses for most software are designed to take away your
11 freedom to share and change it. By contrast, the GNU General Public
12 License is intended to guarantee your freedom to share and change free
13 software--to make sure the software is free for all its users. This
14 General Public License applies to most of the Free Software
15 Foundation's software and to any other program whose authors commit to
16 using it. (Some other Free Software Foundation software is covered by
17 the GNU Library General Public License instead.) You can apply it to
18 your programs, too.
19
20 When we speak of free software, we are referring to freedom, not
21 price. Our General Public Licenses are designed to make sure that you
22 have the freedom to distribute copies of free software (and charge for
23 this service if you wish), that you receive source code or can get it
24 if you want it, that you can change the software or use pieces of it
25 in new free programs; and that you know you can do these things.
26
27 To protect your rights, we need to make restrictions that forbid
28 anyone to deny you these rights or to ask you to surrender the rights.
29 These restrictions translate to certain responsibilities for you if you
30 distribute copies of the software, or if you modify it.
31
32 For example, if you distribute copies of such a program, whether
33 gratis or for a fee, you must give the recipients all the rights that
34 you have. You must make sure that they, too, receive or can get the
35 source code. And you must show them these terms so they know their
36 rights.
37
38 We protect your rights with two steps: (1) copyright the software, and
39 (2) offer you this license which gives you legal permission to copy,
40 distribute and/or modify the software.
41
42 Also, for each author's protection and ours, we want to make certain
43 that everyone understands that there is no warranty for this free
44 software. If the software is modified by someone else and passed on, we
45 want its recipients to know that what they have is not the original, so
46 that any problems introduced by others will not reflect on the original
47 authors' reputations.
48
49 Finally, any free program is threatened constantly by software
50 patents. We wish to avoid the danger that redistributors of a free
51 program will individually obtain patent licenses, in effect making the
52 program proprietary. To prevent this, we have made it clear that any
53 patent must be licensed for everyone's free use or not licensed at all.
54
55 The precise terms and conditions for copying, distribution and
56 modification follow.
57
58 GNU GENERAL PUBLIC LICENSE
59 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
60
61 0. This License applies to any program or other work which contains
62 a notice placed by the copyright holder saying it may be distributed
63 under the terms of this General Public License. The "Program", below,
64 refers to any such program or work, and a "work based on the Program"
65 means either the Program or any derivative work under copyright law:
66 that is to say, a work containing the Program or a portion of it,
67 either verbatim or with modifications and/or translated into another
68 language. (Hereinafter, translation is included without limitation in
69 the term "modification".) Each licensee is addressed as "you".
70
71 Activities other than copying, distribution and modification are not
72 covered by this License; they are outside its scope. The act of
73 running the Program is not restricted, and the output from the Program
74 is covered only if its contents constitute a work based on the
75 Program (independent of having been made by running the Program).
76 Whether that is true depends on what the Program does.
77
78 1. You may copy and distribute verbatim copies of the Program's
79 source code as you receive it, in any medium, provided that you
80 conspicuously and appropriately publish on each copy an appropriate
81 copyright notice and disclaimer of warranty; keep intact all the
82 notices that refer to this License and to the absence of any warranty;
83 and give any other recipients of the Program a copy of this License
84 along with the Program.
85
86 You may charge a fee for the physical act of transferring a copy, and
87 you may at your option offer warranty protection in exchange for a fee.
88
89 2. You may modify your copy or copies of the Program or any portion
90 of it, thus forming a work based on the Program, and copy and
91 distribute such modifications or work under the terms of Section 1
92 above, provided that you also meet all of these conditions:
93
94 a) You must cause the modified files to carry prominent notices
95 stating that you changed the files and the date of any change.
96
97 b) You must cause any work that you distribute or publish, that in
98 whole or in part contains or is derived from the Program or any
99 part thereof, to be licensed as a whole at no charge to all third
100 parties under the terms of this License.
101
102 c) If the modified program normally reads commands interactively
103 when run, you must cause it, when started running for such
104 interactive use in the most ordinary way, to print or display an
105 announcement including an appropriate copyright notice and a
106 notice that there is no warranty (or else, saying that you provide
107 a warranty) and that users may redistribute the program under
108 these conditions, and telling the user how to view a copy of this
109 License. (Exception: if the Program itself is interactive but
110 does not normally print such an announcement, your work based on
111 the Program is not required to print an announcement.)
112
113 These requirements apply to the modified work as a whole. If
114 identifiable sections of that work are not derived from the Program,
115 and can be reasonably considered independent and separate works in
116 themselves, then this License, and its terms, do not apply to those
117 sections when you distribute them as separate works. But when you
118 distribute the same sections as part of a whole which is a work based
119 on the Program, the distribution of the whole must be on the terms of
120 this License, whose permissions for other licensees extend to the
121 entire whole, and thus to each and every part regardless of who wrote it.
122
123 Thus, it is not the intent of this section to claim rights or contest
124 your rights to work written entirely by you; rather, the intent is to
125 exercise the right to control the distribution of derivative or
126 collective works based on the Program.
127
128 In addition, mere aggregation of another work not based on the Program
129 with the Program (or with a work based on the Program) on a volume of
130 a storage or distribution medium does not bring the other work under
131 the scope of this License.
132
133 3. You may copy and distribute the Program (or a work based on it,
134 under Section 2) in object code or executable form under the terms of
135 Sections 1 and 2 above provided that you also do one of the following:
136
137 a) Accompany it with the complete corresponding machine-readable
138 source code, which must be distributed under the terms of Sections
139 1 and 2 above on a medium customarily used for software interchange; or,
140
141 b) Accompany it with a written offer, valid for at least three
142 years, to give any third party, for a charge no more than your
143 cost of physically performing source distribution, a complete
144 machine-readable copy of the corresponding source code, to be
145 distributed under the terms of Sections 1 and 2 above on a medium
146 customarily used for software interchange; or,
147
148 c) Accompany it with the information you received as to the offer
149 to distribute corresponding source code. (This alternative is
150 allowed only for noncommercial distribution and only if you
151 received the program in object code or executable form with such
152 an offer, in accord with Subsection b above.)
153
154 The source code for a work means the preferred form of the work for
155 making modifications to it. For an executable work, complete source
156 code means all the source code for all modules it contains, plus any
157 associated interface definition files, plus the scripts used to
158 control compilation and installation of the executable. However, as a
159 special exception, the source code distributed need not include
160 anything that is normally distributed (in either source or binary
161 form) with the major components (compiler, kernel, and so on) of the
162 operating system on which the executable runs, unless that component
163 itself accompanies the executable.
164
165 If distribution of executable or object code is made by offering
166 access to copy from a designated place, then offering equivalent
167 access to copy the source code from the same place counts as
168 distribution of the source code, even though third parties are not
169 compelled to copy the source along with the object code.
170
171 4. You may not copy, modify, sublicense, or distribute the Program
172 except as expressly provided under this License. Any attempt
173 otherwise to copy, modify, sublicense or distribute the Program is
174 void, and will automatically terminate your rights under this License.
175 However, parties who have received copies, or rights, from you under
176 this License will not have their licenses terminated so long as such
177 parties remain in full compliance.
178
179 5. You are not required to accept this License, since you have not
180 signed it. However, nothing else grants you permission to modify or
181 distribute the Program or its derivative works. These actions are
182 prohibited by law if you do not accept this License. Therefore, by
183 modifying or distributing the Program (or any work based on the
184 Program), you indicate your acceptance of this License to do so, and
185 all its terms and conditions for copying, distributing or modifying
186 the Program or works based on it.
187
188 6. Each time you redistribute the Program (or any work based on the
189 Program), the recipient automatically receives a license from the
190 original licensor to copy, distribute or modify the Program subject to
191 these terms and conditions. You may not impose any further
192 restrictions on the recipients' exercise of the rights granted herein.
193 You are not responsible for enforcing compliance by third parties to
194 this License.
195
196 7. If, as a consequence of a court judgment or allegation of patent
197 infringement or for any other reason (not limited to patent issues),
198 conditions are imposed on you (whether by court order, agreement or
199 otherwise) that contradict the conditions of this License, they do not
200 excuse you from the conditions of this License. If you cannot
201 distribute so as to satisfy simultaneously your obligations under this
202 License and any other pertinent obligations, then as a consequence you
203 may not distribute the Program at all. For example, if a patent
204 license would not permit royalty-free redistribution of the Program by
205 all those who receive copies directly or indirectly through you, then
206 the only way you could satisfy both it and this License would be to
207 refrain entirely from distribution of the Program.
208
209 If any portion of this section is held invalid or unenforceable under
210 any particular circumstance, the balance of the section is intended to
211 apply and the section as a whole is intended to apply in other
212 circumstances.
213
214 It is not the purpose of this section to induce you to infringe any
215 patents or other property right claims or to contest validity of any
216 such claims; this section has the sole purpose of protecting the
217 integrity of the free software distribution system, which is
218 implemented by public license practices. Many people have made
219 generous contributions to the wide range of software distributed
220 through that system in reliance on consistent application of that
221 system; it is up to the author/donor to decide if he or she is willing
222 to distribute software through any other system and a licensee cannot
223 impose that choice.
224
225 This section is intended to make thoroughly clear what is believed to
226 be a consequence of the rest of this License.
227
228 8. If the distribution and/or use of the Program is restricted in
229 certain countries either by patents or by copyrighted interfaces, the
230 original copyright holder who places the Program under this License
231 may add an explicit geographical distribution limitation excluding
232 those countries, so that distribution is permitted only in or among
233 countries not thus excluded. In such case, this License incorporates
234 the limitation as if written in the body of this License.
235
236 9. The Free Software Foundation may publish revised and/or new versions
237 of the General Public License from time to time. Such new versions will
238 be similar in spirit to the present version, but may differ in detail to
239 address new problems or concerns.
240
241 Each version is given a distinguishing version number. If the Program
242 specifies a version number of this License which applies to it and "any
243 later version", you have the option of following the terms and conditions
244 either of that version or of any later version published by the Free
245 Software Foundation. If the Program does not specify a version number of
246 this License, you may choose any version ever published by the Free Software
247 Foundation.
248
249 10. If you wish to incorporate parts of the Program into other free
250 programs whose distribution conditions are different, write to the author
251 to ask for permission. For software which is copyrighted by the Free
252 Software Foundation, write to the Free Software Foundation; we sometimes
253 make exceptions for this. Our decision will be guided by the two goals
254 of preserving the free status of all derivatives of our free software and
255 of promoting the sharing and reuse of software generally.
256
257 NO WARRANTY
258
259 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
260 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
261 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
262 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
263 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
264 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
265 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
266 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
267 REPAIR OR CORRECTION.
268
269 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
270 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
271 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
272 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
273 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
274 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
275 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
276 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
277 POSSIBILITY OF SUCH DAMAGES.
278
279 END OF TERMS AND CONDITIONS
280
281 Appendix: How to Apply These Terms to Your New Programs
282
283 If you develop a new program, and you want it to be of the greatest
284 possible use to the public, the best way to achieve this is to make it
285 free software which everyone can redistribute and change under these terms.
286
287 To do so, attach the following notices to the program. It is safest
288 to attach them to the start of each source file to most effectively
289 convey the exclusion of warranty; and each file should have at least
290 the "copyright" line and a pointer to where the full notice is found.
291
292 <one line to give the program's name and a brief idea of what it does.>
293 Copyright (C) 19yy <name of author>
294
295 This program is free software; you can redistribute it and/or modify
296 it under the terms of the GNU General Public License as published by
297 the Free Software Foundation; either version 2 of the License, or
298 (at your option) any later version.
299
300 This program is distributed in the hope that it will be useful,
301 but WITHOUT ANY WARRANTY; without even the implied warranty of
302 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303 GNU General Public License for more details.
304
305 You should have received a copy of the GNU General Public License
306 along with this program; if not, write to the Free Software
307 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02111, USA.
308
309 Also add information on how to contact you by electronic and paper mail.
310
311 If the program is interactive, make it output a short notice like this
312 when it starts in an interactive mode:
313
314 Gnomovision version 69, Copyright (C) 19yy name of author
315 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
316 This is free software, and you are welcome to redistribute it
317 under certain conditions; type `show c' for details.
318
319 The hypothetical commands `show w' and `show c' should show the appropriate
320 parts of the General Public License. Of course, the commands you use may
321 be called something other than `show w' and `show c'; they could even be
322 mouse-clicks or menu items--whatever suits your program.
323
324 You should also get your employer (if you work as a programmer) or your
325 school, if any, to sign a "copyright disclaimer" for the program, if
326 necessary. Here is a sample; alter the names:
327
328 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
329 `Gnomovision' (which makes passes at compilers) written by James Hacker.
330
331 <signature of Ty Coon>, 1 April 1989
332 Ty Coon, President of Vice
333
334 This General Public License does not permit incorporating your program into
335 proprietary programs. If your program is a subroutine library, you may
336 consider it more useful to permit linking proprietary applications with the
337 library. If this is what you want to do, use the GNU Library General
338 Public License instead of this License.
0 /* emacs objective-c mode -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "StoreManager.h"
4
5 @interface CalendarView : NSView
6 {
7 NSTextField *title;
8 Date *date;
9 Date *monthDisplayed;
10 NSPopUpButton *month;
11 NSStepper *stepper;
12 NSTextField *text;
13 NSMatrix *matrix;
14 NSFont *normalFont;
15 NSFont *boldFont;
16 IBOutlet id delegate;
17 IBOutlet NSObject <AgendaDataSource> *dataSource;
18 NSTimer *_dayTimer;
19 int bezeledCell;
20 }
21
22 - (id)initWithFrame:(NSRect)frame;
23 - (Date *)date;
24 - (NSString *)dateAsString;
25 - (id)delegate;
26 - (id)dataSource;
27 - (void)setDate:(Date *)date;
28 - (void)setDelegate:(id)delegate;
29 - (void)setDataSource:(NSObject <AgendaDataSource> *)source;
30 @end
31
32 @interface NSObject(CalendarViewDelegate)
33 - (void)calendarView:(CalendarView *)cs selectedDateChanged:(Date *)date;
34 - (void)calendarView:(CalendarView *)cs currentDateChanged:(Date *)date;
35 - (void)calendarView:(CalendarView *)cs userActionForDate:(Date *)date;
36 @end
0 #import <AppKit/AppKit.h>
1 #import "Date.h"
2 #import "CalendarView.h"
3 #import "StoreManager.h"
4
5 @interface DayFormatter : NSFormatter
6 @end
7 @implementation DayFormatter
8 - (NSString *)stringForObjectValue:(id)anObject
9 {
10 NSAssert([anObject isKindOfClass:[Date class]], @"Needs a Date as input");
11 return [NSString stringWithFormat:@"%2d", [anObject dayOfMonth]];
12 }
13 - (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
14 {
15 return NO;
16 }
17 - (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
18 {
19 return nil;
20 }
21 @end
22
23 @implementation CalendarView
24 - (void)dealloc
25 {
26 [[NSNotificationCenter defaultCenter] removeObserver:self];
27 RELEASE(date);
28 RELEASE(monthDisplayed);
29 [_dayTimer invalidate];
30 RELEASE(_dayTimer);
31 RELEASE(delegate);
32 RELEASE(dataSource);
33 [boldFont release];
34 [normalFont release];
35 [title release];
36 [matrix release];
37 [month release];
38 [stepper release];
39 [super dealloc];
40 }
41
42 - (id)initWithFrame:(NSRect)frame
43 {
44 int i;
45 int j;
46 int tag;
47 Date *now;
48 DayFormatter *formatter;
49
50 self = [super initWithFrame:frame];
51 if (self) {
52 NSArray *months = [[NSUserDefaults standardUserDefaults] objectForKey:NSMonthNameArray];
53 NSArray *days = [[NSUserDefaults standardUserDefaults] objectForKey:NSShortWeekDayNameArray];
54 boldFont = RETAIN([NSFont boldSystemFontOfSize:11]);
55 normalFont = RETAIN([NSFont systemFontOfSize:11]);
56 delegate = nil;
57 dataSource = nil;
58
59 title = [[NSTextField alloc] initWithFrame: NSMakeRect(85, 142, 80, 20)];
60 [title setEditable:NO];
61 [title setDrawsBackground:NO];
62 [title setBezeled:NO];
63 [title setBordered:NO];
64 [title setSelectable:NO];
65 [title setFont:normalFont];
66 [title setAlignment: NSCenterTextAlignment];
67 [self addSubview: title];
68
69 month = [[NSPopUpButton alloc] initWithFrame: NSMakeRect(8, 140, 80, 25)];
70 [month setFont:normalFont];
71 [month addItemsWithTitles: months];
72 [month setTarget: self];
73 [month setAction: @selector(selectMonth:)];
74 [self addSubview: month];
75
76 text = [[NSTextField alloc] initWithFrame: NSMakeRect(164, 142, 40, 21)];
77 [text setEditable: NO];
78 [text setAlignment: NSRightTextAlignment];
79 [text setFont:normalFont];
80 [self addSubview: text];
81
82 stepper = [[NSStepper alloc] initWithFrame: NSMakeRect(206, 140, 16, 25)];
83 [stepper setMinValue: 1970];
84 [stepper setMaxValue: 2037];
85 [stepper setTarget: self];
86 [stepper setAction: @selector(selectYear:)];
87 [stepper setFont:normalFont];
88 [self addSubview: stepper];
89
90 NSTextFieldCell *cell = [NSTextFieldCell new];
91 [cell setEditable: NO];
92 [cell setSelectable: NO];
93 [cell setAlignment: NSRightTextAlignment];
94 [cell setFont:normalFont];
95
96 matrix = [[NSMatrix alloc] initWithFrame: NSMakeRect(9, 8, 220, 128)
97 mode: NSListModeMatrix
98 prototype: cell
99 numberOfRows: 7
100 numberOfColumns: 8];
101 [matrix setIntercellSpacing: NSZeroSize];
102 [matrix setDelegate:self];
103 [matrix setAction: @selector(selectDay:)];
104 [matrix setDoubleAction: @selector(doubleClick:)];
105
106 NSColor *orange = [NSColor orangeColor];
107 NSColor *white = [NSColor whiteColor];
108 for (i = 0; i < 8; i++) {
109 cell = [matrix cellAtRow: 0 column: i];
110 [cell setBackgroundColor: orange];
111 [cell setTextColor: white];
112 [cell setDrawsBackground: YES];
113 if (i < 7 && i > 0)
114 [cell setStringValue: [[days objectAtIndex: i] substringToIndex:1]];
115 else if (i == 7)
116 [cell setStringValue: [[days objectAtIndex: 0] substringToIndex:1]];
117 }
118 for (i = 0; i < 7; i++) {
119 cell = [matrix cellAtRow: i column: 0];
120 [cell setBackgroundColor: orange];
121 [cell setTextColor: white];
122 [cell setDrawsBackground: YES];
123 }
124 formatter = [DayFormatter new];
125 for (i = 1, tag = 1; i < 8; i++) {
126 for (j = 1; j < 7; j++) {
127 [[matrix cellAtRow: j column: i] setFormatter:formatter];
128 [[matrix cellAtRow: j column: i] setTag:tag++];
129 }
130 }
131 [formatter release];
132 [self addSubview: matrix];
133
134 /*
135 * FIXME : this should be [Date today] but it leads to
136 * every appointment starting at 00:00. Probably a problem
137 * between ical time and ical date
138 */
139 now = [Date now];
140 [self setDate:now];
141 [now incrementDay];
142 [now clearTime];
143 _dayTimer = [[NSTimer alloc] initWithFireDate:[now calendarDate]
144 interval:86400 target:self
145 selector:@selector(dayChanged:)
146 userInfo:nil
147 repeats:YES];
148 [[NSRunLoop currentRunLoop] addTimer:_dayTimer forMode:NSDefaultRunLoopMode];
149 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dataChanged:) name:SADataChanged object:nil];
150 }
151 return self;
152 }
153
154 - (void)updateTitle
155 {
156 [title setStringValue:[self dateAsString]];
157 }
158
159 - (void)clearSelectedDay
160 {
161 [[matrix cellWithTag:bezeledCell] setBezeled:NO];
162 }
163
164 - (void)setSelectedDay
165 {
166 NSCell *cell;
167 int i, j;
168 id object;
169
170 for (i = 1; i < 8; i++) {
171 for (j = 1; j < 7; j++) {
172 cell = [matrix cellAtRow:j column:i];
173 object = [cell objectValue];
174 if (object != nil && ![date compare:object]) {
175 bezeledCell = [cell tag];
176 [cell setBezeled:YES];
177 [self updateTitle];
178 return;
179 }
180 }
181 }
182 }
183
184 - (void)updateView
185 {
186 int row, column, week;
187 Date *day, *today;
188 NSTextFieldCell *cell;
189 NSColor *clear = [NSColor clearColor];
190 NSColor *white = [NSColor whiteColor];
191 NSColor *black = [NSColor blackColor];
192
193 [self clearSelectedDay];
194 today = [Date today];
195 day = [monthDisplayed copy];
196 [day setDay: 1];
197 week = [day weekOfYear];
198 column = [day weekday];
199 if (!column)
200 column = 7;
201 [day changeDayBy:1-column];
202 for (row = 1; row < 7; row++, week++) {
203 [[matrix cellAtRow:row column:0] setStringValue:[NSString stringWithFormat:@"%d ", week]];
204 for (column = 1; column < 8; column++, [day incrementDay]) {
205 cell = [matrix cellAtRow: row column: column];
206 if ([day compare:today] == 0) {
207 [cell setBackgroundColor:[NSColor yellowColor]];
208 [cell setDrawsBackground:YES];
209 } else {
210 [cell setBackgroundColor:clear];
211 [cell setDrawsBackground:NO];
212 }
213 [cell setObjectValue:[day copy]];
214 if (dataSource && [[dataSource scheduledAppointmentsForDay:day] count])
215 [cell setFont:boldFont];
216 else
217 [cell setFont: normalFont];
218 if ([day monthOfYear] == [monthDisplayed monthOfYear])
219 [cell setTextColor:black];
220 else
221 [cell setTextColor:white];
222 }
223 }
224 [self setSelectedDay];
225 [day release];
226 }
227
228 - (void)dayChanged:(NSTimer *)timer
229 {
230 [self updateView];
231 if ([delegate respondsToSelector:@selector(calendarView:currentDateChanged:)])
232 [delegate calendarView:self currentDateChanged:[Date today]];
233 }
234
235 - (void)selectMonth:(id)sender
236 {
237 int idx = [month indexOfSelectedItem] + 1;
238 [date setMonth: idx];
239 [monthDisplayed setMonth: idx];
240 [self updateView];
241 if ([delegate respondsToSelector:@selector(calendarView:selectedDateChanged:)])
242 [delegate calendarView:self selectedDateChanged:date];
243 }
244
245 - (void)selectYear:(id)sender
246 {
247 int year = [stepper intValue];
248 [text setIntValue: year];
249 [date setYear: year];
250 [monthDisplayed setYear: year];
251 [self updateView];
252 if ([delegate respondsToSelector:@selector(calendarView:selectedDateChanged:)])
253 [delegate calendarView:self selectedDateChanged:date];
254 }
255
256 - (void)selectDay:(id)sender
257 {
258 id day = [[matrix selectedCell] objectValue];
259 if ([day isKindOfClass:[Date class]]) {
260 [self clearSelectedDay];
261 ASSIGNCOPY(date, day);
262 [self setSelectedDay];
263 if ([delegate respondsToSelector:@selector(calendarView:selectedDateChanged:)])
264 [delegate calendarView:self selectedDateChanged:date];
265 }
266 }
267
268 - (void)doubleClick:(id)sender
269 {
270 if ([[matrix selectedCell] tag] > 0 && [delegate respondsToSelector:@selector(calendarView:userActionForDate:)])
271 [delegate calendarView:self userActionForDate:date];
272 }
273
274 - (void)setDelegate: (id)aDelegate
275 {
276 ASSIGN(delegate, aDelegate);
277 }
278 - (id)delegate
279 {
280 return delegate;
281 }
282
283 - (void)setDate:(Date *)nDate
284 {
285 ASSIGNCOPY(date, nDate);
286 ASSIGNCOPY(monthDisplayed, nDate);
287 [text setIntValue: [nDate year]];
288 [stepper setIntValue: [nDate year]];
289 [month selectItemAtIndex: [nDate monthOfYear] - 1];
290 [self updateView];
291 if ([delegate respondsToSelector:@selector(calendarView:selectedDateChanged:)])
292 [delegate calendarView:self selectedDateChanged:date];
293 }
294 - (Date *)date
295 {
296 return date;
297 }
298
299 - (NSString *)dateAsString
300 {
301 return [[date calendarDate] descriptionWithCalendarFormat:[[NSUserDefaults standardUserDefaults] objectForKey:NSShortDateFormatString]];
302 }
303
304 - (void)setDataSource:(NSObject <AgendaDataSource> *)source
305 {
306 ASSIGN(dataSource, source);
307 [self updateView];
308 }
309 - (id)dataSource
310 {
311 return dataSource;
312 }
313
314 - (void)dataChanged:(NSNotification *)not
315 {
316 [self updateView];
317 }
318 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 @class ConfigManager;
3
4 @protocol ConfigListener
5 - (void)config:(ConfigManager*)config dataDidChangedForKey:(NSString *)key;
6 @end
7
8 @interface ConfigManager : NSObject
9 {
10 NSString *_key;
11 ConfigManager *_parent;
12 NSMutableDictionary *_defaults;
13 NSMutableDictionary *_dict;
14 NSMutableDictionary *_cpkey;
15 }
16
17 - (ConfigManager *)initForKey:(NSString *)key withParent:(ConfigManager *)parent;
18 + (ConfigManager *)globalConfig;
19 - (void)registerDefaults:(NSDictionary *)defaults;
20 - (void)registerClient:(id <ConfigListener>)client forKey:(NSString *)key;
21 - (void)unregisterClient:(id <ConfigListener>)client forKey:(NSString*)key;
22 - (void)unregisterClient:(id <ConfigListener>)client;
23 - (id)objectForKey:(NSString *)key;
24 - (void)removeObjectForKey:(NSString *)key;
25 - (void)setObject:(id)value forKey:(NSString *)key;
26 - (int)integerForKey:(NSString *)key;
27 - (void)setInteger:(int)value forKey:(NSString *)key;
28 - (NSDictionary *)dictionaryForKey:(NSString *)key;
29 - (void)setDictionary:(NSDictionary *)dict forKey:(NSString *)key;
30
31 @end
32
0 #import <Foundation/Foundation.h>
1 #import "ConfigManager.h"
2
3 @implementation ConfigManager(Private)
4
5 - (ConfigManager *)initRoot
6 {
7 self = [super init];
8 if (self) {
9 _cpkey = [NSMutableDictionary new];
10 _dict = [NSMutableDictionary new];
11 _defaults = [NSMutableDictionary new];
12 [_dict setDictionary:[[NSUserDefaults standardUserDefaults]
13 persistentDomainForName:[[NSProcessInfo processInfo] processName]]];
14 }
15 return self;
16 }
17
18 @end
19
20
21 @implementation ConfigManager
22
23 static ConfigManager *singleton;
24
25 - (ConfigManager *)initForKey:(NSString *)key withParent:(ConfigManager *)parent
26 {
27 NSAssert(key != nil, @"ConfigManager initForKey called with nil key");
28 self = [super init];
29 if (self) {
30 _cpkey = [NSMutableDictionary new];
31 _dict = [NSMutableDictionary new];
32 _defaults = [NSMutableDictionary new];
33 if (parent == nil)
34 parent = [ConfigManager globalConfig];
35 [_dict setDictionary:[parent dictionaryForKey:key]];
36 ASSIGN(_parent, parent);
37 ASSIGNCOPY(_key, key);
38 }
39 return self;
40 }
41
42 + (ConfigManager *)globalConfig
43 {
44 if (singleton == nil)
45 singleton = [[ConfigManager alloc] initRoot];
46 return singleton;
47 }
48
49 - (void)dealloc
50 {
51 RELEASE(_key);
52 RELEASE(_parent);
53 [_defaults release];
54 [_dict release];
55 [_cpkey release];
56 [super dealloc];
57 }
58
59 - (void)notifyListenerForKey:(NSString *)key
60 {
61 NSEnumerator *enumerator;
62 NSMutableSet *set, *tmp;
63 id <ConfigListener> listener;
64 if (key)
65 set = [_cpkey objectForKey:key];
66 else {
67 set = [NSMutableSet new];
68 enumerator = [_cpkey objectEnumerator];
69 while ((tmp = [enumerator nextObject]))
70 [set unionSet:tmp];
71 }
72 enumerator = [set objectEnumerator];
73 while ((listener = [enumerator nextObject]))
74 [listener config:self dataDidChangedForKey:key];
75 if (!key)
76 [set release];
77 }
78
79 - (void)registerDefaults:(NSDictionary *)defaults
80 {
81 [_defaults addEntriesFromDictionary:defaults];
82 }
83
84 - (id)objectForKey:(NSString *)key
85 {
86 id obj = [_dict objectForKey:key];
87
88 if (obj)
89 return obj;
90 return [_defaults objectForKey:key];
91 }
92
93 - (void)removeObjectForKey:(NSString *)key
94 {
95 [_dict removeObjectForKey:key];
96 [self notifyListenerForKey:key];
97 if (_parent)
98 [_parent setObject:_dict forKey:_key];
99 else
100 [[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
101 }
102
103 - (void)setObject:(id)value forKey:(NSString *)key
104 {
105 [_dict setObject:value forKey:key];
106 [self notifyListenerForKey:key];
107 if (_parent)
108 [_parent setObject:_dict forKey:_key];
109 else
110 [[NSUserDefaults standardUserDefaults] setObject:value forKey:key];
111 }
112
113 - (int)integerForKey:(NSString *)key
114 {
115 id object;
116
117 object = [self objectForKey:key];
118 if (object != nil)
119 return [object intValue];
120 return 0;
121 }
122
123 - (void)setInteger:(int)value forKey:(NSString *)key
124 {
125 [self setObject:[NSNumber numberWithInt:value] forKey:key];
126 }
127
128 - (NSDictionary *)dictionaryForKey:(NSString *)key
129 {
130 id object;
131
132 object = [self objectForKey:key];
133 if (object != nil && [object isKindOfClass:[NSDictionary class]])
134 return object;
135 return nil;
136 }
137
138 - (void)setDictionary:(NSDictionary *)dict forKey:(NSString *)key
139 {
140 [self setObject:dict forKey:key];
141 }
142
143 - (void)registerClient:(id <ConfigListener>)client forKey:(NSString*)key
144 {
145 NSAssert(key != nil, @"You have to register for a specific key");
146 NSMutableSet *listeners = [_cpkey objectForKey:key];
147 if (listeners)
148 [listeners addObject:client];
149 else
150 listeners = [NSMutableSet setWithObject:client];
151 [_cpkey setObject:listeners forKey:key];
152 }
153
154 - (void)unregisterClient:(id <ConfigListener>)client forKey:(NSString*)key
155 {
156 NSMutableSet *set = [_cpkey objectForKey:key];
157 if (set)
158 [set removeObject:client];
159 }
160
161 - (void)unregisterClient:(id <ConfigListener>)client
162 {
163 NSMutableSet *set;
164 NSEnumerator *enumerator = [_cpkey objectEnumerator];
165
166 while ((set = [enumerator nextObject]))
167 [set removeObject:client];
168 }
169
170 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3
4 @interface DataTree : NSObject
5 {
6 id _parent;
7 NSMutableArray *_children;
8 NSMutableDictionary *_attributes;
9 }
10
11 + (id)dataTreeWithAttributes:(NSDictionary *)attributes;
12 - (void)setParent:(id)parent;
13 - (id)parent;
14 - (void)setChildren:(NSArray *)children;
15 - (void)addChild:(id)child;
16 - (void)removeChildren;
17 - (NSArray *)children;
18 - (void)setAttributes:(NSDictionary *)attributes;
19 - (void)setValue:(id)value forKey:(NSString *)key;
20 - (id)valueForKey:(NSString *)key;
21 - (void)sortChildrenUsingFunction:(int (*)(id, id, void *))compare context:(void *)context;
22 @end
0 #import "DataTree.h"
1
2 @implementation DataTree
3 - (id)init
4 {
5 self = [super init];
6 if (self) {
7 _parent = nil;
8 _children = [[NSMutableArray alloc] init];
9 _attributes = [[NSMutableDictionary alloc] init];
10 }
11 return self;
12 }
13
14 - (void)dealloc
15 {
16 RELEASE(_parent);
17 [_attributes release];
18 [_children release];
19 [super dealloc];
20 }
21
22 - (id)initWithAttributes:(NSDictionary *)attributes
23 {
24 self = [self init];
25 if (self)
26 [self setAttributes:attributes];
27 return self;
28 }
29
30 + (id)dataTreeWithAttributes:(NSDictionary *)attributes
31 {
32 return AUTORELEASE([[DataTree alloc] initWithAttributes:attributes]);
33 }
34
35 - (void)setParent:(id)parent
36 {
37 ASSIGN(_parent, parent);
38 }
39
40 - (id)parent
41 {
42 return _parent;
43 }
44
45 - (void)setChildren:(NSArray *)children
46 {
47 [_children setArray:children];
48 }
49
50 - (void)addChild:(id)child
51 {
52 [_children addObject:child];
53 }
54
55 - (void)removeChildren
56 {
57 [_children removeAllObjects];
58 }
59
60 - (NSArray *)children
61 {
62 return _children;
63 }
64
65 - (void)setAttributes:(NSDictionary *)attributes
66 {
67 [_attributes setDictionary:attributes];
68 }
69
70 - (void)setValue:(id)value forKey:(NSString *)key
71 {
72 [_attributes setValue:value forKey:key];
73 }
74
75 - (id)valueForKey:(NSString *)key
76 {
77 return [_attributes valueForKey:key];
78 }
79
80 - (void)sortChildrenUsingFunction:(int (*)(id, id, void *))compare context:(void *)context
81 {
82 [_children sortUsingFunction:compare context:context];
83 }
84 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3 #import <ical.h>
4
5 @interface Date : NSObject
6 {
7 struct icaltimetype _time;
8 }
9
10 + (id)now;
11 + (id)today;
12 - (id)initWithCalendarDate:(NSCalendarDate *)cd withTime:(BOOL)time;
13 - (NSCalendarDate *)calendarDate;
14 - (NSComparisonResult)compare:(id)aDate;
15 - (NSComparisonResult)compareTime:(id)aTime;
16 - (int)year;
17 - (int)monthOfYear;
18 - (int)hourOfDay;
19 - (int)secondOfMinute;
20 - (int)minuteOfHour;
21 - (int)minuteOfDay;
22 - (int)dayOfMonth;
23 - (int)weekday;
24 - (int)weekOfYear;
25 - (int)numberOfDaysInMonth;
26 - (int)daysUntil:(Date *)date;
27 - (int)daysSince:(Date *)date;
28 - (int)weeksUntil:(Date *)date;
29 - (int)weeksSince:(Date *)date;
30 - (int)monthsUntil:(Date *)date;
31 - (int)monthsSince:(Date *)date;
32 - (int)yearsUntil:(Date *)date;
33 - (int)yearSince:(Date *)date;
34 - (void)clearTime;
35 - (void)setSecondOfMinute:(int)second;
36 - (void)setMinute:(int)minute;
37 - (void)setDay:(int)day;
38 - (void)setMonth:(int)month;
39 - (void)setYear:(int)year;
40 - (void)incrementDay;
41 - (void)changeYearBy:(int)diff;
42 - (void)changeDayBy:(int)diff;
43 - (void)changeMinuteBy:(int)diff;
44 - (NSEnumerator *)enumeratorTo:(Date *)end;
45 - (BOOL)isDate;
46 - (void)setDate:(BOOL)date;
47 @end
48
49 @interface Date(iCalendar)
50 - (id)initWithICalTime:(struct icaltimetype)time;
51 - (void)setDateToICalTime:(struct icaltimetype)time;
52 - (struct icaltimetype)iCalTime;
53 @end
54
0 #import "Date.h"
1
2 @interface NSDateEnumerator : NSEnumerator
3 {
4 Date *_start;
5 Date *_end;
6 }
7 - (id)initWithStart:(Date *)start end:(Date *)end;
8 @end
9 @implementation NSDateEnumerator
10 - (id)initWithStart:(Date *)start end:(Date *)end
11 {
12 self = [super init];
13 if (self != nil) {
14 _start = [start copy];
15 _end = [end copy];
16 [_start changeDayBy:-1];
17 }
18 return self;
19 }
20 - (id)nextObject
21 {
22 [_start incrementDay];
23 if ([_start daysUntil:_end] < 0)
24 return nil;
25 return _start;
26 }
27 - (void)dealloc
28 {
29 [_start release];
30 [_end release];
31 [super dealloc];
32 }
33 @end
34
35 @implementation Date(NSCoding)
36 - (void)encodeWithCoder:(NSCoder *)coder
37 {
38 [coder encodeObject:[NSString stringWithCString:icaltime_as_ical_string(_time)] forKey:@"icalTime"];
39 }
40 - (id)initWithCoder:(NSCoder *)coder
41 {
42 _time = icaltime_from_string([[coder decodeObjectForKey:@"icalTime"] cString]);
43 return self;
44 }
45 @end
46
47 /*
48 * Based on ChronographerSource Appointment class
49 */
50
51 @implementation Date
52 - (id)copyWithZone:(NSZone *)zone
53 {
54 Date *new = [Date allocWithZone:zone];
55 new->_time = _time;
56 return new;
57 }
58
59 - (NSComparisonResult)compare:(id)aDate
60 {
61 return icaltime_compare_date_only(_time, ((Date *)aDate)->_time);
62 }
63
64 - (NSComparisonResult)compareTime:(id)aDate
65 {
66 return icaltime_compare(_time, ((Date *)aDate)->_time);
67 }
68
69 - (NSString *)description
70 {
71 return [NSString stringWithCString:icaltime_as_ical_string(_time)];
72 }
73
74 - (id)init
75 {
76 const char *tzone;
77 icaltimezone *tz, *utc;
78
79 self = [super init];
80 if (self) {
81 tzone = [[[[NSCalendarDate calendarDate] timeZone] description] cString];
82 utc = icaltimezone_get_utc_timezone();
83 tz = icaltimezone_get_builtin_timezone(tzone);
84 if (!tz)
85 NSLog(@"Couldn't get a timezone corresponding to %s", tzone);
86 _time = icaltime_current_time_with_zone(NULL);
87 icaltimezone_convert_time(&_time, utc, tz);
88 }
89 return self;
90 }
91 - (id)initWithCalendarDate:(NSCalendarDate *)cd withTime:(BOOL)time
92 {
93 self = [self init];
94 if (self) {
95 [self setDate:!time];
96 _time.year = [cd yearOfCommonEra];
97 _time.month = [cd monthOfYear];
98 _time.day = [cd dayOfMonth];
99 if (time) {
100 _time.hour = [cd hourOfDay];
101 _time.minute = [cd minuteOfHour];
102 _time.second = [cd secondOfMinute];
103 }
104 }
105 return self;
106 }
107 + (id)now
108 {
109 return AUTORELEASE([[Date alloc] init]);
110 }
111 + (id)today
112 {
113 Date *d = [[Date alloc] init];
114 [d setDate:YES];
115 return AUTORELEASE(d);
116 }
117
118 - (NSCalendarDate *)calendarDate
119 {
120 return [NSCalendarDate dateWithYear:_time.year
121 month:_time.month
122 day:_time.day
123 hour:_time.hour
124 minute:_time.minute
125 second:_time.second
126 timeZone:nil];
127 }
128
129 - (int)year
130 {
131 return _time.year;
132 }
133 - (int)monthOfYear
134 {
135 return _time.month;
136 }
137 - (int)hourOfDay
138 {
139 return _time.hour;
140 }
141 - (int)secondOfMinute
142 {
143 return _time.second;
144 }
145 - (int)minuteOfHour
146 {
147 return _time.minute;
148 }
149 - (int)minuteOfDay
150 {
151 return _time.hour * 60 + _time.minute;
152 }
153 - (int)dayOfMonth
154 {
155 return _time.day;
156 }
157 /* 0 = sunday */
158 - (int)weekday
159 {
160 return icaltime_day_of_week(_time) - 1;
161 }
162 - (int)weekOfYear
163 {
164 /* FIXME : ical function doesn't work. See ticket #3 */
165 return icaltime_week_number(_time);
166 }
167 - (int)numberOfDaysInMonth
168 {
169 return icaltime_days_in_month(_time.month, _time.year);
170 }
171
172 /* if diff is 23, returns 0 day */
173 - (int)daysUntil:(Date *)date
174 {
175 struct icaldurationtype dt;
176
177 dt = icaltime_subtract(date->_time, _time);
178 if (dt.is_neg)
179 return -dt.days;
180 return dt.days;
181 }
182
183 - (int)daysSince:(Date *)date
184 {
185 struct icaldurationtype dt;
186
187 dt = icaltime_subtract(_time, date->_time);
188 if (dt.is_neg)
189 return -dt.days;
190 return dt.days;
191 }
192
193 - (int)weeksUntil:(Date *)date
194 {
195 struct icaldurationtype dt;
196
197 dt = icaltime_subtract(date->_time, _time);
198 if (dt.is_neg)
199 return -dt.weeks;
200 return dt.weeks;
201 }
202
203 - (int)weeksSince:(Date *)date
204 {
205 struct icaldurationtype dt;
206
207 dt = icaltime_subtract(_time, date->_time);
208 if (dt.is_neg)
209 return -dt.weeks;
210 return dt.weeks;
211 }
212
213 - (int)monthsUntil:(Date *)date
214 {
215 int months = 0;
216 NSLog(@"monthsUntil");
217 return months;
218 }
219
220 - (int)monthsSince:(Date *)date
221 {
222 int months = 0;
223 NSLog(@"monthsSince");
224 return months;
225 }
226
227 - (int)yearsUntil:(Date *)date
228 {
229 return date->_time.year - _time.year;
230 }
231
232 - (int)yearSince:(Date *)date
233 {
234 return _time.year - date->_time.year;
235 }
236
237 - (void)clearTime
238 {
239 _time.hour = 0;
240 _time.minute = 0;
241 _time.second = 0;
242 }
243
244 - (void)setSecondOfMinute:(int)second
245 {
246 _time.second = second;
247 _time = icaltime_normalize(_time);
248 }
249
250 - (void)setMinute:(int)minute
251 {
252 int old = [self minuteOfDay];
253 struct icaldurationtype dt = {0, 0, 0, 0, minute - old, 0};
254 _time = icaltime_add(_time, dt);
255 }
256
257 - (void)setDay:(int)day
258 {
259 _time.day = day;
260 _time = icaltime_normalize(_time);
261 }
262
263 - (void)setMonth:(int)month
264 {
265 _time.month = month;
266 _time = icaltime_normalize(_time);
267 }
268
269 - (void)setYear:(int)year
270 {
271 _time.year = year;
272 }
273
274 - (void)incrementDay
275 {
276 struct icaldurationtype dt = {0, 1, 0, 0, 0, 0};
277 _time = icaltime_add(_time, dt);
278 }
279
280 - (void)changeYearBy:(int)diff
281 {
282 _time.year += diff;
283 }
284
285 - (void)changeDayBy:(int)diff
286 {
287 struct icaldurationtype dt = {diff < 0, ABS(diff), 0, 0, 0, 0};
288 _time = icaltime_add(_time, dt);
289 }
290
291 - (void)changeMinuteBy:(int)diff
292 {
293 struct icaldurationtype dt = {diff < 0, 0, 0, 0, ABS(diff), 0};
294 _time = icaltime_add(_time, dt);
295 }
296
297 - (NSEnumerator *)enumeratorTo:(Date *)end
298 {
299 id e;
300 e = [NSDateEnumerator allocWithZone:NSDefaultMallocZone()];
301 e = [e initWithStart:self end:end];
302 return AUTORELEASE(e);
303 }
304
305 - (BOOL)isDate
306 {
307 return _time.is_date;
308 }
309 - (void)setDate:(BOOL)date
310 {
311 _time.is_date = date;
312 if (!date)
313 icaltime_set_timezone(&_time, NULL);
314 }
315 @end
316
317 @implementation Date(iCalendar)
318 - (id)initWithICalTime:(struct icaltimetype)time
319 {
320 self = [super init];
321 if (self)
322 [self setDateToICalTime:time];
323 return self;
324 }
325
326 - (void)setDateToICalTime:(struct icaltimetype)time
327 {
328 _time = time;
329 }
330
331 - (struct icaltimetype)iCalTime
332 {
333 return _time;
334 }
335 @end
336
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "ConfigManager.h"
4 #import "Event.h"
5 #import "StoreManager.h"
6
7 @class AppointmentView;
8
9 @interface DayView : NSView <ConfigListener>
10 {
11 IBOutlet id <AgendaDataSource> dataSource;
12 IBOutlet id delegate;
13 int _firstH;
14 int _lastH;
15 int _minStep;
16 NSPoint _startPt;
17 NSPoint _endPt;
18 NSDictionary *_textAttributes;
19 AppointmentView *_selected;
20 NSColor *_backgroundColor;
21 NSColor *_alternateBackgroundColor;
22 }
23
24 - (void)selectAppointmentView:(AppointmentView *)aptv;
25 - (NSRect)frameForAppointment:(Event *)apt;
26 - (int)minuteToPosition:(int)minutes;
27 - (int)positionToMinute:(float)position;
28 - (int)roundMinutes:(int)minutes;
29 - (id)delegate;
30 - (void)reloadData;
31 - (int)firstHour;
32 - (int)lastHour;
33 - (int)minimumStep;
34 - (void)deselectAll:(id)sender;
35 @end
36
37 @interface NSObject(DayViewDelegate)
38 - (void)dayView:(DayView *)dayview editEvent:(Event *)event;
39 - (void)dayView:(DayView *)dayview modifyEvent:(Event *)event;
40 - (void)dayView:(DayView *)dayview createEventFrom:(int)start to:(int)end;
41 - (void)dayView:(DayView *)dayview selectEvent:(Event *)event;
42 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "AgendaStore.h"
3 #import "DayView.h"
4 #import "ConfigManager.h"
5 #import "iCalTree.h"
6 #import "defines.h"
7
8 static NSImage *repeatImage;
9
10 #define RedimRect(frame) NSMakeRect(frame.origin.x, frame.origin.y, frame.size.width, 10)
11 #define TextRect(rect) NSMakeRect(rect.origin.x + 4, rect.origin.y, rect.size.width - 8, rect.size.height - 2)
12
13 @interface AppointmentView : NSView
14 {
15 Event *_apt;
16 BOOL _selected;
17 }
18 - (Event *)appointment;
19 - (void)setSelected:(BOOL)selected;
20 @end
21
22 @implementation AppointmentView
23 - (id)initWithFrame:(NSRect)frameRect appointment:(Event *)apt;
24 {
25 self = [super initWithFrame:frameRect];
26 if (self) {
27 ASSIGN(_apt, apt);
28 _selected = NO;
29 }
30 return self;
31 }
32 - (void)dealloc
33 {
34 RELEASE(_apt);
35 [super dealloc];
36 }
37 - (BOOL)selected
38 {
39 return _selected;
40 }
41 - (void)setSelected:(BOOL)selected
42 {
43 _selected = selected;
44 }
45 #define CEC_BORDERSIZE 1
46 #define RADIUS 5
47 - (void)drawRect:(NSRect)rect
48 {
49 NSString *title;
50 NSString *label;
51 Date *start = [_apt startDate];
52 NSColor *color = [[_apt store] eventColor];
53 NSColor *darkColor = [NSColor colorWithCalibratedRed:[color redComponent] - 0.3
54 green:[color greenComponent] - 0.3
55 blue:[color blueComponent] - 0.3
56 alpha:[color alphaComponent]];
57 NSDictionary *textAttributes = [NSDictionary dictionaryWithObject:[[_apt store] textColor]
58 forKey:NSForegroundColorAttributeName];
59
60 if ([_apt allDay])
61 title = [NSString stringWithFormat:@"All day : %@", [_apt summary]];
62 else
63 title = [NSString stringWithFormat:@"%2dh%0.2d : %@", [start hourOfDay], [start minuteOfHour], [_apt summary]];
64 if ([_apt text])
65 label = [NSString stringWithFormat:@"%@\n\n%@", title, [[_apt text] string]];
66 else
67 label = [NSString stringWithString:title];
68
69 PSnewpath();
70 PSmoveto(RADIUS + CEC_BORDERSIZE, CEC_BORDERSIZE);
71 PSrcurveto(-RADIUS, 0, -RADIUS, RADIUS, -RADIUS, RADIUS);
72 PSrlineto(0, NSHeight(rect) + rect.origin.y - 2 * (RADIUS + CEC_BORDERSIZE));
73 PSrcurveto( 0, RADIUS, RADIUS, RADIUS, RADIUS, RADIUS);
74 PSrlineto(NSWidth(rect) - 2 * (RADIUS + CEC_BORDERSIZE),0);
75 PSrcurveto( RADIUS, 0, RADIUS, -RADIUS, RADIUS, -RADIUS);
76 PSrlineto(0, -NSHeight(rect) - rect.origin.y + 2 * (RADIUS + CEC_BORDERSIZE));
77 PSrcurveto(0, -RADIUS, -RADIUS, -RADIUS, -RADIUS, -RADIUS);
78 PSclosepath();
79 PSgsave();
80 [color set];
81 PSsetalpha(0.7);
82 PSfill();
83 PSgrestore();
84 if (_selected)
85 [[NSColor whiteColor] set];
86 else
87 [darkColor set];
88 PSsetalpha(0.7);
89 PSsetlinewidth(CEC_BORDERSIZE);
90 PSstroke();
91 if (![_apt allDay]) {
92 NSRect rd = RedimRect(rect);
93 PSnewpath();
94 PSmoveto(RADIUS + CEC_BORDERSIZE, rd.origin.y);
95 PSrcurveto( -RADIUS, 0, -RADIUS, RADIUS, -RADIUS, RADIUS);
96 PSrlineto(0,NSHeight(rd) - 2 * (RADIUS + CEC_BORDERSIZE));
97 PSrcurveto( 0, RADIUS, RADIUS, RADIUS, RADIUS, RADIUS);
98 PSrlineto(NSWidth(rd) - 2 * (RADIUS + CEC_BORDERSIZE),0);
99 PSrcurveto( RADIUS, 0, RADIUS, -RADIUS, RADIUS, -RADIUS);
100 PSrlineto(0, -NSHeight(rd) + 2 * (RADIUS + CEC_BORDERSIZE));
101 PSrcurveto( 0, -RADIUS, -RADIUS, -RADIUS, -RADIUS, -RADIUS);
102 PSclosepath();
103 [darkColor set];
104 PSsetalpha(0.7);
105 PSfill();
106 }
107 [label drawInRect:TextRect(rect) withAttributes:textAttributes];
108 if ([_apt interval] != RI_NONE)
109 [repeatImage compositeToPoint:NSMakePoint(rect.size.width - 18, rect.size.height - 18) operation:NSCompositeSourceOver];
110 }
111
112 - (void)mouseDown:(NSEvent *)theEvent
113 {
114 DayView *parent = (DayView *)[self superview];
115 id delegate = [parent delegate];
116 NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
117 int diff;
118 int start;
119 int minutes;
120 BOOL keepOn = YES;
121 BOOL modified = NO;
122 BOOL inResize;
123
124 if ([theEvent clickCount] > 1) {
125 if ([delegate respondsToSelector:@selector(dayView:editEvent:)])
126 [delegate dayView:parent editEvent:_apt];
127 return;
128 }
129 [self becomeFirstResponder];
130 [parent selectAppointmentView:self];
131
132 if (![[_apt store] writable] || [_apt allDay])
133 return;
134 inResize = [self mouse:mouseLoc inRect:RedimRect([self bounds])];
135 if (inResize) {
136 [[NSCursor resizeUpDownCursor] push];
137 start = [[_apt startDate] minuteOfDay];
138 while (keepOn) {
139 theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
140 mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:self];
141
142 switch ([theEvent type]) {
143 case NSLeftMouseDragged:
144 minutes = [parent roundMinutes:[parent positionToMinute:mouseLoc.y] - start];
145 if (minutes != [_apt duration]) {
146 [_apt setDuration:minutes];
147 modified = YES;
148 [parent setNeedsDisplay:YES];
149 }
150 break;
151 case NSLeftMouseUp:
152 keepOn = NO;
153 break;
154 default:
155 break;
156 }
157 }
158 } else {
159 [[NSCursor openHandCursor] push];
160 mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:self];
161 diff = [parent minuteToPosition:[[_apt startDate] minuteOfDay]] - mouseLoc.y;
162 while (keepOn) {
163 theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
164 mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:self];
165 switch ([theEvent type]) {
166 case NSLeftMouseDragged:
167 minutes = [parent roundMinutes:[parent positionToMinute:mouseLoc.y + diff]];
168 if (minutes != [[_apt startDate] minuteOfDay]) {
169 [[_apt startDate] setMinute:minutes];
170 modified = YES;
171 [parent setNeedsDisplay:YES];
172 }
173 break;
174 case NSLeftMouseUp:
175 keepOn = NO;
176 break;
177 default:
178 break;
179 }
180 }
181 }
182 [NSCursor pop];
183 if (modified && [delegate respondsToSelector:@selector(dayView:modifyEvent:)])
184 [delegate dayView:parent modifyEvent:_apt];
185 }
186 - (Event *)appointment
187 {
188 return _apt;
189 }
190 - (BOOL)acceptsFirstResponder
191 {
192 return YES;
193 }
194 @end
195
196 NSComparisonResult compareAppointmentViews(id a, id b, void *data)
197 {
198 return [[[a appointment] startDate] compareTime:[[b appointment] startDate]];
199 }
200
201 @implementation DayView
202 - (NSDictionary *)defaults
203 {
204 NSDictionary *dict = [NSDictionary
205 dictionaryWithObjects:[NSArray arrayWithObjects:@"9", @"18", @"15", nil]
206 forKeys:[NSArray arrayWithObjects:FIRST_HOUR, LAST_HOUR, MIN_STEP, nil]];
207 return dict;
208 }
209
210 - (id)initWithFrame:(NSRect)frameRect
211 {
212 self = [super initWithFrame:frameRect];
213 if (self) {
214 ConfigManager *config = [ConfigManager globalConfig];
215 [config registerDefaults:[self defaults]];
216 [config registerClient:self forKey:FIRST_HOUR];
217 [config registerClient:self forKey:LAST_HOUR];
218 [config registerClient:self forKey:MIN_STEP];
219
220 _firstH = [config integerForKey:FIRST_HOUR];
221 _lastH = [config integerForKey:LAST_HOUR];
222 _minStep = [config integerForKey:MIN_STEP];
223 _textAttributes = [[NSDictionary dictionaryWithObject:[NSColor textColor]
224 forKey:NSForegroundColorAttributeName] retain];
225 _backgroundColor = [[[NSColor controlBackgroundColor] colorUsingColorSpaceName:NSCalibratedRGBColorSpace] retain];
226 _alternateBackgroundColor = [[NSColor colorWithCalibratedRed:[_backgroundColor redComponent] + 0.05
227 green:[_backgroundColor greenComponent] + 0.05
228 blue:[_backgroundColor blueComponent] + 0.05
229 alpha:[_backgroundColor alphaComponent]] retain];
230
231 if (!repeatImage) {
232 NSString *path = [[NSBundle mainBundle] pathForImageResource:@"repeat"];
233 repeatImage = [[NSImage alloc] initWithContentsOfFile:path];
234 [repeatImage setFlipped:YES];
235 }
236 [self reloadData];
237 }
238 return self;
239 }
240
241 - (void)dealloc
242 {
243 [_backgroundColor release];
244 [_alternateBackgroundColor release];
245 [_textAttributes release];
246 [super dealloc];
247 }
248
249 /* FIXME : the following could probably be simplified... */
250 - (int)_minuteToSize:(int)minutes
251 {
252 return minutes * [self frame].size.height / ((_lastH - _firstH + 1) * 60);
253 }
254 - (int)minuteToPosition:(int)minutes
255 {
256 return [self frame].size.height - [self _minuteToSize:minutes - (_firstH * 60)] - 1;
257 }
258 - (int)positionToMinute:(float)position
259 {
260 return ((_lastH + 1) * 60) - ((_lastH - _firstH + 1) * 60) * position / [self frame].size.height;
261 }
262
263 - (void)fixFrames
264 {
265 int i, j, k, n;
266 NSArray *subviews;
267 AppointmentView *view, *next;
268 NSRect fview, fnext;
269 float width, x;
270
271 subviews = [[self subviews] sortedArrayUsingFunction:compareAppointmentViews context:NULL];
272 if ([subviews count] < 2)
273 return;
274 for (i = 0; i < [subviews count] - 1; i++) {
275 view = [subviews objectAtIndex:i];
276 fview = [view frame];
277 for (j = i + 1, n = 1; j < [subviews count]; j++) {
278 next = [subviews objectAtIndex:j];
279 fnext = [next frame];
280 if (!NSIntersectsRect(fview, fnext))
281 break;
282 n++;
283 fview = NSUnionRect(fview, fnext);
284 }
285 if (n != 1) {
286 width = [view frame].size.width / n;
287 x = [view frame].origin.x;
288 for (k = i; k < i + n; k++) {
289 view = [subviews objectAtIndex:k];
290 fview = [view frame];
291 fview.size.width = width;
292 fview.origin.x = x;
293 x += width;
294 [view setFrame:fview];
295 }
296 }
297 }
298 }
299 - (NSRect)frameForAppointment:(Event *)apt
300 {
301 int size, start;
302
303 if ([apt allDay])
304 return NSMakeRect(40, 0, [self frame].size.width - 48, [self frame].size.height);
305 start = [self minuteToPosition:[[apt startDate] minuteOfDay]];
306 size = [self _minuteToSize:[apt duration]];
307 return NSMakeRect(40, start - size, [self frame].size.width - 48, size);
308 }
309 - (int)roundMinutes:(int)minutes
310 {
311 int rounded = minutes / _minStep * _minStep;
312 return (rounded < _minStep) ? _minStep : rounded;
313 }
314
315 - (void)drawRect:(NSRect)rect
316 {
317 NSSize size;
318 NSString *hour;
319 AppointmentView *aptv;
320 NSEnumerator *enumerator;
321 int h, start;
322 int hrow;
323 float miny, maxy;
324
325 /*
326 * FIXME : this is ugly and slow, we're doing
327 * work when it's not needed and probably twice.
328 */
329 enumerator = [[self subviews] objectEnumerator];
330 while ((aptv = [enumerator nextObject]))
331 [aptv setFrame:[self frameForAppointment:[aptv appointment]]];
332 [self fixFrames];
333 /*
334 * FIXME : if we draw the string in the same
335 * loop it doesn't appear on the screen.
336 */
337 hrow = [self _minuteToSize:60];
338 for (h = _firstH; h <= _lastH + 1; h++) {
339 start = [self minuteToPosition:h * 60];
340 if (h % 2)
341 [_backgroundColor set];
342 else
343 [_alternateBackgroundColor set];
344 NSRectFill(NSMakeRect(0, start, rect.size.width, hrow + 1));
345 }
346 for (h = _firstH; h <= _lastH; h++) {
347 hour = [NSString stringWithFormat:@"%d h", h];
348 start = [self minuteToPosition:h * 60];
349 size = [hour sizeWithAttributes:_textAttributes];
350 [hour drawAtPoint:NSMakePoint(4, start - hrow / 2 - size.height / 2) withAttributes:_textAttributes];
351 }
352 if (_startPt.x != _endPt.x && _startPt.y != _endPt.y) {
353 miny = MIN(_startPt.y, _endPt.y);
354 maxy = MAX(_startPt.y, _endPt.y);
355 [[NSColor grayColor] set];
356 NSFrameRect(NSMakeRect(40, miny, rect.size.width - 48, maxy - miny));
357 }
358 }
359
360 - (id)dataSource
361 {
362 return dataSource;
363 }
364 - (void)setDataSource:(id)source
365 {
366 dataSource = source;
367 }
368 - (id)delegate
369 {
370 return delegate;
371 }
372 - (void)setDelegate:(id)theDelegate
373 {
374 delegate = theDelegate;
375 }
376
377 - (void)selectAppointmentView:(AppointmentView *)aptv
378 {
379 if (_selected != aptv) {
380 [_selected setSelected:NO];
381 [aptv setSelected:YES];
382 [self setNeedsDisplay:YES];
383 _selected = aptv;
384 if ([delegate respondsToSelector:@selector(dayView:selectEvent:)])
385 [delegate dayView:self selectEvent:[aptv appointment]];
386 }
387 }
388
389 - (void)reloadData
390 {
391 ConfigManager *config = [ConfigManager globalConfig];
392 NSEnumerator *enumerator, *enm;
393 AppointmentView *aptv;
394 Event *apt;
395 NSSet *events;
396 BOOL found;
397
398 events = [dataSource scheduledAppointmentsForDay:nil];
399 enumerator = [[self subviews] objectEnumerator];
400 while ((aptv = [enumerator nextObject])) {
401 if (![events containsObject:[aptv appointment]] || ![[[aptv appointment] store] displayed]) {
402 if (aptv == _selected)
403 _selected = nil;
404 [aptv removeFromSuperviewWithoutNeedingDisplay];
405 [aptv release];
406 }
407 }
408 enumerator = [events objectEnumerator];
409 while ((apt = [enumerator nextObject])) {
410 found = NO;
411 enm = [[self subviews] objectEnumerator];
412 while ((aptv = [enm nextObject])) {
413 if ([apt isEqual:[aptv appointment]]) {
414 found = YES;
415 break;
416 }
417 }
418 if (found == NO) {
419 /* FIXME : probably shouldn't be there */
420 [config registerClient:self forKey:[[apt store] description]];
421 if ([[apt store] displayed])
422 [self addSubview:[[AppointmentView alloc] initWithFrame:NSZeroRect appointment:apt]];
423 }
424 }
425 [self setNeedsDisplay:YES];
426 }
427
428 - (void)mouseDown:(NSEvent *)theEvent
429 {
430 int start;
431 int end;
432 BOOL keepOn = YES;
433 NSPoint mouseLoc = [self convertPoint:[theEvent locationInWindow] fromView:nil];
434
435 [[self window] makeFirstResponder:self];
436 _startPt = _endPt = mouseLoc;
437 [[NSCursor crosshairCursor] push];
438 while (keepOn) {
439 theEvent = [[self window] nextEventMatchingMask: NSLeftMouseUpMask | NSLeftMouseDraggedMask];
440 _endPt = [self convertPoint:[theEvent locationInWindow] fromView:nil];
441 switch ([theEvent type]) {
442 case NSLeftMouseUp:
443 keepOn = NO;
444 break;
445 default:
446 break;
447 }
448 [self setNeedsDisplay:YES];
449 }
450 [NSCursor pop];
451 if (ABS(_startPt.y - _endPt.y) > 7 && [self mouse:_endPt inRect:[self bounds]]) {
452 start = [self positionToMinute:MAX(_startPt.y, _endPt.y)];
453 end = [self positionToMinute:MIN(_startPt.y, _endPt.y)];
454 if ([delegate respondsToSelector:@selector(dayView:createEventFrom:to:)])
455 [delegate dayView:self createEventFrom:[self roundMinutes:start] to:[self roundMinutes:end]];
456 }
457 _startPt = _endPt = NSMakePoint(0, 0);
458 [self setNeedsDisplay:YES];
459 }
460
461 - (void)keyDown:(NSEvent *)theEvent
462 {
463 NSString *characters = [theEvent characters];
464 unichar character = 0;
465
466 if ([characters length] > 0)
467 character = [characters characterAtIndex: 0];
468
469 switch (character) {
470 case '\r':
471 case NSEnterCharacter:
472 NSLog(@"enter");
473 if (_selected != nil) {
474 if ([delegate respondsToSelector:@selector(dayView:editEvent:)])
475 [delegate dayView:self editEvent:[_selected appointment]];
476 return;
477 }
478 case NSUpArrowFunctionKey:
479 [self moveUp:self];
480 return;
481 case NSDownArrowFunctionKey:
482 [self moveDown:self];
483 return;
484 case NSTabCharacter:
485 if (_selected != nil) {
486 unsigned int index = [[self subviews] indexOfObject:_selected];
487 if (index != NSNotFound) {
488 if ([theEvent modifierFlags] & NSShiftKeyMask) {
489 index--;
490 if (index < 0)
491 index = [[self subviews] count] - 1;
492 } else {
493 index++;
494 if (index >= [[self subviews] count])
495 index = 0;
496 }
497 [self selectAppointmentView:[[self subviews] objectAtIndex:index]];
498 }
499 return;
500 }
501 }
502 [super keyDown:theEvent];
503 }
504
505 - (void)moveUp:(id)sender
506 {
507 if (_selected != nil) {
508 [[[_selected appointment] startDate] changeMinuteBy:-_minStep];
509 [_selected setFrame:[self frameForAppointment:[_selected appointment]]];
510 if ([delegate respondsToSelector:@selector(dayView:modifyEvent:)])
511 [delegate dayView:self modifyEvent:[_selected appointment]];
512 }
513 }
514
515 - (void)moveDown:(id)sender
516 {
517 if (_selected != nil) {
518 [[[_selected appointment] startDate] changeMinuteBy:_minStep];
519 [_selected setFrame:[self frameForAppointment:[_selected appointment]]];
520 if ([delegate respondsToSelector:@selector(dayView:modifyEvent:)])
521 [delegate dayView:self modifyEvent:[_selected appointment]];
522 }
523 }
524
525 - (BOOL)acceptsFirstResponder
526 {
527 return YES;
528 }
529
530 - (void)config:(ConfigManager*)config dataDidChangedForKey:(NSString *)key
531 {
532 _firstH = [config integerForKey:FIRST_HOUR];
533 _lastH = [config integerForKey:LAST_HOUR];
534 _minStep = [config integerForKey:MIN_STEP];
535 [self reloadData];
536 }
537
538 - (int)firstHour
539 {
540 return _firstH;
541 }
542 - (int)lastHour
543 {
544 return _lastH;
545 }
546 - (int)minimumStep
547 {
548 return _minStep;
549 }
550
551 - (void)deselectAll:(id)sender
552 {
553 if (_selected) {
554 [_selected setSelected:NO];
555 [self setNeedsDisplay:YES];
556 _selected = nil;
557 }
558 }
559 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <ical.h>
3 #import "AgendaStore.h"
4 #import "Date.h"
5
6 enum classificationType
7 {
8 CT_NONE = 0,
9 CT_PUBLIC,
10 CT_PRIVATE,
11 CT_CONFIDENTIAL
12 };
13
14 @interface Element : NSObject <NSCoding>
15 {
16 id <MemoryStore> _store;
17 NSString *_uid;
18 NSString *_summary;
19 NSAttributedString *_text;
20 enum classificationType _classification;
21 Date *_stamp;
22 }
23
24 - (id)initWithSummary:(NSString *)summary;
25 - (void)generateUID;
26 - (id <MemoryStore>)store;
27 - (NSAttributedString *)text;
28 - (NSString *)summary;
29 - (NSString *)UID;
30 - (enum classificationType)classification;
31 - (Date *)dateStamp;
32
33 - (void)setStore:(id <MemoryStore>)store;
34 - (void)setText:(NSAttributedString *)text;
35 - (void)setSummary:(NSString *)summary;
36 - (void)setUID:(NSString *)uid;
37 - (void)setClassification:(enum classificationType)classification;
38 - (void)setDateStamp:(Date *)stamp;
39
40 - (id)initWithICalComponent:(icalcomponent *)ic;
41 - (icalcomponent *)asICalComponent;
42 - (void)deleteProperty:(icalproperty_kind)kind fromComponent:(icalcomponent *)ic;
43 - (BOOL)updateICalComponent:(icalcomponent *)ic;
44 - (int)iCalComponentType;
45 @end
0 #import <Foundation/Foundation.h>
1 #import "Date.h"
2 #import "Element.h"
3
4 @implementation Element
5 -(void)encodeWithCoder:(NSCoder *)coder
6 {
7 [coder encodeObject:_summary forKey:@"title"];
8 [coder encodeObject:[_text string] forKey:@"descriptionText"];
9 [coder encodeObject:_uid forKey:@"uid"];
10 [coder encodeInt:_classification forKey:@"classification"];
11 if (_stamp)
12 [coder encodeObject:_stamp forKey:@"dtstamp"];
13 }
14 -(id)initWithCoder:(NSCoder *)coder
15 {
16 _summary = [[coder decodeObjectForKey:@"title"] retain];
17 _text = [[NSAttributedString alloc] initWithString:[coder decodeObjectForKey:@"descriptionText"]];
18 if ([coder containsValueForKey:@"uid"])
19 _uid = [[coder decodeObjectForKey:@"uid"] retain];
20 else
21 [self generateUID];
22 if ([coder containsValueForKey:@"classification"])
23 _classification = [coder decodeIntForKey:@"classification"];
24 else
25 _classification = CT_PUBLIC;
26 if ([coder containsValueForKey:@"dtstamp"])
27 _stamp = [[coder decodeObjectForKey:@"dtstamp"] retain];
28 else
29 _stamp = nil;
30 return self;
31 }
32
33 - (id)init
34 {
35 self = [super init];
36 if (self) {
37 [self generateUID];
38 [self setDateStamp:[Date now]];
39 }
40 return self;
41 }
42
43 - (id)initWithSummary:(NSString *)summary
44 {
45 self = [self init];
46 if (self)
47 [self setSummary:summary];
48 return self;
49 }
50
51 - (void)dealloc
52 {
53 [super dealloc];
54 RELEASE(_summary);
55 RELEASE(_text);
56 RELEASE(_store);
57 RELEASE(_uid);
58 RELEASE(_stamp);
59 }
60
61 - (id <MemoryStore>)store
62 {
63 return _store;
64 }
65 - (void)setStore:(id <MemoryStore>)store
66 {
67 ASSIGN(_store, store);
68 }
69
70 - (NSAttributedString *)text
71 {
72 return _text;
73 }
74 - (void)setText:(NSAttributedString *)text
75 {
76 ASSIGN(_text, text);
77 }
78
79 - (NSString *)summary
80 {
81 return _summary;
82 }
83 - (void)setSummary:(NSString *)summary
84 {
85 ASSIGN(_summary, summary);
86 }
87
88 - (void)generateUID
89 {
90 Date *now = [Date now];
91 static Date *lastDate;
92 static int counter;
93
94 if (!lastDate)
95 ASSIGNCOPY(lastDate, now);
96 else {
97 if (![lastDate compareTime:now])
98 counter++;
99 else {
100 ASSIGNCOPY(lastDate, now);
101 counter = 0;
102 }
103 }
104 [self setUID:[NSString stringWithFormat:@"SimpleAgenda-%@-%d-%@", [now description], counter, [[NSHost currentHost] address]]];
105 }
106 - (NSString *)UID
107 {
108 return _uid;
109 }
110 - (void)setUID:(NSString *)aUid;
111 {
112 ASSIGNCOPY(_uid, aUid);
113 }
114
115 - (enum classificationType)classification
116 {
117 return _classification;
118 }
119 - (void)setClassification:(enum classificationType)classification
120 {
121 _classification = classification;
122 }
123
124 - (Date *)dateStamp
125 {
126 return _stamp;
127 }
128 - (void)setDateStamp:(Date *)stamp;
129 {
130 ASSIGNCOPY(_stamp, stamp);
131 }
132
133 - (id)initWithICalComponent:(icalcomponent *)ic
134 {
135 icalproperty *prop;
136 Date *date;
137
138 self = [self init];
139 if (self == nil)
140 return nil;
141
142 prop = icalcomponent_get_first_property(ic, ICAL_UID_PROPERTY);
143 if (!prop) {
144 NSLog(@"No UID");
145 goto init_error;
146 }
147 [self setUID:[NSString stringWithCString:icalproperty_get_uid(prop)]];
148
149 prop = icalcomponent_get_first_property(ic, ICAL_SUMMARY_PROPERTY);
150 if (!prop) {
151 NSLog(@"No summary");
152 goto init_error;
153 }
154 [self setSummary:[NSString stringWithCString:icalproperty_get_summary(prop) encoding:NSUTF8StringEncoding]];
155 prop = icalcomponent_get_first_property(ic, ICAL_DESCRIPTION_PROPERTY);
156 if (prop) {
157 NSAttributedString *as = [[NSAttributedString alloc] initWithString:[NSString stringWithCString:icalproperty_get_description(prop) encoding:NSUTF8StringEncoding]];
158 [self setText:as];
159 [as release];
160 }
161 prop = icalcomponent_get_first_property(ic, ICAL_DTSTAMP_PROPERTY);
162 if (prop) {
163 date = [[Date alloc] initWithICalTime:icalproperty_get_dtstamp(prop)];
164 [self setDateStamp:date];
165 [date release];
166 }
167 return self;
168 init_error:
169 NSLog(@"Error creating Element from iCal component");
170 [self release];
171 return nil;
172 }
173
174 - (icalcomponent *)asICalComponent
175 {
176 icalcomponent *ic = icalcomponent_new([self iCalComponentType]);
177 if (!ic) {
178 NSLog(@"Couldn't create iCalendar component");
179 return NULL;
180 }
181 if (![self updateICalComponent:ic]) {
182 icalcomponent_free(ic);
183 return NULL;
184 }
185 return ic;
186 }
187 - (void)deleteProperty:(icalproperty_kind)kind fromComponent:(icalcomponent *)ic
188 {
189 icalproperty *prop;
190 prop = icalcomponent_get_first_property(ic, kind);
191 if (prop)
192 icalcomponent_remove_property(ic, prop);
193 /*
194 * FIXME : not sure if the following is wise
195 * while ((prop = icalcomponent_get_next_property(ic, kind)))
196 * icalcomponent_remove_property(ic, prop);
197 */
198 }
199 - (BOOL)updateICalComponent:(icalcomponent *)ic
200 {
201 [self deleteProperty:ICAL_UID_PROPERTY fromComponent:ic];
202 icalcomponent_add_property(ic, icalproperty_new_uid([[self UID] cString]));
203 [self deleteProperty:ICAL_SUMMARY_PROPERTY fromComponent:ic];
204 if ([self summary])
205 icalcomponent_add_property(ic, icalproperty_new_summary([[self summary] UTF8String]));
206 [self deleteProperty:ICAL_DESCRIPTION_PROPERTY fromComponent:ic];
207 if ([self text])
208 icalcomponent_add_property(ic, icalproperty_new_description([[[self text] string] UTF8String]));
209 [self deleteProperty:ICAL_DTSTAMP_PROPERTY fromComponent:ic];
210 icalcomponent_add_property(ic, icalproperty_new_dtstamp([_stamp iCalTime]));
211 return YES;
212 }
213 - (int)iCalComponentType
214 {
215 NSLog(@"Shouldn't be used");
216 return -1;
217 }
218 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "Date.h"
3 #import "Element.h"
4 #import "AgendaStore.h"
5
6 enum intervalType
7 {
8 RI_NONE = 0,
9 RI_DAILY,
10 RI_WEEKLY,
11 RI_MONTHLY,
12 RI_YEARLY
13 };
14
15 @interface Event : Element
16 {
17 NSString *_location;
18 BOOL _allDay;
19 Date *startDate;
20 Date *endDate;
21 int duration;
22 enum intervalType interval;
23 int frequency;
24 int scheduleLevel;
25 }
26
27 - (id)initWithStartDate:(Date *)start duration:(int)minutes title:(NSString *)aTitle;
28 - (BOOL)isScheduledForDay:(Date *)day;
29 - (BOOL)isScheduledBetweenDay:(Date *)start andDay:(Date *)end;
30 - (NSString *)details;
31 - (BOOL)contains:(NSString *)text;
32
33 - (NSString *)location;
34 - (BOOL)allDay;
35 - (int)duration;
36 - (int)frequency;
37 - (Date *)startDate;
38 - (Date *)endDate;
39 - (int)interval;
40
41 - (void)setLocation:(NSString *)aLocation;
42 - (void)setAllDay:(BOOL)allDay;
43 - (void)setDuration:(int)duration;
44 - (void)setFrequency:(int)frequency;
45 - (void)setStartDate:(Date *)startDate;
46 - (void)setEndDate:(Date *)endDate;
47 - (void)setInterval:(int)interval;
48 @end
49
50 @interface Event(iCalendar)
51 - (id)initWithICalComponent:(icalcomponent *)ic;
52 - (BOOL)updateICalComponent:(icalcomponent *)ic;
53 @end
54
0 /*
1 * Based on ChronographerSource Appointment class
2 */
3
4 #import <Foundation/Foundation.h>
5 #import "Event.h"
6
7 @implementation Event(NSCoding)
8 -(void)encodeWithCoder:(NSCoder *)coder
9 {
10 [super encodeWithCoder:coder];
11 [coder encodeObject:startDate forKey:@"sdate"];
12 [coder encodeObject:endDate forKey:@"edate"];
13 [coder encodeInt:interval forKey:@"interval"];
14 [coder encodeInt:frequency forKey:@"frequency"];
15 [coder encodeInt:duration forKey:@"duration"];
16 [coder encodeObject:_location forKey:@"location"];
17 [coder encodeBool:_allDay forKey:@"allDay"];
18 }
19 -(id)initWithCoder:(NSCoder *)coder
20 {
21 [super initWithCoder:coder];
22 startDate = [[coder decodeObjectForKey:@"sdate"] retain];
23 endDate = [[coder decodeObjectForKey:@"edate"] retain];
24 interval = [coder decodeIntForKey:@"interval"];
25 frequency = [coder decodeIntForKey:@"frequency"];
26 duration = [coder decodeIntForKey:@"duration"];
27 _location = [[coder decodeObjectForKey:@"location"] retain];
28 if ([coder containsValueForKey:@"allDay"])
29 _allDay = [coder decodeBoolForKey:@"allDay"];
30 else
31 _allDay = NO;
32 return self;
33 }
34 @end
35
36 @implementation Event
37 - (id)copy
38 {
39 NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self];
40 Event *new = [NSKeyedUnarchiver unarchiveObjectWithData:data];
41 [new generateUID];
42 return new;
43 }
44
45 - (id)initWithStartDate:(Date *)start duration:(int)minutes title:(NSString *)aTitle
46 {
47 self = [self initWithSummary:aTitle];
48 if (self) {
49 [self setStartDate:start];
50 [self setDuration:minutes];
51 }
52 return self;
53 }
54
55 - (void)dealloc
56 {
57 [super dealloc];
58 RELEASE(_location);
59 RELEASE(startDate);
60 RELEASE(endDate);
61 }
62
63 /*
64 * Code adapted from ChronographerSource Appointment:isScheduledFor
65 */
66 - (BOOL)isScheduledForDay:(Date *)day
67 {
68 NSAssert(day != nil, @"Empty day argument");
69 if ([day daysUntil:startDate] > 0 || (endDate && [day daysSince:endDate] > 0))
70 return NO;
71 switch (interval) {
72 case RI_NONE:
73 return [day compare:startDate] == 0;
74 case RI_DAILY:
75 return ((frequency == 1) ||
76 ([startDate daysUntil: day] % frequency) == 0);
77 case RI_WEEKLY:
78 return (([startDate weekday] == [day weekday]) &&
79 ((frequency == 1) ||
80 (([startDate weeksUntil: day] % frequency) == 0)));
81 case RI_MONTHLY:
82 return (([startDate dayOfMonth] == [day dayOfMonth]) &&
83 ((frequency == 1) ||
84 (([startDate monthsUntil: day] % frequency) == 0)));
85 case RI_YEARLY:
86 return ((([startDate dayOfMonth] == [day dayOfMonth]) &&
87 ([startDate monthOfYear] == [day monthOfYear])) &&
88 ((frequency == 1) ||
89 (([startDate yearsUntil: day] % frequency) == 0)));
90 }
91 return NO;
92 }
93
94 - (BOOL)isScheduledBetweenDay:(Date *)start andDay:(Date *)end
95 {
96 int nd;
97 Date *work = [start copy];
98
99 for (nd = 0; nd < [start daysUntil:end] + 1; nd++) {
100 if ([self isScheduledForDay:work]) {
101 [work release];
102 return YES;
103 }
104 [work incrementDay];
105 }
106 [work release];
107 return NO;
108 }
109
110 - (NSString *)location
111 {
112 return _location;
113 }
114
115 - (void)setLocation:(NSString *)location
116 {
117 ASSIGN(_location, location);
118 }
119
120 - (BOOL)allDay
121 {
122 return _allDay;
123 }
124 - (void)setAllDay:(BOOL)allDay
125 {
126 [startDate setDate:allDay];
127 _allDay = allDay;
128 }
129
130 - (NSString *)description
131 {
132 return [NSString stringWithFormat:@"<%@> from <%@> for <%d> to <%@> (%d)", [self summary], [startDate description], [self duration], [endDate description], interval];
133 }
134
135 - (NSString *)details
136 {
137 if ([self allDay])
138 return @"all day";
139 int minute = [[self startDate] minuteOfDay];
140 return [NSString stringWithFormat:@"%dh%02d", minute / 60, minute % 60];
141 }
142
143 - (BOOL)contains:(NSString *)text
144 {
145 if ([self summary] && [[self summary] rangeOfString:text options:NSCaseInsensitiveSearch].length > 0)
146 return YES;
147 if (_location && [_location rangeOfString:text options:NSCaseInsensitiveSearch].length > 0)
148 return YES;
149 if ([self text] && [[[self text] string] rangeOfString:text options:NSCaseInsensitiveSearch].length > 0)
150 return YES;
151 return NO;
152 }
153
154 - (int)duration
155 {
156 return duration;
157 }
158
159 - (int)frequency
160 {
161 return frequency;
162 }
163
164 - (Date *)startDate
165 {
166 return startDate;
167 }
168
169 - (Date *)endDate
170 {
171 return endDate;
172 }
173
174 - (int)interval
175 {
176 return interval;
177 }
178
179 - (void)setDuration:(int)newDuration
180 {
181 duration = newDuration;
182 }
183
184 - (void)setFrequency:(int)newFrequency
185 {
186 frequency = newFrequency;
187 }
188
189 - (void)setStartDate:(Date *)newStartDate
190 {
191 ASSIGNCOPY(startDate, newStartDate);
192 }
193
194 - (void)setEndDate:(Date *)date
195 {
196 ASSIGNCOPY(endDate, date);
197 }
198
199 - (void)setInterval:(int)newInterval
200 {
201 interval = newInterval;
202 }
203 @end
204
205 @implementation Event(iCalendar)
206 - (id)initWithICalComponent:(icalcomponent *)ic
207 {
208 icalproperty *prop;
209 icalproperty *pstart;
210 icalproperty *pend;
211 icalproperty *pdur;
212 struct icaltimetype start;
213 struct icaltimetype end;
214 struct icaldurationtype diff;
215 struct icalrecurrencetype rec;
216 Date *date;
217 const char *location;
218
219 self = [super initWithICalComponent:ic];
220 if (self == nil)
221 return nil;
222
223 pstart = icalcomponent_get_first_property(ic, ICAL_DTSTART_PROPERTY);
224 if (!pstart) {
225 NSLog(@"No start date");
226 goto init_error;
227 }
228 start = icalproperty_get_dtstart(pstart);
229 date = [[Date alloc] initWithICalTime:start];
230 [self setStartDate:date];
231 pend = icalcomponent_get_first_property(ic, ICAL_DTEND_PROPERTY);
232 pdur = icalcomponent_get_first_property(ic, ICAL_DURATION_PROPERTY);
233 if ((!pend && !pdur) || [date isDate])
234 [self setAllDay:YES];
235 else {
236 if (!pend)
237 diff = icalproperty_get_duration(pdur);
238 else {
239 end = icalproperty_get_dtend(pend);
240 diff = icaltime_subtract(end, start);
241 }
242 [self setDuration:icaldurationtype_as_int(diff) / 60];
243 }
244
245 prop = icalcomponent_get_first_property(ic, ICAL_RRULE_PROPERTY);
246 if (prop) {
247 rec = icalproperty_get_rrule(prop);
248 switch (rec.freq) {
249 case ICAL_DAILY_RECURRENCE:
250 [self setInterval:RI_DAILY];
251 [self setFrequency:rec.interval];
252 break;
253 case ICAL_WEEKLY_RECURRENCE:
254 [self setInterval:RI_WEEKLY];
255 [self setFrequency:rec.interval];
256 break;
257 case ICAL_MONTHLY_RECURRENCE:
258 [self setInterval:RI_MONTHLY];
259 [self setFrequency:rec.interval];
260 break;
261 case ICAL_YEARLY_RECURRENCE:
262 [self setInterval:RI_YEARLY];
263 [self setFrequency:rec.interval];
264 break;
265 default:
266 NSLog(@"ToDo");
267 break;
268 }
269 if (!icaltime_is_null_time(rec.until)) {
270 [date setDateToICalTime:rec.until];
271 [self setEndDate:date];
272 }
273 }
274 [date release];
275 location = icalcomponent_get_location(ic);
276 if (location)
277 [self setLocation:[NSString stringWithCString:location encoding:NSUTF8StringEncoding]];
278 return self;
279
280 init_error:
281 NSLog(@"Error creating Event from iCal component");
282 [self release];
283 return nil;
284 }
285
286 - (icalcomponent *)asICalComponent
287 {
288 icalcomponent *ic = icalcomponent_new(ICAL_VEVENT_COMPONENT);
289 if (!ic) {
290 NSLog(@"Couldn't create iCalendar component");
291 return NULL;
292 }
293 if (![self updateICalComponent:ic]) {
294 icalcomponent_free(ic);
295 return NULL;
296 }
297 return ic;
298 }
299
300 - (BOOL)updateICalComponent:(icalcomponent *)ic
301 {
302 Date *end;
303 struct icalrecurrencetype irec;
304 icalproperty *prop;
305
306 if (![super updateICalComponent:ic])
307 return NO;
308
309 [self deleteProperty:ICAL_LOCATION_PROPERTY fromComponent:ic];
310 if ([self location])
311 icalcomponent_add_property(ic, icalproperty_new_location([[self location] UTF8String]));
312
313 [self deleteProperty:ICAL_DTSTART_PROPERTY fromComponent:ic];
314 icalcomponent_add_property(ic, icalproperty_new_dtstart([startDate iCalTime]));
315
316 [self deleteProperty:ICAL_DTEND_PROPERTY fromComponent:ic];
317 [self deleteProperty:ICAL_DURATION_PROPERTY fromComponent:ic];
318 if (![self allDay])
319 icalcomponent_add_property(ic, icalproperty_new_dtend(icaltime_add([startDate iCalTime], icaldurationtype_from_int(duration * 60))));
320 else {
321 end = [startDate copy];
322 [end incrementDay];
323 icalcomponent_add_property(ic, icalproperty_new_dtend([end iCalTime]));
324 [end release];
325 /* OGo workaround ? */
326 prop = icalcomponent_get_first_property(ic, ICAL_X_PROPERTY);
327 while (prop) {
328 if (!strcmp(icalproperty_get_x_name(prop), "X-MICROSOFT-CDO-ALLDAYEVENT"))
329 icalcomponent_remove_property(ic, prop);
330 prop = icalcomponent_get_next_property(ic, ICAL_X_PROPERTY);
331 }
332 prop = icalproperty_new_from_string("X-MICROSOFT-CDO-ALLDAYEVENT:TRUE");
333 icalcomponent_add_property(ic, prop);
334 }
335
336 [self deleteProperty:ICAL_RRULE_PROPERTY fromComponent:ic];
337 if (interval != RI_NONE) {
338 icalrecurrencetype_clear(&irec);
339 switch (interval) {
340 case RI_DAILY:
341 irec.freq = ICAL_DAILY_RECURRENCE;
342 break;
343 case RI_WEEKLY:
344 irec.freq = ICAL_WEEKLY_RECURRENCE;
345 break;
346 case RI_MONTHLY:
347 irec.freq = ICAL_MONTHLY_RECURRENCE;
348 break;
349 case RI_YEARLY:
350 irec.freq = ICAL_YEARLY_RECURRENCE;
351 break;
352 default:
353 NSLog(@"ToDo");
354 }
355 if (endDate != nil)
356 irec.until = [endDate iCalTime];
357 else
358 irec.until = icaltime_null_time();
359 icalcomponent_add_property(ic, icalproperty_new_rrule(irec));
360 }
361 return YES;
362 }
363
364 - (int)iCalComponentType
365 {
366 return ICAL_VEVENT_COMPONENT;
367 }
368 @end
369
0 #
1 # GNUmakefile - Generated by ProjectCenter
2 #
3
4 include $(GNUSTEP_MAKEFILES)/common.make
5
6 #
7 # Application
8 #
9 VERSION = 0.36
10 PACKAGE_NAME = SimpleAgenda
11 APP_NAME = SimpleAgenda
12 SimpleAgenda_APPLICATION_ICON = Calendar.tiff
13
14
15 #
16 # Libraries
17 #
18 SimpleAgenda_LIBRARIES_DEPEND_UPON += -lical
19
20 #
21 # Resource files
22 #
23 SimpleAgenda_RESOURCE_FILES = \
24 Resources/Agenda.gorm \
25 Resources/Appointment.gorm \
26 Resources/Preferences.gorm \
27 Resources/iCalendar.gorm \
28 Resources/Task.gorm \
29 Resources/GroupDAV.gorm \
30 Resources/Calendar.tiff \
31 Resources/ical-file.tiff \
32 Resources/repeat.tiff
33
34
35 #
36 # Header files
37 #
38 SimpleAgenda_HEADER_FILES = \
39 AppController.h \
40 AgendaStore.h \
41 LocalStore.h \
42 AppointmentEditor.h \
43 CalendarView.h \
44 StoreManager.h \
45 DayView.h \
46 Event.h \
47 PreferencesController.h \
48 HourFormatter.h \
49 UserDefaults.h \
50 iCalStore.h \
51 ConfigManager.h \
52 Date.h \
53 iCalTree.h \
54 DataTree.h \
55 Element.h \
56 Task.h \
57 TaskEditor.h \
58 MemoryStore.h \
59 GroupDAVStore.h \
60 WebDAVResource.h
61
62 #
63 # Class files
64 #
65 SimpleAgenda_OBJC_FILES = \
66 AppController.m \
67 LocalStore.m \
68 AppointmentEditor.m \
69 CalendarView.m \
70 StoreManager.m \
71 DayView.m \
72 Event.m \
73 PreferencesController.m \
74 HourFormatter.m \
75 iCalStore.m \
76 ConfigManager.m \
77 Date.m \
78 iCalTree.m \
79 DataTree.m \
80 Element.m \
81 Task.m \
82 TaskEditor.m \
83 MemoryStore.m \
84 GroupDAVStore.m \
85 WebDAVResource.m
86
87 #
88 # Other sources
89 #
90 SimpleAgenda_OBJC_FILES += \
91 SimpleAgenda.m
92
93 #
94 # Makefiles
95 #
96 -include GNUmakefile.preamble
97 include $(GNUSTEP_MAKEFILES)/aggregate.make
98 include $(GNUSTEP_MAKEFILES)/application.make
99 -include GNUmakefile.postamble
0 #
1 # GNUmakefile.postamble - Generated by ProjectCenter
2 #
3
4 # Things to do before compiling
5 # before-all::
6
7 # Things to do after compiling
8 # after-all::
9
10 # Things to do before installing
11 # before-install::
12
13 # Things to do after installing
14 # after-install::
15
16 # Things to do before uninstalling
17 # before-uninstall::
18
19 # Things to do after uninstalling
20 # after-uninstall::
21
22 # Things to do before cleaning
23 # before-clean::
24
25 # Things to do after cleaning
26 # after-clean::
27
28 # Things to do before distcleaning
29 # before-distclean::
30
31 # Things to do after distcleaning
32 # after-distclean::
33
34 # Things to do before checking
35 # before-check::
36
37 # Things to do after checking
38 # after-check::
39
0 #
1 # GNUmakefile.preamble - Generated by ProjectCenter
2 #
3
4 # Additional flags to pass to the preprocessor
5 ADDITIONAL_CPPFLAGS +=
6
7 # Additional flags to pass to Objective C compiler
8 ADDITIONAL_OBJCFLAGS +=
9
10 # Additional flags to pass to C compiler
11 ADDITIONAL_CFLAGS +=
12
13 # Additional flags to pass to the linker
14 ADDITIONAL_LDFLAGS +=
15
16 # Additional include directories the compiler should search
17 ADDITIONAL_INCLUDE_DIRS +=
18
19 # Additional library directories the linker should search
20 ADDITIONAL_LIB_DIRS += -L/usr/local/lib
21
22 # Additional GUI libraries to link
23 ADDITIONAL_GUI_LIBS += -lical
24
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "MemoryStore.h"
3 #import "iCalTree.h"
4 #import "WebDAVResource.h"
5
6 @interface GroupDAVStore : MemoryStore <AgendaStore>
7 {
8 NSURL *_url;
9 WebDAVResource *_calendar;
10 WebDAVResource *_task;
11 NSMutableDictionary *_uidhref;
12 NSMutableDictionary *_hreftree;
13 NSMutableDictionary *_hrefresource;
14 NSMutableArray *_modifiedhref;
15 }
16 @end
0 #import <Foundation/Foundation.h>
1 #import <GNUstepBase/GSXML.h>
2 #import "GNUstepBase/GSMime.h"
3 #import "Event.h"
4 #import "Task.h"
5 #import "GroupDAVStore.h"
6 #import "WebDAVResource.h"
7 #import "iCalTree.h"
8 #import "defines.h"
9
10 @interface GroupDAVDialog : NSObject
11 {
12 IBOutlet id panel;
13 IBOutlet id name;
14 IBOutlet id url;
15 IBOutlet id cancel;
16 IBOutlet id ok;
17 IBOutlet id check;
18 IBOutlet id calendar;
19 IBOutlet id task;
20 BOOL isURL;
21 }
22 - (BOOL)show;
23 - (NSString *)url;
24 - (NSString *)calendar;
25 - (NSString *)task;
26 - (void)selectItem:(id)sender;
27 @end
28 @implementation GroupDAVDialog
29 - (id)initWithName:(NSString *)storeName
30 {
31 self = [super init];
32 if (self) {
33 if (![NSBundle loadNibNamed:@"GroupDAV" owner:self])
34 return nil;
35 [name setStringValue:storeName];
36 [url setStringValue:@"http://"];
37 [calendar removeAllItems];
38 [task removeAllItems];
39 isURL = NO;
40 }
41 return self;
42 }
43 - (void)dealloc
44 {
45 [panel close];
46 [super dealloc];
47 }
48 - (BOOL)show
49 {
50 [ok setEnabled:NO];
51 return [NSApp runModalForWindow:panel];
52 }
53 - (void)buttonClicked:(id)sender
54 {
55 if (sender == cancel)
56 [NSApp stopModalWithCode:0];
57 else if (sender == ok)
58 [NSApp stopModalWithCode:1];
59 else if (sender == check)
60 [self controlTextDidEndEditing:nil];
61 }
62
63 - (void)controlTextDidChange:(NSNotification *)notification
64 {
65 NS_DURING
66 {
67 isURL = [NSURL stringIsValidURL:[url stringValue]];
68 }
69 NS_HANDLER
70 {
71 isURL = NO;
72 }
73 NS_ENDHANDLER
74 }
75 - (void)updateOK
76 {
77 if ([calendar indexOfSelectedItem] != -1 || [task indexOfSelectedItem] != -1)
78 [ok setEnabled:YES];
79 else
80 [ok setEnabled:NO];
81 }
82 - (void)selectItem:(id)sender
83 {
84 [self updateOK];
85 }
86 - (void)controlTextDidEndEditing:(NSNotification *)aNotification
87 {
88 int i;
89 WebDAVResource *resource;
90 GSXMLParser *parser;
91 NSData *propfind;
92 NSString *body = @"<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><prop><getlastmodified/><executable/><resourcetype/></prop></propfind>";
93 GSXPathContext *xpc;
94 GSXPathNodeSet *set;
95
96 [calendar removeAllItems];
97 [task removeAllItems];
98 if (isURL) {
99 resource = [[WebDAVResource alloc] initWithURL:[NSURL URLWithString:[url stringValue]]];
100 propfind = [resource propfind:[body dataUsingEncoding:NSUTF8StringEncoding] attributes:[NSDictionary dictionaryWithObject:@"Infinity" forKey:@"Depth"]];
101 parser = [GSXMLParser parserWithData:propfind];
102 if ([parser parse]) {
103 xpc = [[GSXPathContext alloc] initWithDocument:[[parser document] strippedDocument]];
104 set = (GSXPathNodeSet *)[xpc evaluateExpression:@"//response[propstat/prop/resourcetype/vevent-collection]/href/text()"];
105 for (i = 0; i < [set count]; i++)
106 [calendar addItemWithTitle:[[set nodeAtIndex:i] content]];
107 set = (GSXPathNodeSet *)[xpc evaluateExpression:@"//response[propstat/prop/resourcetype/vtodo-collection]/href/text()"];
108 for (i = 0; i < [set count]; i++)
109 [task addItemWithTitle:[[set nodeAtIndex:i] content]];
110 [xpc release];
111 }
112 [propfind release];
113 [resource release];
114 }
115 [self updateOK];
116 }
117 - (NSString *)url
118 {
119 return [url stringValue];
120 }
121 - (NSString *)calendar
122 {
123 return [calendar titleOfSelectedItem];
124 }
125 - (NSString *)task
126 {
127 return [task titleOfSelectedItem];;
128 }
129 @end
130
131 @interface GroupDAVStore(Private)
132 - (void)initTimer:(id)object;
133 - (void)initStoreAsync:(id)object;
134 - (void)fetchData:(id)object;
135 @end
136
137 @implementation GroupDAVStore
138 - (NSDictionary *)defaults
139 {
140 return [NSDictionary dictionaryWithObjectsAndKeys:
141 [NSArchiver archivedDataWithRootObject:[NSColor blueColor]], ST_COLOR,
142 [NSArchiver archivedDataWithRootObject:[NSColor darkGrayColor]], ST_TEXT_COLOR,
143 [NSNumber numberWithBool:NO], ST_RW,
144 [NSNumber numberWithBool:YES], ST_DISPLAY,
145 nil, nil];
146 }
147
148 - (id)initWithName:(NSString *)name
149 {
150 self = [super initWithName:name];
151 if (self) {
152 _uidhref = [[NSMutableDictionary alloc] initWithCapacity:512];
153 _hreftree = [[NSMutableDictionary alloc] initWithCapacity:512];
154 _hrefresource = [[NSMutableDictionary alloc] initWithCapacity:512];
155 _modifiedhref = [NSMutableArray new];
156 [NSThread detachNewThreadSelector:@selector(initStoreAsync:) toTarget:self withObject:nil];
157 }
158 return self;
159 }
160
161 + (BOOL)registerWithName:(NSString *)name
162 {
163 ConfigManager *cm;
164 GroupDAVDialog *dialog;
165 NSURL *calendarURL;
166 NSURL *taskURL;
167 NSURL *baseURL;
168
169 dialog = [[GroupDAVDialog alloc] initWithName:name];
170 if ([dialog show] == YES) {
171 baseURL = [NSURL URLWithString:[dialog url]];
172 calendarURL = [NSURL URLWithString:[dialog calendar] possiblyRelativeToURL:baseURL];
173 taskURL = [NSURL URLWithString:[dialog task] possiblyRelativeToURL:baseURL];
174 [dialog release];
175 cm = [[ConfigManager alloc] initForKey:name withParent:nil];
176 [cm setObject:[dialog url] forKey:ST_URL];
177 if (calendarURL)
178 [cm setObject:[calendarURL description] forKey:ST_CALENDAR_URL];
179 if (taskURL)
180 [cm setObject:[taskURL description] forKey:ST_TASK_URL];
181 [cm setObject:[[self class] description] forKey:ST_CLASS];
182 [cm setObject:[NSNumber numberWithBool:YES] forKey:ST_RW];
183 return YES;
184 }
185 [dialog release];
186 return NO;
187 }
188
189 + (NSString *)storeTypeName
190 {
191 return @"GroupDAV store";
192 }
193
194 - (void)dealloc
195 {
196 [self write];
197 DESTROY(_url);
198 DESTROY(_calendar);
199 DESTROY(_task);
200 DESTROY(_uidhref);
201 DESTROY(_hreftree);
202 DESTROY(_hrefresource);
203 DESTROY(_modifiedhref);
204 [super dealloc];
205 }
206
207 - (void)refreshData:(NSTimer *)timer
208 {
209 [self read];
210 }
211
212 - (void)add:(Element *)elt
213 {
214 NSURL *url;
215 WebDAVResource *resource;
216 iCalTree *tree;
217
218 if ([elt isKindOfClass:[Event class]])
219 url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [[_calendar url] absoluteString], [elt UID]]];
220 else
221 url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/%@", [[_task url] absoluteString], [elt UID]]];
222 resource = [[WebDAVResource alloc] initWithURL:url];
223 if ([_url user])
224 [resource setUser:[_url user] password:[_url password]];
225 tree = [iCalTree new];
226 if ([tree add:elt]) {
227 [resource put:[tree iCalTreeAsData] attributes:[NSDictionary dictionaryWithObjectsAndKeys:@"text/calendar; charset=utf-8", @"Content-Type", @"*", @"If-None-Match", nil, nil]];
228 if ([resource httpStatus] > 199 && [resource httpStatus] < 300)
229 /* FIXME : this is extremely slow. We should only load attributes and fetch new or modified elements */
230 /* Reloading all data is a way to handle href and uid modification done by the server */
231 [self fetchData:nil];
232 else
233 NSLog(@"Error %d writing event to %@", [resource httpStatus], [url absoluteString]);
234 }
235 [tree release];
236 [resource release];
237 }
238
239 - (void)remove:(Element *)elt
240 {
241 NSString *href = [_uidhref objectForKey:[elt UID]];
242 WebDAVResource *resource = [_hrefresource objectForKey:href];
243
244 [resource delete];
245 if ([resource httpStatus] == 412) {
246 /* FIXME : force a visual refresh ? */
247 [resource updateAttributes];
248 NSLog(@"Couldn't delete item, it has changed on the server");
249 } else {
250 [_hrefresource removeObjectForKey:href];
251 [_uidhref removeObjectForKey:[elt UID]];
252 [super remove:elt];
253 }
254 }
255
256 /* FIXME : update the iCal tree iif data was written succesfully */
257 - (void)update:(Element *)elt
258 {
259 NSString *href = [_uidhref objectForKey:[elt UID]];
260 iCalTree *tree = [_hreftree objectForKey:href];
261
262 if ([tree update:(Event *)elt]) {
263 [super update:elt];
264 [_modifiedhref addObject:href];
265 [self write];
266 }
267 }
268
269 - (BOOL)read
270 {
271 /* FIXME : this should call something else, same thing for iCalStore ? */
272 /* This version won't work for deleted elements etc */
273 [self fetchData:nil];
274 return YES;
275 }
276
277 - (BOOL)write
278 {
279 NSEnumerator *enumerator;
280 WebDAVResource *element;
281 iCalTree *tree;
282 NSString *href;
283 NSArray *copy;
284
285 copy = [_modifiedhref copy];
286 enumerator = [copy objectEnumerator];
287 while ((href = [enumerator nextObject])) {
288 element = [_hrefresource objectForKey:href];
289 tree = [_hreftree objectForKey:href];
290 [element put:[tree iCalTreeAsData] attributes:[NSDictionary dictionaryWithObject:@"text/calendar; charset=utf-8" forKey:@"Content-Type"]];
291 /* Read it back to update the attributes */
292 /* FIXME : RFC says we should update the list instead */
293 [element updateAttributes];
294 [_modifiedhref removeObject:href];
295 NSLog(@"Written %@", href);
296 }
297 [copy release];
298 return YES;
299 }
300 @end
301
302
303 @implementation GroupDAVStore(Private)
304 - (void)initTimer:(id)object
305 {
306 }
307 - (void)initStoreAsync:(id)object
308 {
309 NSAutoreleasePool *pool = [NSAutoreleasePool new];
310 _url = [[NSURL alloc] initWithString:[_config objectForKey:ST_URL]];
311 if ([_config objectForKey:ST_CALENDAR_URL]) {
312 _calendar = [[WebDAVResource alloc] initWithURL:[[NSURL alloc] initWithString:[_config objectForKey:ST_CALENDAR_URL]]];
313 if ([_url user])
314 [_calendar setUser:[_url user] password:[_url password]];
315 } else
316 _calendar = nil;
317 if ([_config objectForKey:ST_TASK_URL]) {
318 _task = [[WebDAVResource alloc] initWithURL:[[NSURL alloc] initWithString:[_config objectForKey:ST_TASK_URL]]];
319 if ([_url user])
320 [_task setUser:[_url user] password:[_url password]];
321 } else
322 _task = nil;
323 [self performSelectorOnMainThread:@selector(fetchData:) withObject:nil waitUntilDone:YES];
324 [self performSelectorOnMainThread:@selector(initTimer:) withObject:nil waitUntilDone:YES];
325 [pool release];
326 }
327
328 - (void)fetchList:(NSArray *)items
329 {
330 WebDAVResource *element;
331 NSData *ical;
332 iCalTree *tree;
333 NSEnumerator *enumerator;
334 NSString *href;
335
336 enumerator = [items objectEnumerator];
337 while ((href = [enumerator nextObject])) {
338 element = [[WebDAVResource alloc] initWithURL:[NSURL URLWithString:href]];
339 if ([_url user])
340 [element setUser:[_url user] password:[_url password]];
341 tree = [iCalTree new];
342 ical = [element get];
343 if (ical && [tree parseData:ical]) {
344 [self fillWithElements:[tree components]];
345 [_hreftree setObject:tree forKey:href];
346 [_hrefresource setObject:element forKey:href];
347 [_uidhref setObject:href forKey:[[[tree components] anyObject] UID]];
348 }
349 DESTROY(ical);
350 [tree release];
351 [element release];
352 }
353 }
354 - (void)fetchData:(id)object
355 {
356 if (_calendar)
357 [self fetchList:AUTORELEASE([_calendar listICalItems])];
358 if (_task)
359 [self fetchList:AUTORELEASE([_task listICalItems])];
360 NSLog(@"GroupDAVStore from %@ : loaded %d appointment(s)", [_url absoluteString], [[self events] count]);
361 NSLog(@"GroupDAVStore from %@ : loaded %d tasks(s)", [_url absoluteString], [[self tasks] count]);
362 }
363 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3
4 @interface HourFormatter : NSFormatter
5 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "HourFormatter.h"
3
4 @implementation HourFormatter
5
6 - (NSString *)stringForObjectValue:(id)anObject
7 {
8 if (![anObject isKindOfClass:[NSNumber class]])
9 return nil;
10 int m = ([anObject floatValue] - [anObject intValue]) * 100;
11 return [NSString stringWithFormat:@"%dh%02d", [anObject intValue], 60 * m / 100];
12 }
13
14 - (BOOL)getObjectValue:(id *)anObject forString:(NSString *)string errorDescription:(NSString **)error
15 {
16 return NO;
17 }
18
19 - (NSAttributedString *)attributedStringForObjectValue:(id)anObject withDefaultAttributes:(NSDictionary *)attributes
20 {
21 return nil;
22 }
23
24 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "MemoryStore.h"
3
4 @interface LocalStore : MemoryStore <AgendaStore>
5 {
6 NSString *_globalPath;
7 NSString *_globalFile;
8 NSString *_globalTaskFile;
9 }
10 @end
11
0 #import <AppKit/AppKit.h>
1 #import "LocalStore.h"
2 #import "Event.h"
3 #import "Task.h"
4 #import "defines.h"
5
6 #define CurrentVersion 2
7 #define LocalAgendaPath @"~/GNUstep/Library/SimpleAgenda"
8
9 @implementation LocalStore
10
11 - (NSDictionary *)defaults
12 {
13 return [NSDictionary dictionaryWithObjectsAndKeys:
14 [NSArchiver archivedDataWithRootObject:[NSColor yellowColor]], ST_COLOR,
15 [NSArchiver archivedDataWithRootObject:[NSColor darkGrayColor]], ST_TEXT_COLOR,
16 [NSNumber numberWithBool:YES], ST_RW,
17 [NSNumber numberWithBool:YES], ST_DISPLAY,
18 [NSNumber numberWithInt:CurrentVersion], ST_VERSION,
19 nil, nil];
20 }
21
22 - (id)initWithName:(NSString *)name
23 {
24 NSString *filename;
25
26 self = [super initWithName:name];
27 if (self) {
28 filename = [_config objectForKey:ST_FILE];
29 _globalPath = [[LocalAgendaPath stringByExpandingTildeInPath] retain];
30 _globalFile = [[NSString pathWithComponents:[NSArray arrayWithObjects:_globalPath, filename, nil]] retain];
31 _globalTaskFile = [[NSString stringWithFormat:@"%@.tasks", _globalFile] retain];
32 [self read];
33 }
34 return self;
35 }
36
37 + (BOOL)registerWithName:(NSString *)name
38 {
39 ConfigManager *cm;
40
41 cm = [[ConfigManager alloc] initForKey:name withParent:nil];
42 [cm setObject:[name copy] forKey:ST_FILE];
43 [cm setObject:[[self class] description] forKey:ST_CLASS];
44 return YES;
45 }
46
47 + (NSString *)storeTypeName
48 {
49 return @"Simple file store";
50 }
51
52 - (void)dealloc
53 {
54 [self write];
55 [_globalFile release];
56 [_globalTaskFile release];
57 [_globalPath release];
58 [super dealloc];
59 }
60
61 - (BOOL)read
62 {
63 NSFileManager *fm = [NSFileManager defaultManager];
64 NSSet *savedData;
65 BOOL isDir;
66 int version;
67
68 if (![fm fileExistsAtPath:_globalPath]) {
69 if (![fm createDirectoryAtPath:_globalPath attributes:nil]) {
70 NSLog(@"Error creating dir %@", _globalPath);
71 return NO;
72 }
73 NSLog(@"Created directory %@", _globalPath);
74 }
75 if ([fm fileExistsAtPath:_globalFile isDirectory:&isDir] && !isDir) {
76 savedData = [NSKeyedUnarchiver unarchiveObjectWithFile:_globalFile];
77 if (savedData) {
78 [self fillWithElements:savedData];
79 NSLog(@"LocalStore from %@ : loaded %d appointment(s)", _globalFile, [[self events] count]);
80 version = [_config integerForKey:ST_VERSION];
81 if (version < CurrentVersion) {
82 [_config setInteger:CurrentVersion forKey:ST_VERSION];
83 [self write];
84 }
85 }
86 }
87 if ([fm fileExistsAtPath:_globalTaskFile isDirectory:&isDir] && !isDir) {
88 savedData = [NSKeyedUnarchiver unarchiveObjectWithFile:_globalTaskFile];
89 if (savedData) {
90 [self fillWithElements:savedData];
91 NSLog(@"LocalStore from %@ : loaded %d tasks(s)", _globalTaskFile, [[self tasks] count]);
92 }
93 }
94 return YES;
95 }
96
97 - (BOOL)write
98 {
99 NSSet *set;
100 NSSet *tasks;
101
102 if (![self modified])
103 return YES;
104 set = [NSSet setWithArray:[self events]];
105 tasks = [NSSet setWithArray:[self tasks]];
106 if ([NSKeyedArchiver archiveRootObject:set toFile:_globalFile] &&
107 [NSKeyedArchiver archiveRootObject:tasks toFile:_globalTaskFile]) {
108 NSLog(@"LocalStore written to %@", _globalFile);
109 [self setModified:NO];
110 return YES;
111 }
112 NSLog(@"Unable to write to %@, make this store read only", _globalFile);
113 [self setWritable:NO];
114 return NO;
115 }
116 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "AgendaStore.h"
3 #import "ConfigManager.h"
4
5 @interface MemoryStore : NSObject <MemoryStore>
6 {
7 ConfigManager *_config;
8 NSMutableDictionary *_data;
9 NSMutableDictionary *_tasks;
10 BOOL _modified;
11 NSString *_name;
12 BOOL _displayed;
13 BOOL _writable;
14 }
15 @end
0 #import <AppKit/AppKit.h>
1 #import "MemoryStore.h"
2 #import "Event.h"
3 #import "Task.h"
4 #import "defines.h"
5
6 @implementation MemoryStore
7
8 - (NSDictionary *)defaults
9 {
10 return nil;
11 }
12
13 - (id)initWithName:(NSString *)name
14 {
15 NSString *filename;
16
17 self = [super init];
18 if (self) {
19 _name = [name copy];
20 _config = [[ConfigManager alloc] initForKey:name withParent:nil];
21 [_config registerDefaults:[self defaults]];
22 filename = [_config objectForKey:ST_FILE];
23 _modified = NO;
24 _data = [[NSMutableDictionary alloc] initWithCapacity:128];
25 _tasks = [[NSMutableDictionary alloc] initWithCapacity:16];
26 _writable = [[_config objectForKey:ST_RW] boolValue];
27 _displayed = [[_config objectForKey:ST_DISPLAY] boolValue];
28 }
29 return self;
30 }
31
32 + (id)storeNamed:(NSString *)name
33 {
34 return AUTORELEASE([[self allocWithZone: NSDefaultMallocZone()] initWithName:name]);
35 }
36
37 + (BOOL)registerWithName:(NSString *)name
38 {
39 return NO;
40 }
41 + (NSString *)storeTypeName
42 {
43 return nil;
44 }
45
46 - (void)dealloc
47 {
48 [_data release];
49 [_tasks release];
50 [_name release];
51 [_config release];
52 [super dealloc];
53 }
54
55 - (NSArray *)events
56 {
57 return [_data allValues];
58 }
59 - (NSArray *)tasks
60 {
61 return [_tasks allValues];
62 }
63
64 /* Should be used only when loading data */
65 - (void)fillWithElements:(NSSet *)set
66 {
67 NSEnumerator *enumerator = [set objectEnumerator];
68 Element *elt;
69
70 while ((elt = [enumerator nextObject])) {
71 [elt setStore:self];
72 if ([elt isKindOfClass:[Event class]])
73 [_data setValue:elt forKey:[elt UID]];
74 else
75 [_tasks setValue:elt forKey:[elt UID]];
76 }
77 [[NSNotificationCenter defaultCenter] postNotificationName:SADataChangedInStore object:self];
78 }
79
80 - (void)add:(Element *)elt
81 {
82 [elt setStore:self];
83 if ([elt isKindOfClass:[Event class]])
84 [_data setValue:elt forKey:[elt UID]];
85 else
86 [_tasks setValue:elt forKey:[elt UID]];
87 _modified = YES;
88 [[NSNotificationCenter defaultCenter] postNotificationName:SADataChangedInStore object:self];
89 }
90
91 - (void)remove:(Element *)elt
92 {
93 if ([elt isKindOfClass:[Event class]])
94 [_data removeObjectForKey:[elt UID]];
95 else
96 [_tasks removeObjectForKey:[elt UID]];
97 _modified = YES;
98 [[NSNotificationCenter defaultCenter] postNotificationName:SADataChangedInStore object:self];
99 }
100
101 - (void)update:(Element *)elt;
102 {
103 [elt setDateStamp:[Date now]];
104 _modified = YES;
105 [[NSNotificationCenter defaultCenter] postNotificationName:SADataChangedInStore object:self];
106 }
107
108 - (BOOL)contains:(Element *)elt
109 {
110 if ([elt isKindOfClass:[Event class]])
111 return [_data objectForKey:[elt UID]] != nil;
112 return [_tasks objectForKey:[elt UID]] != nil;
113 }
114
115 -(BOOL)writable
116 {
117 return _writable;
118 }
119
120 - (void)setWritable:(BOOL)writable
121 {
122 _writable = writable;
123 [_config setObject:[NSNumber numberWithBool:_writable] forKey:ST_RW];
124 }
125
126 - (BOOL)modified
127 {
128 return _modified;
129 }
130
131 - (void)setModified:(BOOL)modified
132 {
133 _modified = modified;
134 }
135
136 - (NSString *)description
137 {
138 return _name;
139 }
140
141 - (NSColor *)eventColor
142 {
143 NSData *theData =[_config objectForKey:ST_COLOR];
144 return [NSUnarchiver unarchiveObjectWithData:theData];
145 }
146
147 - (void)setEventColor:(NSColor *)color
148 {
149 NSData *data = [NSArchiver archivedDataWithRootObject:color];
150 [_config setObject:data forKey:ST_COLOR];
151 }
152
153 - (NSColor *)textColor
154 {
155 NSData *theData =[_config objectForKey:ST_TEXT_COLOR];
156 return [NSUnarchiver unarchiveObjectWithData:theData];
157 }
158
159 - (void)setTextColor:(NSColor *)color
160 {
161 NSData *data = [NSArchiver archivedDataWithRootObject:color];
162 [_config setObject:data forKey:ST_TEXT_COLOR];
163 }
164
165 - (BOOL)displayed
166 {
167 return _displayed;
168 }
169
170 - (void)setDisplayed:(BOOL)state
171 {
172 _displayed = state;
173 [_config setObject:[NSNumber numberWithBool:_displayed] forKey:ST_DISPLAY];
174 }
175
176 @end
0 version 0.36 (2008/01/20)
1 * Calendar UI changes : to reduce calendar size, use different
2 visual hints. Today has a yellow background (unchanged), the
3 selected day cell is bezeled/pushed (was a bold font) and busy
4 days use a bold font (instead of a tick mark). Always show six
5 weeks with black text for the chosen month and white text for
6 the previous and next ones. Use a defined font size so that it
7 all fits whatever the user choose as a default size.
8 * Day view : circle through appointments with TAB and edit the
9 selected one with enter
10 * Day view : no more appointments overlapping. The algorithm is
11 not 100% correct, we might want to change that in the future
12 * Change license for future GNUstep GPLv3 release compatibility
13 Thanks to Yavor Doganov for pointing out the issue.
14 * Use ETags to prevent overwriting distant modifications
15 * Add a menu item to force agendas to reload their data
16 * Bug fixes and various improvements
17 * Experimental GroupDAV support : some things work but use with
18 care. Feedback appreciated
19
20 version 0.35 (2007/12/31)
21 * Fix (fingers crossed) timezone bugs and current day timer
22 * New website at http://coyote.octets.fr/simpleagenda
23
24 version 0.34 (2007/12/13)
25 * Fix loading and saving logic : when loading an iCalendar,
26 we used to refresh the view for each appointment and task.
27 Should be quite faster when loading a big calendar. Also,
28 stop saving unmodified local calendars on exit.
29 * Fix position for appointments going outside the day view
30 * Show abbreviated date in day view tab
31 * Double click on the calendar to add an appointment
32 * Fix iCalendar stores data refresh timer
33 * Workaround for the element selection problem. Needs a far
34 better solution.
35
36 version 0.33 (2007/12/08)
37 * Fix dates of summary events
38 * Add a visual hint for recurrent events
39 * Fix iCalendar date and duration encoding
40 * Fix pasteboard interaction
41 * Draw appointments with transparency (code from old
42 http://www.linuks.mine.nu/agenda/agenda-0.1.tar.gz)
43
44 version 0.32 (2007/12/07)
45 * Add 'SimpleAgenda/Create Task' service
46 * Internal modifications and refactoring
47 * Add Open tasks category to summary view
48 * Enable 24 hours DayView
49 * Change iCalStore creation to set store writable flag if
50 possible without erasing existing data
51 * Show days with appointments in MonthView.
52 * Real asynchronous startup for remote stores. Thanks to
53 Dennis Leeuw for this bug report (and many others)
54 * Fix end date processing for recurrent events : events can
55 repeat for ever or until a specified date
56
57 version 0.31 (2007/11/21)
58 * Bug fixes
59 * Appointments sorted by date in the summary
60 * Deleted code to read old Date objects encoding
61 * Update search results when store data change
62
63 version 0.30 (2007/11/12)
64 * Refactor code to handle multiple kinds of events
65 * Add simple task (iCalendar VTODO) support
66 * Add a task view
67 * Coherent ui : in summary, day view and task view,
68 simple click to select and double click to edit
69 * Make calendar view smaller
70 * Fix appointment resize bug in day view
71
72 version 0.29 (2007/11/02)
73 * Preferences dialog uses a popup to select categories (r276)
74 * Events text color selectable in store preferences
75 * Fix time zone bugs
76 * Fix iCal authenticated loading
77
78 version 0.28
79 * Simple text search
80
81 version 0.27 (2007/09/18)
82 * Fix keyboard handling in the day view
83 * Add store creation in preferences dialog
84 * Load iCal stores asynchronously
85 * Fix keyboard navigation in preferences dialog
86
87 version 0.26 (2007/08/16)
88 * Save main window geometry
89 * Add a 'Save all' item menu to fsync data
90 * Register SimpleAgenda as an application handling
91 .ics files and import events when user open one
92 * Add a NEWS file
93 * Fix compilation with gcc 3.3
94 * Fix events dates in the summary
95 * Selecting an event in the summary shows it in the day view
0 {
1 APPLICATIONICON = Calendar.tiff;
2 APP_DOCUMENT_BASED = NO;
3 APP_TYPE = GORM;
4 CLASS_FILES = (
5 AppController.m,
6 LocalStore.m,
7 AppointmentEditor.m,
8 CalendarView.m,
9 StoreManager.m,
10 DayView.m,
11 Event.m,
12 PreferencesController.m,
13 HourFormatter.m,
14 iCalStore.m,
15 ConfigManager.m,
16 Date.m,
17 iCalTree.m,
18 DataTree.m,
19 Element.m,
20 Task.m,
21 TaskEditor.m,
22 MemoryStore.m,
23 GroupDAVStore.m,
24 WebDAVResource.m
25 );
26 COMPILEROPTIONS = "";
27 CPPOPTIONS = "";
28 CREATION_DATE = "2007-01-08 21:35:48 +0100";
29 DOCU_FILES = (
30 );
31 FRAMEWORKS = (
32 );
33 HEADER_FILES = (
34 AppController.h,
35 AgendaStore.h,
36 LocalStore.h,
37 AppointmentEditor.h,
38 CalendarView.h,
39 StoreManager.h,
40 DayView.h,
41 Event.h,
42 PreferencesController.h,
43 HourFormatter.h,
44 UserDefaults.h,
45 iCalStore.h,
46 ConfigManager.h,
47 Date.h,
48 iCalTree.h,
49 DataTree.h,
50 Element.h,
51 Task.h,
52 TaskEditor.h,
53 MemoryStore.h,
54 GroupDAVStore.h,
55 WebDAVResource.h
56 );
57 IMAGES = (
58 Calendar.tiff,
59 "ical-file.tiff",
60 repeat.tiff
61 );
62 INSTALLDIR = "$(HOME)/GNUstep";
63 INTERFACES = (
64 Agenda.gorm,
65 Appointment.gorm,
66 Preferences.gorm,
67 iCalendar.gorm,
68 Task.gorm,
69 GroupDAV.gorm
70 );
71 LANGUAGE = English;
72 LAST_EDITING = "2008-01-20 00:51:43 +0100";
73 LIBRARIES = (
74 "gnustep-base",
75 "gnustep-gui",
76 ical
77 );
78 LINKEROPTIONS = "";
79 LOCALIZED_RESOURCES = (
80 );
81 MAININTERFACE = Agenda.gorm;
82 MAKEFILEDIR = "$(GNUSTEP_MAKEFILES)";
83 OBJC_COMPILEROPTIONS = "";
84 OTHER_RESOURCES = (
85 );
86 OTHER_SOURCES = (
87 SimpleAgenda.m
88 );
89 PC_WINDOWS = {
90 ProjectBrowser = "{x = 0; y = 0; width = 838; height = 409}";
91 ProjectWindow = "285 87 842 913 0 0 1280 1024 ";
92 ShowToolbar = YES;
93 };
94 PRINCIPAL_CLASS = NSApplication;
95 PROJECT_AUTHORS = (
96 "Philippe Roussel <p.o.roussel@free.fr>"
97 );
98 PROJECT_COPYRIGHT = "Copyright (C) 2007";
99 PROJECT_COPYRIGHT_DESC = "Released under GPL Version 2";
100 PROJECT_CREATOR = "Philippe Roussel";
101 PROJECT_DESCRIPTION = "Simple agenda and calendar application";
102 PROJECT_DOCUMENTTYPES = (
103 {
104 NSDocumentClass = "";
105 NSHumanReadableName = "";
106 NSIcon = "ical-file.tiff";
107 NSName = "";
108 NSRole = "";
109 NSUnixExtensions = (
110 ics
111 );
112 }
113 );
114 PROJECT_GROUP = "No group avaliable!";
115 PROJECT_MAINTAINER = "Philippe Roussel <p.o.rousse@free.fr>";
116 PROJECT_NAME = SimpleAgenda;
117 PROJECT_RELEASE = 0.36;
118 PROJECT_SUMMARY = "No summary avaliable!";
119 PROJECT_TYPE = Application;
120 PROJECT_URL = "http://coyote.octets.fr/simpleagenda/";
121 SEARCH_HEADER_DIRS = (
122 );
123 SEARCH_LIB_DIRS = (
124 "/usr/local/lib"
125 );
126 SUBPROJECTS = (
127 );
128 SUPPORTING_FILES = (
129 GNUmakefile.preamble,
130 GNUmakefile,
131 GNUmakefile.postamble
132 );
133 USER_LANGUAGES = (
134 French,
135 English
136 );
137 }
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "AgendaStore.h"
4 #import "StoreManager.h"
5
6 @interface PreferencesController : NSObject
7 {
8 IBOutlet id panel;
9 IBOutlet id dayStart;
10 IBOutlet id dayEnd;
11 IBOutlet id minStep;
12 IBOutlet id dayStartText;
13 IBOutlet id dayEndText;
14 IBOutlet id minStepText;
15 IBOutlet id storePopUp;
16 IBOutlet id storeColor;
17 IBOutlet id storeTextColor;
18 IBOutlet id defaultStorePopUp;
19 IBOutlet id storeDisplay;
20 IBOutlet id storeWritable;
21 IBOutlet id removeButton;
22 IBOutlet id storeClass;
23 IBOutlet id storeName;
24 IBOutlet id createButton;
25 IBOutlet NSBox *slot;
26 IBOutlet id globalPreferences;
27 IBOutlet id storePreferences;
28 IBOutlet id storeFactory;
29 IBOutlet id itemPopUp;
30 StoreManager *_sm;
31 }
32
33 -(id)initWithStoreManager:(StoreManager *)sm;
34 -(void)showPreferences;
35
36 -(void)selectStore:(id)sender;
37 -(void)changeColor:(id)sender;
38 -(void)changeTextColor:(id)sender;
39 -(void)changeStart:(id)sender;
40 -(void)changeEnd:(id)sender;
41 -(void)changeStep:(id)sender;
42 -(void)selectDefaultStore:(id)sender;
43 -(void)toggleDisplay:(id)sender;
44 -(void)toggleWritable:(id)sender;
45 -(void)removeStore:(id)sender;
46 -(void)createStore:(id)sender;
47 -(void)selectItem:(id)sender;
48
49 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "PreferencesController.h"
3 #import "HourFormatter.h"
4 #import "ConfigManager.h"
5 #import "defines.h"
6
7 @implementation PreferencesController
8
9 -(id)initWithStoreManager:(StoreManager *)sm
10 {
11 self = [super init];
12 if (self) {
13 if (![NSBundle loadNibNamed:@"Preferences" owner:self])
14 return nil;
15
16 ASSIGN(_sm, sm);
17 HourFormatter *formatter = [[[HourFormatter alloc] init] autorelease];
18 [[dayStartText cell] setFormatter:formatter];
19 [[dayEndText cell] setFormatter:formatter];
20 [[minStepText cell] setFormatter:formatter];
21 RETAIN(globalPreferences);
22 RETAIN(storePreferences);
23 RETAIN(storeFactory);
24 [self selectItem:itemPopUp];
25 [panel setFrameAutosaveName:@"preferencesPanel"];
26 /* FIXME : I can't set these within Gorm */
27 [dayStart setContinuous:YES];
28 [dayEnd setContinuous:YES];
29 [minStep setContinuous:YES];
30 }
31 return self;
32 }
33
34 - (void)dealloc
35 {
36 RELEASE(_sm);
37 RELEASE(globalPreferences);
38 RELEASE(storePreferences);
39 RELEASE(storeFactory);
40 [super dealloc];
41 }
42
43 -(void)_setupStores
44 {
45 ConfigManager *config = [ConfigManager globalConfig];
46 NSString *defaultStore = [config objectForKey:ST_DEFAULT];
47 NSEnumerator *list = [_sm storeEnumerator];
48 id <AgendaStore> aStore;
49
50 [defaultStorePopUp removeAllItems];
51 while ((aStore = [list nextObject])) {
52 if ([aStore writable])
53 [defaultStorePopUp addItemWithTitle:[aStore description]];
54 }
55 [defaultStorePopUp selectItemWithTitle:defaultStore];
56
57 list = [_sm storeEnumerator];
58 [storePopUp removeAllItems];
59 while ((aStore = [list nextObject]))
60 [storePopUp addItemWithTitle:[aStore description]];
61 [storePopUp selectItemAtIndex:0];
62 [self selectStore:self];
63 }
64
65 -(void)showPreferences
66 {
67 NSEnumerator *backends = [[StoreManager backends] objectEnumerator];
68 ConfigManager *config = [ConfigManager globalConfig];
69 int start = [config integerForKey:FIRST_HOUR];
70 int end = [config integerForKey:LAST_HOUR];
71 int step = [config integerForKey:MIN_STEP];
72 Class backend;
73
74 [dayStart setIntValue:start];
75 [dayEnd setIntValue:end];
76 [dayStartText setIntValue:start];
77 [dayEndText setIntValue:end];
78 [minStep setDoubleValue:step/60.0];
79 [minStepText setDoubleValue:step/60.0];
80
81 [self _setupStores];
82 [storeClass removeAllItems];
83 while ((backend = [backends nextObject]))
84 [storeClass addItemWithTitle:[backend storeTypeName]];
85 [storeClass selectItemAtIndex:0];
86 [createButton setEnabled:NO];
87 [panel makeKeyAndOrderFront:self];
88 }
89
90
91 -(void)selectStore:(id)sender
92 {
93 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
94 [storeColor setColor:[store eventColor]];
95 [storeTextColor setColor:[store textColor]];
96 [storeDisplay setState:[store displayed]];
97 [storeWritable setState:[store writable]];
98 if ([[defaultStorePopUp titleOfSelectedItem] isEqual:[store description]])
99 [removeButton setEnabled:NO];
100 else
101 [removeButton setEnabled:YES];
102 }
103
104 -(void)changeColor:(id)sender
105 {
106 NSColor *rgb = [[storeColor color] colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
107 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
108 [store setEventColor:rgb];
109 }
110
111 -(void)changeTextColor:(id)sender
112 {
113 NSColor *rgb = [[storeTextColor color] colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
114 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
115 [store setTextColor:rgb];
116 }
117
118 -(void)changeStart:(id)sender
119 {
120 int value = [dayStart intValue];
121 if (value != [[ConfigManager globalConfig] integerForKey:FIRST_HOUR]) {
122 [dayStartText setIntValue:value];
123 [[ConfigManager globalConfig] setInteger:value forKey:FIRST_HOUR];
124 }
125 }
126
127 -(void)changeEnd:(id)sender
128 {
129 int value = [dayEnd intValue];
130 if (value != [[ConfigManager globalConfig] integerForKey:LAST_HOUR]) {
131 [dayEndText setIntValue:value];
132 [[ConfigManager globalConfig] setInteger:value forKey:LAST_HOUR];
133 }
134 }
135
136 -(void)changeStep:(id)sender
137 {
138 double value = [minStep doubleValue];
139 if (value * 60 != [[ConfigManager globalConfig] integerForKey:MIN_STEP]) {
140 [minStepText setDoubleValue:value];
141 [[ConfigManager globalConfig] setInteger:value * 60 forKey:MIN_STEP];
142 }
143 }
144
145 -(void)selectDefaultStore:(id)sender
146 {
147 [_sm setDefaultStore:[defaultStorePopUp titleOfSelectedItem]];
148 [self selectStore:nil];
149 }
150
151 -(void)toggleDisplay:(id)sender
152 {
153 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
154 [store setDisplayed:[storeDisplay state]];
155 }
156
157 -(void)toggleWritable:(id)sender
158 {
159 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
160 [store setWritable:[storeWritable state]];
161 }
162
163 /* We only allow the removal of non-default stores */
164 -(void)removeStore:(id)sender
165 {
166 id <AgendaStore> store = [_sm storeForName:[storePopUp titleOfSelectedItem]];
167 ConfigManager *config = [ConfigManager globalConfig];
168 NSMutableArray *storeArray = [config objectForKey:STORES];
169
170 [storeArray removeObject:[store description]];
171 [config setObject:storeArray forKey:STORES];
172 [config removeObjectForKey:[store description]];
173 [_sm removeStoreNamed:[store description]];
174 /* FIXME : This could be done by registering STORES key */
175 [self _setupStores];
176 }
177
178 -(void)createStore:(id)sender
179 {
180 ConfigManager *config = [ConfigManager globalConfig];
181 NSMutableArray *storeArray = [NSMutableArray arrayWithArray:[config objectForKey:STORES]];
182 Class backend;
183
184 backend = [StoreManager backendForName:[storeClass titleOfSelectedItem]];
185 if (backend && [backend registerWithName:[storeName stringValue]]) {
186 [_sm addStoreNamed:[storeName stringValue]];
187 [storeArray addObject:[storeName stringValue]];
188 [config setObject:storeArray forKey:STORES];
189 [self _setupStores];
190 }
191 [storeName setStringValue:@""];
192 [createButton setEnabled:NO];
193 }
194
195 -(void)selectItem:(id)sender
196 {
197 switch ([sender indexOfSelectedItem]) {
198 case 0:
199 [slot setContentView:globalPreferences];
200 break;
201 case 1:
202 [slot setContentView:storePreferences];
203 break;
204 case 2:
205 [slot setContentView:storeFactory];
206 break;
207 }
208 [itemPopUp setNextKeyView:[slot contentView]];
209 }
210
211 -(void)controlTextDidChange:(NSNotification *)notification
212 {
213 if ([notification object] == storeName) {
214 if ([_sm storeForName:[storeName stringValue]] || ![[storeName stringValue] length])
215 [createButton setEnabled:NO];
216 else
217 [createButton setEnabled:YES];
218 }
219 }
220
221 @end
(New empty file)
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 AppController = {
3 Actions = (
4 "copy:",
5 "cut:",
6 "paste:",
7 "editAppointment:",
8 "delAppointment:",
9 "exportAppointment:",
10 "saveAll:",
11 "showPrefPanel:",
12 "addAppointment:",
13 "addTask:",
14 "editAppointment:",
15 "delAppointment:",
16 "exportAppointment:",
17 "doSearch:",
18 "clearSearch:",
19 "reloadAll:"
20 );
21 Outlets = (
22 calendar,
23 dayView,
24 summary,
25 search,
26 taskView,
27 window,
28 tabs
29 );
30 Super = NSObject;
31 };
32 CalendarView = {
33 Actions = (
34 "setDelegate:"
35 );
36 Outlets = (
37 delegate,
38 dataSource
39 );
40 Super = NSBox;
41 };
42 DayView = {
43 Actions = (
44 );
45 Outlets = (
46 dataSource,
47 delegate
48 );
49 Super = NSView;
50 };
51 DayViewController = {
52 Actions = (
53 "setDelegate:"
54 );
55 Outlets = (
56 delegate
57 );
58 Super = NSObject;
59 };
60 FirstResponder = {
61 Actions = (
62 "addAppointment:",
63 "addTask:",
64 "clearSearch:",
65 "createOrEditAnAppointment:",
66 "delAppointment:",
67 "doSearch:",
68 "editAppointment:",
69 "exportAppointment:",
70 "reloadAll:",
71 "orderFrontFontPanel:",
72 "saveAll:",
73 "setDataSource:",
74 "setDelegate:",
75 "showPrefPanel:",
76 "updateDate:",
77 "updateView:"
78 );
79 Super = NSObject;
80 };
81 }
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 AppointmentEditor = {
3 Actions = (
4 "cancel:",
5 "validate:",
6 "selectFrequency:",
7 "toggleUntil:",
8 "toggleAllDay:"
9 );
10 Outlets = (
11 description,
12 title,
13 duration,
14 repeat,
15 endDate,
16 durationText,
17 location,
18 store,
19 allDay,
20 ok,
21 until
22 );
23 Super = NSWindowController;
24 };
25 FirstResponder = {
26 Actions = (
27 "toggleAllDay:",
28 "selectFrequency:",
29 "toggleUntil:",
30 "validate:"
31 );
32 Super = NSObject;
33 };
34 }
Binary diff not shown
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 FirstResponder = {
3 Actions = (
4 "buttonClicked:",
5 "selectItem:"
6 );
7 Super = NSObject;
8 };
9 GroupDAVDialog = {
10 Actions = (
11 "buttonClicked:",
12 "selectItem:"
13 );
14 Outlets = (
15 cancel,
16 name,
17 ok,
18 panel,
19 url,
20 check,
21 calendar,
22 task
23 );
24 Super = NSObject;
25 };
26 }
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 FirstResponder = {
3 Actions = (
4 "changeEnd:",
5 "changeStart:",
6 "changeStep:",
7 "createStore:",
8 "selectItem:",
9 "removeStore:",
10 "selectDefaultStore:",
11 "selectStore:",
12 "setColor:",
13 "toggleDisplay:",
14 "toggleWritable:"
15 );
16 Super = NSObject;
17 };
18 PreferencesController = {
19 Actions = (
20 "selectStore:",
21 "changeColor:",
22 "changeTextColor:",
23 "changeEnd:",
24 "changeStart:",
25 "changeStep:",
26 "selectDefaultStore:",
27 "toggleDisplay:",
28 "toggleWritable:",
29 "removeStore:",
30 "createStore:",
31 "selectItem:"
32 );
33 Outlets = (
34 panel,
35 dayStart,
36 dayEnd,
37 dayStartText,
38 dayEndText,
39 minStep,
40 minStepText,
41 storeColor,
42 storeTextColor,
43 storePopUp,
44 defaultStorePopUp,
45 storeDisplay,
46 storeWritable,
47 removeButton,
48 storeClass,
49 storeName,
50 createButton,
51 slot,
52 globalPreferences,
53 itemPopUp,
54 storeFactory,
55 storePreferences
56 );
57 Super = NSObject;
58 };
59 }
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 FirstResponder = {
3 Actions = (
4 "validate:"
5 );
6 Super = NSObject;
7 };
8 TaskEditor = {
9 Actions = (
10 "validate:",
11 "cancel:"
12 );
13 Outlets = (
14 window,
15 description,
16 summary,
17 store,
18 state,
19 ok
20 );
21 Super = NSObject;
22 };
23 }
0 {
1 "## Comment" = "Do NOT change this file, Gorm maintains it";
2 FirstResponder = {
3 Actions = (
4 "cancelClicked:",
5 "okClicked:"
6 );
7 Super = NSObject;
8 };
9 iCalStore = {
10 Actions = (
11 );
12 Outlets = (
13 _delegate
14 );
15 Super = NSObject;
16 };
17 iCalStoreDialog = {
18 Actions = (
19 "cancelClicked:",
20 "okClicked:"
21 );
22 Outlets = (
23 panel,
24 name,
25 ok,
26 url,
27 error,
28 warning
29 );
30 Super = NSObject;
31 };
32 }
Binary diff not shown
Binary diff not shown
0 /*
1 Project: SimpleAgenda
2
3 Copyright (C) 2007 Philippe Roussel
4
5 Author: Philippe Roussel <p.o.roussel@free.fr>
6
7 Created: 2007-01-08 21:35:48 +0100 by philou
8
9 This application is free software; you can redistribute it and/or
10 modify it under the terms of the GNU General Public
11 License as published by the Free Software Foundation; either
12 version 2 of the License or any later version.
13
14 This application is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 Library General Public License for more details.
18
19 You should have received a copy of the GNU General Public
20 License along with this library; if not, write to the Free
21 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA.
22 */
23
24 #import <AppKit/AppKit.h>
25
26 int main(int argc, const char *argv[])
27 {
28 //[NSObject enableDoubleReleaseCheck:YES];
29 return NSApplicationMain (argc, argv);
30 }
31
0 {
1 ApplicationDescription = "Simple agenda and calendar application";
2 ApplicationIcon = Calendar.tiff;
3 ApplicationName = SimpleAgenda;
4 ApplicationRelease = 0.36;
5 Authors = (
6 "Philippe Roussel <p.o.roussel@free.fr>"
7 );
8 Copyright = "Copyright (C) 2007";
9 CopyrightDescription = "Released under GPL Version 2";
10 FullVersionID = 0.36;
11 NSExecutable = SimpleAgenda;
12 NSIcon = Calendar.tiff;
13 NSMainNibFile = Agenda.gorm;
14 NSPrincipalClass = NSApplication;
15 NSRole = Application;
16 NSServices = (
17 {
18 NSKeyEquivalent = {
19 default = t;
20 };
21 NSMenuItem = {
22 default = "SimpleAgenda/Create Task";
23 };
24 NSMessage = newTask;
25 NSPortName = SimpleAgenda;
26 NSSendTypes = (
27 NSStringPboardType
28 );
29 }
30 );
31 NSTypes = (
32 {
33 NSDocumentClass = "";
34 NSHumanReadableName = "";
35 NSIcon = "ical-file.tiff";
36 NSName = "";
37 NSRole = "";
38 NSUnixExtensions = (
39 ics
40 );
41 }
42 );
43 URL = "http://coyote.octets.fr/simpleagenda/";
44 }
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "Date.h"
3 #import "Element.h"
4
5 @protocol AgendaDataSource
6 - (NSSet *)scheduledAppointmentsForDay:(Date *)day;
7 @end
8
9 #define SADataChanged @"DataDidChanged"
10
11 @interface StoreManager : NSObject
12 {
13 NSMutableDictionary *_stores;
14 id _defaultStore;
15 }
16
17 + (NSArray *)backends;
18 + (Class)backendForName:(NSString *)name;
19
20 - (void)addStoreNamed:(NSString *)name;
21 - (void)removeStoreNamed:(NSString *)name;
22 - (id <AgendaStore>)storeForName:(NSString *)name;
23 - (void)setDefaultStore:(NSString *)name;
24 - (id <AgendaStore>)defaultStore;
25 - (NSEnumerator *)storeEnumerator;
26 - (void)synchronise;
27 - (void)refresh;
28 - (id <AgendaStore>)storeContainingElement:(Element *)elt;
29 - (NSArray *)allEvents;
30 - (NSArray *)allTasks;
31 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3 #import "AgendaStore.h"
4 #import "StoreManager.h"
5 #import "ConfigManager.h"
6 #import "defines.h"
7 #import "LocalStore.h"
8 #import "iCalStore.h"
9
10 static NSMutableDictionary *backends = nil;
11
12 @implementation StoreManager
13
14 #define PERSONAL_AGENDA @"Personal Agenda"
15
16 + (void)initialize
17 {
18 NSArray *classes = GSObjCAllSubclassesOfClass([MemoryStore class]);
19 NSEnumerator *enumerator = [classes objectEnumerator];
20 Class backendClass;
21
22 if (backends == nil) {
23 backends = [[NSMutableDictionary alloc] initWithCapacity:[classes count]];
24 while ((backendClass = [enumerator nextObject])) {
25 if ([backendClass conformsToProtocol:@protocol(AgendaStore)])
26 [backends setObject:backendClass forKey:[backendClass storeTypeName]];
27 else
28 NSLog(@"Can't register %@ as a store backend", [backendClass description]);
29 }
30 }
31 }
32 + (NSArray *)backends
33 {
34 return [backends allValues];
35 }
36 + (Class)backendForName:(NSString *)name
37 {
38 return [backends valueForKey:name];
39 }
40
41 - (NSDictionary *)defaults
42 {
43 NSDictionary *local = [NSDictionary
44 dictionaryWithObjects:[NSArray arrayWithObjects:@"LocalStore", @"Personal", nil]
45 forKeys:[NSArray arrayWithObjects:ST_CLASS, ST_FILE, nil]];
46 NSDictionary *dict = [NSDictionary
47 dictionaryWithObjects:[NSArray arrayWithObjects: [NSArray arrayWithObject:PERSONAL_AGENDA], local, PERSONAL_AGENDA, nil]
48 forKeys:[NSArray arrayWithObjects: STORES, PERSONAL_AGENDA, ST_DEFAULT, nil]];
49 return dict;
50 }
51
52 - (id)init
53 {
54 NSArray *storeArray;
55 NSString *defaultStore;
56 NSEnumerator *enumerator;
57 NSString *stname;
58 ConfigManager *config = [ConfigManager globalConfig];
59
60 self = [super init];
61 if (self) {
62 [config registerDefaults:[self defaults]];
63 storeArray = [config objectForKey:STORES];
64 defaultStore = [config objectForKey:ST_DEFAULT];
65 _stores = [[NSMutableDictionary alloc] initWithCapacity:1];
66 enumerator = [storeArray objectEnumerator];
67 while ((stname = [enumerator nextObject]))
68 [self addStoreNamed:stname];
69 [self setDefaultStore:defaultStore];
70 [[NSNotificationCenter defaultCenter] addObserver:self
71 selector:@selector(dataChanged:)
72 name:SADataChangedInStore
73 object:nil];
74 }
75 return self;
76 }
77
78 - (void)dealloc
79 {
80 [[NSNotificationCenter defaultCenter] removeObserver:self];
81 RELEASE(_defaultStore);
82 [_stores release];
83 [super dealloc];
84 }
85
86 - (void)dataChanged:(NSNotification *)not
87 {
88 [[NSNotificationCenter defaultCenter] postNotificationName:SADataChanged object:self];
89 }
90
91 - (void)addStoreNamed:(NSString *)name
92 {
93 Class storeClass;
94 id <AgendaStore> store;
95 NSDictionary *dict;
96
97 dict = [[ConfigManager globalConfig] objectForKey:name];
98 if (dict) {
99 storeClass = NSClassFromString([dict objectForKey:ST_CLASS]);
100 store = [storeClass storeNamed:name];
101 if (store) {
102 [_stores setObject:store forKey:name];
103 NSLog(@"Added %@ to StoreManager", name);
104 [self dataChanged:nil];
105 } else
106 NSLog(@"Unable to initialize store %@", name);
107 }
108 }
109
110 - (void)removeStoreNamed:(NSString *)name
111 {
112 [_stores removeObjectForKey:name];
113 NSLog(@"Removed %@ from StoreManager", name);
114 [self dataChanged:nil];
115 }
116
117 - (id <AgendaStore>)storeForName:(NSString *)name
118 {
119 return [_stores objectForKey:name];
120 }
121
122 - (void)setDefaultStore:(NSString *)name
123 {
124 id st = [self storeForName:name];
125 if (st != nil) {
126 ASSIGN(_defaultStore, st);
127 [[ConfigManager globalConfig] setObject:name forKey:ST_DEFAULT];
128 }
129 }
130
131 - (id <AgendaStore>)defaultStore
132 {
133 return _defaultStore;
134 }
135
136 - (NSEnumerator *)storeEnumerator
137 {
138 return [_stores objectEnumerator];
139 }
140
141 - (void)synchronise
142 {
143 NSEnumerator *enumerator = [_stores objectEnumerator];
144 id <AgendaStore> store;
145
146 while ((store = [enumerator nextObject]))
147 [store write];
148 }
149
150 - (void)refresh
151 {
152 NSEnumerator *enumerator = [_stores objectEnumerator];
153 id <AgendaStore> store;
154
155 while ((store = [enumerator nextObject]))
156 [store read];
157 }
158
159 - (id <AgendaStore>)storeContainingElement:(Element *)elt
160 {
161 NSEnumerator *enumerator = [_stores objectEnumerator];
162 id <AgendaStore> store;
163
164 while ((store = [enumerator nextObject]))
165 if ([store contains:elt])
166 return store;
167 return nil;
168 }
169
170 - (NSArray *)allEvents
171 {
172 NSMutableArray *all = [NSMutableArray arrayWithCapacity:128];
173 NSEnumerator *enumerator = [_stores objectEnumerator];
174 id <AgendaStore> store;
175
176 while ((store = [enumerator nextObject]))
177 [all addObjectsFromArray:[store events]];
178 return all;
179 }
180 - (NSArray *)allTasks;
181 {
182 NSMutableArray *all = [NSMutableArray arrayWithCapacity:128];
183 NSEnumerator *enumerator = [_stores objectEnumerator];
184 id <AgendaStore> store;
185
186 while ((store = [enumerator nextObject]))
187 [all addObjectsFromArray:[store tasks]];
188 return all;
189 }
190 @end
191
0 In no particular order :
1
2 * redesign networking
3
4 * alerts and user notification (visual, by mail etc)
5
6 * week and month views
7
8 * build a framework to allow other applications to access the data ?
9 (or let somebody else implement Apple Calendar framework)
10
11 * fix headers mess
12
13 * fix element selection (lack of) mechanism
14
15 Want to help out ? Contact me at p.o.roussel@free.fr
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "Date.h"
3 #import "Element.h"
4 #import "AgendaStore.h"
5
6 enum taskState
7 {
8 TK_NONE = 0,
9 TK_INPROCESS,
10 TK_COMPLETED,
11 TK_CANCELED
12 };
13
14 @interface Task : Element
15 {
16 enum taskState _state;
17 Date *_completionDate;
18 }
19
20 + (NSArray *)stateNamesArray;
21 - (enum taskState)state;
22 - (NSString *)stateAsString;
23 - (void)setState:(enum taskState)state;
24 - (Date *)completionDate;
25 - (void)setCompletionDate:(Date *)cd;
26 @end
0 #import <Foundation/Foundation.h>
1 #import "Task.h"
2
3 static NSString *stateName[] = {@"None", @"Started", @"Completed", @"Canceled"};
4
5 @implementation Task(NSCoding)
6 -(void)encodeWithCoder:(NSCoder *)coder
7 {
8 [super encodeWithCoder:coder];
9 [coder encodeInt:_state forKey:@"state"];
10 if (_completionDate != nil)
11 [coder encodeObject:_completionDate forKey:@"completion"];
12 }
13 -(id)initWithCoder:(NSCoder *)coder
14 {
15 [super initWithCoder:coder];
16 _state = [coder decodeIntForKey:@"state"];
17 if ([coder containsValueForKey:@"completion"])
18 _completionDate = [[coder decodeObjectForKey:@"completion"] retain];
19 else
20 _completionDate = nil;
21 return self;
22 }
23 @end
24
25 @implementation Task
26 + (NSArray *)stateNamesArray
27 {
28 return [NSArray arrayWithObjects:stateName count:4];
29 }
30
31 - (id)init
32 {
33 self = [super init];
34 if (self) {
35 _state = TK_NONE;
36 _completionDate = nil;
37 }
38 return self;
39 }
40 - (void)dealloc
41 {
42 RELEASE(_completionDate);
43 [super dealloc];
44 }
45 - (enum taskState)state
46 {
47 return _state;
48 }
49 - (NSString *)stateAsString
50 {
51 return stateName[_state];
52 }
53 - (void)setState:(enum taskState)state
54 {
55 _state = state;
56 if (state == TK_COMPLETED)
57 [self setCompletionDate:[Date today]];
58 else
59 [self setCompletionDate:nil];
60 }
61 - (Date *)completionDate
62 {
63 return _completionDate;
64 }
65 - (void)setCompletionDate:(Date *)cd
66 {
67 if (_completionDate != nil)
68 RELEASE(_completionDate);
69 if (cd != nil)
70 ASSIGNCOPY(_completionDate, cd);
71 else
72 _completionDate = nil;
73 }
74 @end
75
76
77 @implementation Task(iCalendar)
78 - (id)initWithICalComponent:(icalcomponent *)ic
79 {
80 icalproperty *prop;
81
82 self = [super initWithICalComponent:ic];
83 if (self == nil)
84 return nil;
85 prop = icalcomponent_get_first_property(ic, ICAL_STATUS_PROPERTY);
86 if (prop) {
87 switch (icalproperty_get_status(prop))
88 {
89 case ICAL_STATUS_COMPLETED:
90 [self setState:TK_COMPLETED];
91 break;
92 case ICAL_STATUS_CANCELLED:
93 [self setState:TK_CANCELED];
94 break;
95 case ICAL_STATUS_INPROCESS:
96 [self setState:TK_INPROCESS];
97 break;
98 default:
99 [self setState:TK_NONE];
100 }
101 }
102 else
103 [self setState:TK_NONE];
104 return self;
105 }
106
107 - (icalcomponent *)asICalComponent
108 {
109 icalcomponent *ic = icalcomponent_new(ICAL_VTODO_COMPONENT);
110 if (!ic) {
111 NSLog(@"Couldn't create iCalendar component");
112 return NULL;
113 }
114 [self updateICalComponent:ic];
115 return ic;
116 }
117
118 static int statusCorr[] = {ICAL_STATUS_NONE, ICAL_STATUS_INPROCESS, ICAL_STATUS_COMPLETED, ICAL_STATUS_CANCELLED};
119
120 - (BOOL)updateICalComponent:(icalcomponent *)ic
121 {
122 if (![super updateICalComponent:ic])
123 return NO;
124 [self deleteProperty:ICAL_STATUS_PROPERTY fromComponent:ic];
125 icalcomponent_add_property(ic, icalproperty_new_status(statusCorr[[self state]]));
126 return YES;
127 }
128
129 - (int)iCalComponentType
130 {
131 return ICAL_VTODO_COMPONENT;
132 }
133 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "Task.h"
3 #import "StoreManager.h"
4
5 @interface TaskEditor : NSObject
6 {
7 id window;
8 id description;
9 id summary;
10 id store;
11 id state;
12 id ok;
13 }
14
15 - (BOOL)editTask:(Task *)data withStoreManager:(StoreManager *)sm;
16 - (void)validate:(id)sender;
17 - (void)cancel:(id)sender;
18
19 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <AppKit/AppKit.h>
3 #import "TaskEditor.h"
4
5 @implementation TaskEditor
6 -(id)init
7 {
8 self = [super init];
9 if (self) {
10 if (![NSBundle loadNibNamed:@"Task" owner:self])
11 return nil;
12 }
13 return self;
14 }
15
16 -(BOOL)editTask:(Task *)task withStoreManager:(StoreManager *)sm
17 {
18 NSEnumerator *list = [sm storeEnumerator];
19 id <MemoryStore> aStore;
20 id <MemoryStore> originalStore;
21 int ret;
22
23 [summary setStringValue:[task summary]];
24
25 [[description textStorage] deleteCharactersInRange:NSMakeRange(0, [[description textStorage] length])];
26 [[description textStorage] appendAttributedString:[task text]];
27
28 [window makeFirstResponder:summary];
29
30 originalStore = [task store];
31 if (!originalStore)
32 [task setStore:[sm defaultStore]];
33 else if (![originalStore writable])
34 [ok setEnabled:NO];
35
36 [store removeAllItems];
37 while ((aStore = [list nextObject])) {
38 if ([aStore writable] || aStore == originalStore)
39 [store addItemWithTitle:[aStore description]];
40 }
41 [store selectItemWithTitle:[[task store] description]];
42
43 [state removeAllItems];
44 [state addItemsWithTitles:[Task stateNamesArray]];
45 [state selectItemWithTitle:[task stateAsString]];
46
47 ret = [NSApp runModalForWindow:window];
48 [window close];
49 if (ret == NSOKButton) {
50 [task setSummary:[summary stringValue]];
51 [task setText:[[description textStorage] copy]];
52 [task setState:[state indexOfSelectedItem]];
53 aStore = [sm storeForName:[store titleOfSelectedItem]];
54 if (!originalStore)
55 [aStore add:task];
56 else if (originalStore == aStore)
57 [aStore update:task];
58 else {
59 [originalStore remove:task];
60 [aStore add:task];
61 }
62 return YES;
63 }
64 return NO;
65 }
66
67 -(void)validate:(id)sender
68 {
69 [NSApp stopModalWithCode: NSOKButton];
70 }
71 -(void)cancel:(id)sender
72 {
73 [NSApp stopModalWithCode: NSCancelButton];
74 }
75 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3 #import <GNUstepBase/GSXML.h>
4
5 @interface WebDAVResource : NSObject <NSURLHandleClient>
6 {
7 @private
8 NSURL *_url;
9 Class _handleClass;
10 NSLock *_lock;
11 BOOL _dataChanged;
12 NSURLHandleStatus _status;
13 int _httpStatus;
14 NSString *_reason;
15 NSString *_lastModified;
16 NSString *_etag;
17 NSString *_location;
18 NSString *_user;
19 NSString *_password;
20 BOOL _debug;
21 }
22
23 - (id)initWithURL:(NSURL *)url;
24 - (void)setDebug:(BOOL)debug;
25 - (BOOL)readable;
26 /* WARNING Destructive */
27 - (BOOL)writableWithData:(NSData *)data;
28 - (int)httpStatus;
29 - (NSString *)reason;
30 - (NSString *)location;
31 - (NSURLHandleStatus)status;
32 - (BOOL)dataChanged;
33 - (NSURL *)url;
34 - (NSData *)options;
35 - (NSData *)getWithAttributes:(NSDictionary *)attributes;
36 - (NSData *)get;
37 - (NSData *)put:(NSData *)data;
38 - (NSData *)put:(NSData *)data attributes:(NSDictionary *)attributes;
39 - (NSData *)delete;
40 - (NSData *)deleteWithAttributes:(NSDictionary *)attributes;
41 - (NSData *)propfind:(NSData *)data;
42 - (NSData *)propfind:(NSData *)data attributes:(NSDictionary *)attributes;
43 - (NSArray *)listICalItems;
44 - (void)updateAttributes;
45 - (void)setUser:(NSString *)user password:(NSString *)password;
46 @end
47
48 @interface NSURL(SimpleAgenda)
49 + (BOOL)stringIsValidURL:(NSString *)string;
50 + (NSURL *)URLWithString:(NSString *)string possiblyRelativeToURL:(NSURL *)base;
51 - (NSURL *)redirection;
52 @end
53
54 @interface GSXMLDocument(SimpleAgenda)
55 - (GSXMLDocument *)strippedDocument;
56 @end
0 #import <GNUstepBase/GSXML.h>
1 #import "GNUstepBase/GSMime.h"
2 #import "WebDAVResource.h"
3
4 @implementation WebDAVResource
5 - (void)dealloc
6 {
7 DESTROY(_user);
8 DESTROY(_password);
9 DESTROY(_lock);
10 DESTROY(_url);
11 DESTROY(_lastModified);
12 DESTROY(_location);
13 [super dealloc];
14 }
15
16 - (void)fixURLScheme
17 {
18 NSString *fixed;
19
20 if ([[_url scheme] hasPrefix:@"webcal"]) {
21 fixed = [[_url absoluteString] stringByReplacingString:@"webcal" withString:@"http"];
22 DESTROY(_url);
23 _url = [[NSURL alloc] initWithString:fixed];
24 }
25 }
26
27 - (id)initWithURL:(NSURL *)anUrl
28 {
29 self = [super init];
30 if (self) {
31 /* FIXME : this causes a bogus GET for every resource creation */
32 _url = [anUrl redirection];
33 [self fixURLScheme];
34 _handleClass = [NSURLHandle URLHandleClassForURL:_url];
35 _lock = [NSLock new];
36 _user = nil;
37 _password = nil;
38 _debug = NO;
39 }
40 return self;
41 }
42
43 - (void)setDebug:(BOOL)debug
44 {
45 _debug = debug;
46 }
47
48 /* FIXME : ugly hack to work around NSURLHandle shortcomings */
49 - (NSString *)basicAuth
50 {
51 NSMutableString *authorisation;
52 NSString *toEncode;
53
54 authorisation = [NSMutableString stringWithCapacity: 64];
55 if ([_password length] > 0)
56 toEncode = [NSString stringWithFormat: @"%@:%@", _user, _password];
57 else
58 toEncode = [NSString stringWithFormat: @"%@", _user];
59 [authorisation appendFormat: @"Basic %@", [GSMimeDocument encodeBase64String: toEncode]];
60 return authorisation;
61 }
62
63 - (NSData *)requestWithMethod:(NSString *)method body:(NSData *)body attributes:(NSDictionary *)attributes
64 {
65 NSEnumerator *keys;
66 NSString *key;
67 NSData *data;
68 NSString *property;
69 NSURLHandle *handle;
70
71 [_lock lock];
72 handle = [[_handleClass alloc] initWithURL:_url cached:NO];
73 [handle writeProperty:method forKey:GSHTTPPropertyMethodKey];
74 if (attributes) {
75 keys = [attributes keyEnumerator];
76 while ((key = [keys nextObject]))
77 [handle writeProperty:[attributes objectForKey:key] forKey:key];
78 }
79 if (_user && ![_url user])
80 [handle writeProperty:[self basicAuth] forKey:@"Authorization"];
81 if (_etag && ([method isEqual:@"PUT"] || [method isEqual:@"DELETE"]))
82 [handle writeProperty:[NSString stringWithFormat:@"([%@])", _etag] forKey:@"If"];
83 if (body)
84 [handle writeData:body];
85 if (_debug)
86 NSLog(@"%@ %@ (%@)", [_url absoluteString], method, [attributes description]);
87 data = RETAIN([handle resourceData]);
88 _status = [handle status];
89 _httpStatus = [[handle propertyForKeyIfAvailable:NSHTTPPropertyStatusCodeKey] intValue];
90 if (_debug) {
91 if (data)
92 NSLog(@"%@ =>\n%@", method, AUTORELEASE([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]));
93 else
94 NSLog(@"%@ status %d", method, _httpStatus);
95 }
96 property = [handle propertyForKeyIfAvailable:NSHTTPPropertyStatusReasonKey];
97 if (property)
98 ASSIGNCOPY(_reason, property);
99 else
100 DESTROY(_reason);
101
102 if ([method isEqual:@"GET"]) {
103 property = [handle propertyForKeyIfAvailable:@"Last-Modified"];
104 if (!_lastModified || (property && ![property isEqual:_lastModified])) {
105 _dataChanged = YES;
106 ASSIGNCOPY(_lastModified, property);
107 }
108 property = [handle propertyForKeyIfAvailable:@"ETag"];
109 if (!_etag || (property && ![property isEqual:_etag])) {
110 _dataChanged = YES;
111 ASSIGNCOPY(_etag, property);
112 }
113 }
114 if ([method isEqual:@"PUT"]) {
115 property = [handle propertyForKeyIfAvailable:@"Location"];
116 if (property) {
117 ASSIGNCOPY(_location, property);
118 if (_debug)
119 NSLog(@"Location: %@", _location);
120 } else
121 DESTROY(_location);
122 }
123 [handle release];
124 [_lock unlock];
125 return data;
126 }
127
128 /*
129 * Status | Meaning
130 * 200 | OK
131 * 207 | MULTI STATUS
132 * 304 | NOT MODIFIED
133 * 401 | NO AUTH
134 * 403 | WRONG PERM
135 * 404 | NO FILE
136 * ...
137 */
138 - (BOOL)readable
139 {
140 NSData *data;
141
142 data = [self get];
143 if (data) {
144 [data release];
145 if ((_httpStatus > 199 && _httpStatus < 300) || _httpStatus == 404)
146 return YES;
147 }
148 return NO;
149 }
150
151 /*
152 * Status | Meaning
153 * 201 | OK OVERWRITE
154 * 204 | OK CREATE
155 * 401 | NO AUTH
156 * 403 | WRONG PERM
157 * ...
158 */
159 - (BOOL)writableWithData:(NSData *)data
160 {
161 NSData *read;
162
163 read = [self put:data];
164 [read release];
165 if (_httpStatus > 199 && _httpStatus < 300)
166 return YES;
167 return NO;
168 }
169
170 - (int)httpStatus
171 {
172 return _httpStatus;
173 }
174
175 - (NSString *)reason
176 {
177 return _reason;
178 }
179
180 - (NSString *)location
181 {
182 return _location;
183 }
184
185 - (NSURLHandleStatus)status
186 {
187 return _status;
188 }
189
190 - (BOOL)dataChanged
191 {
192 return _dataChanged;
193 }
194
195 - (NSURL *)url
196 {
197 return _url;
198 }
199
200 - (NSData *)options
201 {
202 return [self requestWithMethod:@"OPTIONS" body:nil attributes:nil];
203 }
204
205 - (NSData *)getWithAttributes:(NSDictionary *)attributes
206 {
207 return [self requestWithMethod:@"GET" body:nil attributes:attributes];
208 }
209
210 - (NSData *)get
211 {
212 return [self requestWithMethod:@"GET" body:nil attributes:nil];
213 }
214
215 /* FIXME : change put and delete into void methods */
216 - (NSData *)put:(NSData *)data
217 {
218 return [self requestWithMethod:@"PUT" body:data attributes:nil];
219 }
220
221 - (NSData *)put:(NSData *)data attributes:(NSDictionary *)attributes
222 {
223 return [self requestWithMethod:@"PUT" body:data attributes:attributes];
224 }
225
226 - (NSData *)delete
227 {
228 return [self requestWithMethod:@"DELETE" body:nil attributes:nil];
229 }
230
231 - (NSData *)deleteWithAttributes:(NSDictionary *)attributes
232 {
233 return [self requestWithMethod:@"DELETE" body:nil attributes:attributes];
234 }
235
236 - (NSData *)propfind:(NSData *)data
237 {
238 return [self requestWithMethod:@"PROPFIND" body:data attributes:nil];
239 }
240
241 - (NSData *)propfind:(NSData *)data attributes:(NSDictionary *)attributes
242 {
243 return [self requestWithMethod:@"PROPFIND" body:data attributes:attributes];
244 }
245
246 - (NSArray *)listICalItems
247 {
248 int i;
249 NSString *body = @"<?xml version=\"1.0\" encoding=\"utf-8\"?><propfind xmlns=\"DAV:\"><prop><getetag/></prop></propfind>";
250 GSXMLParser *parser;
251 NSData *propfind;
252 NSMutableArray *result;
253 GSXPathContext *xpc;
254 GSXPathNodeSet *set;
255 NSURL *elementURL;
256
257 result = [NSMutableArray new];
258 propfind = [self propfind:[body dataUsingEncoding:NSUTF8StringEncoding] attributes:[NSDictionary dictionaryWithObject:@"1" forKey:@"Depth"]];
259 parser = [GSXMLParser parserWithData:propfind];
260 if ([parser parse]) {
261 if (_debug)
262 NSLog(@"%s xml document \n%@", __PRETTY_FUNCTION__, [[[parser document] strippedDocument] description]);
263 xpc = [[GSXPathContext alloc] initWithDocument:[[parser document] strippedDocument]];
264 set = (GSXPathNodeSet *)[xpc evaluateExpression:@"//response[propstat/prop/getetag]/href/text()"];
265 if (_debug)
266 NSLog(@"found %d ical item(s)", [set count]);
267 for (i = 0; i < [set count]; i++) {
268 elementURL = [NSURL URLWithString:[[set nodeAtIndex:i] content] possiblyRelativeToURL:_url];
269 if (elementURL) {
270 [result addObject:[elementURL absoluteString]];
271 if (_debug)
272 NSLog([elementURL absoluteString]);
273 }
274 }
275 [xpc release];
276 }
277 [propfind release];
278 return result;
279 }
280
281 static NSString *GETETAG = @"string(/multistatus/response/propstat/prop/getetag/text())";
282 static NSString *GETLASTMODIFIED = @"string(/multistatus/response/propstat/prop/getlastmodified/text())";
283 - (void)updateAttributes;
284 {
285 NSData *propfind = [self propfind:nil];
286 GSXMLParser *parser;
287 GSXPathContext *xpc;
288 GSXPathString *result;
289
290 if (propfind) {
291 parser = [GSXMLParser parserWithData:propfind];
292 if ([parser parse]) {
293 xpc = [[GSXPathContext alloc] initWithDocument:[[parser document] strippedDocument]];
294 result = (GSXPathString *)[xpc evaluateExpression:GETETAG];
295 if (result)
296 ASSIGNCOPY(_etag, [result stringValue]);
297 result = (GSXPathString *)[xpc evaluateExpression:GETLASTMODIFIED];
298 if (result)
299 ASSIGNCOPY(_lastModified, [result stringValue]);
300 }
301 [parser release];
302 [propfind release];
303 }
304 }
305
306 - (void)setUser:(NSString *)user password:(NSString *)password
307 {
308 ASSIGNCOPY(_user, user);
309 ASSIGNCOPY(_password, password);
310 }
311
312 - (void)URLHandle:(NSURLHandle *)sender resourceDataDidBecomeAvailable:(NSData *)newData
313 {
314 }
315 - (void)URLHandle:(NSURLHandle *)sender resourceDidFailLoadingWithReason:(NSString *)reason
316 {
317 }
318 - (void)URLHandleResourceDidBeginLoading:(NSURLHandle *)sender
319 {
320 }
321 - (void)URLHandleResourceDidCancelLoading:(NSURLHandle *)sender
322 {
323 }
324 - (void)URLHandleResourceDidFinishLoading:(NSURLHandle *)sender
325 {
326 }
327 @end
328
329 @implementation NSURL(SimpleAgenda)
330 + (BOOL)stringIsValidURL:(NSString *)string
331 {
332 BOOL valid = NO;
333 NSURL *url;
334
335 NS_DURING
336 {
337 url = [NSURL URLWithString:string];
338 valid = url ? YES : NO;
339 }
340 NS_HANDLER
341 {
342 }
343 NS_ENDHANDLER
344 return valid;
345 }
346 + (NSURL *)URLWithString:(NSString *)string possiblyRelativeToURL:(NSURL *)base
347 {
348 NSURL *url;
349
350 if ([NSURL stringIsValidURL:string])
351 url = [NSURL URLWithString:string];
352 else
353 url = [NSURL URLWithString:[[base absoluteString] stringByReplacingString:[base path] withString:string]];
354 return url;
355 }
356 - (NSURL *)redirection
357 {
358 NSString *location;
359
360 location = [self propertyForKey:@"Location"];
361 if (location) {
362 NSLog(@"Redirected to %@", location);
363 return [[NSURL URLWithString:location] redirection];
364 }
365 return [self copy];
366 }
367 @end
368
369 /* FIXME : move this method to GSXMLParser ? */
370 @implementation GSXMLDocument(SimpleAgenda)
371 static GSXMLDocument *removeXSLT;
372 static const NSString *removeString = @"<?xml version='1.0' encoding='UTF-8'?> \
373 <xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'> \
374 <xsl:output method='xml' encoding='UTF-8' /> \
375 <xsl:template match='/'> \
376 <xsl:copy> \
377 <xsl:apply-templates /> \
378 </xsl:copy> \
379 </xsl:template> \
380 <xsl:template match='*'> \
381 <xsl:element name='{local-name()}'> \
382 <xsl:apply-templates select='@* | node()' /> \
383 </xsl:element> \
384 </xsl:template> \
385 <xsl:template match='@*'> \
386 <xsl:attribute name='{local-name()}'><xsl:value-of select='.' /></xsl:attribute> \
387 </xsl:template> \
388 <xsl:template match='text() | processing-instruction() | comment()'> \
389 <xsl:copy /> \
390 </xsl:template> \
391 </xsl:stylesheet>";
392 - (GSXMLDocument *)strippedDocument
393 {
394 if (removeXSLT == nil) {
395 GSXMLParser *parser = [GSXMLParser parserWithData:[removeString dataUsingEncoding:NSUTF8StringEncoding]];
396 if (![parser parse]) {
397 NSLog(@"Error parsing xslt document");
398 return nil;
399 }
400 removeXSLT = RETAIN([parser document]);
401 }
402 return [self xsltTransform:removeXSLT];
403 }
404 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #define FIRST_HOUR @"firstHour"
3 #define LAST_HOUR @"lastHour"
4 #define MIN_STEP @"minimumStep"
5
6 #define STORE_CLASSES @"storeClasses"
7
8 #define STORES @"stores"
9 #define ST_DEFAULT @"defaultStore"
10 #define ST_CLASS @"storeClass"
11 #define ST_FILE @"storeFilename"
12 #define ST_COLOR @"storeColor"
13 #define ST_TEXT_COLOR @"storeTextColor"
14 #define ST_URL @"storeURL"
15 #define ST_CALENDAR_URL @"storeCalendarURL"
16 #define ST_TASK_URL @"storeTaskURL"
17 #define ST_RW @"storeWritable"
18 #define ST_REFRESH @"storeRefresh"
19 #define ST_DISPLAY @"storeDisplay"
20 #define ST_VERSION @"storeVersion"
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import "MemoryStore.h"
3 #import "iCalTree.h"
4 #import "WebDAVResource.h"
5
6 @interface iCalStore : MemoryStore <AgendaStore>
7 {
8 iCalTree *_tree;
9 NSURL *_url;
10 int _minutesBeforeRefresh;
11 NSTimer *_refreshTimer;
12 WebDAVResource *_resource;
13 }
14 @end
0 #import <Foundation/Foundation.h>
1 #import "Event.h"
2 #import "Task.h"
3 #import "iCalStore.h"
4 #import "WebDAVResource.h"
5 #import "defines.h"
6
7 @interface iCalStoreDialog : NSObject
8 {
9 IBOutlet id panel;
10 IBOutlet id name;
11 IBOutlet id url;
12 IBOutlet id ok;
13 IBOutlet id error;
14 IBOutlet id warning;
15 }
16 - (BOOL)show;
17 - (NSString *)url;
18 @end
19 @implementation iCalStoreDialog
20 - (id)initWithName:(NSString *)storeName
21 {
22 self = [super init];
23 if (self) {
24 if (![NSBundle loadNibNamed:@"iCalendar" owner:self])
25 return nil;
26 [warning setHidden:YES];
27 [name setStringValue:storeName];
28 [url setStringValue:@"http://"];
29 }
30 return self;
31 }
32 - (void)dealloc
33 {
34 [panel close];
35 [super dealloc];
36 }
37 - (BOOL)show
38 {
39 [ok setEnabled:NO];
40 return [NSApp runModalForWindow:panel];
41 }
42 - (void)okClicked:(id)sender
43 {
44 NSURL *tmp;
45 WebDAVResource *resource;
46
47 tmp = [NSURL URLWithString:[url stringValue]];
48 resource = AUTORELEASE([[WebDAVResource alloc] initWithURL:tmp]);
49 if ([resource readable])
50 [NSApp stopModalWithCode:1];
51 else {
52 [error setStringValue:[NSString stringWithFormat:@"Unable to read from this URL : %@", [tmp propertyForKey:NSHTTPPropertyStatusReasonKey]]];
53 [warning setHidden:NO];
54 }
55 }
56 - (void)cancelClicked:(id)sender
57 {
58 [NSApp stopModalWithCode:0];
59 }
60 - (void)controlTextDidChange:(NSNotification *)notification
61 {
62 NS_DURING
63 {
64 [ok setEnabled:[NSURL stringIsValidURL:[url stringValue]]];
65 }
66 NS_HANDLER
67 {
68 [ok setEnabled:NO];
69 }
70 NS_ENDHANDLER
71 }
72 - (NSString *)url
73 {
74 return [url stringValue];
75 }
76 @end
77
78 @interface iCalStore(Private)
79 - (void)fetchData:(id)object;
80 - (void)parseData:(NSData *)data;
81 - (void)initTimer:(id)object;
82 - (void)initStoreAsync:(id)object;
83 @end
84
85 @implementation iCalStore
86 - (NSDictionary *)defaults
87 {
88 return [NSDictionary dictionaryWithObjectsAndKeys:
89 [NSArchiver archivedDataWithRootObject:[NSColor blueColor]], ST_COLOR,
90 [NSArchiver archivedDataWithRootObject:[NSColor darkGrayColor]], ST_TEXT_COLOR,
91 [NSNumber numberWithBool:NO], ST_RW,
92 [NSNumber numberWithBool:YES], ST_DISPLAY,
93 nil, nil];
94 }
95
96 - (id)initWithName:(NSString *)name
97 {
98 self = [super initWithName:name];
99 if (self) {
100 _tree = [iCalTree new];
101 [NSThread detachNewThreadSelector:@selector(initStoreAsync:) toTarget:self withObject:nil];
102 }
103 return self;
104 }
105
106 + (BOOL)registerWithName:(NSString *)name
107 {
108 ConfigManager *cm;
109 iCalStoreDialog *dialog;
110 NSURL *storeURL;
111 BOOL writable = NO;
112 WebDAVResource *resource;
113 NSData *data;
114
115 dialog = [[iCalStoreDialog alloc] initWithName:name];
116 if ([dialog show] == YES) {
117 storeURL = [NSURL URLWithString:[dialog url]];
118 resource = [[WebDAVResource alloc] initWithURL:storeURL];
119 data = [resource get];
120 writable = NO;
121 if (data) {
122 writable = [resource writableWithData:data];
123 [data release];
124 }
125 [resource release];
126 [dialog release];
127 cm = [[ConfigManager alloc] initForKey:name withParent:nil];
128 [cm setObject:[storeURL description] forKey:ST_URL];
129 [cm setObject:[[self class] description] forKey:ST_CLASS];
130 [cm setObject:[NSNumber numberWithBool:writable] forKey:ST_RW];
131 return YES;
132 }
133 [dialog release];
134 return NO;
135 }
136
137 + (NSString *)storeTypeName
138 {
139 return @"iCalendar store";
140 }
141
142 - (void)dealloc
143 {
144 [_refreshTimer invalidate];
145 [self write];
146 DESTROY(_resource);
147 DESTROY(_url);
148 DESTROY(_tree);
149 [super dealloc];
150 }
151
152 - (void)refreshData:(NSTimer *)timer
153 {
154 [self read];
155 }
156
157 - (void)add:(Element *)elt
158 {
159 if ([_tree add:elt]) {
160 [super add:elt];
161 if (![_url isFileURL])
162 [self write];
163 }
164 }
165
166 - (void)remove:(Element *)elt
167 {
168 if ([_tree remove:elt]) {
169 [super remove:elt];
170 if (![_url isFileURL])
171 [self write];
172 }
173 }
174
175 - (void)update:(Element *)elt
176 {
177 if ([_tree update:(Event *)elt]) {
178 [super update:elt];
179 if (![_url isFileURL])
180 [self write];
181 }
182 }
183
184 - (BOOL)read
185 {
186 [self fetchData:nil];
187 return [_resource dataChanged];
188 }
189
190 - (BOOL)write
191 {
192 NSData *data;
193 NSData *read;
194
195 if (![self modified] || ![self writable])
196 return YES;
197 data = [_tree iCalTreeAsData];
198 if (data) {
199 read = [_resource put:data];
200 DESTROY(read);
201 if ([_resource status] == NSURLHandleLoadSucceeded) {
202 [_resource updateAttributes];
203 [self setModified:NO];
204 NSLog(@"iCalStore written to %@", [_url absoluteString]);
205 return YES;
206 }
207 if ([_resource httpStatus] == 412) {
208 NSRunAlertPanel(@"Error : data source modified", @"To prevent losing modifications, this agenda\nwill be updated and marked as read-only. ", @"Ok", nil, nil);
209 [self read];
210 }
211 NSLog(@"Unable to write to %@, make this store read only", [_url absoluteString]);
212 [self setWritable:NO];
213 return NO;
214 }
215 return YES;
216 }
217 @end
218
219
220 @implementation iCalStore(Private)
221 - (void)fetchData:(id)object
222 {
223 [self parseData:AUTORELEASE([_resource get])];
224 }
225 - (void)parseData:(NSData *)data
226 {
227 if ([_tree parseData:data]) {
228 [self fillWithElements:[_tree components]];
229 NSLog(@"iCalStore from %@ : loaded %d appointment(s)", [_url absoluteString], [[self events] count]);
230 NSLog(@"iCalStore from %@ : loaded %d tasks(s)", [_url absoluteString], [[self tasks] count]);
231 } else
232 NSLog(@"Couldn't parse data from %@", [_url absoluteString]);
233 }
234 - (void)initTimer:(id)object
235 {
236 if ([_config objectForKey:ST_REFRESH])
237 _minutesBeforeRefresh = [_config integerForKey:ST_REFRESH];
238 else
239 _minutesBeforeRefresh = 30;
240 _refreshTimer = [[NSTimer alloc] initWithFireDate:nil
241 interval:_minutesBeforeRefresh * 60
242 target:self selector:@selector(refreshData:)
243 userInfo:nil repeats:YES];
244 [[NSRunLoop currentRunLoop] addTimer:_refreshTimer forMode:NSDefaultRunLoopMode];
245 }
246 - (void)initStoreAsync:(id)object
247 {
248 NSAutoreleasePool *pool = [NSAutoreleasePool new];
249 _url = [[NSURL alloc] initWithString:[_config objectForKey:ST_URL]];
250 _resource = [[WebDAVResource alloc] initWithURL:_url];
251 [self performSelectorOnMainThread:@selector(fetchData:) withObject:nil waitUntilDone:YES];
252 [self performSelectorOnMainThread:@selector(initTimer:) withObject:nil waitUntilDone:YES];
253 [pool release];
254 }
255 @end
0 /* emacs buffer mode hint -*- objc -*- */
1
2 #import <Foundation/Foundation.h>
3 #import "Element.h"
4 #import "ical.h"
5
6 @interface iCalTree : NSObject
7 {
8 icalcomponent *root;
9 }
10
11 - (BOOL)parseString:(NSString *)string;
12 - (BOOL)parseData:(NSData *)data;
13 - (NSString *)iCalTreeAsString;
14 - (NSData *)iCalTreeAsData;
15 - (NSSet *)components;
16 - (BOOL)add:(Element *)event;
17 - (BOOL)remove:(Element *)event;
18 - (BOOL)update:(Element *)event;
19 @end
20
0 #import "iCalTree.h"
1 #import "Event.h"
2 #import "Task.h"
3
4 @implementation iCalTree
5
6 - (id)init
7 {
8 self = [super init];
9 if (self) {
10 root = icalcomponent_vanew(ICAL_VCALENDAR_COMPONENT,
11 icalproperty_new_version("1.0"),
12 icalproperty_new_prodid("-//Octets//NONSGML SimpleAgenda Calendar//EN"),
13 0);
14 if (!root) {
15 [self release];
16 return nil;
17 }
18 }
19 return self;
20 }
21
22 - (void)dealloc
23 {
24 [super dealloc];
25 if (root)
26 icalcomponent_free(root);
27 }
28
29 - (BOOL)parseString:(NSString *)string;
30 {
31 icalcomponent *icomp;
32
33 icomp = icalparser_parse_string([string cStringUsingEncoding:NSUTF8StringEncoding]);
34 if (icomp) {
35 if (root)
36 icalcomponent_free(root);
37 root = icomp;
38 return YES;
39 }
40 return NO;
41 }
42
43 - (BOOL)parseData:(NSData *)data
44 {
45 NSString *text;
46
47 text = AUTORELEASE([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
48 return [self parseString:text];
49 }
50
51 - (NSString *)iCalTreeAsString
52 {
53 /* OGo workaround ? */
54 icalproperty *prop = icalcomponent_get_first_property(root, ICAL_METHOD_PROPERTY);
55 if (prop)
56 icalcomponent_remove_property(root, prop);
57 icalcomponent_strip_errors(root);
58 return [NSString stringWithUTF8String:icalcomponent_as_ical_string(root)];
59 }
60
61 - (NSData *)iCalTreeAsData;
62 {
63 return [[self iCalTreeAsString] dataUsingEncoding:NSUTF8StringEncoding];
64 }
65
66 - (NSSet *)components
67 {
68 icalcomponent *ic;
69 Event *ev;
70 Task *task;
71 NSMutableSet *work = [NSMutableSet setWithCapacity:32];
72
73 for (ic = icalcomponent_get_first_component(root, ICAL_VEVENT_COMPONENT);
74 ic != NULL; ic = icalcomponent_get_next_component(root, ICAL_VEVENT_COMPONENT)) {
75 ev = [[Event alloc] initWithICalComponent:ic];
76 if (ev) {
77 [work addObject:ev];
78 [ev release];
79 }
80 }
81 for (ic = icalcomponent_get_first_component(root, ICAL_VTODO_COMPONENT);
82 ic != NULL; ic = icalcomponent_get_next_component(root, ICAL_VTODO_COMPONENT)) {
83 task = [[Task alloc] initWithICalComponent:ic];
84 if (task) {
85 [work addObject:task];
86 [task release];
87 }
88 }
89 return [NSSet setWithSet:work];
90 }
91
92 - (icalcomponent *)componentForEvent:(Element *)elt
93 {
94 NSString *uid = [elt UID];
95 icalcomponent *ic;
96 icalproperty *prop;
97 int type = [elt iCalComponentType];
98
99 for (ic = icalcomponent_get_first_component(root, type);
100 ic != NULL; ic = icalcomponent_get_next_component(root, type)) {
101 prop = icalcomponent_get_first_property(ic, ICAL_UID_PROPERTY);
102 if (prop) {
103 if ([uid isEqual:[NSString stringWithCString:icalproperty_get_uid(prop)]])
104 return ic;
105 }
106 }
107 NSLog(@"iCalendar component not found for %@", [elt description]);
108 return NULL;
109 }
110
111 - (BOOL)add:(Element *)elt
112 {
113 icalcomponent *ic = [elt asICalComponent];
114 if (!ic)
115 return NO;
116 icalcomponent_add_component(root, ic);
117 return YES;
118 }
119
120 - (BOOL)remove:(Element *)elt
121 {
122 icalcomponent *ic = [self componentForEvent:elt];
123 if (!ic)
124 return NO;
125 icalcomponent_remove_component(root, ic);
126 return YES;
127 }
128
129 - (BOOL)update:(Element *)elt
130 {
131 icalcomponent *ic = [self componentForEvent:elt];
132 if (!ic)
133 return NO;
134 return [elt updateICalComponent:ic];
135 }
136 @end