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/collection.cpp

920 lines
30 KiB

/***************************************************************************
copyright : (C) 2001-2006 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 "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 <tdelocale.h>
#include <tqregexp.h>
#include <tqvaluestack.h>
using Tellico::Data::Collection;
const char* Collection::s_emptyGroupTitle = I18N_NOOP("(Empty)");
const TQString Collection::s_peopleGroupName = TQString::fromLatin1("_people");
Collection::Collection(const TQString& title_)
: TQObject(), TDEShared(), m_nextEntryId(0), m_title(title_), m_entryIdDict(997)
, m_trackGroups(false) {
m_entryGroupDicts.setAutoDelete(true);
m_id = getID();
}
Collection::~Collection() {
}
TQString 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(TQString());
}
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 TQDict 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(TQString::fromLatin1("columns"), TQChar('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()) {
TQStringList allowed = currField->allowed();
const TQStringList& newAllowed = newField_->allowed();
for(TQStringList::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(TQString());
}
}
// if new field has additional extended properties, add those
for(StringMap::ConstIterator it = newField_->propertyList().begin(); it != newField_->propertyList().end(); ++it) {
const TQString propName = it.key();
const TQString 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, TQString::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, TQString::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 TQString 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 TQString oldTitle = oldField->title();
const TQString 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(TQString());
}
// 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 TQString& 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_, TQString());
}
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 TQString& 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 TQString& Collection::fieldNameByTitle(const TQString& title_) const {
if(title_.isEmpty()) {
return TQString();
}
FieldPtr f = fieldByTitle(title_);
if(!f) { // might happen in MainWindow::saveCollectionOptions
return TQString();
}
return f->name();
}
const TQString& Collection::fieldTitleByName(const TQString& name_) const {
if(name_.isEmpty()) {
return TQString();
}
FieldPtr f = fieldByName(name_);
if(!f) {
kdWarning() << "Collection::fieldTitleByName() - no field named " << name_ << endl;
return TQString();
}
return f->title();
}
TQStringList Collection::valuesByFieldName(const TQString& name_) const {
if(name_.isEmpty()) {
return TQStringList();
}
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 TQString& name_) const {
return m_fieldNameDict.isEmpty() ? 0 : name_.isEmpty() ? 0 : m_fieldNameDict.find(name_);
}
Tellico::Data::FieldPtr Collection::fieldByTitle(const TQString& title_) const {
return m_fieldTitleDict.isEmpty() ? 0 : title_.isEmpty() ? 0 : m_fieldTitleDict.find(title_);
}
bool Collection::hasField(const TQString& name_) const {
return fieldByName(name_) != 0;
}
bool Collection::isAllowed(const TQString& key_, const TQString& 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 TQString& 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 TQString& 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) {
TQStringList groups = entryGroupNamesByField(entry, fieldName_);
for(TQStringList::ConstIterator groupIt = groups.begin(); groupIt != groups.end(); ++groupIt) {
// find the group for this group name
// bool fields used the field title
TQString 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
TQDictIterator<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 TQString 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
TQStringList Collection::entryGroupNamesByField(EntryPtr entry_, const TQString& 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() {
TQDictIterator<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());
TQValueStack<FieldPtr> fieldsToCheck;
fieldsToCheck.push(field_);
while(!fieldsToCheck.isEmpty()) {
FieldPtr f = fieldsToCheck.pop();
const TQStringList depFields = f->dependsOn();
for(TQStringList::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_, TQString::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) + TQString::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 TQString sep = TQString::fromLatin1("::");
TQStringList vals1 = e1->fields(field, false);
TQStringList vals2 = e2->fields(field, false);
while(vals1.count() < vals2.count()) {
vals1 += TQString();
}
for(uint i = 0; i < vals2.count(); ++i) {
if(vals2[i].isEmpty()) {
continue;
}
if(vals1[i].isEmpty()) {
vals1[i] = vals2[i];
ret = true;
} else {
TQStringList parts1 = TQStringList::split(sep, vals1[i], true);
TQStringList parts2 = TQStringList::split(sep, vals2[i], true);
bool changedPart = false;
while(parts1.count() < parts2.count()) {
parts1 += TQString();
}
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(TQString::fromLatin1("; ")));
}
// remove the merging due to use comments
// maybe in the future have a more intelligent way
#if 0
} 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
TQStringList items1 = e1->fields(field, false);
TQStringList items2 = e2->fields(field, false);
for(TQStringList::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(TQString::fromLatin1("; ")));
ret = true;
#endif
} 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"