File indexing completed on 2024-04-28 16:31:51
0001 /*************************************************************************** 0002 Copyright (C) 2001-2009 Robby Stephenson <robby@periapsis.org> 0003 ***************************************************************************/ 0004 0005 /*************************************************************************** 0006 * * 0007 * This program is free software; you can redistribute it and/or * 0008 * modify it under the terms of the GNU General Public License as * 0009 * published by the Free Software Foundation; either version 2 of * 0010 * the License or (at your option) version 3 or any later version * 0011 * accepted by the membership of KDE e.V. (or its successor approved * 0012 * by the membership of KDE e.V.), which shall act as a proxy * 0013 * defined in Section 14 of version 3 of the license. * 0014 * * 0015 * This program is distributed in the hope that it will be useful, * 0016 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0017 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0018 * GNU General Public License for more details. * 0019 * * 0020 * You should have received a copy of the GNU General Public License * 0021 * along with this program. If not, see <http://www.gnu.org/licenses/>. * 0022 * * 0023 ***************************************************************************/ 0024 0025 #include "collection.h" 0026 #include "field.h" 0027 #include "entry.h" 0028 #include "entrygroup.h" 0029 #include "derivedvalue.h" 0030 #include "fieldformat.h" 0031 #include "utils/string_utils.h" 0032 #include "utils/stringset.h" 0033 #include "entrycomparison.h" 0034 #include "tellico_debug.h" 0035 0036 #include <KLocalizedString> 0037 0038 using namespace Tellico; 0039 using Tellico::Data::Collection; 0040 0041 const QString Collection::s_peopleGroupName = QStringLiteral("_people"); 0042 0043 Collection::Collection(const QString& title_) 0044 : QObject(), QSharedData(), m_nextEntryId(1), m_title(title_), m_trackGroups(false) { 0045 m_id = getID(); 0046 } 0047 0048 Collection::Collection(bool addDefaultFields_, const QString& title_) 0049 : QObject(), QSharedData(), m_nextEntryId(1), m_title(title_), m_trackGroups(false) { 0050 if(m_title.isEmpty()) { 0051 m_title = i18n("My Collection"); 0052 } 0053 m_id = getID(); 0054 if(addDefaultFields_) { 0055 addField(Field::createDefaultField(Field::IDField)); 0056 addField(Field::createDefaultField(Field::TitleField)); 0057 addField(Field::createDefaultField(Field::CreatedDateField)); 0058 addField(Field::createDefaultField(Field::ModifiedDateField)); 0059 } 0060 } 0061 0062 Collection::~Collection() { 0063 // maybe we should just call clear() ? 0064 foreach(EntryGroupDict* dict, m_entryGroupDicts) { 0065 qDeleteAll(*dict); 0066 } 0067 qDeleteAll(m_entryGroupDicts); 0068 m_entryGroupDicts.clear(); 0069 } 0070 0071 bool Collection::addFields(Tellico::Data::FieldList list_) { 0072 bool success = true; 0073 foreach(FieldPtr field, list_) { 0074 success &= addField(field); 0075 } 0076 return success; 0077 } 0078 0079 bool Collection::addField(Tellico::Data::FieldPtr field_) { 0080 Q_ASSERT(field_); 0081 if(!field_) { 0082 return false; 0083 } 0084 0085 // this essentially checks for duplicates 0086 if(hasField(field_->name())) { 0087 myDebug() << "replacing" << field_->name() << "in collection" << m_title; 0088 removeField(fieldByName(field_->name()), true); 0089 } 0090 0091 m_fields.append(field_); 0092 m_fieldByName.insert(field_->name(), field_.data()); 0093 m_fieldByTitle.insert(field_->title(), field_.data()); 0094 0095 if(field_->formatType() == FieldFormat::FormatName) { 0096 m_peopleFields.append(field_); // list of people attributes 0097 if(m_peopleFields.count() > 1) { 0098 // the second time that a person field is added, add a "pseudo-group" for people 0099 if(!m_entryGroupDicts.contains(s_peopleGroupName)) { 0100 EntryGroupDict* d = new EntryGroupDict(); 0101 m_entryGroupDicts.insert(s_peopleGroupName, d); 0102 m_entryGroups.prepend(s_peopleGroupName); 0103 } 0104 } 0105 } 0106 0107 if(field_->type() == Field::Image) { 0108 m_imageFields.append(field_); 0109 } 0110 0111 if(!field_->category().isEmpty() && !m_fieldCategories.contains(field_->category())) { 0112 m_fieldCategories << field_->category(); 0113 } 0114 0115 if(field_->hasFlag(Field::AllowGrouped)) { 0116 // m_entryGroupsDicts autoDeletes each QDict when the Collection d'tor is called 0117 EntryGroupDict* dict = new EntryGroupDict(); 0118 m_entryGroupDicts.insert(field_->name(), dict); 0119 // cache the possible groups of entries 0120 m_entryGroups << field_->name(); 0121 } 0122 0123 if(m_defaultGroupField.isEmpty() && field_->hasFlag(Field::AllowGrouped)) { 0124 m_defaultGroupField = field_->name(); 0125 } 0126 0127 if(field_->hasFlag(Field::Derived)) { 0128 DerivedValue dv(field_); 0129 if(dv.isRecursive(this)) { 0130 field_->setProperty(QStringLiteral("template"), QString()); 0131 } 0132 } 0133 0134 // refresh all dependent fields, in case one references this new one 0135 foreach(FieldPtr existingField, m_fields) { 0136 if(existingField->hasFlag(Field::Derived)) { 0137 emit signalRefreshField(existingField); 0138 } 0139 } 0140 0141 return true; 0142 } 0143 0144 bool Collection::mergeField(Tellico::Data::FieldPtr newField_) { 0145 bool structuralChange = false; 0146 if(!newField_) { 0147 return structuralChange; 0148 } 0149 0150 FieldPtr currField = fieldByName(newField_->name()); 0151 if(!currField) { 0152 // does not exist in current collection, add it 0153 Data::FieldPtr f(new Field(*newField_)); 0154 bool success = addField(f); 0155 if(success) { 0156 emit mergeAddedField(CollPtr(this), f); 0157 } else { 0158 myDebug() << "Failed to add field:" << f->name(); 0159 } 0160 // adding a new field is a structural change to the collection 0161 return success; 0162 } 0163 0164 if(newField_->type() == Field::Table2) { 0165 newField_->setType(Data::Field::Table); 0166 newField_->setProperty(QStringLiteral("columns"), QStringLiteral("2")); 0167 } 0168 0169 // the original field type is kept 0170 if(currField->type() != newField_->type()) { 0171 myDebug() << "skipping, field type mismatch for " << currField->title(); 0172 return structuralChange; 0173 } 0174 0175 // if field is a Choice, then make sure all values are there 0176 if(currField->type() == Field::Choice && currField->allowed() != newField_->allowed()) { 0177 QStringList allowed = currField->allowed(); 0178 const QStringList& newAllowed = newField_->allowed(); 0179 for(QStringList::ConstIterator it = newAllowed.begin(); it != newAllowed.end(); ++it) { 0180 if(!allowed.contains(*it)) { 0181 allowed.append(*it); 0182 structuralChange = true; 0183 } 0184 } 0185 currField->setAllowed(allowed); 0186 } 0187 0188 // don't change original format flags 0189 // don't change original category 0190 // add new description if current is empty 0191 if(currField->description().isEmpty()) { 0192 currField->setDescription(newField_->description()); 0193 } 0194 0195 // if new field has additional extended properties, add those 0196 for(StringMap::const_iterator it = newField_->propertyList().begin(); it != newField_->propertyList().end(); ++it) { 0197 const QString propName = it.key(); 0198 const QString currValue = currField->property(propName); 0199 if(currValue.isEmpty()) { 0200 currField->setProperty(propName, it.value()); 0201 structuralChange = true; 0202 } else if (it.value() != currValue) { 0203 if(currField->type() == Field::URL && propName == QLatin1String("relative")) { 0204 myWarning() << "relative URL property does not match for " << currField->name(); 0205 } else if((currField->type() == Field::Table && propName == QLatin1String("columns")) 0206 || (currField->type() == Field::Rating && propName == QLatin1String("maximum"))) { 0207 bool ok; 0208 uint currNum = Tellico::toUInt(currValue, &ok); 0209 uint newNum = Tellico::toUInt(it.value(), &ok); 0210 if(newNum > currNum) { // bigger values 0211 currField->setProperty(propName, QString::number(newNum)); 0212 structuralChange = true; 0213 } 0214 } else if(currField->type() == Field::Rating && propName == QLatin1String("minimum")) { 0215 bool ok; 0216 uint currNum = Tellico::toUInt(currValue, &ok); 0217 uint newNum = Tellico::toUInt(it.value(), &ok); 0218 if(newNum < currNum) { // smaller values 0219 currField->setProperty(propName, QString::number(newNum)); 0220 structuralChange = true; 0221 } 0222 } 0223 } 0224 if(propName == QLatin1String("template") && currField->hasFlag(Field::Derived)) { 0225 DerivedValue dv(currField); 0226 if(dv.isRecursive(this)) { 0227 currField->setProperty(QStringLiteral("template"), QString()); 0228 } 0229 } 0230 } 0231 0232 // combine flags 0233 currField->setFlags(currField->flags() | newField_->flags()); 0234 return structuralChange; 0235 } 0236 0237 // be really careful with these field pointers, try not to call too many other functions 0238 // which may depend on the field list 0239 bool Collection::modifyField(Tellico::Data::FieldPtr newField_) { 0240 if(!newField_) { 0241 return false; 0242 } 0243 // myDebug() << "; 0244 0245 // the field name never changes 0246 const QString fieldName = newField_->name(); 0247 FieldPtr oldField = fieldByName(fieldName); 0248 if(!oldField) { 0249 myDebug() << "no field named " << fieldName; 0250 return false; 0251 } 0252 0253 // update name dict 0254 m_fieldByName.insert(fieldName, newField_.data()); 0255 0256 // update titles 0257 const QString oldTitle = oldField->title(); 0258 const QString newTitle = newField_->title(); 0259 if(oldTitle == newTitle) { 0260 m_fieldByTitle.insert(newTitle, newField_.data()); 0261 } else { 0262 m_fieldByTitle.remove(oldTitle); 0263 m_fieldByTitle.insert(newTitle, newField_.data()); 0264 } 0265 0266 // now replace the field pointer in the list 0267 int pos = m_fields.indexOf(oldField); 0268 if(pos > -1) { 0269 m_fields.replace(pos, newField_); 0270 } else { 0271 myDebug() << "no index found!"; 0272 return false; 0273 } 0274 0275 // update category list. 0276 if(oldField->category() != newField_->category()) { 0277 m_fieldCategories.clear(); 0278 foreach(FieldPtr it, m_fields) { 0279 // add category if it's not in the list yet 0280 if(!it->category().isEmpty() && !m_fieldCategories.contains(it->category())) { 0281 m_fieldCategories += it->category(); 0282 } 0283 } 0284 } 0285 0286 if(newField_->hasFlag(Field::Derived)) { 0287 DerivedValue dv(newField_); 0288 if(dv.isRecursive(this)) { 0289 newField_->setProperty(QStringLiteral("template"), QString()); 0290 } 0291 } 0292 0293 // keep track of if the entry groups will need to be reset 0294 bool resetGroups = false; 0295 0296 // if format is different, go ahead and invalidate all formatted entry values 0297 if(oldField->formatType() != newField_->formatType()) { 0298 // invalidate cached format strings of all entry attributes of this name 0299 foreach(EntryPtr entry, m_entries) { 0300 entry->invalidateFormattedFieldValue(fieldName); 0301 } 0302 resetGroups = true; 0303 } 0304 0305 // check to see if the people "pseudo-group" needs to be updated 0306 // only if only one of the two is a name 0307 bool wasPeople = oldField->formatType() == FieldFormat::FormatName; 0308 bool isPeople = newField_->formatType() == FieldFormat::FormatName; 0309 if(wasPeople) { 0310 m_peopleFields.removeAll(oldField); 0311 if(!isPeople) { 0312 resetGroups = true; 0313 } 0314 } 0315 if(isPeople) { 0316 // if there's more than one people field and no people dict exists yet, add it 0317 if(m_peopleFields.count() > 1 && !m_entryGroupDicts.contains(s_peopleGroupName)) { 0318 EntryGroupDict* d = new EntryGroupDict(); 0319 m_entryGroupDicts.insert(s_peopleGroupName, d); 0320 // put it at the top of the list 0321 m_entryGroups.prepend(s_peopleGroupName); 0322 } 0323 m_peopleFields.append(newField_); 0324 if(!wasPeople) { 0325 resetGroups = true; 0326 } 0327 } 0328 0329 bool wasGrouped = oldField->hasFlag(Field::AllowGrouped); 0330 bool isGrouped = newField_->hasFlag(Field::AllowGrouped); 0331 if(wasGrouped) { 0332 if(!isGrouped) { 0333 // in order to keep list in the same order, don't remove unless new field is not groupable 0334 m_entryGroups.removeAll(fieldName); 0335 delete m_entryGroupDicts.take(fieldName); // no auto-delete here 0336 myDebug() << "no longer grouped: " << fieldName; 0337 resetGroups = true; 0338 } else { 0339 // don't do this, it wipes out the old groups! 0340 // m_entryGroupDicts.replace(fieldName, new EntryGroupDict()); 0341 } 0342 } else if(isGrouped) { 0343 EntryGroupDict* d = new EntryGroupDict(); 0344 m_entryGroupDicts.insert(fieldName, d); 0345 if(!wasGrouped) { 0346 // cache the possible groups of entries 0347 m_entryGroups << fieldName; 0348 } 0349 resetGroups = true; 0350 } 0351 0352 if(oldField->type() == Field::Image) { 0353 m_imageFields.removeAll(oldField); 0354 } 0355 if(newField_->type() == Field::Image) { 0356 m_imageFields.append(newField_); 0357 } 0358 0359 if(resetGroups) { 0360 // myLog() << "invalidating groups"; 0361 invalidateGroups(); 0362 } 0363 0364 // now to update all entries if the field is a derived value and the template changed 0365 if(newField_->hasFlag(Field::Derived) && 0366 oldField->property(QStringLiteral("template")) != newField_->property(QStringLiteral("template"))) { 0367 emit signalRefreshField(newField_); 0368 } 0369 0370 return true; 0371 } 0372 0373 bool Collection::removeField(const QString& name_, bool force_) { 0374 return removeField(fieldByName(name_), force_); 0375 } 0376 0377 // force allows me to force the deleting of the title field if I need to 0378 bool Collection::removeField(Tellico::Data::FieldPtr field_, bool force_/*=false*/) { 0379 if(!field_ || !m_fields.contains(field_)) { 0380 if(field_) { 0381 myDebug() << "can't delete field:" << field_->name(); 0382 } 0383 return false; 0384 } 0385 // myDebug() << "name = " << field_->name(); 0386 0387 // can't delete the title field 0388 if((field_->hasFlag(Field::NoDelete)) && !force_) { 0389 return false; 0390 } 0391 0392 foreach(EntryPtr entry, m_entries) { 0393 // setting the fields to an empty string removes the value from the entry's list 0394 entry->setField(field_, QString()); 0395 } 0396 0397 bool success = true; 0398 if(field_->formatType() == FieldFormat::FormatName) { 0399 m_peopleFields.removeAll(field_); 0400 } 0401 0402 if(field_->type() == Field::Image) { 0403 m_imageFields.removeAll(field_); 0404 } 0405 m_fieldByName.remove(field_->name()); 0406 m_fieldByTitle.remove(field_->title()); 0407 0408 if(fieldsByCategory(field_->category()).count() == 1) { 0409 m_fieldCategories.removeAll(field_->category()); 0410 } 0411 0412 if(field_->hasFlag(Field::AllowGrouped)) { 0413 EntryGroupDict* dict = m_entryGroupDicts.take(field_->name()); 0414 qDeleteAll(*dict); 0415 m_entryGroups.removeAll(field_->name()); 0416 if(field_->name() == m_defaultGroupField && !m_entryGroups.isEmpty()) { 0417 setDefaultGroupField(m_entryGroups.first()); 0418 } 0419 } 0420 0421 m_fields.removeAll(field_); 0422 0423 // refresh all dependent fields, rather lazy, but there's 0424 // likely to be weird effects when checking dependent fields 0425 // while removing one, so refresh all of them 0426 foreach(FieldPtr field, m_fields) { 0427 if(field->hasFlag(Field::Derived)) { 0428 emit signalRefreshField(field); 0429 } 0430 } 0431 0432 return success; 0433 } 0434 0435 void Collection::reorderFields(const Tellico::Data::FieldList& list_) { 0436 // assume the lists have the same pointers! 0437 m_fields = list_; 0438 0439 // also reset category list, since the order may have changed 0440 m_fieldCategories.clear(); 0441 foreach(FieldPtr field, m_fields) { 0442 if(!field->category().isEmpty() && !m_fieldCategories.contains(field->category())) { 0443 m_fieldCategories << field->category(); 0444 } 0445 } 0446 } 0447 0448 void Collection::addEntries(const Tellico::Data::EntryList& entries_) { 0449 if(entries_.isEmpty()) { 0450 return; 0451 } 0452 0453 foreach(EntryPtr entry, entries_) { 0454 if(!entry) { 0455 Q_ASSERT(entry); 0456 continue; 0457 } 0458 bool foster = false; 0459 if(this != entry->collection().data()) { 0460 entry->setCollection(CollPtr(this)); 0461 foster = true; 0462 } 0463 0464 m_entries.append(entry); 0465 // myDebug() << "added entry (" << entry->title() << ")" << entry->id(); 0466 0467 if(entry->id() >= m_nextEntryId) { 0468 m_nextEntryId = entry->id() + 1; 0469 } else if(entry->id() == -1) { 0470 entry->setId(m_nextEntryId); 0471 ++m_nextEntryId; 0472 } else if(m_entryById.contains(entry->id())) { 0473 if(!foster) { 0474 myDebug() << "the collection already has an entry with id = " << entry->id(); 0475 } 0476 entry->setId(m_nextEntryId); 0477 ++m_nextEntryId; 0478 } 0479 m_entryById.insert(entry->id(), entry.data()); 0480 0481 if(hasField(QStringLiteral("cdate")) && entry->field(QStringLiteral("cdate")).isEmpty()) { 0482 // use mdate if it exists 0483 QString cdate = entry->field(QStringLiteral("mdate")); 0484 if(cdate.isEmpty()) { 0485 cdate = QDate::currentDate().toString(Qt::ISODate); 0486 } 0487 entry->setField(QStringLiteral("cdate"), cdate, false); 0488 } 0489 if(hasField(QStringLiteral("mdate")) && entry->field(QStringLiteral("mdate")).isEmpty()) { 0490 entry->setField(QStringLiteral("mdate"), QDate::currentDate().toString(Qt::ISODate), false); 0491 } 0492 } 0493 if(m_trackGroups) { 0494 populateCurrentDicts(entries_, fieldNames()); 0495 } 0496 } 0497 0498 void Collection::removeEntriesFromDicts(const Tellico::Data::EntryList& entries_, const QStringList& fields_) { 0499 QSet<EntryGroup*> modifiedGroups; 0500 foreach(EntryPtr entry, entries_) { 0501 // need a copy of the vector since it gets changed 0502 QList<EntryGroup*> groups = entry->groups(); 0503 foreach(EntryGroup* group, groups) { 0504 // only clear groups for the modified fields, skip the others 0505 // also clear for all derived values, just in case 0506 if(!fields_.contains(group->fieldName()) && hasField(group->fieldName()) && !fieldByName(group->fieldName())->hasFlag(Field::Derived)) { 0507 continue; 0508 } 0509 if(entry->removeFromGroup(group)) { 0510 modifiedGroups.insert(group); 0511 } 0512 if(group->isEmpty() && !m_groupsToDelete.contains(group)) { 0513 m_groupsToDelete.push_back(group); 0514 } 0515 } 0516 } 0517 if(!modifiedGroups.isEmpty()) { 0518 emit signalGroupsModified(CollPtr(this), modifiedGroups.values()); 0519 } 0520 } 0521 0522 // this function gets called whenever an entry is modified. Its purpose is to keep the 0523 // groupDicts current. It first removes the entry from every group to which it belongs, 0524 // then it repopulates the dicts with the entry's fields 0525 void Collection::updateDicts(const Tellico::Data::EntryList& entries_, const QStringList& fields_) { 0526 if(entries_.isEmpty() || !m_trackGroups) { 0527 return; 0528 } 0529 QStringList modifiedFields = fields_; 0530 if(modifiedFields.isEmpty()) { 0531 // myDebug() << "updating all fields"; 0532 modifiedFields = fieldNames(); 0533 } 0534 removeEntriesFromDicts(entries_, modifiedFields); 0535 populateCurrentDicts(entries_, modifiedFields); 0536 cleanGroups(); 0537 } 0538 0539 bool Collection::removeEntries(const Tellico::Data::EntryList& vec_) { 0540 if(vec_.isEmpty()) { 0541 return false; 0542 } 0543 0544 removeEntriesFromDicts(vec_, fieldNames()); 0545 bool success = true; 0546 foreach(EntryPtr entry, vec_) { 0547 m_entryById.remove(entry->id()); 0548 m_entries.removeAll(entry); 0549 } 0550 cleanGroups(); 0551 return success; 0552 } 0553 0554 Tellico::Data::FieldList Collection::fieldsByCategory(const QString& cat_) { 0555 #ifndef NDEBUG 0556 if(!m_fieldCategories.contains(cat_)) { 0557 myDebug() << cat_ << "' is not in category list"; 0558 } 0559 #endif 0560 if(cat_.isEmpty()) { 0561 myDebug() << "empty category!"; 0562 return FieldList(); 0563 } 0564 0565 FieldList list; 0566 foreach(FieldPtr field, m_fields) { 0567 if(field->category() == cat_) { 0568 list.append(field); 0569 } 0570 } 0571 return list; 0572 } 0573 0574 QString Collection::fieldNameByTitle(const QString& title_) const { 0575 if(title_.isEmpty()) { 0576 return QString(); 0577 } 0578 FieldPtr f = fieldByTitle(title_); 0579 if(!f) { // might happen in MainWindow::saveCollectionOptions 0580 return QString(); 0581 } 0582 return f->name(); 0583 } 0584 0585 QStringList Collection::fieldNames() const { 0586 return m_fieldByName.keys(); 0587 } 0588 0589 QStringList Collection::fieldTitles() const { 0590 return m_fieldByTitle.keys(); 0591 } 0592 0593 QString Collection::fieldTitleByName(const QString& name_) const { 0594 if(name_.isEmpty()) { 0595 return QString(); 0596 } 0597 FieldPtr f = fieldByName(name_); 0598 if(!f) { 0599 myWarning() << "no field named " << name_; 0600 return QString(); 0601 } 0602 return f->title(); 0603 } 0604 0605 QStringList Collection::valuesByFieldName(const QString& name_) const { 0606 if(name_.isEmpty()) { 0607 return QStringList(); 0608 } 0609 0610 StringSet values; 0611 foreach(EntryPtr entry, m_entries) { 0612 values.add(FieldFormat::splitValue(entry->field(name_))); 0613 } // end entry loop 0614 0615 return values.values(); 0616 } 0617 0618 Tellico::Data::FieldPtr Collection::fieldByName(const QString& name_) const { 0619 return FieldPtr(m_fieldByName.value(name_)); 0620 } 0621 0622 Tellico::Data::FieldPtr Collection::fieldByTitle(const QString& title_) const { 0623 return FieldPtr(m_fieldByTitle.value(title_)); 0624 } 0625 0626 bool Collection::hasField(const QString& name_) const { 0627 return m_fieldByName.contains(name_); 0628 } 0629 0630 bool Collection::isAllowed(const QString& field_, const QString& value_) const { 0631 // empty string is always allowed 0632 if(value_.isEmpty()) { 0633 return true; 0634 } 0635 0636 // find the field with a name of 'key_' 0637 FieldPtr field = fieldByName(field_); 0638 0639 // if the type is not multiple choice or if value_ is allowed, return true 0640 if(field && (field->type() != Field::Choice || field->allowed().contains(value_))) { 0641 return true; 0642 } 0643 0644 return false; 0645 } 0646 0647 Tellico::Data::EntryGroupDict* Collection::entryGroupDictByName(const QString& name_) { 0648 // myDebug() << name_; 0649 m_lastGroupField = name_; // keep track, even if it's invalid 0650 if(name_.isEmpty() || !m_entryGroupDicts.contains(name_) || m_entries.isEmpty()) { 0651 return nullptr; 0652 } 0653 EntryGroupDict* dict = m_entryGroupDicts.value(name_); 0654 if(dict && dict->isEmpty()) { 0655 const bool b = signalsBlocked(); 0656 // block signals so all the group created/modified signals don't fire 0657 blockSignals(true); 0658 populateDict(dict, name_, m_entries); 0659 blockSignals(b); 0660 } 0661 return dict; 0662 } 0663 0664 void Collection::populateDict(Tellico::Data::EntryGroupDict* dict_, const QString& fieldName_, const Tellico::Data::EntryList& entries_) { 0665 // myDebug() << fieldName_; 0666 Q_ASSERT(dict_); 0667 const bool isBool = hasField(fieldName_) && fieldByName(fieldName_)->type() == Field::Bool; 0668 0669 QSet<EntryGroup*> modifiedGroups; 0670 foreach(EntryPtr entry, entries_) { 0671 const QStringList groups = entryGroupNamesByField(entry, fieldName_); 0672 foreach(QString groupTitle, groups) { // krazy:exclude=foreach 0673 // find the group for this group name 0674 // bool fields use the field title 0675 if(isBool && !groupTitle.isEmpty()) { 0676 groupTitle = fieldTitleByName(fieldName_); 0677 } 0678 EntryGroup* group = dict_->value(groupTitle); 0679 // if the group doesn't exist, create it 0680 if(!group) { 0681 group = new EntryGroup(groupTitle, fieldName_); 0682 dict_->insert(groupTitle, group); 0683 } else if(group->isEmpty()) { 0684 // if it's empty, then it was previously added to the vector of groups to delete 0685 // remove it from that vector now that we're adding to it 0686 m_groupsToDelete.removeOne(group); 0687 } 0688 if(entry->addToGroup(group)) { 0689 modifiedGroups.insert(group); 0690 } 0691 } // end group loop 0692 } // end entry loop 0693 if(!modifiedGroups.isEmpty()) { 0694 emit signalGroupsModified(CollPtr(this), modifiedGroups.values()); 0695 } 0696 } 0697 0698 void Collection::populateCurrentDicts(const Tellico::Data::EntryList& entries_, const QStringList& fields_) { 0699 if(m_entryGroupDicts.isEmpty()) { 0700 return; 0701 } 0702 0703 // special case when adding an entry to a new empty collection 0704 // there are no existing non-empty groups 0705 bool allEmpty = true; 0706 0707 // iterate over all the possible groupDicts 0708 // for each dict, get the value of that field for the entry 0709 // if multiple values are allowed, split the value and then insert the 0710 // entry pointer into the dict for each value 0711 QHash<QString, EntryGroupDict*>::const_iterator dictIt = m_entryGroupDicts.constBegin(); 0712 for( ; dictIt != m_entryGroupDicts.constEnd(); ++dictIt) { 0713 // skip dicts for fields not in the modified list 0714 if(!fields_.contains(dictIt.key())) { 0715 continue; 0716 } 0717 // only populate if it's not empty, since they are 0718 // populated on demand 0719 if(!dictIt.value()->isEmpty()) { 0720 populateDict(dictIt.value(), dictIt.key(), entries_); 0721 allEmpty = false; 0722 } 0723 } 0724 0725 if(allEmpty) { 0726 // myDebug() << "all collection dicts are empty"; 0727 // still need to populate the current group dict 0728 EntryGroupDict* dict = m_entryGroupDicts.value(m_lastGroupField); 0729 if(dict) { 0730 populateDict(dict, m_lastGroupField, entries_); 0731 } 0732 } 0733 } 0734 0735 // return a string list for all the groups that the entry belongs to 0736 // for a given field. Normally, this would just be splitting the entry's value 0737 // for the field, but if the field name is the people pseudo-group, then it gets 0738 // a bit more complicated 0739 QStringList Collection::entryGroupNamesByField(Tellico::Data::EntryPtr entry_, const QString& fieldName_) { 0740 if(fieldName_ != s_peopleGroupName) { 0741 return entry_->groupNamesByFieldName(fieldName_); 0742 } 0743 0744 // the empty group is only returned if the entry has an empty list for every people field 0745 bool allEmpty = true; 0746 StringSet values; 0747 foreach(FieldPtr field, m_peopleFields) { 0748 const QStringList groups = entry_->groupNamesByFieldName(field->name()); 0749 if(allEmpty && (groups.count() != 1 || !groups.at(0).isEmpty())) { 0750 allEmpty = false; 0751 } 0752 values.add(groups); 0753 } 0754 if(!allEmpty) { 0755 // we don't want the empty string 0756 values.remove(QString()); 0757 } 0758 return values.values(); 0759 } 0760 0761 void Collection::invalidateGroups() { 0762 foreach(EntryGroupDict* dict, m_entryGroupDicts) { 0763 qDeleteAll(*dict); 0764 dict->clear(); 0765 // don't delete the dict, just clear it 0766 } 0767 0768 // populateDicts() will make signals that the group view is connected to, block those 0769 blockSignals(true); 0770 foreach(EntryPtr entry, m_entries) { 0771 entry->invalidateFormattedFieldValue(); 0772 entry->clearGroups(); 0773 } 0774 blockSignals(false); 0775 } 0776 0777 Tellico::Data::EntryPtr Collection::entryById(Data::ID id_) { 0778 return EntryPtr(m_entryById.value(id_)); 0779 } 0780 0781 void Collection::addBorrower(Tellico::Data::BorrowerPtr borrower_) { 0782 if(!borrower_) { 0783 return; 0784 } 0785 // check against existing borrower uid 0786 BorrowerPtr existingBorrower; 0787 foreach(BorrowerPtr bor, m_borrowers) { 0788 if(bor->uid() == borrower_->uid()) { 0789 existingBorrower = bor; 0790 break; 0791 } 0792 } 0793 if(!existingBorrower) { 0794 m_borrowers.append(borrower_); 0795 } else if(existingBorrower != borrower_) { 0796 // need to merge loans 0797 QHash<QString, LoanPtr> existingLoans; 0798 foreach(LoanPtr loan, existingBorrower->loans()) { 0799 existingLoans.insert(loan->uid(), loan); 0800 } 0801 foreach(LoanPtr loan, borrower_->loans()) { 0802 if(!existingLoans.contains(loan->uid())) { 0803 existingBorrower->addLoan(loan); 0804 } 0805 } 0806 } 0807 } 0808 0809 void Collection::addFilter(Tellico::FilterPtr filter_) { 0810 if(!filter_) { 0811 return; 0812 } 0813 0814 m_filters.append(filter_); 0815 } 0816 0817 bool Collection::removeFilter(Tellico::FilterPtr filter_) { 0818 if(!filter_) { 0819 return false; 0820 } 0821 0822 return m_filters.removeAll(filter_) > 0; 0823 } 0824 0825 void Collection::clear() { 0826 // since the collection holds a pointer to each entry and each entry 0827 // hold a pointer to the collection, and they're both sharedptrs, 0828 // neither will ever get deleted, unless the collection removes 0829 // all held pointers, specifically to entries 0830 m_fields.clear(); 0831 m_peopleFields.clear(); 0832 m_imageFields.clear(); 0833 m_fieldCategories.clear(); 0834 m_fieldByName.clear(); 0835 m_fieldByTitle.clear(); 0836 m_defaultGroupField.clear(); 0837 0838 m_entries.clear(); 0839 m_entryById.clear(); 0840 foreach(EntryGroupDict* dict, m_entryGroupDicts) { 0841 qDeleteAll(*dict); 0842 } 0843 qDeleteAll(m_entryGroupDicts); 0844 m_entryGroupDicts.clear(); 0845 m_entryGroups.clear(); 0846 m_groupsToDelete.clear(); 0847 m_filters.clear(); 0848 m_borrowers.clear(); 0849 } 0850 0851 void Collection::cleanGroups() { 0852 foreach(EntryGroup* group, m_groupsToDelete) { 0853 EntryGroupDict* dict = entryGroupDictByName(group->fieldName()); 0854 if(!dict) { 0855 continue; 0856 } 0857 EntryGroup* groupToDelete = dict->take(group->groupName()); 0858 delete groupToDelete; 0859 } 0860 m_groupsToDelete.clear(); 0861 } 0862 0863 QString Collection::prepareText(const QString& text_) const { 0864 return text_; 0865 } 0866 0867 int Collection::sameEntry(Tellico::Data::EntryPtr entry1_, Tellico::Data::EntryPtr entry2_) const { 0868 if(!entry1_ || !entry2_) { 0869 return 0; 0870 } 0871 // used to just return 0, but we really want a default generic implementation 0872 // that specific collections can override. 0873 0874 int res = 0; 0875 // start with twice the title score 0876 // and since the minimum is > 10, then need more than just a perfect title match 0877 res += EntryComparison::MATCH_WEIGHT_MED*EntryComparison::score(entry1_, entry2_, QStringLiteral("title"), this); 0878 // then add score for each field 0879 foreach(FieldPtr field, entry1_->collection()->fields()) { 0880 res += EntryComparison::MATCH_WEIGHT_LOW*EntryComparison::score(entry1_, entry2_, field->name(), this); 0881 if(res >= EntryComparison::ENTRY_PERFECT_MATCH) return res; 0882 } 0883 return res; 0884 } 0885 0886 Tellico::Data::ID Collection::getID() { 0887 static ID id = 0; 0888 return ++id; 0889 } 0890 0891 Data::FieldPtr Collection::primaryImageField() const { 0892 return m_imageFields.isEmpty() ? Data::FieldPtr() : fieldByName(m_imageFields.front()->name()); 0893 }