summaryrefslogtreecommitdiffstats
path: root/src/entry.cpp
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 19:17:32 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-03-01 19:17:32 +0000
commite38d2351b83fa65c66ccde443777647ef5cb6cff (patch)
tree1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/entry.cpp
downloadtellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.tar.gz
tellico-e38d2351b83fa65c66ccde443777647ef5cb6cff.zip
Added KDE3 version of Tellico
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/tellico@1097620 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'src/entry.cpp')
-rw-r--r--src/entry.cpp466
1 files changed, 466 insertions, 0 deletions
diff --git a/src/entry.cpp b/src/entry.cpp
new file mode 100644
index 0000000..a558ba2
--- /dev/null
+++ b/src/entry.cpp
@@ -0,0 +1,466 @@
+/***************************************************************************
+ copyright : (C) 2001-2006 by Robby Stephenson
+ email : robby@periapsis.org
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of version 2 of the GNU General Public License as *
+ * published by the Free Software Foundation; *
+ * *
+ ***************************************************************************/
+
+#include "entry.h"
+#include "collection.h"
+#include "field.h"
+#include "translators/bibtexhandler.h" // needed for BibtexHandler::cleanText()
+#include "document.h"
+#include "tellico_debug.h"
+#include "tellico_utils.h"
+#include "tellico_debug.h"
+#include "latin1literal.h"
+#include "../isbnvalidator.h"
+#include "../lccnvalidator.h"
+
+#include <klocale.h>
+
+#include <qregexp.h>
+
+using Tellico::Data::Entry;
+using Tellico::Data::EntryGroup;
+
+EntryGroup::EntryGroup(const QString& group, const QString& field)
+ : QObject(), EntryVec(), m_group(Tellico::shareString(group)), m_field(Tellico::shareString(field)) {
+}
+
+EntryGroup::~EntryGroup() {
+ // need a copy since we remove ourselves
+ EntryVec vec = *this;
+ for(Data::EntryVecIt entry = vec.begin(); entry != vec.end(); ++entry) {
+ entry->removeFromGroup(this);
+ }
+}
+
+bool Entry::operator==(const Entry& e1) {
+// special case for file catalog, just check the url
+ if(m_coll && m_coll->type() == Collection::File &&
+ e1.m_coll && e1.m_coll->type() == Collection::File) {
+ // don't forget case where both could have empty urls
+ // but different values for other fields
+ QString u = field(QString::fromLatin1("url"));
+ if(!u.isEmpty()) {
+ // versions before 1.2.7 could have saved the url without the protocol
+ bool b = KURL::fromPathOrURL(u) == KURL::fromPathOrURL(e1.field(QString::fromLatin1("url")));
+ if(b) {
+ return true;
+ } else {
+ Data::FieldPtr f = m_coll->fieldByName(QString::fromLatin1("url"));
+ if(f && f->property(QString::fromLatin1("relative")) == Latin1Literal("true")) {
+ return KURL(Document::self()->URL(), u) == KURL::fromPathOrURL(e1.field(QString::fromLatin1("url")));
+ }
+ }
+ }
+ }
+ if(e1.m_fields.count() != m_fields.count()) {
+ return false;
+ }
+ for(StringMap::ConstIterator it = e1.m_fields.begin(); it != e1.m_fields.end(); ++it) {
+ if(!m_fields.contains(it.key()) || m_fields[it.key()] != it.data()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+Entry::Entry(CollPtr coll_) : KShared(), m_coll(coll_), m_id(-1) {
+#ifndef NDEBUG
+ if(!coll_) {
+ kdWarning() << "Entry() - null collection pointer!" << endl;
+ }
+#endif
+}
+
+Entry::Entry(CollPtr coll_, int id_) : KShared(), m_coll(coll_), m_id(id_) {
+#ifndef NDEBUG
+ if(!coll_) {
+ kdWarning() << "Entry() - null collection pointer!" << endl;
+ }
+#endif
+}
+
+Entry::Entry(const Entry& entry_) :
+ KShared(entry_),
+ m_coll(entry_.m_coll),
+ m_id(-1),
+ m_fields(entry_.m_fields),
+ m_formattedFields(entry_.m_formattedFields) {
+}
+
+Entry& Entry::operator=(const Entry& other_) {
+ if(this == &other_) return *this;
+
+// myDebug() << "Entry::operator=()" << endl;
+ static_cast<KShared&>(*this) = static_cast<const KShared&>(other_);
+ m_coll = other_.m_coll;
+ m_id = other_.m_id;
+ m_fields = other_.m_fields;
+ m_formattedFields = other_.m_formattedFields;
+ return *this;
+}
+
+Entry::~Entry() {
+}
+
+Tellico::Data::CollPtr Entry::collection() const {
+ return m_coll;
+}
+
+void Entry::setCollection(CollPtr coll_) {
+ if(coll_ == m_coll) {
+ myDebug() << "Entry::setCollection() - already belongs to collection!" << endl;
+ return;
+ }
+ // special case adding a book to a bibtex collection
+ // it would be better to do this in a real OOO way, but this should work
+ const bool addEntryType = m_coll->type() == Collection::Book &&
+ coll_->type() == Collection::Bibtex &&
+ !m_coll->hasField(QString::fromLatin1("entry-type"));
+ m_coll = coll_;
+ m_id = -1;
+ // set this after changing the m_coll pointer since setField() checks field validity
+ if(addEntryType) {
+ setField(QString::fromLatin1("entry-type"), QString::fromLatin1("book"));
+ }
+}
+
+QString Entry::title() const {
+ return formattedField(QString::fromLatin1("title"));
+}
+
+QString Entry::field(Data::FieldPtr field_, bool formatted_/*=false*/) const {
+ return field(field_->name(), formatted_);
+}
+
+QString Entry::field(const QString& fieldName_, bool formatted_/*=false*/) const {
+ if(formatted_) {
+ return formattedField(fieldName_);
+ }
+
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+ if(!f) {
+ return QString::null;
+ }
+ if(f->type() == Field::Dependent) {
+ return dependentValue(this, f->description(), false);
+ }
+
+ if(!m_fields.isEmpty() && m_fields.contains(fieldName_)) {
+ return m_fields[fieldName_];
+ }
+ return QString::null;
+}
+
+QString Entry::formattedField(Data::FieldPtr field_) const {
+ return formattedField(field_->name());
+}
+
+QString Entry::formattedField(const QString& fieldName_) const {
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+ if(!f) {
+ return QString::null;
+ }
+
+ Field::FormatFlag flag = f->formatFlag();
+ if(f->type() == Field::Dependent) {
+ if(flag == Field::FormatNone) {
+ return dependentValue(this, f->description(), false);
+ } else {
+ // format sub fields and whole string
+ return Field::format(dependentValue(this, f->description(), true), flag);
+ }
+ }
+
+ // if auto format is not set or FormatNone, then just return the value
+ if(flag == Field::FormatNone) {
+ return field(fieldName_);
+ }
+
+ if(m_formattedFields.isEmpty() || !m_formattedFields.contains(fieldName_)) {
+ QString value = field(fieldName_);
+ if(!value.isEmpty()) {
+ // special for Bibtex collections
+ if(m_coll->type() == Collection::Bibtex) {
+ BibtexHandler::cleanText(value);
+ }
+ value = Field::format(value, flag);
+ m_formattedFields.insert(fieldName_, value);
+ }
+ return value;
+ }
+ // otherwise, just look it up
+ return m_formattedFields[fieldName_];
+}
+
+QStringList Entry::fields(Data::FieldPtr field_, bool formatted_) const {
+ return fields(field_->name(), formatted_);
+}
+
+QStringList Entry::fields(const QString& field_, bool formatted_) const {
+ QString s = formatted_ ? formattedField(field_) : field(field_);
+ if(s.isEmpty()) {
+ return QStringList();
+ }
+ return Field::split(s, true);
+}
+
+bool Entry::setField(Data::FieldPtr field_, const QString& value_) {
+ return setField(field_->name(), value_);
+}
+
+bool Entry::setField(const QString& name_, const QString& value_) {
+ if(name_.isEmpty()) {
+ kdWarning() << "Entry::setField() - empty field name for value: " << value_ << endl;
+ return false;
+ }
+ // an empty value means remove the field
+ if(value_.isEmpty()) {
+ if(!m_fields.isEmpty() && m_fields.contains(name_)) {
+ m_fields.remove(name_);
+ }
+ invalidateFormattedFieldValue(name_);
+ return true;
+ }
+
+#ifndef NDEBUG
+ if(m_coll && (m_coll->fields().count() == 0 || !m_coll->hasField(name_))) {
+ myDebug() << "Entry::setField() - unknown collection entry field - "
+ << name_ << endl;
+ return false;
+ }
+#endif
+
+ if(m_coll && !m_coll->isAllowed(name_, value_)) {
+ myDebug() << "Entry::setField() - for " << name_
+ << ", value is not allowed - " << value_ << endl;
+ return false;
+ }
+
+ Data::FieldPtr f = m_coll->fieldByName(name_);
+ if(!f) {
+ return false;
+ }
+
+ // the string store is probable only useful for fields with auto-completion or choice/number/bool
+ if(!(f->flags() & Field::AllowMultiple) &&
+ ((f->type() == Field::Choice || f->type() == Field::Bool || f->type() == Field::Number) ||
+ (f->type() == Field::Line && (f->flags() & Field::AllowCompletion)))) {
+ m_fields.insert(Tellico::shareString(name_), Tellico::shareString(value_));
+ } else {
+ m_fields.insert(Tellico::shareString(name_), value_);
+ }
+ invalidateFormattedFieldValue(name_);
+ return true;
+}
+
+bool Entry::addToGroup(EntryGroup* group_) {
+ if(!group_ || m_groups.contains(group_)) {
+ return false;
+ }
+
+ m_groups.push_back(group_);
+ group_->append(this);
+// m_coll->groupModified(group_);
+ return true;
+}
+
+bool Entry::removeFromGroup(EntryGroup* group_) {
+ // if the removal isn't successful, just return
+ bool success = m_groups.remove(group_);
+ success = success && group_->remove(this);
+// myDebug() << "Entry::removeFromGroup() - removing from group - "
+// << group_->fieldName() << "::" << group_->groupName() << endl;
+ if(success) {
+// m_coll->groupModified(group_);
+ } else {
+ myDebug() << "Entry::removeFromGroup() failed! " << endl;
+ }
+ return success;
+}
+
+void Entry::clearGroups() {
+ m_groups.clear();
+}
+
+// this function gets called before m_groups is updated. In fact, it is used to
+// update that list. This is the function that actually parses the field values
+// and returns the list of the group names.
+QStringList Entry::groupNamesByFieldName(const QString& fieldName_) const {
+// myDebug() << "Entry::groupsByfieldName() - " << fieldName_ << endl;
+ FieldPtr f = m_coll->fieldByName(fieldName_);
+
+ // easy if not allowing multiple values
+ if(!(f->flags() & Field::AllowMultiple)) {
+ QString value = formattedField(fieldName_);
+ if(value.isEmpty()) {
+ return i18n(Collection::s_emptyGroupTitle);
+ } else {
+ return value;
+ }
+ }
+
+ QStringList groups = fields(fieldName_, true);
+ if(groups.isEmpty()) {
+ return i18n(Collection::s_emptyGroupTitle);
+ } else if(f->type() == Field::Table) {
+ // quick hack for tables, how often will a user have "::" in their value?
+ // only use first column for group
+ QStringList::Iterator it = groups.begin();
+ while(it != groups.end()) {
+ (*it) = (*it).section(QString::fromLatin1("::"), 0, 0);
+ if((*it).isEmpty()) {
+ it = groups.remove(it); // points to next in list
+ } else {
+ ++it;
+ }
+ }
+ }
+ return groups;
+}
+
+bool Entry::isOwned() {
+ return (m_coll && m_id > -1 && m_coll->entryCount() > 0 && m_coll->entries().contains(this));
+}
+
+// a null string means invalidate all
+void Entry::invalidateFormattedFieldValue(const QString& name_) {
+ if(name_.isNull()) {
+ m_formattedFields.clear();
+ } else if(!m_formattedFields.isEmpty() && m_formattedFields.contains(name_)) {
+ m_formattedFields.remove(name_);
+ }
+}
+
+// format is something like "%{year} %{author}"
+QString Entry::dependentValue(ConstEntryPtr entry_, const QString& format_, bool formatted_) {
+ if(!entry_) {
+ return format_;
+ }
+
+ QString result, fieldName;
+ FieldPtr field;
+
+ int endPos;
+ int curPos = 0;
+ int pctPos = format_.find('%', curPos);
+ while(pctPos != -1 && pctPos+1 < static_cast<int>(format_.length())) {
+ if(format_[pctPos+1] == '{') {
+ endPos = format_.find('}', pctPos+2);
+ if(endPos > -1) {
+ result += format_.mid(curPos, pctPos-curPos);
+ fieldName = format_.mid(pctPos+2, endPos-pctPos-2);
+ field = entry_->collection()->fieldByName(fieldName);
+ if(!field) {
+ // allow the user to also use field titles
+ field = entry_->collection()->fieldByTitle(fieldName);
+ }
+ if(field) {
+ // don't format, just capitalize
+ result += entry_->field(field, formatted_);
+ } else if(fieldName == Latin1Literal("id")) {
+ result += QString::number(entry_->id());
+ } else {
+ result += format_.mid(pctPos, endPos-pctPos+1);
+ }
+ curPos = endPos+1;
+ } else {
+ break;
+ }
+ } else {
+ result += format_.mid(curPos, pctPos-curPos+1);
+ curPos = pctPos+1;
+ }
+ pctPos = format_.find('%', curPos);
+ }
+ result += format_.mid(curPos, format_.length()-curPos);
+// myDebug() << "Entry::dependentValue() - " << format_ << " = " << result << endl;
+ // sometimes field value might empty, resulting in multiple consecutive white spaces
+ // so let's simplify that...
+ return result.simplifyWhiteSpace();
+}
+
+int Entry::compareValues(EntryPtr e1, EntryPtr e2, const QString& f, ConstCollPtr c) {
+ return compareValues(e1, e2, c->fieldByName(f));
+}
+
+int Entry::compareValues(EntryPtr e1, EntryPtr e2, FieldPtr f) {
+ if(!e1 || !e2 || !f) {
+ return 0;
+ }
+ QString s1 = e1->field(f).lower();
+ QString s2 = e2->field(f).lower();
+ if(s1.isEmpty() || s2.isEmpty()) {
+ return 0;
+ }
+ // complicated string matching, here are the cases I want to match
+ // "bend it like beckham" == "bend it like beckham (widescreen edition)"
+ // "the return of the king" == "return of the king"
+ if(s1 == s2) {
+ return 5;
+ }
+ // special case for isbn
+ if(f->name() == Latin1Literal("isbn") && ISBNValidator::isbn10(s1) == ISBNValidator::isbn10(s2)) {
+ return 5;
+ }
+ if(f->name() == Latin1Literal("lccn") && LCCNValidator::formalize(s1) == LCCNValidator::formalize(s2)) {
+ return 5;
+ }
+ if(f->formatFlag() == Field::FormatName) {
+ s1 = e1->field(f, true).lower();
+ s2 = e2->field(f, true).lower();
+ if(s1 == s2) {
+ return 5;
+ }
+ }
+ // try removing punctuation
+ QRegExp notAlphaNum(QString::fromLatin1("[^\\s\\w]"));
+ QString s1a = s1; s1a.remove(notAlphaNum);
+ QString s2a = s2; s2a.remove(notAlphaNum);
+ if(!s1a.isEmpty() && s1a == s2a) {
+// myDebug() << "match without punctuation" << endl;
+ return 5;
+ }
+ Field::stripArticles(s1);
+ Field::stripArticles(s2);
+ if(!s1.isEmpty() && s1 == s2) {
+// myDebug() << "match without articles" << endl;
+ return 3;
+ }
+ // try removing everything between parentheses
+ QRegExp rx(QString::fromLatin1("\\s*\\(.*\\)\\s*"));
+ s1.remove(rx);
+ s2.remove(rx);
+ if(!s1.isEmpty() && s1 == s2) {
+// myDebug() << "match without parentheses" << endl;
+ return 2;
+ }
+ if(f->flags() & Field::AllowMultiple) {
+ QStringList sl1 = e1->fields(f, false);
+ QStringList sl2 = e2->fields(f, false);
+ int matches = 0;
+ for(QStringList::ConstIterator it = sl1.begin(); it != sl1.end(); ++it) {
+ matches += sl2.contains(*it);
+ }
+ if(matches == 0 && f->formatFlag() == Field::FormatName) {
+ sl1 = e1->fields(f, true);
+ sl2 = e2->fields(f, true);
+ for(QStringList::ConstIterator it = sl1.begin(); it != sl1.end(); ++it) {
+ matches += sl2.contains(*it);
+ }
+ }
+ return matches;
+ }
+ return 0;
+}
+
+#include "entry.moc"