You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
593 lines
18 KiB
593 lines
18 KiB
/***************************************************************************
|
|
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 <tdelocale.h>
|
|
#include <tdeglobal.h>
|
|
|
|
#include <tqstringlist.h>
|
|
#include <tqregexp.h>
|
|
#include <tqdatetime.h>
|
|
|
|
namespace {
|
|
static const TQRegExp comma_split = TQRegExp(TQString::fromLatin1("\\s*,\\s*"));
|
|
}
|
|
|
|
using Tellico::Data::Field;
|
|
|
|
//these get overwritten, but are here since they're static
|
|
TQStringList Field::s_articles;
|
|
TQStringList Field::s_articlesApos;
|
|
TQRegExp Field::s_delimiter = TQRegExp(TQString::fromLatin1("\\s*;\\s*"));
|
|
|
|
// this constructor is for anything but Choice type
|
|
Field::Field(const TQString& name_, const TQString& title_, Type type_/*=Line*/)
|
|
: TDEShared(), 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(TQString::fromLatin1("columns"), TQChar('2'));
|
|
} else {
|
|
setProperty(TQString::fromLatin1("columns"), TQChar('1'));
|
|
}
|
|
} else if(m_type == Date) { // hidden from user
|
|
m_formatFlag = FormatDate;
|
|
} else if(m_type == Rating) {
|
|
setProperty(TQString::fromLatin1("minimum"), TQChar('1'));
|
|
setProperty(TQString::fromLatin1("maximum"), TQChar('5'));
|
|
}
|
|
m_id = getID();
|
|
}
|
|
|
|
// if this constructor is called, the type is necessarily Choice
|
|
Field::Field(const TQString& name_, const TQString& title_, const TQStringList& allowed_)
|
|
: TDEShared(), 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_)
|
|
: TDEShared(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(TQString::fromLatin1("columns"), TQChar('2'));
|
|
}
|
|
m_id = getID();
|
|
}
|
|
|
|
Field& Field::operator=(const Field& field_) {
|
|
if(this == &field_) return *this;
|
|
|
|
static_cast<TDEShared&>(*this) = static_cast<const TDEShared&>(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(TQString::fromLatin1("columns"), TQChar('2'));
|
|
}
|
|
m_flags = field_.flags();
|
|
m_formatFlag = field_.formatFlag();
|
|
m_properties = field_.propertyList();
|
|
m_id = getID();
|
|
return *this;
|
|
}
|
|
|
|
Field::~Field() {
|
|
}
|
|
|
|
void Field::setTitle(const TQString& 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 = TQStringList();
|
|
}
|
|
if(m_type == Table || m_type == Table2) {
|
|
m_flags |= AllowMultiple;
|
|
if(m_type == Table2) {
|
|
m_type = Table;
|
|
setProperty(TQString::fromLatin1("columns"), TQChar('2'));
|
|
}
|
|
if(property(TQString::fromLatin1("columns")).isEmpty()) {
|
|
setProperty(TQString::fromLatin1("columns"), TQChar('1'));
|
|
}
|
|
}
|
|
if(isSingleCategory()) {
|
|
m_category = m_title;
|
|
}
|
|
// hidden from user
|
|
if(type_ == Date) {
|
|
m_formatFlag = FormatDate;
|
|
}
|
|
}
|
|
|
|
void Field::setCategory(const TQString& 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 TQString& Field::defaultValue() const {
|
|
return property(TQString::fromLatin1("default"));
|
|
}
|
|
|
|
void Field::setDefaultValue(const TQString& value_) {
|
|
if(value_.isEmpty() || m_type != Choice || m_allowed.findIndex(value_) > -1) {
|
|
setProperty(TQString::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 TQStringList fieldNames = dependsOn();
|
|
// do NOT call recursively!
|
|
for(TQStringList::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;
|
|
}
|
|
|
|
TQStringList Field::dependsOn() const {
|
|
TQStringList list;
|
|
if(m_type != Dependent) {
|
|
return list;
|
|
}
|
|
|
|
TQRegExp rx(TQString::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;
|
|
}
|
|
|
|
TQString Field::format(const TQString& value_, FormatFlag flag_) {
|
|
if(value_.isEmpty()) {
|
|
return value_;
|
|
}
|
|
|
|
TQString 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;
|
|
}
|
|
|
|
TQString Field::formatTitle(const TQString& title_) {
|
|
TQString newTitle = title_;
|
|
// special case for multi-column tables, assume user never has '::' in a value
|
|
const TQString colonColon = TQString::fromLatin1("::");
|
|
TQString 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 TQString lower = newTitle.lower();
|
|
// TODO if the title has ",the" at the end, put it at the front
|
|
for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
|
|
// assume white space is already stripped
|
|
// the articles are already in lower-case
|
|
if(lower.startsWith(*it + TQChar(' '))) {
|
|
TQRegExp regexp(TQChar('^') + TQRegExp::escape(*it) + TQString::fromLatin1("\\s*"), false);
|
|
// can't just use *it since it's in lower-case
|
|
TQString article = newTitle.left((*it).length());
|
|
newTitle = newTitle.replace(regexp, TQString())
|
|
.append(TQString::fromLatin1(", "))
|
|
.append(article);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// also, arbitrarily impose rule that a space must follow every comma
|
|
newTitle.replace(comma_split, TQString::fromLatin1(", "));
|
|
return newTitle + tail;
|
|
}
|
|
|
|
TQString Field::formatName(const TQString& name_, bool multiple_/*=true*/) {
|
|
static const TQRegExp spaceComma(TQString::fromLatin1("[\\s,]"));
|
|
static const TQString colonColon = TQString::fromLatin1("::");
|
|
// the ending look-ahead is so that a space is not added at the end
|
|
static const TQRegExp periodSpace(TQString::fromLatin1("\\.\\s*(?=.)"));
|
|
|
|
TQStringList entries;
|
|
if(multiple_) {
|
|
// split by semi-colon, optionally preceded or followed by white spacee
|
|
entries = TQStringList::split(s_delimiter, name_, false);
|
|
} else {
|
|
entries << name_;
|
|
}
|
|
|
|
TQRegExp lastWord;
|
|
lastWord.setCaseSensitive(false);
|
|
|
|
TQStringList names;
|
|
for(TQStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it) {
|
|
TQString name = *it;
|
|
// special case for 2-column tables, assume user never has '::' in a value
|
|
TQString tail;
|
|
if(name.find(colonColon) > -1) {
|
|
tail = colonColon + name.section(colonColon, 1);
|
|
name = name.section(colonColon, 0, 0);
|
|
}
|
|
name.replace(periodSpace, TQString::fromLatin1(". "));
|
|
if(Config::autoCapitalization()) {
|
|
name = capitalize(name);
|
|
}
|
|
|
|
// split the name by white space and commas
|
|
TQStringList words = TQStringList::split(spaceComma, name, false);
|
|
lastWord.setPattern(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));
|
|
|
|
// 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, TQString::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(TQChar(',')));
|
|
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(TQChar(',')));
|
|
words.remove(words.fromLast());
|
|
|
|
// update last word regexp
|
|
lastWord.setPattern(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));
|
|
|
|
// 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(TQChar('^') + TQRegExp::escape(words.last()) + TQChar('$'));
|
|
}
|
|
|
|
names << words.join(TQChar(' ')) + tail;
|
|
} else {
|
|
names << name + tail;
|
|
}
|
|
}
|
|
|
|
return names.join(TQString::fromLatin1("; "));
|
|
}
|
|
|
|
TQString Field::formatDate(const TQString& 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
|
|
TQStringList s = TQStringList::split('-', date_, true);
|
|
bool ok = true;
|
|
int y = s.count() > 0 ? s[0].toInt(&ok) : TQDate::currentDate().year();
|
|
if(ok) {
|
|
empty = false;
|
|
} else {
|
|
y = TQDate::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_ : TQString(TQDate(y, m, d).toString(Qt::ISODate));
|
|
// use short form
|
|
// return TDEGlobal::locale()->formatDate(date, true);
|
|
}
|
|
|
|
TQString Field::capitalize(TQString str_) {
|
|
// regexp to split words
|
|
static const TQRegExp rx(TQString::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;
|
|
|
|
TQRegExp wordRx;
|
|
wordRx.setCaseSensitive(false);
|
|
|
|
TQStringList notCap = Config::noCapitalizationList();
|
|
// don't capitalize the surname prefixes
|
|
// does this hold true everywhere other than english?
|
|
notCap += Config::surnamePrefixList();
|
|
|
|
TQString word = str_.mid(0, pos);
|
|
// now check to see if words starts with apostrophe list
|
|
for(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++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(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++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(TQChar('^') + TQRegExp::escape(word) + TQChar('$'));
|
|
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_;
|
|
}
|
|
|
|
TQString Field::sortKeyTitle(const TQString& title_) {
|
|
const TQString lower = title_.lower();
|
|
for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
|
|
// assume white space is already stripped
|
|
// the articles are already in lower-case
|
|
if(lower.startsWith(*it + TQChar(' '))) {
|
|
return title_.mid((*it).length() + 1);
|
|
}
|
|
}
|
|
// check apostrophes, too
|
|
for(TQStringList::ConstIterator it = s_articlesApos.constBegin(); it != s_articlesApos.constEnd(); ++it) {
|
|
if(lower.startsWith(*it)) {
|
|
return title_.mid((*it).length());
|
|
}
|
|
}
|
|
return title_;
|
|
}
|
|
|
|
// articles should all be in lower-case
|
|
void Field::articlesUpdated() {
|
|
s_articles = Config::articleList();
|
|
s_articlesApos.clear();
|
|
for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
|
|
if((*it).endsWith(TQChar('\''))) {
|
|
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
|
|
TQStringList Field::typeTitles() {
|
|
const FieldMap& map = typeMap();
|
|
TQStringList 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;
|
|
}
|
|
|
|
TQStringList Field::split(const TQString& string_, bool allowEmpty_) {
|
|
return string_.isEmpty() ? TQStringList() : TQStringList::split(s_delimiter, string_, allowEmpty_);
|
|
}
|
|
|
|
void Field::addAllowed(const TQString& value_) {
|
|
if(m_type != Choice) {
|
|
return;
|
|
}
|
|
if(m_allowed.findIndex(value_) == -1) {
|
|
m_allowed += value_;
|
|
}
|
|
}
|
|
|
|
void Field::setProperty(const TQString& key_, const TQString& 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(TQString::fromLatin1("rating")) != Latin1Literal("true")) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
int min = 10;
|
|
int max = 1;
|
|
bool ok;
|
|
const TQStringList& allow = field_->allowed();
|
|
for(TQStringList::ConstIterator it = allow.begin(); it != allow.end(); ++it) {
|
|
int n = Tellico::toUInt(*it, &ok);
|
|
if(!ok) {
|
|
return; // no need to convert
|
|
}
|
|
min = TQMIN(min, n);
|
|
max = TQMAX(max, n);
|
|
}
|
|
max = TQMIN(max, 10);
|
|
if(min >= max) {
|
|
min = 1;
|
|
max = 5;
|
|
}
|
|
field_->setProperty(TQString::fromLatin1("minimum"), TQString::number(min));
|
|
field_->setProperty(TQString::fromLatin1("maximum"), TQString::number(max));
|
|
field_->setProperty(TQString::fromLatin1("rating"), TQString());
|
|
field_->setType(Rating);
|
|
}
|
|
|
|
// static
|
|
long Field::getID() {
|
|
static long id = 0;
|
|
return ++id;
|
|
}
|
|
|
|
void Field::stripArticles(TQString& value) {
|
|
if(s_articles.isEmpty()) {
|
|
return;
|
|
}
|
|
for(TQStringList::ConstIterator it = s_articles.constBegin(); it != s_articles.constEnd(); ++it) {
|
|
TQRegExp rx(TQString::fromLatin1("\\b") + *it + TQString::fromLatin1("\\b"));
|
|
value.remove(rx);
|
|
}
|
|
value = value.stripWhiteSpace();
|
|
value.remove(TQRegExp(TQString::fromLatin1(",$")));
|
|
}
|