Logo Search packages:      
Sourcecode: tagcolledit version File versions

ItemList.cc

/*
 * Item list widget
 *
 * Copyright (C) 2003  Enrico Zini <enrico@debian.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#pragma implementation

#include "ItemList.h"
#include "TagMenu.h"

#include <map>

#include <sigc++/sigc++.h>
#include <gtkmm/treemodel.h>

#include "Environment.h"

using namespace std;
using namespace Tagcoll;

class TagsetCellRenderer : public Gtk::CellRendererText
{
protected:
      ItemList& itemList;

      Glib::Property< TagSet > _property_tagset;
     
      /*
      virtual void get_size_vfunc(Gtk::Widget& widget,
                                                const Gdk::Rectangle* cell_area,
                                                int* x_offset, int* y_offset,
                                                int* width,    int* height);
      */

      virtual void render_vfunc(const Glib::RefPtr<Gdk::Drawable>& drawable,
                                                Gtk::Widget& widget,
                                                const Gdk::Rectangle& background_area,
                                                const Gdk::Rectangle& cell_area,
                                                const Gdk::Rectangle& expose_area,
                                                Gtk::CellRendererState flags);

      virtual bool activate_vfunc(GdkEvent* event,
                                                Gtk::Widget& widget,
                                                const Glib::ustring& path,
                                                const Gdk::Rectangle& background_area,
                                                const Gdk::Rectangle& cell_area,
                                                Gtk::CellRendererState flags);

public:
      TagsetCellRenderer(ItemList& itemList);
      ~TagsetCellRenderer() {}
      
      Glib::PropertyProxy< TagSet > property_tagset();
};

TagsetCellRenderer::TagsetCellRenderer(ItemList& itemList) :
      Glib::ObjectBase(typeid (TagsetCellRenderer)),
      Gtk::CellRendererText(),
      itemList(itemList),
      _property_tagset(*this, "tagset", TagSet())
{
      /*
      property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE;
      property_xpad() = 2;
      property_ypad() = 2;
      */
}

Glib::PropertyProxy< TagSet > TagsetCellRenderer::property_tagset()
{
      return _property_tagset.get_proxy();
}


/*
void TagsetCellRenderer::get_size_vfunc(Gtk::Widget&,
                                                            const Gdk::Rectangle* cell_area,
                                                            int* x_offset, int* y_offset,
                                                            int* width,    int* height)
{
      enum { TOGGLE_WIDTH = 12 };

      const int calc_width  = property_xpad() * 2 + TOGGLE_WIDTH;
      const int calc_height = property_ypad() * 2 + TOGGLE_WIDTH;

      if (width)
            *width = calc_width;

      if (height)
            *height = calc_height;

      if (cell_area)
      {
            if (x_offset)
            {
                  *x_offset = int(property_xalign() * (cell_area->get_width() - calc_width));
                  *x_offset = std::max(0, *x_offset);
            }

            if (y_offset)
            {
                  *y_offset = int(property_yalign() * (cell_area->get_height() - calc_height));
                  *y_offset = std::max(0, *y_offset);
            }
      }
}
*/

void TagsetCellRenderer::render_vfunc(const Glib::RefPtr<Gdk::Drawable>& drawable,
                                                            Gtk::Widget& widget,
                                                            const Gdk::Rectangle& rect1,
                                                            const Gdk::Rectangle& cell_area,
                                                            const Gdk::Rectangle& rect2,
                                                            Gtk::CellRendererState flags)
{
      TagSet ts = _property_tagset.get_value() - itemList._tagset;
      string tags;
      for (TagSet::const_iterator j = ts.begin();
                  j != ts.end(); j++)
            if (j == ts.begin())
                  tags += j->fullname();
            else
                  tags += ", " + j->fullname();

      property_text().set_value(tags);
      Gtk::CellRendererText::render_vfunc(drawable, widget, rect1, cell_area, rect2, flags);
}


