mirror of
https://github.com/doublecmd/doublecmd.git
synced 2026-06-28 10:02:14 +00:00
471 lines
12 KiB
ObjectPascal
471 lines
12 KiB
ObjectPascal
{
|
|
Drag&Drop operations for QT.
|
|
}
|
|
|
|
unit uDragDropQt;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, Controls, uDragDropEx,
|
|
qtwidgets
|
|
{$IF DEFINED(LCLQT)}
|
|
, qt4
|
|
{$ELSEIF DEFINED(LCLQT5)}
|
|
, qt5
|
|
{$ENDIF}
|
|
;
|
|
|
|
type
|
|
TDragDropSourceQT = class(TDragDropSource)
|
|
|
|
function RegisterEvents(DragBeginEvent : uDragDropEx.TDragBeginEvent;
|
|
RequestDataEvent: uDragDropEx.TRequestDataEvent;
|
|
DragEndEvent : uDragDropEx.TDragEndEvent): Boolean; override;
|
|
|
|
function DoDragDrop(const FileNamesList: TStringList;
|
|
MouseButton: TMouseButton;
|
|
ScreenStartPoint: TPoint): Boolean; override;
|
|
|
|
private
|
|
function GetWidget: QWidgetH;
|
|
end;
|
|
|
|
TDragDropTargetQT = class(TDragDropTarget)
|
|
public
|
|
constructor Create(TargetControl: TWinControl); override;
|
|
|
|
function RegisterEvents(DragEnterEvent: uDragDropEx.TDragEnterEvent;
|
|
DragOverEvent : uDragDropEx.TDragOverEvent;
|
|
DropEvent : uDragDropEx.TDropEvent;
|
|
DragLeaveEvent: uDragDropEx.TDragLeaveEvent): Boolean; override;
|
|
|
|
procedure UnregisterEvents; override;
|
|
|
|
private
|
|
FEventHook : QObject_hookH;
|
|
|
|
function EventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl; // called by QT
|
|
|
|
function GetWidget: QWidgetH;
|
|
|
|
function HasSupportedFormat(DropEvent: QDropEventH): Boolean;
|
|
|
|
function OnDragEnter(DragEnterEvent: QDragEnterEventH): Boolean;
|
|
function OnDragOver(DragMoveEvent: QDragMoveEventH): Boolean;
|
|
function OnDrop(DropEvent: QDropEventH): Boolean;
|
|
function OnDragLeave(DragLeaveEvent: QDragLeaveEventH): Boolean;
|
|
end;
|
|
|
|
function QtActionToDropEffect(Action: QtDropAction): TDropEffect;
|
|
function DropEffectToQtAction(DropEffect: TDropEffect): QtDropAction;
|
|
function QtDropEventPointToLCLPoint(const PDropEventPoint: PQtPoint): TPoint;
|
|
|
|
|
|
implementation
|
|
|
|
uses
|
|
uClipboard, LCLIntf;
|
|
|
|
|
|
const
|
|
uriListMimeW : WideString = uriListMime;
|
|
textPlainMimeW : WideString = textPlainMime;
|
|
|
|
|
|
{$IF DEFINED(LCLQT5)}
|
|
function QDropEvent_pos(handle: QDropEventH): PQtPoint; overload;
|
|
const
|
|
retval: TQtPoint = (x: 0; y: 0);
|
|
begin
|
|
Result:= @retval;
|
|
QDropEvent_pos(handle, Result);
|
|
end;
|
|
{$ENDIF}
|
|
|
|
function GetWidgetFromLCLControl(AWinControl: TWinControl): QWidgetH; inline;
|
|
begin
|
|
// Custom controls (TQtCustomControl) are created by LCL as
|
|
// QAbstractScrollArea with a viewport (and two scrollbars).
|
|
// We want the viewport to be the source/target of drag&drop, so we use
|
|
// GetContainerWidget which returns the viewport widget for custom controls
|
|
// and regular widget handle for others.
|
|
|
|
Result := TQtWidget(AWinControl.Handle).GetContainerWidget;
|
|
end;
|
|
|
|
{ ---------- TDragDropSourceQT ---------- }
|
|
|
|
function TDragDropSourceQT.RegisterEvents(DragBeginEvent : uDragDropEx.TDragBeginEvent;
|
|
RequestDataEvent: uDragDropEx.TRequestDataEvent;
|
|
DragEndEvent : uDragDropEx.TDragEndEvent): Boolean;
|
|
begin
|
|
inherited;
|
|
|
|
// RequestDataEvent is not handled in QT.
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
function TDragDropSourceQT.DoDragDrop(const FileNamesList: TStringList;
|
|
MouseButton: TMouseButton;
|
|
ScreenStartPoint: TPoint): Boolean;
|
|
|
|
procedure SetMimeDataInFormat(MimeData: QMimeDataH;
|
|
MimeType: WideString;
|
|
DataString: AnsiString);
|
|
var
|
|
ByteArray: QByteArrayH;
|
|
begin
|
|
ByteArray := QByteArray_create(PAnsiChar(DataString));
|
|
try
|
|
QMimeData_setData(MimeData, @MimeType, ByteArray);
|
|
finally
|
|
QByteArray_destroy(ByteArray);
|
|
end;
|
|
end;
|
|
|
|
var
|
|
DragObject: QDragH = nil;
|
|
MimeData: QMimeDataH = nil;
|
|
|
|
begin
|
|
Result := False;
|
|
|
|
// Simulate drag-begin event.
|
|
if Assigned(GetDragBeginEvent) then
|
|
begin
|
|
Result := GetDragBeginEvent()();
|
|
if Result = False then Exit;
|
|
end;
|
|
|
|
DragObject := QDrag_create(GetWidget); // deleted automatically by QT
|
|
try
|
|
MimeData := QMimeData_create;
|
|
QDrag_setMimeData(DragObject, MimeData); // MimeData owned by DragObject after this
|
|
|
|
SetMimeDataInFormat(MimeData, uriListMimeW, FormatUriList(FileNamesList));
|
|
SetMimeDataInFormat(MimeData, textPlainMimeW, FormatTextPlain(FileNamesList));
|
|
|
|
except
|
|
QDrag_destroy(DragObject);
|
|
raise;
|
|
end;
|
|
|
|
// Start drag&drop operation (default to Copy action).
|
|
QDrag_exec(DragObject, QtCopyAction or QtLinkAction or QtMoveAction, qtCopyAction);
|
|
|
|
// Simulate drag-end event.
|
|
if Assigned(GetDragEndEvent) then
|
|
begin
|
|
if Result = True then
|
|
Result := GetDragEndEvent()()
|
|
else
|
|
GetDragEndEvent()()
|
|
end;
|
|
end;
|
|
|
|
function TDragDropSourceQT.GetWidget: QWidgetH;
|
|
begin
|
|
Result := GetWidgetFromLCLControl(GetControl);
|
|
end;
|
|
|
|
{ ---------- TDragDropTargetQT ---------- }
|
|
|
|
constructor TDragDropTargetQT.Create(TargetControl: TWinControl);
|
|
begin
|
|
inherited;
|
|
|
|
FEventHook := nil;
|
|
end;
|
|
|
|
|
|
function TDragDropTargetQT.RegisterEvents(DragEnterEvent: uDragDropEx.TDragEnterEvent;
|
|
DragOverEvent : uDragDropEx.TDragOverEvent;
|
|
DropEvent : uDragDropEx.TDropEvent;
|
|
DragLeaveEvent: uDragDropEx.TDragLeaveEvent): Boolean;
|
|
begin
|
|
inherited;
|
|
|
|
QWidget_setAcceptDrops(GetWidget, True);
|
|
|
|
if Assigned(FEventHook) then
|
|
QObject_hook_destroy(FEventHook);
|
|
|
|
// Tap into target widget's events.
|
|
FEventHook := QObject_hook_create(GetWidget);
|
|
QObject_hook_hook_events(FEventHook, @EventFilter);
|
|
|
|
Result := True;
|
|
end;
|
|
|
|
procedure TDragDropTargetQT.UnregisterEvents;
|
|
begin
|
|
QWidget_setAcceptDrops(GetWidget, False);
|
|
|
|
if Assigned(FEventHook) then
|
|
begin
|
|
QObject_hook_destroy(FEventHook);
|
|
FEventHook := nil;
|
|
end;
|
|
|
|
inherited;
|
|
end;
|
|
|
|
function TDragDropTargetQT.GetWidget: QWidgetH;
|
|
begin
|
|
Result := GetWidgetFromLCLControl(GetControl);
|
|
end;
|
|
|
|
function TDragDropTargetQT.HasSupportedFormat(DropEvent: QDropEventH): Boolean;
|
|
var
|
|
MimeData: QMimeDataH;
|
|
begin
|
|
MimeData := QDropEvent_mimeData(DropEvent);
|
|
|
|
if Assigned(MimeData) then
|
|
begin
|
|
if QMimeData_hasFormat(mimedata, @urilistmimew) or
|
|
QMimeData_hasFormat(mimedata, @textPlainMimeW)
|
|
then
|
|
Exit(True);
|
|
end;
|
|
|
|
Result := False;
|
|
end;
|
|
|
|
function TDragDropTargetQT.EventFilter(Sender: QObjectH; Event: QEventH): Boolean; cdecl;
|
|
begin
|
|
Result := False; // False means the event is not filtered out.
|
|
|
|
case QEvent_type(Event) of
|
|
|
|
QEventDragEnter:
|
|
begin
|
|
QEvent_accept(Event);
|
|
OnDragEnter(QDragEnterEventH(Event));
|
|
end;
|
|
|
|
QEventDragMove:
|
|
begin
|
|
QEvent_accept(Event);
|
|
OnDragOver(QDragMoveEventH(Event));
|
|
end;
|
|
|
|
QEventDrop:
|
|
begin
|
|
QEvent_accept(Event);
|
|
OnDrop(QDropEventH(Event));
|
|
end;
|
|
|
|
QEventDragLeave:
|
|
begin
|
|
QEvent_accept(Event);
|
|
OnDragLeave(QDragLeaveEventH(Event));
|
|
end;
|
|
|
|
// QEventDragResponse - used internally by QT
|
|
end;
|
|
end;
|
|
|
|
function TDragDropTargetQT.OnDragEnter(DragEnterEvent: QDragEnterEventH): Boolean;
|
|
var
|
|
CursorPosition: TPoint;
|
|
DropEffect: TDropEffect;
|
|
DropEvent: QDropEventH;
|
|
QtAction: QtDropAction;
|
|
begin
|
|
// QDragEnterEvent inherits from QDragMoveEvent, which inherits from QDropEvent.
|
|
DropEvent := QDropEventH(DragEnterEvent);
|
|
|
|
if not HasSupportedFormat(DropEvent) then
|
|
begin
|
|
QDropEvent_setDropAction(DropEvent, QtIgnoreAction);
|
|
Result := False;
|
|
end
|
|
else if Assigned(GetDragEnterEvent) then
|
|
begin
|
|
DropEffect := QtActionToDropEffect(QDropEvent_proposedAction(DropEvent));
|
|
CursorPosition := QtDropEventPointToLCLPoint(QDropEvent_pos(DropEvent));
|
|
CursorPosition := GetControl.ClientToScreen(CursorPosition);
|
|
|
|
Result := GetDragEnterEvent()(DropEffect, CursorPosition);
|
|
|
|
if Result then
|
|
QtAction := DropEffectToQtAction(DropEffect)
|
|
else
|
|
QtAction := QtIgnoreAction;
|
|
|
|
QDropEvent_setDropAction(DropEvent, QtAction);
|
|
end
|
|
else
|
|
begin
|
|
QDropEvent_acceptProposedAction(DropEvent);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TDragDropTargetQT.OnDragOver(DragMoveEvent: QDragMoveEventH): Boolean;
|
|
var
|
|
CursorPosition: TPoint;
|
|
DropEffect: TDropEffect;
|
|
DropEvent: QDropEventH;
|
|
QtAction: QtDropAction;
|
|
begin
|
|
// QDragMoveEvent inherits from QDropEvent.
|
|
DropEvent := QDropEventH(DragMoveEvent);
|
|
|
|
if not HasSupportedFormat(DropEvent) then
|
|
begin
|
|
QDropEvent_setDropAction(DropEvent, QtIgnoreAction);
|
|
Result := False;
|
|
end
|
|
else if Assigned(GetDragOverEvent) then
|
|
begin
|
|
DropEffect := QtActionToDropEffect(QDropEvent_proposedAction(DropEvent));
|
|
CursorPosition := QtDropEventPointToLCLPoint(QDropEvent_pos(DropEvent));
|
|
CursorPosition := GetControl.ClientToScreen(CursorPosition);
|
|
|
|
Result := GetDragOverEvent()(DropEffect, CursorPosition);
|
|
|
|
if Result then
|
|
QtAction := DropEffectToQtAction(DropEffect)
|
|
else
|
|
QtAction := QtIgnoreAction;
|
|
|
|
QDropEvent_setDropAction(DropEvent, QtAction);
|
|
end
|
|
else
|
|
begin
|
|
QDropEvent_acceptProposedAction(DropEvent);
|
|
Result := True;
|
|
end;
|
|
end;
|
|
|
|
function TDragDropTargetQT.OnDrop(DropEvent: QDropEventH): Boolean;
|
|
|
|
function GetMimeDataInFormat(MimeData: QMimeDataH; MimeType: WideString): AnsiString;
|
|
var
|
|
ByteArray: QByteArrayH;
|
|
Size: Integer;
|
|
Data: PAnsiChar;
|
|
begin
|
|
if QMimeData_hasFormat(MimeData, @MimeType) then
|
|
begin
|
|
ByteArray := QByteArray_create();
|
|
try
|
|
QMimeData_data(MimeData, ByteArray, @MimeType);
|
|
Size := QByteArray_size(ByteArray);
|
|
Data := QByteArray_data(ByteArray);
|
|
|
|
if (Size > 0) and Assigned(Data) then
|
|
SetString(Result, Data, Size);
|
|
|
|
finally
|
|
QByteArray_destroy(ByteArray);
|
|
end;
|
|
end
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
var
|
|
DropAction: QtDropAction;
|
|
DropEffect: TDropEffect;
|
|
CursorPosition: TPoint;
|
|
uriList: String;
|
|
FileNamesList: TStringList = nil;
|
|
MimeData: QMimeDataH;
|
|
begin
|
|
Result := False;
|
|
|
|
// QDropEvent_possibleActions() returns all actions allowed by the source.
|
|
// QDropEvent_proposedAction() is the action proposed by the source.
|
|
DropAction := QDropEvent_dropAction(DropEvent); // action to be performed by the target
|
|
DropEffect := QtActionToDropEffect(DropAction);
|
|
|
|
CursorPosition := QtDropEventPointToLCLPoint(QDropEvent_pos(dropEvent));
|
|
CursorPosition := GetControl.ClientToScreen(CursorPosition);
|
|
|
|
QDropEvent_setDropAction(DropEvent, QtIgnoreAction); // default to ignoring the drop
|
|
|
|
MimeData := QDropEvent_mimeData(DropEvent);
|
|
if Assigned(GetDropEvent) and Assigned(MimeData) then
|
|
begin
|
|
if QMimeData_hasFormat(MimeData, @uriListMimeW) then
|
|
|
|
uriList := URIDecode(Trim(GetMimeDataInFormat(MimeData, uriListMimeW)))
|
|
|
|
else if QMimeData_hasFormat(MimeData, @textPlainMimeW) then
|
|
|
|
// try decoding, as text/plain may also be percent-encoded
|
|
uriList := URIDecode(Trim(GetMimeDataInFormat(MimeData, textPlainMimeW)))
|
|
|
|
else
|
|
Exit; // reject the drop
|
|
|
|
try
|
|
FileNamesList := ExtractFilenames(uriList);
|
|
|
|
if Assigned(FileNamesList) and (FileNamesList.Count > 0) then
|
|
Result := GetDropEvent()(FileNamesList, DropEffect, CursorPosition);
|
|
|
|
finally
|
|
if Assigned(FileNamesList) then
|
|
FreeAndNil(FileNamesList);
|
|
end;
|
|
|
|
QDropEvent_setDropAction(DropEvent, DropAction); // accept the drop
|
|
end;
|
|
end;
|
|
|
|
function TDragDropTargetQT.OnDragLeave(DragLeaveEvent: QDragLeaveEventH): Boolean;
|
|
begin
|
|
if Assigned(GetDragLeaveEvent) then
|
|
Result := GetDragLeaveEvent()()
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
{ ---------------------------------------------------------------------------- }
|
|
|
|
function QtActionToDropEffect(Action: QtDropAction): TDropEffect;
|
|
begin
|
|
case Action of
|
|
QtCopyAction: Result := DropCopyEffect;
|
|
QtMoveAction: Result := DropMoveEffect;
|
|
QtTargetMoveAction: Result := DropMoveEffect;
|
|
QtLinkAction: Result := DropLinkEffect;
|
|
else Result := DropNoEffect;
|
|
end;
|
|
end;
|
|
|
|
function DropEffectToQtAction(DropEffect: TDropEffect): QtDropAction;
|
|
begin
|
|
case DropEffect of
|
|
DropCopyEffect: Result := QtCopyAction;
|
|
DropMoveEffect: Result := QtMoveAction;
|
|
DropLinkEffect: Result := QtLinkAction;
|
|
else Result := QtIgnoreAction;
|
|
end;
|
|
end;
|
|
|
|
function QtDropEventPointToLCLPoint(const PDropEventPoint: PQtPoint): TPoint;
|
|
begin
|
|
if Assigned(PDropEventPoint) then
|
|
begin
|
|
if (PDropEventPoint^.x <> 0) or (PDropEventPoint^.y <> 0) then
|
|
begin
|
|
Result.X := PDropEventPoint^.x;
|
|
Result.Y := PDropEventPoint^.y;
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
GetCursorPos(Result);
|
|
end;
|
|
|
|
end.
|
|
|