From 943a9e444d757dbdf868c4ff607bbccfb21f5485 Mon Sep 17 00:00:00 2001 From: Jinhao Date: Tue, 9 Oct 2018 03:56:50 +0800 Subject: [PATCH] init dragdrop contains a new class simple_dragdrop and changes of listbox for dragdrop --- build/codeblocks/nana.cbp | 1 + build/vc2013/nana.vcxproj | 1 + build/vc2013/nana.vcxproj.filters | 3 + build/vc2015/nana.vcxproj | 1 + build/vc2015/nana.vcxproj.filters | 3 + build/vc2017/nana.vcxproj | 1 + build/vc2017/nana.vcxproj.filters | 3 + include/nana/gui/basis.hpp | 7 + include/nana/gui/detail/basic_window.hpp | 6 +- include/nana/gui/dragdrop.hpp | 45 ++ include/nana/gui/programming_interface.hpp | 7 +- include/nana/gui/widgets/listbox.hpp | 3 + source/gui/detail/basic_window.cpp | 3 +- source/gui/dragdrop.cpp | 609 ++++++++++++++++++ source/gui/programming_interface.cpp | 31 + source/gui/widgets/listbox.cpp | 92 ++- source/gui/widgets/skeletons/content_view.cpp | 15 +- 17 files changed, 812 insertions(+), 19 deletions(-) create mode 100644 include/nana/gui/dragdrop.hpp create mode 100644 source/gui/dragdrop.cpp 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); /// +#include + +#include +#include + +#include +#include + +#ifdef NANA_WINDOWS +# include +# 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_; + }; +#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; + + 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(); + } + } + + void remove(window wd) + { + auto i = table_.find(API::root(wd)); + if (i != table_.end()) + { + if (0 == i->second->Release()) + table_.erase(i); + } + + drop_assoc_.erase(wd); + } + + bool dragdrop(window drag_wd) + { + 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; + } + + drop_association& drop_assoc() + { + return drop_assoc_; + } + private: + std::map table_; + drop_association drop_assoc_; + }; + + + struct simple_dragdrop::implementation + { + window window_handle; + std::function predicate; + std::map> targets; + + bool dragging{ false }; + +#if 0 + 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); + +#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()); + } + }); + + 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(); + } + }); +#else + 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(); } };