fpspreadsheet: Add "Paste from clipboard" (for biff8 format)

git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@4355 8e941d3f-bd1b-0410-a28a-d453659cc2b4
This commit is contained in:
wp_xxyyzz
2015-09-24 15:27:28 +00:00
parent ad8daec47c
commit 39b73bcfec
4 changed files with 211 additions and 41 deletions

View File

@ -655,18 +655,22 @@ type
out SheetType: TsSpreadsheetFormat): Boolean; overload;
class function GetFormatFromFileHeader(AStream: TStream;
out SheetType: TsSpreadsheetFormat): Boolean; overload;
function CreateSpreadReader(AFormat: TsSpreadsheetFormat): TsBasicSpreadReader;
function CreateSpreadReader(AFormat: TsSpreadsheetFormat;
AClipboardMode: Boolean = false): TsBasicSpreadReader;
function CreateSpreadWriter(AFormat: TsSpreadsheetFormat;
AClipboardMode: Boolean = false): TsBasicSpreadWriter;
procedure ReadFromFile(AFileName: string; AFormat: TsSpreadsheetFormat); overload;
procedure ReadFromFile(AFileName: string); overload;
procedure ReadFromFileIgnoringExtension(AFileName: string);
procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
procedure ReadFromStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
AClipboardMode: Boolean = false);
procedure WriteToFile(const AFileName: string;
const AFormat: TsSpreadsheetFormat;
const AOverwriteExisting: Boolean = False); overload;
procedure WriteToFile(const AFileName: String; const AOverwriteExisting: Boolean = False); overload;
procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat; AClipboardMode: Boolean = false);
procedure WriteToFile(const AFileName: String;
const AOverwriteExisting: Boolean = False); overload;
procedure WriteToStream(AStream: TStream; AFormat: TsSpreadsheetFormat;
AClipboardMode: Boolean = false);
{ Worksheet list handling methods }
function AddWorksheet(AName: string;
@ -727,6 +731,7 @@ type
{ Clipboard }
procedure CopyToClipboardStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
procedure PasteFromClipboardStream(AStream: TStream; AFormat: TsSpreadsheetFormat);
(*
{ Color handling }
function FPSColorToHexString(AColor: TsColor; ARGBColor: TFPColor): String;
@ -1594,10 +1599,10 @@ begin
toRow := AToCell^.Row;
toCol := AToCell^.Col;
// Copy cell values and formats
// Copy cell values
AToCell^ := AFromCell^;
// Fix row and column indexes overwritten
// Restore row and column indexes overwritten by the previous instruction
AToCell^.Row := toRow;
AToCell^.Col := toCol;
AToCell^.Worksheet := self;
@ -3558,12 +3563,9 @@ end;
-------------------------------------------------------------------------------}
procedure TsWorksheet.SelectCell(ARow, ACol: Cardinal);
begin
if not FWorkbook.NotificationsEnabled then
exit;
FActiveCellRow := ARow;
FActiveCellCol := ACol;
if Assigned(FOnSelectCell) then
if FWorkbook.NotificationsEnabled and Assigned(FOnSelectCell) then
FOnSelectCell(Self, ARow, ACol);
end;
@ -6603,17 +6605,22 @@ end;
to workbook. An exception is raised when the document has
a different format.
@param AClipboardMode If TRUE it is checked that the reader class supports
clipboard access.
@return An instance of a TsCustomSpreadReader descendent which is able to
read the given file format.
-------------------------------------------------------------------------------}
function TsWorkbook.CreateSpreadReader(AFormat: TsSpreadsheetFormat): TsBasicSpreadReader;
function TsWorkbook.CreateSpreadReader(AFormat: TsSpreadsheetFormat;
AClipboardMode: Boolean = false): TsBasicSpreadReader;
var
i: Integer;
begin
Result := nil;
for i := 0 to Length(GsSpreadFormats) - 1 do
if GsSpreadFormats[i].Format = AFormat then
for i := 0 to High(GsSpreadFormats) do
if (GsSpreadFormats[i].Format = AFormat) and
((not AClipboardMode) or GsSpreadformats[i].CanReadFromClipboard) then
begin
Result := GsSpreadFormats[i].ReaderClass.Create(self);
Break;
@ -6641,9 +6648,9 @@ var
begin
Result := nil;
for i := 0 to Length(GsSpreadFormats) - 1 do
for i := 0 to High(GsSpreadFormats) do
if (GsSpreadFormats[i].Format = AFormat) and
(AClipboardMode or GsSpreadFormats[i].CanWriteToClipboard) then
((not AClipboardMode) or GsSpreadFormats[i].CanWriteToClipboard) then
begin
Result := GsSpreadFormats[i].WriterClass.Create(self);
Break;
@ -6807,9 +6814,10 @@ end;
@param AStream Stream being read
@param AFormat File format assumed.
@param AClipboardMode The calling method has read the stream from the clipboard.
-------------------------------------------------------------------------------}
procedure TsWorkbook.ReadFromStream(AStream: TStream;
AFormat: TsSpreadsheetFormat);
AFormat: TsSpreadsheetFormat; AClipboardMode: Boolean = false);
var
AReader: TsBasicSpreadReader;
ok: Boolean;
@ -6821,7 +6829,9 @@ begin
try
ok := false;
AStream.Position := 0;
AReader.ReadFromStream(AStream);
if AClipboardMode then
AReader.ReadFromClipboardStream(AStream) else
AReader.ReadFromStream(AStream);
ok := true;
finally
dec(FLockCount);
@ -6970,6 +6980,10 @@ begin
// name here as well.
if (FLockCount = 0) and Assigned(FOnAddWorksheet) then
FOnAddWorksheet(self, Result);
// Make sure that there is an "active" worksheet
if FActiveWorksheet = nil then
SelectWorksheet(result);
end;
{@@ ----------------------------------------------------------------------------
@ -7775,7 +7789,7 @@ begin
clipsheet.SelectCell(ActiveWorksheet.ActiveCellRow, ActiveWorksheet.ActiveCellCol);
// Write this workbook to a stream. Set the last parameter (ClipboardMode)
// to TRUE to use the dedicated clipboard routine.
// to TRUE to use the dedicated clipboard routine if needed.
clipbook.WriteToStream(AStream, AFormat, true);
// The calling routine which copies the stream to the clipboard requires
@ -7786,7 +7800,80 @@ begin
end;
end;
(*
{@@ ----------------------------------------------------------------------------
Copies the cells stored in the specified stream to the active worksheet.
The provided stream contains data from the system's clipboard.
Note that transfer from the clipboard to the stream has to be done by the
calling routine since fpspreadsheet does not "know" the system's clipboard.
-------------------------------------------------------------------------------}
procedure TsWorkbook.PasteFromClipboardStream(AStream: TStream;
AFormat: TsSpreadsheetFormat);
var
clipbook: TsWorkbook;
clipsheet: TsWorksheet;
sel: TsCellRange;
selArray: TsCellRangeArray;
r, c: LongInt;
dr, dc: LongInt;
srccell, destcell: PCell;
i, n: Integer;
begin
if AStream = nil then
exit;
if ActiveWorksheet = nil then
exit;
// Create workbook into which the clipboard stream will write
clipbook := TsWorkbook.Create;
try
clipbook.Options := clipbook.Options + [boReadFormulas];
// Read stream into this temporary workbook
// Set last parameter (ClipboardMode) to TRUE to activate special format
// treatment for clipboard, if needed.
clipbook.ReadFromStream(AStream, AFormat, true);
clipsheet := clipbook.GetWorksheetByIndex(0);
// Find offset of active cell to left/top cell in temporary sheet
dr := LongInt(ActiveWorksheet.ActiveCellRow) - clipsheet.GetFirstRowIndex(true);
dc := LongInt(ActiveWorksheet.ActiveCellCol) - clipsheet.GetFirstColIndex(true);
// Copy cells from temporary workbook to active worksheet.
// Shift them such that the top/left cell of temp sheet is at the active cell.
for srccell in clipsheet.Cells do
begin
r := LongInt(srcCell.Row) + dr;
c := LongInt(srcCell.Col) + dc;
destcell := ActiveWorksheet.GetCell(r, c);
ActiveWorksheet.CopyCell(srccell, destcell);
end;
// Select the same cells as in the clipboard
n := clipsheet.GetSelectionCount;
SetLength(selArray, n);
for i := 0 to n-1 do
begin
sel := clipsheet.GetSelection[i];
selArray[i].Row1 := sel.Row1 + dr;
selArray[i].Col1 := sel.Col1 + dc;
selArray[i].Row2 := sel.Row2 + dr;
selArray[i].Col2 := sel.Col2 + dc;
end;
ActiveWorksheet.SetSelection(selArray);
// Select active cell. If not found in the file, let's use the last cell of the selections
if (clipsheet.ActiveCellRow <> 0) and (clipsheet.ActiveCellCol <> 0) then
begin
r := clipsheet.ActiveCellRow;
c := clipsheet.ActiveCellCol;
end else
begin
r := sel.Row2;
c := sel.Col2;
end;
ActiveWorksheet.SelectCell(r + dr, c + dc);
finally
clipbook.Free;
end;
end;
(*
{@@ ----------------------------------------------------------------------------
Adds a color to the palette and returns its palette index, but only if the
color does not already exist - in this case, it returns the index of the

View File

@ -1151,11 +1151,15 @@ var
cell: PCell;
stream: TStream;
begin
sel := FWorksheet.GetSelection;
if Length(sel) = 0 then
exit;
Clipboard.Open;
try
Clipboard.Clear;
// Ensure the 'Biff8' clipboard format is registered
// Ensure that the 'Biff8' clipboard format is registered
cfBiff8Format := Clipboard.FindFormatID('Biff8');
If cfBiff8Format = 0 Then
cfBiff8Format := RegisterClipboardFormat('Biff8');
@ -1189,9 +1193,6 @@ begin
FCutPending := false;
ClearCellClipboard;
sel := FWorksheet.GetSelection;
if Length(sel) = 0 then
exit;
for i:=0 to High(sel) do
for r := sel[i].Row1 to sel[i].Row2 do
@ -1232,7 +1233,51 @@ var
cell: PCell;
baserng, rng: TsCellRange;
baseRow, baseCol: Cardinal;
cf: Integer;
fmt: TsSpreadsheetFormat;
stream: TStream;
begin
Clipboard.Open;
try
// Ensure that the 'Biff8' clipboard format is registered
// In fpspreadsheet, this is currently the clipboard format which supports most features.
fmt := sfExcel8;
cf := Clipboard.FindFormatID('Biff8');
If cf = 0 Then
cf := RegisterClipboardFormat('Biff8');
// If Biff8 cannot be found use Biff5 instead
if cf = 0 then
begin
fmt := sfExcel5;
cf := Clipboard.FindFormatID('Biff5');
If cf = 0 Then
cf := RegisterClipboardFormat('Biff5');
end;
// Exit if there are no spreadsheet data in clipboard
if cf = 0 then
MessageDlg('No appropriate spreadsheet data in clipboard', mtError, [mbOk], 0);
stream := TMemoryStream.Create;
try
Clipboard.GetFormat(cf, stream);
FWorkbook.PasteFromClipboardStream(stream, fmt);
// To do: HTML format, CSV format, XML format, TEXT format
// I don't know which format is written by xlsx and ods natively.
finally
stream.Free;
end;
finally
Clipboard.Close;
end;
exit;
if CellClipboard.Count = 0 then
exit;

View File

@ -3443,7 +3443,7 @@ end;
initialization
// Registers this reader / writer in fpSpreadsheet
RegisterSpreadFormat(TsSpreadBIFF8Reader, TsSpreadBIFF8Writer, sfExcel8, false, true);
RegisterSpreadFormat(TsSpreadBIFF8Reader, TsSpreadBIFF8Writer, sfExcel8, true, true);
// Converts the palette to litte-endian
MakeLEPalette(PALETTE_BIFF8);

View File

@ -357,6 +357,7 @@ type
FPalette: TsPalette;
FWorksheetNames: TStrings;
FCurrentWorksheet: Integer;
FActivePane: Integer;
procedure AddBuiltinNumFormats; override;
procedure ApplyCellFormatting(ACell: PCell; XFIndex: Word); virtual;
@ -452,6 +453,7 @@ type
public
constructor Create(AWorkbook: TsWorkbook); override;
destructor Destroy; override;
procedure ReadFromClipboardStream(AStream: TStream); override;
end;
@ -463,7 +465,6 @@ type
FCodePage: String; // in a format prepared for lconvencoding.ConvertEncoding
FFirstNumFormatIndexInFile: Integer;
FPalette: TsPalette;
FClipboardMode: Boolean;
procedure AddBuiltinNumFormats; override;
function FindXFIndex(ACell: PCell): Integer; virtual;
@ -823,6 +824,9 @@ begin
// Initial base date in case it won't be read from file
FDateMode := dm1900;
// Index of active pane (no panes --> index is 3 ... OMG!...)
FActivePane := 3;
// Limitations of BIFF5 and BIFF8 file format
FLimitations.MaxColCount := 256;
FLimitations.MaxRowCount := 65536;
@ -1332,6 +1336,16 @@ begin
FWorksheet.DefaultRowHeight := h - ROW_HEIGHT_CORRECTION;
end;
{@@ ----------------------------------------------------------------------------
Public specialized stream reading method for clipboard access.
For BIFF file format, data are stored in the clipboard in the same way as in
a regular stream/file.
-------------------------------------------------------------------------------}
procedure TsSpreadBIFFReader.ReadFromClipboardStream(AStream: TStream);
begin
ReadFromStream(AStream);
end;
{@@ ----------------------------------------------------------------------------
Reads the (number) FORMAT record for formatting numerical data
To be overridden by descendants.
@ -1723,12 +1737,16 @@ begin
if (FWorksheet.LeftPaneWidth = 0) and (FWorksheet.TopPaneHeight = 0) then
FWorksheet.Options := FWorksheet.Options - [soHasFrozenPanes];
{ There's more information which is not supported here:
Offset Size Description
4 2 Index to first visible row in bottom pane(s)
6 2 Index to first visible column in right pane(s)
8 1 Identifier of pane with active cell cursor (see below)
[9] 1 Not used (BIFF5-BIFF8 only, not written in BIFF2-BIFF4) }
// Index to first visible row in bottom pane(s) -- not used
AStream.ReadWord;
// Index to first visible column in right pane(s) -- not used
AStream.ReadWord;
// Identifier of pane with active cell cursor
FActivePane := AStream.ReadByte;
// If BIFF5-BIFF8 there is 1 more byte which is not used here.
end;
{@@ ----------------------------------------------------------------------------
@ -2175,12 +2193,14 @@ end;
-------------------------------------------------------------------------------}
procedure TsSpreadBIFFReader.ReadSELECTION(AStream: TStream);
var
{%H-}actPane: byte;
{%H-}paneIdx: byte;
{%H-}rngIndex: Word;
actRow, actCol: Word;
n, i: Integer;
sel: TsCellRangeArray;
begin
// Active pane
actPane := AStream.ReadByte;
// Pane index
paneIdx := AStream.ReadByte;
// Row index of the active cell
actRow := WordLEToN(AStream.ReadWord);
@ -2192,9 +2212,24 @@ begin
rngIndex := WordLEToN(AStream.ReadWord);
// Count of selected ranges
// Selected ranges --> ignore
n := WordLEToN(AStream.ReadWord);
SetLength(sel, n);
FWorksheet.SelectCell(actRow, actCol);
// Selected ranges
for i := 0 to n - 1 do
begin
sel[i].Row1 := WordLEToN(AStream.ReadWord);
sel[i].Row2 := WordLEToN(AStream.ReadWord);
sel[i].Col1 := AStream.ReadByte; // 8-bit column indexes even for biff8!
sel[i].Col2 := AStream.ReadByte;
end;
// Apply selections to worksheet, but only in the pane with the cursor
if paneIdx = FActivePane then
begin
if Length(sel) > 0 then FWorksheet.SetSelection(sel);
FWorksheet.SelectCell(actRow, actCol);
end;
end;
{@@ ----------------------------------------------------------------------------
@ -3126,6 +3161,13 @@ end;
Writes a PANE record to the stream.
Valid for all BIFF versions. The difference for BIFF5-BIFF8 is a non-used
byte at the end. Activate IsBiff58 in these cases.
Pane numbering scheme: <pre>
--------- ----------- ----------- -----------
| | | 3 | | | | | 3 | 1 |
| 3 | |----------- | 3 | 1 | |-----+-----
| | | 2 | | | | | 2 | 0 |
--------- ---------- ----------- ----------- </pre>
-------------------------------------------------------------------------------}
procedure TsSpreadBIFFWriter.WritePane(AStream: TStream; ASheet: TsWorksheet;
IsBiff58: Boolean; out ActivePane: Byte);
@ -3170,11 +3212,7 @@ begin
else
AStream.WriteWord(WordToLE(0));
{ Identifier of pane with active cell cursor
0 = right-bottom
1 = right-top
2 = left-bottom
3 = left-top }
{ Identifier of pane with active cell cursor, see header for numbering scheme }
if (soHasFrozenPanes in ASheet.Options) then begin
if (ASheet.LeftPaneWidth = 0) and (ASheet.TopPaneHeight = 0) then
ActivePane := 3