tvplanit: Export events to ical files (still buggy).

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@8401 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2022-08-20 17:06:03 +00:00
parent cbf979cfcc
commit 8cdd98b2ff
16 changed files with 485 additions and 73 deletions

View File

@ -1023,11 +1023,6 @@ msgctxt "vpsr.rspopupaddevent"
msgid "Add event..."
msgstr "Ereignis hinzufügen..."
#: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Von iCalendar-Datei(en) importieren..."
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..."
@ -1048,6 +1043,16 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr "Ereignis bearbeiten..."
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
#, fuzzy
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Von iCalendar-Datei(en) importieren..."
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr "Ereignisse überlagern"
@ -1206,6 +1211,10 @@ msgstr "Format in Datei speichern?"
msgid "Save format to \"%s\"?"
msgstr "Format speichern als \"%s\"?"
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr "Als vCard exportieren"
@ -1798,3 +1807,4 @@ msgstr "Unbekannte Achsen-Spezifikation: %s"
#: vpsr.sxmldecnotatbeg
msgid "The XML declaration must appear before the first element"
msgstr "Die XML-Deklaration muss vor dem ersten Element erscheinen"

View File

@ -1014,11 +1014,6 @@ msgstr "Pixels"
msgid "Add event..."
msgstr "Add event..."
#: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Import from iCalendar file(s)..."
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..."
@ -1036,6 +1031,16 @@ msgstr "&Delete event..."
msgid "Edit event..."
msgstr "Edit event..."
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
#, fuzzy
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Import from iCalendar file(s)..."
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr "Overlay events"
@ -1192,6 +1197,10 @@ msgstr "Save format to file?"
msgid "Save format to \"%s\"?"
msgstr "Save format to \"%s\"?"
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr "Export to vCard"
@ -1779,3 +1788,4 @@ msgstr "Unknown axis specifier: %s"
#: vpsr.sxmldecnotatbeg
msgid "The XML declaration must appear before the first element"
msgstr "The XML declaration must appear before the first element"

View File

@ -1014,11 +1014,6 @@ msgctxt "vpsr.rspopupaddevent"
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)..."
@ -1039,6 +1034,15 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr ""
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr ""
@ -1197,6 +1201,10 @@ msgstr ""
msgid "Save format to \"%s\"?"
msgstr ""
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -1029,11 +1029,6 @@ msgctxt "vpsr.rspopupaddevent"
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)..."
@ -1054,6 +1049,15 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr "Modifier un événement"
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr ""
@ -1212,6 +1216,10 @@ msgstr ""
msgid "Save format to \"%s\"?"
msgstr ""
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -1023,11 +1023,6 @@ msgctxt "vpsr.rspopupaddevent"
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)..."
@ -1048,6 +1043,15 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr "Gebeurtenis bewerken..."
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr ""
@ -1206,6 +1210,10 @@ msgstr ""
msgid "Save format to \"%s\"?"
msgstr ""
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -1022,11 +1022,6 @@ msgstr "Pixele"
msgid "Add event..."
msgstr "Dodaj wydarzenie..."
#: vpsr.rspopupaddeventfromical
msgctxt "vpsr.rspopupaddeventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Import z pliku iCalendar"
#: vpsr.rspopupaddtaskfromical
msgctxt "vpsr.rspopupaddtaskfromical"
msgid "Import from iCalendar file(s)..."
@ -1047,6 +1042,16 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr "Edytuj wydarzenie..."
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
#, fuzzy
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr "Import z pliku iCalendar"
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr "Nakładające się wydarzenia"
@ -1205,6 +1210,10 @@ msgstr "Zapisać dane do pliku?"
msgid "Save format to \"%s\"?"
msgstr "Zapisać format do \"%s\"?"
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -1004,11 +1004,6 @@ msgstr ""
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)..."
@ -1026,6 +1021,15 @@ msgstr ""
msgid "Edit event..."
msgstr ""
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr ""
@ -1182,6 +1186,10 @@ msgstr ""
msgid "Save format to \"%s\"?"
msgstr ""
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -1023,11 +1023,6 @@ msgctxt "vpsr.rspopupaddevent"
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)..."
@ -1048,6 +1043,15 @@ msgctxt "vpsr.rspopupeditevent"
msgid "Edit event..."
msgstr "Изменить событие..."
#: vpsr.rspopupexporteventtoical
msgid "Export to iCalendar file..."
msgstr ""
#: vpsr.rspopupimporteventfromical
msgctxt "vpsr.rspopupimporteventfromical"
msgid "Import from iCalendar file(s)..."
msgstr ""
#: vpsr.rspopupresourcegroups
msgid "Overlay events"
msgstr ""
@ -1206,6 +1210,10 @@ msgstr ""
msgid "Save format to \"%s\"?"
msgstr ""
#: vpsr.rssaveicaltitle
msgid "Export to iCal file"
msgstr ""
#: vpsr.rssavevcardtitle
msgid "Export to vCard"
msgstr ""

