1 /***************************************************************************
2 Copyright (C) 2001-2009 Robby Stephenson <robby@periapsis.org>
3 ***************************************************************************/
4
5 /***************************************************************************
6 * *
7 * This program is free software; you can redistribute it and/or *
8 * modify it under the terms of the GNU General Public License as *
9 * published by the Free Software Foundation; either version 2 of *
10 * the License or (at your option) version 3 or any later version *
11 * accepted by the membership of KDE e.V. (or its successor approved *
12 * by the membership of KDE e.V.), which shall act as a proxy *
13 * defined in Section 14 of version 3 of the license. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
22 * *
23 ***************************************************************************/
24
25 #include "entry.h"
26 #include "entrygroup.h"
27 #include "collection.h"
28 #include "field.h"
29 #include "derivedvalue.h"
30 #include "utils/string_utils.h"
31 #include "utils/stringset.h"
32 #include "tellico_debug.h"
33
34 #include <KLocalizedString>
35
36 using namespace Tellico;
37 using namespace Tellico::Data;
38 using Tellico::Data::Entry;
39
Entry(Tellico::Data::CollPtr coll_)40 Entry::Entry(Tellico::Data::CollPtr coll_) : QSharedData(), m_coll(coll_), m_id(-1) {
41 #ifndef NDEBUG
42 if(!coll_) {
43 myWarning() << "null collection pointer!";
44 }
45 #endif
46 }
47
Entry(Tellico::Data::CollPtr coll_,Data::ID id_)48 Entry::Entry(Tellico::Data::CollPtr coll_, Data::ID id_) : QSharedData(), m_coll(coll_), m_id(id_) {
49 #ifndef NDEBUG
50 if(!coll_) {
51 myWarning() << "null collection pointer!";
52 }
53 #endif
54 }
55
Entry(const Entry & entry_)56 Entry::Entry(const Entry& entry_) :
57 QSharedData(entry_),
58 m_coll(entry_.m_coll),
59 m_id(-1),
60 m_fieldValues(entry_.m_fieldValues),
61 m_formattedFields(entry_.m_formattedFields) {
62 // special case for creation date since it gets set in Collection::addEntry IF cdate is empty
63 m_fieldValues.remove(QStringLiteral("cdate"));
64 m_fieldValues.remove(QStringLiteral("mdate"));
65 }
66
operator =(const Entry & other_)67 Entry& Entry::operator=(const Entry& other_) {
68 if(this == &other_) return *this;
69
70 // static_cast<QSharedData&>(*this) = static_cast<const QSharedData&>(other_);
71 m_coll = other_.m_coll;
72 m_id = other_.m_id;
73 m_fieldValues = other_.m_fieldValues;
74 // special case for creation date field which gets set in Collection::addEntry IF cdate is empty
75 m_fieldValues.remove(QStringLiteral("cdate"));
76 m_fieldValues.remove(QStringLiteral("mdate"));
77 m_formattedFields = other_.m_formattedFields;
78 return *this;
79 }
80
~Entry()81 Entry::~Entry() {
82 }
83
collection() const84 Tellico::Data::CollPtr Entry::collection() const {
85 return m_coll;
86 }
87
setCollection(Tellico::Data::CollPtr coll_)88 void Entry::setCollection(Tellico::Data::CollPtr coll_) {
89 if(coll_ == m_coll) {
90 // myDebug() << "already belongs to collection!";
91 return;
92 }
93 // special case adding a book to a bibtex collection
94 // it would be better to do this in a real object-oriented way, but this should work
95 const bool addEntryType = m_coll->type() == Collection::Book &&
96 coll_->type() == Collection::Bibtex &&
97 !m_coll->hasField(QStringLiteral("entry-type"));
98 m_coll = coll_;
99 m_id = -1;
100 // set this after changing the m_coll pointer since setField() checks field validity
101 if(addEntryType) {
102 setField(QStringLiteral("entry-type"), QStringLiteral("book"));
103 }
104 }
105
title() const106 QString Entry::title() const {
107 return field(QStringLiteral("title"));
108 }
109
field(const QString & fieldName_) const110 QString Entry::field(const QString& fieldName_) const {
111 return field(m_coll->fieldByName(fieldName_));
112 }
113
field(Tellico::Data::FieldPtr field_) const114 QString Entry::field(Tellico::Data::FieldPtr field_) const {
115 if(!field_) {
116 return QString();
117 }
118
119 if(field_->hasFlag(Field::Derived)) {
120 DerivedValue dv(field_);
121 return dv.value(EntryPtr(const_cast<Entry*>(this)), false);
122 }
123
124 return m_fieldValues.value(field_->name());
125 }
126
formattedField(const QString & fieldName_,FieldFormat::Request request_) const127 QString Entry::formattedField(const QString& fieldName_, FieldFormat::Request request_) const {
128 return formattedField(m_coll->fieldByName(fieldName_), request_);
129 }
130
formattedField(Tellico::Data::FieldPtr field_,FieldFormat::Request request_) const131 QString Entry::formattedField(Tellico::Data::FieldPtr field_, FieldFormat::Request request_) const {
132 if(!field_) {
133 return QString();
134 }
135
136 // don't format the value unless it's requested to do so
137 if(request_ == FieldFormat::AsIsFormat) {
138 return field(field_);
139 }
140
141 const FieldFormat::Type flag = field_->formatType();
142 if(field_->hasFlag(Field::Derived)) {
143 DerivedValue dv(field_);
144 // format sub fields and whole string
145 return FieldFormat::format(dv.value(EntryPtr(const_cast<Entry*>(this)), true), flag, request_);
146 }
147
148 // if auto format is not set or FormatNone, then just return the value
149 if(flag == FieldFormat::FormatNone) {
150 return m_coll->prepareText(field(field_));
151 }
152
153 if(!m_formattedFields.contains(field_->name())) {
154 QString formattedValue;
155 if(field_->type() == Field::Table) {
156 QStringList rows;
157 // we only format the first column
158 foreach(const QString& row, FieldFormat::splitTable(field(field_))) {
159 QStringList columns = FieldFormat::splitRow(row);
160 QStringList newValues;
161 if(!columns.isEmpty()) {
162 foreach(const QString& value, FieldFormat::splitValue(columns.at(0))) {
163 newValues << FieldFormat::format(value, field_->formatType(), FieldFormat::DefaultFormat);
164 }
165 columns.replace(0, newValues.join(FieldFormat::delimiterString()));
166 }
167 rows << columns.join(FieldFormat::columnDelimiterString());
168 }
169 formattedValue = rows.join(FieldFormat::rowDelimiterString());
170 } else {
171 QStringList values;
172 if(field_->hasFlag(Field::AllowMultiple)) {
173 values = FieldFormat::splitValue(field(field_));
174 } else {
175 values << field(field_);
176 }
177 QStringList formattedValues;
178 foreach(const QString& value, values) {
179 formattedValues << FieldFormat::format(m_coll->prepareText(value), flag, request_);
180 }
181 formattedValue = formattedValues.join(FieldFormat::delimiterString());
182 }
183 if(!formattedValue.isEmpty()) {
184 m_formattedFields.insert(field_->name(), Tellico::shareString(formattedValue));
185 }
186 return formattedValue;
187 }
188 // otherwise, just look it up
189 return m_formattedFields.value(field_->name());
190 }
191
setField(Tellico::Data::FieldPtr field_,const QString & value_,bool updateMDate_)192 bool Entry::setField(Tellico::Data::FieldPtr field_, const QString& value_, bool updateMDate_) {
193 return setField(field_->name(), value_, updateMDate_);
194 }
195
196 // updating the modified date of the entry is expensive with the call to QDate::currentDate
197 // when loading a collection from a file (in particular), it's faster to ignore that date
setField(const QString & name_,const QString & value_,bool updateMDate_)198 bool Entry::setField(const QString& name_, const QString& value_, bool updateMDate_) {
199 if(name_.isEmpty()) {
200 myWarning() << "empty field name for value:" << value_;
201 return false;
202 }
203
204 if(m_coll->fields().isEmpty()) {
205 myDebug() << "collection has no fields, can't add -" << name_;
206 return false;
207 }
208
209 if(!m_coll->hasField(name_)) {
210 myDebug() << "unknown collection entry field -" << name_
211 << "in collection" << m_coll->title();
212 myDebug() << "not adding" << value_;
213 return false;
214 }
215
216 const bool wasDifferent = field(name_) != value_;
217 const bool res = setFieldImpl(name_, value_);
218 // returning true means entry was successfully modified
219 if(res && wasDifferent && updateMDate_ &&
220 name_ != QLatin1String("mdate") && m_coll->hasField(QStringLiteral("mdate"))) {
221 setFieldImpl(QStringLiteral("mdate"), QDate::currentDate().toString(Qt::ISODate));
222 }
223 return res;
224 }
225
setFieldImpl(const QString & name_,const QString & value_)226 bool Entry::setFieldImpl(const QString& name_, const QString& value_) {
227 // an empty value means remove the field
228 if(value_.isEmpty()) {
229 if(m_fieldValues.remove(name_)) {
230 invalidateFormattedFieldValue(name_);
231 }
232 return true;
233 }
234
235 if(m_coll && !m_coll->isAllowed(name_, value_)) {
236 myDebug() << "for" << name_ << ", value is not allowed -" << value_;
237 return false;
238 }
239
240 Data::FieldPtr f = m_coll->fieldByName(name_);
241 if(!f) {
242 return false;
243 }
244
245 // the string store is probable only useful for fields with auto-completion or choice/number/bool
246 bool shareType = f->type() == Field::Choice ||
247 f->type() == Field::Bool ||
248 f->type() == Field::Image ||
249 f->type() == Field::Rating ||
250 f->type() == Field::Number;
251 if(!(f->hasFlag(Field::AllowMultiple)) &&
252 (shareType ||
253 (f->type() == Field::Line && (f->flags() & Field::AllowCompletion)))) {
254 m_fieldValues.insert(Tellico::shareString(name_), Tellico::shareString(value_));
255 } else {
256 m_fieldValues.insert(Tellico::shareString(name_), value_);
257 }
258 invalidateFormattedFieldValue(name_);
259 return true;
260 }
261
addToGroup(EntryGroup * group_)262 bool Entry::addToGroup(EntryGroup* group_) {
263 if(!group_ || m_groups.contains(group_)) {
264 return false;
265 }
266
267 m_groups.push_back(group_);
268 group_->append(EntryPtr(this));
269 return true;
270 }
271
removeFromGroup(EntryGroup * group_)272 bool Entry::removeFromGroup(EntryGroup* group_) {
273 // if the removal isn't successful, just return
274 bool success = m_groups.removeOne(group_);
275 success = group_->removeOne(EntryPtr(this)) && success;
276 // myDebug() << "removing from group - " << group_->fieldName() << "--" << group_->groupName();
277 if(!success) {
278 myDebug() << "failed!";
279 }
280 return success;
281 }
282
clearGroups()283 void Entry::clearGroups() {
284 m_groups.clear();
285 }
286
287 // this function gets called before m_groups is updated. In fact, it is used to
288 // update that list. This is the function that actually parses the field values
289 // and returns the list of the group names.
groupNamesByFieldName(const QString & fieldName_) const290 QStringList Entry::groupNamesByFieldName(const QString& fieldName_) const {
291 // myDebug() << fieldName_;
292 FieldPtr f = m_coll->fieldByName(fieldName_);
293 if(!f) {
294 myWarning() << "no field named" << fieldName_;
295 return QStringList();
296 }
297
298 StringSet groups;
299 // check table before multiple since tables are always multiple
300 if(f->type() == Field::Table) {
301 // we only take groups from the first column
302 foreach(const QString& row, FieldFormat::splitTable(field(f))) {
303 const QStringList columns = FieldFormat::splitRow(row);
304 const QStringList values = columns.isEmpty() ? QStringList() : FieldFormat::splitValue(columns.at(0));
305 foreach(const QString& value, values) {
306 groups.add(FieldFormat::format(value, f->formatType(), FieldFormat::DefaultFormat));
307 }
308 }
309 } else if(f->hasFlag(Field::AllowMultiple)) {
310 // use a string split instead of regexp split, since we've already enforced the space after the semi-comma
311 groups.add(FieldFormat::splitValue(formattedField(f), FieldFormat::StringSplit));
312 } else {
313 groups.add(formattedField(f));
314 }
315
316 // possible to be empty for no value
317 // but we want to populate an empty group
318 return groups.isEmpty() ? QStringList(QString()) : groups.values();
319 }
320
isOwned()321 bool Entry::isOwned() {
322 return (m_coll && m_id > -1 && m_coll->entryCount() > 0 && m_coll->entries().contains(EntryPtr(this)));
323 }
324
325 // an empty string means invalidate all
invalidateFormattedFieldValue(const QString & name_)326 void Entry::invalidateFormattedFieldValue(const QString& name_) {
327 if(name_.isEmpty()) {
328 m_formattedFields.clear();
329 } else if(!m_formattedFields.isEmpty()) {
330 m_formattedFields.remove(name_);
331 }
332 }
333