bool TagsetCellRenderer::activate_vfunc(GdkEvent*,
                                                            Gtk::Widget&,
                                                            const Glib::ustring& path,
                                                            const Gdk::Rectangle&,
                                                            const Gdk::Rectangle&,
                                                            Gtk::CellRendererState)
{
      /*
      if (property_activatable_)
      {
            signal_toggled_(path);
            return true;
      }
      */

      return false;
}


/*
void ItemList::do_changed()
{
      _signal_changed.emit();
}

void ItemList::on_selectedList_row_activated(const Gtk::TreePath& path, Gtk::TreeViewColumn*)
{
      unsigned int count_pre = _selected.size();
      if(const Gtk::TreeIter iter = selectedTagList.get_model()->get_iter(path))
      {
            Gtk::TreeRow row = *iter;
            Glib::ustring s = row[tagListModelColumns.tag];
            _selected -= s;
      }
      updateLists();
      if (count_pre != _selected.size())
            do_changed();
}

void ItemList::on_availableList_row_activated(const Gtk::TreePath& path, Gtk::TreeViewColumn*)
{
      unsigned int count_pre = _selected.size();
      if(const Gtk::TreeIter iter = availableTagList.get_model()->get_iter(path))
      {
            Gtk::TreeRow row = *iter;
            Glib::ustring s = row[tagListModelColumns.tag];
            _selected += s;
      }
      updateLists();
      if (count_pre != _selected.size())
            do_changed();
}
*/

void ItemList::on_filter_changed()
{
      filter = filterEdit.get_text();
      updateList();
}

void ItemList::on_document_changed()
{
      updateList();
}

void ItemList::on_add_tag(Tag tag)
{
      Glib::RefPtr<Gtk::TreeSelection> sel = itemList.get_selection();
      
      // Build the list of selected items
      selected.clear();

      Gtk::TreeSelection::ListHandle_Path lhp = sel->get_selected_rows();
      for (Gtk::TreeSelection::ListHandle_Path::const_iterator i = lhp.begin(); i != lhp.end(); i++)
      {
            Gtk::TreeModel::Row row = *itemListModel->get_iter(*i);
            Glib::ustring s = row[itemListModelColumns.name];
            selected.push_back(s);
      }

      // Build the Change
      TagcollChange<string, Tag> change;
      for (vector<string>::const_iterator i = selected.begin();
                  i != selected.end(); i++)
      {
            TagSet ts = doc.collection().getTagsetForItem(*i);
            change.insert(make_pair(*i, ts + tag));
      }

      // Post the change
      do_signal_request_tagcoll_change(change);
}

void ItemList::on_remove_tag(Tag tag)
{
      Glib::RefPtr<Gtk::TreeSelection> sel = itemList.get_selection();
      
      // Build the list of selected items
      selected.clear();

      Gtk::TreeSelection::ListHandle_Path lhp = sel->get_selected_rows();
      for (Gtk::TreeSelection::ListHandle_Path::const_iterator i = lhp.begin(); i != lhp.end(); i++)
      {
            Gtk::TreeModel::Row row = *itemListModel->get_iter(*i);
            Glib::ustring s = row[itemListModelColumns.name];
            selected.push_back(s);
      }
      
      // Build the Change
      TagcollChange<string, Tag> change;
      for (vector<string>::const_iterator i = selected.begin();
                  i != selected.end(); i++)
      {
            TagSet ts = doc.collection().getTagsetForItem(*i);
            change.insert(make_pair(*i, ts - tag));
      }

      // Post the change
      do_signal_request_tagcoll_change(change);
}

void ItemList::do_signal_request_tagcoll_change(TagcollChange<string, Tag> change)
{
      signal_request_tagcoll_change().emit(change);
}

void ItemList::do_signal_request_tagset_merge()
{
      signal_request_tagset_merge().emit();
}

void ItemList::do_signal_request_tagset_intersect()
{
      signal_request_tagset_intersect().emit();
}

void ItemList::do_signal_request_item_copy()
{
      signal_request_item_copy().emit();
}

void ItemList::do_signal_request_item_move()
{
      signal_request_item_move().emit();
}

void ItemList::do_signal_select_tagset(TagSet tagset)
{
      //debug("SRTA %.*s %.*s\n", PFSTR(item), PFSTR(tag));
      signal_select_tagset().emit(tagset);
}

