diff --git a/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpi b/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpi
index dda4a8d9a..d1b1dc218 100644
--- a/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpi
+++ b/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpi
@@ -28,10 +28,13 @@
-
+
-
+
+
+
+
diff --git a/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpr b/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpr
index 1d3ae70d5..91629d122 100644
--- a/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpr
+++ b/components/fpspreadsheet/examples/fpsctrls_no_install/demo_ctrls.lpr
@@ -7,7 +7,7 @@ uses
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
- Forms, main;
+ Forms, tachartlazaruspkg, main;
{$R *.res}
diff --git a/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpi b/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpi
index 3b346347c..b81e9bb7e 100644
--- a/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpi
+++ b/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpi
@@ -51,10 +51,13 @@
-
+
-
+
+
+
+
diff --git a/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpr b/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpr
index 75994f791..481d4c295 100644
--- a/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpr
+++ b/components/fpspreadsheet/examples/fpsgrid_no_install/fpsgrid.lpr
@@ -7,7 +7,7 @@ uses
cthreads,
{$ENDIF}{$ENDIF}
Interfaces, // this includes the LCL widgetset
- Forms, mainfrm
+ Forms, tachartlazaruspkg, mainfrm
{ you can add units after this };
{$R *.res}
diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas
index 0361ab78c..3ef824e21 100755
--- a/components/fpspreadsheet/fpspreadsheet.pas
+++ b/components/fpspreadsheet/fpspreadsheet.pas
@@ -969,8 +969,10 @@ type
FOnWriteCellData: TsWorkbookWriteCellDataEvent;
FOnReadCellData: TsWorkbookReadCellDataEvent;
FOnChangeWorksheet: TsWorksheetEvent;
+ FOnRenameWorksheet: TsWorksheetEvent;
FOnAddWorksheet: TsWorksheetEvent;
FOnRemoveWorksheet: TsRemoveWorksheetEvent;
+ FOnRemovingWorksheet: TsWorksheetEvent;
FOnSelectWorksheet: TsWorksheetEvent;
FOnChangePalette: TNotifyEvent;
FFileName: String;
@@ -1033,6 +1035,14 @@ type
function ValidWorksheetName(var AName: String;
ReplaceDuplicateName: Boolean = false): Boolean;
+ { String-to-cell/range conversion }
+ function TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
+ out ARow,ACol: Cardinal; AListSeparator: Char = #0): Boolean;
+ function TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
+ out ARange: TsCellRange; AListSeparator: Char = #0): Boolean;
+ function TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
+ out ARanges: TsCellRangeArray; AListSeparator: Char = #0): Boolean;
+
{ Font handling }
function AddFont(const AFontName: String; ASize: Single;
AStyle: TsFontStyles; AColor: TsColor): Integer; overload;
@@ -1093,8 +1103,12 @@ type
property OnChangeWorksheet: TsWorksheetEvent read FOnChangeWorksheet write FOnChangeWorksheet;
{@@ This event fires whenever a workbook is loaded }
property OnOpenWorkbook: TNotifyEvent read FOnOpenWorkbook write FOnOpenWorkbook;
- {@@ This event fires when a worksheet is deleted }
+ {@@ This event fires whenever a worksheet is renamed }
+ property OnRenameWorksheet: TsWorksheetEvent read FOnRenameWorksheet write FOnRenameWorksheet;
+ {@@ This event fires AFTER a worksheet has been deleted }
property OnRemoveWorksheet: TsRemoveWorksheetEvent read FOnRemoveWorksheet write FOnRemoveWorksheet;
+ {@@ This event fires BEFORE a worksheet is deleted }
+ property OnRemovingWorksheet: TsWorksheetEvent read FOnRemovingWorksheet write FOnRemovingWorksheet;
{@@ This event fires when a worksheet is made "active"}
property OnSelectWorksheet: TsWorksheetEvent read FOnSelectWorksheet write FOnSelectWorksheet;
{@@ This event allows to provide external cell data for writing to file,
@@ -3597,7 +3611,7 @@ begin
begin
FName := AName;
if (FWorkbook.FLockCount = 0) and Assigned(FWorkbook.FOnChangeWorksheet) then
- FWorkbook.FOnChangeWorksheet(FWorkbook, self);
+ FWorkbook.FOnRenameWorksheet(FWorkbook, self);
end;
end;
@@ -7098,6 +7112,8 @@ begin
i := GetWorksheetIndex(AWorksheet);
if (i <> -1) and (AWorksheet <> nil) then
begin
+ if Assigned(FOnRemovingWorksheet) then
+ FOnRemovingWorksheet(self, AWorksheet);
FWorksheets.Delete(i);
AWorksheet.Free;
if Assigned(FOnRemoveWorksheet) then
@@ -7199,6 +7215,150 @@ begin
end;
*)
+{ String-to-cell/range conversion }
+
+{@@ ----------------------------------------------------------------------------
+ Analyses a string which can contain an array of cell ranges along with a
+ worksheet name. Extracts the worksheet (if missing the "active" worksheet of
+ the workbook is returned) and the cell's row and column indexes.
+
+ @param AText General cell range string in Excel notation,
+ i.e. worksheet name + ! + cell in A1 notation.
+ Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
+ @param AWorksheet Pointer to the worksheet referred to by AText. If AText
+ does not contain the worksheet name, the active worksheet
+ of the workbook is returned
+ @param ARow, ACol Zero-based row and column index of the cell identified
+ by ATest. If AText contains one ore more cell ranges
+ then the upper left corner of the first range is returned.
+ @param AListSeparator Character to separate the cell blocks in the text
+ If #0 then the ListSeparator of the workbook's FormatSettings
+ is used.
+ @returns TRUE if AText is a valid list of cell ranges, FALSE if not. If the
+ result is FALSE then AWorksheet, ARow and ACol may have unpredictable
+ values.
+-------------------------------------------------------------------------------}
+function TsWorkbook.TryStrToCell(AText: String; out AWorksheet: TsWorksheet;
+ out ARow,ACol: Cardinal; AListSeparator: Char = #0): Boolean;
+var
+ ranges: TsCellRangeArray;
+begin
+ Result := TryStrToCellRanges(AText, AWorksheet, ranges, AListSeparator);
+ if Result then
+ begin
+ ARow := ranges[0].Row1;
+ ACol := ranges[0].Col1;
+ end;
+end;
+
+{@@ ----------------------------------------------------------------------------
+ Analyses a string which can contain an array of cell ranges along with a
+ worksheet name. Extracts the worksheet (if missing the "active" worksheet of
+ the workbook is returned) and the cell range (or the first cell range, if there
+ are several ranges).
+
+ @param AText General cell range string in Excel notation,
+ i.e. worksheet name + ! + cell in A1 notation.
+ Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
+ @param AWorksheet Pointer to the worksheet referred to by AText. If AText
+ does not contain the worksheet name, the active worksheet
+ of the workbook is returned
+ @param ARange TsCellRange records identifying the cell block. If AText
+ contains several cell ranges the first one is returned.
+ @param AListSeparator Character to separate the cell blocks in the text
+ If #0 then the ListSeparator of the workbook's FormatSettings
+ is used.
+ @returns TRUE if AText is a valid cell range, FALSE if not. If the
+ result is FALSE then AWorksheet and ARange may have unpredictable
+ values.
+-------------------------------------------------------------------------------}
+function TsWorkbook.TryStrToCellRange(AText: String; out AWorksheet: TsWorksheet;
+ out ARange: TsCellRange; AListSeparator: Char = #0): Boolean;
+var
+ ranges: TsCellRangeArray;
+begin
+ Result := TryStrToCellRanges(AText, AWorksheet, ranges, AListSeparator);
+ if Result then ARange := ranges[0];
+end;
+
+{@@ ----------------------------------------------------------------------------
+ Analyses a string which can contain an array of cell ranges along with a
+ worksheet name. Extracts the worksheet (if missing the "active" worksheet of
+ the workbook is returned) and the range array.
+
+ @param AText General cell range string in Excel notation,
+ i.e. worksheet name + ! + cell in A1 notation.
+ Example: Sheet1!A1:A10; A1:A10 or A1 are valid as well.
+ @param AWorksheet Pointer to the worksheet referred to by AText. If AText
+ does not contain the worksheet name, the active worksheet
+ of the workbook is returned
+ @param ARanges Array of TsCellRange records identifying the cell blocks
+ @param AListSeparator Character to separate the cell blocks in the text
+ If #0 then the ListSeparator of the workbook's FormatSettings
+ is used.
+ @returns TRUE if AText is a valid list of cell ranges, FALSE if not. If the
+ result is FALSE then AWorksheet and ARanges may have unpredictable
+ values.
+-------------------------------------------------------------------------------}
+function TsWorkbook.TryStrToCellRanges(AText: String; out AWorksheet: TsWorksheet;
+ out ARanges: TsCellRangeArray; AListSeparator: Char = #0): Boolean;
+var
+ i: Integer;
+ s: String;
+ L: TStrings;
+begin
+ Result := false;
+ AWorksheet := nil;
+ SetLength(ARanges, 0);
+
+ if AText = '' then
+ exit;
+
+ i := pos(SHEETSEPARATOR, AText);
+ if i = 0 then
+ AWorksheet := FActiveWorksheet
+ else begin
+ AWorksheet := GetWorksheetByName(Copy(AText, 1, i-1));
+ if AWorksheet = nil then
+ exit;
+ AText := Copy(AText, i+1, Length(AText));
+ end;
+
+ L := TStringList.Create;
+ try
+ if AListSeparator = #0 then
+ L.Delimiter := FormatSettings.ListSeparator
+ else
+ L.Delimiter := AListSeparator;
+ L.StrictDelimiter := true;
+ L.DelimitedText := AText;
+ if L.Count = 0 then
+ begin
+ AWorksheet := nil;
+ exit;
+ end;
+ SetLength(ARanges, L.Count);
+ for i:=0 to L.Count-1 do begin
+ if pos(':', L[i]) = 0 then begin
+ Result := ParseCellString(L[i], ARanges[i].Row1, ARanges[i].Col1);
+ if Result then begin
+ ARanges[i].Row2 := ARanges[i].Row1;
+ ARanges[i].Col2 := ARanges[i].Col1;
+ end;
+ end else
+ Result := ParseCellRangeString(L[i], ARanges[i]);
+ if not Result then begin
+ SetLength(ARanges, 0);
+ AWorksheet := nil;
+ exit;
+ end;
+ end;
+ finally
+ L.Free;
+ end;
+end;
+
+
{ Font handling }
{@@ ----------------------------------------------------------------------------
diff --git a/components/fpspreadsheet/fpspreadsheetchart.pas b/components/fpspreadsheet/fpspreadsheetchart.pas
index 0054eb50e..bd811d050 100644
--- a/components/fpspreadsheet/fpspreadsheetchart.pas
+++ b/components/fpspreadsheet/fpspreadsheetchart.pas
@@ -1,11 +1,15 @@
-{
-fpspreadsheetgrid.pas
+{ fpspreadsheetchart.pas }
-Chart data source designed to work together with TChart from Lazarus to display the data
-and with TsWorksheetGrid from FPSpreadsheet to load data from a grid.
+{@@ ----------------------------------------------------------------------------
+Chart data source designed to work together with TChart from Lazarus
+to display the data and with FPSpreadsheet to load data.
+
+AUTHORS: Felipe Monteiro de Carvalho, Werner Pamler
+
+LICENSE: See the file COPYING.modifiedLGPL.txt, included in the Lazarus
+ distribution, for details about the license.
+-------------------------------------------------------------------------------}
-AUTHORS: Felipe Monteiro de Carvalho
-}
unit fpspreadsheetchart;
{$mode objfpc}{$H+}
@@ -15,11 +19,11 @@ interface
uses
Classes, SysUtils, LResources, Forms, Controls, Graphics, Dialogs,
// TChart
- {tasources,} TACustomSource,
- // FPSpreadsheet Visual
- fpspreadsheetctrls, fpspreadsheetgrid,
+ TACustomSource,
// FPSpreadsheet
- fpspreadsheet, fpsutils;
+ fpspreadsheet, fpsutils,
+ // FPSpreadsheet Visual
+ fpspreadsheetctrls, fpspreadsheetgrid;
type
@@ -83,8 +87,8 @@ type
FWorkbookSource: TsWorkbookSource;
FWorkbook: TsWorkbook;
FWorksheets: array[TsXYRange] of TsWorksheet;
+ FRangeStr: array[TsXYRange] of String;
FRanges: array[TsXYRange] of TsCellRangeArray;
- FDirections: array[TsXYRange] of TsSelectionDirection;
FPointsNumber: Cardinal;
function GetRange(AIndex: TsXYRange): String;
function GetWorkbook: TsWorkbook;
@@ -94,10 +98,13 @@ type
procedure SetWorkbookSource(AValue: TsWorkbookSource);
protected
FCurItem: TChartDataItem;
+ function BuildRangeStr(AIndex: TsXYRange; AListSeparator: char = #0): String;
function CountValues(AIndex: TsXYRange): Integer;
function GetCount: Integer; override;
function GetItem(AIndex: Integer): PChartDataItem; override;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
+ procedure Prepare; overload;
+ procedure Prepare(AIndex: TsXYRange); overload;
procedure SetYCount(AValue: Cardinal); override;
public
destructor Destroy; override;
@@ -270,27 +277,65 @@ end;
{ TsWorkbookChartSource }
{------------------------------------------------------------------------------}
+{@@ ----------------------------------------------------------------------------
+ Destructor of the WorkbookChartSource.
+ Removes itself from the WorkbookSource's listener list.
+-------------------------------------------------------------------------------}
destructor TsWorkbookChartSource.Destroy;
begin
if FWorkbookSource <> nil then FWorkbookSource.RemoveListener(self);
inherited Destroy;
end;
+{@@ ----------------------------------------------------------------------------
+ Constructs the range string from the stored internal information. Is needed
+ to have the worksheet name in the range string in order to make the range
+ string unique.
+-------------------------------------------------------------------------------}
+function TsWorkbookChartSource.BuildRangeStr(AIndex: TsXYRange;
+ AListSeparator: Char = #0): String;
+var
+ L: TStrings;
+ range: TsCellRange;
+begin
+ if (FWorkbook = nil) or (FWorksheets[AIndex] = nil) or (Length(FRanges) = 0) then
+ exit('');
+
+ L := TStringList.Create;
+ try
+ if AListSeparator = #0 then
+ L.Delimiter := FWorkbook.FormatSettings.ListSeparator
+ else
+ L.Delimiter := AListSeparator;
+ L.StrictDelimiter := true;
+ for range in FRanges[AIndex] do
+ L.Add(GetCellRangeString(range, rfAllRel, true));
+ Result := FWorksheets[AIndex].Name + SHEETSEPARATOR + L.DelimitedText;
+ finally
+ L.Free;
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Counts the number of x or y values contained in the x/y ranges
+
+ @param AIndex Identifies whether values in the x or y ranges are counted.
-------------------------------------------------------------------------------}
function TsWorkbookChartSource.CountValues(AIndex: TsXYRange): Integer;
var
ir: Integer;
+ range: TsCellRange;
begin
Result := 0;
- case FDirections[AIndex] of
- fpsVerticalSelection:
- for ir:=0 to High(FRanges[AIndex]) do
- inc(Result, FRanges[AIndex, ir].Row2 - FRanges[AIndex, ir].Row1 + 1);
- fpsHorizontalSelection:
- for ir:=0 to High(FRanges[AIndex]) do
- inc(Result, FRanges[AIndex, ir].Col2 - FRanges[AIndex, ir].Col1 + 1);
+ for range in FRanges[AIndex] do
+ begin
+ if range.Col1 = range.Col2 then
+ inc(Result, range.Row2 - range.Row1 + 1)
+ else
+ if range.Row1 = range.Row2 then
+ inc(Result, range.Col2 - range.Col1 + 1)
+ else
+ raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high.');
end;
end;
@@ -306,6 +351,10 @@ end;
{@@ ----------------------------------------------------------------------------
Main ChartSource method called from the series requiring data for plotting.
Retrieves the data from the workbook.
+
+ @param AIndex Index of the data point in the series.
+ @return Pointer to a TChartDataItem record containing the x and y coordinates,
+ the data point mark text, and the individual data point color.
-------------------------------------------------------------------------------}
function TsWorkbookChartSource.GetItem(AIndex: Integer): PChartDataItem;
var
@@ -325,25 +374,8 @@ end;
by the workbook's FormatSettings.
-------------------------------------------------------------------------------}
function TsWorkbookChartsource.GetRange(AIndex: TsXYRange): String;
-var
- L: TStrings;
- ir: Integer;
begin
- if FWorksheets[AIndex] = nil then
- begin
- Result := '';
- exit;
- end;
-
- L := TStringList.Create;
- try
- L.Delimiter := Workbook.FormatSettings.ListSeparator;
- for ir:=0 to High(FRanges[AIndex]) do
- L.Add(GetCellRangeString(FRanges[AIndex, ir], rfAllRel, true));
- Result := FWorksheets[AIndex].Name + SHEETSEPARATOR + L.DelimitedText;
- finally
- L.Free;
- end;
+ Result := FRangeStr[AIndex];
end;
{@@ ----------------------------------------------------------------------------
@@ -358,45 +390,59 @@ begin
FWorkbook := Result;
end;
+{@@ ----------------------------------------------------------------------------
+ Helper method the prepare the information required for the series data point.
+
+ @param XOrY Identifies whether the method retrieves the x or y
+ coordinate.
+ @param APointIndex Index of the data point for which the data are required
+ @param ANumber (output) x or y coordinate of the data point
+ @param AText Data point marks label text
+-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.GetXYItem(XOrY:TsXYRange; APointIndex: Integer;
out ANumber: Double; out AText: String);
var
range: TsCellRange;
- i, j: Integer;
+ idx, ir: Integer;
len: Integer;
row, col: Cardinal;
cell: PCell;
begin
cell := nil;
- i := 0;
- case FDirections[XOrY] of
- fpsVerticalSelection:
- for j:=0 to High(FRanges[XOrY]) do begin
- range := FRanges[XOrY, j];
- len := range.Row2 - range.Row1 + 1;
- if (APointIndex >= i) and (APointIndex < i + len) then begin
- row := range.Row1 + APointIndex - i;
- col := range.Col1;
- cell := FWorksheets[XOrY].FindCell(row, col);
- break;
- end;
- inc(i, len);
- end;
+ idx := 0;
+ if FRanges[XOrY] = nil then
+ exit;
- fpsHorizontalSelection:
- for j:=0 to High(FRanges[XOrY]) do begin
- range := FRanges[XOrY, j];
- len := range.Col2 - range.Col1 + 1;
- if (APointIndex >= i) and (APointIndex < i + len) then begin
- row := range.Row1;
- col := range.Col1 + APointIndex - i;
- cell := FWorksheets[XOrY].FindCell(row, col);
- break;
- end;
- inc(i, len);
+ for range in FRanges[XOrY] do
+ begin
+ if (range.Col1 = range.Col2) then // vertical range
+ begin
+ len := range.Row2 - range.Row1 + 1;
+ if (APointIndex >= idx) and (APointIndex < idx + len) then
+ begin
+ row := range.Row1 + APointIndex - idx;
+ col := range.Col1;
+ break;
end;
+ inc(idx, len);
+ end else // horizontal range
+ if (range.Row1 = range.Row2) then
+ begin
+ len := range.Col2 - range.Col1 + 1;
+ if (APointIndex >= idx) and (APointIndex < idx + len) then
+ begin
+ row := range.Row1;
+ col := range.Col1 + APointIndex - idx;
+ break;
+ end;
+ end else
+ raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high');
end;
- if cell = nil then begin
+
+ cell := FWorksheets[XOrY].FindCell(row, col);
+
+ if cell = nil then
+ begin
ANumber := NaN;
AText := '';
end else
@@ -417,9 +463,11 @@ end;
and to worksheet changes by selecting the tab corresponding to the selected
worksheet.
- @param AChangedItems Set with elements identifying whether workbook, worksheet
- cell content or cell formatting has changed
+ @param AChangedItems Set with elements identifying whether workbook,
+ worksheet, cell content or cell formatting has changed
@param AData Additional data, not used here
+
+ @see TsNotificationItem
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.ListenerNotification(
AChangedItems: TsNotificationItems; AData: Pointer = nil);
@@ -431,9 +479,26 @@ var
begin
Unused(AData);
- // Worksheet changes
- if (lniWorksheet in AChangedItems) and (Workbook <> nil) then
- Reset;
+ // Workbook has been successfully loaded, all sheets are ready
+ if (lniWorkbook in AChangedItems) then
+ Prepare;
+
+ // Used worksheet has been renamed?
+ if (lniWorksheetRename in AChangedItems) then
+ for xy in TsXYRange do
+ if TsWorksheet(AData) = FWorksheets[xy] then begin
+ FRangeStr[xy] := BuildRangeStr(xy);
+ Prepare(xy);
+ end;
+
+ // Used worksheet will be deleted?
+ if (lniWorksheetRemoving in AChangedItems) then
+ for xy in TsXYRange do
+ if TsWorksheet(AData) = FWorksheets[xy] then begin
+ FWorksheets[xy] := nil;
+ FRangeStr[xy] := BuildRangeStr(xy);
+ Prepare(xy);
+ end;
// Cell changes: Enforce recalculation of axes if modified cell is within the
// x or y range(s).
@@ -457,6 +522,7 @@ begin
end;
end;
+
{@@ ----------------------------------------------------------------------------
Standard component notification: The ChartSource is notified that the
WorkbookSource is being removed.
@@ -469,6 +535,57 @@ begin
SetWorkbookSource(nil);
end;
+{@@ ----------------------------------------------------------------------------
+ Parses the x and y cell range strings and extracts internal information
+ (worksheet used, cell range coordinates)
+-------------------------------------------------------------------------------}
+procedure TsWorkbookChartSource.Prepare;
+begin
+ Prepare(rngX);
+ Prepare(rngY);
+end;
+
+{@@ ----------------------------------------------------------------------------
+ Parses the range string of the data specified by AIndex and extracts internal
+ information (worksheet used, cell range coordinates)
+
+ @param AIndex Identifies whether x or y cell ranges are analyzed
+-------------------------------------------------------------------------------}
+procedure TsWorkbookChartSource.Prepare(AIndex: TsXYRange);
+const
+ XY: array[TsXYRange] of string = ('x', 'y');
+var
+ range: TsCellRange;
+begin
+ if (FWorkbook = nil) or (FRangeStr[AIndex] = '') then begin
+ FWorksheets[AIndex] := nil;
+ SetLength(FRanges[AIndex], 0);
+ FPointsNumber := 0;
+ Reset;
+ exit;
+ end;
+
+ if FWorkbook.TryStrToCellRanges(FRangeStr[AIndex], FWorksheets[AIndex], FRanges[AIndex])
+ then begin
+ for range in FRanges[AIndex] do
+ if (range.Col1 <> range.Col2) and (range.Row1 <> range.Row2) then
+ raise Exception.Create('x/y ranges can only be 1 column wide or 1 row high');
+ FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
+ // If x and y ranges are of different size empty data points will be plotted.
+ Reset;
+ // Make sure to include worksheet name in RangeString.
+ FRangeStr[AIndex] := BuildRangeStr(AIndex);
+ end else
+ if (FWorkbook.GetWorksheetCount > 0) then begin
+ if FWorksheets[AIndex] = nil then
+ raise Exception.CreateFmt('Worksheet of %s cell range "%s" does not exist.',
+ [XY[AIndex], FRangeStr[AIndex]])
+ else
+ raise Exception.CreateFmt('No valid %s cell range in "%s".',
+ [XY[AIndex], FRangeStr[AIndex]]);
+ end;
+end;
+
{@@ ----------------------------------------------------------------------------
Resets internal buffers and notfies chart elements of the changes,
in particular, enforces recalculation of axis limits
@@ -491,59 +608,9 @@ end;
character defined in the Workbook's FormatSettings.
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.SetRange(AIndex: TsXYRange; const AValue: String);
-var
- s: String;
- p, i: Integer;
- L: TStrings;
- sd: TsSelectionDirection;
- sd0: TsSelectionDirection;
begin
- if (FWorkbook = nil) then
- exit;
-
- p := pos(SHEETSEPARATOR, AValue);
- if p = 0 then
- begin
- FWorksheets[AIndex] := FWorkbook.ActiveWorksheet;
- s := AValue;
- end else
- begin
- s := Copy(AValue, 1, p-1);
- FWorksheets[AIndex] := FWorkbook.GetWorksheetByName(s);
- if FWorksheets[AIndex] = nil then
- raise Exception.CreateFmt('%s cell range "%s" is in a non-existing '+
- 'worksheet.', [''+char(ord('x')+ord(AIndex)), AValue]);
- s := Copy(AValue, p+1, Length(AValue));
- end;
- L := TStringList.Create;
- try
- L.Delimiter := FWorkbook.FormatSettings.ListSeparator;
- L.DelimitedText := s;
- if L.Count = 0 then
- raise Exception.CreateFmt('No %s cell range contained in "%s".',
- [''+char(ord('x')+ord(AIndex)), AValue]
- );
- sd := fpsVerticalSelection;
- SetLength(FRanges[AIndex], L.Count);
- for i:=0 to L.Count-1 do
- if ParseCellRangeString(L[i], FRanges[AIndex, i]) then begin
- if FRanges[AIndex, i].Col1 = FRanges[AIndex, i].Col2 then
- sd := fpsVerticalSelection
- else
- if FRanges[AIndex, i].Row1 = FRanges[AIndex, i].Row2 then
- sd := fpsHorizontalSelection
- else
- raise Exception.Create('Selection can only be 1 column wide or 1 row high');
- end else
- raise Exception.CreateFmt('No valid %s cell range in "%s".',
- [''+char(ord('x')+ord(AIndex)), L[i]]
- );
- FPointsNumber := Max(CountValues(rngX), CountValues(rngY));
- // If x and y ranges are of different size empty data points will be plotted.
- Reset;
- finally
- L.Free;
- end;
+ FRangeStr[AIndex] := AValue;
+ Prepare;
end;
{@@ ----------------------------------------------------------------------------
@@ -564,12 +631,11 @@ end;
{@@ ----------------------------------------------------------------------------
Inherited ChartSource method telling the series how many y values are used.
- Currently we support only single valued data
+ Currently we support only single valued data (YCount = 1).
-------------------------------------------------------------------------------}
procedure TsWorkbookChartSource.SetYCount(AValue: Cardinal);
begin
FYCount := AValue;
- // currently not used
end;
diff --git a/components/fpspreadsheet/fpspreadsheetctrls.pas b/components/fpspreadsheet/fpspreadsheetctrls.pas
index 80182b5e0..29bf1e4cf 100644
--- a/components/fpspreadsheet/fpspreadsheetctrls.pas
+++ b/components/fpspreadsheet/fpspreadsheetctrls.pas
@@ -39,8 +39,10 @@ type
{@@ Describes during communication between WorkbookSource and visual controls
which kind of item has changed: the workbook, the worksheet, a cell value,
or a cell formatting, etc. }
- TsNotificationItem = (lniWorkbook, lniWorksheet, lniCell, lniSelection,
- lniAbortSelection, lniRow, lniPalette);
+ TsNotificationItem = (lniWorkbook,
+ lniWorksheet, lniWorksheetAdd, lniWorksheetRemoving, lniWorksheetRemove,
+ lniWorksheetRename,
+ lniCell, lniSelection, lniAbortSelection, lniRow, lniPalette);
{@@ This set accompanies the notification between WorkbookSource and visual
controls and describes which items have changed in the spreadsheet. }
TsNotificationItems = set of TsNotificationItem;
@@ -81,6 +83,8 @@ type
procedure WorksheetAddedHandler(Sender: TObject; ASheet: TsWorksheet);
procedure WorksheetChangedHandler(Sender: TObject; ASheet: TsWorksheet);
procedure WorksheetRemovedHandler(Sender: TObject; ASheetIndex: Integer);
+ procedure WorksheetRemovingHandler(Sender: TObject; AWorksheet: TsWorksheet);
+ procedure WorksheetRenamedHandler(Sender: TObject; AWorksheet: TsWorksheet);
procedure WorksheetSelectedHandler(Sender: TObject; AWorksheet: TsWorksheet);
protected
@@ -772,6 +776,8 @@ begin
FWorkbook.OnAddWorksheet := @WorksheetAddedHandler;
FWorkbook.OnChangeWorksheet := @WorksheetChangedHandler;
FWorkbook.OnRemoveWorksheet := @WorksheetRemovedHandler;
+ FWorkbook.OnRemovingWorksheet := @WorksheetRemovingHandler;
+ FWorkbook.OnRenameWorksheet := @WorksheetRenamedHandler;
FWorkbook.OnSelectWorksheet := @WorksheetSelectedHandler;
FWorkbook.OnChangePalette := @WorkbookChangedPaletteHandler;
// Pass options to workbook
@@ -1227,7 +1233,7 @@ procedure TsWorkbookSource.WorksheetAddedHandler(Sender: TObject;
ASheet: TsWorksheet);
begin
Unused(Sender);
- NotifyListeners([lniWorkbook]);
+ NotifyListeners([lniWorksheetAdd]);
SelectWorksheet(ASheet);
end;
@@ -1275,10 +1281,34 @@ begin
end else
sheet := FWorksheet;
FWorksheet := sheet; // is needed by listeners!
- NotifyListeners([lniWorkbook]);
+ NotifyListeners([lniWorksheetRemove]);
SelectWorksheet(sheet);
end;
+{@@ ----------------------------------------------------------------------------
+ Event handler called BEFORE a worksheet is deleted.
+
+ @param Sender Workbook containing the worksheet
+ @param AWorksheet Worksheet which is to be deleted
+-------------------------------------------------------------------------------}
+procedure TsWorkbookSource.WorksheetRemovingHandler(Sender: TObject;
+ AWorksheet: TsWorksheet);
+begin
+ NotifyListeners([lniWorksheetRemoving], AWorksheet);
+end;
+
+{@@ ----------------------------------------------------------------------------
+ Event handler called after a worksheet has been renamed
+
+ @param Sender Workbook containing the worksheet
+ @param AWorksheet Worksheet which has been renamed
+-------------------------------------------------------------------------------}
+procedure TsWorkbookSource.WorksheetRenamedHandler(Sender: TObject;
+ AWorksheet: TsWorksheet);
+begin
+ NotifyListeners([lniWorksheetRename], AWorksheet);
+end;
+
{@@ ----------------------------------------------------------------------------
Event handler called whenever a the workbook makes a worksheet "active".
@@ -1388,16 +1418,23 @@ var
begin
Unused(AData);
- // Workbook changed
- if (lniWorkbook in AChangedItems) then
+ // Workbook changed: new workbook, worksheet added/renamed/deleted
+ if (AChangedItems * [lniWorkbook, lniWorksheetAdd, lniWorksheetRemove, lniWorksheetRename] <> []) then
begin
inc(FLockCount); // avoid WorkbookSelect message when adding each tab
GetSheetList(Tabs);
- TabIndex := Tabs.Count-1;
+ if (lniWorkbook in AChangedItems) then
+ TabIndex := 0
+ else
+ if (lniWorksheetAdd in AChangedItems) then
+ TabIndex := Tabs.Count-1
+ else
+ if (lniWorksheetRename in AChangedItems) then
+ TabIndex := Workbook.GetWorksheetIndex(TsWorksheet(AData));
dec(FLockCount);
end;
- // Worksheet changed
+ // Worksheet selected
if (lniWorksheet in AChangedItems) and (Worksheet <> nil) then
begin
i := Tabs.IndexOf(Worksheet.Name);
diff --git a/components/fpspreadsheet/fpspreadsheetgrid.pas b/components/fpspreadsheet/fpspreadsheetgrid.pas
index 49301514b..216726eca 100644
--- a/components/fpspreadsheet/fpspreadsheetgrid.pas
+++ b/components/fpspreadsheet/fpspreadsheetgrid.pas
@@ -2902,7 +2902,8 @@ end;
procedure TsCustomWorksheetGrid.Loaded;
begin
inherited;
- NewWorkbook(FInitColCount, FInitRowCount);
+ if FWorkbookSource = nil then
+ NewWorkbook(FInitColCount, FInitRowCount);
end;
{@@ ----------------------------------------------------------------------------