From 9b3a49a43808261a0007913934c05e8ec653eb7b Mon Sep 17 00:00:00 2001 From: wp_xxyyzz Date: Mon, 12 Jun 2017 09:19:55 +0000 Subject: [PATCH] tvplanit: Initial commit of json datastore. git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@5930 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- .../examples/datastores/json/project1.lpi | 81 ++ .../examples/datastores/json/project1.lpr | 20 + .../examples/datastores/json/unit1.lfm | 342 +++++++++ .../examples/datastores/json/unit1.pas | 91 +++ .../tvplanit/images/reg_palette_list.txt | 3 + components/tvplanit/languages/vpsr.de.po | 4 + components/tvplanit/languages/vpsr.fi.po | 4 + components/tvplanit/languages/vpsr.fr.po | 4 + components/tvplanit/languages/vpsr.nl.po | 4 + components/tvplanit/languages/vpsr.po | 4 + components/tvplanit/languages/vpsr.ru.po | 4 + components/tvplanit/laz_visualplanit.lpk | 6 +- components/tvplanit/source/design/vpreg.pas | 2 + components/tvplanit/source/design/vpreg.res | Bin 175908 -> 183628 bytes components/tvplanit/source/include/vp.inc | 1 + components/tvplanit/source/include/vpsr.inc | 1 + components/tvplanit/source/vpjsonds.pas | 721 ++++++++++++++++++ 17 files changed, 1291 insertions(+), 1 deletion(-) create mode 100644 components/tvplanit/examples/datastores/json/project1.lpi create mode 100644 components/tvplanit/examples/datastores/json/project1.lpr create mode 100644 components/tvplanit/examples/datastores/json/unit1.lfm create mode 100644 components/tvplanit/examples/datastores/json/unit1.pas create mode 100644 components/tvplanit/source/vpjsonds.pas diff --git a/components/tvplanit/examples/datastores/json/project1.lpi b/components/tvplanit/examples/datastores/json/project1.lpi new file mode 100644 index 000000000..4f661d701 --- /dev/null +++ b/components/tvplanit/examples/datastores/json/project1.lpi @@ -0,0 +1,81 @@ + + + + + + + + + + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + </General> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="2"> + <Item1> + <PackageName Value="laz_visualplanit"/> + </Item1> + <Item2> + <PackageName Value="LCL"/> + </Item2> + </RequiredPackages> + <Units Count="2"> + <Unit0> + <Filename Value="project1.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="unit1.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="Form1"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + <UnitName Value="Unit1"/> + </Unit1> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="project1"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Debugging> + <UseExternalDbgSyms Value="True"/> + </Debugging> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/components/tvplanit/examples/datastores/json/project1.lpr b/components/tvplanit/examples/datastores/json/project1.lpr new file mode 100644 index 000000000..cbaf9b146 --- /dev/null +++ b/components/tvplanit/examples/datastores/json/project1.lpr @@ -0,0 +1,20 @@ +program project1; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX}{$IFDEF UseCThreads} + cthreads, + {$ENDIF}{$ENDIF} + Interfaces, // this includes the LCL widgetset + Forms, Unit1, laz_visualplanit; + +{$R *.res} + +begin + RequireDerivedFormResource := True; + Application.Initialize; + Application.CreateForm(TForm1, Form1); + Application.Run; +end. + diff --git a/components/tvplanit/examples/datastores/json/unit1.lfm b/components/tvplanit/examples/datastores/json/unit1.lfm new file mode 100644 index 000000000..7ca4bacbf --- /dev/null +++ b/components/tvplanit/examples/datastores/json/unit1.lfm @@ -0,0 +1,342 @@ +object Form1: TForm1 + Left = 225 + Height = 686 + Top = 155 + Width = 980 + Caption = 'Form1' + ClientHeight = 686 + ClientWidth = 980 + OnCreate = FormCreate + LCLVersion = '1.9.0.0' + object Panel1: TPanel + Left = 0 + Height = 33 + Top = 0 + Width = 980 + Align = alTop + AutoSize = True + BevelOuter = bvNone + ClientHeight = 33 + ClientWidth = 980 + TabOrder = 0 + object VpResourceCombo1: TVpResourceCombo + AnchorSideLeft.Control = Panel1 + Left = 4 + Height = 23 + Top = 5 + Width = 208 + DataStore = VpJSONDataStore1 + Style = csDropDownList + Borderspacing.Left = 4 + Borderspacing.Top = 4 + Borderspacing.Bottom = 4 + end + object BtnNewRes: TButton + AnchorSideLeft.Control = VpResourceCombo1 + AnchorSideLeft.Side = asrBottom + Left = 216 + Height = 25 + Top = 4 + Width = 98 + AutoSize = True + BorderSpacing.Left = 4 + BorderSpacing.Top = 4 + BorderSpacing.Bottom = 4 + Caption = 'New resource' + OnClick = BtnNewResClick + TabOrder = 1 + end + object BtnEditRes: TButton + AnchorSideLeft.Control = BtnNewRes + AnchorSideLeft.Side = asrBottom + Left = 318 + Height = 25 + Top = 4 + Width = 94 + AutoSize = True + BorderSpacing.Left = 4 + BorderSpacing.Top = 4 + BorderSpacing.Bottom = 4 + Caption = 'Edit resource' + OnClick = BtnEditResClick + TabOrder = 2 + end + end + object PageControl1: TPageControl + Left = 0 + Height = 653 + Top = 33 + Width = 980 + ActivePage = TabSheet1 + Align = alClient + TabIndex = 0 + TabOrder = 1 + object TabSheet1: TTabSheet + Caption = 'Events and tasks' + ClientHeight = 625 + ClientWidth = 972 + object VpDayView1: TVpDayView + Left = 0 + Height = 625 + Top = 0 + Width = 301 + DataStore = VpJSONDataStore1 + ControlLink = VpControlLink1 + Color = clWindow + Font.Height = -12 + ParentFont = False + Align = alLeft + ReadOnly = False + TabStop = True + TabOrder = 0 + AllDayEventAttributes.BackgroundColor = clBtnShadow + AllDayEventAttributes.EventBorderColor = cl3DDkShadow + AllDayEventAttributes.EventBackgroundColor = clBtnFace + AllDayEventAttributes.Font.Height = -12 + ShowEventTimes = False + DrawingStyle = dsFlat + TimeSlotColors.Active = clWhite + TimeSlotColors.Inactive = 8454143 + TimeSlotColors.Holiday = 16744703 + TimeSlotColors.Weekend = 16777088 + TimeSlotColors.ActiveRange.RangeBegin = h_00 + TimeSlotColors.ActiveRange.RangeEnd = h_00 + HeadAttributes.Font.Height = -13 + HeadAttributes.Color = clBtnFace + RowHeadAttributes.HourFont.Height = -24 + RowHeadAttributes.MinuteFont.Height = -12 + RowHeadAttributes.Color = clBtnFace + ShowResourceName = True + LineColor = clGray + GutterWidth = 7 + DateLabelFormat = 'dddd, mmmm dd, yyyy' + Granularity = gr30Min + DefaultTopHour = h_07 + TimeFormat = tf12Hour + end + object Panel2: TPanel + Left = 306 + Height = 625 + Top = 0 + Width = 386 + Align = alLeft + BevelOuter = bvNone + Caption = 'Panel2' + ClientHeight = 625 + ClientWidth = 386 + TabOrder = 1 + object VpWeekView1: TVpWeekView + Left = 0 + Height = 379 + Top = 0 + Width = 386 + DataStore = VpJSONDataStore1 + ControlLink = VpControlLink1 + Color = clWindow + Font.Height = -12 + ParentFont = False + AllDayEventAttributes.BackgroundColor = clWindow + AllDayEventAttributes.EventBorderColor = clGray + AllDayEventAttributes.EventBackgroundColor = clBtnFace + AllDayEventAttributes.Font.Height = -12 + DateLabelFormat = 'dddd, mmmm dd, yyyy' + DayHeadAttributes.Color = clBtnFace + DayHeadAttributes.DateFormat = 'dddd mmmm, dd' + DayHeadAttributes.Font.Height = -13 + DayHeadAttributes.Font.Name = 'Tahoma' + DayHeadAttributes.Bordered = True + DrawingStyle = dsFlat + EventFont.Height = -12 + HeadAttributes.Font.Height = -12 + HeadAttributes.Color = clBtnFace + LineColor = clGray + TimeFormat = tf12Hour + ShowEventTime = True + WeekStartsOn = dtMonday + Align = alClient + TabStop = True + TabOrder = 0 + end + object VpMonthView1: TVpMonthView + Left = 0 + Height = 241 + Top = 384 + Width = 386 + DataStore = VpJSONDataStore1 + ControlLink = VpControlLink1 + Color = clWindow + Font.Height = -12 + ParentFont = False + Align = alBottom + TabStop = True + TabOrder = 1 + KBNavigation = True + DateLabelFormat = 'mmmm yyyy' + DayHeadAttributes.Font.Height = -13 + DayHeadAttributes.Font.Name = 'Tahoma' + DayHeadAttributes.Color = clBtnFace + DayNameStyle = dsShort + DayNumberFont.Height = -12 + DrawingStyle = dsFlat + EventDayStyle = [] + EventFont.Height = -12 + HeadAttributes.Color = clBtnFace + OffDayColor = clSilver + SelectedDayColor = clRed + ShowEvents = True + ShowEventTime = False + TimeFormat = tf12Hour + TodayAttributes.Color = clSilver + TodayAttributes.BorderPen.Color = clRed + TodayAttributes.BorderPen.Width = 3 + WeekStartsOn = dtSunday + end + object Splitter2: TSplitter + Cursor = crVSplit + Left = 0 + Height = 5 + Top = 379 + Width = 386 + Align = alBottom + ResizeAnchor = akBottom + end + end + object VpTaskList1: TVpTaskList + Left = 697 + Height = 625 + Top = 0 + Width = 275 + DataStore = VpJSONDataStore1 + ControlLink = VpControlLink1 + Color = clWindow + Align = alClient + TabStop = True + TabOrder = 2 + ReadOnly = False + DisplayOptions.CheckBGColor = clWindow + DisplayOptions.CheckColor = cl3DDkShadow + DisplayOptions.CheckStyle = csCheck + DisplayOptions.DueDateFormat = 'dd.MM.yyyy' + DisplayOptions.ShowCompletedTasks = False + DisplayOptions.ShowAll = False + DisplayOptions.ShowDueDate = True + DisplayOptions.OverdueColor = clRed + DisplayOptions.NormalColor = clBlack + DisplayOptions.CompletedColor = clGray + LineColor = clGray + MaxVisibleTasks = 250 + TaskHeadAttributes.Color = clSilver + TaskHeadAttributes.Font.Height = -12 + DrawingStyle = dsFlat + ShowResourceName = True + end + object Splitter1: TSplitter + Left = 692 + Height = 625 + Top = 0 + Width = 5 + end + object Splitter3: TSplitter + Left = 301 + Height = 625 + Top = 0 + Width = 5 + end + end + object TabSheet2: TTabSheet + Caption = 'Contacts' + ClientHeight = 624 + ClientWidth = 972 + object VpContactButtonBar1: TVpContactButtonBar + Left = 0 + Height = 624 + Top = 0 + Width = 40 + DrawingStyle = dsFlat + RadioStyle = False + Align = alLeft + end + object VpContactGrid1: TVpContactGrid + Left = 40 + Height = 624 + Top = 0 + Width = 932 + DataStore = VpJSONDataStore1 + ControlLink = VpControlLink1 + Color = clWindow + Font.Height = -12 + ParentFont = False + Align = alClient + TabStop = True + TabOrder = 1 + AllowInPlaceEditing = True + BarWidth = 3 + BarColor = clSilver + ColumnWidth = 200 + ContactHeadAttributes.Color = clSilver + ContactHeadAttributes.Font.Height = -12 + ContactHeadAttributes.Bordered = True + DrawingStyle = dsFlat + end + end + end + object VpControlLink1: TVpControlLink + DataStore = VpJSONDataStore1 + Printer.BottomMargin = 0 + Printer.DayStart = h_08 + Printer.DayEnd = h_05 + Printer.Granularity = gr30Min + Printer.LeftMargin = 0 + Printer.MarginUnits = imAbsolutePixel + Printer.PrintFormats = <> + Printer.RightMargin = 0 + Printer.TopMargin = 0 + left = 136 + top = 264 + end + object VpResourceEditDialog1: TVpResourceEditDialog + Version = 'v1.08' + DataStore = VpJSONDataStore1 + Options = [] + Placement.Position = mpCenter + Placement.Top = 10 + Placement.Left = 10 + Placement.Height = 250 + Placement.Width = 400 + left = 136 + top = 335 + end + object PopupMenu1: TPopupMenu + left = 129 + top = 527 + end + object VpJSONDataStore1: TVpJSONDataStore + CategoryColorMap.Category0.Color = clNavy + CategoryColorMap.Category0.Description = 'Category 0' + CategoryColorMap.Category1.Color = clRed + CategoryColorMap.Category1.Description = 'Category 1' + CategoryColorMap.Category2.Color = clYellow + CategoryColorMap.Category2.Description = 'Category 2' + CategoryColorMap.Category3.Color = clLime + CategoryColorMap.Category3.Description = 'Category 3' + CategoryColorMap.Category4.Color = clPurple + CategoryColorMap.Category4.Description = 'Category 4' + CategoryColorMap.Category5.Color = clTeal + CategoryColorMap.Category5.Description = 'Category 5' + CategoryColorMap.Category6.Color = clFuchsia + CategoryColorMap.Category6.Description = 'Category 6' + CategoryColorMap.Category7.Color = clOlive + CategoryColorMap.Category7.Description = 'Category 7' + CategoryColorMap.Category8.Color = clAqua + CategoryColorMap.Category8.Description = 'Category 8' + CategoryColorMap.Category9.Color = clMaroon + CategoryColorMap.Category9.Description = 'Category 9' + HiddenCategories.BackgroundColor = clSilver + HiddenCategories.Color = clGray + EnableEventTimer = True + PlayEventSounds = True + FileName = 'data.json' + left = 136 + top = 192 + end +end diff --git a/components/tvplanit/examples/datastores/json/unit1.pas b/components/tvplanit/examples/datastores/json/unit1.pas new file mode 100644 index 000000000..63431e7e0 --- /dev/null +++ b/components/tvplanit/examples/datastores/json/unit1.pas @@ -0,0 +1,91 @@ +unit Unit1; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls, + StdCtrls, ComCtrls, Menus, + VpBaseDS, VpDayView, VpWeekView, VpTaskList, VpContactGrid, VpMonthView, + VpResEditDlg, VpContactButtons, VpJSONDs; + +type + + { TForm1 } + + TForm1 = class(TForm) + BtnNewRes: TButton; + BtnEditRes: TButton; + PageControl1: TPageControl; + Panel1: TPanel; + Panel2: TPanel; + PopupMenu1: TPopupMenu; + Splitter1: TSplitter; + Splitter2: TSplitter; + Splitter3: TSplitter; + TabSheet1: TTabSheet; + TabSheet2: TTabSheet; + VpContactButtonBar1: TVpContactButtonBar; + VpContactGrid1: TVpContactGrid; + VpControlLink1: TVpControlLink; + VpDayView1: TVpDayView; + VpJSONDataStore1: TVpJSONDataStore; + VpMonthView1: TVpMonthView; + VpResourceCombo1: TVpResourceCombo; + VpResourceEditDialog1: TVpResourceEditDialog; + VpTaskList1: TVpTaskList; + VpWeekView1: TVpWeekView; + procedure BtnNewResClick(Sender: TObject); + procedure BtnEditResClick(Sender: TObject); + procedure FormCreate(Sender: TObject); + private + { private declarations } + public + { public declarations } + end; + +var + Form1: TForm1; + +implementation + +{$R *.lfm} + +uses + LazFileUtils, + VpData; + + +{ TForm1 } + +// Adds a new resource +procedure TForm1.BtnNewResClick(Sender: TObject); +begin + VpResourceEditDialog1.AddNewResource; +end; + +// Edits the currently selected resource +procedure TForm1.BtnEditResClick(Sender: TObject); +begin + // Open the resource editor dialog, everything is done here. + VpResourceEditDialog1.Execute; +end; + +// Load the last resource. +procedure TForm1.FormCreate(Sender: TObject); +var + lastRes: TVpResource; + datastore: TVpCustomDatastore; +begin + datastore := VpControlLink1.Datastore; + datastore.Connected := true; + if datastore.Resources.Count > 0 then + begin + lastRes := datastore.Resources.Items[datastore.Resources.Count-1]; + datastore.ResourceID := lastRes.ResourceID; + end; +end; + +end. + diff --git a/components/tvplanit/images/reg_palette_list.txt b/components/tvplanit/images/reg_palette_list.txt index 5b400e65d..b5a0e2e84 100644 --- a/components/tvplanit/images/reg_palette_list.txt +++ b/components/tvplanit/images/reg_palette_list.txt @@ -10,6 +10,9 @@ palette/tvpflexdatastore_200.png palette/tvpinidatastore.png palette/tvpinidatastore_150.png palette/tvpinidatastore_200.png +palette/tvpjsondatastore.png +palette/tvpjsondatastore_150.png +palette/tvpjsondatastore_200.png palette/tvpsqlite3datastore.png palette/tvpsqlite3datastore_150.png palette/tvpsqlite3datastore_200.png diff --git a/components/tvplanit/languages/vpsr.de.po b/components/tvplanit/languages/vpsr.de.po index c64ee919c..50bdab581 100644 --- a/components/tvplanit/languages/vpsr.de.po +++ b/components/tvplanit/languages/vpsr.de.po @@ -715,6 +715,10 @@ msgstr "Komponente muss mit einem TVpControlLink verbunden sein." msgid "Day increment unit not specified." msgstr "Intervall nicht angegeben." +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "Fehler: Datastore-Dateiname nicht angegeben." + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "Sprachdatei nicht gefunden." diff --git a/components/tvplanit/languages/vpsr.fi.po b/components/tvplanit/languages/vpsr.fi.po index 44f0acd05..39cafe261 100644 --- a/components/tvplanit/languages/vpsr.fi.po +++ b/components/tvplanit/languages/vpsr.fi.po @@ -705,6 +705,10 @@ msgstr "" msgid "Day increment unit not specified." msgstr "" +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "" + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "" diff --git a/components/tvplanit/languages/vpsr.fr.po b/components/tvplanit/languages/vpsr.fr.po index 67db06f4b..d41704a76 100644 --- a/components/tvplanit/languages/vpsr.fr.po +++ b/components/tvplanit/languages/vpsr.fr.po @@ -721,6 +721,10 @@ msgstr "Le composant doit être lié à un TVpControlLink" msgid "Day increment unit not specified." msgstr "" +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "" + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "Fichier de localization non-trouvé" diff --git a/components/tvplanit/languages/vpsr.nl.po b/components/tvplanit/languages/vpsr.nl.po index bdf9c97c9..07e67106f 100644 --- a/components/tvplanit/languages/vpsr.nl.po +++ b/components/tvplanit/languages/vpsr.nl.po @@ -715,6 +715,10 @@ msgstr "Component moet moet met een TVpControlLink verbonden zijn" msgid "Day increment unit not specified." msgstr "" +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "" + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "Vertalingenbestand niet gevonden." diff --git a/components/tvplanit/languages/vpsr.po b/components/tvplanit/languages/vpsr.po index abe3189cc..2f801c1b7 100644 --- a/components/tvplanit/languages/vpsr.po +++ b/components/tvplanit/languages/vpsr.po @@ -705,6 +705,10 @@ msgstr "" msgid "Day increment unit not specified." msgstr "" +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "" + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "" diff --git a/components/tvplanit/languages/vpsr.ru.po b/components/tvplanit/languages/vpsr.ru.po index df7d77f6d..5d4b53e7a 100644 --- a/components/tvplanit/languages/vpsr.ru.po +++ b/components/tvplanit/languages/vpsr.ru.po @@ -715,6 +715,10 @@ msgstr "" msgid "Day increment unit not specified." msgstr "" +#: vpsr.rsnofilenamespecified +msgid "Error: No datastore filename specified." +msgstr "" + #: vpsr.rsnolocalizationfile msgid "Localization file not found." msgstr "" diff --git a/components/tvplanit/laz_visualplanit.lpk b/components/tvplanit/laz_visualplanit.lpk index 898998476..2ef879764 100644 --- a/components/tvplanit/laz_visualplanit.lpk +++ b/components/tvplanit/laz_visualplanit.lpk @@ -32,7 +32,7 @@ Portions created by TurboPower Software Inc. are Copyright (C) 2002 TurboPower S Contributor(s): "/> <Version Major="1" Release="7"/> - <Files Count="71"> + <Files Count="72"> <Item1> <Filename Value="source\vpbase.pas"/> <UnitName Value="VpBase"/> @@ -317,6 +317,10 @@ Contributor(s): "/> <Filename Value="source\vpclock.pas"/> <UnitName Value="VpClock"/> </Item71> + <Item72> + <Filename Value="source\vpjsonds.pas"/> + <UnitName Value="VpJSONDs"/> + </Item72> </Files> <i18n> <EnableI18N Value="True"/> diff --git a/components/tvplanit/source/design/vpreg.pas b/components/tvplanit/source/design/vpreg.pas index 2874f0d46..1fdf28898 100644 --- a/components/tvplanit/source/design/vpreg.pas +++ b/components/tvplanit/source/design/vpreg.pas @@ -181,6 +181,7 @@ uses {$IFDEF LCL} VpIniDS, { IniFile datastore } VpXmlDS, { XML file datastore } + VpJSONDS, { JSON file datastore } VpBufDS, { Datastore for TBufDataset } VpFBDS, { Datastore for Firebird database } VpSqlite3DS, { Datastore for sqlite3 } @@ -678,6 +679,7 @@ begin {$IFDEF LCL} TVpIniDatastore, TVpXmlDatastore, + TVpJSONDatastore, TVpBufDSDatastore, TVpSqlite3Datastore, TVpFirebirdDatastore diff --git a/components/tvplanit/source/design/vpreg.res b/components/tvplanit/source/design/vpreg.res index 8c4af0fd4c2f237cd3146037b7bb97121866ff7c..8a79cfa22422177cccbcf3b1c683062abb1addc1 100644 GIT binary patch delta 7323 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 delta 36 scmX>zi+jmEt_=;@tP@!o7+fYh&eEMMV8+(`ptJo!C*$@9olH5-01Q<Qpa1{> diff --git a/components/tvplanit/source/include/vp.inc b/components/tvplanit/source/include/vp.inc index 3ad8f25b2..2c06f5772 100644 --- a/components/tvplanit/source/include/vp.inc +++ b/components/tvplanit/source/include/vp.inc @@ -122,3 +122,4 @@ { This defines whether the new icons will be linked into the package. } {$DEFINE NEW_ICONS} + diff --git a/components/tvplanit/source/include/vpsr.inc b/components/tvplanit/source/include/vpsr.inc index 7df1ae13d..548012d49 100644 --- a/components/tvplanit/source/include/vpsr.inc +++ b/components/tvplanit/source/include/vpsr.inc @@ -63,6 +63,7 @@ resourcestring RSInvalidTableSpecified = 'Error: Invalid table specified.'; RSUnableToOpen = 'Error: Unable to open '; RSSQLUpdateError = 'Error: Unable to update '; {!!.01} + RSNoFilenameSpecified = 'Error: No datastore filename specified.'; RSPhoneTypeLabel1 = 'Assistant'; RSPhoneTypeLabel2 = 'Callback'; diff --git a/components/tvplanit/source/vpjsonds.pas b/components/tvplanit/source/vpjsonds.pas new file mode 100644 index 000000000..76c898c28 --- /dev/null +++ b/components/tvplanit/source/vpjsonds.pas @@ -0,0 +1,721 @@ +{ Visual PlanIt datastore for json files } + +{$I vp.inc} + +unit VpJSONDs; + +interface + +uses + SysUtils, Classes, db, fpjson, + VpData, VpBaseDS; + +type + TVpJSONDataStore = class(TVpCustomDataStore) + private + FFileName: String; + FFormatSettings: TFormatSettings; + procedure SetFilename(AValue: String); + + protected + { ancestor methods } + procedure Loaded; override; + procedure SetConnected(const Value: boolean); override; + + { JSON access methods } + function ContactToJSON(AContact: TVpContact): TJSONObject; + function EventToJSON(AEvent: TVpEvent): TJSONObject; + function ResourceToJSON(AResource: TVpResource): TJSONObject; + function TaskToJSON(ATask: TVpTask): TJSONObject; + + function JSONToContact(AObj: TJSONObject; AResource: TVpResource): TVpContact; + function JSONToEvent(AObj: TJSONObject; AResource: TVpResource): TVpEvent; + function JSONToResource(AObj: TJSONObject): TVpResource; + function JSONToTask(AObj: TJSONObject; AResource: TVpResource): TVpTask; + + procedure ReadJSON; + procedure WriteJSON; + + { other methods } + function UniqueID(AValue: Integer): Boolean; + + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + + function GetNextID(TableName: string): Integer; override; + + procedure LoadEvents; override; + procedure LoadContacts; override; + procedure LoadTasks; override; + + procedure PostEvents; override; + procedure PostContacts; override; + procedure PostTasks; override; + procedure PostResources; override; + + procedure SetResourceByName(Value: String); override; + + published + property AutoConnect default false; + property FileName: String read FFileName write SetFilename; + end; + + +implementation + +uses + LazFileUtils, + jsonparser, + VpConst, VpSR, VpMisc; + +constructor TVpJSONDatastore.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + + FFormatSettings := DefaultFormatSettings; + FFormatSettings.DecimalSeparator := '.'; + FFormatSettings.ThousandSeparator := #0; + FFormatSettings.LongDateFormat := 'yyyy-mm-dd'; + FFormatSettings.ShortDateFormat := 'yyyy-mm-dd'; + FFormatSettings.LongTimeFormat := 'hh:nn:ss'; + FFormatSettings.ShortTimeFormat := 'hh:nn'; + FFormatSettings.DateSeparator := '-'; + FFormatSettings.TimeSeparator := ':'; + + FDayBuffer := 1000*365; // 1000 years, i.e. deactivate daybuffer mechanism +end; + +destructor TVpJSONDatastore.Destroy; +begin + SetConnected(false); + inherited; +end; + +function TVpJSONDatastore.ContactToJSON(AContact: TVpContact): TJSONObject; +begin + Result := TJSONObject.Create; + Result.Add('RecordID', AContact.RecordID); + Result.Add('Job_Position', AContact.Job_Position); + Result.Add('FirstName', AContact.FirstName); + Result.Add('LastName', AContact.LastName); + Result.Add('Birthdate', DateToStr(AContact.Birthdate, FFormatSettings)); + Result.Add('Anniversary', DateToStr(AContact.Anniversary, FFormatSettings)); + Result.Add('Title', AContact.Title); + Result.Add('Company', AContact.Company); + Result.Add('Department', AContact.Department); + Result.Add('EMail1', AContact.EMail1); + Result.Add('EMail2', AContact.EMail2); + Result.Add('EMail3', AContact.EMail3); + Result.Add('EMailType1', ord(AContact.EMailType1)); + Result.Add('EMailType2', ord(AContact.EMailType2)); + Result.Add('EMailType3', ord(AContact.EMailType3)); + Result.Add('Phone1', AContact.Phone1); + Result.Add('Phone2', AContact.Phone2); + Result.Add('Phone3', AContact.Phone3); + Result.Add('Phone4', AContact.Phone4); + Result.Add('Phone5', AContact.Phone5); + Result.Add('PhoneType1', AContact.PhoneType1); + Result.Add('PhoneType2', AContact.PhoneType2); + Result.Add('PhoneType3', AContact.PhoneType3); + Result.Add('PhoneType4', AContact.PhoneType4); + Result.Add('PhoneType5', AContact.PhoneType5); + Result.Add('Website1', AContact.Website1); + Result.Add('Website2', AContact.Website2); + Result.Add('WebsiteType1', AContact.WebsiteType1); + Result.Add('WebsiteType2', AContact.WebsiteType2); + Result.Add('Address1', AContact.Address1); + Result.Add('Address2', AContact.Address2); + Result.Add('City1', AContact.City1); + Result.Add('City2', AContact.City2); + Result.Add('State1', AContact.State1); + Result.Add('State2', AContact.State2); + Result.Add('Zip1', AContact.Zip1); + Result.Add('Zip2', AContact.Zip2); + Result.Add('Country1', AContact.Country1); + Result.Add('Country2', AContact.Country2); + Result.Add('AddressType1', ord(AContact.AddressType1)); + Result.Add('AddressType2', ord(AContact.AddressType2)); + Result.Add('Notes', AContact.Notes); + Result.Add('Category', ord(AContact.Category)); + Result.Add('Custom1', AContact.Custom1); + Result.Add('Custom2', AContact.Custom2); + Result.Add('Custom3', AContact.Custom3); + Result.Add('Custom4', AContact.Custom4); + Result.Add('UserField0', AContact.UserField0); + Result.Add('UserField1', AContact.UserField1); + Result.Add('UserField2', AContact.UserField2); + Result.Add('UserField3', AContact.UserField3); + Result.Add('UserField4', AContact.UserField4); + Result.Add('UserField5', AContact.UserField5); + Result.Add('UserField6', AContact.UserField6); + Result.Add('UserField7', AContact.UserField7); + Result.Add('UserField8', AContact.UserField8); + Result.Add('UserField9', AContact.UserField9); +end; + + +function TVpJSONDatastore.EventToJSON(AEvent: TVpEvent): TJSONObject; +begin + Result := TJSONObject.Create; + Result.Add('RecordID', AEvent.RecordID); + Result.Add('Description', AEvent.Description); + Result.Add('Notes', AEvent.Notes); + Result.Add('Location', AEvent.Location); + Result.Add('Category', AEvent.Category); + Result.Add('AllDayEvent', AEvent.AllDayEvent); + if AEvent.StartTime = 0 then + Result.Add('StartTime', '') else + Result.Add('StartTime', FormatDateTime('c', AEvent.StartTime, FFormatSettings)); + if AEvent.EndTime = 0 then + Result.Add('EndTime', '') else + Result.Add('EndTime', FormatDateTime('c', AEvent.EndTime, FFormatSettings)); + Result.Add('DingPath', AEvent.DingPath); + Result.Add('AlertDisplayed', AEvent.AlertDisplayed); + Result.Add('AlarmSet', AEvent.AlarmSet); + Result.Add('AlarmAdvance', AEvent.AlarmAdvance); + Result.Add('AlarmAdvanceType', ord(AEvent.AlarmAdvanceType)); + Result.Add('SnoozeTime', TimeToStr(AEvent.SnoozeTime, FFormatSettings)); + Result.Add('RepeatCode', ord(AEvent.RepeatCode)); + if AEvent.RepeatRangeEnd = 0 then + Result.Add('RepeatRangeEnd', '') else + Result.Add('RepeatRangeEnd', FormatDateTime('c', AEvent.RepeatRangeEnd, FFormatSettings)); + Result.Add('CustomInterval', AEvent.CustomInterval); + Result.Add('UserField0', AEvent.UserField0); + Result.Add('UserField1', AEvent.UserField1); + Result.Add('UserField2', AEvent.UserField2); + Result.Add('UserField3', AEvent.UserField3); + Result.Add('UserField4', AEvent.UserField4); + Result.Add('UserField5', AEvent.UserField5); + Result.Add('UserField6', AEvent.UserField6); + Result.Add('UserField7', AEvent.UserField7); + Result.Add('UserField8', AEvent.UserField8); + Result.Add('UserField9', AEvent.UserField9); +end; + +function TVpJSONDatastore.GetNextID(TableName: string): Integer; +begin + Unused(TableName); + (* + if FUseAutoInc then + { This is not needed in the BufDataset datastore as these tables use + autoincrement fields. } + Result := -1 + else + { If autoincrement fields are not wanted the ID values are created from + random numbers. } + *) + { The ID values are created from unique random numbers. } + repeat + Result := Random(High(Integer)); + until UniqueID(Result) and (Result <> -1); +end; + +function TVpJSONDatastore.JSONToContact(AObj: TJSONObject; + AResource: TVpResource): TVpContact; +var + recID: Integer; + s: String; +begin + if AObj = nil then + exit; + + recID := AObj.Find('RecordID').AsInteger; + Result := AResource.Contacts.AddContact(recID); + Result.Job_Position := AObj.Get('Job_Position', ''); + Result.FirstName := AObj.Get('FirstName', ''); + Result.LastName := AObj.Get('LastName', ''); + s := AObj.Get('Birthdate', ''); + if s <> '' then + Result.BirthDate := StrToDate(s, FFormatSettings) else + Result.BirthDate := 0; + s := AObj.Get('Anniversary', ''); + if s <> '' then + Result.Anniversary := StrToDate(s, FFormatSettings) else + Result.Anniversary := 0; + Result.Title := AObj.Get('Title', ''); + Result.Company := AObj.Get('Company', ''); + Result.Department := AObj.Get('Department', ''); + Result.Email1 := AObj.Get('EMail1', ''); + Result.Email2 := AObj.Get('EMail2', ''); + Result.Email3 := AObj.Get('EMail3', ''); + Result.EmailType1 := AObj.Get('EMailType1', 0); + Result.EmailType2 := AObj.Get('EMailType2', 0); + Result.EmailType3 := AObj.Get('EMailType3', 0); + Result.Phone1 := AObj.Get('Phone1', ''); + Result.Phone2 := AObj.Get('Phone2', ''); + Result.Phone3 := AObj.Get('Phone3', ''); + Result.Phone4 := AObj.Get('Phone4', ''); + Result.Phone5 := AObj.Get('Phone5', ''); + Result.PhoneType1 := AObj.Get('PhoneType1', 0); + Result.PhoneType2 := AObj.Get('PhoneType2', 0); + Result.PhoneType3 := AObj.Get('PhoneType3', 0); + Result.PhoneType4 := AObj.Get('PhoneType4', 0); + Result.PhoneType5 := AObj.Get('PhoneType5', 0); + Result.Website1 := AObj.Get('Website1', ''); + Result.Website2 := AObj.Get('Website2', ''); + Result.WebsiteType1 := AObj.Get('WebsiteType1', 0); + Result.WebsiteType2 := AObj.Get('WebsiteType2', 0); + Result.Address1 := AObj.Get('Address1', ''); + Result.Address2:= AObj.Get('Address2', ''); + Result.AddressType1 := AObj.Get('AddressType1', 0); + Result.AddressType2:= AObj.Get('AddressType2', 0); + Result.City1 := AObj.Get('City1', ''); + Result.City2 := AObj.Get('City2', ''); + Result.Zip1 := AObj.Get('Zip1', ''); + Result.Zip2 := AObj.Get('Zip2', ''); + Result.Country1 := AObj.Get('Country1', ''); + Result.Country2 := AObj.Get('Country2', ''); + Result.Notes := AObj.Get('Notes', ''); + Result.Category := AObj.Get('Category', 0); + Result.Custom1 := AObj.Get('Custom1', ''); + Result.Custom2 := AObj.Get('Custom2', ''); + Result.Custom3 := AObj.Get('Custom3', ''); + Result.Custom4 := AObj.Get('Custom4', ''); + Result.UserField0 := AObj.Get('UserField0', ''); + Result.UserField1 := AObj.Get('UserField1', ''); + Result.UserField2 := AObj.Get('UserField2', ''); + Result.UserField3 := AObj.Get('UserField3', ''); + Result.UserField4 := AObj.Get('UserField4', ''); + Result.UserField5 := AObj.Get('UserField5', ''); + Result.UserField6 := AObj.Get('UserField6', ''); + Result.UserField7 := AObj.Get('UserField7', ''); + Result.UserField8 := AObj.Get('UserField8', ''); + Result.UserField9 := AObj.Get('UserField9', ''); +end; + +function TVpJSONDatastore.JSONToEvent(AObj: TJSONObject; + AResource: TVpResource): TVpEvent; +var + evID: Integer; + startDate, endDate: TDateTime; + s: String; +begin + if AObj = nil then + exit; + + evID := AObj.Find('RecordID').AsInteger; + s := AObj.Get('StartTime', ''); + if s <> '' then + startDate := StrToDateTime(s, FFormatSettings) else + startDate := 0; + s := AObj.Get('EndTime', ''); + if s <> '' then + endDate := StrToDateTime(s, FFormatSettings) else + endDate := 0; + Result := AResource.Schedule.AddEvent(evID, startDate, endDate); + Result.Description := AObj.Get('Description', ''); + Result.Notes := AObj.Get('Notes', ''); + Result.Location := AObj.Get('Location', ''); + Result.Category := AObj.Get('Category', 0); + Result.AllDayEvent := AObj.Get('AllDayEvent', false); + Result.DingPath := AObj.Get('DingPath', ''); + Result.AlertDisplayed := AObj.Get('AlertDisplayed', false); + Result.AlarmSet := AObj.Get('AlarmSet', false); + Result.AlarmAdvance := AObj.Get('AlarmAdvance', 0); + Result.AlarmAdvanceType := TVpAlarmAdvType(AObj.Get('AlarmAdvanceType', 0)); + s := AObj.Get('SnoozeTime', ''); + if s <> '' then + Result.SnoozeTime := StrToTime(s, FFormatSettings) else + Result.SnoozeTime := 0; + Result.RepeatCode := TVpRepeatType(AObj.Get('RepeatCode', 0)); + s := AObj.Get('RepeatRangeEnd', ''); + if s <> '' then + Result.RepeatRangeEnd := StrToDateTime(s, FFormatSettings) else + Result.RepeatRangeEnd := 0; + Result.CustomInterval := AObj.Get('CustomInterval', 0); + Result.UserField0 := AObj.Get('UserField0', ''); + Result.UserField1 := AObj.Get('UserField1', ''); + Result.UserField2 := AObj.Get('UserField2', ''); + Result.UserField3 := AObj.Get('UserField3', ''); + Result.UserField4 := AObj.Get('UserField4', ''); + Result.UserField5 := AObj.Get('UserField5', ''); + Result.UserField6 := AObj.Get('UserField6', ''); + Result.UserField7 := AObj.Get('UserField7', ''); + Result.UserField8 := AObj.Get('UserField8', ''); + Result.UserField9 := AObj.Get('UserField9', ''); +end; + +function TVpJSONDatastore.JSONToResource(AObj: TJSONObject): TVpResource; +var + resID: Integer; +begin + resID := AObj.Find('ResourceID').AsInteger; + Result := Resources.AddResource(resID); + Result.Description := AObj.Get('Description',''); + Result.Notes := AObj.Get('Notes', ''); + Result.ResourceActive := AObj.Get('ResourceActive', false); + Result.UserField0 := AObj.Get('UserField0', ''); + Result.UserField1 := AObj.Get('UserField1', ''); + Result.UserField2 := AObj.Get('UserField2', ''); + Result.UserField3 := AObj.Get('UserField3', ''); + Result.UserField4 := AObj.Get('UserField4', ''); + Result.UserField5 := AObj.Get('UserField5', ''); + Result.UserField6 := AObj.Get('UserField6', ''); + Result.UserField7 := AObj.Get('UserField7', ''); + Result.UserField8 := AObj.Get('UserField8', ''); + Result.UserField9 := AObj.Get('UserField9', ''); +end; + +function TvpJSONDatastore.JSONToTask(AObj: TJSONObject; + AResource: TVpResource): TVpTask; +var + recID: Integer; + s: String; +begin + recID := AObj.Find('RecordID').AsInteger; + Result := AResource.Tasks.AddTask(recID); + s := AObj.Get('DueDate', ''); + if s <> '' then + Result.DueDate := StrToDateTime(s, FFormatSettings) else + Result.DueDate := 0; + Result.Description := AObj.Get('Description', ''); + Result.Details := AObj.Get('Details', ''); + Result.Complete := AObj.Get('Complete', false); + s := AObj.Get('CreatedOn', ''); + if s <> '' then + Result.CreatedOn := StrToDate(s, FFormatSettings) else + Result.CreatedOn := 0; + s := AObj.Get('CompletedOn', ''); + if s <> '' then + Result.CompletedOn := StrToDate(s, FFormatSettings) else + Result.CompletedOn := 0; + Result.Priority := AObj.Get('Priority', 0); + Result.Category := AObj.Get('Category', 0); + Result.UserField0 := AObj.Get('UserField0', ''); + Result.UserField1 := AObj.Get('UserField1', ''); + Result.UserField2 := AObj.Get('UserField2', ''); + Result.UserField3 := AObj.Get('UserField3', ''); + Result.UserField4 := AObj.Get('UserField4', ''); + Result.UserField5 := AObj.Get('UserField5', ''); + Result.UserField6 := AObj.Get('UserField6', ''); + Result.UserField7 := AObj.Get('UserField7', ''); + Result.UserField8 := AObj.Get('UserField8', ''); + Result.UserField9 := AObj.Get('UserField9', ''); +end; + +procedure TVpJSONDatastore.LoadContacts; +begin + // nothing to do +end; + +procedure TVpJSONDatastore.Loaded; +begin + inherited; + if not (csDesigning in ComponentState) then + Connected := AutoConnect; +end; + +procedure TVpJSONDatastore.LoadEvents; +begin + // nothing to do +end; + +procedure TVpJSONDatastore.LoadTasks; +begin + // nothing to do +end; + +procedure TVpJSONDatastore.PostContacts; +var + i: Integer; + contact: TVpContact; +begin + if Resource = nil then + exit; + for i := Resource.Contacts.Count-1 downto 0 do begin + contact := Resource.Contacts.GetContact(i); + if contact.Deleted then + contact.Free; + end; + RefreshContacts; +end; + +procedure TVpJSONDatastore.PostEvents; +var + i: Integer; + event: TVpEvent; +begin + if Resource = nil then + exit; + for i := Resource.Schedule.EventCount-1 downto 0 do begin + event := Resource.Schedule.GetEvent(i); + if event.Deleted then + event.Free; + end; + RefreshEvents; +end; + +procedure TVpJSONDatastore.PostResources; +begin + // Nothing to do... +end; + +procedure TVpJSONDatastore.PostTasks; +var + i: Integer; + task: TVpTask; +begin + if Resource = nil then + exit; + for i := Resource.Tasks.Count-1 downto 0 do begin + task := Resource.Tasks.GetTask(i); + if task.Deleted then + task.Free; + end; + RefreshTasks; +end; + +procedure TVpJSONDatastore.ReadJSON; +var + p: TJSONParser; + stream: TFileStream; + json: TJSONObject; + resObj: TJSONObject; + resObjArray: TJSONArray; + objArray: TJSONArray; + res: TVpResource; + i, j: Integer; +begin + if FFileName = '' then + raise Exception.Create(RSNoFilenameSpecified); + + stream := TFileStream.Create(FFilename, fmOpenRead + fmShareDenyWrite); + try + Resources.ClearResources; + p := TJSONParser.Create(stream); + try + json := p.Parse as TJSONObject; + resObjArray := json.Find('Resources', jtArray) as TJSONArray; + if Assigned(resObjArray) then + for i := 0 to resObjArray.Count-1 do begin + resObj := resObjArray.Objects[i]; + res := JSONToResource(resObj); + // Extract events + objArray := resObj.Find('Events', jtArray) as TJSONArray; + if Assigned(objArray) then + for j := 0 to objArray.Count-1 do + JSONToEvent(objArray.Objects[j], res); + // Extract contacts + objArray := resObj.Find('Contacts', jtArray) as TJSONArray; + if Assigned(objArray) then + for j := 0 to objArray.Count-1 do + JSONToContact(objArray.Objects[j], res); + // Extract tasks + objArray := resObj.Find('Tasks', jtArray) as TJSONArray; + if Assigned(objArray) then + for j := 0 to objArray.Count - 1 do + JSONToTask(objArray.Objects[j], res); + end; + finally + p.Free; + end; + finally + stream.Free; + end; +end; + +function TVPJSONDatastore.ResourceToJSON(AResource: TVpResource): TJSONObject; +begin + Result := TJSONObject.Create; + Result.Add('ResourceID', AResource.ResourceID); + Result.Add('Description', AResource.Description); + Result.Add('Notes', AResource.Notes); + Result.Add('ResourceActive', AResource.ResourceActive); + Result.Add('UserField0', AResource.UserField0); + Result.Add('UserField1', AResource.UserField1); + Result.Add('UserField2', AResource.UserField2); + Result.Add('UserField3', AResource.UserField3); + Result.Add('UserField4', AResource.UserField4); + Result.Add('UserField5', AResource.UserField5); + Result.Add('UserField6', AResource.UserField6); + Result.Add('UserField7', AResource.UserField7); + Result.Add('UserField8', AResource.UserField8); + Result.Add('UserField9', AResource.UserField9); +end; + +procedure TVpJSONDatastore.SetConnected(const Value: boolean); +begin + { Don't do anything with live data until run time. } + if (csDesigning in ComponentState) or (csLoading in ComponentState) then + Exit; + + { Connecting or disconnecting? } + if Value then + ReadJSON + else + WriteJSON; + + inherited SetConnected(Value); +end; + +procedure TVpJSONDatastore.SetResourceByName(Value: string); +var + I: integer; + res : TVpResource; +begin + for I := 0 to pred(Resources.Count) do begin + res := Resources.Items[I]; + if Res = nil then + Continue; + + if res.Description = Value then begin + if ResourceID <> Res.ResourceID then begin + ResourceID := Res.ResourceID; + RefreshResource; + end; + Exit; + end; + end; +end; + +procedure TVpJSONDatastore.SetFilename(AValue: String); +begin + if AValue = FFilename then + exit; + if Connected then + raise Exception.Create('The datastore must not be connected when the filename is set.'); + FFileName := AValue; + if AutoConnect then ReadJSON; +end; + +function TVPJSONDatastore.TaskToJSON(ATask: TVpTask): TJSONObject; +begin + Result := TJSONObject.Create; + Result.Add('RecordID', ATask.RecordID); + if ATask.DueDate = 0 then + Result.Add('DueDate', '') else + Result.Add('DueDate', FormatDateTime('c', ATask.DueDate, FFormatSettings)); + Result.Add('Description', ATask.Description); + Result.Add('Details', ATask.Details); + Result.Add('Complete', ATask.Complete); + if ATask.CreatedOn = 0 then + Result.Add('CreatedOn', '') else + Result.Add('CreatedOn', DateToStr(ATask.CreatedOn, FFormatSettings)); + if ATask.CompletedOn = 0 then + Result.Add('CompletedOn', '') else + Result.Add('CompletedOn', DateToStr(ATask.CompletedOn, FFormatSettings)); + Result.Add('Priority', ATask.Priority); + Result.Add('Category', ATask.Category); + Result.Add('UserField0', ATask.UserField0); + Result.Add('UserField1', ATask.UserField1); + Result.Add('UserField2', ATask.UserField2); + Result.Add('UserField3', ATask.UserField3); + Result.Add('UserField4', ATask.UserField4); + Result.Add('UserField5', ATask.UserField5); + Result.Add('UserField6', ATask.UserField6); + Result.Add('UserField7', ATask.UserField7); + Result.Add('UserField8', ATask.UserField8); + Result.Add('UserField9', ATask.UserField9); +end; + + +function TVpJSONDatastore.UniqueID(AValue: Integer): Boolean; +var + i, j: Integer; + res: TVpResource; +begin + Result := false; + for i:=0 to Resources.Count-1 do begin + res := Resources.Items[i]; + if res.ResourceID = AValue then + exit; + for j:=0 to res.Contacts.Count-1 do + if res.Contacts.GetContact(j).RecordID = AValue then + exit; + for j:=0 to res.Tasks.Count-1 do + if res.Tasks.GetTask(j).RecordID = AValue then + exit; + for j:=0 to res.Schedule.EventCount-1 do + if res.Schedule.GetEvent(j).RecordID = AValue then + exit; + end; + Result := true; +end; + +procedure TVpJSONDatastore.WriteJSON; +var + json: TJSONObject; + resObj: TJSONObject; + resObjArray, evObjArray, contObjArray, tasksObjArray: TJSONArray; + res: TVpResource; + ev: TVpEvent; + cont: TVpContact; + task: TvpTask; + i, j: Integer; +// s: String; + stream: TStream; +begin + if FFilename = '' then + raise Exception.Create(RSNoFilenameSpecified); + + if not Connected then + exit; + + json := TJSONObject.Create; + try + resObjArray := TJSONArray.Create; + + json.Add('Resources', resObjArray); + for i := 0 to Resources.Count-1 do begin + // Add resource + res := Resources.Items[i]; + if res.Deleted then + Continue; + resObj := ResourceToJSON(res); + resObjArray.Add(resObj); + + // Add events of resource + if res.Schedule.EventCount > 0 then + begin + evObjArray := TJSONArray.Create; + resObj.Add('Events', evObjArray); + for j:=0 to res.Schedule.EventCount - 1 do begin + ev := res.Schedule.GetEvent(j); + if ev.Deleted then + Continue; + evObjArray.Add(EventToJSON(ev)); + end; + end; + + // Add contacts of resource + if res.Contacts.Count > 0 then + begin; + contObjArray := TJSONArray.Create; + resObj.Add('Contacts', contObjArray); + for j:=0 to res.Contacts.Count-1 do begin + cont := res.Contacts.GetContact(j); + if cont.Deleted then + Continue; + contObjArray.Add(ContactToJSON(cont)); + end; + end; + + // Add tasks + if res.Tasks.Count > 0 then + begin + tasksObjArray := TJSONArray.Create; + resObj.Add('Tasks', tasksObjArray); + for j := 0 to res.Tasks.Count - 1 do begin + task := res.Tasks.GetTask(j); + if task.Deleted then + Continue; + tasksObjArray.Add(TaskToJSON(task)); + end; + end; + end; + + stream := TFileStream.Create(FFilename, fmCreate); + try + json.DumpJSON(stream); +// s := json.FormatJSON; +// stream.Write(s[1], Length(s)); + finally + stream.Free; + end; + finally + json.Free; + end; +end; + +end.