diff --git a/.travis.yml b/.travis.yml index d98c0dcf..47cc31ef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ matrix: - alsa-oss - libx11-dev - libxft-dev + - libxcursor-dev sources: - ubuntu-toolchain-r-test @@ -36,6 +37,7 @@ matrix: - alsa-oss - libx11-dev - libxft-dev + - libxcursor-dev sources: - ubuntu-toolchain-r-test - llvm-toolchain-precise diff --git a/build/codeblocks/nana.cbp b/build/codeblocks/nana.cbp index 18c78755..d48270ec 100644 --- a/build/codeblocks/nana.cbp +++ b/build/codeblocks/nana.cbp @@ -66,6 +66,7 @@ + diff --git a/build/vc2013/nana.vcxproj b/build/vc2013/nana.vcxproj index 8501f83f..4051495b 100644 --- a/build/vc2013/nana.vcxproj +++ b/build/vc2013/nana.vcxproj @@ -202,6 +202,7 @@ + diff --git a/build/vc2013/nana.vcxproj.filters b/build/vc2013/nana.vcxproj.filters index 6fe722eb..4c14b4d7 100644 --- a/build/vc2013/nana.vcxproj.filters +++ b/build/vc2013/nana.vcxproj.filters @@ -333,6 +333,9 @@ Source Files\nana\detail + + Source Files\nana\gui + diff --git a/build/vc2015/nana.vcxproj b/build/vc2015/nana.vcxproj index 3ec4cf7d..f271092c 100644 --- a/build/vc2015/nana.vcxproj +++ b/build/vc2015/nana.vcxproj @@ -196,6 +196,7 @@ + diff --git a/build/vc2015/nana.vcxproj.filters b/build/vc2015/nana.vcxproj.filters index d1d7ba78..c0cfcbdc 100644 --- a/build/vc2015/nana.vcxproj.filters +++ b/build/vc2015/nana.vcxproj.filters @@ -291,6 +291,9 @@ Source Files\detail + + Source Files\gui + diff --git a/build/vc2017/nana.vcxproj b/build/vc2017/nana.vcxproj index cc3c70dc..39d61fe8 100644 --- a/build/vc2017/nana.vcxproj +++ b/build/vc2017/nana.vcxproj @@ -179,6 +179,7 @@ + diff --git a/build/vc2017/nana.vcxproj.filters b/build/vc2017/nana.vcxproj.filters index 10e92180..65431a04 100644 --- a/build/vc2017/nana.vcxproj.filters +++ b/build/vc2017/nana.vcxproj.filters @@ -292,6 +292,9 @@ Sources\gui + + Sources\gui + diff --git a/include/nana/gui/basis.hpp b/include/nana/gui/basis.hpp index a582d7b9..f7ed845d 100644 --- a/include/nana/gui/basis.hpp +++ b/include/nana/gui/basis.hpp @@ -65,6 +65,13 @@ namespace nana blend }; + enum class dragdrop_status + { + not_ready, + ready, + in_progress + }; + namespace category { enum class flags diff --git a/include/nana/gui/detail/basic_window.hpp b/include/nana/gui/detail/basic_window.hpp index 6c5bc202..5911b598 100644 --- a/include/nana/gui/detail/basic_window.hpp +++ b/include/nana/gui/detail/basic_window.hpp @@ -1,7 +1,7 @@ /** * A Basic Window Widget Definition * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -179,7 +179,8 @@ namespace detail bool ignore_menubar_focus : 1; ///< A flag indicates whether the menubar sets the focus. bool ignore_mouse_focus : 1; ///< A flag indicates whether the widget accepts focus when clicking on it bool space_click_enabled : 1; ///< A flag indicates whether enable mouse_down/click/mouse_up when pressing and releasing whitespace key. - unsigned Reserved :18; + bool draggable : 1; + unsigned Reserved :17; unsigned char tab; ///< indicate a window that can receive the keyboard TAB mouse_action action; mouse_action action_before; @@ -234,6 +235,7 @@ namespace detail ///< if the active_window is null, the parent of this window keeps focus. paint::graphics glass_buffer; ///< if effect.bground is avaiable. Refer to window_layout::make_bground. update_state upd_state; + dragdrop_status dnd_state{ dragdrop_status::not_ready }; union { diff --git a/include/nana/gui/dragdrop.hpp b/include/nana/gui/dragdrop.hpp new file mode 100644 index 00000000..e68279b8 --- /dev/null +++ b/include/nana/gui/dragdrop.hpp @@ -0,0 +1,45 @@ +/** +* 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.hpp +* @author: Jinhao(cnjinhao@hotmail.com) +*/ +#ifndef NANA_GUI_DRAGDROP_INCLUDED +#define NANA_GUI_DRAGDROP_INCLUDED + +#include +#include +#include "basis.hpp" + +#include + +namespace nana +{ + class simple_dragdrop + { + struct implementation; + + simple_dragdrop(const simple_dragdrop&) = delete; + simple_dragdrop& operator=(const simple_dragdrop&) = delete; + + simple_dragdrop(simple_dragdrop&&) = delete; + simple_dragdrop& operator=(simple_dragdrop&&) = delete; + public: + simple_dragdrop(window drag_wd); + ~simple_dragdrop(); + + /// Sets a condition that determines whether the drag&drop can start + void condition(std::function predicate_fn); + void make_drop(window target, std::function drop_fn); + private: + implementation* const impl_; + }; +} + +#endif \ No newline at end of file diff --git a/include/nana/gui/programming_interface.hpp b/include/nana/gui/programming_interface.hpp index 134b0082..56c3ec7d 100644 --- a/include/nana/gui/programming_interface.hpp +++ b/include/nana/gui/programming_interface.hpp @@ -1,7 +1,7 @@ /* * Nana GUI Programming Interface Implementation * Nana C++ Library(http://www.nanapro.org) - * Copyright(C) 2003-2017 Jinhao(cnjinhao@hotmail.com) + * Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -117,6 +117,9 @@ namespace API void lazy_refresh(); void draw_shortkey_underline(paint::graphics&, const std::string& text, wchar_t shortkey, std::size_t shortkey_position, const point& text_pos, const color&); + + void window_draggable(window, bool enabled); + bool window_draggable(window); }//end namespace dev @@ -476,6 +479,8 @@ namespace API ::std::optional> content_extent(window wd, unsigned limited_px, bool limit_width); unsigned screen_dpi(bool x_requested); + + dragdrop_status window_dragdrop_status(::nana::window); }//end namespace API }//end namespace nana diff --git a/include/nana/gui/widgets/listbox.hpp b/include/nana/gui/widgets/listbox.hpp index a2eee239..dde46edc 100644 --- a/include/nana/gui/widgets/listbox.hpp +++ b/include/nana/gui/widgets/listbox.hpp @@ -1497,6 +1497,9 @@ the nana::detail::basic_window member pointer scheme void erase(index_pairs indexes); ///dispatch(reinterpret_cast(modal)); } + void platform_spec::msg_dispatch(std::function msg_filter_fn) + { + msg_dispatcher_->dispatch(msg_filter_fn); + + } + void* platform_spec::request_selection(native_window_type requestor, Atom type, size_t& size) { if(requestor) @@ -1104,6 +1110,40 @@ namespace detail return graph; } + + bool platform_spec::register_dragdrop(native_window_type wd, dragdrop_interface* ddrop) + { + platform_scope_guard lock; + if(0 != xdnd_.dragdrop.count(wd)) + return false; + + int dndver = 4; + ::XChangeProperty(display_, reinterpret_cast(wd), atombase_.xdnd_aware, XA_ATOM, sizeof(int) * 8, + PropModeReplace, reinterpret_cast(&dndver), 1); + + auto & ref_drop = xdnd_.dragdrop[wd]; + ref_drop.dragdrop = ddrop; + ref_drop.ref_count = 1; + return true; + } + + dragdrop_interface* platform_spec::remove_dragdrop(native_window_type wd) + { + platform_scope_guard lock; + auto i = xdnd_.dragdrop.find(wd); + if(i == xdnd_.dragdrop.end()) + return nullptr; + + auto ddrop = i->second; + if(ddrop.ref_count <= 1) + { + xdnd_.dragdrop.erase(i); + return ddrop.dragdrop; + } + --ddrop.ref_count; + return nullptr; + } + //_m_msg_filter //@return: _m_msg_filter returns three states // 0 = msg_dispatcher dispatches the XEvent @@ -1163,7 +1203,7 @@ namespace detail else if(evt.xselection.property == self.atombase_.xdnd_selection) { bool accepted = false; - msg.kind = msg.kind_mouse_drop; + msg.kind = msg_packet_tag::pkt_family::mouse_drop; msg.u.mouse_drop.window = 0; if(bytes_left > 0 && type == self.xdnd_.good_type) { diff --git a/source/detail/posix/msg_dispatcher.hpp b/source/detail/posix/msg_dispatcher.hpp index 9ba12ad1..f5b13bb9 100644 --- a/source/detail/posix/msg_dispatcher.hpp +++ b/source/detail/posix/msg_dispatcher.hpp @@ -143,7 +143,7 @@ namespace detail { //Make a cleanup msg packet to infor the dispatcher the window is closed. msg_packet_tag msg; - msg.kind = msg.kind_cleanup; + msg.kind = msg_packet_tag::pkt_family::cleanup; msg.u.packet_window = wd; thr->msg_queue.push_back(msg); } @@ -171,6 +171,30 @@ namespace detail } } } + + template + void dispatch(MsgFilter msg_filter_fn) + { + auto tid = nana::system::this_thread_id(); + msg_packet_tag msg; + int qstate; + + //Test whether the thread is registered for window, and retrieve the queue state for event + while((qstate = _m_read_queue(tid, msg, 0))) + { + //the queue is empty + if(-1 == qstate) + { + if(false == _m_wait_for_queue(tid)) + proc_.timer_proc(tid); + } + else + { + if(msg_filter_fn(msg)) + return; + } + } + } private: void _m_msg_driver() { @@ -220,7 +244,7 @@ namespace detail switch(proc_.filter_proc(event, msg_pack)) { case 0: - msg_pack.kind = msg_pack.kind_xevent; + msg_pack.kind = msg_packet_tag::pkt_family::xevent; msg_pack.u.xevent = event; _m_msg_dispatch(msg_pack); break; @@ -246,9 +270,9 @@ namespace detail { switch(pack.kind) { - case msg_packet_tag::kind_xevent: + case msg_packet_tag::pkt_family::xevent: return _m_event_window(pack.u.xevent); - case msg_packet_tag::kind_mouse_drop: + case msg_packet_tag::pkt_family::mouse_drop: return pack.u.mouse_drop.window; default: break; @@ -294,7 +318,7 @@ namespace detail //Check whether the event dispatcher is used for the modal window //and when the modal window is closing, the event dispatcher would //stop event pumping. - if((modal == msg.u.packet_window) && (msg.kind == msg.kind_cleanup)) + if((modal == msg.u.packet_window) && (msg.kind == msg_packet_tag::pkt_family::cleanup)) return 0; return 1; diff --git a/source/detail/posix/msg_packet.hpp b/source/detail/posix/msg_packet.hpp index ea24091f..fbc0848f 100644 --- a/source/detail/posix/msg_packet.hpp +++ b/source/detail/posix/msg_packet.hpp @@ -10,8 +10,8 @@ namespace detail { struct msg_packet_tag { - enum kind_t{kind_xevent, kind_mouse_drop, kind_cleanup}; - kind_t kind; + enum class pkt_family{xevent, mouse_drop, cleanup}; + pkt_family kind; union { XEvent xevent; diff --git a/source/detail/posix/platform_spec.hpp b/source/detail/posix/platform_spec.hpp index 3191ddb1..af472a92 100644 --- a/source/detail/posix/platform_spec.hpp +++ b/source/detail/posix/platform_spec.hpp @@ -36,6 +36,7 @@ #include #include +#include #include "msg_packet.hpp" #include "../platform_abstraction_types.hpp" @@ -176,6 +177,12 @@ namespace detail ~platform_scope_guard(); }; + class dragdrop_interface + { + public: + virtual ~dragdrop_interface() = default; + }; + class platform_spec { typedef platform_spec self_type; @@ -246,6 +253,7 @@ namespace detail void msg_insert(native_window_type); void msg_set(timer_proc_type, event_proc_type); void msg_dispatch(native_window_type modal); + void msg_dispatch(std::function); //X Selections void* request_selection(native_window_type requester, Atom type, size_t & bufsize); @@ -255,6 +263,9 @@ namespace detail //@biref: The image object should be kept for a long time till the window is closed, // the image object is release in remove() method. const nana::paint::graphics& keep_window_icon(native_window_type, const nana::paint::image&); + + bool register_dragdrop(native_window_type, dragdrop_interface*); + dragdrop_interface* remove_dragdrop(native_window_type); private: static int _m_msg_filter(XEvent&, msg_packet_tag&); void _m_caret_routine(); @@ -311,6 +322,14 @@ namespace detail int timestamp; Window wd_src; nana::point pos; + + struct refcount_dragdrop + { + dragdrop_interface* dragdrop{nullptr}; + std::size_t ref_count{0}; + }; + + std::map dragdrop; }xdnd_; msg_dispatcher * msg_dispatcher_; diff --git a/source/gui/detail/basic_window.cpp b/source/gui/detail/basic_window.cpp index dc072370..e0f6b02d 100644 --- a/source/gui/detail/basic_window.cpp +++ b/source/gui/detail/basic_window.cpp @@ -1,7 +1,7 @@ /* * A Basic Window Widget Definition * Nana C++ Library(http://www.nanapro.org) -* Copyright(C) 2003-2016 Jinhao(cnjinhao@hotmail.com) +* Copyright(C) 2003-2018 Jinhao(cnjinhao@hotmail.com) * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at @@ -410,6 +410,7 @@ namespace nana flags.enabled = true; flags.modal = false; flags.take_active = true; + flags.draggable = false; flags.dropable = false; flags.fullscreen = false; flags.tab = nana::detail::tab_type::none; diff --git a/source/gui/detail/bedrock_posix.cpp b/source/gui/detail/bedrock_posix.cpp index 0504cf95..cbee18d2 100644 --- a/source/gui/detail/bedrock_posix.cpp +++ b/source/gui/detail/bedrock_posix.cpp @@ -418,10 +418,10 @@ namespace detail { switch(msg.kind) { - case nana::detail::msg_packet_tag::kind_xevent: + case nana::detail::msg_packet_tag::pkt_family::xevent: window_proc_for_xevent(display, msg.u.xevent); break; - case nana::detail::msg_packet_tag::kind_mouse_drop: + case nana::detail::msg_packet_tag::pkt_family::mouse_drop: window_proc_for_packet(display, msg); break; default: break; @@ -441,7 +441,7 @@ namespace detail switch(msg.kind) { - case nana::detail::msg_packet_tag::kind_mouse_drop: + case nana::detail::msg_packet_tag::pkt_family::mouse_drop: msgwd = brock.wd_manager().find_window(native_window, {msg.u.mouse_drop.x, msg.u.mouse_drop.y}); if(msgwd) { diff --git a/source/gui/dragdrop.cpp b/source/gui/dragdrop.cpp new file mode 100644 index 00000000..a4323dc2 --- /dev/null +++ b/source/gui/dragdrop.cpp @@ -0,0 +1,824 @@ +/** +* 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 + +#ifdef NANA_WINDOWS +# include +# include +# include +# include +#elif defined(NANA_X11) +# include "../detail/posix/platform_spec.hpp" +# include +# include +# include +#endif + +namespace nana +{ + /// drop_association + /** + * This class is used for querying whether tow windows have a connection of drag and drop + */ + class drop_association + { + public: + void add(window source, window target) + { + assoc_[source].insert(target); + } + + void erase(window wd) + { + assoc_.erase(wd); + + for (auto & assoc : assoc_) + assoc.second.erase(wd); + } + + bool has(window source, window target) const + { + auto i = assoc_.find(source); + if (i != assoc_.end()) + return (0 != i->second.count(target)); + + return false; + } + private: + std::map> assoc_; + }; + +#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: + win32com_drop_target(const drop_association& drop_assoc) : + drop_assoc_(drop_assoc) + {} + + void set_source(window wd) + { + source_window_ = wd; + } + public: + //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* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) + { + *pdwEffect &= DROPEFFECT_COPY; + return S_OK; + } + + STDMETHODIMP DragOver(DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) + { + auto hovered_wd = API::find_window(point(pt.x, pt.y)); + if ((hovered_wd && (hovered_wd == source_window_)) || drop_assoc_.has(source_window_, hovered_wd)) + *pdwEffect &= DROPEFFECT_COPY; + else + *pdwEffect = DROPEFFECT_NONE; + + return S_OK; + } + + STDMETHODIMP DragLeave() + { + return E_NOTIMPL; + } + + STDMETHODIMP Drop(IDataObject* pDataObj, DWORD grfKeyState, POINTL pt, DWORD* pdwEffect) + { + return E_NOTIMPL; + } + private: + LONG ref_count_{ 1 }; + + window source_window_{ nullptr }; + const drop_association& drop_assoc_; + }; + + 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 drop_data : public win32com_iunknown + { + struct medium + { + STGMEDIUM * stgmedium; + FORMATETC * format; + }; + public: + STDMETHODIMP GetData(FORMATETC *request_format, STGMEDIUM *pmedium) override + { + if (!(request_format && pmedium)) + return E_INVALIDARG; + + pmedium->hGlobal = nullptr; + + for (auto & med : mediums_) + { + if ((request_format->tymed & med.format->tymed) && + (request_format->dwAspect == med.format->dwAspect) && + (request_format->cfFormat == med.format->cfFormat)) + { + return _m_copy_medium(pmedium, med.stgmedium, med.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 & med : mediums_) + { + if (med.format->tymed & pformatetc->tymed) + { + if (med.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; + + medium retain; + retain.format = new FORMATETC; + retain.stgmedium = new (std::nothrow) STGMEDIUM; + if (nullptr == retain.stgmedium) + { + delete retain.format; + return E_FAIL; + } + + std::memset(retain.format, 0, sizeof(FORMATETC)); + std::memset(retain.stgmedium, 0, sizeof(STGMEDIUM)); + + *retain.format = *pformatetc; + + _m_copy_medium(retain.stgmedium, pmedium, pformatetc); + + if (TRUE == fRelease) + ::ReleaseStgMedium(pmedium); + + mediums_.emplace_back(retain); + return S_OK; + } + + STDMETHODIMP EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppenumFormatEtc) override + { + if (NULL == ppenumFormatEtc) + return E_INVALIDARG; + + if (DATADIR_GET != dwDirection) + return E_NOTIMPL; + + *ppenumFormatEtc = NULL; + + FORMATETC rgfmtetc[] = + { + { CF_UNICODETEXT, NULL, 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: + stgmed_dst->hBitmap = (HBITMAP)OleDuplicateData(stgmed_src->hBitmap, fmt_src->cfFormat, 0); + break; + case TYMED_MFPICT: + stgmed_dst->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData(stgmed_src->hMetaFilePict, fmt_src->cfFormat, 0); + break; + case TYMED_ENHMF: + stgmed_dst->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData(stgmed_src->hEnhMetaFile, 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 mediums_; + }; +#elif defined(NANA_X11) + class x11_dragdrop: public detail::dragdrop_interface + { + + }; + + 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_; + }; +#endif + + class dragdrop_service + { + dragdrop_service() = default; + public: + static dragdrop_service& instance() + { + static dragdrop_service serv; + return serv; + } + + void create_dragdrop(window wd) + { + auto native_wd = API::root(wd); + if (nullptr == native_wd) + return; + +#ifdef NANA_WINDOWS + if(table_.empty()) + ::OleInitialize(nullptr); + + win32com_drop_target* drop_target = nullptr; + + auto i = table_.find(native_wd); + if (i == table_.end()) + { + drop_target = new win32com_drop_target{drop_assoc_}; + ::RegisterDragDrop(reinterpret_cast(native_wd), drop_target); + + table_[native_wd] = drop_target; + } + else + { + drop_target = i->second; + drop_target->AddRef(); + } +#elif defined(NANA_X11) + auto ddrop = new x11_dragdrop; + if(!_m_spec().register_dragdrop(native_wd, ddrop)) + delete ddrop; +#endif + } + + void remove(window wd) + { +#ifdef NANA_WINDOWS + auto i = table_.find(API::root(wd)); + if (i != table_.end()) + { + if (0 == i->second->Release()) + table_.erase(i); + } +#elif defined(NANA_X11) + auto ddrop = _m_spec().remove_dragdrop(API::root(wd)); + delete ddrop; +#endif + drop_assoc_.erase(wd); + + } + + bool dragdrop(window drag_wd) + { +#ifdef NANA_WINDOWS + auto i = table_.find(API::root(drag_wd)); + if (table_.end() == i) + return false; + + auto drop_src = new drop_source{ drag_wd }; + auto drop_dat = new (std::nothrow) drop_data; + if (!drop_dat) + { + delete drop_src; + return false; + } + + i->second->set_source(drag_wd); + + + DWORD eff; + auto status = ::DoDragDrop(drop_dat, drop_src, DROPEFFECT_COPY, &eff); + + i->second->set_source(nullptr); + + return true; +#elif defined(NANA_X11) + auto const native_wd = reinterpret_cast(API::root(drag_wd)); + + { + detail::platform_scope_guard lock; + ::XSetSelectionOwner(_m_spec().open_display(), _m_spec().atombase().xdnd_selection, native_wd, CurrentTime); + } + + + hovered_.window_handle = nullptr; + hovered_.native_wd = 0; + window target_wd = 0; + auto& atombase = _m_spec().atombase(); + //while(true) + { + + _m_spec().msg_dispatch([this, drag_wd, native_wd, &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(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_wd, atombase.xdnd_enter, atombase.text_uri_list, XA_STRING); + hovered_.native_wd = native_cur_wd; + } + + auto cur_wd = API::find_window(API::cursor_position()); + + if(hovered_.window_handle != cur_wd) + { + _m_free_cursor(); + + hovered_.window_handle = cur_wd; + + if((drag_wd == cur_wd) || drop_assoc_.has(drag_wd, cur_wd)) + hovered_.cursor = ::XcursorFilenameLoadCursor(disp, icons_.cursor("dnd-move").c_str()); + else + hovered_.cursor = ::XcursorFilenameLoadCursor(disp, icons_.cursor("dnd-none").c_str()); + ::XDefineCursor(disp, native_cur_wd, hovered_.cursor); + } + } + else if(msg_pkt.u.xevent.type == ButtonRelease) + { + target_wd = API::find_window(API::cursor_position()); + ::XUndefineCursor(disp, hovered_.native_wd); + _m_free_cursor(); + return true; + } + + } + return false; + }); + } + + return (nullptr != target_wd); +#endif + return false; + } + + drop_association& drop_assoc() + { + return drop_assoc_; + } +#ifdef NANA_X11 + private: + static nana::detail::platform_spec & _m_spec() + { + return nana::detail::platform_spec::instance(); + } + + //dndversion<<24, fl_XdndURIList, XA_STRING, 0 + static void _m_client_msg(Window wd_target, Window wd_src, Atom xdnd_atom, Atom data, Atom data_type) + { + auto const display = _m_spec().open_display(); + XEvent evt; + ::memset(&evt, 0, sizeof evt); + evt.xany.type = ClientMessage; + evt.xany.display = display; + evt.xclient.window = wd_target; + evt.xclient.message_type = xdnd_atom; + evt.xclient.format = 32; + + //Target window + evt.xclient.data.l[0] = wd_src; + //Accept set + evt.xclient.data.l[1] = 1; + evt.xclient.data.l[2] = data; + evt.xclient.data.l[3] = data_type; + evt.xclient.data.l[4] = 0; + + ::XSendEvent(display, wd_target, True, NoEventMask, &evt); + + } + + static int _m_xdnd_aware(Window wd) + { + Atom actual; int format; unsigned long count, remaining; + unsigned char *data = 0; + XGetWindowProperty(_m_spec().open_display(), wd, _m_spec().atombase().xdnd_aware, + 0, 4, False, XA_ATOM, &actual, &format, &count, &remaining, &data); + + int version = 0; + if ((actual == XA_ATOM) && (format==32) && count && data) + version = int(*(Atom*)data); + + if (data) + ::XFree(data); + return version; + } + + void _m_free_cursor() + { + if(hovered_.cursor) + { + ::XFreeCursor(_m_spec().open_display(), hovered_.cursor); + hovered_.cursor = 0; + } + + } +#endif + private: + drop_association drop_assoc_; +#ifdef NANA_WINDOWS + std::map table_; +#elif defined (NANA_X11) + 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; + std::function predicate; + std::map> targets; + + bool dragging{ false }; + +#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) + { + dragdrop_service::instance().create_dragdrop(drag_wd); + + if (!API::is_window(drag_wd)) + { + delete impl_; + throw std::invalid_argument("simple_dragdrop: invalid window handle"); + } + + impl_->window_handle = drag_wd; + API::dev::window_draggable(drag_wd, true); + + auto & events = API::events<>(drag_wd); + +#if 1 //#ifdef NANA_WINDOWS + 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; + } + }); + + 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; + + auto has_dropped = dragdrop_service::instance().dragdrop(arg.window_handle); + + 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(); + } + }); +#elif 1 + events.mouse_down.connect_unignorable([drag_wd](const arg_mouse& arg){ + if (arg.is_left_button() && API::is_window(drag_wd)) + { + //API::set_capture(drag_wd, true); + + using basic_window = ::nana::detail::basic_window; + auto real_wd = reinterpret_cast<::nana::detail::basic_window*>(drag_wd); + real_wd->other.dnd_state = dragdrop_status::ready; + } + }); + + events.mouse_move.connect_unignorable([this](const arg_mouse& arg){ + if (!arg.is_left_button()) + return; + + if (impl_->dragging) + { + auto drop_wd = API::find_window(API::cursor_position()); + auto i = impl_->targets.find(drop_wd); + + } + else + { + if ((!impl_->predicate) || impl_->predicate()) + { + if (API::is_window(arg.window_handle)) + { + impl_->dragging = true; + 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; + + dragdrop_service::instance().dragdrop(arg.window_handle); + return; + } + } + + //API::release_capture(impl_->window_handle); + } + + }); + + events.mouse_up.connect_unignorable([this]{ + if (impl_->cancel()) + { + auto drop_wd = API::find_window(API::cursor_position()); + auto i = impl_->targets.find(drop_wd); + if (impl_->targets.end() == i || !i->second) + return; + + i->second(); + } + }); + + events.key_press.connect_unignorable([this]{ + impl_->cancel(); + }); +#endif + } + + simple_dragdrop::~simple_dragdrop() + { + dragdrop_service::instance().remove(impl_->window_handle); + API::dev::window_draggable(impl_->window_handle, false); + delete impl_; + } + + void simple_dragdrop::condition(std::function predicate_fn) + { + impl_->predicate.swap(predicate_fn); + } + + void simple_dragdrop::make_drop(window target, std::function drop_fn) + { + dragdrop_service::instance().drop_assoc().add(impl_->window_handle, target); + impl_->targets[target].swap(drop_fn); + } +} \ No newline at end of file diff --git a/source/gui/programming_interface.cpp b/source/gui/programming_interface.cpp index 698aa543..1f3ac8c9 100644 --- a/source/gui/programming_interface.cpp +++ b/source/gui/programming_interface.cpp @@ -411,6 +411,26 @@ namespace API } } + + void window_draggable(window wd, bool enabled) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(real_wd)) + real_wd->flags.draggable = enabled; + } + + bool window_draggable(window wd) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + if (restrict::wd_manager().available(real_wd)) + return real_wd->flags.draggable; + + return false; + } + + }//end namespace dev widget* get_widget(window wd) @@ -1518,5 +1538,16 @@ namespace API { return ::nana::platform_abstraction::screen_dpi(x_requested); } + + dragdrop_status window_dragdrop_status(::nana::window wd) + { + auto real_wd = reinterpret_cast(wd); + internal_scope_guard lock; + + if (restrict::wd_manager().available(real_wd)) + return real_wd->other.dnd_state; + + return dragdrop_status::not_ready; + } }//end namespace API }//end namespace nana diff --git a/source/gui/widgets/listbox.cpp b/source/gui/widgets/listbox.cpp index e6a5c1c6..29a28ec1 100644 --- a/source/gui/widgets/listbox.cpp +++ b/source/gui/widgets/listbox.cpp @@ -1968,6 +1968,12 @@ namespace nana std::vector active_panes_; };//end class es_lister + enum class operation_states + { + none, + msup_deselect + }; + /// created and live by the trigger, holds data for listbox: the state of the struct does not effect on member funcions, therefore all data members are public. struct essence @@ -1981,7 +1987,9 @@ namespace nana bool auto_draw{true}; bool checkable{false}; bool if_image{false}; +#if 0 //deprecated bool deselect_deferred{ false }; //deselects items when mouse button is released. +#endif unsigned text_height; ::nana::listbox::export_options def_exp_options; @@ -1999,6 +2007,12 @@ namespace nana std::function ctg_icon_renderer; ///< Renderer for the category icon + struct operation_rep + { + operation_states state{operation_states::none}; + index_pair item; + }operation; + struct mouse_selection_part { bool started{ false }; @@ -2121,6 +2135,10 @@ namespace nana void update_mouse_selection(const point& screen_pos) { + //Don't update if it is not started + if (!mouse_selection.started) + return; + mouse_selection.screen_pos = screen_pos; auto logic_pos = coordinate_cast(screen_pos, true); @@ -4132,8 +4150,13 @@ namespace nana using item_state = essence::item_state; using parts = essence::parts; +#if 0 //deprecated //Cancel deferred deselection operation when mouse moves. essence_->deselect_deferred = false; +#else + if (operation_states::msup_deselect == essence_->operation.state) + essence_->operation.state = operation_states::none; +#endif bool need_refresh = false; @@ -4293,21 +4316,44 @@ namespace nana essence_->mouse_selection.reverse_selection = true; new_selected_status = !essence_->cs_status(abs_item_pos, true); } +#if 0 //deprecated + else if (nana::mouse::right_button == arg.button) + { + //Unselects all selected items if the current item is not selected before selecting. + auto selected = lister.pick_items(true); + if (selected.cend() == std::find(selected.cbegin(), selected.cend(), item_pos)) + lister.select_for_all(false, abs_item_pos); + } else { - if (nana::mouse::right_button == arg.button) + //Unselects all selected items except current item if right button clicked. + lister.select_for_all(false, abs_item_pos); //cancel all selections + } +#else + else + { + auto selected = lister.pick_items(true); + if (selected.cend() != std::find(selected.cbegin(), selected.cend(), item_pos)) { - //Unselects all selected items if the current item is not selected before selecting. - auto selected = lister.pick_items(true); - if (selected.cend() == std::find(selected.cbegin(), selected.cend(), item_pos)) - lister.select_for_all(false, abs_item_pos); + //If the current selected one has been selected before selecting, remains the selection states for all + //selected items. But these items will be unselected when the mouse is released. + + //Other items will be unselected if multiple items are selected. + if (selected.size() > 1) + { + essence_->operation.item = abs_item_pos; + + //Don't deselect the selections if the listbox is draggable. + //It should remain the selections for drag-and-drop + + if (!API::dev::window_draggable(arg.window_handle)) + essence_->operation.state = operation_states::msup_deselect; + } } else - { - //Unselects all selected items except current item if right button clicked. - lister.select_for_all(false, abs_item_pos); //cancel all selections - } + lister.select_for_all(false, abs_item_pos); } +#endif } else { @@ -4377,7 +4423,15 @@ namespace nana //Deselection of all items is deferred to the mouse up event when ctrl or shift is not pressed //Pressing ctrl or shift is to selects other items without deselecting current selections. +#if 0 //deprecated essence_->deselect_deferred = !(arg.ctrl || arg.shift); +#else + if (!(arg.ctrl || arg.shift)) + { + essence_->operation.state = operation_states::msup_deselect; + essence_->operation.item = index_pair{nana::npos, nana::npos}; + } +#endif } if(update) @@ -4419,11 +4473,19 @@ namespace nana need_refresh = true; } +#if 0 //deprecated if (essence_->deselect_deferred) { essence_->deselect_deferred = false; need_refresh |= (essence_->lister.select_for_all(false)); } +#endif + + if (operation_states::msup_deselect == essence_->operation.state) + { + essence_->operation.state = operation_states::none; + need_refresh |= essence_->lister.select_for_all(false, essence_->operation.item); + } if (need_refresh) { @@ -5790,6 +5852,18 @@ namespace nana return item_proxy(ess); } + listbox::index_pair listbox::hovered() const + { + internal_scope_guard lock; + using parts = drawerbase::listbox::essence::parts; + + auto & ptr_where = _m_ess().pointer_where; + if ((ptr_where.first == parts::list || ptr_where.first == parts::checker) && ptr_where.second != npos) + return _m_ess().lister.advance(_m_ess().first_display(), static_cast(ptr_where.second)); + + return index_pair{ npos, npos }; + } + bool listbox::sortable() const { internal_scope_guard lock; diff --git a/source/gui/widgets/skeletons/content_view.cpp b/source/gui/widgets/skeletons/content_view.cpp index 0596b4c6..cb7e291b 100644 --- a/source/gui/widgets/skeletons/content_view.cpp +++ b/source/gui/widgets/skeletons/content_view.cpp @@ -65,7 +65,7 @@ namespace nana { bool passive{ true }; //The passive mode determines whether to update if scrollbar changes. It updates the client window if passive is true. - bool drag_started{ false }; + bool drag_view_move{ false }; //indicates the status of the content-view if it moves origin by dragging point origin; std::shared_ptr cv_scroll; @@ -120,19 +120,22 @@ namespace nana { if (!arg.is_left_button()) return; - this->drag_started = this->view.view_area().is_hit(arg.pos); + this->drag_view_move = this->view.view_area().is_hit(arg.pos); } else if (event_code::mouse_move == arg.evt_code) { - if (this->drag_started && this->drive(arg.pos)) + if (this->drag_view_move && (dragdrop_status::not_ready == API::window_dragdrop_status(this->window_handle))) { - tmr.interval(16); - tmr.start(); + if (this->drive(arg.pos)) + { + tmr.interval(16); + tmr.start(); + } } } else if (event_code::mouse_up == arg.evt_code) { - this->drag_started = false; + this->drag_view_move = false; tmr.stop(); } };