fine-tune drag'n'drop in a tree

View: New views
4 Messages — Rating Filter:   Alert me  

fine-tune drag'n'drop in a tree

by Ionutz Borcoman :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

Hi,

I'm playing around with the TreeStore, but I'm unable to obtain the effects I
want.

Let's assume I have this tree:

-- 1
   -- 1.1
      -- 1.1.1
      -- 1.1.2
   -- 1.2
      -- 1.2.1
      -- 1.2.2
-- 2

I want to be able to do these:
* move (1) after (2) or (2) in front of (1)
* move (1.1) after (1.2) or (1.2) in front of (1.1)
* move (1.1) or (1.2) under (2)
* (1.1.1), (1.1.2), (1.2.1) and (1.2.2) should not be draggable
* (1.1) and (1.2) should always be attached to (1) or (2)
* (2) or (1) should always stay under the root

I have so far created a TreeStore with a model that has a
Gtk::TreeModelColumn<int> m_level member.

I have set m_level to 1 for (1) and (2) and with 2 for (1.1) and (1.2).
The "unmovable" rows has this set to 0.

I was able to restrict this way what rows can be dragged via
row_draggable_vfunc. But I'm unable to controll the dropping mechanism
correctly. That is:
* I can't attach (1.1) or (1.2)
* move (2) in front of (1) if I drop it over (1)
* move (1) after (2) if I drop (1) over (2)
* move (1.1) as the last item if I drop it over (1)

Any help would be appreciated. I have attached the code I've wrote so far.

Thanx,

Johnny

[treewindow.cc]

#include "treewindow.h"
#include <iostream>

TreeModel::TreeModel()
{
        // From the GTMM Book:
        //
        // "We can't just call Gtk::TreeModel(m_columns) in the initializer list
        //  because m_columns does not exist when the base class constructor runs.
        //  And we can't have a static m_columns instance, because that would be
        //  instantiated before the gtk type system.
        //  So, we use this method, which should only be used just after creation!"
        //
        set_column_types(m_columns);
}

Glib::RefPtr<TreeModel> TreeModel::create()
{
        return Glib::RefPtr<TreeModel> (new TreeModel);
}

bool TreeModel::row_draggable_vfunc(const Gtk::TreeModel::Path& path) const
{
        TreeModel* model = const_cast<TreeModel*>(this);
        const_iterator iter = model->get_iter(path);
        if(iter)
        {
                Row row = *iter;
                std::cout << "row_draggable_vfunc:"
                        << " id: " << row[m_columns.m_id]
                        << " level: " << row[m_columns.m_level]
                        << std::endl;
                return (row[m_columns.m_level] > 0);
        }

        std::cout << "row_draggable_vfunc: false - no row" << std::endl;
       
        return false;
}

bool TreeModel::row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const
{
        // obtain the row being dragged
        Glib::RefPtr<Gtk::TreeModel> ref_model = Glib::RefPtr<Gtk::TreeModel>(const_cast<TreeModel*>(this));
        ref_model->reference();
       
        Gtk::TreeModel::Path orig;
        Gtk::TreeModel::Path::get_from_selection_data(selection_data, ref_model, orig);

        const_iterator orig_iter = ref_model->get_iter(orig);
        if(orig_iter)
        {
                Row orig_row = *orig_iter;
                std::cout << "\torig: "
                        << " id: " << orig_row[m_columns.m_id]
                        << " level: " << orig_row[m_columns.m_level]
                        << std::endl;
                       
                TreeModel* model = const_cast<TreeModel*>(this);
       
                const_iterator dest_iter = model->get_iter(dest);
                if(dest_iter)
                {
                        Row dest_row = *dest_iter;
                        std::cout << "\tdest: "
                                << " id: " << dest_row[m_columns.m_id]
                                << " level: " << dest_row[m_columns.m_level]
                                << std::endl;

                        return (orig_row[m_columns.m_level] > 0 && dest_row[m_columns.m_level] > 0);
                }
        }

        std::cout << "row_drop_possible_vfunc: false - no row" << std::endl;
       
        return false;
}

// bool TreeModel::drag_data_received_vfunc(const Gtk::TreeModel::Path & dest, const Gtk::SelectionData & selection_data)
// {
// return true;
// }

TreeWindow::TreeWindow(BaseObjectType* object,
        const Glib::RefPtr<Gnome::Glade::Xml>& refGlade)
