You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tellico/src/groupview.cpp

496 lines
15 KiB

/***************************************************************************
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 <kpopupmenu.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kaction.h>
#include <tqstringlist.h>
#include <tqcolor.h>
#include <tqregexp.h>
#include <tqheader.h>
using Tellico::GroupView;
GroupView::GroupView(TQWidget* parent_, const char* name_/*=0*/)
: GUI::ListView(parent_, name_), m_notSortedYet(true), m_coll(0) {
addColumn(TQString()); // header text gets updated later
header()->setStretchEnabled(true, 0);
setResizeMode(TQListView::NoColumn);
setRootIsDecorated(true);
setShowSortIndicator(true);
setTreeStepSize(15);
setFullWidth(true);
connect(this, TQT_SIGNAL(contextMenuRequested(TQListViewItem*, const TQPoint&, int)),
TQT_SLOT(contextMenuRequested(TQListViewItem*, const TQPoint&, int)));
connect(this, TQT_SIGNAL(expanded(TQListViewItem*)),
TQT_SLOT(slotExpanded(TQListViewItem*)));
connect(this, TQT_SIGNAL(collapsed(TQListViewItem*)),
TQT_SLOT(slotCollapsed(TQListViewItem*)));
m_groupOpenPixmap = SmallIcon(TQString::fromLatin1("folder_open"));
m_groupClosedPixmap = SmallIcon(TQString::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(TQString::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<Data::EntryGroup> groups_) {
if(!coll_ || groups_.isEmpty()) {
kdWarning() << "GroupView::slotModifyGroups() - null coll or group pointer!" << endl;
return;
}
for(PtrVector<Data::EntryGroup>::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' TQListView::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<GUI::ListViewItem*>(currentItem());
if(it && it->isEntryItem() && entry_ == static_cast<EntryItem*>(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<Data::EntryGroup>::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(TQListViewItem* item = groupItem->firstChild(); item; item = item->nextSibling()) {
EntryItem* entryItem = static_cast<EntryItem*>(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_) {
TQListViewItem* 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(TQListViewItem* item_, const TQPoint& point_, int) {
if(!item_) {
return;
}
KPopupMenu menu(this);
GUI::ListViewItem* item = static_cast<GUI::ListViewItem*>(item_);
if(item->isEntryGroupItem()) {
menu.insertItem(SmallIconSet(TQString::fromLatin1("2downarrow")),
i18n("Expand All Groups"), this, TQT_SLOT(slotExpandAll()));
menu.insertItem(SmallIconSet(TQString::fromLatin1("2uparrow")),
i18n("Collapse All Groups"), this, TQT_SLOT(slotCollapseAll()));
menu.insertItem(SmallIconSet(TQString::fromLatin1("filter")),
i18n("Filter by Group"), this, TQT_SLOT(slotFilterGroup()));
} else if(item->isEntryItem()) {
Controller::self()->plugEntryActions(&menu);
}
menu.exec(point_);
}
void GroupView::slotCollapsed(TQListViewItem* item_) {
// only change icon for group items
if(static_cast<GUI::ListViewItem*>(item_)->isEntryGroupItem()) {
if(item_->text(0) == i18n(Data::Collection::s_emptyGroupTitle)) {
item_->setPixmap(0, SmallIcon(TQString::fromLatin1("folder_red")));
} else {
item_->setPixmap(0, m_groupClosedPixmap);
}
static_cast<GUI::ListViewItem*>(item_)->clear();
}
}
void GroupView::slotExpanded(TQListViewItem* item_) {
EntryGroupItem* item = static_cast<EntryGroupItem*>(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(TQString::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(TQString::fromLatin1("person-open"));
m_groupClosedPixmap = UserIcon(TQString::fromLatin1("person"));
}
Data::FieldPtr f = coll_->fieldByName(TQString::fromLatin1("title"));
if(f) {
setComparison(0, ListViewComparison::create(f));
}
updateHeader();
populateCollection();
slotCollapseAll();
// myDebug() << "GroupView::addCollection - done" << endl;
}
void GroupView::setGroupField(const TQString& 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(TQString::fromLatin1("person-open"));
m_groupClosedPixmap = UserIcon(TQString::fromLatin1("person"));
} else {
m_groupOpenPixmap = SmallIcon(TQString::fromLatin1("folder_open"));
m_groupClosedPixmap = SmallIcon(TQString::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(TQDictIterator<Data::EntryGroup> 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<EntryGroupItem*>(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<EntryItem*>(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 {
TQString 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<EntryGroupItem*>(firstChild())->group() == 0) {
populateCollection();
}
resetComparisons();
}
void GroupView::removeField(Data::CollPtr, Data::FieldPtr) {
resetComparisons();
}
void GroupView::updateHeader(Data::FieldPtr field_/*=0*/) {
TQString t = field_ ? field_->title() : groupTitle();
if(sortStyle() == ListView::SortByText) {
setColumnText(0, t);
} else {
setColumnText(0, i18n("%1 (Sort by Count)").tqarg(t));
}
}
TQString GroupView::groupTitle() {
TQString 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(TQString::fromLatin1("title"));
if(f) {
setComparison(0, ListViewComparison::create(f));
}
}
#include "groupview.moc"