improve dnd interfaces

This commit is contained in:
Jinhao 2018-12-27 07:29:11 +08:00
parent 01ed1d13e9
commit ce1b143b59
5 changed files with 213 additions and 189 deletions

View File

@ -22,6 +22,14 @@
namespace nana
{
/// Drag and drop actions
enum class dnd_action
{
copy, ///< Copy the data to target.
move, ///< Move the data to target.
link ///< Create a link from source data to target.
};
class simple_dragdrop
{
struct implementation;
@ -67,7 +75,12 @@ namespace nana
data(const data&) = delete;
data& operator=(const data&) = delete;
public:
data();
/// Constructor
/**
* Constructs a data object used for drag and drop
* @param requested_action Indicates how the data to be transferred.
*/
data(dnd_action requested_action = dnd_action::copy);
data(data&&);
~data();
@ -81,9 +94,29 @@ namespace nana
dragdrop(window source);
~dragdrop();
/// Condition of dragging
/***
* The preciate function is called when press mouse button on the source window, it returns true to indicate the start of dragging. If the predicate is not set, it always start to drag.
* @param predicate_fn A predicate function to be set.
*/
void condition(std::function<bool()> predicate_fn);
/// Transferred data
/**
* Set a data generator. When drag begins, it is called to generate a data object for transferring.
* @param generator It returns the data for transferring.
*/
void prepare_data(std::function<data()> generator);
void drop_finished(std::function<void(bool)> finish_fn);
/// Drop handler
/**
* The drop handler is called when the drop operation is completed. There are 3 parameters for the handler
* dropped Indicates whether the data is accepted by a target window.
* executed_action Indicates the action returned by target window. Ignore if dropped is false.
* data_transferred The data object which is generated by the generator.
* @param finish_fn The drop handling function.
*/
void drop_finished(std::function<void(bool dropped, dnd_action executed_action, data& data_transferred)> finish_fn);
private:
implementation* const impl_;
};

View File

@ -513,6 +513,8 @@ namespace detail
atombase_.xdnd_position = ::XInternAtom(display_, "XdndPosition", False);
atombase_.xdnd_status = ::XInternAtom(display_, "XdndStatus", False);
atombase_.xdnd_action_copy = ::XInternAtom(display_, "XdndActionCopy", False);
atombase_.xdnd_action_move = ::XInternAtom(display_, "XdndActionMove", False);
atombase_.xdnd_action_link = ::XInternAtom(display_, "XdndActionLink", False);
atombase_.xdnd_drop = ::XInternAtom(display_, "XdndDrop", False);
atombase_.xdnd_selection = ::XInternAtom(display_, "XdndSelection", False);
atombase_.xdnd_typelist = ::XInternAtom(display_, "XdndTypeList", False);

View File

@ -159,6 +159,8 @@ namespace detail
Atom xdnd_position;
Atom xdnd_status;
Atom xdnd_action_copy;
Atom xdnd_action_move;
Atom xdnd_action_link;
Atom xdnd_drop;
Atom xdnd_selection;
Atom xdnd_typelist;

View File

@ -16,69 +16,26 @@
#include "platform_spec.hpp"
#include <nana/filesystem/filesystem.hpp>
#include <vector>
#include "theme.hpp"
#include <X11/Xcursor/Xcursor.h>
#include <vector>
#define DEBUG_XDND_PROTOCOL
#ifdef DEBUG_XDND_PROTOCOL
#include <iostream> //debug
#endif
namespace nana{
namespace detail
{
class shared_icons
{
public:
shared_icons()
{
path_ = "/usr/share/icons/";
ifs_.open(path_ + "default/index.theme");
}
std::string cursor(const std::string& name)
{
auto theme = _m_read("Icon Theme", "Inherits");
return path_ + theme + "/cursors/" + name;
}
private:
std::string _m_read(const std::string& category, const std::string& key)
{
ifs_.seekg(0, std::ios::beg);
bool found_cat = false;
while(ifs_.good())
{
std::string text;
std::getline(ifs_, text);
if(0 == text.find('['))
{
if(found_cat)
break;
if(text.find(category + "]") != text.npos)
{
found_cat = true;
}
}
else if(found_cat && (text.find(key + "=") == 0))
{
return text.substr(key.size() + 1);
}
}
return {};
}
private:
std::string path_;
std::ifstream ifs_;
};
struct xdnd_data
{
Atom requested_action;
std::vector<std::filesystem::path> files;
};
@ -100,31 +57,40 @@ namespace nana{
auto disp = spec_.open_display();
detail::platform_scope_guard lock;
::XSetSelectionOwner(disp, spec_.atombase().xdnd_selection, source, CurrentTime);
std::cout<<"XSetSelectionOwner "<<source<<std::endl;
shared_icons icons;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"XSetSelectionOwner "<<source<<std::endl;
#endif
/* //deprecated
theme icons;
cursor_.dnd_copy = ::XcursorFilenameLoadCursor(disp, icons.cursor("dnd-copy").c_str());
cursor_.dnd_move = ::XcursorFilenameLoadCursor(disp, icons.cursor("dnd-move").c_str());
cursor_.dnd_none = ::XcursorFilenameLoadCursor(disp, icons.cursor("dnd-none").c_str());
cursor_.dnd_none = ::XcursorFilenameLoadCursor(disp, icons.cursor("dnd-none").c_str());
*/
cursor_.dnd_copy = ::XcursorLibraryLoadCursor(disp, "dnd-copy");
cursor_.dnd_move = ::XcursorLibraryLoadCursor(disp, "dnd-move");
cursor_.dnd_none = ::XcursorLibraryLoadCursor(disp, "dnd-none");
}
~xdnd_protocol()
{
auto disp = spec_.open_display();
::XFreeCursor(disp, cursor_.dnd_copy);
::XFreeCursor(disp, cursor_.dnd_move);
::XFreeCursor(disp, cursor_.dnd_none);
}
void mouse_move(Window wd, const nana::point& pos)
void mouse_move(Window wd, const nana::point& pos, Atom requested_action)
{
if(wd != target_)
{
_m_xdnd_leave();
if(_m_xdnd_enter(wd))
_m_xdnd_position(pos);
_m_xdnd_position(pos, requested_action);
}
else
_m_xdnd_position(pos);
_m_xdnd_position(pos, requested_action);
}
void mouse_leave()
@ -132,9 +98,14 @@ namespace nana{
_m_xdnd_leave();
}
void mouse_release()
bool mouse_release()
{
_m_xdnd_drop();
return _m_xdnd_drop();
}
Atom executed_action() const
{
return executed_action_;
}
//Return true to exit xdnd_protocol event handler
@ -144,16 +115,18 @@ namespace nana{
if(atombase.xdnd_status == xclient.message_type)
{
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Event: XdndStatus"<<std::endl;
#endif
if(xdnd_status_state::position != xstate_ && xdnd_status_state::drop != xstate_)
return false;
Window target_wd = static_cast<Window>(xclient.data.l[0]);
bool is_accepted_by_target = (xclient.data.l[1] & 1);
std::cout<<"XdndStatus: Accepted="<<is_accepted_by_target<<", target="<<is_accepted_by_target<<std::endl;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"XdndStatus: Accepted="<<is_accepted_by_target<<", target="<<target_wd<<std::endl;
#endif
if(xclient.data.l[1] & 0x2)
{
rectangle rct{
@ -166,21 +139,28 @@ namespace nana{
if(!rct.empty())
{
mvout_table_[target_wd] = rct;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<". rct=("<<rct.x<<","<<rct.y<<","<<rct.width<<","<<rct.height<<")";
#endif
}
}
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<std::endl;
#endif
_m_cursor(is_accepted_by_target);
if((!is_accepted_by_target) && (xdnd_status_state::drop == xstate_))
{
_m_xdnd_leave();
return true;
}
executed_action_ = xclient.data.l[4];
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<" Assign ExecutedAction = "<<static_cast<int>(executed_action_)<<std::endl;
#endif
xstate_ = xdnd_status_state::normal;
}
else if(atombase.xdnd_finished == xclient.message_type)
@ -193,8 +173,9 @@ namespace nana{
{
if(spec_.atombase().xdnd_selection == xselectionrequest.selection)
{
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Event SelectionRequest: XdndSelection"<<std::endl;
#endif
::XEvent evt;
evt.xselection.type = SelectionNotify;
evt.xselection.display = xselectionrequest.display;
@ -204,6 +185,7 @@ namespace nana{
evt.xselection.property = 0;
evt.xselection.time = xselectionrequest.time;
#ifdef DEBUG_XDND_PROTOCOL
auto property_name = ::XGetAtomName(spec_.open_display(), xselectionrequest.property); //debug
std::cout<<"SelectionRequest"<<std::endl;
@ -213,11 +195,13 @@ namespace nana{
std::cout<<" property ="<<xselectionrequest.property<<", name="<<property_name<<std::endl;
std::cout<<" target ="<<xselectionrequest.target<<std::endl;
::XFree(property_name);
#endif
if(xselectionrequest.target == spec_.atombase().text_uri_list)
{
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"SelectionRequest target = text_uri_list";
#endif
if(data.files.size())
{
std::string uri_list;
@ -227,9 +211,9 @@ namespace nana{
uri_list += file.u8string();
uri_list += "\r\n";
}
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<". URIs="<<uri_list<<std::endl;
#endif
::XChangeProperty (spec_.open_display(),
xselectionrequest.requestor,
xselectionrequest.property,
@ -242,9 +226,10 @@ namespace nana{
}
}
#ifdef DEBUG_XDND_PROTOCOL
else
std::cout<<"SelectionRequest target = "<<xselectionrequest.target<<std::endl;
#endif
platform_scope_guard lock;
::XSendEvent(spec_.open_display(), xselectionrequest.requestor, False, 0, &evt);
::XFlush(spec_.open_display());
@ -254,7 +239,6 @@ namespace nana{
}
}
private:
bool _m_xdnd_enter(Window wd)
{
//xdnd version of the window
@ -263,13 +247,15 @@ namespace nana{
return false;
target_ = wd;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Send XdndEnter, text/uri-list="<<spec_.atombase().text_uri_list<<std::endl;
#endif
_m_client_msg(spec_.atombase().xdnd_enter, (xdnd_ver << 24), spec_.atombase().text_uri_list, XA_STRING);
return true;
}
void _m_xdnd_position(const nana::point& pos)
void _m_xdnd_position(const nana::point& pos, Atom requested_action)
{
if(xdnd_status_state::normal != xstate_)
return;
@ -278,12 +264,13 @@ namespace nana{
if(i != mvout_table_.end() && i->second.is_hit(pos))
return;
std::cout<<"Send: XdndPosition"<<std::endl;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Send: XdndPosition, RequestedAction="<<requested_action<<", XdndActionCopy="<<spec_.atombase().xdnd_action_copy<<std::endl;
#endif
xstate_ = xdnd_status_state::position;
//Send XdndPosition
long position = (pos.x << 16 | pos.y);
_m_client_msg(spec_.atombase().xdnd_position, 0, position, CurrentTime, spec_.atombase().xdnd_action_copy);
_m_client_msg(spec_.atombase().xdnd_position, 0, position, CurrentTime, requested_action);
}
void _m_xdnd_leave()
@ -292,19 +279,30 @@ namespace nana{
if(target_)
{
std::cout<<"Send: XdndLeave"<<std::endl;
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Send: XdndLeave, reset ExecutedAction"<<std::endl;
#endif
_m_client_msg(spec_.atombase().xdnd_leave, 0, 0, 0);
target_ = 0;
executed_action_ = 0;
}
}
void _m_xdnd_drop()
bool _m_xdnd_drop()
{
::XUndefineCursor(spec_.open_display(), source_);
xstate_ = xdnd_status_state::drop;
std::cout<<"Send: XdndDrop"<<std::endl;
_m_client_msg(spec_.atombase().xdnd_drop, 0, CurrentTime, 0);
if(executed_action_)
{
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Send: XdndDrop"<<std::endl;
#endif
_m_client_msg(spec_.atombase().xdnd_drop, 0, CurrentTime, 0);
}
target_ = 0;
return (executed_action_ != 0);
}
private:
//dndversion<<24, fl_XdndURIList, XA_STRING, 0
@ -343,7 +341,9 @@ namespace nana{
if ((actual == XA_ATOM) && (format==32) && count && data)
{
version = int(*(Atom*)data);
#ifdef DEBUG_XDND_PROTOCOL
std::cout<<"Get:XdndAware version:"<<version<<std::endl;
#endif
}
if (data)
@ -355,39 +355,17 @@ namespace nana{
{
::XDefineCursor(spec_.open_display(), source_, (accepted ? cursor_.dnd_move : cursor_.dnd_none));
}
#if 0 //deprecated
//Check if window has a property
static bool _m_has_property(Window wd, Atom atom, unsigned char** data)
{
Atom type = 0;
int f;
unsigned long n, a;
unsigned char * data_back = nullptr;
if(nullptr == data)
data = &data_back;
if (::XGetWindowProperty(spec_.open_display(), wd, atom, 0, 0, False, AnyPropertyType, &type, &f,&n,&a,data) == Success)
{
//release the *data if failed to get the property or unspecified output buffer
if((0 == type) || data_back)
::XFree(*data);
return (0 != type);
}
return false;
}
#endif
private:
nana::detail::platform_spec& spec_;
Window const source_;
Window target_{ 0 };
Window const source_;
Window target_{ 0 };
Atom executed_action_{ 0 };
xdnd_status_state xstate_{xdnd_status_state::normal};
std::map<Window, nana::rectangle> mvout_table_;
struct cursor_rep
{
Cursor dnd_copy{ 0 };
Cursor dnd_move{ 0 };
Cursor dnd_none{ 0 };
}cursor_;

View File

@ -39,17 +39,31 @@ namespace nana
{
struct dragdrop_data
{
dnd_action requested_action;
std::vector<std::filesystem::path> files;
};
#ifdef NANA_X11
xdnd_data to_xdnd_data(const dragdrop_data& data)
{
xdnd_data xdata;
xdata.files = data.files;
return xdata;
}
xdnd_data to_xdnd_data() const noexcept
{
auto & atombase = nana::detail::platform_spec::instance().atombase();
xdnd_data xdata;
xdata.requested_action = atombase.xdnd_action_copy;
switch(requested_action)
{
case dnd_action::copy:
xdata.requested_action = atombase.xdnd_action_copy; break;
case dnd_action::move:
xdata.requested_action = atombase.xdnd_action_move; break;
case dnd_action::link:
xdata.requested_action = atombase.xdnd_action_link; break;
}
xdata.files = files;
return xdata;
}
#endif
};
}
@ -744,7 +758,7 @@ namespace nana
}
}
bool dragdrop(window drag_wd, dropdata_type* dropdata)
bool dragdrop(window drag_wd, dropdata_type* dropdata, dnd_action* executed_action)
{
auto i = table_.find(API::root(drag_wd));
if ((!dropdata) && table_.end() == i)
@ -764,6 +778,19 @@ namespace nana
delete drop_src;
if (executed_action)
{
switch (result_effect)
{
case DROPEFFECT_COPY:
*executed_action = dnd_action::copy; break;
case DROPEFFECT_MOVE:
*executed_action = dnd_action::move; break;
case DROPEFFECT_LINK:
*executed_action = dnd_action::link; break;
}
}
return (DROPEFFECT_NONE != result_effect);
#elif defined(NANA_X11)
auto& atombase = _m_spec().atombase();
@ -780,12 +807,14 @@ namespace nana
hovered_.window_handle = nullptr;
hovered_.native_wd = 0;
window target_wd = 0;
if(executed_action)
*executed_action = dropdata->data()->requested_action;
if(ddrop->simple_mode())
{
_m_spec().msg_dispatch([this, ddrop, drag_wd, native_source, &target_wd, &atombase](const detail::msg_packet_tag& msg_pkt) mutable{
_m_spec().msg_dispatch([this, ddrop, drag_wd, native_source, &atombase](const detail::msg_packet_tag& msg_pkt) mutable{
if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind)
{
auto const disp = _m_spec().open_display();
@ -793,35 +822,7 @@ namespace nana
{
auto pos = API::cursor_position();
auto native_cur_wd = reinterpret_cast<Window>(detail::native_interface::find_window(pos.x, pos.y));
#if 0
const char* icon = nullptr;
if(hovered_.native_wd != native_cur_wd)
{
if(hovered_.native_wd)
{
_m_free_cursor();
::XUndefineCursor(disp, hovered_.native_wd);
}
_m_client_msg(native_cur_wd, native_source, 1, atombase.xdnd_enter, atombase.text_uri_list, XA_STRING);
hovered_.native_wd = native_cur_wd;
if(!ddrop->simple_mode())
icon = "dnd-move";
}
if(ddrop->simple_mode())
{
auto cur_wd = API::find_window(API::cursor_position());
if(hovered_.window_handle != cur_wd)
{
hovered_.window_handle = cur_wd;
icon = (((drag_wd == cur_wd) || ddrop->has(drag_wd, cur_wd)) ? "dnd-move" : "dnd-none");
}
}
#else
const char* icon = nullptr;
if(hovered_.native_wd != native_cur_wd)
{
@ -835,23 +836,19 @@ namespace nana
hovered_.native_wd = native_cur_wd;
}
if(ddrop->simple_mode())
auto cur_wd = API::find_window(API::cursor_position());
std::cout<<" Hovered="<<cur_wd;
if(hovered_.window_handle != cur_wd)
{
auto cur_wd = API::find_window(API::cursor_position());
hovered_.window_handle = cur_wd;
std::cout<<" Hovered="<<cur_wd;
if(hovered_.window_handle != cur_wd)
{
hovered_.window_handle = cur_wd;
icon = (((drag_wd == cur_wd) || ddrop->has(drag_wd, cur_wd)) ? "dnd-move" : "dnd-none");
std::cout<<" ICON="<<icon;
}
std::cout<<std::endl;
icon = (((drag_wd == cur_wd) || ddrop->has(drag_wd, cur_wd)) ? "dnd-move" : "dnd-none");
std::cout<<" ICON="<<icon;
}
else
icon = "dnd-move";
std::cout<<std::endl;
if(icon)
{
@ -859,11 +856,9 @@ namespace nana
hovered_.cursor = ::XcursorFilenameLoadCursor(disp, icons_.cursor(icon).c_str());
::XDefineCursor(disp, native_cur_wd, hovered_.cursor);
}
#endif
}
else if(msg_pkt.u.xevent.type == ButtonRelease)
{
target_wd = API::find_window(API::cursor_position());
::XUndefineCursor(disp, hovered_.native_wd);
_m_free_cursor();
API::release_capture(drag_wd);
@ -873,16 +868,19 @@ namespace nana
}
return detail::propagation_chain::stop;
});
//In simple mode, it always returns true. The drag and drop is determined by class simple_dragdrop
return true;
}
else
{
auto data = detail::to_xdnd_data(*dropdata->data());
auto data = dropdata->data()->to_xdnd_data();
API::set_capture(drag_wd, true);
nana::detail::xdnd_protocol xdnd_proto{native_source};
//Not simple mode
_m_spec().msg_dispatch([this, ddrop, &data, drag_wd, xdnd_proto, native_source, &target_wd, &atombase](const detail::msg_packet_tag& msg_pkt) mutable{
_m_spec().msg_dispatch([this, ddrop, &data, drag_wd, &xdnd_proto, native_source, &atombase](const detail::msg_packet_tag& msg_pkt) mutable{
if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind)
{
auto const disp = _m_spec().open_display();
@ -891,7 +889,7 @@ namespace nana
auto pos = API::cursor_position();
auto native_cur_wd = reinterpret_cast<Window>(detail::native_interface::find_window(pos.x, pos.y));
xdnd_proto.mouse_move(native_cur_wd, pos);
xdnd_proto.mouse_move(native_cur_wd, pos, data.requested_action);
}
else if(ClientMessage == msg_pkt.u.xevent.type)
{
@ -911,10 +909,12 @@ namespace nana
std::cout<<"ButtonRelease"<<std::endl;
API::release_capture(drag_wd);
xdnd_proto.mouse_release();
std::cout<<"mouse_release"<<std::endl;
target_wd = API::find_window(API::cursor_position());
_m_free_cursor();
//Exits the msg loop if xdnd_proto doesn't send the XdndDrop because of refusal of the DND
if(!xdnd_proto.mouse_release())
return detail::propagation_chain::exit;
}
return detail::propagation_chain::stop;
@ -922,9 +922,14 @@ namespace nana
return detail::propagation_chain::pass;
});
}
if(xdnd_proto.executed_action() != 0)
{
if(executed_action)
*executed_action = _m_from_xdnd_action(xdnd_proto.executed_action());
return (nullptr != target_wd);
return true;
}
}
#endif
return false;
}
@ -936,6 +941,19 @@ namespace nana
return nana::detail::platform_spec::instance();
}
static dnd_action _m_from_xdnd_action(Atom action) noexcept
{
auto & atombase = _m_spec().atombase();
if(action == atombase.xdnd_action_copy)
return dnd_action::copy;
else if(action == atombase.xdnd_action_move)
return dnd_action::move;
else if(action == atombase.xdnd_action_link)
return dnd_action::link;
return dnd_action::copy;
}
//dndversion<<24, fl_XdndURIList, XA_STRING, 0
static void _m_client_msg(Window wd_target, Window wd_src, int flag, Atom xdnd_atom, Atom data, Atom data_type)
{
@ -974,7 +992,7 @@ namespace nana
#ifdef NANA_WINDOWS
#elif defined (NANA_X11)
nana::detail::shared_icons icons_;
nana::detail::theme icons_;
struct hovered_status
{
Window native_wd{0};
@ -1070,7 +1088,7 @@ namespace nana
std::unique_ptr<dragdrop_service::dropdata_type> dropdata{new dragdrop_service::dropdata_type};
auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle, dropdata.get());
auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle, dropdata.get(), nullptr);
real_wd->other.dnd_state = dragdrop_status::not_ready;
impl_->dragging = false;
@ -1125,7 +1143,7 @@ namespace nana
dragdrop_session * ddrop{nullptr};
std::function<bool()> predicate;
std::function<data()> generator;
std::function<void(bool)> drop_finished;
std::function<void(bool, dnd_action, data&)> drop_finished;
struct event_handlers
{
@ -1143,28 +1161,18 @@ namespace nana
void make_drop()
{
if (!generator)
return;
auto transf_data = generator();
dragdrop_service::dropdata_type dropdata;
dropdata.assign(*transf_data.real_data_);
/* //deprecated
#ifdef NANA_WINDOWS
drop_source drop_src{ source_handle };
DWORD result_effect = DROPEFFECT_NONE;
auto status = ::DoDragDrop(&dropdata, &drop_src, DROPEFFECT_COPY | DROPEFFECT_MOVE, &result_effect);
if (DROPEFFECT_NONE == result_effect)
{
}
if (drop_finished)
drop_finished(DROPEFFECT_NONE != result_effect);
#else
#endif
*/
auto has_dropped = dragdrop_service::instance().dragdrop(source_handle, &dropdata);
dnd_action executed_action;
auto has_dropped = dragdrop_service::instance().dragdrop(source_handle, &dropdata, &executed_action);
if(drop_finished)
drop_finished(has_dropped);
drop_finished(has_dropped, executed_action, transf_data);
}
};
@ -1240,15 +1248,16 @@ namespace nana
impl_->generator = generator;
}
void dragdrop::drop_finished(std::function<void(bool)> finish_fn)
void dragdrop::drop_finished(std::function<void(bool, dnd_action, data&)> finish_fn)
{
impl_->drop_finished = finish_fn;
}
dragdrop::data::data():
dragdrop::data::data(dnd_action requested_action):
real_data_(new detail::dragdrop_data)
{
real_data_->requested_action = requested_action;
}
dragdrop::data::~data()