: Gtk::Window(object)
{
        // connect button signals
        Gtk::Button* btn = 0;
        refGlade->get_widget("quitbtn", btn);
        if(!btn)
                throw std::runtime_error("quitbtn not found");
        btn->signal_clicked().connect(sigc::mem_fun(*this, &TreeWindow::on_quitbtn));

        btn = 0;
        refGlade->get_widget("dumpbtn", btn);
        if(!btn)
                throw std::runtime_error("dumpbtn not found");
       
        btn->signal_clicked().connect(sigc::mem_fun(*this, &TreeWindow::on_dumpbtn));

        Gtk::TreeView* view = 0;
        refGlade->get_widget("treeview", view);
        if(!view)
                throw std::runtime_error("treeview not found");
               
        // create model and connect to the view
        m_model = TreeModel::create();
        TreeModel::ModelColumns& cols = m_model->m_columns;
       
        view->set_model(m_model);
       
        // set model data
        Gtk::TreeModel::Row row = *(m_model->append());
        row[cols.m_id] = 1;
        row[cols.m_name] = "foo1";
        row[cols.m_level] = 1;
       
        Gtk::TreeModel::Row row2 = *(m_model->append(row.children()));
        row2[cols.m_id] = 11;
        row2[cols.m_name] = "foo1.1";
        row2[cols.m_level] = 2;

        row2 = *(m_model->append(row.children()));
        row2[cols.m_id] = 12;
        row2[cols.m_name] = "foo1.2";
        row2[cols.m_level] = 2;
       
        Gtk::TreeModel::Row row3 = *(m_model->append(row2.children()));
        row3[cols.m_id] = 121;
        row3[cols.m_name] = "foo1.2.1";
        row3[cols.m_level] = 0;

        row3 = *(m_model->append(row2.children()));
        row3[cols.m_id] = 122;
        row3[cols.m_name] = "foo1.2.2";
        row3[cols.m_level] = 0;
       
        row2 = *(m_model->append(row.children()));
        row2[cols.m_id] = 13;
        row2[cols.m_name] = "foo1.3";
        row2[cols.m_level] = 2;
       
        row = *(m_model->append());
        row[cols.m_id] = 2;
        row[cols.m_name] = "bar2";
        row[cols.m_level] = 1;

        // show the columns
        view->append_column("ID", cols.m_id);
        view->append_column("Name", cols.m_name);

        for(int i=0; i< view->get_columns().size(); ++i)
        {
                Gtk::TreeViewColumn* p = view->get_column(i);
                p->set_reorderable();
        }
}

TreeWindow::~TreeWindow()
{
}

void TreeWindow::on_quitbtn()
{
        hide();
}

void TreeWindow::on_dumpbtn()
{
        std::cout << "Dumping the model" << std::endl;
        typedef Gtk::TreeModel::Children TChildren;
        TChildren children = m_model->children();
        for(TChildren::iterator it = children.begin(); it != children.end(); ++it)
        {
                Gtk::TreeModel::Row row = *it;
                std::cout << "\t" << row[m_model->m_columns.m_id] << ", " << row[m_model->m_columns.m_name] << std::endl;
        }
}


[treewindow.h]

#ifndef TREEWINDOW_H
#define TREEWINDOW_H

#include <gtkmm.h>
#include <libglademm.h>

class TreeModel: public Gtk::TreeStore
{
public:
        class ModelColumns : public Gtk::TreeModel::ColumnRecord
        {
        public:
                ModelColumns()
                {
                        add(m_id);
                        add(m_name);
                        add(m_level);
                }

                Gtk::TreeModelColumn<int> m_id;
                Gtk::TreeModelColumn<Glib::ustring> m_name;
                Gtk::TreeModelColumn<int> m_level;
        };

        static Glib::RefPtr<TreeModel> create();
        ModelColumns m_columns;
       
private:
        TreeModel();

        // overide drag_n_drop virtual functions
        bool row_draggable_vfunc(const Gtk::TreeModel::Path& path) const;
        bool row_drop_possible_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data) const;
// bool drag_data_received_vfunc(const Gtk::TreeModel::Path& dest, const Gtk::SelectionData& selection_data);
};

class TreeWindow: public Gtk::Window
{
public:
        TreeWindow(BaseObjectType* object, const Glib::RefPtr<Gnome::Glade::Xml>& refGlade);
        ~TreeWindow();
private:
        Glib::RefPtr<TreeModel> m_model;