void ItemList::do_signal_select_tagset_other_panel(TagSet tagset)
{
      //debug("SRTA %.*s %.*s\n", PFSTR(item), PFSTR(tag));
      signal_select_tagset_other_panel().emit(tagset);
}

bool ItemList::on_event(GdkEvent* e)
{
      if (e->type == GDK_BUTTON_PRESS && e->button.button == 3)
      {
            Gtk::TreeModel::Path path;
            Gtk::TreeViewColumn* column;
            int cell_x, cell_y;
            if (itemList.get_path_at_pos(
                        (int)e->button.x, (int)e->button.y,
                        path, column,
                        cell_x, cell_y))
            {
                  // Clicked on an item
                  debug("Cell %d, %d\n", cell_x, cell_y);

                  Gtk::TreeModel::Row row = *itemListModel->get_iter(path);
                  Glib::ustring tag = row[itemListModelColumns.name];
                  TagSet ts = row[itemListModelColumns.tags];
                  
                  debug("Name: %.*s, %d tags\n", PFSTR(tag), ts.size());

                  Glib::RefPtr<Gtk::TreeSelection> sel = itemList.get_selection();
                  int rows = sel->count_selected_rows();

                  itemPopup.items().clear();

                  if (rows == 1)
                  {
                        itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Select this tag set",
                                          sigc::bind< TagSet >(
                                                sigc::mem_fun(*this, &ItemList::do_signal_select_tagset), ts)));
                        itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Select this tag set in the other panel",
                                          sigc::bind< TagSet >(
                                                sigc::mem_fun(*this, &ItemList::do_signal_select_tagset_other_panel), ts)));
                  }
                  else
                  {
                        itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Merge",
                                          sigc::mem_fun(*this, &ItemList::do_signal_request_tagset_merge)));
                        itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Intersect",
                                          sigc::mem_fun(*this, &ItemList::do_signal_request_tagset_intersect)));
                  }
                  itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Copy to other panel",
                                    sigc::mem_fun(*this, &ItemList::do_signal_request_item_copy)));
                  itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Move to other panel",
                                    sigc::mem_fun(*this, &ItemList::do_signal_request_item_move)));

                  TagMenu<TagcollDocument<string> >* addMenu = new TagMenu<TagcollDocument<string> >();
                  addMenu->set_manage();
                  addMenu->populateUnselected(doc, ts);
                  string stag = tag;
                  addMenu->signal_selected().connect(
                                    sigc::mem_fun(*this, &ItemList::on_add_tag));
                  itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("_Add...", *addMenu));
      
                  if (!ts.empty())
                  {
                        itemPopup.items().push_back(Gtk::Menu_Helpers::SeparatorElem());
                        for (TagSet::const_iterator i = ts.begin();
                                    i != ts.end(); i++)
                              itemPopup.items().push_back(Gtk::Menu_Helpers::MenuElem("Remove " + i->fullname(),
                                    sigc::bind<Tag>(
                                          sigc::mem_fun(*this, &ItemList::on_remove_tag), *i)));
                  }

                  itemPopup.popup(e->button.button, e->button.time);

                  //printf("Menu finished\n");

                  //delete addMenu;
                  return true;
            } else {
                  // Clicked outside
                  warning("itemList.get_path_at_pos failed\n");
                  return false;
            }
      }
      return false;
}

