doublecmd/src/platform/udragdropqt.pas
2017-01-07 17:05:34 +00:00

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.