diff --git a/components/fpspreadsheet/fpspreadsheet.pas b/components/fpspreadsheet/fpspreadsheet.pas index 30eb7aaad..d8a6dfae0 100755 --- a/components/fpspreadsheet/fpspreadsheet.pas +++ b/components/fpspreadsheet/fpspreadsheet.pas @@ -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 diff --git a/components/fpspreadsheet/fpspreadsheetctrls.pas b/components/fpspreadsheet/fpspreadsheetctrls.pas index e85f2bf64..3798199f3 100644 --- a/components/fpspreadsheet/fpspreadsheetctrls.pas +++ b/components/fpspreadsheet/fpspreadsheetctrls.pas @@ -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; diff --git a/components/fpspreadsheet/xlsbiff8.pas b/components/fpspreadsheet/xlsbiff8.pas index d0be69dbe..ae4d80992 100755 --- a/components/fpspreadsheet/xlsbiff8.pas +++ b/components/fpspreadsheet/xlsbiff8.pas @@ -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); diff --git a/components/fpspreadsheet/xlscommon.pas b/components/fpspreadsheet/xlscommon.pas index 54c9af12c..52a8d6731 100644 --- a/components/fpspreadsheet/xlscommon.pas +++ b/components/fpspreadsheet/xlscommon.pas @@ -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:
+ --------- ----------- ----------- ----------- + | | | 3 | | | | | 3 | 1 | + | 3 | |----------- | 3 | 1 | |-----+----- + | | | 2 | | | | | 2 | 0 | + --------- ---------- ----------- ------------------------------------------------------------------------------------------} 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