ItemList::ItemList(TagcollDocument<std::string>& doc)
      : doc(doc), filterLabel("Filter:")
{
      // Add the TreeView, inside a ScrolledWindow
      scrolledItemList.add(itemList);

      // Only show the scrollbars when they are necessary:
      scrolledItemList.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);

      pack_start(scrolledItemList, Gtk::PACK_EXPAND_WIDGET);
      pack_start(filterHBox, Gtk::PACK_SHRINK);

      filterHBox.pack_start(filterLabel, Gtk::PACK_SHRINK);
      filterHBox.pack_start(filterEdit, Gtk::PACK_EXPAND_WIDGET);

      // Create the Tree model
      itemListModel = Gtk::ListStore::create(itemListModelColumns);
      itemList.set_model(itemListModel);

      // Add the view columns
      itemList.append_column("Name", itemListModelColumns.name);

      {
            TagsetCellRenderer*const renderer = Gtk::manage(new TagsetCellRenderer(*this));
            Gtk::TreeViewColumn*const column  = new Gtk::TreeViewColumn("Tags", *Gtk::manage(renderer));
            itemList.append_column(*Gtk::manage(column));
            column->add_attribute(renderer->property_tagset(), itemListModelColumns.tags);
      }
      //itemList.append_column("Tags", itemListModelColumns.tags);

      itemList.get_column(0)->set_resizable(true);
      itemList.get_column(1)->set_resizable(true);

      Glib::RefPtr<Gtk::TreeSelection> itemListSelection = itemList.get_selection();
      itemListSelection->set_mode(Gtk::SELECTION_MULTIPLE);
      itemListSelection->signal_changed().connect(sigc::mem_fun(*this, &ItemList::do_selection_changed));
      
      itemList.add_events(Gdk::BUTTON_PRESS_MASK);
      itemList.signal_event().connect(sigc::mem_fun(*this, &ItemList::on_event));

      // Setup the lists as a drag source
      std::list<Gtk::TargetEntry> listTargets;
      listTargets.push_back(Gtk::TargetEntry("TAGCOLL"));
      listTargets.push_back(Gtk::TargetEntry("text/plain"));
      itemList.drag_source_set(listTargets, Gdk::ModifierType(GDK_BUTTON1_MASK), Gdk::ACTION_COPY | Gdk::ACTION_MOVE);
      itemList.signal_drag_data_get().connect(sigc::mem_fun(*this, &ItemList::on_itemList_drag_data_get));

      listTargets.clear();
      listTargets.push_back(Gtk::TargetEntry("TAG"));
      listTargets.push_back(Gtk::TargetEntry("TAGCOLL"));
      itemList.drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY | Gdk::ACTION_MOVE);
      itemList.signal_drag_data_received().connect(sigc::mem_fun(*this, &ItemList::on_itemList_drop_drag_data_received) );

      // Fill the list
      updateList();
      
      /*
      selectedItemList.signal_row_activated().connect(sigc::slot(*this, &ItemList::on_selectedList_row_activated));
      availableItemList.signal_row_activated().connect(sigc::slot(*this, &ItemList::on_availableList_row_activated));
      */
      itemList.signal_focus_in_event().connect(sigc::mem_fun(*this, &ItemList::on_focus_in));
      filterEdit.signal_changed().connect(sigc::mem_fun(*this, &ItemList::on_filter_changed));
      doc.signal_changed().connect(sigc::mem_fun(*this, &ItemList::on_document_changed));
}

void ItemList::on_itemList_drag_data_get(
            const Glib::RefPtr<Gdk::DragContext>&, Gtk::SelectionData& selection_data, guint, guint)
{
      std::map<std::string, TagSet > sel = getSelection();
      if (sel.size())
      {
            Glib::ustring str;
            for (std::map< string, TagSet >::const_iterator i = sel.begin();
                        i != sel.end(); i++)
            {
                  str += i->first;

                  for (TagSet::const_iterator j = i->second.begin();
                              j != i->second.end(); j++)
                  if (j == i->second.begin())
                        str += ": " + j->fullname();
                  else
                        str += ", " + j->fullname();

                  str += "\n";
            }
            
            selection_data.set(selection_data.get_target(), 8, (const guchar*)str.data(), str.size());
      }
}

void ItemList::on_itemList_drop_drag_data_received(
            const Glib::RefPtr<Gdk::DragContext>& context, int, int, const Gtk::SelectionData& selection_data, guint, guint time)
{
      if ((selection_data.get_length() >= 0) && (selection_data.get_format() == 8))
      {
            string type = selection_data.get_data_type();
            warning("Type: %.*s\n", PFSTR(type));
            Glib::ustring data((const char*)selection_data.get_data(), selection_data.get_length());
            Tag tag = doc.vocabulary().findTag(data);
            warning("Received: %.*s [%sfound in vocabulary]\n", PFSTR(data), tag ? "" : "not ");

            if (type == "TAG" && tag)
            {
                  std::map< std::string, TagSet > sel = getSelection();

                  TagcollChange<string, Tag> change;
                  
                  for (std::map< string, TagSet >::const_iterator i = sel.begin();
                              i != sel.end(); i++)
                        change.insert(make_pair(i->first, i->second + tag));
                  
                  doc.applyChange(change);
            }
            else if (type == "TAGCOLL")
            {
            }
      }

      context->drag_finish(false, false, time);
}



