File indexing completed on 2024-04-28 05:08:18
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 "entry.h" 0026 #include "entrygroup.h" 0027 #include "collection.h" 0028 #include "field.h" 0029 #include "derivedvalue.h" 0030 #include "utils/string_utils.h" 0031 #include "utils/stringset.h" 0032 #include "tellico_debug.h" 0033 0034 #include <KLocalizedString> 0035 0036 using namespace Tellico; 0037 using namespace Tellico::Data; 0038 using Tellico::Data::Entry; 0039 0040 Entry::Entry(Tellico::Data::CollPtr coll_) : QSharedData(), m_coll(coll_), m_id(-1) { 0041 #ifndef NDEBUG 0042 if(!coll_) { 0043 myWarning() << "null collection pointer!"; 0044 } 0045 #endif 0046 } 0047 0048 Entry::Entry(Tellico::Data::CollPtr coll_, Data::ID id_) : QSharedData(), m_coll(coll_), m_id(id_) { 0049 #ifndef NDEBUG 0050 if(!coll_) { 0051 myWarning() << "null collection pointer!"; 0052 } 0053 #endif 0054 } 0055 0056 Entry::Entry(const Entry& entry_) : 0057 QSharedData(entry_), 0058 m_coll(entry_.m_coll), 0059 m_id(-1), 0060 m_fieldValues(entry_.m_fieldValues), 0061 m_formattedFields(entry_.m_formattedFields) { 0062 // special case for creation date since it gets set in Collection::addEntry IF cdate is empty 0063 m_fieldValues.remove(QStringLiteral("cdate")); 0064 m_fieldValues.remove(QStringLiteral("mdate")); 0065 } 0066 0067 Entry& Entry::operator=(const Entry& other_) { 0068 if(this == &other_) return *this; 0069 0070 // static_cast<QSharedData&>(*this) = static_cast<const QSharedData&>(other_); 0071 m_coll = other_.m_coll; 0072 m_id = other_.m_id; 0073 m_fieldValues = other_.m_fieldValues; 0074 // special case for creation date field which gets set in Collection::addEntry IF cdate is empty 0075 m_fieldValues.remove(QStringLiteral("cdate")); 0076 m_fieldValues.remove(QStringLiteral("mdate")); 0077 m_formattedFields = other_.m_formattedFields; 0078 return *this; 0079 } 0080 0081 Entry::~Entry() = default; 0082 0083 Tellico::Data::CollPtr Entry::collection() const { 0084 return m_coll; 0085 } 0086 0087 void Entry::setCollection(Tellico::Data::CollPtr coll_) { 0088 if(coll_ == m_coll) { 0089 // myDebug() << "already belongs to collection!"; 0090 return; 0091 } 0092 // special case adding a book to a bibtex collection 0093 // it would be better to do this in a real object-oriented way, but this should work 0094 const bool addEntryType = m_coll->type() == Collection::Book && 0095 coll_->type() == Collection::Bibtex && 0096 !m_coll->hasField(QStringLiteral("entry-type")); 0097 m_coll = coll_; 0098 m_id = -1; 0099 // set this after changing the m_coll pointer since setField() checks field validity 0100 if(addEntryType) { 0101 setField(QStringLiteral("entry-type"), QStringLiteral("book")); 0102 } 0103 } 0104 0105 QString Entry::title(FieldFormat::Request request_) const { 0106 return formattedField(m_coll ? m_coll->titleField() : QStringLiteral("title"), request_); 0107 } 0108 0109 QString Entry::field(const QString& fieldName_) const { 0110 return field(m_coll->fieldByName(fieldName_)); 0111 } 0112 0113 QString Entry::field(Tellico::Data::FieldPtr field_) const { 0114 if(!field_) { 0115 return QString(); 0116 } 0117 0118 if(field_->hasFlag(Field::Derived)) { 0119 DerivedValue dv(field_); 0120 return dv.value(EntryPtr(const_cast<Entry*>(this)), false); 0121 } 0122 0123 return m_fieldValues.value(field_->name()); 0124 } 0125 0126 QString Entry::formattedField(const QString& fieldName_, FieldFormat::Request request_) const { 0127 return formattedField(m_coll->fieldByName(fieldName_), request_); 0128 } 0129 0130 QString Entry::formattedField(Tellico::Data::FieldPtr field_, FieldFormat::Request request_) const { 0131 if(!field_) { 0132 return QString(); 0133 } 0134 0135 // don't format the value unless it's requested to do so 0136 if(request_ == FieldFormat::AsIsFormat) { 0137 return field(field_); 0138 } 0139 0140 const FieldFormat::Type flag = field_->formatType(); 0141 if(field_->hasFlag(Field::Derived)) { 0142 DerivedValue dv(field_); 0143 // format sub fields and whole string 0144 return FieldFormat::format(dv.value(EntryPtr(const_cast<Entry*>(this)), true), flag, request_); 0145 } 0146 0147 // if auto format is not set or FormatNone, then just return the value 0148 if(flag == FieldFormat::FormatNone) { 0149 return m_coll->prepareText(field(field_)); 0150 } 0151 0152 if(!m_formattedFields.contains(field_->name())) { 0153 QString formattedValue; 0154 if(field_->type() == Field::Table) { 0155 QStringList rows; 0156 // we only format the first column 0157 foreach(const QString& row, FieldFormat::splitTable(field(field_))) { 0158 QStringList columns = FieldFormat::splitRow(row); 0159 QStringList newValues; 0160 if(!columns.isEmpty()) { 0161 foreach(const QString& value, FieldFormat::splitValue(columns.at(0))) { 0162 newValues << FieldFormat::format(value, field_->formatType(), FieldFormat::DefaultFormat); 0163 } 0164 columns.replace(0, newValues.join(FieldFormat::delimiterString())); 0165 } 0166 rows << columns.join(FieldFormat::columnDelimiterString()); 0167 } 0168 formattedValue = rows.join(FieldFormat::rowDelimiterString()); 0169 } else { 0170 QStringList values; 0171 if(field_->hasFlag(Field::AllowMultiple)) { 0172 values = FieldFormat::splitValue(field(field_)); 0173 } else { 0174 values << field(field_); 0175 } 0176 QStringList formattedValues; 0177 foreach(const QString& value, values) { 0178 formattedValues << FieldFormat::format(m_coll->prepareText(value), flag, request_); 0179 } 0180 formattedValue = formattedValues.join(FieldFormat::delimiterString()); 0181 } 0182 if(!formattedValue.isEmpty()) { 0183 m_formattedFields.insert(field_->name(), Tellico::shareString(formattedValue)); 0184 } 0185 return formattedValue; 0186 } 0187 // otherwise, just look it up 0188 return m_formattedFields.value(field_->name()); 0189 } 0190 0191 bool Entry::setField(Tellico::Data::FieldPtr field_, const QString& value_, bool updateMDate_) { 0192 return setField(field_->name(), value_, updateMDate_); 0193 } 0194 0195 // updating the modified date of the entry is expensive with the call to QDate::currentDate 0196 // when loading a collection from a file (in particular), it's faster to ignore that date 0197 bool Entry::setField(const QString& name_, const QString& value_, bool updateMDate_) { 0198 if(name_.isEmpty()) { 0199 myWarning() << "empty field name for value:" << value_; 0200 return false; 0201 } 0202 0203 if(m_coll->fields().isEmpty()) { 0204 myDebug() << "collection has no fields, can't add -" << name_; 0205 return false; 0206 } 0207 0208 if(!m_coll->hasField(name_)) { 0209 myDebug() << "unknown collection entry field -" << name_ 0210 << "in collection" << m_coll->title(); 0211 myDebug() << "not adding" << value_; 0212 return false; 0213 } 0214 0215 const bool wasDifferent = field(name_) != value_; 0216 const bool res = setFieldImpl(name_, value_); 0217 // returning true means entry was successfully modified 0218 if(res && wasDifferent && updateMDate_ && 0219 name_ != QLatin1String("mdate") && m_coll->hasField(QStringLiteral("mdate"))) { 0220 setFieldImpl(QStringLiteral("mdate"), QDate::currentDate().toString(Qt::ISODate)); 0221 } 0222 return res; 0223 } 0224 0225 bool Entry::setFieldImpl(const QString& name_, const QString& value_) { 0226 // an empty value means remove the field 0227 if(value_.isEmpty()) { 0228 if(m_fieldValues.remove(name_)) { 0229 invalidateFormattedFieldValue(name_); 0230 } 0231 return true; 0232 } 0233 0234 if(m_coll && !m_coll->isAllowed(name_, value_)) { 0235 myDebug() << "for" << name_ << ", value is not allowed -" << value_; 0236 return false; 0237 } 0238 0239 Data::FieldPtr f = m_coll->fieldByName(name_); 0240 if(!f) { 0241 return false; 0242 } 0243 0244 // the string store is probable only useful for fields with auto-completion or choice/number/bool 0245 bool shareType = f->type() == Field::Choice || 0246 f->type() == Field::Bool || 0247 f->type() == Field::Image || 0248 f->type() == Field::Rating || 0249 f->type() == Field::Number; 0250 if(!(f->hasFlag(Field::AllowMultiple)) && 0251 (shareType || 0252 (f->type() == Field::Line && (f->flags() & Field::AllowCompletion)))) { 0253 m_fieldValues.insert(Tellico::shareString(name_), Tellico::shareString(value_)); 0254 } else { 0255 m_fieldValues.insert(Tellico::shareString(name_), value_); 0256 } 0257 invalidateFormattedFieldValue(name_); 0258 return true; 0259 } 0260 0261 bool Entry::addToGroup(EntryGroup* group_) { 0262 if(!group_ || m_groups.contains(group_)) { 0263 return false; 0264 } 0265 0266 m_groups.push_back(group_); 0267 group_->append(EntryPtr(this)); 0268 return true; 0269 } 0270 0271 bool Entry::removeFromGroup(EntryGroup* group_) { 0272 // if the removal isn't successful, just return 0273 bool success = m_groups.removeOne(group_); 0274 success = group_->removeOne(EntryPtr(this)) && success; 0275 // myDebug() << "removing from group - " << group_->fieldName() << "--" << group_->groupName(); 0276 if(!success) { 0277 myDebug() << "failed!"; 0278 } 0279 return success; 0280 } 0281 0282 void Entry::clearGroups() { 0283 m_groups.clear(); 0284 } 0285 0286 // this function gets called before m_groups is updated. In fact, it is used to 0287 // update that list. This is the function that actually parses the field values 0288 // and returns the list of the group names. 0289 QStringList Entry::groupNamesByFieldName(const QString& fieldName_) const { 0290 // myDebug() << fieldName_; 0291 FieldPtr f = m_coll->fieldByName(fieldName_); 0292 if(!f) { 0293 myWarning() << "no field named" << fieldName_; 0294 return QStringList(); 0295 } 0296 0297 StringSet groups; 0298 // check table before multiple since tables are always multiple 0299 if(f->type() == Field::Table) { 0300 // we only take groups from the first column 0301 foreach(const QString& row, FieldFormat::splitTable(field(f))) { 0302 const QStringList columns = FieldFormat::splitRow(row); 0303 const QStringList values = columns.isEmpty() ? QStringList() : FieldFormat::splitValue(columns.at(0)); 0304 foreach(const QString& value, values) { 0305 groups.add(FieldFormat::format(value, f->formatType(), FieldFormat::DefaultFormat)); 0306 } 0307 } 0308 } else if(f->hasFlag(Field::AllowMultiple)) { 0309 // use a string split instead of regexp split, since we've already enforced the space after the semi-comma 0310 groups.add(FieldFormat::splitValue(formattedField(f), FieldFormat::StringSplit)); 0311 } else { 0312 groups.add(formattedField(f)); 0313 } 0314 0315 // possible to be empty for no value 0316 // but we want to populate an empty group 0317 return groups.isEmpty() ? QStringList(QString()) : groups.values(); 0318 } 0319 0320 bool Entry::isOwned() { 0321 return (m_coll && m_id > -1 && m_coll->entryCount() > 0 && m_coll->entries().contains(EntryPtr(this))); 0322 } 0323 0324 // an empty string means invalidate all 0325 void Entry::invalidateFormattedFieldValue(const QString& name_) { 0326 if(name_.isEmpty()) { 0327 m_formattedFields.clear(); 0328 } else if(!m_formattedFields.isEmpty()) { 0329 m_formattedFields.remove(name_); 0330 } 0331 }