cnjinhao / nana

a modern C++ GUI library
https://nana.acemind.cn
Boost Software License 1.0
2.29k stars 331 forks source link

Multi selection support for treebox #691

Closed edgarbernal5 closed 1 month ago

edgarbernal5 commented 4 months ago

Hi! Just wondering if we can add support for multi selection as already is with listbox, don't know if you have plans to do it soon, and if not can you give me an insight of it, where can I start from? Thanks in advance!

edgarbernal5 commented 4 months ago

Hi again! After debugging and modifying the code, I am able to add support for multiselect. I added Toggle and Replace Select (ctrl and shift key modifiers respectively).

To sum up:

bool set_selected_multi(node_type* node, bool crtl, bool shift)
{
    if (crtl)
    {
        data.stop_drawing = true;
        if (node_state.is_selected_node(node))
        {
            node_state.remove_node(node);
            item_proxy iprx(data.trigger_ptr, node);
            data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, false }, data.widget_ptr->handle());
        }
        else
        {
            node_state.add_node(node);
            item_proxy iprx(data.trigger_ptr, node);
            data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle());
        }
        data.stop_drawing = false;

        node_state.selected = node;
        return true;
    }

    data.stop_drawing = true;
    if (node_state.selected)
    {
        //node_state.selected through node

        node_state.clear();
        auto target1 = node_state.selected;
        auto target2 = node;
        item_proxy{ data.trigger_ptr, attr.tree_cont.get_root() }.visit_recursively([&](item_proxy&& i)
        {
            if (i.empty())
            {
                return false;
            }
            else if ((!i.child().empty() && !i.expanded()))
            {
                if ((target1 == nullptr || target2 == nullptr) && (i._m_node() == target1 || i._m_node() == target2))
                {
                    node_state.add_node(i._m_node());
                    return false;
                }
            }

            if ((target1 == nullptr || target2 == nullptr) && (i._m_node() == target1 || i._m_node() == target2))
            {
                node_state.add_node(i._m_node());
                return false;
            }

            if (i._m_node() == node_state.selected)
            {
                target1 = nullptr;
                node_state.add_node(i._m_node());
            }
            else if (i._m_node() == node)
            {
                target2 = nullptr;
                node_state.add_node(i._m_node());
            }
            else if ((target1 == nullptr || target2 == nullptr))
            {
                if (i.hidden())
                    return false;

                node_state.add_node(i._m_node());
            }
            return true;
        });

        for (auto& sel_node : node_state.nodes_selected)
        {
            item_proxy iprx(data.trigger_ptr, sel_node);
            data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle());
        }
    }
    else
    {
        node_state.add_node(node);
        item_proxy iprx(data.trigger_ptr, node);
        data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle());
    }

    node_state.selected = node;

    data.stop_drawing = false;
    return true;
}

Existing set_selected function was modified to manage selected node vector:

bool set_selected(node_type * node)
{
    if(node_state.selected != node)
    {
        data.stop_drawing = true;
        if (node_state.selected)
        {
            auto copy_nodes = node_state.nodes_selected;
            node_state.clear();
            for (auto& selected_node : copy_nodes)
            {
                item_proxy iprx(data.trigger_ptr, selected_node);
                data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, false }, data.widget_ptr->handle());
            }
        }

        node_state.selected = node;
        if (node)
        {
            node_state.add_node(node_state.selected);
            item_proxy iprx(data.trigger_ptr, node_state.selected);
            data.widget_ptr->events().selected.emit(::nana::arg_treebox{ *data.widget_ptr, iprx, true }, data.widget_ptr->handle());
        }
        data.stop_drawing = false;
        return true;
    }
    return false;
}

Finally to know if a node is selected before rendering it, modified node attribute for selected field: ndattr.selected = (node_state.is_selected_node(node));

And that's pretty much of it. Want to know what do you think guys? @cnjinhao @qPCR4vir. Thanks for your time!

cnjinhao commented 4 months ago

Hi, @edgarbernal5 , good job!

edgarbernal5 commented 4 months ago

Thanks!!! I created the PR #693

edgarbernal5 commented 3 months ago

How do we use this new feature?

In the client side, set the flag to true m_treebox.enable_multiselection(true);

I just added two new methods in treebox:

void treebox::deselect_all()
{
    auto dw = &get_drawer_trigger();
    bool should_draw = dw->impl()->node_state.nodes_selected.size() > 0;
    dw->impl()->node_state.deselect_all();
    if (should_draw)
        dw->impl()->draw(false);
}

and this:

void treebox::selected(std::vector<treebox::item_proxy>& selected_nodes) const
{
    auto dw = &get_drawer_trigger();
    for (auto& item : dw->impl()->node_state.nodes_selected)
    {
        selected_nodes.push_back({ const_cast<drawer_trigger_t*>(dw) , item });
    }
}
cnjinhao commented 3 months ago

Look forward to your PR.

edgarbernal5 commented 3 months ago

Nice!! The PR is created #694.