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