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
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 |
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 | }⏎ |
Binary diff not shown
Binary diff not shown
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
Binary diff not shown
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 | }⏎ |
Binary diff not shown
Binary diff not shown
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 | }⏎ |
Binary diff not shown
Binary diff not shown
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 | }⏎ |
Binary diff not shown
Binary diff not shown
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
Binary diff not shown
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 |