mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-06 11:52:41 +02:00

OnlinePackageManager: Adapt to the new virtualtreeview package. git-svn-id: trunk@60132 -
407 lines
14 KiB
PHP
407 lines
14 KiB
PHP
{$warnings off}
|
|
{$hints off}
|
|
function TBaseVirtualTree.GetTreeFromDataObject(const DataObject: IDataObject): TBaseVirtualTree;
|
|
|
|
// Returns the owner/sender of the given data object by means of a special clipboard format
|
|
// or nil if the sender is in another process or no virtual tree at all.
|
|
|
|
var
|
|
Medium: TStgMedium;
|
|
Data: PVTReference;
|
|
|
|
begin
|
|
Result := nil;
|
|
{
|
|
if Assigned(DataObject) then
|
|
begin
|
|
StandardOLEFormat.cfFormat := CF_VTREFERENCE;
|
|
if DataObject.GetData(StandardOLEFormat, Medium) = S_OK then
|
|
begin
|
|
Data := GlobalLock(Medium.hGlobal);
|
|
if Assigned(Data) then
|
|
begin
|
|
if Data.Process = GetCurrentProcessID then
|
|
Result := Data.Tree;
|
|
GlobalUnlock(Medium.hGlobal);
|
|
end;
|
|
ReleaseStgMedium(@Medium);
|
|
end;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.RenderOLEData(const FormatEtcIn: TFormatEtc; out Medium: TStgMedium;
|
|
ForClipboard: Boolean): HResult;
|
|
|
|
// Returns a memory expression of all currently selected nodes in the Medium structure.
|
|
// Note: The memory requirement of this method might be very high. This depends however on the requested storage format.
|
|
// For HGlobal (a global memory block) we need to render first all nodes to local memory and copy this then to
|
|
// the global memory in Medium. This is necessary because we have first to determine how much
|
|
// memory is needed before we can allocate it. Hence for a short moment we need twice the space as used by the
|
|
// nodes alone (plus the amount the nodes need in the tree anyway)!
|
|
// With IStream this does not happen. We directly stream out the nodes and pass the constructed stream along.
|
|
|
|
//--------------- local function --------------------------------------------
|
|
{
|
|
procedure WriteNodes(Stream: TStream);
|
|
|
|
var
|
|
Selection: TNodeArray;
|
|
I: Integer;
|
|
|
|
begin
|
|
if ForClipboard then
|
|
Selection := GetSortedCutCopySet(True)
|
|
else
|
|
Selection := GetSortedSelection(True);
|
|
for I := 0 to High(Selection) do
|
|
WriteNode(Stream, Selection[I]);
|
|
end;
|
|
|
|
//--------------- end local function ----------------------------------------
|
|
|
|
var
|
|
Data: PCardinal;
|
|
ResPointer: Pointer;
|
|
ResSize: Integer;
|
|
OLEStream: IStream;
|
|
VCLStream: TStream;
|
|
}
|
|
begin
|
|
{
|
|
FillChar(Medium, SizeOf(Medium), 0);
|
|
// We can render the native clipboard format in two different storage media.
|
|
if (FormatEtcIn.cfFormat = CF_VIRTUALTREE) and (FormatEtcIn.tymed and (TYMED_HGLOBAL or TYMED_ISTREAM) <> 0) then
|
|
begin
|
|
VCLStream := nil;
|
|
try
|
|
Medium.PunkForRelease := nil;
|
|
// Return data in one of the supported storage formats, prefer IStream.
|
|
if FormatEtcIn.tymed and TYMED_ISTREAM <> 0 then
|
|
begin
|
|
// Create an IStream on a memory handle (here it is 0 which indicates to implicitely allocated a handle).
|
|
// Do not use TStreamAdapter as it is not compatible with OLE (when flushing the clipboard OLE wants the HGlobal
|
|
// back which is not supported by TStreamAdapater).
|
|
CreateStreamOnHGlobal(0, True, OLEStream);
|
|
|
|
VCLStream := TOLEStream.Create(OLEStream);
|
|
WriteNodes(VCLStream);
|
|
// Rewind stream.
|
|
VCLStream.Position := 0;
|
|
Medium.tymed := TYMED_ISTREAM;
|
|
IUnknown(Medium.Pstm) := OLEStream;
|
|
Result := S_OK;
|
|
end
|
|
else
|
|
begin
|
|
VCLStream := TMemoryStream.Create;
|
|
WriteNodes(VCLStream);
|
|
ResPointer := TMemoryStream(VCLStream).Memory;
|
|
ResSize := VCLStream.Position;
|
|
|
|
// Allocate memory to hold the string.
|
|
if ResSize > 0 then
|
|
begin
|
|
Medium.hGlobal := GlobalAlloc(GHND or GMEM_SHARE, ResSize + SizeOf(Cardinal));
|
|
Data := GlobalLock(Medium.hGlobal);
|
|
// Store the size of the data too, for easy retrival.
|
|
Data^ := ResSize;
|
|
Inc(Data);
|
|
Move(ResPointer^, Data^, ResSize);
|
|
GlobalUnlock(Medium.hGlobal);
|
|
Medium.tymed := TYMED_HGLOBAL;
|
|
|
|
Result := S_OK;
|
|
end
|
|
else
|
|
Result := E_FAIL;
|
|
end;
|
|
finally
|
|
// We can free the VCL stream here since it was either a pure memory stream or only a wrapper around
|
|
// the OLEStream which exists independently.
|
|
VCLStream.Free;
|
|
end;
|
|
end
|
|
else // Ask application descendants to render self defined formats.
|
|
Result := DoRenderOLEData(FormatEtcIn, Medium, ForClipboard);
|
|
}
|
|
end;
|
|
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
type
|
|
// needed to handle OLE global memory objects
|
|
TOLEMemoryStream = class(TCustomMemoryStream)
|
|
public
|
|
function Write(const Buffer; Count: Integer): Longint; override;
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TOLEMemoryStream.Write(const Buffer; Count: Integer): Integer;
|
|
|
|
begin
|
|
//raise EStreamError.CreateRes(PResStringRec(@SCantWriteResourceStreamError));
|
|
raise EStreamError.Create(SCantWriteResourceStreamError);
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TBaseVirtualTree.ProcessOLEData(Source: TBaseVirtualTree; DataObject: IDataObject; TargetNode: PVirtualNode;
|
|
Mode: TVTNodeAttachMode; Optimized: Boolean): Boolean;
|
|
|
|
// Recreates the (sub) tree structure serialized into memory and provided by DataObject. The new nodes are attached to
|
|
// the passed node or FRoot if TargetNode is nil according to Mode. Optimized can be set to True if the entire operation
|
|
// happens within the same process (i.e. sender and receiver of the OLE operation are located in the same process).
|
|
// Optimize = True makes only sense if the operation to carry out is a move hence it is also the indication of the
|
|
// operation to be done here. Source is the source of the OLE data and only of use (and usually assigned) when
|
|
// an OLE operation takes place in the same application.
|
|
// Returns True on success, i.e. the CF_VIRTUALTREE format is supported by the data object and the structure could be
|
|
// recreated, otherwise False.
|
|
|
|
var
|
|
Medium: TStgMedium;
|
|
Stream: TStream;
|
|
Data: Pointer;
|
|
Node: PVirtualNode;
|
|
Nodes: TNodeArray;
|
|
I: Integer;
|
|
Res: HRESULT;
|
|
ChangeReason: TChangeReason;
|
|
|
|
begin
|
|
{
|
|
Nodes := nil;
|
|
// Check the data format available by the data object.
|
|
with StandardOLEFormat do
|
|
begin
|
|
// Read best format.
|
|
cfFormat := CF_VIRTUALTREE;
|
|
end;
|
|
Result := DataObject.QueryGetData(StandardOLEFormat) = S_OK;
|
|
if Result and not (toReadOnly in FOptions.FMiscOptions) then
|
|
begin
|
|
BeginUpdate;
|
|
Result := False;
|
|
try
|
|
if TargetNode = nil then
|
|
TargetNode := FRoot;
|
|
if TargetNode = FRoot then
|
|
begin
|
|
case Mode of
|
|
amInsertBefore:
|
|
Mode := amAddChildFirst;
|
|
amInsertAfter:
|
|
Mode := amAddChildLast;
|
|
end;
|
|
end;
|
|
|
|
// Optimized means source is known and in the same process so we can access its pointers, which avoids duplicating
|
|
// the data while doing a serialization. Can only be used with cut'n paste and drag'n drop with move effect.
|
|
if Optimized then
|
|
begin
|
|
if tsOLEDragging in Source.FStates then
|
|
Nodes := Source.FDragSelection
|
|
else
|
|
Nodes := Source.GetSortedCutCopySet(True);
|
|
|
|
if Mode in [amInsertBefore,amAddChildLast] then
|
|
begin
|
|
for I := 0 to High(Nodes) do
|
|
if not HasAsParent(TargetNode, Nodes[I]) then
|
|
Source.MoveTo(Nodes[I], TargetNode, Mode, False);
|
|
end
|
|
else
|
|
begin
|
|
for I := High(Nodes) downto 0 do
|
|
if not HasAsParent(TargetNode, Nodes[I]) then
|
|
Source.MoveTo(Nodes[I], TargetNode, Mode, False);
|
|
end;
|
|
Result := True;
|
|
end
|
|
else
|
|
begin
|
|
if Source = Self then
|
|
ChangeReason := crNodeCopied
|
|
else
|
|
ChangeReason := crNodeAdded;
|
|
Res := DataObject.GetData(StandardOLEFormat, Medium);
|
|
if Res = S_OK then
|
|
begin
|
|
case Medium.tymed of
|
|
TYMED_ISTREAM, // IStream interface
|
|
TYMED_HGLOBAL: // global memory block
|
|
begin
|
|
Stream := nil;
|
|
if Medium.tymed = TYMED_ISTREAM then
|
|
Stream := TOLEStream.Create(IUnknown(Medium.Pstm) as IStream)
|
|
else
|
|
begin
|
|
Data := GlobalLock(Medium.hGlobal);
|
|
if Assigned(Data) then
|
|
begin
|
|
// Get the total size of data to retrieve.
|
|
I := PCardinal(Data)^;
|
|
Inc(PCardinal(Data));
|
|
Stream := TOLEMemoryStream.Create;
|
|
TOLEMemoryStream(Stream).SetPointer(Data, I);
|
|
end;
|
|
end;
|
|
if Assigned(Stream) then
|
|
try
|
|
while Stream.Position < Stream.Size do
|
|
begin
|
|
Node := MakeNewNode;
|
|
InternalConnectNode(Node, TargetNode, Self, Mode);
|
|
InternalAddFromStream(Stream, VTTreeStreamVersion, Node);
|
|
// This seems a bit strange because of the callback for granting to add the node
|
|
// which actually comes after the node has been added. The reason is that the node must
|
|
// contain valid data otherwise I don't see how the application can make a funded decision.
|
|
if not DoNodeCopying(Node, TargetNode) then
|
|
DeleteNode(Node)
|
|
else
|
|
DoNodeCopied(Node);
|
|
StructureChange(Node, ChangeReason);
|
|
|
|
// In order to maintain the same node order when restoring nodes in the case of amInsertAfter
|
|
// we have to move the reference node continously. Othwise we would end up with reversed node order.
|
|
if Mode = amInsertAfter then
|
|
TargetNode := Node;
|
|
end;
|
|
Result := True;
|
|
finally
|
|
Stream.Free;
|
|
if Medium.tymed = TYMED_HGLOBAL then
|
|
GlobalUnlock(Medium.hGlobal);
|
|
end;
|
|
end;
|
|
end;
|
|
ReleaseStgMedium(@Medium);
|
|
end;
|
|
end;
|
|
finally
|
|
EndUpdate;
|
|
end;
|
|
end;
|
|
}
|
|
end;
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
|
|
function TCustomVirtualStringTree.ContentToClipboard(Format: Word; Source: TVSTTextSourceType): HGLOBAL;
|
|
|
|
// This method constructs a shareable memory object filled with string data in the required format. Supported are:
|
|
// CF_TEXT - plain ANSI text (Unicode text is converted using the user's current locale)
|
|
// CF_UNICODETEXT - plain Unicode text
|
|
// CF_CSV - comma separated plain ANSI text
|
|
// CF_VRTF + CF_RTFNOOBS - rich text (plain ANSI)
|
|
// CF_HTML - HTML text encoded using UTF-8
|
|
//
|
|
// Result is the handle to a globally allocated memory block which can directly be used for clipboard and drag'n drop
|
|
// transfers. The caller is responsible for freeing the memory. If for some reason the content could not be rendered
|
|
// the Result is 0.
|
|
|
|
//--------------- local function --------------------------------------------
|
|
{
|
|
procedure MakeFragment(var HTML: string);
|
|
|
|
// Helper routine to build a properly-formatted HTML fragment.
|
|
|
|
const
|
|
Version = 'Version:1.0'#13#10;
|
|
StartHTML = 'StartHTML:';
|
|
EndHTML = 'EndHTML:';
|
|
StartFragment = 'StartFragment:';
|
|
EndFragment = 'EndFragment:';
|
|
DocType = '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">';
|
|
HTMLIntro = '<html><head><META http-equiv=Content-Type content="text/html; charset=utf-8">' +
|
|
'</head><body><!--StartFragment-->';
|
|
HTMLExtro = '<!--EndFragment--></body></html>';
|
|
NumberLengthAndCR = 10;
|
|
|
|
// Let the compiler determine the description length.
|
|
DescriptionLength = Length(Version) + Length(StartHTML) + Length(EndHTML) + Length(StartFragment) +
|
|
Length(EndFragment) + 4 * NumberLengthAndCR;
|
|
|
|
var
|
|
Description: string;
|
|
StartHTMLIndex,
|
|
EndHTMLIndex,
|
|
StartFragmentIndex,
|
|
EndFragmentIndex: Integer;
|
|
|
|
begin
|
|
// The HTML clipboard format is defined by using byte positions in the entire block where HTML text and
|
|
// fragments start and end. These positions are written in a description. Unfortunately the positions depend on the
|
|
// length of the description but the description may change with varying positions.
|
|
// To solve this dilemma the offsets are converted into fixed length strings which makes it possible to know
|
|
// the description length in advance.
|
|
StartHTMLIndex := DescriptionLength; // position 0 after the description
|
|
StartFragmentIndex := StartHTMLIndex + Length(DocType) + Length(HTMLIntro);
|
|
EndFragmentIndex := StartFragmentIndex + Length(HTML);
|
|
EndHTMLIndex := EndFragmentIndex + Length(HTMLExtro);
|
|
|
|
Description := Version +
|
|
SysUtils.Format('%s%.8d', [StartHTML, StartHTMLIndex]) + #13#10 +
|
|
SysUtils.Format('%s%.8d', [EndHTML, EndHTMLIndex]) + #13#10 +
|
|
SysUtils.Format('%s%.8d', [StartFragment, StartFragmentIndex]) + #13#10 +
|
|
SysUtils.Format('%s%.8d', [EndFragment, EndFragmentIndex]) + #13#10;
|
|
HTML := Description + DocType + HTMLIntro + HTML + HTMLExtro;
|
|
end;
|
|
}
|
|
//--------------- end local function ----------------------------------------
|
|
|
|
var
|
|
Data: Pointer;
|
|
DataSize: Cardinal;
|
|
S: string;
|
|
WS: WideString;
|
|
P: Pointer;
|
|
|
|
begin
|
|
Result := 0;
|
|
{
|
|
case Format of
|
|
CF_TEXT:
|
|
begin
|
|
S := ContentToText(Source, #9) + #0;
|
|
Data := PChar(S);
|
|
DataSize := Length(S);
|
|
end;
|
|
CF_UNICODETEXT:
|
|
begin
|
|
WS := ContentToUnicode(Source, #9) + #0;
|
|
Data := PWideChar(WS);
|
|
DataSize := 2 * Length(WS);
|
|
end;
|
|
else
|
|
if Format = CF_CSV then
|
|
S := ContentToText(Source, ListSeparator) + #0
|
|
else
|
|
if (Format = CF_VRTF) or (Format = CF_VRTFNOOBJS) then
|
|
S := ContentToRTF(Source) + #0
|
|
else
|
|
if Format = CF_HTML then
|
|
begin
|
|
S := ContentToHTML(Source);
|
|
// Build a valid HTML clipboard fragment.
|
|
MakeFragment(S);
|
|
S := S + #0;
|
|
end;
|
|
Data := PChar(S);
|
|
DataSize := Length(S);
|
|
end;
|
|
|
|
if DataSize > 0 then
|
|
begin
|
|
Result := GlobalAlloc(GHND or GMEM_SHARE, DataSize);
|
|
P := GlobalLock(Result);
|
|
Move(Data^, P^, DataSize);
|
|
GlobalUnlock(Result);
|
|
end;
|
|
}
|
|
end;
|