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 }