View File

@ -181,6 +181,7 @@ resourcestring
RSStartEndTimesEqual = 'Start and end times cannot be equal.';
RSCannotEditOverlayedEvent= 'Cannot edit this overlayed event.';
RSLoadICalTitle = 'Import from iCal file(s)';
RSSaveICalTitle = 'Export to iCal file';
RSNoOverlayedEvents = 'none';
RSOverlayedEvent = 'overlayed';
RSOverlayed = 'Overlayed';
@ -207,7 +208,8 @@ resourcestring
{ Popup specific }
RSPopupAddEvent = 'Add event...';
RSPopupAddEventFromICal = 'Import from iCalendar file(s)...';
RSPopupImportEventFromICal= 'Import from iCalendar file(s)...';
RSPopupExportEventToICal = 'Export to iCalendar file...';
RSPopupEditEvent = 'Edit event...';
RSPopupDeleteEvent = '&Delete event...';
RSPopupChangeDate = 'Change date';

View File

@ -81,7 +81,8 @@ type
mikAddEvent, mikEditEvent, mikDeleteEvent,
mikAddTask, mikEditTask, mikDeleteTask,
mikAddContact, mikEditContact, mikDeleteContact,
mikImportEventFromICal, mikImportTaskFromICal,
mikImportEventFromICal, mikExportEventToICal,
mikImportTaskFromICal,
mikImportContactFromVCards, mikExportContactToVCard,
mikResourceGroups, mikNoOverlaidEvents,
mikChangeDate, mikCustomDate, mikToday, mikYesterday, mikTomorrow,
@ -94,7 +95,8 @@ type
RSPopupAddEvent, RSPopupEditEvent, RSPopupDeleteEvent,
RSTaskPopupAdd, RSTaskPopupEdit, RSTaskPopupDelete,
RSContactPopupAdd, RSContactPopupEdit, RSContactPopupDelete,
RSPopupAddEventFromICal, RSPopupAddTaskFromICal,
RSPopupImportEventFromICal, RSPopupExportEventToICal,
RSPopupAddTaskFromICal,
RSContactPopupImportVCards, RSContactPopupExportVCard,
RSPopupResourceGroups, RSNoOverlayedEvents,
RSPopupChangeDate, RSCustomDate, RSToday, RSYesterday, RSTomorrow,

View File

@ -1676,7 +1676,6 @@ end;
procedure TVpContactGrid.PopupImportVCards(Sender: TObject);
var
dlg: TOpenDialog;
vcards: TVpVCards;
i: Integer;
fn: String;
id: Integer;

View File