        void on_quitbtn();
        void on_dumpbtn();
};

#endif


<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
<!--Generated with glade3 3.4.4 on Wed Apr 30 20:56:12 2008 -->
<glade-interface>
  <widget class="GtkWindow" id="TreeWindow">
    <property name="default_width">400</property>
    <property name="default_height">200</property>
    <child>
      <widget class="GtkVBox" id="vbox1">
        <property name="visible">True</property>
        <child>
          <widget class="GtkScrolledWindow" id="scrolledwindow1">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
            <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
            <child>
              <widget class="GtkTreeView" id="treeview">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="headers_clickable">True</property>
                <property name="reorderable">True</property>
                <property name="rules_hint">True</property>
                <property name="enable_tree_lines">True</property>
              </widget>
            </child>
          </widget>
        </child>
        <child>
          <widget class="GtkHButtonBox" id="hbuttonbox1">
            <property name="visible">True</property>
            <property name="layout_style">GTK_BUTTONBOX_SPREAD</property>
            <child>
              <widget class="GtkButton" id="dumpbtn">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="label" translatable="yes">Dump Model</property>
                <property name="response_id">0</property>
              </widget>
            </child>
            <child>
              <widget class="GtkButton" id="quitbtn">
                <property name="visible">True</property>
                <property name="can_focus">True</property>
                <property name="receives_default">True</property>
                <property name="label" translatable="yes">Quit</property>
                <property name="response_id">0</property>
              </widget>
              <packing>
                <property name="position">1</property>
              </packing>
            </child>
          </widget>
          <packing>
            <property name="expand">False</property>
            <property name="padding">5</property>
            <property name="position">1</property>
          </packing>
        </child>
      </widget>
    </child>
  </widget>
</glade-interface>

[treemain.cc]

#include "treewindow.h"
#include <iostream>

int main(int argc, char *argv[])
{
        Gtk::Main kit(argc, argv);
        Glib::RefPtr<Gnome::Glade::Xml> refXml;

        try
        {
                refXml = Gnome::Glade::Xml::create(SOURCE_DIR "/treemain.glade");
        }
        catch(const Gnome::Glade::XmlError& ex)
        {
                std::cerr << ex.what() << std::endl;
                return 1;
        }

        TreeWindow* window = 0;
        refXml->get_widget_derived("TreeWindow", window);
        if(window)
        {
                kit.run(*window);
        }
        else
        {
                std::cerr << "Window template not found." << std::endl;
                return 1;
        }

        return 0;
}


_______________________________________________
gtkmm-list mailing list
gtkmm-list@...
http://mail.gnome.org/mailman/listinfo/gtkmm-list

Re: fine-tune drag'n'drop in a tree

by Murray Cumming :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Sat, 2008-05-03 at 13:29 +0300, Ionutz Borcoman wrote:

> Hi,
>
> I'm playing around with the TreeStore, but I'm unable to obtain the effects I
> want.
>
> Let's assume I have this tree:
>
> -- 1
>    -- 1.1
>       -- 1.1.1
>       -- 1.1.2
>    -- 1.2
>       -- 1.2.1
>       -- 1.2.2
> -- 2
>
> I want to be able to do these:
> * move (1) after (2) or (2) in front of (1)
> * move (1.1) after (1.2) or (1.2) in front of (1.1)
> * move (1.1) or (1.2) under (2)
> * (1.1.1), (1.1.2), (1.2.1) and (1.2.2) should not be draggable
> * (1.1) and (1.2) should always be attached to (1) or (2)
> * (2) or (1) should always stay under the root
>
> I have so far created a TreeStore with a model that has a
> Gtk::TreeModelColumn<int> m_level member.
>
> I have set m_level to 1 for (1) and (2) and with 2 for (1.1) and (1.2).
> The "unmovable" rows has this set to 0.
>
> I was able to restrict this way what rows can be dragged via
> row_draggable_vfunc. But I'm unable to controll the dropping mechanism
> correctly.

You can do this by overriding the row_drop_possible_vfunc() virtual
method. You can then add any logic you like, usually examining the
relevant model data for that row.

I do that in Glom's (ugly) layout dialog. For instance, text items can
be dropped into group items, but not into other text items:
http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.h?view=markup
http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.cc?view=markup

It works well, so we should probably mention in the documentation.

