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 }