diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-03-01 19:17:32 +0000 |
commit | e38d2351b83fa65c66ccde443777647ef5cb6cff (patch) | |
tree | 1897fc20e9f73a81c520a5b9f76f8ed042124883 /src/field.cpp | |
download | tellico-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/field.cpp')
-rw-r--r-- | src/field.cpp | 594 |
1 files changed, 594 insertions, 0 deletions
diff --git a/src/field.cpp b/src/field.cpp new file mode 100644 index 0000000..206062a --- /dev/null +++ b/src/field.cpp @@ -0,0 +1,594 @@ +/*************************************************************************** + 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 "field.h" +#include "tellico_utils.h" +#include "latin1literal.h" +#include "tellico_debug.h" +#include "core/tellico_config.h" +#include "collection.h" + +#include <klocale.h> +#include <kglobal.h> + +#include <qstringlist.h> +#include <qregexp.h> +#include <qdatetime.h> + +namespace { + static const QRegExp comma_split = QRegExp(QString::fromLatin1("\\s*,\\s*")); +} + +using Tellico::Data::Field; + +//these get overwritten, but are here since they're static +QStringList Field::s_articlesApos; +QRegExp Field::s_delimiter = QRegExp(QString::fromLatin1("\\s*;\\s*")); + +// this constructor is for anything but Choice type +Field::Field(const QString& name_, const QString& title_, Type type_/*=Line*/) + : KShared(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_), + m_type(type_), m_flags(0), m_formatFlag(FormatNone) { + +#ifndef NDEBUG + if(m_type == Choice) { + kdWarning() << "Field() - A different constructor should be called for multiple choice attributes." << endl; + kdWarning() << "Constructing a Field with name = " << name_ << endl; + } +#endif + // a paragraph's category is always its title, along with tables + if(isSingleCategory()) { + m_category = m_title; + } + if(m_type == Table || m_type == Table2) { + m_flags = AllowMultiple; + if(m_type == Table2) { + m_type = Table; + setProperty(QString::fromLatin1("columns"), QChar('2')); + } else { + setProperty(QString::fromLatin1("columns"), QChar('1')); + } + } else if(m_type == Date) { // hidden from user + m_formatFlag = FormatDate; + } else if(m_type == Rating) { + setProperty(QString::fromLatin1("minimum"), QChar('1')); + setProperty(QString::fromLatin1("maximum"), QChar('5')); + } + m_id = getID(); +} + +// if this constructor is called, the type is necessarily Choice +Field::Field(const QString& name_, const QString& title_, const QStringList& allowed_) + : KShared(), m_name(name_), m_title(title_), m_category(i18n("General")), m_desc(title_), + m_type(Field::Choice), m_allowed(allowed_), m_flags(0), m_formatFlag(FormatNone) { + m_id = getID(); +} + +Field::Field(const Field& field_) + : KShared(field_), m_name(field_.name()), m_title(field_.title()), m_category(field_.category()), + m_desc(field_.description()), m_type(field_.type()), + m_flags(field_.flags()), m_formatFlag(field_.formatFlag()), + m_properties(field_.propertyList()) { + if(m_type == Choice) { + m_allowed = field_.allowed(); + } else if(m_type == Table2) { + m_type = Table; + setProperty(QString::fromLatin1("columns"), QChar('2')); + } + m_id = getID(); +} + +Field& Field::operator=(const Field& field_) { + if(this == &field_) return *this; + + static_cast<KShared&>(*this) = static_cast<const KShared&>(field_); + m_name = field_.name(); + m_title = field_.title(); + m_category = field_.category(); + m_desc = field_.description(); + m_type = field_.type(); + if(m_type == Choice) { + m_allowed = field_.allowed(); + } else if(m_type == Table2) { + m_type = Table; + setProperty(QString::fromLatin1("columns"), QChar('2')); + } + m_flags = field_.flags(); + m_formatFlag = field_.formatFlag(); + m_properties = field_.propertyList(); + m_id = getID(); + return *this; +} + +Field::~Field() { +} + +void Field::setTitle(const QString& title_) { + m_title = title_; + if(isSingleCategory()) { + m_category = title_; + } +} + +void Field::setType(Field::Type type_) { + m_type = type_; + if(m_type != Field::Choice) { + m_allowed = QStringList(); + } + if(m_type == Table || m_type == Table2) { + m_flags |= AllowMultiple; + if(m_type == Table2) { + m_type = Table; + setProperty(QString::fromLatin1("columns"), QChar('2')); + } + if(property(QString::fromLatin1("columns")).isEmpty()) { + setProperty(QString::fromLatin1("columns"), QChar('1')); + } + } + if(isSingleCategory()) { + m_category = m_title; + } + // hidden from user + if(type_ == Date) { + m_formatFlag = FormatDate; + } +} + +void Field::setCategory(const QString& category_) { + if(!isSingleCategory()) { + m_category = category_; + } +} + +void Field::setFlags(int flags_) { + // tables always have multiple allowed + if(m_type == Table || m_type == Table2) { + m_flags = AllowMultiple | flags_; + } else { + m_flags = flags_; + } +} + +void Field::setFormatFlag(FormatFlag flag_) { + // Choice and Data fields are not allowed a format + if(m_type != Choice && m_type != Date) { + m_formatFlag = flag_; + } +} + +const QString& Field::defaultValue() const { + return property(QString::fromLatin1("default")); +} + +void Field::setDefaultValue(const QString& value_) { + if(m_type != Choice || m_allowed.findIndex(value_) > -1) { + setProperty(QString::fromLatin1("default"), value_); + } +} + +bool Field::isSingleCategory() const { + return (m_type == Para || m_type == Table || m_type == Table2 || m_type == Image); +} + +// format is something like "%{year} %{author}" +Tellico::Data::FieldVec Field::dependsOn(CollPtr coll_) const { + FieldVec vec; + if(m_type != Dependent || !coll_) { + return vec; + } + + const QStringList fieldNames = dependsOn(); + // do NOT call recursively! + for(QStringList::ConstIterator it = fieldNames.begin(); it != fieldNames.end(); ++it) { + FieldPtr field = coll_->fieldByName(*it); + if(!field) { + // allow the user to also use field titles + field = coll_->fieldByTitle(*it); + } + if(field) { + vec.append(field); + } + } + return vec; +} + +QStringList Field::dependsOn() const { + QStringList list; + if(m_type != Dependent) { + return list; + } + + QRegExp rx(QString::fromLatin1("%\\{(.+)\\}")); + rx.setMinimal(true); + // do NOT call recursively! + for(int pos = m_desc.find(rx); pos > -1; pos = m_desc.find(rx, pos+3)) { + list << rx.cap(1); + } + return list; +} + +QString Field::format(const QString& value_, FormatFlag flag_) { + if(value_.isEmpty()) { + return value_; + } + + QString text; + switch(flag_) { + case FormatTitle: + text = formatTitle(value_); + break; + case FormatName: + text = formatName(value_); + break; + case FormatDate: + text = formatDate(value_); + break; + case FormatPlain: + text = Config::autoCapitalization() ? capitalize(value_) : value_; + break; + default: + text = value_; + break; + } + return text; +} + +QString Field::formatTitle(const QString& title_) { + QString newTitle = title_; + // special case for multi-column tables, assume user never has '::' in a value + const QString colonColon = QString::fromLatin1("::"); + QString tail; + if(newTitle.find(colonColon) > -1) { + tail = colonColon + newTitle.section(colonColon, 1); + newTitle = newTitle.section(colonColon, 0, 0); + } + + if(Config::autoCapitalization()) { + newTitle = capitalize(newTitle); + } + + if(Config::autoFormat()) { + const QString lower = newTitle.lower(); + // TODO if the title has ",the" at the end, put it at the front + const QStringList& articles = Config::articleList(); + for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) { + // assume white space is already stripped + // the articles are already in lower-case + if(lower.startsWith(*it + QChar(' '))) { + QRegExp regexp(QChar('^') + QRegExp::escape(*it) + QString::fromLatin1("\\s*"), false); + // can't just use *it since it's in lower-case + QString article = newTitle.left((*it).length()); + newTitle = newTitle.replace(regexp, QString::null) + .append(QString::fromLatin1(", ")) + .append(article); + break; + } + } + } + + // also, arbitrarily impose rule that a space must follow every comma + newTitle.replace(comma_split, QString::fromLatin1(", ")); + return newTitle + tail; +} + +QString Field::formatName(const QString& name_, bool multiple_/*=true*/) { + static const QRegExp spaceComma(QString::fromLatin1("[\\s,]")); + static const QString colonColon = QString::fromLatin1("::"); + // the ending look-ahead is so that a space is not added at the end + static const QRegExp periodSpace(QString::fromLatin1("\\.\\s*(?=.)")); + + QStringList entries; + if(multiple_) { + // split by semi-colon, optionally preceded or followed by white spacee + entries = QStringList::split(s_delimiter, name_, false); + } else { + entries << name_; + } + + QRegExp lastWord; + lastWord.setCaseSensitive(false); + + QStringList names; + for(QStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) { + QString name = *it; + // special case for 2-column tables, assume user never has '::' in a value + QString tail; + if(name.find(colonColon) > -1) { + tail = colonColon + name.section(colonColon, 1); + name = name.section(colonColon, 0, 0); + } + name.replace(periodSpace, QString::fromLatin1(". ")); + if(Config::autoCapitalization()) { + name = capitalize(name); + } + + // split the name by white space and commas + QStringList words = QStringList::split(spaceComma, name, false); + lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$')); + + // if it contains a comma already and the last word is not a suffix, don't format it + if(!Config::autoFormat() || (name.find(',') > -1 && Config::nameSuffixList().grep(lastWord).isEmpty())) { + // arbitrarily impose rule that no spaces before a comma and + // a single space after every comma + name.replace(comma_split, QString::fromLatin1(", ")); + names << name + tail; + continue; + } + // otherwise split it by white space, move the last word to the front + // but only if there is more than one word + if(words.count() > 1) { + // if the last word is a suffix, it has to be kept with last name + if(Config::nameSuffixList().grep(lastWord).count() > 0) { + words.prepend(words.last().append(QChar(','))); + words.remove(words.fromLast()); + } + + // now move the word + // adding comma here when there had been a suffix is because it was originally split with space or comma + words.prepend(words.last().append(QChar(','))); + words.remove(words.fromLast()); + + // update last word regexp + lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$')); + + // this is probably just something for me, limited to english + while(Config::surnamePrefixList().grep(lastWord).count() > 0) { + words.prepend(words.last()); + words.remove(words.fromLast()); + lastWord.setPattern(QChar('^') + QRegExp::escape(words.last()) + QChar('$')); + } + + names << words.join(QChar(' ')) + tail; + } else { + names << name + tail; + } + } + + return names.join(QString::fromLatin1("; ")); +} + +QString Field::formatDate(const QString& date_) { + // internally, this is "year-month-day" + // any of the three may be empty + // if they're not digits, return the original string + bool empty = true; + // for empty year, use current + // for empty month or date, use 1 + QStringList s = QStringList::split('-', date_, true); + bool ok = true; + int y = s.count() > 0 ? s[0].toInt(&ok) : QDate::currentDate().year(); + if(ok) { + empty = false; + } else { + y = QDate::currentDate().year(); + } + int m = s.count() > 1 ? s[1].toInt(&ok) : 1; + if(ok) { + empty = false; + } else { + m = 1; + } + int d = s.count() > 2 ? s[2].toInt(&ok) : 1; + if(ok) { + empty = false; + } else { + d = 1; + } + // rather use ISO date formatting than locale formatting for now. Primarily, it makes sorting just work. + return empty ? date_ : QDate(y, m, d).toString(Qt::ISODate); + // use short form +// return KGlobal::locale()->formatDate(date, true); +} + +QString Field::capitalize(QString str_) { + // regexp to split words + static const QRegExp rx(QString::fromLatin1("[-\\s,.;]")); + + if(str_.isEmpty()) { + return str_; + } + // first letter is always capitalized + str_.replace(0, 1, str_.at(0).upper()); + + // special case for french words like l'espace + + int pos = str_.find(rx, 1); + int nextPos; + + QRegExp wordRx; + wordRx.setCaseSensitive(false); + + QStringList notCap = Config::noCapitalizationList(); + // don't capitalize the surname prefixes + // does this hold true everywhere other than english? + notCap += Config::surnamePrefixList(); + + QString word = str_.mid(0, pos); + // now check to see if words starts with apostrophe list + for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) { + if(word.lower().startsWith(*it)) { + uint l = (*it).length(); + str_.replace(l, 1, str_.at(l).upper()); + break; + } + } + + while(pos > -1) { + // also need to compare against list of non-capitalized words + nextPos = str_.find(rx, pos+1); + if(nextPos == -1) { + nextPos = str_.length(); + } + word = str_.mid(pos+1, nextPos-pos-1); + bool aposMatch = false; + // now check to see if words starts with apostrophe list + for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) { + if(word.lower().startsWith(*it)) { + uint l = (*it).length(); + str_.replace(pos+l+1, 1, str_.at(pos+l+1).upper()); + aposMatch = true; + break; + } + } + + if(!aposMatch) { + wordRx.setPattern(QChar('^') + QRegExp::escape(word) + QChar('$')); + if(notCap.grep(wordRx).isEmpty() && nextPos-pos > 1) { + str_.replace(pos+1, 1, str_.at(pos+1).upper()); + } + } + + pos = str_.find(rx, pos+1); + } + return str_; +} + +QString Field::sortKeyTitle(const QString& title_) { + const QString lower = title_.lower(); + const QStringList& articles = Config::articleList(); + for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) { + // assume white space is already stripped + // the articles are already in lower-case + if(lower.startsWith(*it + QChar(' '))) { + return title_.mid((*it).length() + 1); + } + } + // check apostrophes, too + for(QStringList::ConstIterator it = s_articlesApos.begin(); it != s_articlesApos.end(); ++it) { + if(lower.startsWith(*it)) { + return title_.mid((*it).length()); + } + } + return title_; +} + +// articles should all be in lower-case +void Field::articlesUpdated() { + const QStringList articles = Config::articleList(); + s_articlesApos.clear(); + for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) { + if((*it).endsWith(QChar('\''))) { + s_articlesApos += (*it); + } + } +} + +// if these are changed, then CollectionFieldsDialog should be checked since it +// checks for equality against some of these strings +Field::FieldMap Field::typeMap() { + FieldMap map; + map[Field::Line] = i18n("Simple Text"); + map[Field::Para] = i18n("Paragraph"); + map[Field::Choice] = i18n("Choice"); + map[Field::Bool] = i18n("Checkbox"); + map[Field::Number] = i18n("Number"); + map[Field::URL] = i18n("URL"); + map[Field::Table] = i18n("Table"); + map[Field::Image] = i18n("Image"); + map[Field::Dependent] = i18n("Dependent"); +// map[Field::ReadOnly] = i18n("Read Only"); + map[Field::Date] = i18n("Date"); + map[Field::Rating] = i18n("Rating"); + return map; +} + +// just for formatting's sake +QStringList Field::typeTitles() { + const FieldMap& map = typeMap(); + QStringList list; + list.append(map[Field::Line]); + list.append(map[Field::Para]); + list.append(map[Field::Choice]); + list.append(map[Field::Bool]); + list.append(map[Field::Number]); + list.append(map[Field::URL]); + list.append(map[Field::Date]); + list.append(map[Field::Table]); + list.append(map[Field::Image]); + list.append(map[Field::Rating]); + list.append(map[Field::Dependent]); + return list; +} + +QStringList Field::split(const QString& string_, bool allowEmpty_) { + return string_.isEmpty() ? QStringList() : QStringList::split(s_delimiter, string_, allowEmpty_); +} + +void Field::addAllowed(const QString& value_) { + if(m_type != Choice) { + return; + } + if(m_allowed.findIndex(value_) == -1) { + m_allowed += value_; + } +} + +void Field::setProperty(const QString& key_, const QString& value_) { + m_properties.insert(key_, value_); +} + +void Field::setPropertyList(const StringMap& props_) { + m_properties = props_; +} + +void Field::convertOldRating(Data::FieldPtr field_) { + if(field_->type() != Data::Field::Choice) { + return; // nothing to do + } + + if(field_->name() != Latin1Literal("rating") + && field_->property(QString::fromLatin1("rating")) != Latin1Literal("true")) { + return; // nothing to do + } + + int min = 10; + int max = 1; + bool ok; + const QStringList& allow = field_->allowed(); + for(QStringList::ConstIterator it = allow.begin(); it != allow.end(); ++it) { + int n = Tellico::toUInt(*it, &ok); + if(!ok) { + return; // no need to convert + } + min = QMIN(min, n); + max = QMAX(max, n); + } + max = QMIN(max, 10); + if(min >= max) { + min = 1; + max = 5; + } + field_->setProperty(QString::fromLatin1("minimum"), QString::number(min)); + field_->setProperty(QString::fromLatin1("maximum"), QString::number(max)); + field_->setProperty(QString::fromLatin1("rating"), QString::null); + field_->setType(Rating); +} + +// static +long Field::getID() { + static long id = 0; + return ++id; +} + +void Field::stripArticles(QString& value) { + const QStringList articles = Config::articleList(); + if(articles.isEmpty()) { + return; + } + for(QStringList::ConstIterator it = articles.begin(); it != articles.end(); ++it) { + QRegExp rx(QString::fromLatin1("\\b") + *it + QString::fromLatin1("\\b")); + value.remove(rx); + } + value = value.stripWhiteSpace(); + value.remove(QRegExp(QString::fromLatin1(",$"))); +} |