diff --git a/components/tvplanit/languages/vpsr.de.po b/components/tvplanit/languages/vpsr.de.po index 2310efef2..c76d70189 100644 --- a/components/tvplanit/languages/vpsr.de.po +++ b/components/tvplanit/languages/vpsr.de.po @@ -929,6 +929,12 @@ msgid "Add event..." msgstr "Ereignis hinzufügen..." #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/languages/vpsr.fi.po b/components/tvplanit/languages/vpsr.fi.po index 33d3ac42b..9a2ae12d0 100644 --- a/components/tvplanit/languages/vpsr.fi.po +++ b/components/tvplanit/languages/vpsr.fi.po @@ -920,6 +920,12 @@ msgid "Add event..." msgstr "" #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/languages/vpsr.fr.po b/components/tvplanit/languages/vpsr.fr.po index 979f1f1f1..8b2012abc 100644 --- a/components/tvplanit/languages/vpsr.fr.po +++ b/components/tvplanit/languages/vpsr.fr.po @@ -935,6 +935,12 @@ msgid "Add event..." msgstr "Ajouter un événement..." #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/languages/vpsr.nl.po b/components/tvplanit/languages/vpsr.nl.po index 27f5100a0..785b3ac3b 100644 --- a/components/tvplanit/languages/vpsr.nl.po +++ b/components/tvplanit/languages/vpsr.nl.po @@ -929,6 +929,12 @@ msgid "Add event..." msgstr "Gebeurtenis toevoegen..." #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/languages/vpsr.po b/components/tvplanit/languages/vpsr.po index 1ed76198b..9079544e7 100644 --- a/components/tvplanit/languages/vpsr.po +++ b/components/tvplanit/languages/vpsr.po @@ -919,6 +919,12 @@ msgid "Add event..." msgstr "" #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/languages/vpsr.ru.po b/components/tvplanit/languages/vpsr.ru.po index 63fca18d8..56bf414c2 100644 --- a/components/tvplanit/languages/vpsr.ru.po +++ b/components/tvplanit/languages/vpsr.ru.po @@ -929,6 +929,12 @@ msgid "Add event..." msgstr "Добавить событие..." #: vpsr.rspopupaddeventfromical +msgctxt "vpsr.rspopupaddeventfromical" +msgid "Import from iCalendar file(s)..." +msgstr "" + +#: vpsr.rspopupaddtaskfromical +msgctxt "vpsr.rspopupaddtaskfromical" msgid "Import from iCalendar file(s)..." msgstr "" diff --git a/components/tvplanit/source/include/vpsr.inc b/components/tvplanit/source/include/vpsr.inc index 850cd5854..e68e90d27 100644 --- a/components/tvplanit/source/include/vpsr.inc +++ b/components/tvplanit/source/include/vpsr.inc @@ -185,6 +185,7 @@ resourcestring {Task Specific} RSConfirmDeleteTask = 'Delete this task from your list?'; RSTaskPopupAdd = 'Add task...'; + RSPopupAddTaskFromICal = 'Import from iCalendar file(s)...'; RSTaskPopupEdit = 'Edit task...'; RSTaskPopupDelete = 'Delete task...'; RSTaskTitleResource = 'Task list - '; {!!.01} diff --git a/components/tvplanit/source/vpdata.pas b/components/tvplanit/source/vpdata.pas index eed678be1..6cf46c677 100644 --- a/components/tvplanit/source/vpdata.pas +++ b/components/tvplanit/source/vpdata.pas @@ -422,6 +422,7 @@ type public constructor Create(Owner: TVpTasks); destructor Destroy; override; + procedure LoadFromICalendar(AEntry: TVpICalToDo); property Loading: Boolean read FLoading write FLoading; property Changed: Boolean read FChanged write SetChanged; property Deleted: Boolean read FDeleted write FDeleted; @@ -439,8 +440,8 @@ type property Complete: Boolean read FComplete write SetComplete; property CreatedOn: TDateTime read FCreatedOn write SetCreatedOn; property CompletedOn: TDateTIme read FCompletedOn write SetCompletedOn; - property Priority: Integer read FPriority write SetPriority; - property Category: Integer read FCategory write SetCategory; + property Priority: Integer read FPriority write SetPriority; // --> TVpTaskPriority + property Category: Integer read FCategory write SetCategory; // --> TVpCategoryType { Reserved for your use } property UserField0: string read FUserField0 write FUserField0; @@ -1305,7 +1306,6 @@ begin FCategory := k; end; - { All-day event } FAllDayEvent := (frac(FStartTime) = 0) and (frac(FEndTime) = 0); @@ -1361,18 +1361,6 @@ begin FRepeatCode := rtCustom; FCustInterval := AEntry.RecurrenceInterval; // * SecondsInDay; end; - (* - 'HOURLY': - begin - FRepeatCode := rtCustom; - FCustInterval := AEntry.RecurrenceInterval * SecondsInHour; - end; - 'MINUTELY': - begin - FRepeatCode := rtCustom; - FCustInterval := AEntry.RecurrenceInterval * SecondsInMinute; - end; - *) end; if (AEntry.RecurrenceEndDate = 0) and (AEntry.RecurrenceCount > 0) then begin FRepeatRangeEnd := trunc(FStartTime); @@ -1388,9 +1376,6 @@ begin end; end else FRepeatRangeEnd := AEntry.RecurrenceEndDate; - - // There is also "CustomInterval" which may be extracted from the RecurrenceByXXXX data - // But this is very complex... end; procedure TVpEvent.SetAlarmAdv(Value: Integer); @@ -2674,6 +2659,55 @@ begin result := (Trunc(DueDate) < now + 1); end; +procedure TVpTask.LoadFromICalendar(AEntry: TVpICalToDo); +var + dt: Double; + cat: String; + i, j, k: Integer; + ct: TVpCategoryType; + catFound: Boolean; +begin + if AEntry = nil then + exit; + + { Standard event properties } + FDescription := AEntry.Summary; + FDetails := AEntry.Comment; + FCreatedOn := AEntry.StartTime[false]; + FDueDate := AEntry.DueTime[false]; + FCompletedOn := AEntry.CompletedTime[false]; + + { Status } + FComplete := SameText(AEntry.Status, 'COMPLETED'); + + { Priority } + case AEntry.Priority of + 1, 2, 3: FPriority := ord(tpHigh); + 4, 5, 6: FPriority := ord(tpNormal); + 7, 8, 9: FPriority := ord(tpLow); + else FPriority := ord(tpNormal); + end; + + { Category } + { tvplanit has only 1 category, ical may have several. We pick the first one + defined by TVpCategorytype. If none is found we select ctOther. } + FCategory := ord(ctOther); + catFound := false; + if AEntry.CategoryCount > 0 then begin + for i := 0 to AEntry.CategoryCount-1 do begin + cat := AEntry.category[i]; + for ct in TVpCategoryType do begin + if cat = CategoryLabel(ct) then begin + FCategory := ord(ct); + catFound := true; + break; + end; + end; + if catFound then break; + end; + end; +end; + procedure TVpTask.SetCategory(const Value: Integer); begin if Value <> FCategory then begin diff --git a/components/tvplanit/source/vpical.pas b/components/tvplanit/source/vpical.pas index 262da4652..f9d8ac6de 100644 --- a/components/tvplanit/source/vpical.pas +++ b/components/tvplanit/source/vpical.pas @@ -89,6 +89,40 @@ type property RecurrenceByXXX: String read FRecurrenceByXXX; end; + TVpICalToDo = class(TVpICalEntry) + private + FSummary: String; + FComment: String; + FStartTime: TDateTime; + FStartTimeTZ: String; + FDueTime: TDateTime; + FDueTimeTZ: String; + FCompletedTime: TDateTime; + FCompletedTimeTZ: String; + FDuration: double; + FCategories: TStrings; + FPriority: integer; + FStatus: String; + function GetCategory(AIndex: integer): String; + function GetCategoryCount: Integer; + function GetCompletedTime(UTC: Boolean): TDateTime; + function GetDueTime(UTC: Boolean): TDateTime; + function GetStartTime(UTC: Boolean): TDateTime; + public + constructor Create(AOwner: TVpICalendar); override; + destructor Destroy; override; + procedure Analyze; override; + property Summary: String read FSummary; + property Comment: String read FComment; + property StartTime[UTC: Boolean]: TDateTime read GetStartTime; + property DueTime[UTC: Boolean]: TDateTime read GetDueTime; + property CompletedTime[UTC: Boolean]: TDateTime read GetCompletedTime; + property Category[AIndex: Integer]: String read GetCategory; + property CategoryCount: Integer read GetCategoryCount; + property Priority: Integer read FPriority; // 0=undefined, 1-highest, 9=lowest + property Status: String read FStatus; + end; + TVpICalendar = class private FEntries: array of TVpICalEntry; @@ -411,6 +445,114 @@ begin end; +{==============================================================================} +{ TVpICalToDo } +{==============================================================================} + +constructor TVpICalToDo.Create(AOwner: TVpICalendar); +begin + inherited; + FCategories := TStringList.Create; + FCategories.Delimiter := VALUE_DELIMITER; + FCategories.StrictDelimiter := true; +end; + +destructor TVpICalToDo.Destroy; +begin + FCategories.Free; + inherited; +end; + +procedure TVpICalToDo.Analyze; +var + i, j: Integer; + item: TVpICalItem; + L: TStrings; + s: String; + isUTC: Boolean; +begin + inherited; + + for i := 0 to FItems.Count-1 do begin + item := TVpICalItem(FItems[i]); + case item.Key of + 'SUMMARY': + FSummary := item.Value; + 'COMMENT': + FComment := item.Value; + 'DTSTART': + begin + FStartTimeTZ := item.GetAttribute('TZID'); + FStartTime := iCalDateTime(item.Value, isUTC); + if not isUTC then + FStartTime := FCalendar.LocalTimeToUTC(FStartTime, FStartTimeTZ); + end; + 'DUE': + begin + FDueTimeTZ := item.GetAttribute('TZID'); + FDueTime := iCalDateTime(item.Value, isUTC); + if not isUTC then + FDueTime := FCalendar.LocalTimeToUTC(FDueTime, FDueTimeTZ); + end; + 'DURATION': + FDuration := ICalDuration(item.Value); + 'COMPLETED': + begin + FCompletedTimeTZ := item.GetAttribute('TZID'); + FCompletedTime := iCalDateTime(item.Value, isUTC); + if not isUTC then + FCompletedTime := FCalendar.LocalTimeToUTC(FCompletedTime, FCompletedTimeTZ); + end; + 'CATEGORIES': + FCategories.DelimitedText := item.Value; + 'PRIORITY': + FPriority := StrToIntDef(item.Value, 0); + 'STATUS': + FStatus := item.Value; + end; + end; +end; + +function TVpICalToDo.GetCategory(AIndex: Integer): String; +begin + if (AIndex >= 0) and (AIndex < FCategories.Count) then + Result := FCategories[AIndex] + else + Result := ''; +end; + +function TVpICalToDo.GetCategoryCount: Integer; +begin + Result := FCategories.Count; +end; + +function TVpICalToDo.GetCompletedTime(UTC: Boolean): TDateTime; +begin + Result := FCompletedTime; + if (Result > 0) and (not UTC) then + Result := FCalendar.UTCToLocalTime(Result, FCompletedTimeTZ); +end; + +function TVpICalToDo.GetDueTime(UTC: Boolean): TDateTime; +begin + if FDueTime <> 0 then + Result := FDueTime + else + Result := FStartTime + FDuration; + if (Result > 0) and (not UTC) then + Result := FCalendar.UTCToLocalTime(Result, FDueTimeTZ); +end; + +function TVpICalToDo.GetStartTime(UTC: Boolean): TDateTime; +begin + if UTC then + Result := FStartTime + else + Result := FCalendar.LocalTimeToUTC(FStartTime, FStartTimeTZ); +end; + + + {==============================================================================} { TVpICalendar } {==============================================================================} @@ -512,12 +654,15 @@ begin currEntry := TVpICalEvent.Create(self); FEntries[n] := currEntry; end; - 'VFREEBUSY': - currEntry := nil; 'VTODO': - currEntry := nil; + begin + currEntry :=TVpICalToDo.Create(self); + FEntries[n] := currEntry; + end; 'VJOURNAL': currEntry := nil; + 'VFREEBUSY': + currEntry := nil; 'VALARM': if currEntry is TVpICalEvent then begin oldEntry := currEntry; diff --git a/components/tvplanit/source/vptasklist.pas b/components/tvplanit/source/vptasklist.pas index 8d9e4eaea..8c5f62532 100644 --- a/components/tvplanit/source/vptasklist.pas +++ b/components/tvplanit/source/vptasklist.pas @@ -172,6 +172,7 @@ type { internal methods } procedure InitializeDefaultPopup; procedure PopupAddTask(Sender: TObject); + procedure PopupAddFromICalFile(Sender: TObject); procedure PopupDeleteTask(Sender: TObject); procedure PopupEditTask(Sender: TObject); procedure tlSetVScrollPos; @@ -259,7 +260,8 @@ type implementation uses - SysUtils, Forms, Dialogs, VpTaskEditDlg, VpDlg, VpTasklistPainter; + SysUtils, Forms, Dialogs, + VpDlg, VpTaskEditDlg, VpTasklistPainter, VpICal; (*****************************************************************************) @@ -848,7 +850,7 @@ procedure TVpTaskList.InitializeDefaultPopup; var NewItem: TMenuItem; begin - if RSTaskPopupAdd <> '' then begin + if RSTaskPopupAdd <> '' then begin // "Add" NewItem := TMenuItem.Create(Self); NewItem.Caption := RSTaskPopupAdd; NewItem.OnClick := PopupAddTask; @@ -856,7 +858,15 @@ begin FDefaultPopup.Items.Add(NewItem); end; - if RSTaskPopupEdit <> '' then begin + if RSPopupAddTaskFromICal <> '' then begin // Import from iCal + NewItem := TMenuItem.Create(Self); + NewItem.Caption := RSPopupAddTaskFromICal; + NewItem.OnClick := PopupAddFromICalFile; + NewItem.Tag := 0; + FDefaultPopup.Items.Add(NewItem); + end; + + if RSTaskPopupEdit <> '' then begin // "Edit" NewItem := TMenuItem.Create(Self); NewItem.Caption := RSTaskPopupEdit; NewItem.OnClick := PopupEditTask; @@ -864,7 +874,7 @@ begin FDefaultPopup.Items.Add(NewItem); end; - if RSTaskPopupDelete <> '' then begin + if RSTaskPopupDelete <> '' then begin // "Delete" NewItem := TMenuItem.Create(Self); NewItem.Caption := RSTaskPopupDelete; NewItem.OnClick := PopupDeleteTask; @@ -886,6 +896,51 @@ begin end; {=====} +procedure TVpTaskList.PopupAddFromICalFile(Sender: TObject); +var + dlg: TOpenDialog; + ical: TVpICalendar; + fn: String; + i: Integer; + id: Integer; +begin + if ReadOnly or (not CheckCreateResource) then + exit; + + dlg := TOpenDialog.Create(nil); + try + dlg.Title := RSLoadICalTitle; + dlg.Filter := RSICalFilter; + dlg.FileName := ''; + dlg.Options := dlg.Options + [ofAllowMultiSelect, ofFileMustExist]; + if dlg.Execute then begin + Screen.Cursor := crHourGlass; + Application.ProcessMessages; + ical := TVpICalendar.Create; + try + for fn in dlg.Files do begin + ical.LoadFromFile(fn); + for i := 0 to ical.Count-1 do begin + if not (ical[i] is TVpICalToDo) then + Continue; + id := DataStore.GetNextID(EventsTableName); + FActiveTask := Datastore.Resource.Tasks.AddTask(id); + FActiveTask.LoadFromICalendar(TVpICalToDo(ical[i])); + Datastore.PostTasks; + Datastore.NotifyDependents; + end; + end; + Invalidate; + finally + ical.Free; + Screen.Cursor := crDefault; + end; + end; + finally + dlg.Free; + end; +end; + procedure TVpTaskList.PopupDeleteTask(Sender: TObject); begin if ReadOnly then