/*************************************************************************** copyright : (C) 2001-2007 by Robby Stephenson email : robby@periapsis.org ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of version 2 of the GNU General Public License as * * published by the Free Software Foundation; * * * ***************************************************************************/ #include "groupview.h" #include "collection.h" #include "document.h" #include "field.h" #include "filter.h" #include "controller.h" #include "entryitem.h" #include "entrygroupitem.h" #include "entry.h" #include "field.h" #include "filter.h" #include "tellico_kernel.h" #include "listviewcomparison.h" #include "../tellico_debug.h" #include #include #include #include #include #include #include #include using Tellico::GroupView; GroupView::GroupView(QWidget* parent_, const char* name_/*=0*/) : GUI::ListView(parent_, name_), m_notSortedYet(true), m_coll(0) { addColumn(QString::null); // header text gets updated later header()->setStretchEnabled(true, 0); setResizeMode(QListView::NoColumn); setRootIsDecorated(true); setShowSortIndicator(true); setTreeStepSize(15); setFullWidth(true); connect(this, SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)), SLOT(contextMenuRequested(QListViewItem*, const QPoint&, int))); connect(this, SIGNAL(expanded(QListViewItem*)), SLOT(slotExpanded(QListViewItem*))); connect(this, SIGNAL(collapsed(QListViewItem*)), SLOT(slotCollapsed(QListViewItem*))); m_groupOpenPixmap = SmallIcon(QString::fromLatin1("folder_open")); m_groupClosedPixmap = SmallIcon(QString::fromLatin1("folder")); } Tellico::EntryGroupItem* GroupView::addGroup(Data::EntryGroup* group_) { if(group_->isEmpty()) { return 0; } int type = -1; if(m_coll && m_coll->hasField(group_->fieldName())) { type = m_coll->fieldByName(group_->fieldName())->type(); } EntryGroupItem* item = new EntryGroupItem(this, group_, type); if(group_->groupName() == i18n(Data::Collection::s_emptyGroupTitle)) { item->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red"))); item->setSortWeight(10); } else { item->setPixmap(0, m_groupClosedPixmap); } m_groupDict.insert(group_->groupName(), item); item->setExpandable(!group_->isEmpty()); return item; } void GroupView::slotReset() { clear(); m_groupDict.clear(); } void GroupView::removeCollection(Data::CollPtr coll_) { if(!coll_) { kdWarning() << "GroupView::removeCollection() - null coll pointer!" << endl; return; } // myDebug() << "GroupView::removeCollection() - " << coll_->title() << endl; blockSignals(true); slotReset(); blockSignals(false); } void GroupView::slotModifyGroups(Data::CollPtr coll_, PtrVector groups_) { if(!coll_ || groups_.isEmpty()) { kdWarning() << "GroupView::slotModifyGroups() - null coll or group pointer!" << endl; return; } for(PtrVector::Iterator group = groups_.begin(); group != groups_.end(); ++group) { // if the entries aren't grouped by field of the modified group, // we don't care, so return if(m_groupBy != group->fieldName()) { continue; } // myDebug() << "GroupView::slotModifyGroups() - " << group->fieldName() << "/" << group->groupName() << endl; EntryGroupItem* par = m_groupDict.find(group->groupName()); if(par) { if(group->isEmpty()) { m_groupDict.remove(par->text(0)); delete par; continue; } // the group might get deleted and recreated out from under us, // so do a sanity check par->setGroup(group.ptr()); } else { if(group->isEmpty()) { myDebug() << "GroupView::slotModifyGroups() - trying to add empty group" << endl; continue; } par = addGroup(group.ptr()); } setUpdatesEnabled(false); bool open = par->isOpen(); par->setOpen(false); // closing and opening the item will clear the items par->setOpen(open); setUpdatesEnabled(true); } // don't want any selected clearSelection(); sort(); // in case the count changed, or group name } // don't 'shadow' QListView::setSelected void GroupView::setEntrySelected(Data::EntryPtr entry_) { // myDebug() << "GroupView::slotSetSelected()" << endl; // if entry_ is null pointer, set no selected if(!entry_) { // don't move this one outside the block since it calls setCurrentItem(0) clearSelection(); return; } // if the selected entry is the same as the current one, just return GUI::ListViewItem* it = static_cast(currentItem()); if(it && it->isEntryItem() && entry_ == static_cast(it)->entry()) { return; } // have to find a group whose field is the same as currently shown if(m_groupBy.isEmpty()) { myDebug() << "GroupView::slotSetSelected() - no group field" << endl; return; } const Data::EntryGroup* group = 0; for(PtrVector::ConstIterator it = entry_->groups().begin(); it != entry_->groups().end(); ++it) { if(it->fieldName() == m_groupBy) { group = it.ptr(); break; } } if(!group) { myDebug() << "GroupView::slotSetSelected() - entry is not in any current groups!" << endl; return; } EntryGroupItem* groupItem = m_groupDict.find(group->groupName()); if(!groupItem) { return; } clearSelection(); for(QListViewItem* item = groupItem->firstChild(); item; item = item->nextSibling()) { EntryItem* entryItem = static_cast(item); if(entryItem->entry() == entry_) { blockSignals(true); setSelected(item, true); setCurrentItem(item); blockSignals(false); ensureItemVisible(item); return; } } } void GroupView::slotExpandAll(int depth_/*=-1*/) { if(childCount() == 0) { return; } setSiblingsOpen(depth_, true); } void GroupView::slotCollapseAll(int depth_/*=-1*/) { if(childCount() == 0) { return; } setSiblingsOpen(depth_, false); } void GroupView::setSiblingsOpen(int depth_, bool open_) { QListViewItem* item = 0; if(depth_ == -1) { item = currentItem(); if(!item) { return; } depth_ = item->depth(); } switch(depth_) { case 0: item = firstChild(); break; case 1: item = firstChild()->firstChild(); break; default: return; } for( ; item; item = item->nextSibling()) { item->setOpen(open_); } } void GroupView::contextMenuRequested(QListViewItem* item_, const QPoint& point_, int) { if(!item_) { return; } KPopupMenu menu(this); GUI::ListViewItem* item = static_cast(item_); if(item->isEntryGroupItem()) { menu.insertItem(SmallIconSet(QString::fromLatin1("2downarrow")), i18n("Expand All Groups"), this, SLOT(slotExpandAll())); menu.insertItem(SmallIconSet(QString::fromLatin1("2uparrow")), i18n("Collapse All Groups"), this, SLOT(slotCollapseAll())); menu.insertItem(SmallIconSet(QString::fromLatin1("filter")), i18n("Filter by Group"), this, SLOT(slotFilterGroup())); } else if(item->isEntryItem()) { Controller::self()->plugEntryActions(&menu); } menu.exec(point_); } void GroupView::slotCollapsed(QListViewItem* item_) { // only change icon for group items if(static_cast(item_)->isEntryGroupItem()) { if(item_->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) { item_->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red"))); } else { item_->setPixmap(0, m_groupClosedPixmap); } static_cast(item_)->clear(); } } void GroupView::slotExpanded(QListViewItem* item_) { EntryGroupItem* item = static_cast(item_); // only change icon for group items if(!item->isEntryGroupItem()) { return; } setUpdatesEnabled(false); if(item->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) { item->setPixmap(0, SmallIcon(QString::fromLatin1("folder_red_open"))); } else { item->setPixmap(0, m_groupOpenPixmap); } Data::EntryGroup* group = item->group(); if(!group) { myDebug() << "GroupView::slotExpanded() - no entry group! - " << item->text(0) << endl; } else { for(Data::EntryVecIt entryIt = group->begin(); entryIt != group->end(); ++entryIt) { new EntryItem(item, entryIt); } } setUpdatesEnabled(true); triggerUpdate(); } void GroupView::addCollection(Data::CollPtr coll_) { // myDebug() << "GroupView::addCollection()" << endl; if(!coll_) { kdWarning() << "GroupView::addCollection() - null coll pointer!" << endl; return; } m_coll = coll_; // if the collection doesn't have the grouped field, and it's not the pseudo-group, // change it to default if(m_groupBy.isEmpty() || (!coll_->hasField(m_groupBy) && m_groupBy != Data::Collection::s_peopleGroupName)) { m_groupBy = coll_->defaultGroupField(); } // when the coll gets set for the first time, the pixmaps need to be updated if((m_coll->hasField(m_groupBy) && m_coll->fieldByName(m_groupBy)->formatFlag() == Data::Field::FormatName) || m_groupBy == Data::Collection::s_peopleGroupName) { m_groupOpenPixmap = UserIcon(QString::fromLatin1("person-open")); m_groupClosedPixmap = UserIcon(QString::fromLatin1("person")); } Data::FieldPtr f = coll_->fieldByName(QString::fromLatin1("title")); if(f) { setComparison(0, ListViewComparison::create(f)); } updateHeader(); populateCollection(); slotCollapseAll(); // myDebug() << "GroupView::addCollection - done" << endl; } void GroupView::setGroupField(const QString& groupField_) { // myDebug() << "GroupView::setGroupField - " << groupField_ << endl; if(groupField_.isEmpty() || groupField_ == m_groupBy) { return; } m_groupBy = groupField_; if(!m_coll) { return; // can't do anything yet, but still need to set the variable } if((m_coll->hasField(groupField_) && m_coll->fieldByName(groupField_)->formatFlag() == Data::Field::FormatName) || groupField_ == Data::Collection::s_peopleGroupName) { m_groupOpenPixmap = UserIcon(QString::fromLatin1("person-open")); m_groupClosedPixmap = UserIcon(QString::fromLatin1("person")); } else { m_groupOpenPixmap = SmallIcon(QString::fromLatin1("folder_open")); m_groupClosedPixmap = SmallIcon(QString::fromLatin1("folder")); } updateHeader(); populateCollection(); } void GroupView::populateCollection() { if(!m_coll) { return; } // myDebug() << "GroupView::populateCollection() - " << m_groupBy << endl; if(m_groupBy.isEmpty()) { m_groupBy = m_coll->defaultGroupField(); } setUpdatesEnabled(false); clear(); // delete all groups m_groupDict.clear(); // if there's no group field, just return if(m_groupBy.isEmpty()) { setUpdatesEnabled(true); return; } Data::EntryGroupDict* dict = m_coll->entryGroupDictByName(m_groupBy); if(!dict) { // could happen if m_groupBy is non empty, but there are no entries with a value return; } // iterate over all the groups in the dict // e.g. if the dict is "author", loop over all the author groups for(QDictIterator it(*dict); it.current(); ++it) { addGroup(it.current()); } setUpdatesEnabled(true); triggerUpdate(); } void GroupView::slotFilterGroup() { const GUI::ListViewItemList& items = selectedItems(); GUI::ListViewItem* item = items.getFirst(); // only works for entry groups if(!item || !item->isEntryGroupItem()) { return; } FilterPtr filter = new Filter(Filter::MatchAny); for(GUI::ListViewItemListIt it(items); it.current(); ++it) { if(static_cast(it.current())->count() == 0) { //ignore empty items continue; } // need to check for people group if(m_groupBy == Data::Collection::s_peopleGroupName) { Data::EntryPtr entry = static_cast(it.current()->firstChild())->entry(); Data::FieldVec fields = entry->collection()->peopleFields(); for(Data::FieldVec::Iterator fIt = fields.begin(); fIt != fields.end(); ++fIt) { filter->append(new FilterRule(fIt->name(), it.current()->text(0), FilterRule::FuncContains)); } } else { QString s = it.current()->text(0); if(s != i18n(Data::Collection::s_emptyGroupTitle)) { filter->append(new FilterRule(m_groupBy, it.current()->text(0), FilterRule::FuncContains)); } } } if(!filter->isEmpty()) { emit signalUpdateFilter(filter); } } // this gets called when header() is clicked, so cycle through void GroupView::setSorting(int col_, bool asc_) { if(asc_ && !m_notSortedYet) { // cycle through after ascending if(sortStyle() == ListView::SortByText) { setSortStyle(ListView::SortByCount); } else { setSortStyle(ListView::SortByText); } } updateHeader(); m_notSortedYet = false; ListView::setSorting(col_, asc_); } void GroupView::addField(Data::CollPtr, Data::FieldPtr) { resetComparisons(); } void GroupView::modifyField(Data::CollPtr, Data::FieldPtr, Data::FieldPtr newField_) { if(newField_->name() == m_groupBy) { updateHeader(newField_); } // if the grouping changed at all, our groups got deleted out from under us // so check first pointer, too. The groups could be deleted if the format type // changes, too, so not enough just to check group flag if(childCount() > 0 && static_cast(firstChild())->group() == 0) { populateCollection(); } resetComparisons(); } void GroupView::removeField(Data::CollPtr, Data::FieldPtr) { resetComparisons(); } void GroupView::updateHeader(Data::FieldPtr field_/*=0*/) { QString t = field_ ? field_->title() : groupTitle(); if(sortStyle() == ListView::SortByText) { setColumnText(0, t); } else { setColumnText(0, i18n("%1 (Sort by Count)").arg(t)); } } QString GroupView::groupTitle() { QString title; if(!m_coll || m_groupBy.isEmpty()) { title = i18n("Group Name Header", "Group"); } else { Data::FieldPtr f = m_coll->fieldByName(m_groupBy); if(f) { title = f->title(); } else if(m_groupBy == Data::Collection::s_peopleGroupName) { title = i18n("People"); } } return title; } void GroupView::resetComparisons() { if(!m_coll) { return; } Data::FieldPtr f = m_coll->fieldByName(QString::fromLatin1("title")); if(f) { setComparison(0, ListViewComparison::create(f)); } } #include "groupview.moc"