@ -241,6 +241,7 @@ type
procedure DeleteEvent(Event: TVpEvent);
function EventCountByDay(Value: TDateTime): Integer;
procedure EventsByDate(Date: TDateTime; EventList: TList);
procedure ExportICalFile(const AFileName: String; const AEvents: TVpEventArr);
function GetEvent(Index: Integer): TVpEvent;
function ImportICalFile(const AFileName: String; APreview: Boolean = false;
ADefaultCategory: Integer = -1): TVpEventArr;
@ -312,6 +313,7 @@ type
destructor Destroy; override;
function CanEdit: Boolean;
function CopyToSchedule(ASchedule: TVpSchedule): TVpEvent;
function CreateICalEvent(ACalendar: TVpICalendar): TVpICalEvent;
class procedure GetAlarmParams(ATrigger: TDateTime; out AdvTime: Integer;
out AdvTimeUnits: TVpAlarmAdvType);
function GetResource: TVpResource;
@ -1343,6 +1345,94 @@ begin
Result.UserField8 := FUserField9;
end;
{ Warning: Recurrence and Alarm poorly tested... }
function TVpEvent.CreateICalEvent(ACalendar: TVpICalendar): TVpICalEvent;
var
datastore: TVpCustomDatastore;
begin
Result := TVpICalEvent.Create(ACalendar);
Result.Summary := FDescription;
Result.Description := FNotes;
Result.Location := FLocation;
Result.StartTime[false] := FStartTime;
Result.EndTime[false] := FEndTime + OneSecond;
datastore := TVpCustomDatastore(GetResource.Owner.Owner);
Result.Categories.Text := datastore.CategoryColorMap.GetCategoryName(FCategory);
Result.RecurrenceFrequency := '';
Result.RecurrenceInterval := 0;
Result.RecurrenceCount := 0;
Result.RecurrenceByXXX := '';
case FRepeatCode of
rtDaily:
begin
Result.RecurrenceFrequency := 'DAILY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime));
end;
rtWeekly:
begin
Result.RecurrenceFrequency := 'WEEKLY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime)) div 7;
end;
rtMonthlyByDay:
begin
Result.RecurrenceFrequency := 'MONTHLY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime)) div 30;
end;
rtMonthlyByDate:
begin
Result.RecurrenceFrequency := 'MONTHLY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime)) div 30;
end;
rtYearlyByDay:
begin
Result.RecurrenceFrequency := 'YEARLY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime)) div 365;
end;
rtYearlyByDate:
begin
Result.RecurrenceFrequency := 'YEARLY';
Result.RecurrenceInterval := 1;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime)) div 365;
end;
rtCustom:
begin
Result.RecurrenceFrequency := 'DAILY';
Result.RecurrenceInterval := FCustInterval;
if FRepeatRangeEnd <> FOREVER_DATE then
Result.RecurrenceCount := DaysBetween(DayOf(FRepeatRangeEnd), DayOf(FStartTime));
end;
else
;
end;
if AlarmSet then
begin
Result.UseAlarm(true);
Result.Alarm.Audio := (FDingPath <> '') and FileExists(FDingPath);
Result.Alarm.AudioSrc := FDingPath;
case FAlarmAdvType of
atMinutes: Result.Alarm.Trigger := FAlarmAdv * OneMinute;
atHours: Result.Alarm.Trigger := FAlarmAdv * OneHour;
atDays: Result.Alarm.Trigger := FAlarmAdv;
end;
Result.Alarm.RepeatCount := 1;
end;
end;
{ Returns the resource to which the event belongs. }
function TVpEvent.GetResource: TVpResource;
begin
@ -1748,6 +1838,23 @@ begin
result := TVpEvent(FEventList[Index]);
end;
procedure TVpSchedule.ExportICalFile(const AFileName: String;
const AEvents: TVpEventArr);
var
cal: TVpICalendar;
lEvent: TVpEvent;
begin
cal := TVpICalendar.Create;
try
for lEvent in AEvents do
if lEvent <> nil then
cal.Add(lEvent.CreateICalEvent(cal));
cal.SaveToFile(AFileName);
finally
cal.Free;
end;
end;
function TVpSchedule.ImportICalFile(const AFileName: String;
APreview: Boolean = false; ADefaultCategory: Integer = -1): TVpEventArr;
const

View File

