diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
commit | e38d2351b83fa65c66ccde443777647ef5cb6cff (patch) | |
tree | 1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/collection.cpp | |
download | tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.tar.gz tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.zip |
Added KDE3 version of Tellico
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/tellico@1097620 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/collection.cpp')
-rw-r--r-- | src/collection.cpp | 915 |
1 files changed, 915 insertions, 0 deletions
diff --git a/src/collection.cpp b/src/collection.cpp new file mode 100644 index 0000000..c6f77ca --- /dev/null +++ b/src/collection.cpp @@ -0,0 +1,915 @@ +/*************************************************************************** + copyright : (C) 2001-2006 by Robby Stephenson + email : [email protected] + ***************************************************************************/ + +/*************************************************************************** + * * + * 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 "collection.h" +#include "field.h" +#include "entry.h" +#include "tellico_debug.h" +#include "latin1literal.h" +#include "tellico_utils.h" +#include "controller.h" +#include "collectionfactory.h" +#include "stringset.h" +#include "tellico_kernel.h" + +#include <klocale.h> + +#include <qregexp.h> +#include <qvaluestack.h> + +using Tellico::Data::Collection; + +const char* Collection::s_emptyGroupTitle = I18N_NOOP("(Empty)"); +const QString Collection::s_peopleGroupName = QString::fromLatin1("_people"); + +Collection::Collection(const QString& title_) + : QObject(), KShared(), m_nextEntryId(0), m_title(title_), m_entryIdDict(997) + , m_trackGroups(false) { + m_entryGroupDicts.setAutoDelete(true); + + m_id = getID(); +} + +Collection::~Collection() { +} + +QString Collection::typeName() const { + return CollectionFactory::typeName(type()); +} + +bool Collection::addFields(FieldVec list_) { + bool success = true; + for(FieldVec::Iterator it = list_.begin(); it != list_.end(); ++it) { + success &= addField(it); + } + return success; +} + +bool Collection::addField(FieldPtr field_) { + if(!field_) { + return false; + } + + // this essentially checks for duplicates + if(hasField(field_->name())) { + myDebug() << "Collection::addField() - replacing " << field_->name() << endl; + removeField(fieldByName(field_->name()), true); + } + + // it's not sufficient to merely check the new field + if(dependentFieldHasRecursion(field_)) { + field_->setDescription(QString()); + } + + m_fields.append(field_); + if(field_->formatFlag() == Field::FormatName) { + m_peopleFields.append(field_); // list of people attributes + if(m_peopleFields.count() > 1) { + // the second time that a person field is added, add a "pseudo-group" for people + if(m_entryGroupDicts.find(s_peopleGroupName) == 0) { + EntryGroupDict* d = new EntryGroupDict(); + d->setAutoDelete(true); + m_entryGroupDicts.insert(s_peopleGroupName, d); + m_entryGroups.prepend(s_peopleGroupName); + } + } + } + m_fieldNameDict.insert(field_->name(), field_); + m_fieldTitleDict.insert(field_->title(), field_); + m_fieldNames << field_->name(); + m_fieldTitles << field_->title(); + if(field_->type() == Field::Image) { + m_imageFields.append(field_); + } + + if(!field_->category().isEmpty() && m_fieldCategories.findIndex(field_->category()) == -1) { + m_fieldCategories << field_->category(); + } + + if(field_->flags() & Field::AllowGrouped) { + // m_entryGroupsDicts autoDeletes each QDict when the Collection d'tor is called + EntryGroupDict* dict = new EntryGroupDict(); + dict->setAutoDelete(true); + m_entryGroupDicts.insert(field_->name(), dict); + // cache the possible groups of entries + m_entryGroups << field_->name(); + } + + if(m_defaultGroupField.isEmpty() && field_->flags() & Field::AllowGrouped) { + m_defaultGroupField = field_->name(); + } + + // refresh all dependent fields, in case one references this new one + for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) { + if(it->type() == Field::Dependent) { + emit signalRefreshField(it); + } + } + + return true; +} + +bool Collection::mergeField(FieldPtr newField_) { + if(!newField_) { + return false; + } + + FieldPtr currField = fieldByName(newField_->name()); + if(!currField) { + // does not exist in current collection, add it + Data::FieldPtr f = new Field(*newField_); + bool success = addField(f); + Controller::self()->addedField(this, f); + return success; + } + + if(newField_->type() == Field::Table2) { + newField_->setType(Data::Field::Table); + newField_->setProperty(QString::fromLatin1("columns"), QChar('2')); + } + + // the original field type is kept + if(currField->type() != newField_->type()) { + myDebug() << "Collection::mergeField() - skipping, field type mismatch for " << currField->title() << endl; + return false; + } + + // if field is a Choice, then make sure all values are there + if(currField->type() == Field::Choice && currField->allowed() != newField_->allowed()) { + QStringList allowed = currField->allowed(); + const QStringList& newAllowed = newField_->allowed(); + for(QStringList::ConstIterator it = newAllowed.begin(); it != newAllowed.end(); ++it) { + if(allowed.findIndex(*it) == -1) { + allowed.append(*it); + } + } + currField->setAllowed(allowed); + } + + // don't change original format flags + // don't change original category + // add new description if current is empty + if(currField->description().isEmpty()) { + currField->setDescription(newField_->description()); + if(dependentFieldHasRecursion(currField)) { + currField->setDescription(QString()); + } + } + + // if new field has additional extended properties, add those + for(StringMap::ConstIterator it = newField_->propertyList().begin(); it != newField_->propertyList().end(); ++it) { + const QString propName = it.key(); + const QString currValue = currField->property(propName); + if(currValue.isEmpty()) { + currField->setProperty(propName, it.data()); + } else if (it.data() != currValue) { + if(currField->type() == Field::URL && propName == Latin1Literal("relative")) { + kdWarning() << "Collection::mergeField() - relative URL property does not match for " << currField->name() << endl; + } else if((currField->type() == Field::Table && propName == Latin1Literal("columns")) + || (currField->type() == Field::Rating && propName == Latin1Literal("maximum"))) { + bool ok; + uint currNum = Tellico::toUInt(currValue, &ok); + uint newNum = Tellico::toUInt(it.data(), &ok); + if(newNum > currNum) { // bigger values + currField->setProperty(propName, QString::number(newNum)); + } + } else if(currField->type() == Field::Rating && propName == Latin1Literal("minimum")) { + bool ok; + uint currNum = Tellico::toUInt(currValue, &ok); + uint newNum = Tellico::toUInt(it.data(), &ok); + if(newNum < currNum) { // smaller values + currField->setProperty(propName, QString::number(newNum)); + } + } + } + } + + // combine flags + currField->setFlags(currField->flags() | newField_->flags()); + return true; +} + +// be really careful with these field pointers, try not to call too many other functions +// which may depend on the field list +bool Collection::modifyField(FieldPtr newField_) { + if(!newField_) { + return false; + } +// myDebug() << "Collection::modifyField() - " << newField_->name() << endl; + +// the field name never changes + const QString fieldName = newField_->name(); + FieldPtr oldField = fieldByName(fieldName); + if(!oldField) { + myDebug() << "Collection::modifyField() - no field named " << fieldName << endl; + return false; + } + + // update name dict + m_fieldNameDict.replace(fieldName, newField_); + + // update titles + const QString oldTitle = oldField->title(); + const QString newTitle = newField_->title(); + if(oldTitle == newTitle) { + m_fieldTitleDict.replace(newTitle, newField_); + } else { + m_fieldTitleDict.remove(oldTitle); + m_fieldTitles.remove(oldTitle); + m_fieldTitleDict.insert(newTitle, newField_); + m_fieldTitles.append(newTitle); + } + + // now replace the field pointer in the list + FieldVec::Iterator it = m_fields.find(oldField); + if(it != m_fields.end()) { + m_fields.insert(it, newField_); + m_fields.remove(oldField); + } else { + myDebug() << "Collection::modifyField() - no index found!" << endl; + return false; + } + + // update category list. + if(oldField->category() != newField_->category()) { + m_fieldCategories.clear(); + for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) { + // add category if it's not in the list yet + if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) { + m_fieldCategories += it->category(); + } + } + } + + if(dependentFieldHasRecursion(newField_)) { + newField_->setDescription(QString()); + } + + // keep track of if the entry groups will need to be reset + bool resetGroups = false; + + // if format is different, go ahead and invalidate all formatted entry values + if(oldField->formatFlag() != newField_->formatFlag()) { + // invalidate cached format strings of all entry attributes of this name + for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) { + it->invalidateFormattedFieldValue(fieldName); + } + resetGroups = true; + } + + // check to see if the people "pseudo-group" needs to be updated + // only if only one of the two is a name + bool wasPeople = oldField->formatFlag() == Field::FormatName; + bool isPeople = newField_->formatFlag() == Field::FormatName; + if(wasPeople) { + m_peopleFields.remove(oldField); + if(!isPeople) { + resetGroups = true; + } + } + if(isPeople) { + // if there's more than one people field and no people dict exists yet, add it + if(m_peopleFields.count() > 1 && m_entryGroupDicts.find(s_peopleGroupName) == 0) { + EntryGroupDict* d = new EntryGroupDict(); + d->setAutoDelete(true); + m_entryGroupDicts.insert(s_peopleGroupName, d); + // put it at the top of the list + m_entryGroups.prepend(s_peopleGroupName); + } + m_peopleFields.append(newField_); + if(!wasPeople) { + resetGroups = true; + } + } + + bool wasGrouped = oldField->flags() & Field::AllowGrouped; + bool isGrouped = newField_->flags() & Field::AllowGrouped; + if(wasGrouped) { + if(!isGrouped) { + // in order to keep list in the same order, don't remove unless new field is not groupable + m_entryGroups.remove(fieldName); + m_entryGroupDicts.remove(fieldName); + myDebug() << "Collection::modifyField() - no longer grouped: " << fieldName << endl; + resetGroups = true; + } else { + // don't do this, it wipes out the old groups! +// m_entryGroupDicts.replace(fieldName, new EntryGroupDict()); + } + } else if(isGrouped) { + EntryGroupDict* d = new EntryGroupDict(); + d->setAutoDelete(true); + m_entryGroupDicts.insert(fieldName, d); + if(!wasGrouped) { + // cache the possible groups of entries + m_entryGroups << fieldName; + } + resetGroups = true; + } + + if(oldField->type() == Field::Image) { + m_imageFields.remove(oldField); + } + if(newField_->type() == Field::Image) { + m_imageFields.append(newField_); + } + + if(resetGroups) { + myLog() << "Collection::modifyField() - invalidating groups" << endl; + invalidateGroups(); + } + + // now to update all entries if the field is a dependent and the description changed + if(newField_->type() == Field::Dependent && oldField->description() != newField_->description()) { + emit signalRefreshField(newField_); + } + + return true; +} + +bool Collection::removeField(const QString& name_, bool force_) { + return removeField(fieldByName(name_), force_); +} + +// force allows me to force the deleting of the title field if I need to +bool Collection::removeField(FieldPtr field_, bool force_/*=false*/) { + if(!field_ || !m_fields.contains(field_)) { + if(field_) { + myDebug() << "Collection::removeField - false: " << field_->name() << endl; + } + return false; + } +// myDebug() << "Collection::removeField() - name = " << field_->name() << endl; + + // can't delete the title field + if((field_->flags() & Field::NoDelete) && !force_) { + return false; + } + + bool success = true; + if(field_->formatFlag() == Field::FormatName) { + success &= m_peopleFields.remove(field_); + } + + if(field_->type() == Field::Image) { + success &= m_imageFields.remove(field_); + } + success &= m_fieldNameDict.remove(field_->name()); + success &= m_fieldTitleDict.remove(field_->title()); + success &= m_fieldNames.remove(field_->name()); + success &= m_fieldTitles.remove(field_->title()); + + if(fieldsByCategory(field_->category()).count() == 1) { + success &= m_fieldCategories.remove(field_->category()); + } + + for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) { + // setting the fields to an empty string removes the value from the entry's list + it->setField(field_, QString::null); + } + + if(field_->flags() & Field::AllowGrouped) { + success &= m_entryGroupDicts.remove(field_->name()); + success &= m_entryGroups.remove(field_->name()); + if(field_->name() == m_defaultGroupField) { + setDefaultGroupField(m_entryGroups[0]); + } + } + + success &= m_fields.remove(field_); + + // refresh all dependent fields, rather lazy, but there's + // likely to be weird effects when checking dependent fields + // while removing one, so refresh all of them + for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) { + if(it->type() == Field::Dependent) { + emit signalRefreshField(it); + } + } + + return success; +} + +void Collection::reorderFields(const FieldVec& list_) { +// assume the lists have the same pointers! + m_fields = list_; + + // also reset category list, since the order may have changed + m_fieldCategories.clear(); + for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) { + if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) { + m_fieldCategories << it->category(); + } + } +} + +void Collection::addEntries(EntryVec entries_) { + if(entries_.isEmpty()) { + return; + } + + for(EntryVec::Iterator entry = entries_.begin(); entry != entries_.end(); ++entry) { + bool foster = false; + if(this != entry->collection()) { + entry->setCollection(this); + foster = true; + } + + m_entries.append(entry); +// myDebug() << "Collection::addEntries() - added entry (" << entry->title() << ")" << endl; + + if(entry->id() >= m_nextEntryId) { + m_nextEntryId = entry->id() + 1; + } else if(entry->id() == -1) { + entry->setId(m_nextEntryId); + ++m_nextEntryId; + } else if(m_entryIdDict.find(entry->id())) { + if(!foster) { + myDebug() << "Collection::addEntries() - the collection already has an entry with id = " << entry->id() << endl; + } + entry->setId(m_nextEntryId); + ++m_nextEntryId; + } + m_entryIdDict.insert(entry->id(), entry.data()); + } + if(m_trackGroups) { + populateCurrentDicts(entries_); + } +} + +void Collection::removeEntriesFromDicts(EntryVec entries_) { + PtrVector<EntryGroup> modifiedGroups; + for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) { + // need a copy of the vector since it gets changed + PtrVector<EntryGroup> groups = entry->groups(); + for(PtrVector<EntryGroup>::Iterator group = groups.begin(); group != groups.end(); ++group) { + if(entry->removeFromGroup(group.ptr()) && !modifiedGroups.contains(group.ptr())) { + modifiedGroups.push_back(group.ptr()); + } + if(group->isEmpty() && !m_groupsToDelete.contains(group.ptr())) { + m_groupsToDelete.push_back(group.ptr()); + } + } + } + emit signalGroupsModified(this, modifiedGroups); +} + +// this function gets called whenever an entry is modified. Its purpose is to keep the +// groupDicts current. It first removes the entry from every group to which it belongs, +// then it repopulates the dicts with the entry's fields +void Collection::updateDicts(EntryVec entries_) { + if(entries_.isEmpty()) { + return; + } + + removeEntriesFromDicts(entries_); + populateCurrentDicts(entries_); + cleanGroups(); +} + +bool Collection::removeEntries(EntryVec vec_) { + if(vec_.isEmpty()) { + return false; + } + +// myDebug() << "Collection::deleteEntry() - deleted entry - " << entry_->title() << endl; + removeEntriesFromDicts(vec_); + bool success = true; + for(EntryVecIt entry = vec_.begin(); entry != vec_.end(); ++entry) { + m_entryIdDict.remove(entry->id()); + success &= m_entries.remove(entry); + } + cleanGroups(); + return success; +} + +Tellico::Data::FieldVec Collection::fieldsByCategory(const QString& cat_) { +#ifndef NDEBUG + if(m_fieldCategories.findIndex(cat_) == -1) { + myDebug() << "Collection::fieldsByCategory() - '" << cat_ << "' is not in category list" << endl; + } +#endif + if(cat_.isEmpty()) { + myDebug() << "Collection::fieldsByCategory() - empty category!" << endl; + return FieldVec(); + } + + FieldVec list; + for(FieldVec::Iterator it = m_fields.begin(); it != m_fields.end(); ++it) { + if(it->category() == cat_) { + list.append(it); + } + } + return list; +} + +const QString& Collection::fieldNameByTitle(const QString& title_) const { + if(title_.isEmpty()) { + return QString::null; + } + FieldPtr f = fieldByTitle(title_); + if(!f) { // might happen in MainWindow::saveCollectionOptions + return QString::null; + } + return f->name(); +} + +const QString& Collection::fieldTitleByName(const QString& name_) const { + if(name_.isEmpty()) { + return QString::null; + } + FieldPtr f = fieldByName(name_); + if(!f) { + kdWarning() << "Collection::fieldTitleByName() - no field named " << name_ << endl; + return QString::null; + } + return f->title(); +} + +QStringList Collection::valuesByFieldName(const QString& name_) const { + if(name_.isEmpty()) { + return QStringList(); + } + bool multiple = (fieldByName(name_)->flags() & Field::AllowMultiple); + + StringSet values; + for(EntryVec::ConstIterator it = m_entries.begin(); it != m_entries.end(); ++it) { + if(multiple) { + values.add(it->fields(name_, false)); + } else { + values.add(it->field(name_)); + } + } // end entry loop + + return values.toList(); +} + +Tellico::Data::FieldPtr Collection::fieldByName(const QString& name_) const { + return m_fieldNameDict.isEmpty() ? 0 : name_.isEmpty() ? 0 : m_fieldNameDict.find(name_); +} + +Tellico::Data::FieldPtr Collection::fieldByTitle(const QString& title_) const { + return m_fieldTitleDict.isEmpty() ? 0 : title_.isEmpty() ? 0 : m_fieldTitleDict.find(title_); +} + +bool Collection::hasField(const QString& name_) const { + return fieldByName(name_) != 0; +} + +bool Collection::isAllowed(const QString& key_, const QString& value_) const { + // empty string is always allowed + if(value_.isEmpty()) { + return true; + } + + // find the field with a name of 'key_' + FieldPtr field = fieldByName(key_); + + // if the type is not multiple choice or if value_ is allowed, return true + if(field && (field->type() != Field::Choice || field->allowed().findIndex(value_) > -1)) { + return true; + } + + return false; +} + +Tellico::Data::EntryGroupDict* Collection::entryGroupDictByName(const QString& name_) { +// myDebug() << "Collection::entryGroupDictByName() - " << name_ << endl; + if(name_.isEmpty()) { + return 0; + } + EntryGroupDict* dict = m_entryGroupDicts.isEmpty() ? 0 : m_entryGroupDicts.find(name_); + if(dict && dict->isEmpty()) { + GUI::CursorSaver cs; + const bool b = signalsBlocked(); + // block signals so all the group created/modified signals don't fire + blockSignals(true); + populateDict(dict, name_, m_entries); + blockSignals(b); + } + return dict; +} + +void Collection::populateDict(EntryGroupDict* dict_, const QString& fieldName_, EntryVec entries_) { +// myDebug() << "Collection::populateDict() - " << fieldName_ << endl; + bool isBool = hasField(fieldName_) && fieldByName(fieldName_)->type() == Field::Bool; + + PtrVector<EntryGroup> modifiedGroups; + for(EntryVecIt entry = entries_.begin(); entry != entries_.end(); ++entry) { + QStringList groups = entryGroupNamesByField(entry, fieldName_); + for(QStringList::ConstIterator groupIt = groups.begin(); groupIt != groups.end(); ++groupIt) { + // find the group for this group name + // bool fields used the field title + QString groupTitle = *groupIt; + if(isBool && groupTitle != i18n(s_emptyGroupTitle)) { + groupTitle = fieldTitleByName(fieldName_); + } + EntryGroup* group = dict_->find(groupTitle); + // if the group doesn't exist, create it + if(!group) { + group = new EntryGroup(groupTitle, fieldName_); + dict_->insert(groupTitle, group); + } else if(group->isEmpty()) { + // if it's empty, then it was added to the vector of groups to delete + // remove it from that vector now that we're adding to it + m_groupsToDelete.remove(group); + } + if(entry->addToGroup(group)) { + modifiedGroups.push_back(group); + } + } // end group loop + } // end entry loop + emit signalGroupsModified(this, modifiedGroups); +} + +void Collection::populateCurrentDicts(EntryVec entries_) { + if(m_entryGroupDicts.isEmpty()) { + return; + } + + // special case when adding an entry to a new empty collection + // there are no existing non-empty groups + bool allEmpty = true; + + // iterate over all the possible groupDicts + // for each dict, get the value of that field for the entry + // if multiple values are allowed, split the value and then insert the + // entry pointer into the dict for each value + QDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts); + for( ; dictIt.current(); ++dictIt) { + // only populate if it's not empty, since they are + // populated on demand + if(!dictIt.current()->isEmpty()) { + populateDict(dictIt.current(), dictIt.currentKey(), entries_); + allEmpty = false; + } + } + + if(allEmpty) { + // need to populate the current group dict + const QString group = Controller::self()->groupBy(); + EntryGroupDict* dict = m_entryGroupDicts[group]; + if(dict) { + populateDict(dict, group, entries_); + } + } +} + +// return a string list for all the groups that the entry belongs to +// for a given field. Normally, this would just be splitting the entry's value +// for the field, but if the field name is the people pseudo-group, then it gets +// a bit more complicated +QStringList Collection::entryGroupNamesByField(EntryPtr entry_, const QString& fieldName_) { + if(fieldName_ != s_peopleGroupName) { + return entry_->groupNamesByFieldName(fieldName_); + } + + StringSet values; + for(FieldVec::Iterator it = m_peopleFields.begin(); it != m_peopleFields.end(); ++it) { + values.add(entry_->groupNamesByFieldName(it->name())); + } + values.remove(i18n(s_emptyGroupTitle)); + return values.toList(); +} + +void Collection::invalidateGroups() { + QDictIterator<EntryGroupDict> dictIt(m_entryGroupDicts); + for( ; dictIt.current(); ++dictIt) { + dictIt.current()->clear(); + } + + // populateDicts() will make signals that the group view is connected to, block those + blockSignals(true); + for(EntryVecIt it = m_entries.begin(); it != m_entries.end(); ++it) { + it->invalidateFormattedFieldValue(); + it->clearGroups(); + } + blockSignals(false); +} + +Tellico::Data::EntryPtr Collection::entryById(long id_) { + return m_entryIdDict[id_]; +} + +void Collection::addBorrower(Data::BorrowerPtr borrower_) { + if(!borrower_) { + return; + } + m_borrowers.append(borrower_); +} + +void Collection::addFilter(FilterPtr filter_) { + if(!filter_) { + return; + } + + m_filters.append(filter_); +} + +bool Collection::removeFilter(FilterPtr filter_) { + if(!filter_) { + return false; + } + + // TODO: test for success + m_filters.remove(filter_); + return true; +} + +void Collection::clear() { + // since the collection holds a pointer to each entry and each entry + // hold a pointer to the collection, and they're both sharedptrs, + // neither will ever get deleted, unless the collection removes + // all held pointers, specifically to entries + m_fields.clear(); + m_peopleFields.clear(); + m_imageFields.clear(); + m_fieldNameDict.clear(); + m_fieldTitleDict.clear(); + + m_entries.clear(); + m_entryIdDict.clear(); + m_entryGroupDicts.clear(); + m_groupsToDelete.clear(); + m_filters.clear(); + m_borrowers.clear(); +} + +void Collection::cleanGroups() { + for(PtrVector<EntryGroup>::Iterator it = m_groupsToDelete.begin(); it != m_groupsToDelete.end(); ++it) { + EntryGroupDict* dict = entryGroupDictByName(it->fieldName()); + if(!dict) { + continue; + } + dict->remove(it->groupName()); + } + m_groupsToDelete.clear(); +} + +bool Collection::dependentFieldHasRecursion(FieldPtr field_) { + if(!field_ || field_->type() != Field::Dependent) { + return false; + } + + StringSet fieldNamesFound; + fieldNamesFound.add(field_->name()); + QValueStack<FieldPtr> fieldsToCheck; + fieldsToCheck.push(field_); + while(!fieldsToCheck.isEmpty()) { + FieldPtr f = fieldsToCheck.pop(); + const QStringList depFields = f->dependsOn(); + for(QStringList::ConstIterator it = depFields.begin(); it != depFields.end(); ++it) { + if(fieldNamesFound.has(*it)) { + // we have recursion + return true; + } + fieldNamesFound.add(*it); + FieldPtr f = fieldByName(*it); + if(!f) { + f = fieldByTitle(*it); + } + if(f) { + fieldsToCheck.push(f); + } + } + } + return false; +} + +int Collection::sameEntry(Data::EntryPtr entry1_, Data::EntryPtr entry2_) const { + if(!entry1_ || !entry2_) { + return 0; + } + // used to just return 0, but we really want a default generic implementation + // that specific collections can override. + + // start with twice the title score + // and since the minimum is > 10, then need more than just a perfect title match + int res = 2*Entry::compareValues(entry1_, entry2_, QString::fromLatin1("title"), this); + // then add score for each field + FieldVec fields = entry1_->collection()->fields(); + for(Data::FieldVecIt it = fields.begin(); it != fields.end(); ++it) { + res += Entry::compareValues(entry1_, entry2_, it->name(), this); + } + return res; +} + +// static +// merges values from e2 into e1 +bool Collection::mergeEntry(EntryPtr e1, EntryPtr e2, bool overwrite_, bool askUser_) { + if(!e1 || !e2) { + myDebug() << "Collection::mergeEntry() - bad entry pointer" << endl; + return false; + } + bool ret = true; + FieldVec fields = e1->collection()->fields(); + for(FieldVec::Iterator field = fields.begin(); field != fields.end(); ++field) { + if(e2->field(field).isEmpty()) { + continue; + } +// myLog() << "Collection::mergeEntry() - reading field: " << field->name() << endl; + if(overwrite_ || e1->field(field).isEmpty()) { +// myLog() << e1->title() << ": updating field(" << field->name() << ") to " << e2->field(field->name()) << endl; + e1->setField(field, e2->field(field)); + ret = true; + } else if(e1->field(field) == e2->field(field)) { + continue; + } else if(field->type() == Field::Para) { + // for paragraph fields, concatenate the values, if they're not equal + e1->setField(field, e1->field(field) + QString::fromLatin1("<br/><br/>") + e2->field(field)); + ret = true; + } else if(field->type() == Field::Table) { + // if field F is a table-type field (album tracks, files, etc.), merge rows (keep their position) + // if e1's F val in [row i, column j] empty, replace with e2's val at same position + // if different (non-empty) vals at same position, CONFLICT! + const QString sep = QString::fromLatin1("::"); + QStringList vals1 = e1->fields(field, false); + QStringList vals2 = e2->fields(field, false); + while(vals1.count() < vals2.count()) { + vals1 += QString(); + } + for(uint i = 0; i < vals2.count(); ++i) { + if(vals2[i].isEmpty()) { + continue; + } + if(vals1[i].isEmpty()) { + vals1[i] = vals2[i]; + ret = true; + } else { + QStringList parts1 = QStringList::split(sep, vals1[i], true); + QStringList parts2 = QStringList::split(sep, vals2[i], true); + bool changedPart = false; + while(parts1.count() < parts2.count()) { + parts1 += QString(); + } + for(uint j = 0; j < parts2.count(); ++j) { + if(parts2[j].isEmpty()) { + continue; + } + if(parts1[j].isEmpty()) { + parts1[j] = parts2[j]; + changedPart = true; + } else if(askUser_ && parts1[j] != parts2[j]) { + int ret = Kernel::self()->askAndMerge(e1, e2, field, parts1[j], parts2[j]); + if(ret == 0) { + return false; // we got cancelled + } + if(ret == 1) { + parts1[j] = parts2[j]; + changedPart = true; + } + } + } + if(changedPart) { + vals1[i] = parts1.join(sep); + ret = true; + } + } + } + if(ret) { + e1->setField(field, vals1.join(QString::fromLatin1("; "))); + } + } else if(field->flags() & Data::Field::AllowMultiple) { + // if field F allows multiple values and not a Table (see above case), + // e1's F values = (e1's F values) U (e2's F values) (union) + // replace e1's field with union of e1's and e2's values for this field + QStringList items1 = e1->fields(field, false); + QStringList items2 = e2->fields(field, false); + for(QStringList::ConstIterator it = items2.begin(); it != items2.end(); ++it) { + // possible to have one value formatted and the other one not... + if(!items1.contains(*it) && !items1.contains(Field::format(*it, field->formatFlag()))) { + items1.append(*it); + } + } +// not sure if I think it should be sorted or not +// items1.sort(); + e1->setField(field, items1.join(QString::fromLatin1("; "))); + ret = true; + } else if(askUser_ && e1->field(field) != e2->field(field)) { + int ret = Kernel::self()->askAndMerge(e1, e2, field); + if(ret == 0) { + return false; // we got cancelled + } + if(ret == 1) { + e1->setField(field, e2->field(field)); + } + } + } + return ret; +} + +long Collection::getID() { + static long id = 0; + return ++id; +} + +#include "collection.moc" |