tvplanit: Import tasks from VTODO section of iCalendar files (*.ics, *.ical)

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@6502 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2018-06-17 20:27:58 +00:00
parent 5706fa833b
commit 53e2ca7944
10 changed files with 296 additions and 25 deletions

View File

@ -929,6 +929,12 @@ msgid "Add event..."
msgstr "Ereignis hinzufügen..." msgstr "Ereignis hinzufügen..."
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -920,6 +920,12 @@ msgid "Add event..."
msgstr "" msgstr ""
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -935,6 +935,12 @@ msgid "Add event..."
msgstr "Ajouter un événement..." msgstr "Ajouter un événement..."
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -929,6 +929,12 @@ msgid "Add event..."
msgstr "Gebeurtenis toevoegen..." msgstr "Gebeurtenis toevoegen..."
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -919,6 +919,12 @@ msgid "Add event..."
msgstr "" msgstr ""
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -929,6 +929,12 @@ msgid "Add event..."
msgstr "Добавить событие..." msgstr "Добавить событие..."
#: vpsr.rspopupaddeventfromical #: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..." msgid "Import from iCalendar file(s)..."
msgstr "" msgstr ""

View File

@ -185,6 +185,7 @@ resourcestring
{Task Specific} {Task Specific}
RSConfirmDeleteTask = 'Delete this task from your list?'; RSConfirmDeleteTask = 'Delete this task from your list?';
RSTaskPopupAdd = 'Add task...'; RSTaskPopupAdd = 'Add task...';
RSPopupAddTaskFromICal = 'Import from iCalendar file(s)...';
RSTaskPopupEdit = 'Edit task...'; RSTaskPopupEdit = 'Edit task...';
RSTaskPopupDelete = 'Delete task...'; RSTaskPopupDelete = 'Delete task...';
RSTaskTitleResource = 'Task list - '; {!!.01} RSTaskTitleResource = 'Task list - '; {!!.01}

View File

@ -422,6 +422,7 @@ type
public public
constructor Create(Owner: TVpTasks); constructor Create(Owner: TVpTasks);
destructor Destroy; override; destructor Destroy; override;
procedure LoadFromICalendar(AEntry: TVpICalToDo);
property Loading: Boolean read FLoading write FLoading; property Loading: Boolean read FLoading write FLoading;
property Changed: Boolean read FChanged write SetChanged; property Changed: Boolean read FChanged write SetChanged;
property Deleted: Boolean read FDeleted write FDeleted; property Deleted: Boolean read FDeleted write FDeleted;
@ -439,8 +440,8 @@ type
property Complete: Boolean read FComplete write SetComplete; property Complete: Boolean read FComplete write SetComplete;
property CreatedOn: TDateTime read FCreatedOn write SetCreatedOn; property CreatedOn: TDateTime read FCreatedOn write SetCreatedOn;
property CompletedOn: TDateTIme read FCompletedOn write SetCompletedOn; property CompletedOn: TDateTIme read FCompletedOn write SetCompletedOn;
property Priority: Integer read FPriority write SetPriority; property Priority: Integer read FPriority write SetPriority; // --> TVpTaskPriority
property Category: Integer read FCategory write SetCategory; property Category: Integer read FCategory write SetCategory; // --> TVpCategoryType
{ Reserved for your use } { Reserved for your use }
property UserField0: string read FUserField0 write FUserField0; property UserField0: string read FUserField0 write FUserField0;
@ -1305,7 +1306,6 @@ begin
FCategory := k; FCategory := k;
end; end;
{ All-day event } { All-day event }
FAllDayEvent := (frac(FStartTime) = 0) and (frac(FEndTime) = 0); FAllDayEvent := (frac(FStartTime) = 0) and (frac(FEndTime) = 0);
@ -1361,18 +1361,6 @@ begin
FRepeatCode := rtCustom; FRepeatCode := rtCustom;
FCustInterval := AEntry.RecurrenceInterval; // * SecondsInDay; FCustInterval := AEntry.RecurrenceInterval; // * SecondsInDay;
end; end;
(*
'HOURLY':
begin
FRepeatCode := rtCustom;
FCustInterval := AEntry.RecurrenceInterval * SecondsInHour;
end;
'MINUTELY':
begin
FRepeatCode := rtCustom;
FCustInterval := AEntry.RecurrenceInterval * SecondsInMinute;
end;
*)
end; end;
if (AEntry.RecurrenceEndDate = 0) and (AEntry.RecurrenceCount > 0) then begin if (AEntry.RecurrenceEndDate = 0) and (AEntry.RecurrenceCount > 0) then begin
FRepeatRangeEnd := trunc(FStartTime); FRepeatRangeEnd := trunc(FStartTime);
@ -1388,9 +1376,6 @@ begin
end; end;
end else end else
FRepeatRangeEnd := AEntry.RecurrenceEndDate; FRepeatRangeEnd := AEntry.RecurrenceEndDate;
// There is also "CustomInterval" which may be extracted from the RecurrenceByXXXX data
// But this is very complex...
end; end;
procedure TVpEvent.SetAlarmAdv(Value: Integer); procedure TVpEvent.SetAlarmAdv(Value: Integer);
@ -2674,6 +2659,55 @@ begin
result := (Trunc(DueDate) < now + 1); result := (Trunc(DueDate) < now + 1);
end; 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); procedure TVpTask.SetCategory(const Value: Integer);
begin begin
if Value <> FCategory then begin if Value <> FCategory then begin