@ -371,7 +371,8 @@ type
{ Popup }
function GetPopupMenu: TPopupMenu; override;
procedure PopupAddEvent(Sender: TObject);
procedure PopupAddFromICalFile(Sender: TObject);
procedure PopupImportICalFile(Sender: TObject);
procedure PopupExportICalFile(Sender: TObject);
procedure PopupDeleteEvent(Sender: TObject);
procedure PopupEditEvent(Sender: TObject);
procedure PopupToday(Sender: TObject);
@ -451,7 +452,8 @@ type
function BuildEventString(AEvent: TVpEvent; UseAsHint: Boolean): String;
procedure DeleteActiveEvent(Verify: Boolean);
procedure DragDrop(Source: TObject; X, Y: Integer); override;
function ImportICalFile(const AFileName: String; APreview: Boolean = false;
procedure ExportICalFile(const AFileName: String; const AEvents: TVpEventArr);
function ImportICalFile(const AFileName: String; APreview: Boolean = false;
ADefaultCategory: Integer = -1): TVpEventArr;
procedure Invalidate; override;
function IsHoliday(ADate: TDate; out AHolidayName: String): Boolean;
@ -1197,14 +1199,22 @@ begin
NewItem.Kind := mikSeparator;
FDefaultPopup.Items.Add(NewItem);
if RSPopupAddEventFromICal <> '' then begin
if RSPopupImportEventFromICal <> '' then begin
NewItem := TVpMenuItem.Create(Self); // Import from iCal
NewItem.Kind := mikImportEventFromICal;
NewItem.OnClick := PopupAddFromICalFile;
NewItem.OnClick := PopupImportICalFile;
NewItem.Tag := 0;
FDefaultPopup.Items.Add(NewItem);
end;
if RSPopupExportEventToICal <> '' then begin // Export ical
NewItem := TVpMenuItem.Create(Self);
NewItem.Kind := mikExportEventToICal;
NewItem.OnClick := PopupExportICalFile;
NewItem.Tag := 1;
FDefaultPopup.Items.Add(NewItem);
end;
NewItem := TVpMenuItem.Create(Self); // ----
NewItem.Kind := mikSeparator;
FDefaultPopup.Items.Add(NewItem);
@ -1363,7 +1373,29 @@ begin
dvSpawnEventEditDialog(True);
end;
procedure TVpDayView.PopupAddFromICalFile(Sender: TObject);
procedure TVpDayView.PopupExportICalFile(Sender: TObject);
var
dlg: TSaveDialog;
begin
if (not Assigned(Datastore)) or (not Assigned(Datastore.Resource)) or
(FActiveEvent = nil)
then
exit;
dlg := TSaveDialog.Create(nil);
try
dlg.Title := RSSaveICalTitle;
dlg.Filter := RSICalFilter;
dlg.FileName := '';
dlg.Options := dlg.Options - [ofAllowMultiSelect] + [ofOverwritePrompt];
if dlg.Execute then
ExportICalFile(dlg.FileName, [FActiveEvent]);
finally
dlg.Free;
end;
end;
procedure TVpDayView.PopupImportICalFile(Sender: TObject);
var
dlg: TOpenDialog;
fn: String;
@ -2353,7 +2385,6 @@ begin
Invalidate;
dvInPlaceEditor.SetFocus;
end;
{=====}
procedure TVpDayView.EndEdit(Sender: TObject);
begin
@ -2377,6 +2408,15 @@ begin
end;
end;
procedure TVpDayview.ExportICalFile(const AFileName: String;
const AEvents: TVpEventArr);
begin
if (not Assigned(Datastore)) or (not Assigned(Datastore.Resource)) then
exit;
Datastore.Resource.Schedule.ExportICalFile(AFileName, AEvents);
end;
{ Reads the events listed in the specified ical file and adds them to the
day view control. All events imported are collected in the Result array.
ADefaultCategory is the category to which the event is assigned if no fitting

View File

@ -19,6 +19,8 @@ type
private
FCalendar: TVpICalendar;
FChecked: Boolean;
protected
procedure SaveToStrings(const AList: TStrings); virtual;
public
constructor Create(ACalendar: TVpICalendar); virtual;
function FindItem(AKey: String): TVpICalItem;
@ -39,13 +41,15 @@ type
FTrigger: Double; // "AlarmAdvance"
FAudio: Boolean;
FAudioSrc: String;
protected
procedure SaveToStrings(const AList: TStrings); override;
public
procedure Analyze; override;
property Duration: Double read FDuration;
property RepeatCount: Integer read FRepeat;
property Trigger: Double read FTrigger;
property Audio: Boolean read FAudio;
property AudioSrc: String read FAudioSrc;
property Duration: Double read FDuration write FDuration;
property RepeatCount: Integer read FRepeat write FRepeat;
property Trigger: Double read FTrigger write FTrigger;
property Audio: Boolean read FAudio write FAudio;
property AudioSrc: String read FAudioSrc write FAudioSrc;
end;
{ TVpICalEvent }
@ -72,26 +76,29 @@ type
function GetCategoryCount: Integer;
function GetEndTime(UTC: Boolean): TDateTime;
function GetStartTime(UTC: Boolean): TDateTime;
procedure SetEndTime(UTC: Boolean; const AValue: TDateTime);
procedure SetStartTime(UTC: Boolean; const AValue: TDateTime);
public
constructor Create(ACalendar: TVpICalendar); override;
destructor Destroy; override;
procedure Analyze; override;
function Categories: TStrings;
function IsAllDayEvent: Boolean;
procedure UseAlarm;
property Summary: String read FSummary; // is "Description" of tvp
property Description: String read FDescription; // is "Notes" of tvp
property Location: String read FLocation;
property StartTime[UTC: Boolean]: TDateTime read GetStartTime;
property EndTime[UTC: Boolean]: TDateTime read GetEndTime;
procedure SaveToStrings(const AList: TStrings); override;
procedure UseAlarm(AEnable: Boolean);
property Summary: String read FSummary write FSummary; // is "Description" of tvp
property Description: String read FDescription write FDescription; // is "Notes" of tvp
property Location: String read FLocation write FLocation;
property StartTime[UTC: Boolean]: TDateTime read GetStartTime write SetStartTime;
property EndTime[UTC: Boolean]: TDateTime read GetEndTime write SetEndTime;
property Category[AIndex: Integer]: String read GetCategory;
property CategoryCount: Integer read GetCategoryCount;
property Alarm: TVpICalAlarm read FAlarm;
property RecurrenceFrequency: String read FRecurrenceFreq;
property RecurrenceInterval: Integer read FRecurrenceInterval;
property RecurrenceEndDate: TDateTime read FRecurrenceEndDate;
property RecurrenceCount: Integer read FRecurrenceCount;
property RecurrenceByXXX: String read FRecurrenceByXXX;
property RecurrenceFrequency: String read FRecurrenceFreq write FRecurrenceFreq;
property RecurrenceInterval: Integer read FRecurrenceInterval write FRecurrenceInterval;
property RecurrenceEndDate: TDateTime read FRecurrenceEndDate write FRecurrenceEndDate;
property RecurrenceCount: Integer read FRecurrenceCount write FRecurrenceCount;
property RecurrenceByXXX: String read FRecurrenceByXXX write FRecurrenceByXXX;
property PickedCategory: Integer read FPickedCategory write FPickedCategory;
end;
@ -120,6 +127,7 @@ type
destructor Destroy; override;
procedure Analyze; override;
function Categories: TStrings;
procedure SaveToStrings(const AList: TStrings); override;
property Summary: String read FSummary;
property Comment: String read FComment;
property StartTime[UTC: Boolean]: TDateTime read GetStartTime;
@ -143,6 +151,8 @@ type
protected
// Reading
procedure LoadFromStrings(const AStrings: TStrings);
// Writing
procedure SaveToStrings(const AList: TStrings);
// Time conversion
function ConvertTime(ADateTime: TDateTime; ATimeZoneID: String; ToUTC: Boolean): TDateTime;
function LocalTimeToUTC(ADateTime: TDateTime; ATimeZoneID: String): TDateTime;
@ -150,13 +160,17 @@ type
public
constructor Create;
destructor Destroy; override;
procedure Add(AEntry: TVpICalEntry);
procedure Clear;
procedure LoadFromFile(const AFileName: String);
procedure LoadFromStream(const AStream: TStream);
procedure SaveToFile(const AFileName: String);
procedure SaveToStream(const AStream: TStream);
property Count: Integer read GetCount;
property EventCount: Integer read GetEventCount;
property TodoCount: Integer read GetToDoCount;
property Entry[AIndex: Integer]: TVpICalEntry read GetEntry; default;
property Version: String read FVersion write FVersion;
end;
@ -165,6 +179,9 @@ implementation
uses
VpConst, VpBase;
const
TIME_FORMAT = 'yyyymmdd"T"hhnnss';
TIME_FORMAT_UTC = TIME_FORMAT + '"Z"';
// Examples: 19970702T160000, or T123000, or 20120101
function iCalDateTime(AText: String; out IsUTC: Boolean): TDateTime;
@ -208,6 +225,34 @@ begin
IsUTC := AText[Length(AText)] = 'Z';
end;
function IsInteger(d, Epsilon: Double): Boolean;
begin
Result := abs(d - round(d)) < Epsilon;
end;
// Converts a duration (in day units) to a string according to ical specs
function Duration2iCalStr(AValue: double): String;
var
isNeg: Boolean = false;
begin
if AValue < 0 then
begin
isNeg := true;
AValue := -AValue;
end;
if IsInteger(AValue, 1.0 / SecondsInDay) then
Result :=Format('P%dS', [round(AValue * SecondsInDay)])
else if IsInteger(AValue, 1.0/MinutesInDay) then
Result := Format('P%dM', [round(AValue * MinutesInDay)])
else if IsInteger(AValue, 1.0/HoursInday) then
Result := Format('P%dH', [round(AValue * HoursInDay)])
else if IsInteger(AValue, 1.0) then
Result := Format('P%dD', [round(AValue)])
else
Result := Format('P%D%s', [trunc(AValue), FormatDateTime('h"H"n"M"s"S"', AValue)]);
if isNeg then Result := '-' + Result;
end;
// Example: PT0H20M0S, or -PT15M, or -P2D
function iCalDuration(AText: String): Double;
var
@ -292,6 +337,9 @@ begin
Result := TVpICalItem(inherited FindItem(AKey, ''));
end;
procedure TVpICalEntry.SaveToStrings(const AList: TStrings);
begin
end;
{==============================================================================}
{ TVpICalAlarm }
@ -320,6 +368,22 @@ begin
end;
end;
procedure TVpICalAlarm.SaveToStrings(const AList: TStrings);
begin
AList.Add('BEGIN:VALARM');
if Audio then
AList.Add('ACTION:AUDIO');
if AudioSrc <> '' then
AList.Add('ATTACH;FMTTYPE=AUDIO:' + AudioSrc);
if RepeatCount > 0 then
AList.Add('REPEAT:' + IntToStr(RepeatCount));
if Trigger <> 0 then
AList.Add('TRIGGER;VALUE=DURATION:' + Duration2iCalStr(Trigger));
if Duration <> 0 then
AList.Add('DURATION;VALUE=DURATION:' + Duration2iCalStr(Duration));
AList.Add('END:VALARM');
end;
{==============================================================================}
{ TVpICalEvent }
@ -471,10 +535,80 @@ begin
Result := false;
end;
procedure TVpICalEvent.UseAlarm;
procedure TVpICalEvent.SaveToStrings(const AList: TStrings);
var
lKey: String = '';
lValue: String = '';
begin
FAlarm.Free;
FAlarm := TVpICalAlarm.Create(FCalendar);
AList.Add('BEGIN:VEVENT');
if FSummary <> '' then
AList.Add('SUMMARY:' + FSummary);
if FDescription <> '' then
AList.Add('DESCRIPTION:' + FDescription);
if FLocation <> '' then
AList.Add('LOCATION:' + FLocation);
if FCategories.Count > 0 then
AList.Add('CATEGORIES:' + FCategories.CommaText);
// todo: check time zones!
if FStartTimeTZ <> '' then
lKey := 'DTSTART;TZID=' + FStartTimeTZ + ':'
else
lKey := 'DTSTART:';
AList.Add(lKey + FormatDateTime(TIME_FORMAT_UTC, StartTime[true]));
if FEndTimeTZ <> '' then
lKey := 'DTEND;TZID=' + FEndTimeTZ + ':'
else
lKey := 'DTEND:';
AList.Add(lKey + FormatDateTime(TIME_FORMAT_UTC, EndTime[true]));
if RecurrenceFrequency <> '' then
begin
lKey := 'RRULE:';
lValue := 'FREQ=' + RecurrenceFrequency;
if RecurrenceInterval > 0 then
lValue := lValue + ';INTERVAL=' + IntToStr(RecurrenceInterval);
if RecurrenceEndDate <> 0 then
lValue := lValue + ';UNTIL=' + FormatDateTime(TIME_FORMAT, RecurrenceEndDate);
if RecurrenceCount > 0 then
lValue := lValue + ';COUNT=' + IntToStr(RecurrenceCount);
if RecurrenceByXXX <> '' then
lValue := lValue + ';' + RecurrenceByXXX;
AList.Add(lKey + lValue);
end;
if Alarm <> nil then
Alarm.SaveToStrings(AList);
end;
procedure TVpICalEvent.SetEndTime(UTC: Boolean; const AValue: TDateTime);
begin
if AValue = NO_DATE then
FEndTime := NO_DATE
else
if UTC then
FEndTime := AValue
else
FEndTime := FCalendar.LocalTimeToUTC(AValue, FEndTimeTZ);
end;
procedure TVpICalEvent.SetStartTime(UTC: Boolean; const AValue: TDateTime);
begin
if AValue = NO_DATE then
FStartTime := NO_DATE
else
if UTC then
FStartTime := AValue
else
FStartTime := FCalendar.LocalTimeToUTC(AValue, FStartTimeTZ);
end;
procedure TVpICalEvent.UseAlarm(AEnable: Boolean);
begin
FreeAndNil(FAlarm);
if AEnable then
FAlarm := TVpICalAlarm.Create(FCalendar);
end;
@ -589,6 +723,10 @@ begin
Result := FCalendar.LocalTimeToUTC(FStartTime, FStartTimeTZ);
end;
procedure TVpICalToDo.SaveToStrings(const AList: TStrings);
begin
// to do...
end;
{==============================================================================}
@ -598,15 +736,25 @@ end;
constructor TVpICalendar.Create;
begin
inherited;
FVersion := '2.0';
SetLength(FEntries, 0);
end;
destructor TVpICalendar.Destroy;
begin
SetLength(FEntries, 0);
Clear;
inherited;
end;
procedure TVpICalendar.Add(AEntry: TVpICalEntry);
var
n: Integer;
begin
n := Length(FEntries);
SetLength(FEntries, n+1);
FEntries[n] := AEntry;
end;
procedure TVpICalendar.Clear;
var
j: Integer;
@ -724,7 +872,7 @@ begin
'VALARM':
if currEntry is TVpICalEvent then begin
oldEntry := currEntry;
TVpICalEvent(currEntry).UseAlarm;
TVpICalEvent(currEntry).UseAlarm(true);
currEntry := TVpICalEvent(currEntry).Alarm;
end;
else
@ -756,6 +904,51 @@ begin
SetLength(FEntries, n);
end;
procedure TVpICalendar.SaveToFile(const AFileName: String);
var
L: TStrings;
begin
L := TStringList.Create;
try
SaveToStrings(L);
L.SaveToFile(AFileName);
finally
L.Free;
end;
end;
procedure TVpICalendar.SaveToStream(const AStream: TStream);
var
L: TStrings;
begin
L := TStringList.Create;
try
SaveToStrings(L);
L.SaveToStream(AStream);
finally
L.Free;
end;
end;
procedure TVpICalendar.SaveToStrings(const AList: TStrings);
var
i: Integer;
begin
AList.Clear;
AList.Add('BEGIN:VCALENDAR');
if FVersion <> '' then
AList.Add('VERSION:' + FVersion);
AList.Add('BEGIN:VTIMEZONE');
// to do: complete here TIMEZONE section with DAYLIGHT and STANDARD sections
AList.Add('END:VTIMEZONE');
for i := 0 to Count-1 do
Entry[i].SaveToStrings(AList);
AList.Add('END:VCALENDAR');
end;
function TVpICalendar.ConvertTime(ADateTime: TDateTime;
ATimeZoneID: String; ToUTC: Boolean): TDateTime;
var

View File

@ -595,7 +595,7 @@ procedure TVpVCards.SaveToStrings(AList: TStrings);
var
i: Integer;
begin
AList.clear;
AList.Clear;
for i := 0 to Count-1 do
FCards[i].SaveToStrings(AList);
end;

View File

@ -1227,7 +1227,7 @@ begin
NewItem.Kind := mikSeparator;
FDefaultPopup.Items.Add(NewItem);
if RSPopupAddEventFromICal <> '' then begin
if RSPopupImportEventFromICal <> '' then begin
NewItem := TVpMenuItem.Create(Self);
NewItem.Kind := mikImportEventFromICal; // Import from iCal
NewItem.OnClick := PopupAddFromICalFile;