void ItemList::updateList()
{
      time_t start = time(NULL);

      std::map< std::string, TagSet > sel = getSelection();
      
      if (_tagset.empty())
      {
            OpSet<string> items = doc.collection().getUntaggedItems();

            Gtk::TreeModel::Row row;
            itemListModel->clear();
            for (OpSet<string>::const_iterator i = items.begin(); i != items.end(); i++)
            {
                  if (filter.empty() || i->find(filter) != string::npos)
                  {
                        row = *(itemListModel->append());
                        row[itemListModelColumns.name] = *i;
                        row[itemListModelColumns.tags] = TagSet();
                        if (sel.find(*i) != sel.end())
                              itemList.get_selection()->select(row);
                  }
            }
      } else {
            std::map< string, OpSet<Tag> > items = doc.collection().getCompanionItemsAndTagsets(_tagset);

            Gtk::TreeModel::Row row;
            itemListModel->clear();
            for (std::map< string, OpSet<Tag> >::const_iterator i = items.begin(); i != items.end(); i++)
            {
                  if (filter.empty() || i->first.find(filter) != string::npos)
                  {
                        row = *(itemListModel->append());
                        row[itemListModelColumns.name] = i->first;
                        row[itemListModelColumns.tags] = i->second;
                        if (sel.find(i->first) != sel.end())
                              itemList.get_selection()->select(row);
                  }
            }
      }

      time_t end = time(NULL);
      if (end != start)
            fprintf(stderr, "ItemList::updateList: %d seconds\n", (end-start));
}

map< string, TagSet > ItemList::getAllItems()
{
      std::map< string, TagSet > res;

      for (Gtk::TreeModel::Children::const_iterator i = itemListModel->children().begin();
                  i != itemListModel->children().end(); i++)
      {
            Gtk::TreeModel::Row row = *i;
            Glib::ustring s = row[itemListModelColumns.name];
            TagSet ts = row[itemListModelColumns.tags];
            res.insert(make_pair(s, ts));
            debug("Found: %.*s\n", PFSTR(s));
      }

      return res;
}

map< string, TagSet > ItemList::getSelection()
{
      Glib::RefPtr<Gtk::TreeSelection> itemSelection = itemList.get_selection();
      Gtk::TreeSelection::ListHandle_Path lhp = itemSelection->get_selected_rows();
      std::map< string, TagSet > res;

      for (Gtk::TreeSelection::ListHandle_Path::const_iterator i = lhp.begin(); i != lhp.end(); i++)
      {
            Gtk::TreeModel::Row row = *itemListModel->get_iter(*i);
            Glib::ustring s = row[itemListModelColumns.name];
            TagSet ts = row[itemListModelColumns.tags];
            res.insert(make_pair(s, ts));
      }

      return res;

      /*
      ItemListSelectionDataGatherer data(*this);
      itemSelection->selected_foreach(sigc::slot(data, &ItemListSelectionDataGatherer::selected_row_callback));
      */
      //return data.data();
}

int ItemList::getSelectionSize()
{
      Glib::RefPtr<Gtk::TreeSelection> itemSelection = itemList.get_selection();
      return itemSelection->count_selected_rows();
}

void ItemList::do_selection_changed()
{
      signal_selection_changed().emit();
}

void ItemList::do_focus_in()
{
      signal_focus_in().emit();
}

bool ItemList::on_focus_in(GdkEventFocus*)
{
      //debug("FOCUS IN\n");
      do_focus_in();
      return false;
}

// vim:set ts=4 sw=4:

Generated by  Doxygen 1.6.0   Back to index