View File

@ -89,6 +89,40 @@ type
property RecurrenceByXXX: String read FRecurrenceByXXX; property RecurrenceByXXX: String read FRecurrenceByXXX;
end; 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 TVpICalendar = class
private private
FEntries: array of TVpICalEntry; FEntries: array of TVpICalEntry;
@ -411,6 +445,114 @@ begin
end; 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 } { TVpICalendar }
{==============================================================================} {==============================================================================}
@ -512,12 +654,15 @@ begin
currEntry := TVpICalEvent.Create(self); currEntry := TVpICalEvent.Create(self);
FEntries[n] := currEntry; FEntries[n] := currEntry;
end; end;
'VFREEBUSY':
currEntry := nil;
'VTODO': 'VTODO':
currEntry := nil; begin
currEntry :=TVpICalToDo.Create(self);
FEntries[n] := currEntry;
end;
'VJOURNAL': 'VJOURNAL':
currEntry := nil; currEntry := nil;
'VFREEBUSY':
currEntry := nil;
'VALARM': 'VALARM':
if currEntry is TVpICalEvent then begin if currEntry is TVpICalEvent then begin
oldEntry := currEntry; oldEntry := currEntry;

View File

@ -172,6 +172,7 @@ type
{ internal methods } { internal methods }
procedure InitializeDefaultPopup; procedure InitializeDefaultPopup;
procedure PopupAddTask(Sender: TObject); procedure PopupAddTask(Sender: TObject);
procedure PopupAddFromICalFile(Sender: TObject);
procedure PopupDeleteTask(Sender: TObject); procedure PopupDeleteTask(Sender: TObject);
procedure PopupEditTask(Sender: TObject); procedure PopupEditTask(Sender: TObject);
procedure tlSetVScrollPos; procedure tlSetVScrollPos;
@ -259,7 +260,8 @@ type
implementation implementation
uses uses
SysUtils, Forms, Dialogs, VpTaskEditDlg, VpDlg, VpTasklistPainter; SysUtils, Forms, Dialogs,
VpDlg, VpTaskEditDlg, VpTasklistPainter, VpICal;
(*****************************************************************************) (*****************************************************************************)
@ -848,7 +850,7 @@ procedure TVpTaskList.InitializeDefaultPopup;
var var
NewItem: TMenuItem; NewItem: TMenuItem;
begin begin
if RSTaskPopupAdd <> '' then begin if RSTaskPopupAdd <> '' then begin // "Add"
NewItem := TMenuItem.Create(Self); NewItem := TMenuItem.Create(Self);
NewItem.Caption := RSTaskPopupAdd; NewItem.Caption := RSTaskPopupAdd;
NewItem.OnClick := PopupAddTask; NewItem.OnClick := PopupAddTask;
@ -856,7 +858,15 @@ begin
FDefaultPopup.Items.Add(NewItem); FDefaultPopup.Items.Add(NewItem);
end; 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 := TMenuItem.Create(Self);
NewItem.Caption := RSTaskPopupEdit; NewItem.Caption := RSTaskPopupEdit;
NewItem.OnClick := PopupEditTask; NewItem.OnClick := PopupEditTask;
@ -864,7 +874,7 @@ begin
FDefaultPopup.Items.Add(NewItem); FDefaultPopup.Items.Add(NewItem);
end; end;
if RSTaskPopupDelete <> '' then begin if RSTaskPopupDelete <> '' then begin // "Delete"
NewItem := TMenuItem.Create(Self); NewItem := TMenuItem.Create(Self);
NewItem.Caption := RSTaskPopupDelete; NewItem.Caption := RSTaskPopupDelete;
NewItem.OnClick := PopupDeleteTask; NewItem.OnClick := PopupDeleteTask;
@ -886,6 +896,51 @@ begin
end; 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); procedure TVpTaskList.PopupDeleteTask(Sender: TObject);
begin begin
if ReadOnly then if ReadOnly then