ultimatepp/uppsrc/CtrlCore/GtkDnD.cpp
2026-01-16 19:26:25 +01:00

364 lines
11 KiB
C++

#include <CtrlCore/CtrlCore.h>
#ifdef GUI_GTK
namespace Upp {
#define LLOG(x) // DLOG(x)
// --------------------------------------------------------------------------------------------
Ptr<Ctrl> Ctrl::dnd_source;
const VectorMap<String, ClipData> *Ctrl::dnd_source_data;
Vector<String> Ctrl::dnd_fmts;
int Ctrl::dnd_result;
Image Ctrl::dnd_icon;
Ctrl *Ctrl::GetDragAndDropSource()
{
return dnd_source;
}
cairo_surface_t *CreateCairoSurface(const Image& img);
void Ctrl::GtkDragBegin(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
if(IsNull(dnd_icon))
gtk_drag_set_icon_default(context);
else {
cairo_surface_t *surface = CreateCairoSurface(dnd_icon);
#if GTK_CHECK_VERSION(3, 10, 0)
cairo_surface_set_device_scale(surface, SCL(1), SCL(1));
#endif
cairo_surface_set_device_offset(surface, DPI(8), DPI(8));
gtk_drag_set_icon_surface(context, surface);
cairo_surface_destroy(surface);
}
LLOG("GtkDragBegin");
}
void Ctrl::GtkDragDelete(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
LLOG("GtkDragDelete");
}
void Ctrl::GtkDragGetData(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
LLOG("GtkDragGetData");
if(!dnd_source)
return;
if(info < (guint)dnd_source_data->GetCount())
GtkSelectionDataSet(data, dnd_source_data->GetKey(info), (*dnd_source_data)[info].Render());
else {
info -= dnd_source_data->GetCount();
if(info < (guint)dnd_fmts.GetCount()) {
String fmt = dnd_fmts[info];
GtkSelectionDataSet(data, fmt, dnd_source->GetDropData(fmt));
}
}
}
void Ctrl::GtkDragEnd(GtkWidget *widget, GdkDragContext *context, gpointer user_data)
{
LLOG("GtkDragEnd");
dnd_result = DND_NONE;
GdkDragAction a = gdk_drag_context_get_selected_action(context);
dnd_result = a == GDK_ACTION_MOVE ? DND_MOVE : a == GDK_ACTION_COPY ? DND_COPY : DND_NONE;
dnd_source = NULL;
}
gboolean Ctrl::GtkDragFailed(GtkWidget *widget, GdkDragContext *context, GtkDragResult result,
gpointer user_data)
{
LLOG("GtkDragFailed");
dnd_source = NULL;
return FALSE;
}
Image MakeDragImage(const Image& arrow, Image sample);
Image MakeDragImage(const Image& arrow, const Image& arrow98, Image sample)
{
return arrow98;
}
int Ctrl::DoDragAndDrop(const char *fmts, const Image& sample, dword actions,
const VectorMap<String, ClipData>& data)
{
LLOG("------------------------------");
LLOG("DoDragAndDrop " << fmts);
TopWindow *w = GetTopWindow();
if(!w || !w->top)
return DND_NONE;
int gdk_actions = 0;
if(actions & DND_MOVE)
gdk_actions |= GDK_ACTION_MOVE;
if(actions & DND_COPY)
gdk_actions |= GDK_ACTION_COPY;
GtkTargetList *list = CreateTargetList(data);
dnd_fmts.Clear();
for(int i = 0; i < data.GetCount(); i++)
AddFmt(list, data.GetKey(i), i);
Vector<String> f = Split(fmts, ';');
for(int i = 0; i < f.GetCount(); i++) {
AddFmt(list, f[i], data.GetCount() + i);
dnd_fmts.Add(f[i]);
}
dnd_source_data = &data;
dnd_result = DND_NONE;
dnd_source = this;
if(IsNull(sample))
dnd_icon = Null;
else {
Size sz = sample.GetSize();
int maxsize = DPI(128);
if(sz.cx > maxsize)
sz.cx = maxsize;
if(sz.cy > maxsize)
sz.cy = maxsize;
sz += 2;
ImageDraw iw(sz);
iw.DrawRect(sz, White());
DrawFrame(iw, sz, Black());
iw.DrawImage(1, 1, sz.cx, sz.cy, sample);
dnd_icon = iw;
}
Top *top = w->GetTop();
if(top)
#if GTK_CHECK_VERSION(3, 10, 0)
gtk_drag_begin_with_coordinates(top->window, list, GdkDragAction(gdk_actions),
GetMouseLeft() ? 1 : GetMouseMiddle() ? 2 : 3,
CurrentEvent.event, -1, -1);
#else
gtk_drag_begin(top->window, list, GdkDragAction(gdk_actions),
GetMouseLeft() ? 1 : GetMouseMiddle() ? 2 : 3, CurrentEvent.event);
#endif
while(dnd_source && GetTopCtrls().GetCount())
ProcessEvents();
dnd_source_data = NULL;
gtk_target_list_unref (list);
LLOG("-------- DoDragAndDrop");
return dnd_result;
}
// --------------------------------------------------------------------------------------------
Index<String> Ctrl::dnd_targets;
String Ctrl::dnd_text_target;
String Ctrl::dnd_image_target;
String Ctrl::dnd_files_target;
GtkWidget *Ctrl::dnd_widget;
GdkDragContext *Ctrl::dnd_context;
guint Ctrl::dnd_time;
String Ctrl::dnd_data;
String Ctrl::dnd_data_fmt;
bool Ctrl::dnd_data_wait;
bool Ctrl::dnd_events;
Ctrl *Ctrl::DragWnd(gpointer user_data)
{
if(dnd_data_wait) // Do nothing if waiting for data
return NULL;
dnd_events = true; // prevent mouse up messages to be issued
ProcessEvents0(NULL, false); // process any events that are already fetched
dnd_events = false;
return GetTopCtrlFromId(user_data);
}
void ToIndex(GtkTargetList *target_list, Index<String>& ndx)
{
gint n;
GtkTargetEntry *t = gtk_target_table_new_from_list(target_list, &n);
for(int i = 0; i < n; i++)
ndx.Add(t[i].target);
gtk_target_table_free(t, n);
gtk_target_list_unref(target_list);
}
void Ctrl::DndTargets(GdkDragContext *context)
{
static Index<String> text_targets;
static Index<String> image_targets;
static Index<String> files_targets;
ONCELOCK {
GtkTargetList *target_list = gtk_target_list_new (NULL, 0);
gtk_target_list_add_text_targets(target_list, 0);
ToIndex(target_list, text_targets);
GtkTargetList *target_list2 = gtk_target_list_new (NULL, 0);
gtk_target_list_add_image_targets(target_list2, 0, TRUE);
ToIndex(target_list2, image_targets);
GtkTargetList *target_list3 = gtk_target_list_new (NULL, 0);
gtk_target_list_add_uri_targets(target_list3, 0);
ToIndex(target_list3, files_targets);
}
dnd_targets.Clear();
dnd_text_target.Clear();
for(GList *list = gdk_drag_context_list_targets(context); list; list = g_list_next (list)) {
String g = gdk_atom_name((GdkAtom)list->data);
if(text_targets.Find(g) >= 0) {
dnd_targets.Add("text");
if(dnd_text_target.IsEmpty())
dnd_text_target = g;
}
else
if(image_targets.Find(g) >= 0) {
dnd_targets.Add("image");
if(dnd_image_target.IsEmpty())
dnd_image_target = g;
}
else
if(files_targets.Find(g) >= 0) {
dnd_targets.Add("files");
if(dnd_files_target.IsEmpty())
dnd_files_target = g;
}
else
dnd_targets.Add(g);
}
}
void Ctrl::GtkDragDataReceived(GtkWidget *widget, GdkDragContext *context,
gint x, gint y, GtkSelectionData *data,
guint info, guint time, gpointer user_data)
{
LLOG("GtkDragDataReceived " << dnd_data_fmt);
dnd_data_wait = false;
if(dnd_data_fmt == "text") {
guchar *s = gtk_selection_data_get_text(data);
if(s) {
dnd_data = (const char *)s;
g_free(s);
}
}
else
if(dnd_data_fmt == "image")
dnd_data = ImageClipFromPixbufUnref(gtk_selection_data_get_pixbuf(data));
else
if(dnd_data_fmt == "files")
dnd_data = FilesClipFromUrisFree(gtk_selection_data_get_uris(data));
else
dnd_data = GtkDataGet(data);
}
bool Ctrl::IsDragAvailable(const char *fmt)
{
LLOG("IsDragAvailable " << fmt << " " << (Ctrl::dnd_targets.Find(fmt) >= 0));
return dnd_targets.Find(fmt) >= 0;
}
String Ctrl::DragGet(const char *fmt)
{
LLOG("DragGet " << fmt << " " << (Ctrl::dnd_targets.Find(fmt) >= 0));
if(Ctrl::dnd_targets.Find(fmt) < 0)
return Null;
dnd_data.Clear();
dnd_data_wait = true;
dnd_data_fmt = fmt;
int t0 = msecs();
gtk_drag_get_data(dnd_widget, dnd_context,
GAtom(strcmp(fmt, "image") == 0 ? ~dnd_image_target :
strcmp(fmt, "text") == 0 ? ~dnd_text_target :
strcmp(fmt, "files") == 0 ? ~dnd_files_target : fmt),
dnd_time);
while(msecs() - t0 < 1000 && dnd_data_wait)
FetchEvents(true);
LLOG("DragGet data length " << dnd_data.GetLength());
return dnd_data;
}
PasteClip Ctrl::GtkDnd(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
guint time, gpointer user_data, bool paste)
{
DndTargets(context);
g_object_ref(context); // make sure these always survive the action...
g_object_ref(widget);
dnd_context = context;
dnd_widget = widget;
dnd_time = time;
PasteClip clip;
clip.type = 1;
clip.paste = paste;
clip.accepted = false;
clip.allowed = DND_MOVE|DND_COPY;
GdkModifierType mod;
GetMouseInfo(gdk_get_default_root_window(), mod);
clip.action = mod & GDK_CONTROL_MASK ? DND_COPY : DND_MOVE;
Ctrl *w = DragWnd(user_data);
if(w) {
GetMouseInfo(gdk_get_default_root_window(), mod);
CurrentState = mod;
CurrentMousePos = Point(SCL(x), SCL(y));
int x1, y1;
gdk_window_get_position(w->gdk(), &x1, &y1);
CurrentMousePos += Point(SCL(x1), SCL(y1));
w->DnD(CurrentMousePos, clip);
}
gdk_drag_status(context, clip.IsAccepted() ? clip.GetAction() == DND_MOVE ? GDK_ACTION_MOVE
: GDK_ACTION_COPY
: GdkDragAction(0), time);
return clip;
}
gboolean Ctrl::GtkDragMotion(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
guint time, gpointer user_data)
{
LLOG("GtkDragMotion");
PasteClip clip = GtkDnd(widget, context, x, y, time, user_data, false);
g_object_unref(widget);
g_object_unref(context);
return TRUE;
}
void Ctrl::GtkDragLeave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer user_data)
{
LLOG("GtkDragLeave");
DnDLeave();
}
gboolean Ctrl::GtkDragDrop(GtkWidget *widget, GdkDragContext *context, gint x, gint y,
guint time, gpointer user_data)
{
LLOG("GtkDragDrop");
PasteClip clip = GtkDnd(widget, context, x, y, time, user_data, true);
gtk_drag_finish(context, clip.IsAccepted(), clip.IsAccepted() && clip.GetAction() == DND_MOVE, time);
g_object_unref(widget);
g_object_unref(context);
DnDLeave();
return TRUE;
}
// -----------------------------------------------------------------------------------------
void Ctrl::DndInit()
{
Top *top = GetTop();
if(!top)
return;
GtkWidget *w = top->window;
gpointer id = (gpointer)(uintptr_t)top->id;
g_signal_connect(w, "drag-begin", G_CALLBACK(GtkDragBegin), id);
g_signal_connect(w, "drag-data-delete", G_CALLBACK(GtkDragDelete), id);
g_signal_connect(w, "drag-data-get", G_CALLBACK(GtkDragGetData), id);
g_signal_connect(w, "drag-data-received", G_CALLBACK(GtkDragDataReceived), id);
g_signal_connect(w, "drag-drop", G_CALLBACK(GtkDragDrop), id);
g_signal_connect(w, "drag-end", G_CALLBACK(GtkDragEnd), id);
g_signal_connect(w, "drag-failed", G_CALLBACK(GtkDragFailed), id);
g_signal_connect(w, "drag-leave", G_CALLBACK(GtkDragLeave), id);
g_signal_connect(w, "drag-motion", G_CALLBACK(GtkDragMotion), id);
gtk_drag_dest_set(w, GtkDestDefaults(0), NULL, 0, GdkDragAction(GDK_ACTION_COPY|GDK_ACTION_MOVE));
}
void Ctrl::DndExit()
{
Top *top = GetTop();
if(top)
gtk_drag_dest_unset(top->window);
}
}
#endif