>  That is:
> * I can't attach (1.1) or (1.2)
> * move (2) in front of (1) if I drop it over (1)
> * move (1) after (2) if I drop (1) over (2)
> * move (1.1) as the last item if I drop it over (1)
>
> Any help would be appreciated. I have attached the code I've wrote so far.
>
> Thanx,
>
> Johnny

--
murrayc@...
www.murrayc.com
www.openismus.com

_______________________________________________
gtkmm-list mailing list
gtkmm-list@...
http://mail.gnome.org/mailman/listinfo/gtkmm-list

Re: fine-tune drag'n'drop in a tree

by Ionutz Borcoman :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Wednesday 07 May 2008 2:25:22 pm Murray Cumming wrote:

> On Sat, 2008-05-03 at 13:29 +0300, Ionutz Borcoman wrote:
> You can do this by overriding the row_drop_possible_vfunc() virtual
> method. You can then add any logic you like, usually examining the
> relevant model data for that row.
>
> I do that in Glom's (ugly) layout dialog. For instance, text items can
> be dropped into group items, but not into other text items:
> http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.h?vi
>ew=markup
> http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.cc?v
>iew=markup
>
> It works well, so we should probably mention in the documentation.

After some more testing with the example from gtkmm tutorial I have managed to
find the effect I was looking for - I had simply to return false when the
parent row is top level or empty:

        Gtk::TreeModel::Path dest_parent = dest;
        bool dest_is_not_top_level = dest_parent.up();
        if(!dest_is_not_top_level || dest_parent.empty())
        {
                return false;
        }

Can somebody please explain when these cases happen? I assume I get
dest_is_not_top_level if the item is the root item, but when do I get
dest_parent.empty()?

Other question related with the D&D in a tree (not critical, but nice to
have): how can I change the cursor displayed during the drag? For example to
display an image, or to add a "+" or "-" to the current image displayed.

And thanx for all of you that stopped me falling to the dark side of qt :-D

Cheers,

Johnny
_______________________________________________
gtkmm-list mailing list
gtkmm-list@...
http://mail.gnome.org/mailman/listinfo/gtkmm-list

Re: fine-tune drag'n'drop in a tree

by Jonathon Jongsma-3 :: Rate this Message:

Reply to Author | View Threaded | Show Only this Message

On Thu, May 8, 2008 at 9:10 AM, Ionutz Borcoman <iborco@...> wrote:

> On Wednesday 07 May 2008 2:25:22 pm Murray Cumming wrote:
>  > On Sat, 2008-05-03 at 13:29 +0300, Ionutz Borcoman wrote:
>
> > You can do this by overriding the row_drop_possible_vfunc() virtual
>  > method. You can then add any logic you like, usually examining the
>  > relevant model data for that row.
>  >
>  > I do that in Glom's (ugly) layout dialog. For instance, text items can
>  > be dropped into group items, but not into other text items:
>  > http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.h?vi
>  >ew=markup
>  > http://svn.gnome.org/viewvc/glom/trunk/glom/mode_data/treestore_layout.cc?v
>  >iew=markup
>  >
>  > It works well, so we should probably mention in the documentation.
>
>  After some more testing with the example from gtkmm tutorial I have managed to
>  find the effect I was looking for - I had simply to return false when the
>  parent row is top level or empty:
>
>         Gtk::TreeModel::Path dest_parent = dest;
>         bool dest_is_not_top_level = dest_parent.up();
>         if(!dest_is_not_top_level || dest_parent.empty())
>         {
>                 return false;
>         }
>
>  Can somebody please explain when these cases happen? I assume I get
>  dest_is_not_top_level if the item is the root item, but when do I get
>  dest_parent.empty()?
>
>  Other question related with the D&D in a tree (not critical, but nice to
>  have): how can I change the cursor displayed during the drag? For example to
>  display an image, or to add a "+" or "-" to the current image displayed.
>
>  And thanx for all of you that stopped me falling to the dark side of qt :-D
>
>  Cheers,
>
>  Johnny

You might find Gdk::Window::pointer_grab() to be useful for changing
the cursor (combined with pointer_ungrab() when you want to change it
back).  Alternately, if that's not flexible enough, you can probably
use the Gdk::Cursor class more directly in combination with
'drag-motion' signals and something like
Gtk::TreeView::get_path_at_pos() (If you want to change the cursor
based on which model item the mouse is currently above).  Hope that
helps.

--
jonner
_______________________________________________
gtkmm-list mailing list
gtkmm-list@...
http://mail.gnome.org/mailman/listinfo/gtkmm-list