]> err.no Git - scalable-opengroupware.org/blobdiff - UI/Scheduler/UIxAppointmentEditor.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1045 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / Scheduler / UIxAppointmentEditor.m
index d0a12451c419c5c953c8a1d52734524658467f5c..3f1fa9b0718ed522d7410808ea1b2d783f9dfb93 100644 (file)
-/*
-  Copyright (C) 2004-2005 SKYRIX Software AG
-
-  This file is part of OpenGroupware.org.
-
-  OGo is free software; you can redistribute it and/or modify it under
-  the terms of the GNU Lesser General Public License as published by the
-  Free Software Foundation; either version 2, or (at your option) any
-  later version.
-
-  OGo is distributed in the hope that it will be useful, but WITHOUT ANY
-  WARRANTY; without even the implied warranty of MERCHANTABILITY or
-  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
-  License for more details.
-
-  You should have received a copy of the GNU Lesser General Public
-  License along with OGo; see the file COPYING.  If not, write to the
-  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
-  02111-1307, USA.
-*/
-
-#include <SOGoUI/UIxComponent.h>
-
-/* TODO: CLEAN UP */
-
-@class NSString;
-@class iCalPerson;
-@class iCalRecurrenceRule;
-@class SOGoAppointment;
-
-@interface UIxAppointmentEditor : UIxComponent
-{
-  NSString *iCalString;
-  NSString *errorText;
-  id item;
-  
-  /* individual values */
-  NSCalendarDate *startDate;
-  NSCalendarDate *endDate;
-  NSCalendarDate *cycleUntilDate;
-  NSString       *title;
-  NSString       *location;
-  NSString       *comment;
-  iCalPerson     *organizer;
-  NSArray        *participants;     /* array of iCalPerson's */
-  NSArray        *resources;        /* array of iCalPerson's */
-  NSString       *priority;
-  NSArray        *categories;
-  NSString       *accessClass;
-  BOOL           isPrivate;         /* default: NO */
-  BOOL           checkForConflicts; /* default: NO */
-  NSDictionary   *cycle;
-  NSString       *cycleEnd;
-}
-
-- (NSString *)iCalStringTemplate;
-- (NSString *)iCalString;
-
-- (void)setIsPrivate:(BOOL)_yn;
-- (void)setAccessClass:(NSString *)_class;
-
-- (void)setCheckForConflicts:(BOOL)_checkForConflicts;
-- (BOOL)checkForConflicts;
-
-- (BOOL)hasCycle;
-- (iCalRecurrenceRule *)rrule;
-- (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule;
-- (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule;
-
-- (BOOL)isCycleEndUntil;
-- (void)setIsCycleEndUntil;
-- (void)setIsCycleEndNever;
-
-- (NSString *)_completeURIForMethod:(NSString *)_method;
-
-- (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
-  treatAsResource:(BOOL)_isResource;
-
-- (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters;
-- (NSString *)iCalParticipantsStringFromQueryParameters;
-- (NSString *)iCalResourcesStringFromQueryParameters;
-- (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
-              format:(NSString *)_format;
-- (NSString *)iCalOrganizerString;
+/* UIxAppointmentEditor.m - this file is part of SOGo
+ *
+ * Copyright (C) 2007 Inverse groupe conseil
+ *
+ * Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ *
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; see the file COPYING.  If not, write to
+ * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
 
-- (id)acceptOrDeclineAction:(BOOL)_accept;
+#import <NGObjWeb/SoObject.h>
+#import <NGObjWeb/WORequest.h>
+#import <NGObjWeb/NSException+HTTP.h>
+#import <NGExtensions/NSCalendarDate+misc.h>
 
-@end
+#import <NGCards/iCalEvent.h>
+#import <NGCards/iCalPerson.h>
 
-#include "common.h"
-#include <NGiCal/NGiCal.h>
-#include <NGExtensions/NGCalendarDateRange.h>
-#include <SOGoUI/SOGoDateFormatter.h>
-#include <SOGo/SOGoAppointment.h>
-#include <SOGo/AgenorUserManager.h>
-#include <Appointments/SOGoAppointmentFolder.h>
-#include <Appointments/SOGoAppointmentObject.h>
-#include "iCalPerson+UIx.h"
-#include "UIxComponent+Agenor.h"
-
-@interface iCalRecurrenceRule (SOGoExtensions)
-- (NSString *)cycleRepresentationForSOGo;
-@end
+#import <SoObjects/SOGo/AgenorUserManager.h>
+#import <SoObjects/SOGo/SOGoUser.h>
+#import <SoObjects/SOGo/SOGoContentObject.h>
+#import <SoObjects/Appointments/SOGoAppointmentFolder.h>
+#import <SoObjects/Appointments/SOGoAppointmentObject.h>
 
-@interface NSDate(UsedPrivates)
-- (NSString *)icalString; // TODO: this is in NGiCal
-@end
+#import "UIxComponentEditor.h"
+#import "UIxAppointmentEditor.h"
 
 @implementation UIxAppointmentEditor
 
-+ (int)version {
-  return [super version] + 0 /* v2 */;
-}
-
-+ (void)initialize {
-  NSAssert2([super version] == 2,
-            @"invalid superclass (%@) version %i !",
-            NSStringFromClass([self superclass]), [super version]);
-}
+- (id) init
+{
+  if ((self = [super init]))
+    {
+      aptStartDate = nil;
+      aptEndDate = nil;
+      item = nil;
+      event = nil;
+    }
 
-- (id)init {
-  self = [super init];
-  if(self) {
-    [self setIsPrivate:NO];
-    [self setCheckForConflicts:NO];
-    [self setIsCycleEndNever];
-  }
   return self;
 }
 
-- (void)dealloc {
-  [self->iCalString     release];
-  [self->errorText      release];
-  [self->item           release];
-
-  [self->startDate      release];
-  [self->endDate        release];
-  [self->cycleUntilDate release];
-  [self->title          release];
-  [self->location       release];
-  [self->organizer      release];
-  [self->comment        release];
-  [self->participants   release];
-  [self->resources      release];
-  [self->priority       release];
-  [self->categories     release];
-  [self->accessClass    release];
-  [self->cycle          release];
-  [self->cycleEnd      release];
-  [super dealloc];
-}
-
-/* accessors */
-
-- (void)setItem:(id)_item {
-  ASSIGN(self->item, _item);
-}
-- (id)item {
-  return self->item;
-}
-
-- (void)setErrorText:(NSString *)_txt {
-  ASSIGNCOPY(self->errorText, _txt);
-}
-- (NSString *)errorText {
-  return self->errorText;
-}
-- (BOOL)hasErrorText {
-  return [self->errorText length] > 0 ? YES : NO;
+/* template values */
+- (iCalEvent *) event
+{
+  return event;
 }
 
-- (NSFormatter *)titleDateFormatter {
-  SOGoDateFormatter *fmt;
-  
-  fmt = [[[SOGoDateFormatter alloc] initWithLocale:[self locale]] autorelease];
-  [fmt setFullWeekdayNameAndDetails];
-  return fmt;
+- (NSString *) saveURL
+{
+  return [NSString stringWithFormat: @"%@/saveAsAppointment",
+                   [[self clientObject] baseURL]];
 }
 
-- (void)setAptStartDate:(NSCalendarDate *)_date {
-  ASSIGN(self->startDate, _date);
-}
-- (NSCalendarDate *)aptStartDate {
-  return self->startDate;
-}
-- (void)setAptEndDate:(NSCalendarDate *)_date {
-  ASSIGN(self->endDate, _date);
-}
-- (NSCalendarDate *)aptEndDate {
-  return self->endDate;
-}
+- (NSString *) _toolbarForCalObject
+{
+  SOGoUser *currentUser;
+  SOGoAppointmentObject *clientObject;
+  NSString *filename, *email;
+  iCalPerson *person;
+  iCalPersonPartStat participationStatus;
+
+  clientObject = [self clientObject];
+  currentUser = [[self context] activeUser];
+  email = [currentUser email];
+  if ([clientObject isOrganizer: email
+                    orOwner: [currentUser login]])
+    filename = @"SOGoAppointmentObject.toolbar";
+  else
+    {
+      if ([clientObject isParticipant: email])
+        {
+          person = [[clientObject component: NO] findParticipantWithEmail: email];
+          participationStatus = [person participationStatus];
+          if (participationStatus == iCalPersonPartStatAccepted)
+            filename = @"SOGoAppointmentObjectDecline.toolbar";
+          else if (participationStatus == iCalPersonPartStatDeclined)
+            filename = @"SOGoAppointmentObjectAccept.toolbar";
+          else
+            filename = @"SOGoAppointmentObjectAcceptOrDecline.toolbar";
+        }
+      else
+        filename = @"SOGoComponentClose.toolbar";
+    }
 
-- (void)setTitle:(NSString *)_value {
-  ASSIGNCOPY(self->title, _value);
-}
-- (NSString *)title {
-  return self->title;
-}
-- (void)setLocation:(NSString *)_value {
-  ASSIGNCOPY(self->location, _value);
-}
-- (NSString *)location {
-  return self->location;
-}
-- (void)setComment:(NSString *)_value {
-  ASSIGNCOPY(self->comment, _value);
-}
-- (NSString *)comment {
-  return self->comment;
+  return filename;
 }
 
-- (void)setParticipants:(NSArray *)_parts {
-  ASSIGN(self->participants, _parts);
-}
-- (NSArray *)participants {
-  return self->participants;
-}
-- (void)setResources:(NSArray *)_res {
-  ASSIGN(self->resources, _res);
-}
-- (NSArray *)resources {
-  return self->resources;
+- (NSString *) toolbar
+{
+  return ([self _toolbarForCalObject]);
 }
 
-/* priorities */
-
-- (NSArray *)priorities {
-  /* 0 == undefined
-     5 == normal
-     1 == high
-  */
-  static NSArray *priorities = nil;
-
-  if (!priorities)
-    priorities = [[NSArray arrayWithObjects:@"0", @"5", @"1", nil] retain];
-  return priorities;
+/* icalendar values */
+- (BOOL) isAllDay
+{
+  return NO;
 }
 
-- (NSString *)itemPriorityText {
-  NSString *key;
-  
-  key = [NSString stringWithFormat:@"prio_%@", self->item];
-  return [self labelForKey:key];
+- (void) setIsAllDay: (BOOL) newIsAllDay
+{
 }
 
-- (void)setPriority:(NSString *)_priority {
-  ASSIGN(self->priority, _priority);
-}
-- (NSString *)priority {
-  return self->priority;
+- (void) setAptStartDate: (NSCalendarDate *) newAptStartDate
+{
+  ASSIGN (aptStartDate, newAptStartDate);
 }
 
-
-/* categories */
-
-- (NSArray *)categoryItems {
-  // TODO: make this configurable?
-  /*
-   Tasks categories will be modified as follow :
-   – by default (a simple logo or no logo at all),
-   – appointment,
-   – outside,
-   – meeting,
-   – holidays,
-   – phone.
-  */
-  static NSArray *categoryItems = nil;
-  
-  if (!categoryItems) {
-    categoryItems = [[NSArray arrayWithObjects:@"APPOINTMENT",
-                                               @"NOT IN OFFICE",
-                                               @"MEETING",
-                                               @"HOLIDAY",
-                                               @"PHONE CALL",
-                                               nil] retain];
-  }
-  return categoryItems;
+- (NSCalendarDate *) aptStartDate
+{
+  return aptStartDate;
 }
 
-- (NSString *)itemCategoryText {
-  return [self labelForKey:self->item];
+- (void) setAptEndDate: (NSCalendarDate *) newAptEndDate
+{
+  ASSIGN (aptEndDate, newAptEndDate);
 }
 
-- (void)setCategories:(NSArray *)_categories {
-  ASSIGN(self->categories, _categories);
-}
-- (NSArray *)categories {
-  return self->categories;
+- (NSCalendarDate *) aptEndDate
+{
+  return aptEndDate;
 }
 
-/* class */
+- (NSArray *) repeatList
+{
+  static NSArray *repeatItems = nil;
+
+  if (!repeatItems)
+    {
+      repeatItems = [NSArray arrayWithObjects: @"DAILY",
+                             @"WEEKLY",
+                             @"BI-WEEKLY",
+                             @"EVERY WEEKDAY",
+                             @"MONTHLY",
+                             @"YEARLY",
+                             @"-",
+                             @"CUSTOM",
+                             nil];
+      [repeatItems retain];
+    }
 
-#if 0
-- (NSArray *)accessClassItems {
-  static NSArray classItems = nil;
-  
-  if (!classItems) {
-    return [[NSArray arrayWithObjects:@"PUBLIC", @"PRIVATE", nil] retain];
-  }
-  return classItems;
+  return repeatItems;
 }
-#endif
 
-- (void)setAccessClass:(NSString *)_class {
-  ASSIGN(self->accessClass, _class);
-}
-- (NSString *)accessClass {
-  return self->accessClass;
-}
+- (NSString *) itemRepeatText
+{
+  NSString *text;
 
-- (void)setIsPrivate:(BOOL)_yn {
-  if (_yn)
-    [self setAccessClass:@"PRIVATE"];
+  if ([item isEqualToString: @"-"])
+    text = item;
   else
-    [self setAccessClass:@"PUBLIC"];
-  self->isPrivate = _yn;
-}
-- (BOOL)isPrivate {
-  return self->isPrivate;
-}
+    text = [self labelForKey: [NSString stringWithFormat: @"repeat_%@", item]];
 
-- (void)setCheckForConflicts:(BOOL)_checkForConflicts {
-  self->checkForConflicts = _checkForConflicts;
-}
-- (BOOL)checkForConflicts {
-  return self->checkForConflicts;
+  return text;
 }
 
-- (NSArray *)cycles {
-  static NSArray *cycles = nil;
-  
-  if (!cycles) {
-    NSBundle *bundle;
-    NSString *path;
-
-    bundle = [NSBundle bundleForClass:[self class]];
-    path   = [bundle pathForResource:@"cycles" ofType:@"plist"];
-    NSAssert(path != nil, @"Cannot find cycles.plist!");
-    cycles = [[NSArray arrayWithContentsOfFile:path] retain];
-    NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!");
-  }
-  return cycles;
+- (void) setItem: (NSString *) newItem
+{
+  item = newItem;
 }
 
-- (void)setCycle:(NSDictionary *)_cycle {
-  ASSIGN(self->cycle, _cycle);
-}
-- (NSDictionary *)cycle {
-  return self->cycle;
-}
-- (BOOL)hasCycle {
-  [self debugWithFormat:@"cycle: %@", self->cycle];
-  if (![self->cycle objectForKey:@"rule"])
-    return NO;
-  return YES;
-}
-- (NSString *)cycleLabel {
-  NSString *key;
-  
-  key = [(NSDictionary *)self->item objectForKey:@"label"];
-  return [self labelForKey:key];
+- (NSString *) item
+{
+  return item;
 }
 
+- (NSArray *) reminderList
+{
+  static NSArray *reminderItems = nil;
+
+  if (!reminderItems)
+    {
+      reminderItems = [NSArray arrayWithObjects: @"5_MINUTES_BEFORE",
+                               @"10_MINUTES_BEFORE",
+                               @"15_MINUTES_BEFORE",
+                               @"30_MINUTES_BEFORE",
+                               @"45_MINUTES_BEFORE",
+                               @"-",
+                               @"1_HOUR_BEFORE",
+                               @"2_HOURS_BEFORE",
+                               @"5_HOURS_BEFORE",
+                               @"15_HOURS_BEFORE",
+                               @"-",
+                               @"1_DAY_BEFORE",
+                               @"2_DAYS_BEFORE",
+                               @"1_WEEK_BEFORE",
+                               @"-",
+                               @"CUSTOM",
+                               nil];
+      [reminderItems retain];
+    }
 
-- (void)setCycleUntilDate:(NSCalendarDate *)_cycleUntilDate {
-  NSCalendarDate *until;
-
-  /* copy hour/minute/second from startDate */
-  until = [_cycleUntilDate hour:[self->startDate hourOfDay]
-                           minute:[self->startDate minuteOfHour]
-                           second:[self->startDate secondOfMinute]];
-  [until setTimeZone:[self->startDate timeZone]];
-  ASSIGN(self->cycleUntilDate, until);
+  return reminderItems;
 }
-- (NSCalendarDate *)cycleUntilDate {
-  return self->cycleUntilDate;
-}
-
-- (iCalRecurrenceRule *)rrule {
-  NSString           *ruleRep;
-  iCalRecurrenceRule *rule;
 
-  if (![self hasCycle])
-    return nil;
-  ruleRep = [self->cycle objectForKey:@"rule"];
-  rule    = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep];
+// - (void) setReminder: (NSString *) reminder
+// {
+//   ASSIGN(reminder, _reminder);
+// }
 
-  if (self->cycleUntilDate && [self isCycleEndUntil])
-    [rule setUntilDate:self->cycleUntilDate];
-  return rule;
-}
+// - (NSString *) reminder
+// {
+//   return reminder;
+// }
 
-- (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule {
-  NSDictionary   *c;
-  NSCalendarDate *until;
-  
-  c = [self cycleMatchingRRule:_rrule];
-  [self setCycle:c];
+- (NSString *) itemReminderText
+{
+  NSString *text;
 
-  until = [[[_rrule untilDate] copy] autorelease];
-  if (!until)
-    until = self->startDate;
+  if ([item isEqualToString: @"-"])
+    text = item;
   else
-    [self setIsCycleEndUntil];
-
-  [until setTimeZone:[self viewTimeZone]];
-  [self setCycleUntilDate:until];
-}
-
-/*
- This method is necessary, because we have a fixed sets of cycles in the UI.
- The model is able to represent arbitrary rules, however.
- There SHOULD be a different UI, similar to iCal.app, to allow modelling
- of more complex rules.
- This method obviously cannot map all existing rules back to the fixed list
- in cycles.plist. This should be fixed in a future version when interop
- becomes more important.
- */
-- (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule {
-  NSString *cycleRep;
-  NSArray  *cycles;
-  unsigned i, count;
-
-  if (!_rrule)
-    return [[self cycles] objectAtIndex:0];
-
-  cycleRep = [_rrule cycleRepresentationForSOGo];
-  cycles   = [self cycles];
-  count    = [cycles count];
-  for (i = 1; i < count; i++) {
-    NSDictionary *c;
-    NSString     *cr;
-
-    c  = [cycles objectAtIndex:i];
-    cr = [c objectForKey:@"rule"];
-    if ([cr isEqualToString:cycleRep])
-      return c;
-  }
-  [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule];
-  return nil;
-}
-
-/* cycle "ends" - supposed to be 'never', 'COUNT' or 'UNTIL' */
-- (NSArray *)cycleEnds {
-  static NSArray *ends = nil;
-  
-  if (!ends) {
-    ends = [[NSArray alloc] initWithObjects:@"cycle_end_never",
-                                            @"cycle_end_until",
-                                            nil];
-  }
-  return ends;
-}
-
-- (void)setCycleEnd:(NSString *)_cycleEnd {
-  ASSIGNCOPY(self->cycleEnd, _cycleEnd);
-}
-- (NSString *)cycleEnd {
-  return self->cycleEnd;
-}
-- (BOOL)isCycleEndUntil {
-  return (self->cycleEnd &&
-          [self->cycleEnd isEqualToString:@"cycle_end_until"]);
-}
-- (void)setIsCycleEndUntil {
-  [self setCycleEnd:@"cycle_end_until"];
-}
-- (void)setIsCycleEndNever {
-  [self setCycleEnd:@"cycle_end_never"];
-}
-
-/* transparency */
-
-- (NSString *)transparency {
-  return @"TRANSPARENT";
-}
-
-
-/* iCal */
-
-- (void)setICalString:(NSString *)_s {
-  ASSIGNCOPY(self->iCalString, _s);
-}
-- (NSString *)iCalString {
-  return self->iCalString;
-}
-
-- (NSString *)iCalStringTemplate {
-  static NSString *iCalStringTemplate = \
-    @"BEGIN:VCALENDAR\r\n"
-    @"METHOD:REQUEST\r\n"
-    @"PRODID:OpenGroupware.org SOGo 0.9\r\n"
-    @"VERSION:2.0\r\n"
-    @"BEGIN:VEVENT\r\n"
-    @"UID:%@\r\n"
-    @"CLASS:PUBLIC\r\n"
-    @"STATUS:CONFIRMED\r\n" /* confirmed by default */
-    @"DTSTAMP:%@\r\n"
-    @"DTSTART:%@\r\n"
-    @"DTEND:%@\r\n"
-    @"TRANSP:%@\r\n"
-    @"SEQUENCE:1\r\n"
-    @"PRIORITY:5\r\n"
-    @"%@"                   /* organizer */
-    @"%@"                   /* participants and resources */
-    @"END:VEVENT\r\n"
-    @"END:VCALENDAR";
-
-  NSCalendarDate *lStartDate, *lEndDate;
-  NSString       *template, *s;
-  unsigned       minutes;
-
-  s = [self queryParameterForKey:@"dur"];
-  if(s && [s length] > 0) {
-    minutes = [s intValue];
-  }
-  else {
-    minutes = 60;
-  }
-  lStartDate = [self selectedDate];
-  lEndDate   = [lStartDate dateByAddingYears:0 months:0 days:0
-                           hours:0 minutes:minutes seconds:0];
-  
-  s          = [self iCalParticipantsAndResourcesStringFromQueryParameters];
-  template   = [NSString stringWithFormat:iCalStringTemplate,
-                                                     [[self clientObject] nameInContainer],
-                                                     [[NSCalendarDate date] icalString],
-                                          [lStartDate icalString],
-                                          [lEndDate icalString],
-                                          [self transparency],
-                                          [self iCalOrganizerString],
-                                          s];
-  return template;
-}
-
-- (NSString *)iCalParticipantsAndResourcesStringFromQueryParameters {
-  NSString *s;
-  
-  s = [self iCalParticipantsStringFromQueryParameters];
-  return [s stringByAppendingString:
-            [self iCalResourcesStringFromQueryParameters]];
-}
+    text = [self labelForKey: [NSString stringWithFormat: @"reminder_%@", item]];
 
-- (NSString *)iCalParticipantsStringFromQueryParameters {
-  static NSString *iCalParticipantString = \
-    @"ATTENDEE;ROLE=REQ-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
-  
-  return [self iCalStringFromQueryParameter:@"ps"
-               format:iCalParticipantString];
+  return text;
 }
 
-- (NSString *)iCalResourcesStringFromQueryParameters {
-  static NSString *iCalResourceString = \
-    @"ATTENDEE;ROLE=NON-PARTICIPANT;CN=\"%@\":mailto:%@\r\n";
-
-  return [self iCalStringFromQueryParameter:@"rs"
-               format:iCalResourceString];
+- (NSString *) repeat
+{
+  return @"";
 }
 
-- (NSString *)iCalStringFromQueryParameter:(NSString *)_qp
-              format:(NSString *)_format
+- (void) setRepeat: (NSString *) newRepeat
 {
-  AgenorUserManager *um;
-  NSMutableString *iCalRep;
-  NSString *s;
-
-  um = [AgenorUserManager sharedUserManager];
-  iCalRep = (NSMutableString *)[NSMutableString string];
-  s = [self queryParameterForKey:_qp];
-  if(s && [s length] > 0) {
-    NSArray *es;
-    unsigned i, count;
-    
-    es = [s componentsSeparatedByString:@","];
-    count = [es count];
-    for(i = 0; i < count; i++) {
-      NSString *email, *cn;
-      
-      email = [es objectAtIndex:i];
-      cn = [um getCNForUID:[um getUIDForEmail:email]];
-      [iCalRep appendFormat:_format, cn, email];
-    }
-  }
-  return iCalRep;
 }
 
-- (NSString *)iCalOrganizerString {
-  static NSString *fmt = @"ORGANIZER;CN=\"%@\":mailto:%@\r\n";
-  return [NSString stringWithFormat:fmt,
-                                      [self cnForUser],
-                                      [self emailForUser]];
+- (NSString *) reminder
+{
+  return @"";
 }
 
-#if 0
-- (iCalPerson *)getOrganizer {
-  iCalPerson *p;
-  NSString   *emailProp;
-  
-  emailProp = [@"mailto:" stringByAppendingString:[self emailForUser]];
-  p = [[[iCalPerson alloc] init] autorelease];
-  [p setEmail:emailProp];
-  [p setCn:[self cnForUser]];
-  return p;
-}
-#endif
-
-
-/* helper */
-
-- (NSString *)_completeURIForMethod:(NSString *)_method {
-  NSString *uri;
-  NSRange r;
-    
-  uri = [[[self context] request] uri];
-    
-  /* first: identify query parameters */
-  r = [uri rangeOfString:@"?" options:NSBackwardsSearch];
-  if (r.length > 0)
-    uri = [uri substringToIndex:r.location];
-    
-  /* next: append trailing slash */
-  if (![uri hasSuffix:@"/"])
-    uri = [uri stringByAppendingString:@"/"];
-  
-  /* next: append method */
-  uri = [uri stringByAppendingString:_method];
-    
-  /* next: append query parameters */
-  return [self completeHrefForMethod:uri];
+- (void) setReminder: (NSString *) newReminder
+{
 }
 
-/* new */
-
-- (id)newAction {
-  /*
-    This method creates a unique ID and redirects to the "edit" method on the
-    new ID.
-    It is actually a folder method and should be defined on the folder.
-    
-    Note: 'clientObject' is the SOGoAppointmentFolder!
-          Update: remember that there are group folders as well.
-  */
-  NSString *uri, *objectId, *method, *ps;
-
-  objectId = [NSClassFromString(@"SOGoAppointmentFolder")
-                              globallyUniqueObjectId];
-  if ([objectId length] == 0) {
-    return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
-                       reason:@"could not create a unique ID"];
-  }
-
-  method = [NSString stringWithFormat:@"Calendar/%@/edit", objectId];
-  method = [[self userFolderPath] stringByAppendingPathComponent:method];
-
-  /* check if participants have already been provided */
-  ps     = [[[self context] request] formValueForKey:@"ps"];
-  if (ps) {
-    [self setQueryParameter:ps forKey:@"ps"];
-  }
-  else if ([[self clientObject] respondsToSelector:@selector(calendarUIDs)]) {
-    AgenorUserManager *um;
-    NSArray           *uids;
-    NSMutableArray    *emails;
-    unsigned          i, count;
-
-    /* add all current calendarUIDs as default participants */
-
-    um     = [AgenorUserManager sharedUserManager];
-    uids   = [[self clientObject] calendarUIDs];
-    count  = [uids count];
-    emails = [NSMutableArray arrayWithCapacity:count];
-    
-    for (i = 0; i < count; i++) {
-      NSString *email;
-      
-      email = [um getEmailForUID:[uids objectAtIndex:i]];
-      if (email)
-        [emails addObject:email];
+/* actions */
+- (NSCalendarDate *) newStartDate
+{
+  NSCalendarDate *newStartDate, *now;
+  NSTimeZone *timeZone;
+  int hour;
+
+  newStartDate = [self selectedDate];
+  if ([[self queryParameterForKey: @"hm"] length] == 0)
+    {
+      now = [NSCalendarDate calendarDate];
+      timeZone = [[context activeUser] timeZone];
+      [now setTimeZone: timeZone];
+      if ([now isDateOnSameDay: newStartDate])
+        {
+          hour = [now hourOfDay];
+          if (hour < 8)
+            newStartDate = [now hour: 8 minute: 0];
+          else if (hour > 18)
+            newStartDate = [[now tomorrow] hour: 8 minute: 0];
+          else
+            newStartDate = now;
+        }
+      else
+        newStartDate = [newStartDate hour: 8 minute: 0];
     }
-    ps = [emails componentsJoinedByString:@","];
-    [self setQueryParameter:ps forKey:@"ps"];
-  }
-  uri = [self completeHrefForMethod:method];
-  return [self redirectToLocation:uri];
-}
-
-/* save */
 
-/* returned dates are in GMT */
-- (NSCalendarDate *)_dateFromString:(NSString *)_str {
-  NSCalendarDate *date;
-  
-  date = [NSCalendarDate dateWithString:_str 
-                        calendarFormat:@"%Y-%m-%d %H:%M %Z"];
-  [date setTimeZone:[self backendTimeZone]];
-  return date;
+  return newStartDate;
 }
 
-- (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values
-  treatAsResource:(BOOL)_isResource
+- (id <WOActionResults>) defaultAction
 {
-  unsigned i, count;
-  NSMutableArray *result;
-
-  count = [_values count];
-  result = [[NSMutableArray alloc] initWithCapacity:count];
-  for (i = 0; i < count; i++) {
-    NSString   *pString, *email, *cn;
-    NSRange    r;
-    iCalPerson *p;
-    
-    pString = [_values objectAtIndex:i];
-    if ([pString length] == 0)
-      continue;
-    
-    /* delimiter between email and cn */
-    r = [pString rangeOfString:@";"];
-    if (r.length > 0) {
-      email = [pString substringToIndex:r.location];
-      cn = (r.location + 1 < [pString length])
-       ? [pString substringFromIndex:r.location + 1]
-       : nil;
+  NSCalendarDate *startDate, *endDate;
+  NSString *duration;
+  unsigned int minutes;
+
+  event = (iCalEvent *) [[self clientObject] component: NO];
+  if (event)
+    {
+      startDate = [event startDate];
+      endDate = [event endDate];
     }
-    else {
-      email = pString;
-      cn    = nil;
-    }
-    if (cn == nil) {
-      /* fallback */
-      AgenorUserManager *um = [AgenorUserManager sharedUserManager];
-      cn = [um getCNForUID:[um getUIDForEmail:email]];
-    }
-    
-    p = [[iCalPerson alloc] init];
-    [p setEmail:[@"mailto:" stringByAppendingString:email]];
-    if ([cn isNotNull]) [p setCn:cn];
-    
-    /* see RFC2445, sect. 4.2.16 for details */
-    [p setRole:_isResource ? @"NON-PARTICIPANT" : @"REQ-PARTICIPANT"];
-    [result addObject:p];
-    [p release];
-  }
-  return [result autorelease];
-}
-
-- (BOOL)isWriteableClientObject {
-  return [[self clientObject] 
-               respondsToSelector:@selector(saveContentString:)];
-}
-
-- (void)loadValuesFromAppointment:(SOGoAppointment *)_appointment {
-  NSString           *s;
-  iCalRecurrenceRule *rrule;
-
-  if ((self->startDate = [[_appointment startDate] copy]) == nil)
-    self->startDate = [[[NSCalendarDate date] hour:11 minute:0] copy];
-  if ((self->endDate = [[_appointment endDate] copy]) == nil) {
-    self->endDate =
-      [[self->startDate hour:[self->startDate hourOfDay] + 1 minute:0] copy];
-  }
-  [self->startDate setTimeZone:[self viewTimeZone]];
-  [self->endDate   setTimeZone:[self viewTimeZone]];
-  
-  self->title        = [[_appointment summary]  copy];
-  self->location     = [[_appointment location] copy];
-  self->comment      = [[_appointment comment]  copy];
-  self->priority     = [[_appointment priority] copy];
-  self->categories   = [[_appointment categories]   retain];
-  self->organizer    = [[_appointment organizer]    retain];
-  self->participants = [[_appointment participants] retain];
-  self->resources    = [[_appointment resources]    retain];
-
-  s                  = [_appointment accessClass];
-  if(!s || [s isEqualToString:@"PUBLIC"])
-    [self setIsPrivate:NO];
   else
-    [self setIsPrivate:YES]; /* we're possibly loosing information here */
-
-  /* cycles */
-  rrule = [_appointment recurrenceRule];
-  [self adjustCycleControlsForRRule:rrule];
-}
+    {
+      startDate = [self newStartDate];
+      duration = [self queryParameterForKey:@"dur"];
+      if ([duration length] > 0)
+       minutes = [duration intValue];
+      else
+       minutes = 60;
+      endDate
+       = [startDate dateByAddingYears: 0 months: 0 days: 0
+                    hours: 0 minutes: minutes seconds: 0];
+    }
 
-- (void)saveValuesIntoAppointment:(SOGoAppointment *)_appointment {
-  /* merge in form values */
-  NSArray *attendees, *lResources;
-  
-  [_appointment setStartDate:[self aptStartDate]];
-  [_appointment setEndDate:[self aptEndDate]];
-  
-  [_appointment setSummary:[self title]];
-  [_appointment setLocation:[self location]];
-  [_appointment setComment:[self comment]];
-  [_appointment setPriority:[self priority]];
-  [_appointment setCategories:[self categories]];
-
-  [_appointment setAccessClass:[self accessClass]];
-  [_appointment setTransparency:[self transparency]];
-
-#if 0
-  /*
-    Note: bad, bad, bad!
-    Organizer is no form value, thus we MUST NOT change it
-  */
-  [_appointment setOrganizer:self->organizer];
-#endif
-  attendees  = [self participants];
-  lResources = [self resources];
-  if ([lResources count] > 0) {
-    attendees = ([attendees count] > 0)
-      ? [attendees arrayByAddingObjectsFromArray:lResources]
-      : lResources;
-  }
-  [_appointment setAttendees:attendees];
-
-  /* cycles */
-  [_appointment setRecurrenceRule:[self rrule]];
-}
+  ASSIGN (aptStartDate, startDate);
+  ASSIGN (aptEndDate, endDate);
 
-- (void)loadValuesFromICalString:(NSString *)_ical {
-  SOGoAppointment *apt;
+  /* here comes the code for initializing repeat, reminder and isAllDay... */
 
-  apt = [[SOGoAppointment alloc] initWithICalString:_ical];
-  [self loadValuesFromAppointment:apt];
-  [apt release];
+  return self;
 }
 
-/* contact editor compatibility */
-
-- (void)setContentString:(NSString *)_s {
-  [self setICalString:_s];
-}
-- (NSString *)contentStringTemplate {
-  return [self iCalStringTemplate];
-}
+- (id <WOActionResults>) newAction
+{
+  NSString *objectId, *method, *uri;
+  id <WOActionResults> result;
+  Class clientKlazz;
+
+  clientKlazz = [[self clientObject] class];
+  objectId = [clientKlazz globallyUniqueObjectId];
+  if ([objectId length] > 0)
+    {
+      method = [NSString stringWithFormat:@"%@/Calendar/%@/editAsAppointment",
+                         [self userFolderPath], objectId];
+      uri = [self completeHrefForMethod: method];
+      result = [self redirectToLocation: uri];
+    }
+  else
+    result = [NSException exceptionWithHTTPStatus: 500 /* Internal Error */
+                          reason: @"could not create a unique ID"];
 
-- (void)loadValuesFromContentString:(NSString *)_s {
-  [self loadValuesFromICalString:_s];
+  return result;
 }
 
+- (id <WOActionResults>) saveAction
+{
+  SOGoAppointmentObject *clientObject;
+  NSString *iCalString;
 
-/* access */
-
-- (BOOL)isMyApt {
-  if (self->organizer == nil)
-    return YES; // assume this is safe to do, right?
-  
-  // TODO: this should check a set of emails against the SoUser
-  return [[self->organizer rfc822Email] isEqualToString:[self emailForUser]];
-}
+  clientObject = [self clientObject];
+  iCalString = [[clientObject calendar: NO] versitString];
+  [clientObject saveContentString: iCalString];
 
-- (BOOL)canAccessApt {
-  return [self isMyApt];
+  return [self jsCloseWithRefreshMethod: @"refreshAppointmentsAndDisplay()"];
 }
 
-- (BOOL)canEditApt {
-  return [self isMyApt];
+- (BOOL) shouldTakeValuesFromRequest: (WORequest *) request
+                           inContext: (WOContext*) context
+{
+  return ([[self clientObject] isKindOfClass: [SOGoAppointmentObject class]]
+         && [[request method] isEqualToString: @"POST"]);
 }
 
+- (void) takeValuesFromRequest: (WORequest *) _rq
+                     inContext: (WOContext *) _ctx
+{
+  SOGoAppointmentObject *clientObject;
 
-/* conflict management */
-
-- (BOOL)containsConflict:(SOGoAppointment *)_apt {
-  NSArray               *attendees, *uids;
-  SOGoAppointmentFolder *groupCalendar;
-  NSArray               *infos;
-  NSArray               *ranges;
-  id                    folder;
-
-  [self logWithFormat:@"search from %@ to %@", 
-         [_apt startDate], [_apt endDate]];
-
-  folder    = [[self clientObject] container];
-  attendees = [_apt attendees];
-  uids      = [folder uidsFromICalPersons:attendees];
-  if ([uids count] == 0) {
-    [self logWithFormat:@"Note: no UIDs selected."];
-    return NO;
-  }
-
-  groupCalendar = [folder lookupGroupCalendarFolderForUIDs:uids
-                          inContext:[self context]];
-  [self debugWithFormat:@"group calendar: %@", groupCalendar];
-  
-  if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
-    [self errorWithFormat:@"invalid folder to run freebusy query on!"];
-    return NO;
-  }
-
-  infos = [groupCalendar fetchFreebusyInfosFrom:[_apt startDate]
-                         to:[_apt endDate]];
-  [self debugWithFormat:@"  process: %d events", [infos count]];
-
-  ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:@"startDate"
-                  andEndDateKey:@"endDate"];
-  ranges = [ranges arrayByCompactingContainedDateRanges];
-  [self debugWithFormat:@"  blocked ranges: %@", ranges];
-
-  return [ranges count] != 0 ? YES : NO;
-}
+  clientObject = [self clientObject];
+  event = (iCalEvent *) [clientObject component: YES];
 
-/* response generation */
-
-- (NSString *)jsCode {
-  static NSString *script = \
-  @"function showElement(e, show) {\n"
-  @"   e.style.visibility = show ? 'visible' : 'hidden';\n"
-  @"}\n"
-  @"\n"
-  @"function selectHasCycle(sender) {\n"
-  @"  var value = sender.selectedIndex;\n"
-  @"  var show  = (value != 0);\n"
-  @"  var sel   = document.getElementById('cycle_end_mode_selection');"
-  @"  this.showElement(document.getElementById('cycle_end_label'), show);\n"
-  @"  this.showElement(document.getElementById('cycle_end_mode'),  show);\n"
-  @"  this.selectCycleEnd(sel);\n"
-  @"}\n"
-  @"function selectCycleEnd(sender) {\n"
-  @"  var cycleEndUntil = document.getElementById('cycle_end_until');\n"
-  @"  var value = sender.options[sender.selectedIndex].value;\n"
-  @"  var show  = (value == 'cycle_end_until');\n"
-  @"  this.showElement(cycleEndUntil, show);\n"
-  @"}\n"
-  @"\n";
-
-  return script;
-}
+  [super takeValuesFromRequest: _rq inContext: _ctx];
 
-- (NSString *)initialCycleVisibility {
-  if (![self hasCycle])
-    return @"visibility: hidden;";
-  return @"visibility: visible;";
+  [event setStartDate: aptStartDate];
+  [event setEndDate: aptEndDate];
+  if ([clientObject isNew])
+    [event setTransparency: @"OPAQUE"];
 }
 
-- (NSString *)initialCycleEndUntilVisibility {
-  if ([self isCycleEndUntil])
-    return @"visibility: visible;";
-  return @"visibility: hidden;";
-}
-
-
-/* actions */
-
-- (BOOL)shouldTakeValuesFromRequest:(WORequest *)_rq inContext:(WOContext*)_c{
-  return YES;
-}
+// TODO: add tentatively
 
-- (id)testAction {
-  /* for testing only */
-  WORequest       *req;
-  SOGoAppointment *apt;
-  NSString        *content;
-
-  req = [[self context] request];
-  apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
-  [self saveValuesIntoAppointment:apt];
-  content = [apt iCalString];
-  [self logWithFormat:@"%s -- iCal:\n%@",
-    __PRETTY_FUNCTION__,
-    content];
-  [apt release];
-  return self;
-}
+- (id) acceptOrDeclineAction: (BOOL) accept
+{
+  [[self clientObject] changeParticipationStatus: (accept
+                                                  ? @"ACCEPTED"
+                                                  : @"DECLINED")];
 
-- (id<WOActionResults>)defaultAction {
-  NSString *ical;
-  
-  /* load iCalendar file */
-  
-  // TODO: can't we use [clientObject contentAsString]?
-  ical = [[self clientObject] valueForKey:@"iCalString"];
-  if ([ical length] == 0) /* a new appointment */
-    ical = [self contentStringTemplate];
-  
-  [self setContentString:ical];
-  [self loadValuesFromContentString:ical];
-  
-  if (![self canEditApt]) {
-    /* TODO: we need proper ACLs */
-    return [self redirectToLocation:[self _completeURIForMethod:@"../view"]];
-  }
   return self;
 }
 
-- (id)saveAction {
-  SOGoAppointment *apt;
-  iCalPerson      *p;
-  NSString        *content;
-  NSException     *ex;
-  
-  if (![self isWriteableClientObject]) {
-    /* return 400 == Bad Request */
-    return [NSException exceptionWithHTTPStatus:400
-                        reason:@"method cannot be invoked on "
-                               @"the specified object"];
-  }
-  
-  apt = [[SOGoAppointment alloc] initWithICalString:[self iCalString]];
-  if (apt == nil) {
-    NSString *s;
-    
-    s = [self labelForKey:@"Invalid iCal data!"];
-    [self setErrorText:s];
-    return self;
-  }
-  
-  [self saveValuesIntoAppointment:apt];
-  p = [apt findParticipantWithEmail:[self emailForUser]];
-  if (p) {
-    [p setParticipationStatus:iCalPersonPartStatAccepted];
-  }
-
-  if ([self checkForConflicts]) {
-    if ([self containsConflict:apt]) {
-      NSString *s;
-      
-      s = [self labelForKey:@"Conflicts found!"];
-      [self setErrorText:s];
-      [apt release];
-      return self;
-    }
-  }
-  content = [apt iCalString];
-  [apt release]; apt = nil;
-  
-  if (content == nil) {
-    NSString *s;
-    
-    s = [self labelForKey:@"Could not create iCal data!"];
-    [self setErrorText:s];
-    return self;
-  }
-  
-  ex = [[self clientObject] saveContentString:content];
-  if (ex != nil) {
-    [self setErrorText:[ex reason]];
-    return self;
-  }
-  
-  return [self redirectToLocation:[self _completeURIForMethod:@".."]];
-}
-
-- (id)acceptAction {
-  return [self acceptOrDeclineAction:YES];
-}
-
-- (id)declineAction {
-  return [self acceptOrDeclineAction:NO];
+- (id) acceptAction
+{
+  return [self acceptOrDeclineAction: YES];
 }
 
-// TODO: add tentatively
-
-- (id)acceptOrDeclineAction:(BOOL)_accept {
-  // TODO: this should live in the SoObjects
-  SOGoAppointment *apt;
-  iCalPerson      *p;
-  NSString        *iCal, *content;
-  NSException     *ex;
-  
-  if (![self isWriteableClientObject]) {
-    /* 400 == Bad Request */
-    return [NSException exceptionWithHTTPStatus:400
-                        reason:@"method cannot be invoked on "
-                               @"the specified object"];
-  }
-  iCal = [[self clientObject] valueForKey:@"iCalString"];
-  apt  = [[SOGoAppointment alloc] initWithICalString:iCal];
-  if (apt == nil) {
-    /* 500 == Internal Server Error */
-    return [NSException exceptionWithHTTPStatus:500
-                        reason:@"unable to parse appointment"];
-  }
-  
-  if ((p = [apt findParticipantWithEmail:[self emailForUser]]) == nil) {
-    /* 404 == Not found */
-    return [NSException exceptionWithHTTPStatus:404
-                        reason:@"user does not participate in this "
-                               @"appointment"];
-  }
-
-  // TODO: add tentative
-  if (_accept)
-    [p setParticipationStatus:iCalPersonPartStatAccepted];
-  else
-    [p setParticipationStatus:iCalPersonPartStatDeclined];
-
-  content = [apt iCalString];
-  [apt release];
-  
-  if (content == nil) {
-    /* 500 == Internal Server Error */
-    return [NSException exceptionWithHTTPStatus:500
-                        reason:@"Could not create iCalendar data ..."];
-  }
-  
-  ex = [[self clientObject] saveContentString:content];
-  if (ex != nil) {
-    /* 500 == Internal Server Error */
-    return [NSException exceptionWithHTTPStatus:500
-                        reason:[ex reason]];
-  }
-  
-  return [self redirectToLocation:[self _completeURIForMethod:@"../view"]];
+- (id) declineAction
+{
+  return [self acceptOrDeclineAction: NO];
 }
 
-@end /* UIxAppointmentEditor */
+@end