/** * Drag and Drop Implementation * Nana C++ Library(http://www.nanapro.org) * Copyright(C) 2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) * * @file: nana/gui/dragdrop.cpp * @author: Jinhao(cnjinhao@hotmail.com) */ #include #include #include #include #include #include #include #ifdef NANA_WINDOWS # include # include # include # include #elif defined(NANA_X11) # include "../detail/posix/xdnd_protocol.hpp" # include # include # include #endif namespace nana { namespace detail { struct dragdrop_data { std::vector files; }; #ifdef NANA_X11 xdnd_data to_xdnd_data(const dragdrop_data& data) { xdnd_data xdata; xdata.files = data.files; return xdata; } #endif } class dragdrop_session { public: struct target_rep { std::set target; std::map native_target_count; }; virtual ~dragdrop_session() = default; void insert(window source, window target) { auto &rep = table_[source]; rep.target.insert(target); #ifdef NANA_X11 auto native_wd = API::root(target); rep.native_target_count[native_wd] += 1; nana::detail::platform_spec::instance().dragdrop_target(native_wd, true, 1); #endif } void erase(window source, window target) { auto i = table_.find(source); if (table_.end() == i) return; i->second.target.erase(target); #ifdef NANA_WINDOWS if ((nullptr == target) || i->second.target.empty()) table_.erase(i); #else if(nullptr == target) { //remove all targets of source for(auto & native : i->second.native_target_count) { nana::detail::platform_spec::instance().dragdrop_target(native.first, false, native.second); } table_.erase(i); } else { nana::detail::platform_spec::instance().dragdrop_target(API::root(target), false, 1); if(i->second.target.empty()) table_.erase(i); } #endif } bool has(window source, window target) const { auto i = table_.find(source); if (i != table_.end()) return (0 != i->second.target.count(target)); return false; } bool has_source(window source) const { return (table_.count(source) != 0); } bool empty() const { return table_.empty(); } void set_current_source(window source) { if (table_.count(source)) current_source_ = source; else current_source_ = nullptr; } window current_source() const { return current_source_; } private: std::map table_; window current_source_{ nullptr }; }; #ifdef NANA_WINDOWS template class win32com_iunknown : public Interface { public: //Implements IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IUnknown || riid == iid) { *ppv = static_cast(this); AddRef(); return S_OK; } *ppv = NULL; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_count_); } STDMETHODIMP_(ULONG) Release() { LONG cRef = InterlockedDecrement(&ref_count_); if (cRef == 0) delete this; return cRef; } private: LONG ref_count_{ 1 }; }; class win32com_drop_target : public IDropTarget, public dragdrop_session { public: win32com_drop_target(bool simple_mode): simple_mode_(simple_mode) { } //Implements IUnknown STDMETHODIMP QueryInterface(REFIID riid, void **ppv) { if (riid == IID_IUnknown || riid == IID_IDropTarget) { *ppv = static_cast(this); AddRef(); return S_OK; } *ppv = NULL; return E_NOINTERFACE; } STDMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&ref_count_); } STDMETHODIMP_(ULONG) Release() { LONG cRef = InterlockedDecrement(&ref_count_); if (cRef == 0) delete this; return cRef; } private: // IDropTarget STDMETHODIMP DragEnter(IDataObject* data, DWORD grfKeyState, POINTL pt, DWORD* req_effect) { *req_effect &= DROPEFFECT_COPY; FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; STGMEDIUM medium; if (S_OK == data->GetData(&fmt, &medium)) { ::ReleaseStgMedium(&medium); effect_ = DROPEFFECT_COPY; } return S_OK; } STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD* req_effect) { //bool found_data = false; if (simple_mode_) { auto hovered_wd = API::find_window(point(pt.x, pt.y)); if ((hovered_wd && (hovered_wd == this->current_source())) || this->has(this->current_source(), hovered_wd)) *req_effect &= DROPEFFECT_COPY; else *req_effect = DROPEFFECT_NONE; } else { *req_effect = effect_; } return S_OK; } STDMETHODIMP DragLeave() { effect_ = DROPEFFECT_NONE; return S_OK; } STDMETHODIMP Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) { return E_NOTIMPL; } private: LONG ref_count_{ 1 }; bool const simple_mode_; //Simple mode behaves the simple_dragdrop. DWORD effect_{ DROPEFFECT_NONE }; }; class drop_source : public win32com_iunknown { public: drop_source(window wd) : window_handle_(wd) {} window source() const { return window_handle_; } private: // IDropSource STDMETHODIMP QueryContinueDrag(BOOL esc_pressed, DWORD key_state) override { if (esc_pressed) return DRAGDROP_S_CANCEL; //Drop the object if left button is released. if (0 == (key_state & (MK_LBUTTON))) return DRAGDROP_S_DROP; return S_OK; } STDMETHODIMP GiveFeedback(DWORD effect) override { return DRAGDROP_S_USEDEFAULTCURSORS; } private: window const window_handle_; }; class win32_dropdata : public win32com_iunknown { #if 0 //deprecated class enumer : public win32com_iunknown { public: enumer(drop_data& data) : data_(data) {} enumer(const enumer& rhs): data_(rhs.data_), cursor_(rhs.cursor_) { } private: // Implement IEnumFORMATETC HRESULT Clone(IEnumFORMATETC **ppenum) override { *ppenum = new enumer{ *this }; return S_OK; } HRESULT Next(ULONG celt, FORMATETC *rgelt, ULONG *pceltFetched) override { HRESULT result = (cursor_ + celt <= data_.entries_.size() ? S_OK : S_FALSE); auto const fetched = (std::min)(std::size_t(celt), data_.entries_.size() - cursor_); for (std::size_t i = 0; i < fetched; ++i) *rgelt++ = data_.entries_[cursor_++]->format; if (pceltFetched) *pceltFetched = static_cast(fetched); else if (celt > 1) return S_FALSE; return (celt == fetched ? S_OK : S_FALSE); } HRESULT Reset() override { cursor_ = 0; return S_OK; } HRESULT Skip(ULONG celt) override { if (cursor_ + celt < data_.entries_.size()) { cursor_ += celt; return S_OK; } cursor_ = data_.entries_.size(); return S_FALSE; } private: drop_data & data_; std::size_t cursor_; }; #endif public: struct data_entry { FORMATETC format; STGMEDIUM medium; bool read_from; //Indicates the data which is used for reading. ~data_entry() { ::CoTaskMemFree(format.ptd); ::ReleaseStgMedium(&medium); } bool compare(const FORMATETC& fmt, bool rdfrom) const { return (format.cfFormat == fmt.cfFormat && (format.tymed & fmt.tymed) != 0 && (format.dwAspect == DVASPECT_THUMBNAIL || format.dwAspect == DVASPECT_ICON || medium.tymed == TYMED_NULL || format.lindex == fmt.lindex || (format.lindex == 0 && fmt.lindex == -1) || (format.lindex == -1 && fmt.lindex == 0)) && format.dwAspect == fmt.dwAspect && read_from == rdfrom); } }; data_entry * find(const FORMATETC& fmt, bool read_from) const { data_entry * last_weak_match = nullptr; for (auto & entry : entries_) { if (entry->compare(fmt, read_from)) { auto entry_ptd = entry->format.ptd; if (entry_ptd && fmt.ptd && entry_ptd->tdSize == fmt.ptd->tdSize && (0 == std::memcmp(entry_ptd, fmt.ptd, fmt.ptd->tdSize))) return entry.get(); else if (nullptr == entry_ptd && nullptr == fmt.ptd) return entry.get(); last_weak_match = entry.get(); } } return last_weak_match; } void assign(const detail::dragdrop_data& data) { if (!data.files.empty()) { std::size_t bytes = sizeof(wchar_t); for (auto & file : data.files) { auto file_s = file.wstring(); bytes += (file_s.size() + 1) * sizeof(file_s.front()); } auto hglobal = ::GlobalAlloc(GHND | GMEM_SHARE, sizeof(DROPFILES) + bytes); auto dropfiles = reinterpret_cast(::GlobalLock(hglobal)); dropfiles->pFiles = sizeof(DROPFILES); dropfiles->fWide = true; auto file_buf = reinterpret_cast(dropfiles) + sizeof(DROPFILES); for (auto & file : data.files) { auto file_s = file.wstring(); std::memcpy(file_buf, file_s.data(), (file_s.size() + 1) * sizeof(file_s.front())); file_buf += (file_s.size() + 1) * sizeof(file_s.front()); } *reinterpret_cast(file_buf) = 0; ::GlobalUnlock(hglobal); assign(hglobal); } } data_entry* assign(HGLOBAL hglobal) { FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; auto entry = find(fmt, true); if (entry) { //Free the current entry for reuse ::CoTaskMemFree(entry->format.ptd); ::ReleaseStgMedium(&entry->medium); } else { //Create a new entry entries_.emplace_back(new data_entry); entry = entries_.back().get(); } //Assign the format to the entry. entry->read_from = true; entry->format = fmt; //Assign the stgMedium entry->medium.tymed = TYMED_HGLOBAL; entry->medium.hGlobal = hglobal; entry->medium.pUnkForRelease = nullptr; return entry; } public: // Implement IDataObject STDMETHODIMP GetData(FORMATETC *request_format, STGMEDIUM *pmedium) override { if (!(request_format && pmedium)) return E_INVALIDARG; pmedium->hGlobal = nullptr; auto entry = find(*request_format, true); if (entry) return _m_copy_medium(pmedium, &entry->medium, &entry->format); return DV_E_FORMATETC; } STDMETHODIMP GetDataHere(FORMATETC *pformatetc, STGMEDIUM *pmedium) override { return E_NOTIMPL; } STDMETHODIMP QueryGetData(FORMATETC *pformatetc) override { if (NULL == pformatetc) return E_INVALIDARG; if (!(DVASPECT_CONTENT & pformatetc->dwAspect)) return DV_E_DVASPECT; HRESULT result = DV_E_TYMED; for (auto & entry : entries_) { if (entry->format.tymed & pformatetc->tymed) { if (entry->format.cfFormat == pformatetc->cfFormat) return S_OK; result = DV_E_FORMATETC; } } return result; } STDMETHODIMP GetCanonicalFormatEtc(FORMATETC *pformatectIn, FORMATETC *pformatetcOut) override { return E_NOTIMPL; } STDMETHODIMP SetData(FORMATETC *pformatetc, STGMEDIUM *pmedium, BOOL fRelease) override { if (!(pformatetc && pmedium)) return E_INVALIDARG; if (pformatetc->tymed != pmedium->tymed) return E_FAIL; entries_.emplace_back(new data_entry); auto entry = entries_.back().get(); entry->format = *pformatetc; _m_copy_medium(&entry->medium, pmedium, pformatetc); if (TRUE == fRelease) ::ReleaseStgMedium(pmedium); return S_OK; } STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) override { if (NULL == ppenumFormatEtc) return E_INVALIDARG; if (DATADIR_GET != dwDirection) return E_NOTIMPL; *ppenumFormatEtc = nullptr; FORMATETC rgfmtetc[] = { //{ CF_UNICODETEXT, nullptr, DVASPECT_CONTENT, 0, TYMED_HGLOBAL } { CF_HDROP, nullptr, DVASPECT_CONTENT, 0, TYMED_HGLOBAL } }; return ::SHCreateStdEnumFmtEtc(ARRAYSIZE(rgfmtetc), rgfmtetc, ppenumFormatEtc); } STDMETHODIMP DAdvise(FORMATETC *pformatetc, DWORD advf, IAdviseSink *pAdvSink, DWORD *pdwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } STDMETHODIMP DUnadvise(DWORD dwConnection) override { return OLE_E_ADVISENOTSUPPORTED; } STDMETHODIMP EnumDAdvise(IEnumSTATDATA **ppenumAdvise) override { return OLE_E_ADVISENOTSUPPORTED; } private: static HRESULT _m_copy_medium(STGMEDIUM* stgmed_dst, STGMEDIUM* stgmed_src, FORMATETC* fmt_src) { if (!(stgmed_dst && stgmed_src && fmt_src)) return E_INVALIDARG; switch (stgmed_src->tymed) { case TYMED_HGLOBAL: stgmed_dst->hGlobal = (HGLOBAL)OleDuplicateData(stgmed_src->hGlobal, fmt_src->cfFormat, 0); break; case TYMED_GDI: case TYMED_ENHMF: //GDI object can't be copied to an existing HANDLE if (stgmed_dst->hGlobal) return E_INVALIDARG; stgmed_dst->hGlobal = (HBITMAP)OleDuplicateData(stgmed_src->hGlobal, fmt_src->cfFormat, 0); break; case TYMED_MFPICT: stgmed_dst->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(stgmed_src->hMetaFilePict, fmt_src->cfFormat, 0); break; case TYMED_FILE: stgmed_dst->lpszFileName = (LPOLESTR)OleDuplicateData(stgmed_src->lpszFileName, fmt_src->cfFormat, 0); break; case TYMED_ISTREAM: stgmed_dst->pstm = stgmed_src->pstm; stgmed_src->pstm->AddRef(); break; case TYMED_ISTORAGE: stgmed_dst->pstg = stgmed_src->pstg; stgmed_src->pstg->AddRef(); break; case TYMED_NULL: default: break; } stgmed_dst->tymed = stgmed_src->tymed; stgmed_dst->pUnkForRelease = nullptr; if (stgmed_src->pUnkForRelease) { stgmed_dst->pUnkForRelease = stgmed_src->pUnkForRelease; stgmed_src->pUnkForRelease->AddRef(); } return S_OK; } private: std::vector> entries_; }; #elif defined(NANA_X11) constexpr int xdnd_version = 5; class x11_dropdata { public: void assign(const detail::dragdrop_data& data) { data_ = &data; } const detail::dragdrop_data* data() const { return data_; } private: const detail::dragdrop_data* data_{nullptr}; }; class x11_dragdrop: public detail::x11_dragdrop_interface, public dragdrop_session { public: x11_dragdrop(bool simple_mode): simple_mode_(simple_mode) { } bool simple_mode() const noexcept { return simple_mode_; } public: //Implement x11_dragdrop_interface void add_ref() override { ++ref_count_; } std::size_t release() override { std::size_t val = --ref_count_; if(0 == val) delete this; return val; } private: bool const simple_mode_; std::atomic ref_count_{ 1 }; }; #endif class dragdrop_service { dragdrop_service() = default; public: #ifdef NANA_WINDOWS using dragdrop_target = win32com_drop_target; using dropdata_type = win32_dropdata; #else using dragdrop_target = x11_dragdrop; using dropdata_type = x11_dropdata; #endif static dragdrop_service& instance() { static dragdrop_service serv; return serv; } dragdrop_session* create_dragdrop(window wd, bool simple_mode) { auto native_wd = API::root(wd); if (nullptr == native_wd) return nullptr; dragdrop_target * ddrop = nullptr; auto i = table_.find(native_wd); if(table_.end() == i) { ddrop = new dragdrop_target{simple_mode}; #ifdef NANA_WINDOWS if (table_.empty()) ::OleInitialize(nullptr); ::RegisterDragDrop(reinterpret_cast(native_wd), ddrop); #else if(!_m_spec().register_dragdrop(native_wd, ddrop)) { delete ddrop; return nullptr; } #endif table_[native_wd] = ddrop; } else { ddrop = dynamic_cast(i->second); #ifdef NANA_WINDOWS ddrop->AddRef(); #else ddrop->add_ref(); #endif } return ddrop; } void remove(window source) { auto native_src = API::root(source); auto i = table_.find(API::root(source)); if (i == table_.end()) return; i->second->erase(source, nullptr); if (i->second->empty()) { auto ddrop = dynamic_cast(i->second); table_.erase(i); #ifdef NANA_WINDOWS ::RevokeDragDrop(reinterpret_cast(native_src)); ddrop->Release(); #elif defined(NANA_X11) _m_spec().remove_dragdrop(native_src); ddrop->release(); #endif } } bool dragdrop(window drag_wd, dropdata_type* dropdata) { auto i = table_.find(API::root(drag_wd)); if ((!dropdata) && table_.end() == i) return false; internal_revert_guard rvt_lock; #ifdef NANA_WINDOWS auto drop_src = new drop_source{ drag_wd }; i->second->set_current_source(drag_wd); DWORD result_effect{ DROPEFFECT_NONE }; auto status = ::DoDragDrop(dropdata, drop_src, DROPEFFECT_COPY, &result_effect); i->second->set_current_source(nullptr); delete drop_src; return (DROPEFFECT_NONE != result_effect); #elif defined(NANA_X11) auto& atombase = _m_spec().atombase(); auto ddrop = dynamic_cast(i->second); auto const native_source = reinterpret_cast(API::root(drag_wd)); { detail::platform_scope_guard lock; ::XSetSelectionOwner(_m_spec().open_display(), atombase.xdnd_selection, native_source, CurrentTime); } hovered_.window_handle = nullptr; hovered_.native_wd = 0; window target_wd = 0; 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{ if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind) { auto const disp = _m_spec().open_display(); if (MotionNotify == msg_pkt.u.xevent.type) { auto pos = API::cursor_position(); auto native_cur_wd = reinterpret_cast(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) { 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()) { auto cur_wd = API::find_window(API::cursor_position()); std::cout<<" Hovered="<has(drag_wd, cur_wd)) ? "dnd-move" : "dnd-none"); std::cout<<" ICON="<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{ if(detail::msg_packet_tag::pkt_family::xevent == msg_pkt.kind) { auto const disp = _m_spec().open_display(); if (MotionNotify == msg_pkt.u.xevent.type) { auto pos = API::cursor_position(); auto native_cur_wd = reinterpret_cast(detail::native_interface::find_window(pos.x, pos.y)); xdnd_proto.mouse_move(native_cur_wd, pos); } else if(ClientMessage == msg_pkt.u.xevent.type) { auto & xclient = msg_pkt.u.xevent.xclient; if(xdnd_proto.client_message(xclient)) { API::release_capture(drag_wd); return detail::propagation_chain::exit; } } else if(SelectionRequest == msg_pkt.u.xevent.type) { xdnd_proto.selection_request(msg_pkt.u.xevent.xselectionrequest, data); } else if(msg_pkt.u.xevent.type == ButtonRelease) { std::cout<<"ButtonRelease"< table_; #ifdef NANA_WINDOWS #elif defined (NANA_X11) nana::detail::shared_icons icons_; struct hovered_status { Window native_wd{0}; window window_handle{nullptr}; unsigned shape{0}; Cursor cursor{0}; }hovered_; #endif }; struct simple_dragdrop::implementation { window window_handle{ nullptr }; dragdrop_session * ddrop{ nullptr }; std::function predicate; std::map> targets; bool dragging{ false }; struct event_handlers { nana::event_handle destroy; nana::event_handle mouse_move; nana::event_handle mouse_down; }events; #ifdef NANA_X11 bool cancel() { if (!dragging) return false; if (API::is_window(window_handle)) { dragging = true; using basic_window = ::nana::detail::basic_window; auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(window_handle); real_wd->other.dnd_state = dragdrop_status::not_ready; } API::release_capture(window_handle); dragging = false; return true; } #endif }; simple_dragdrop::simple_dragdrop(window drag_wd) : impl_(new implementation) { if (!API::is_window(drag_wd)) { delete impl_; throw std::invalid_argument("simple_dragdrop: invalid window handle"); } impl_->ddrop = dragdrop_service::instance().create_dragdrop(drag_wd, true); impl_->window_handle = drag_wd; API::dev::window_draggable(drag_wd, true); auto & events = API::events<>(drag_wd); impl_->events.destroy = events.destroy.connect_unignorable([this](const arg_destroy&) { dragdrop_service::instance().remove(impl_->window_handle); API::dev::window_draggable(impl_->window_handle, false); }); impl_->events.mouse_down = events.mouse_down.connect_unignorable([this](const arg_mouse& arg){ if (arg.is_left_button() && API::is_window(impl_->window_handle)) { impl_->dragging = ((!impl_->predicate) || impl_->predicate()); using basic_window = ::nana::detail::basic_window; auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(impl_->window_handle); real_wd->other.dnd_state = dragdrop_status::ready; } }); impl_->events.mouse_move = events.mouse_move.connect_unignorable([this](const arg_mouse& arg) { if (!(arg.is_left_button() && impl_->dragging && API::is_window(arg.window_handle))) return; using basic_window = ::nana::detail::basic_window; auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(arg.window_handle); real_wd->other.dnd_state = dragdrop_status::in_progress; std::unique_ptr dropdata{new dragdrop_service::dropdata_type}; auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle, dropdata.get()); real_wd->other.dnd_state = dragdrop_status::not_ready; impl_->dragging = false; if (has_dropped) { auto drop_wd = API::find_window(API::cursor_position()); auto i = impl_->targets.find(drop_wd); if ((impl_->targets.end() != i) && i->second) i->second(); } }); } simple_dragdrop::~simple_dragdrop() { if (impl_->window_handle) { dragdrop_service::instance().remove(impl_->window_handle); API::dev::window_draggable(impl_->window_handle, false); API::umake_event(impl_->events.destroy); API::umake_event(impl_->events.mouse_down); API::umake_event(impl_->events.mouse_move); } delete impl_; } void simple_dragdrop::condition(std::function predicate_fn) { if (nullptr == impl_) throw std::logic_error("simple_dragdrop is empty"); impl_->predicate.swap(predicate_fn); } void simple_dragdrop::make_drop(window target, std::function drop_fn) { if (nullptr == impl_) throw std::logic_error("simple_dragdrop is empty"); impl_->ddrop->insert(impl_->window_handle, target); impl_->targets[target].swap(drop_fn); } //This is class dragdrop struct dragdrop::implementation { window const source_handle; bool dragging{ false }; dragdrop_session * ddrop{nullptr}; std::function predicate; std::function generator; std::function drop_finished; struct event_handlers { nana::event_handle destroy; nana::event_handle mouse_move; nana::event_handle mouse_down; }events; implementation(window source): source_handle(source) { ddrop = dragdrop_service::instance().create_dragdrop(source, false); API::dev::window_draggable(source, true); } void make_drop() { 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); if(drop_finished) drop_finished(has_dropped); } }; dragdrop::dragdrop(window source) : impl_(new implementation(source)) { auto & events = API::events(source); impl_->events.destroy = events.destroy.connect_unignorable([this](const arg_destroy&) { dragdrop_service::instance().remove(impl_->source_handle); API::dev::window_draggable(impl_->source_handle, false); }); impl_->events.mouse_down = events.mouse_down.connect_unignorable([this](const arg_mouse& arg) { if (arg.is_left_button() && API::is_window(impl_->source_handle)) { impl_->dragging = ((!impl_->predicate) || impl_->predicate()); using basic_window = ::nana::detail::basic_window; auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(impl_->source_handle); real_wd->other.dnd_state = dragdrop_status::ready; } }); impl_->events.mouse_move = events.mouse_move.connect_unignorable([this](const arg_mouse& arg) { if (!(arg.is_left_button() && impl_->dragging && API::is_window(arg.window_handle))) return; using basic_window = ::nana::detail::basic_window; auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(arg.window_handle); real_wd->other.dnd_state = dragdrop_status::in_progress; impl_->make_drop(); //deprecated //auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle); real_wd->other.dnd_state = dragdrop_status::not_ready; impl_->dragging = false; /* //deprecated if (has_dropped) { auto drop_wd = API::find_window(API::cursor_position()); //auto i = impl_->targets.find(drop_wd); //if ((impl_->targets.end() != i) && i->second) // i->second(); } */ }); } dragdrop::~dragdrop() { if (impl_->source_handle) { dragdrop_service::instance().remove(impl_->source_handle); API::dev::window_draggable(impl_->source_handle, false); API::umake_event(impl_->events.destroy); API::umake_event(impl_->events.mouse_down); API::umake_event(impl_->events.mouse_move); } delete impl_; } void dragdrop::condition(std::function predicate_fn) { impl_->predicate = predicate_fn; } void dragdrop::prepare_data(std::function generator) { impl_->generator = generator; } void dragdrop::drop_finished(std::function finish_fn) { impl_->drop_finished = finish_fn; } dragdrop::data::data(): real_data_(new detail::dragdrop_data) { } dragdrop::data::~data() { delete real_data_; } dragdrop::data::data(data&& rhs): real_data_(new detail::dragdrop_data) { std::swap(real_data_, rhs.real_data_); } dragdrop::data& dragdrop::data::operator=(data&& rhs) { if (this != &rhs) { auto moved_data = new detail::dragdrop_data; delete real_data_; real_data_ = rhs.real_data_; rhs.real_data_ = moved_data; } return *this; } void dragdrop::data::insert(std::filesystem::path path) { real_data_->files.emplace_back(std::move(path)); } }//end namespace nana